Skip to content

Commit c0ccc96

Browse files
committed
fix(hydra): resolve related class when hydra_prefix is false
When hydra_prefix is not set to true, owl:onProperty uses bare "member" which jsonld.expand resolves via @vocab (e.g. "http://localhost/docs.jsonld#member") instead of "http://www.w3.org/ns/hydra/core#member". This caused Strategy 1 in findRelatedClass to fail. For read-only resources without a POST operation, Strategy 2 also fails, resulting in "Cannot find the class related to..." errors. Relax the onProperty check from exact match to endsWith("#member") so both the correct hydra IRI and the @vocab-expanded IRI are accepted. Closes #147
1 parent 13315a1 commit c0ccc96

File tree

2 files changed

+174
-1
lines changed

2 files changed

+174
-1
lines changed

src/hydra/parseHydraDocumentation.test.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1981,3 +1981,175 @@ test("parse a Hydra documentation with owl:equivalentClass without onProperty hy
19811981
expect(bookConditionResource.name).toBe("book_conditions");
19821982
expect(bookConditionResource.title).toBe("BookCondition");
19831983
});
1984+
1985+
test("parse a Hydra documentation with read-only resource without hydra prefix (bare member)", async () => {
1986+
const readOnlyEntrypoint = {
1987+
"@context": {
1988+
"@vocab": "http://localhost/docs.jsonld#",
1989+
hydra: "http://www.w3.org/ns/hydra/core#",
1990+
greeting: {
1991+
"@id": "Entrypoint/greeting",
1992+
"@type": "@id",
1993+
},
1994+
},
1995+
"@id": "/",
1996+
"@type": "Entrypoint",
1997+
greeting: "/greetings",
1998+
};
1999+
2000+
// Simulate hydra_prefix: false — "member" is NOT prefixed with "hydra:"
2001+
// so jsonld.expand will resolve it via @vocab instead of the hydra context
2002+
const readOnlyDocs = {
2003+
"@context": {
2004+
"@vocab": "http://localhost/docs.jsonld#",
2005+
hydra: "http://www.w3.org/ns/hydra/core#",
2006+
rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
2007+
rdfs: "http://www.w3.org/2000/01/rdf-schema#",
2008+
xmls: "http://www.w3.org/2001/XMLSchema#",
2009+
owl: "http://www.w3.org/2002/07/owl#",
2010+
domain: {
2011+
"@id": "rdfs:domain",
2012+
"@type": "@id",
2013+
},
2014+
range: {
2015+
"@id": "rdfs:range",
2016+
"@type": "@id",
2017+
},
2018+
subClassOf: {
2019+
"@id": "rdfs:subClassOf",
2020+
"@type": "@id",
2021+
},
2022+
expects: {
2023+
"@id": "hydra:expects",
2024+
"@type": "@id",
2025+
},
2026+
returns: {
2027+
"@id": "hydra:returns",
2028+
"@type": "@id",
2029+
},
2030+
},
2031+
"@id": "/docs.jsonld",
2032+
"hydra:title": "API with read-only resource",
2033+
"hydra:description": "An API with a read-only resource and no hydra prefix",
2034+
"hydra:entrypoint": "/",
2035+
"hydra:supportedClass": [
2036+
{
2037+
"@id": "#Greeting",
2038+
"@type": "hydra:Class",
2039+
"rdfs:label": "Greeting",
2040+
"hydra:title": "Greeting",
2041+
"hydra:supportedProperty": [
2042+
{
2043+
"@type": "hydra:SupportedProperty",
2044+
"hydra:property": {
2045+
"@id": "#Greeting/message",
2046+
"@type": "rdf:Property",
2047+
"rdfs:label": "message",
2048+
domain: "#Greeting",
2049+
range: "xmls:string",
2050+
},
2051+
"hydra:title": "message",
2052+
"hydra:required": false,
2053+
"hydra:readable": true,
2054+
"hydra:writeable": false,
2055+
},
2056+
],
2057+
"hydra:supportedOperation": [
2058+
{
2059+
"@type": "hydra:Operation",
2060+
"hydra:method": "GET",
2061+
"hydra:title": "Retrieves Greeting resource.",
2062+
"rdfs:label": "Retrieves Greeting resource.",
2063+
returns: "#Greeting",
2064+
},
2065+
],
2066+
},
2067+
{
2068+
"@id": "#Entrypoint",
2069+
"@type": "hydra:Class",
2070+
"hydra:title": "The API entrypoint",
2071+
"hydra:supportedProperty": [
2072+
{
2073+
"@type": "hydra:SupportedProperty",
2074+
"hydra:property": {
2075+
"@id": "#Entrypoint/greeting",
2076+
"@type": "hydra:Link",
2077+
domain: "#Entrypoint",
2078+
"rdfs:label": "The collection of Greeting resources",
2079+
"rdfs:range": [
2080+
{ "@id": "hydra:PagedCollection" },
2081+
{
2082+
"owl:equivalentClass": {
2083+
// bare "member" without hydra: prefix — this is the bug trigger
2084+
"owl:onProperty": { "@id": "member" },
2085+
"owl:allValuesFrom": { "@id": "#Greeting" },
2086+
},
2087+
},
2088+
],
2089+
"hydra:supportedOperation": [
2090+
{
2091+
"@type": "hydra:Operation",
2092+
"hydra:method": "GET",
2093+
"hydra:title":
2094+
"Retrieves the collection of Greeting resources.",
2095+
"rdfs:label":
2096+
"Retrieves the collection of Greeting resources.",
2097+
returns: "hydra:PagedCollection",
2098+
},
2099+
],
2100+
},
2101+
"hydra:title": "The collection of Greeting resources",
2102+
"hydra:readable": true,
2103+
"hydra:writeable": false,
2104+
},
2105+
],
2106+
"hydra:supportedOperation": {
2107+
"@type": "hydra:Operation",
2108+
"hydra:method": "GET",
2109+
"rdfs:label": "The API entrypoint.",
2110+
returns: "#EntryPoint",
2111+
},
2112+
},
2113+
{
2114+
"@id": "#ConstraintViolation",
2115+
"@type": "hydra:Class",
2116+
"hydra:title": "A constraint violation",
2117+
"hydra:supportedProperty": [],
2118+
},
2119+
{
2120+
"@id": "#ConstraintViolationList",
2121+
"@type": "hydra:Class",
2122+
subClassOf: "hydra:Error",
2123+
"hydra:title": "A constraint violation list",
2124+
"hydra:supportedProperty": [],
2125+
},
2126+
],
2127+
};
2128+
2129+
server.use(
2130+
http.get("http://localhost", () => Response.json(readOnlyEntrypoint, init)),
2131+
http.get("http://localhost/docs.jsonld", () =>
2132+
Response.json(readOnlyDocs, init),
2133+
),
2134+
);
2135+
2136+
const data = await parseHydraDocumentation("http://localhost");
2137+
expect(data.status).toBe(200);
2138+
2139+
const greetingResource = data.api.resources?.find(
2140+
(r) => r.id === "http://localhost/docs.jsonld#Greeting",
2141+
);
2142+
2143+
expect(greetingResource).toBeDefined();
2144+
assert(greetingResource !== undefined);
2145+
expect(greetingResource.name).toBe("greetings");
2146+
expect(greetingResource.title).toBe("Greeting");
2147+
2148+
// Verify read-only: readable but not writable
2149+
assert(greetingResource.fields !== null);
2150+
assert(greetingResource.fields !== undefined);
2151+
expect(greetingResource.fields).toHaveLength(1);
2152+
expect(greetingResource.fields[0]?.name).toBe("message");
2153+
expect(greetingResource.readableFields).toHaveLength(1);
2154+
expect(greetingResource.writableFields).toHaveLength(0);
2155+
});

src/hydra/parseHydraDocumentation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ function findRelatedClass(
205205

206206
if (
207207
allValuesFrom &&
208-
onProperty === "http://www.w3.org/ns/hydra/core#member"
208+
typeof onProperty === "string" &&
209+
onProperty.endsWith("#member")
209210
) {
210211
return findSupportedClass(docs, allValuesFrom);
211212
}

0 commit comments

Comments
 (0)