Skip to content

Handle singleton artifact objects per FAIR spec - #497

Open
kasparsd wants to merge 6 commits into
fairpm:release_1.5.0from
kasparsd:fix-package-artifacts-types
Open

kasparsd wants to merge 6 commits into
fairpm:release_1.5.0from
kasparsd:fix-package-artifacts-types

Conversation

@kasparsd

Copy link
Copy Markdown
Contributor

Fixes #496.

This changeset fixes package handling when a FAIR repository returns releases[].artifacts.package as a single object instead of an array.

Per the FAIR core spec, the artifacts map allows each artifact type value to be either an object or a list of objects. The plugin was normalizing neither shape internally and some consumer paths assumed array access, which caused failures such as Cannot use object of type stdClass as array during install/update flows.

This patch normalizes release artifact values to arrays at parse time in ReleaseDocument::from_data(), so downstream code can consume a consistent internal shape regardless of how the repository serialized the artifact. Consumer paths that read package download URLs or inspect package artifacts were updated accordingly, and regression tests were added to cover both valid spec shapes.

Spec references

  • FAIR Release Document: artifacts is a JSON object whose values “MUST be objects or lists of objects”
  • FAIR Core compatibility implication: clients must accept both valid shapes for artifact values, including artifacts.package

Testing

Added parser regression tests for:

  • singleton artifact object -> normalized to array
  • artifact array -> preserved as-is

kasparsd added 5 commits May 16, 2026 12:16
Signed-off-by: Kaspars Dambis <hi@kaspars.net>
Signed-off-by: Kaspars Dambis <hi@kaspars.net>
Signed-off-by: Kaspars Dambis <hi@kaspars.net>
Signed-off-by: Kaspars Dambis <hi@kaspars.net>
@github-actions

Copy link
Copy Markdown
Contributor

// Score artifacts based on match.
$score_artifact = function ( $artifact ) use ( $langs ) {
$score = 0;
$lang = strtolower( $artifact->lang ?? '' );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Account for missing lang property which is allowed per spec.

return false;
}
$artifact = pick_artifact_by_lang( $release->artifacts->package );
$artifact = isset( $release->artifacts->package ) ? pick_artifact_by_lang( $release->artifacts->package ) : null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return false for the check below?

@costdev costdev Sep 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionally speaking, null and false are both falsy so they'll both fail the truthy $artifact check below as expected.

Semantically speaking, this code is saying "The release artifact is..." (object|null), rather than "This has a release artifact" (bool), so null makes more sense to me when no release artifact exists.

Edit: pick_artifact_by_lang() returns stdClass|null, so this maintains a consistent fallback value type.

@Ipstenu Ipstenu added this to the 1.5.0 milestone Jul 9, 2026
@Ipstenu
Ipstenu changed the base branch from main to release_1.5.0 July 9, 2026 16:31
if ( $url === $release->artifacts->package[0]->url ) {
$content_type = $release->artifacts->package[0]->{'content-type'};
$artifact = array_find(
$release->artifacts->package ?? [],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR normalizes to an array of artifacts for a newly created ReleaseDocument via its ::from_data() method.

However, since the Transient API is a persistent cache mechanism, is may be possible that an existing pre-1.5.0 CACHE_RELEASE_PACKAGES transient value could still have the non-normalized package and therefore this array_find call could run on an object.

The normalization may need to run against $release->artifacts here before the array_find() call, and it may be worth abstracting the artifact normalization to a new static method on the ReleaseDocument class to reduce code duplication.


$selected = pick_artifact_by_lang( [ $first_artifact, $second_artifact ], 'de-DE' );

$this->assertContains( $selected, [ $first_artifact, $second_artifact ], 'Artifacts without lang should still return a valid artifact.' );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double-checking the reasoning why this checks that $selected is in an array rather than being a specific artifact. Is it because of inconsistent sorting across PHP versions or?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants