Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand expansion map to support calling when a relative IRI is detected #452

Merged
merged 16 commits into from
Jun 16, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions lib/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -1043,17 +1043,29 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) {

// prepend vocab
if(relativeTo.vocab && '@vocab' in activeCtx) {
return activeCtx['@vocab'] + value;
value = activeCtx['@vocab'] + value;
}

// prepend base
if(relativeTo.base && '@base' in activeCtx) {
else if(relativeTo.base && '@base' in activeCtx) {
tplooker marked this conversation as resolved.
Show resolved Hide resolved
if(activeCtx['@base']) {
// The null case preserves value as potentially relative
return prependBase(prependBase(options.base, activeCtx['@base']), value);
value = prependBase(prependBase(options.base, activeCtx['@base']), value);
}
} else if(relativeTo.base) {
return prependBase(options.base, value);
value = prependBase(options.base, value);
}

if(!_isAbsoluteIri(value) && options.expansionMap) {
// TODO: use `await` to support async
const expandedResult = options.expansionMap({
relativeIri: value,
activeCtx,
options
});
if(expandedResult !== undefined) {
value = expandedResult;
}
}

return value;
Expand Down
7 changes: 7 additions & 0 deletions lib/expand.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ api.expand = async ({
typeScopedContext = null,
expansionMap = () => undefined
}) => {

// Add expansion map to the processing options
tplooker marked this conversation as resolved.
Show resolved Hide resolved
options = { ...options, expansionMap };

tplooker marked this conversation as resolved.
Show resolved Hide resolved
// nothing to expand
if(element === null || element === undefined) {
return null;
Expand Down Expand Up @@ -420,6 +424,9 @@ async function _expandObject({
const nests = [];
let unexpandedValue;

// Add expansion map to the processing options
tplooker marked this conversation as resolved.
Show resolved Hide resolved
options = {...options, expansionMap};
dlongley marked this conversation as resolved.
Show resolved Hide resolved
tplooker marked this conversation as resolved.
Show resolved Hide resolved

// Figure out if this is the type for a JSON literal
const isJsonType = element[typeKey] &&
_expandIri(activeCtx,
Expand Down
2 changes: 1 addition & 1 deletion lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ api.parseLinkHeader = header => {
while((match = REGEX_LINK_HEADER_PARAMS.exec(params))) {
result[match[1]] = (match[2] === undefined) ? match[3] : match[2];
}
const rel = result['rel'] || '';
const rel = result.rel || '';
if(Array.isArray(rval[rel])) {
rval[rel].push(result);
} else if(rval.hasOwnProperty(rel)) {
Expand Down
263 changes: 263 additions & 0 deletions tests/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -478,3 +478,266 @@ describe('literal JSON', () => {
});
});
});

describe('expansionMap', () => {
it('should be called on un-mapped term', async () => {
tplooker marked this conversation as resolved.
Show resolved Hide resolved
const docWithUnMappedTerm = {
'@context': {
'definedTerm': 'https://example.com#definedTerm'
},
definedTerm: "is defined",
testUndefined: "is undefined"
};

let expansionMapCalled = false;
const expansionMap = info => {
if(info.unmappedProperty === 'testUndefined') {
expansionMapCalled = true;
}
};

await jsonld.expand(docWithUnMappedTerm, {expansionMap});

assert.equal(expansionMapCalled, true);
});

it('should be called on nested un-mapped term', async () => {
tplooker marked this conversation as resolved.
Show resolved Hide resolved
const docWithUnMappedTerm = {
'@context': {
'definedTerm': 'https://example.com#definedTerm'
},
definedTerm: {
testUndefined: "is undefined"
}
};

let expansionMapCalled = false;
const expansionMap = info => {
if(info.unmappedProperty === 'testUndefined') {
expansionMapCalled = true;
}
};

await jsonld.expand(docWithUnMappedTerm, {expansionMap});

assert.equal(expansionMapCalled, true);
});

it('should be called on relative iri for id term', async () => {
const docWithRelativeIriId = {
'@context': {
'definedTerm': 'https://example.com#definedTerm'
},
'@id': "relativeiri",
definedTerm: "is defined"
};

let expansionMapCalled = false;
const expansionMap = info => {
if(info.relativeIri === 'relativeiri') {
expansionMapCalled = true;
}
};

await jsonld.expand(docWithRelativeIriId, {expansionMap});

assert.equal(expansionMapCalled, true);
});

it('should be called on relative iri for id term (nested)', async () => {
const docWithRelativeIriId = {
'@context': {
'definedTerm': 'https://example.com#definedTerm'
},
'@id': "urn:absoluteIri",
definedTerm: {
'@id': "relativeiri"
}
};

let expansionMapCalled = false;
const expansionMap = info => {
if(info.relativeIri === 'relativeiri') {
expansionMapCalled = true;
}
};

await jsonld.expand(docWithRelativeIriId, {expansionMap});

assert.equal(expansionMapCalled, true);
});

it('should be called on relative iri for aliased id term', async () => {
const docWithRelativeIriId = {
'@context': {
'id': '@id',
'definedTerm': 'https://example.com#definedTerm'
},
'id': "relativeiri",
definedTerm: "is defined"
};

let expansionMapCalled = false;
const expansionMap = info => {
if(info.relativeIri === 'relativeiri') {
expansionMapCalled = true;
}
};

await jsonld.expand(docWithRelativeIriId, {expansionMap});

assert.equal(expansionMapCalled, true);
});

it('should be called on relative iri for type term', async () => {
const docWithRelativeIriId = {
'@context': {
'definedTerm': 'https://example.com#definedTerm'
},
'id': "urn:absoluteiri",
'@type': "relativeiri",
definedTerm: "is defined"
};

let expansionMapCalled = false;
const expansionMap = info => {
if(info.relativeIri === 'relativeiri') {
expansionMapCalled = true;
}
};

await jsonld.expand(docWithRelativeIriId, {expansionMap});

assert.equal(expansionMapCalled, true);
});
tplooker marked this conversation as resolved.
Show resolved Hide resolved

it('should be called on relative iri for \
type term with multiple relative iri types', async () => {
const docWithRelativeIriId = {
'@context': {
'definedTerm': 'https://example.com#definedTerm'
},
'id': "urn:absoluteiri",
'@type': ["relativeiri", "anotherRelativeiri" ],
definedTerm: "is defined"
};

let expansionMapCalledTimes = 0;
const expansionMap = info => {
if(info.relativeIri === 'relativeiri' ||
info.relativeIri === 'anotherRelativeiri') {
expansionMapCalledTimes++;
}
};

await jsonld.expand(docWithRelativeIriId, {expansionMap});

assert.equal(expansionMapCalledTimes, 3);
});

it('should be called on relative iri for \
type term with multiple types', async () => {
const docWithRelativeIriId = {
'@context': {
'definedTerm': 'https://example.com#definedTerm'
},
'id': "urn:absoluteiri",
'@type': ["relativeiri", "definedTerm" ],
definedTerm: "is defined"
};

let expansionMapCalled = false;
const expansionMap = info => {
if(info.relativeIri === 'relativeiri') {
expansionMapCalled = true;
}
};

await jsonld.expand(docWithRelativeIriId, {expansionMap});

assert.equal(expansionMapCalled, true);
});

it('should be called on relative iri for aliased type term', async () => {
const docWithRelativeIriId = {
'@context': {
'type': "@type",
'definedTerm': 'https://example.com#definedTerm'
},
'id': "urn:absoluteiri",
'type': "relativeiri",
definedTerm: "is defined"
};

let expansionMapCalled = false;
const expansionMap = info => {
if(info.relativeIri === 'relativeiri') {
expansionMapCalled = true;
}
};

await jsonld.expand(docWithRelativeIriId, {expansionMap});

assert.equal(expansionMapCalled, true);
});

it("should be called on relative iri when @base value is './'", async () => {
const docWithRelativeIriId = {
'@context': {
"@base": "./",
},
'@id': "relativeiri",
};

let expansionMapCalled = false;
const expansionMap = info => {
if(info.relativeIri === '/relativeiri') {
expansionMapCalled = true;
}
};

await jsonld.expand(docWithRelativeIriId, {expansionMap});

assert.equal(expansionMapCalled, true);
});

it("should be called on relative iri when @base value is './'", async () => {
const docWithRelativeIriId = {
'@context': {
"@base": "./",
},
'@id': "relativeiri",
};

let expansionMapCalled = false;
const expansionMap = info => {
if(info.relativeIri === '/relativeiri') {
expansionMapCalled = true;
}
};

await jsonld.expand(docWithRelativeIriId, {expansionMap});

assert.equal(expansionMapCalled, true);
});

it("should be called on relative iri when @vocab value is './'", async () => {
const docWithRelativeIriId = {
'@context': {
"@vocab": "./",
},
'@type': "relativeiri",
};

let expansionMapCalled = false;
const expansionMap = info => {
if(info.relativeIri === '/relativeiri') {
expansionMapCalled = true;
}
};

await jsonld.expand(docWithRelativeIriId, {expansionMap});

assert.equal(expansionMapCalled, true);
});
});
8 changes: 4 additions & 4 deletions tests/test-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ function addManifest(manifest, parent) {
*/
function addTest(manifest, test, tests) {
// expand @id and input base
const test_id = test['@id'] || test['id'];
const test_id = test['@id'] || test.id;
//var number = test_id.substr(2);
test['@id'] =
manifest.baseIri +
Expand Down Expand Up @@ -958,10 +958,10 @@ function createDocumentLoader(test) {
}

// If not JSON-LD, alternate may point there
if(linkHeaders['alternate'] &&
linkHeaders['alternate'].type == 'application/ld+json' &&
if(linkHeaders.alternate &&
linkHeaders.alternate.type == 'application/ld+json' &&
!(contentType || '').match(/^application\/(\w*\+)?json$/)) {
doc.documentUrl = prependBase(url, linkHeaders['alternate'].target);
doc.documentUrl = prependBase(url, linkHeaders.alternate.target);
}
}
}
Expand Down