diff --git a/src/hydra/parseHydraDocumentation.test.ts b/src/hydra/parseHydraDocumentation.test.ts index 1bc6ebd..c4611a1 100644 --- a/src/hydra/parseHydraDocumentation.test.ts +++ b/src/hydra/parseHydraDocumentation.test.ts @@ -2583,3 +2583,54 @@ test("parse a Hydra documentation with bare Link @type (without hydra prefix)", expect(reviewField.reference).toBe(reviewResource); expect(reviewField.embedded).toBeNull(); }); + +test("parse a Hydra documentation describing collections with hydra:memberAssertion", async () => { + const modernDocs = structuredClone(docs); + const entrypointClass = modernDocs["hydra:supportedClass"].find( + (supportedClass) => supportedClass["@id"] === "#Entrypoint", + ); + assert(entrypointClass !== undefined); + + const collectionProperties = entrypointClass["hydra:supportedProperty"] + .map((supportedProperty) => { + const property = supportedProperty["hydra:property"] as Record< + string, + unknown + >; + const ranges = property["rdfs:range"] as + | { + "owl:equivalentClass"?: { "owl:allValuesFrom": { "@id": string } }; + }[] + | undefined; + + return { + property, + memberType: ranges?.find((range) => range["owl:equivalentClass"])?.[ + "owl:equivalentClass" + ]?.["owl:allValuesFrom"]["@id"], + }; + }) + .filter(({ memberType }) => memberType !== undefined); + + for (const { property, memberType } of collectionProperties) { + delete property["rdfs:range"]; + property["range"] = "hydra:Collection"; + property["hydra:memberAssertion"] = { + "hydra:property": { "@id": "rdf:type" }, + "hydra:object": { "@id": memberType }, + }; + } + + server.use( + http.get("http://localhost", () => Response.json(entrypoint, init)), + http.get("http://localhost/docs.jsonld", () => + Response.json(modernDocs, init), + ), + ); + + const response = await parseHydraDocumentation("http://localhost"); + + expect(JSON.stringify(response.api, parsedJsonReplacer, 2)).toBe( + JSON.stringify(expectedApi, null, 2), + ); +}); diff --git a/src/hydra/parseHydraDocumentation.ts b/src/hydra/parseHydraDocumentation.ts index d2fda0a..83e9022 100644 --- a/src/hydra/parseHydraDocumentation.ts +++ b/src/hydra/parseHydraDocumentation.ts @@ -212,6 +212,30 @@ function findRelatedClass( } } + for (const memberAssertion of property[ + "http://www.w3.org/ns/hydra/core#memberAssertion" + ] ?? []) { + const assertedProperty = + memberAssertion["http://www.w3.org/ns/hydra/core#property"]?.[0]?.["@id"]; + if ( + assertedProperty !== "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" + ) { + continue; + } + + const assertedClass = + memberAssertion["http://www.w3.org/ns/hydra/core#object"]?.[0]?.["@id"]; + if (typeof assertedClass !== "string") { + continue; + } + + try { + return findSupportedClass(docs, assertedClass); + } catch { + continue; + } + } + // As a fallback, find an operation available on the property of the entrypoint returning the searched type (usually POST) for (const entrypointSupportedOperation of property[ "http://www.w3.org/ns/hydra/core#supportedOperation" diff --git a/src/hydra/types.ts b/src/hydra/types.ts index 44dcc38..c19b5d4 100644 --- a/src/hydra/types.ts +++ b/src/hydra/types.ts @@ -83,6 +83,18 @@ export interface ExpandedRdfProperty { }, ]; "http://www.w3.org/ns/hydra/core#supportedOperation"?: ExpandedOperation[]; + "http://www.w3.org/ns/hydra/core#memberAssertion"?: { + "http://www.w3.org/ns/hydra/core#property"?: [ + { + "@id": string; + }, + ]; + "http://www.w3.org/ns/hydra/core#object"?: [ + { + "@id": string; + }, + ]; + }[]; "http://www.w3.org/2002/07/owl#maxCardinality": [ { "@value": number;