Skip to content

Commit a4a9d5f

Browse files
committed
Support recursive scoped contexts.
- Support 1.0 recursion error code. - Support 1.1 "context overflow" error code. - Allow recursion in scoped contexts.
1 parent a12a07b commit a4a9d5f

File tree

2 files changed

+54
-24
lines changed

2 files changed

+54
-24
lines changed

lib/ContextResolver.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ module.exports = class ContextResolver {
2828
this.sharedCache = sharedCache;
2929
}
3030

31-
async resolve({context, documentLoader, base, cycles = new Set()}) {
31+
async resolve({
32+
activeCtx, context, documentLoader, base, cycles = new Set()
33+
}) {
3234
// process `@context`
3335
if(context && _isObject(context) && context['@context']) {
3436
context = context['@context'];
@@ -46,7 +48,7 @@ module.exports = class ContextResolver {
4648
if(!resolved) {
4749
// not resolved yet, resolve
4850
resolved = await this._resolveRemoteContext(
49-
{url: ctx, documentLoader, base, cycles});
51+
{activeCtx, url: ctx, documentLoader, base, cycles});
5052
}
5153

5254
// add to output and continue
@@ -109,38 +111,49 @@ module.exports = class ContextResolver {
109111
return resolved;
110112
}
111113

112-
async _resolveRemoteContext({url, documentLoader, base, cycles}) {
114+
async _resolveRemoteContext({activeCtx, url, documentLoader, base, cycles}) {
113115
// resolve relative URL and fetch context
114116
url = prependBase(base, url);
115117
const {context, remoteDoc} = await this._fetchContext(
116-
{url, documentLoader, cycles});
118+
{activeCtx, url, documentLoader, cycles});
117119

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

122124
// resolve, cache, and return context
123125
const resolved = await this.resolve(
124-
{context, documentLoader, base, cycles});
126+
{activeCtx, context, documentLoader, base, cycles});
125127
this._cacheResolvedContext({key: url, resolved, tag: remoteDoc.tag});
126128
return resolved;
127129
}
128130

129-
async _fetchContext({url, documentLoader, cycles}) {
131+
async _fetchContext({activeCtx, url, documentLoader, cycles}) {
130132
// check for max context URLs fetched during a resolve operation
131133
if(cycles.size > MAX_CONTEXT_URLS) {
132134
throw new JsonLdError(
133135
'Maximum number of @context URLs exceeded.',
134136
'jsonld.ContextUrlError',
135-
{code: 'loading remote context failed', max: MAX_CONTEXT_URLS});
137+
{
138+
code: activeCtx.processingMode === 'json-ld-1.0' ?
139+
'loading remote context failed' :
140+
'context overflow',
141+
max: MAX_CONTEXT_URLS
142+
});
136143
}
137144

138145
// check for context URL cycle
146+
// shortcut to avoid extra work that would eventually hit the max above
139147
if(cycles.has(url)) {
140148
throw new JsonLdError(
141149
'Cyclical @context URLs detected.',
142150
'jsonld.ContextUrlError',
143-
{code: 'recursive context inclusion', url});
151+
{
152+
code: activeCtx.processingMode === 'json-ld-1.0' ?
153+
'recursive context inclusion' :
154+
'context overflow',
155+
url
156+
});
144157
}
145158

146159
// track cycles

lib/context.js

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ module.exports = api;
4747
api.process = async ({
4848
activeCtx, localCtx, options,
4949
propagate = true,
50-
overrideProtected = false
50+
overrideProtected = false,
51+
cycles = new Set()
5152
}) => {
5253
// normalize local context to an array of @context objects
5354
if(_isObject(localCtx) && '@context' in localCtx &&
@@ -63,6 +64,7 @@ api.process = async ({
6364

6465
// resolve contexts
6566
const resolved = await options.contextResolver.resolve({
67+
activeCtx,
6668
context: localCtx,
6769
documentLoader: options.documentLoader,
6870
base: options.base
@@ -310,6 +312,7 @@ api.process = async ({
310312

311313
// resolve contexts
312314
const resolvedImport = await options.contextResolver.resolve({
315+
activeCtx,
313316
context: value,
314317
documentLoader: options.documentLoader,
315318
base: options.base
@@ -355,23 +358,37 @@ api.process = async ({
355358
});
356359

357360
if(_isObject(ctx[key]) && '@context' in ctx[key]) {
361+
const keyCtx = ctx[key]['@context'];
362+
let process = true;
363+
if(_isString(keyCtx)) {
364+
const url = prependBase(options.base, keyCtx);
365+
// track processed contexts to avoid scoped context recursion
366+
if(cycles.has(url)) {
367+
process = false;
368+
} else {
369+
cycles.add(url);
370+
}
371+
}
358372
// parse context to validate
359-
try {
360-
await api.process({
361-
activeCtx: rval,
362-
localCtx: ctx[key]['@context'],
363-
overrideProtected: true,
364-
options
365-
});
366-
} catch(e) {
367-
throw new JsonLdError(
368-
'Invalid JSON-LD syntax; invalid scoped context.',
369-
'jsonld.SyntaxError',
370-
{
371-
code: 'invalid scoped context',
372-
context: ctx[key]['@context'],
373-
term: key
373+
if(process) {
374+
try {
375+
await api.process({
376+
activeCtx: rval,
377+
localCtx: ctx[key]['@context'],
378+
overrideProtected: true,
379+
options,
380+
cycles
374381
});
382+
} catch(e) {
383+
throw new JsonLdError(
384+
'Invalid JSON-LD syntax; invalid scoped context.',
385+
'jsonld.SyntaxError',
386+
{
387+
code: 'invalid scoped context',
388+
context: ctx[key]['@context'],
389+
term: key
390+
});
391+
}
375392
}
376393
}
377394
}

0 commit comments

Comments
 (0)