Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.75% covered (success)
93.75%
60 / 64
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ServerInformation
93.75% covered (success)
93.75%
60 / 64
60.00% covered (warning)
60.00%
3 / 5
20.10
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 for
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
 fromJsonLd
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
6.09
 bestCommonApiVersion
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 toJsonLd
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
4.00
1<?php
2
3declare(strict_types=1);
4
5namespace LambdaTwelve\OneRecord\Api;
6
7use LambdaTwelve\OneRecord\JsonLd\ExpandedDocument;
8use LambdaTwelve\OneRecord\JsonLd\JsonLd;
9use LambdaTwelve\OneRecord\JsonLd\JsonLdException;
10use LambdaTwelve\OneRecord\JsonLd\Nodes;
11use LambdaTwelve\OneRecord\Rdf\Iri;
12use LambdaTwelve\OneRecord\Spec\ApiVersion;
13use LambdaTwelve\OneRecord\Spec\DataModelVersion;
14use LambdaTwelve\OneRecord\Spec\Namespaces;
15use LambdaTwelve\OneRecord\Vocabulary\Generated\Api;
16
17/**
18 * api:ServerInformation: GET / on every ONE Record server. The client reads
19 * it to learn which API versions a partner speaks; the server writes it from
20 * its configuration.
21 */
22final readonly class ServerInformation
23{
24    /**
25     * @param list<string> $apiVersions
26     * @param list<string> $contentTypes
27     * @param list<string> $languages
28     * @param list<string> $ontologies unversioned ontology IRIs
29     * @param list<string> $ontologyVersions versioned ontology IRIs
30     * @param list<string> $encodings
31     */
32    public function __construct(
33        public Iri $serverEndpoint,
34        public Iri $dataHolder,
35        public array $apiVersions,
36        public array $contentTypes = ['application/ld+json'],
37        public array $languages = ['en-US'],
38        public array $ontologies = [Namespaces::CARGO_ONTOLOGY, Namespaces::API_ONTOLOGY],
39        public array $ontologyVersions = [],
40        public array $encodings = [],
41        public ?string $dataHolderType = null,
42        public ?Iri $id = null,
43    ) {}
44
45    /**
46     * @param list<ApiVersion> $apiVersions
47     * @param list<DataModelVersion> $dataModelVersions
48     * @param list<string> $languages
49     */
50    public static function for(Iri $serverEndpoint, Iri $dataHolder, array $apiVersions, array $dataModelVersions, array $languages = ['en-US'], ?string $dataHolderType = null): self
51    {
52        $ontologyVersions = [];
53        foreach ($dataModelVersions as $dm) {
54            $ontologyVersions[] = $dm->ontologyVersionIri();
55        }
56        foreach ($apiVersions as $api) {
57            $ontologyVersions[] = $api->ontologyVersionIri();
58        }
59
60        return new self(
61            $serverEndpoint,
62            $dataHolder,
63            array_map(static fn(ApiVersion $v): string => $v->value, $apiVersions),
64            languages: $languages,
65            ontologyVersions: $ontologyVersions,
66            dataHolderType: $dataHolderType,
67        );
68    }
69
70    /**
71     * @param string|array<string, mixed>|ExpandedDocument $document
72     */
73    public static function fromJsonLd(string|array|ExpandedDocument $document): self
74    {
75        try {
76            $expanded = $document instanceof ExpandedDocument ? $document : JsonLd::expand($document);
77        } catch (JsonLdException $e) {
78            throw InvalidDocument::because('Invalid body request', $e->getMessage());
79        }
80        if (!\in_array(Api::ServerInformation, $expanded->rootTypes(), true)) {
81            throw InvalidDocument::because('Invalid resource', 'The body is not api:ServerInformation.');
82        }
83        $graph = $expanded->graph;
84        $root = $expanded->root;
85        $holder = Nodes::iri($graph, $root, Api::hasDataHolder) ?? throw InvalidDocument::because('Invalid resource', 'api:hasDataHolder is required.', Api::hasDataHolder);
86        $endpoint = Nodes::iri($graph, $root, Api::hasServerEndpoint) ?? ($root instanceof Iri ? $root : throw InvalidDocument::because('Invalid resource', 'api:hasServerEndpoint is required.', Api::hasServerEndpoint));
87        $holderTypes = array_map(static fn(Iri $t): string => $t->value, $graph->typesOf($holder));
88
89        return new self(
90            $endpoint,
91            $holder,
92            Nodes::strings($graph, $root, Api::hasSupportedApiVersion),
93            Nodes::strings($graph, $root, Api::hasSupportedContentType),
94            Nodes::strings($graph, $root, Api::hasSupportedLanguage),
95            Nodes::strings($graph, $root, Api::hasSupportedOntology),
96            Nodes::strings($graph, $root, Api::hasSupportedOntologyVersion),
97            Nodes::strings($graph, $root, Api::hasSupportedEncoding),
98            $holderTypes[0] ?? null,
99            $root instanceof Iri ? $root : null,
100        );
101    }
102
103    /**
104     * The highest API version both this server and the partner support, or null.
105     *
106     * @param list<ApiVersion> $ours
107     */
108    public function bestCommonApiVersion(array $ours): ?ApiVersion
109    {
110        $theirs = [];
111        foreach ($this->apiVersions as $version) {
112            $parsed = ApiVersion::tryFromString($version);
113            if ($parsed !== null) {
114                $theirs[$parsed->value] = $parsed;
115            }
116        }
117        foreach (ApiVersion::allDescending() as $candidate) {
118            if (\in_array($candidate, $ours, true) && isset($theirs[$candidate->value])) {
119                return $candidate;
120            }
121        }
122
123        return null;
124    }
125
126    /**
127     * @return array<string, mixed>
128     */
129    public function toJsonLd(): array
130    {
131        $holder = Nodes::ref($this->dataHolder);
132        if ($this->dataHolderType !== null) {
133            $holder = ['@type' => Nodes::compact($this->dataHolderType), ...$holder];
134        }
135        $node = [
136            '@context' => [...Nodes::context(), 'api:hasServerEndpoint' => ['@type' => 'xsd:anyURI'], 'api:hasSupportedOntology' => ['@type' => 'xsd:anyURI'], 'api:hasSupportedOntologyVersion' => ['@type' => 'xsd:anyURI']],
137            '@id' => ($this->id ?? $this->serverEndpoint)->value,
138            '@type' => 'api:ServerInformation',
139            'api:hasDataHolder' => $holder,
140            'api:hasServerEndpoint' => $this->serverEndpoint->value,
141            'api:hasSupportedApiVersion' => $this->apiVersions,
142            'api:hasSupportedContentType' => $this->contentTypes,
143        ];
144        if ($this->encodings !== []) {
145            $node['api:hasSupportedEncoding'] = $this->encodings;
146        }
147        $node['api:hasSupportedLanguage'] = $this->languages;
148        $node['api:hasSupportedOntology'] = $this->ontologies;
149        if ($this->ontologyVersions !== []) {
150            $node['api:hasSupportedOntologyVersion'] = $this->ontologyVersions;
151        }
152
153        return $node;
154    }
155}