Skip to content

Context overflow. #381

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

Merged
merged 9 commits into from
Apr 9, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# jsonld ChangeLog

### Fixed
- Support recusrive scoped contexts.
- Various EARL report updates.

### Changed
- Better support for using a processed context for `null` and caching
`@import`.
- Don't set `@base` in initial context and don't resolve a relative IRI
when setting `@base` in a context, so that the document location can
be kept separate from the context itself.

## 3.0.1 - 2020-03-10

### Fixed
Expand Down
36 changes: 25 additions & 11 deletions lib/ContextResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const {
isObject: _isObject,
isString: _isString,
} = require('./types');
const {
asArray: _asArray
} = require('./util');
const {prependBase} = require('./url');
const JsonLdError = require('./JsonLdError');
const ResolvedContext = require('./ResolvedContext');
Expand All @@ -25,16 +28,16 @@ module.exports = class ContextResolver {
this.sharedCache = sharedCache;
}

async resolve({context, documentLoader, base, cycles = new Set()}) {
async resolve({
activeCtx, context, documentLoader, base, cycles = new Set()
}) {
// process `@context`
if(context && _isObject(context) && context['@context']) {
context = context['@context'];
}

// context is one or more contexts
if(!_isArray(context)) {
context = [context];
}
context = _asArray(context);

// resolve each context in the array
const allResolved = [];
Expand All @@ -45,7 +48,7 @@ module.exports = class ContextResolver {
if(!resolved) {
// not resolved yet, resolve
resolved = await this._resolveRemoteContext(
{url: ctx, documentLoader, base, cycles});
{activeCtx, url: ctx, documentLoader, base, cycles});
}

// add to output and continue
Expand Down Expand Up @@ -108,38 +111,49 @@ module.exports = class ContextResolver {
return resolved;
}

async _resolveRemoteContext({url, documentLoader, base, cycles}) {
async _resolveRemoteContext({activeCtx, url, documentLoader, base, cycles}) {
// resolve relative URL and fetch context
url = prependBase(base, url);
const {context, remoteDoc} = await this._fetchContext(
{url, documentLoader, cycles});
{activeCtx, url, documentLoader, cycles});

// update base according to remote document and resolve any relative URLs
base = remoteDoc.documentUrl || url;
_resolveContextUrls({context, base});

// resolve, cache, and return context
const resolved = await this.resolve(
{context, documentLoader, base, cycles});
{activeCtx, context, documentLoader, base, cycles});
this._cacheResolvedContext({key: url, resolved, tag: remoteDoc.tag});
return resolved;
}

async _fetchContext({url, documentLoader, cycles}) {
async _fetchContext({activeCtx, url, documentLoader, cycles}) {
// check for max context URLs fetched during a resolve operation
if(cycles.size > MAX_CONTEXT_URLS) {
throw new JsonLdError(
'Maximum number of @context URLs exceeded.',
'jsonld.ContextUrlError',
{code: 'loading remote context failed', max: MAX_CONTEXT_URLS});
{
code: activeCtx.processingMode === 'json-ld-1.0' ?
'loading remote context failed' :
'context overflow',
max: MAX_CONTEXT_URLS
});
}

// check for context URL cycle
// shortcut to avoid extra work that would eventually hit the max above
if(cycles.has(url)) {
throw new JsonLdError(
'Cyclical @context URLs detected.',
'jsonld.ContextUrlError',
{code: 'recursive context inclusion', url});
{
code: activeCtx.processingMode === 'json-ld-1.0' ?
'recursive context inclusion' :
'context overflow',
url
});
}

// track cycles
Expand Down
41 changes: 30 additions & 11 deletions lib/compact.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ const {
} = require('./context');

const {
removeBase: _removeBase
removeBase: _removeBase,
prependBase: _prependBase
} = require('./url');

const {
Expand Down Expand Up @@ -226,7 +227,8 @@ api.compact = async ({
expandedIri => api.compactIri({
activeCtx,
iri: expandedIri,
relativeTo: {vocab: false}
relativeTo: {vocab: false},
base: options.base
}));
if(compactedValue.length === 1) {
compactedValue = compactedValue[0];
Expand Down Expand Up @@ -485,7 +487,8 @@ api.compact = async ({
// index on @id or @index or alias of @none
const key = (container.includes('@id') ?
expandedItem['@id'] : expandedItem['@index']) ||
api.compactIri({activeCtx, iri: '@none', vocab: true});
api.compactIri({activeCtx, iri: '@none',
relativeTo: {vocab: true}});
// add compactedItem to map, using value of `@id` or a new blank
// node identifier

Expand Down Expand Up @@ -570,7 +573,7 @@ api.compact = async ({
const indexKey = _getContextValue(
activeCtx, itemActiveProperty, '@index') || '@index';
const containerKey = api.compactIri(
{activeCtx, iri: indexKey, vocab: true});
{activeCtx, iri: indexKey, relativeTo: {vocab: true}});
if(indexKey === '@index') {
key = expandedItem['@index'];
delete compactedItem[containerKey];
Expand All @@ -595,14 +598,15 @@ api.compact = async ({
}
}
} else if(container.includes('@id')) {
const idKey = api.compactIri({activeCtx, iri: '@id', vocab: true});
const idKey = api.compactIri({activeCtx, iri: '@id',
relativeTo: {vocab: true}});
key = compactedItem[idKey];
delete compactedItem[idKey];
} else if(container.includes('@type')) {
const typeKey = api.compactIri({
activeCtx,
iri: '@type',
vocab: true
relativeTo: {vocab: true}
});
let types;
[key, ...types] = _asArray(compactedItem[typeKey] || []);
Expand Down Expand Up @@ -634,7 +638,8 @@ api.compact = async ({

// if compacting this value which has no key, index on @none
if(!key) {
key = api.compactIri({activeCtx, iri: '@none', vocab: true});
key = api.compactIri({activeCtx, iri: '@none',
relativeTo: {vocab: true}});
}
// add compact value to map object using key from expanded value
// based on the container type
Expand Down Expand Up @@ -676,6 +681,7 @@ api.compact = async ({
* @param relativeTo options for how to compact IRIs:
* vocab: true to split after @vocab, false not to.
* @param reverse true if a reverse property is being compacted, false if not.
* @param base the absolute URL to use for compacting document-relative IRIs.
*
* @return the compacted term, prefix, keyword alias, or the original IRI.
*/
Expand All @@ -684,7 +690,8 @@ api.compactIri = ({
iri,
value = null,
relativeTo = {vocab: false},
reverse = false
reverse = false,
base = null
}) => {
// can't compact null
if(iri === null) {
Expand Down Expand Up @@ -933,7 +940,16 @@ api.compactIri = ({

// compact IRI relative to base
if(!relativeTo.vocab) {
return _removeBase(activeCtx['@base'], iri);
if('@base' in activeCtx) {
if(!activeCtx['@base']) {
// The None case preserves rval as potentially relative
return iri;
} else {
return _removeBase(_prependBase(base, activeCtx['@base']), iri);
}
} else {
return _removeBase(base, iri);
}
}

// return IRI as is
Expand Down Expand Up @@ -1050,8 +1066,11 @@ api.compactValue = ({activeCtx, activeProperty, value, options}) => {
const expandedProperty = _expandIri(activeCtx, activeProperty, {vocab: true},
options);
const type = _getContextValue(activeCtx, activeProperty, '@type');
const compacted = api.compactIri(
{activeCtx, iri: value['@id'], relativeTo: {vocab: type === '@vocab'}});
const compacted = api.compactIri({
activeCtx,
iri: value['@id'],
relativeTo: {vocab: type === '@vocab'},
base: options.base});

// compact to scalar
if(type === '@id' || type === '@vocab' || expandedProperty === '@graph') {
Expand Down
Loading