Skip to content

Commit 132ae2d

Browse files
committed
Compute omitted "@id" value in term definitions when possible.
1 parent 7640e1e commit 132ae2d

4 files changed

Lines changed: 197 additions & 41 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @digitalbazaar/cborld ChangeLog
22

3+
## 8.0.1 - 2025-05-dd
4+
5+
### Fixed
6+
- Ensure omitted `"@id"` values in term definitions are resolved using the
7+
term key if possible (for keys that are CURIEs or absolute URLs).
8+
39
## 8.0.0 - 2025-04-24
410

511
### Changed

lib/ActiveContext.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*!
2-
* Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved.
2+
* Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved.
33
*/
44
import {CborldError} from './CborldError.js';
55

@@ -155,19 +155,33 @@ function _resolveCurie({activeTermMap, context, possibleCurie}) {
155155
}
156156

157157
function _resolveCuries({activeTermMap, context, newTermMap}) {
158-
for(const def of newTermMap.values()) {
158+
for(const [key, def] of newTermMap.entries()) {
159159
const id = def['@id'];
160160
const type = def['@type'];
161161
if(id !== undefined) {
162162
def['@id'] = _resolveCurie({
163163
activeTermMap, context, possibleCurie: id
164164
});
165+
} else {
166+
// if `key` is a CURIE, then "@id" can be resolved to a value
167+
const resolved = _resolveCurie({
168+
activeTermMap, context, possibleCurie: key
169+
});
170+
if(resolved.includes(':')) {
171+
def['@id'] = resolved;
172+
}
165173
}
166174
if(type !== undefined) {
167175
def['@type'] = _resolveCurie({
168176
activeTermMap, context, possibleCurie: type
169177
});
170178
}
179+
if(typeof def['@id'] !== 'string') {
180+
throw new CborldError(
181+
'ERR_INVALID_TERM_DEFINITION',
182+
`Invalid JSON-LD term definition for "${key}"; the "@id" value ` +
183+
'could not be determined.');
184+
}
171185
}
172186
}
173187

lib/ContextLoader.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*!
2-
* Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved.
2+
* Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved.
33
*/
44
import {FIRST_CUSTOM_TERM_ID, KEYWORDS_TABLE, reverseMap} from './tables.js';
55
import {CborldError} from './CborldError.js';
@@ -114,11 +114,11 @@ export class ContextLoader {
114114
// normalize definition to an object
115115
if(typeof def === 'string') {
116116
def = {'@id': def};
117-
} else if(!(typeof def === 'object' && typeof def['@id'] === 'string')) {
117+
} else if(Object.prototype.toString.call(def) !== '[object Object]') {
118118
throw new CborldError(
119119
'ERR_INVALID_TERM_DEFINITION',
120120
`Invalid JSON-LD term definition for "${key}"; it must be ` +
121-
'a string or an object with "@id".');
121+
'a string or an object.');
122122
}
123123

124124
// set term definition

tests/encode.spec.js

Lines changed: 172 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -59,43 +59,41 @@ describe('cborld encode', () => {
5959
expect(cborldBytes).equalBytes('d9cb1d8202a0');
6060
});
6161

62-
it('should fail to encode with no typeTableLoader id found',
63-
async () => {
64-
const jsonldDocument = {};
65-
let result;
66-
let error;
67-
try {
68-
result = await encode({
69-
jsonldDocument,
70-
format: 'cbor-ld-1.0',
71-
registryEntryId: 2,
72-
typeTableLoader: _makeTypeTableLoader([])
73-
});
74-
} catch(e) {
75-
error = e;
76-
}
77-
expect(result).to.eql(undefined);
78-
expect(error?.code).to.eql('ERR_NO_TYPETABLE');
79-
});
62+
it('should fail to encode with no typeTableLoader id found', async () => {
63+
const jsonldDocument = {};
64+
let result;
65+
let error;
66+
try {
67+
result = await encode({
68+
jsonldDocument,
69+
format: 'cbor-ld-1.0',
70+
registryEntryId: 2,
71+
typeTableLoader: _makeTypeTableLoader([])
72+
});
73+
} catch(e) {
74+
error = e;
75+
}
76+
expect(result).to.eql(undefined);
77+
expect(error?.code).to.eql('ERR_NO_TYPETABLE');
78+
});
8079

81-
it('should fail with typeTable',
82-
async () => {
83-
const jsonldDocument = {};
84-
let result;
85-
let error;
86-
try {
87-
result = await encode({
88-
jsonldDocument,
89-
format: 'cbor-ld-1.0',
90-
registryEntryId: 1,
91-
typeTable: new Map()
92-
});
93-
} catch(e) {
94-
error = e;
95-
}
96-
expect(result).to.eql(undefined);
97-
expect(error?.name).to.eql('TypeError');
98-
});
80+
it('should fail with typeTable', async () => {
81+
const jsonldDocument = {};
82+
let result;
83+
let error;
84+
try {
85+
result = await encode({
86+
jsonldDocument,
87+
format: 'cbor-ld-1.0',
88+
registryEntryId: 1,
89+
typeTable: new Map()
90+
});
91+
} catch(e) {
92+
error = e;
93+
}
94+
expect(result).to.eql(undefined);
95+
expect(error?.name).to.eql('TypeError');
96+
});
9997

10098
it('should encode an empty JSON-LD Document', async () => {
10199
const jsonldDocument = {};
@@ -151,6 +149,100 @@ describe('cborld encode', () => {
151149
expect(cborldBytes).equalBytes('d9cb1d821a3b9aca00a0');
152150
});
153151

152+
it('should fail with non-object term definition', async () => {
153+
const CONTEXT_URL = 'urn:foo';
154+
const CONTEXT = {
155+
'@context': {
156+
foo: []
157+
}
158+
};
159+
const jsonldDocument = {
160+
'@context': CONTEXT_URL,
161+
foo: 'anything'
162+
};
163+
164+
const documentLoader = url => {
165+
if(url === CONTEXT_URL) {
166+
return {
167+
contextUrl: null,
168+
document: CONTEXT,
169+
documentUrl: url
170+
};
171+
}
172+
throw new Error(`Refused to load URL "${url}".`);
173+
};
174+
175+
const typeTable = new Map(TYPE_TABLE);
176+
177+
const contextTable = new Map(STRING_TABLE);
178+
contextTable.set(CONTEXT_URL, 0x8000);
179+
typeTable.set('context', contextTable);
180+
181+
let result;
182+
let error;
183+
try {
184+
result = await encode({
185+
jsonldDocument,
186+
format: 'cbor-ld-1.0',
187+
registryEntryId: 2,
188+
documentLoader,
189+
typeTableLoader: () => typeTable
190+
});
191+
} catch(e) {
192+
error = e;
193+
}
194+
expect(result).to.eql(undefined);
195+
expect(error?.name).to.eql('CborldError');
196+
});
197+
198+
it('should fail with non-CURIE term with no "@id"', async () => {
199+
const CONTEXT_URL = 'urn:foo';
200+
const CONTEXT = {
201+
'@context': {
202+
nonCurie: {
203+
'@type': 'urn:anything'
204+
}
205+
}
206+
};
207+
const jsonldDocument = {
208+
'@context': CONTEXT_URL,
209+
nonCurie: 'anything'
210+
};
211+
212+
const documentLoader = url => {
213+
if(url === CONTEXT_URL) {
214+
return {
215+
contextUrl: null,
216+
document: CONTEXT,
217+
documentUrl: url
218+
};
219+
}
220+
throw new Error(`Refused to load URL "${url}".`);
221+
};
222+
223+
const typeTable = new Map(TYPE_TABLE);
224+
225+
const contextTable = new Map(STRING_TABLE);
226+
contextTable.set(CONTEXT_URL, 0x8000);
227+
typeTable.set('context', contextTable);
228+
229+
let result;
230+
let error;
231+
try {
232+
result = await encode({
233+
jsonldDocument,
234+
format: 'cbor-ld-1.0',
235+
registryEntryId: 2,
236+
documentLoader,
237+
typeTableLoader: () => typeTable
238+
});
239+
} catch(e) {
240+
error = e;
241+
}
242+
expect(result).to.eql(undefined);
243+
expect(error?.name).to.eql('CborldError');
244+
});
245+
154246
it('should encode xsd dateTime when using a prefix', async () => {
155247
const CONTEXT_URL = 'urn:foo';
156248
const CONTEXT = {
@@ -195,6 +287,50 @@ describe('cborld encode', () => {
195287
expect(cborldBytes).equalBytes('d9cb1d8202a20019800018661a6070bb5f');
196288
});
197289

290+
it('should pass with CURIE term with no "@id"', async () => {
291+
const CONTEXT_URL = 'urn:foo';
292+
const CONTEXT = {
293+
'@context': {
294+
arbitraryPrefix: 'http://www.w3.org/2001/XMLSchema#',
295+
ex: 'https://test.example#',
296+
'ex:foo': {
297+
'@type': 'arbitraryPrefix:dateTime'
298+
}
299+
}
300+
};
301+
const date = '2021-04-09T20:38:55Z';
302+
const jsonldDocument = {
303+
'@context': CONTEXT_URL,
304+
'ex:foo': date
305+
};
306+
307+
const documentLoader = url => {
308+
if(url === CONTEXT_URL) {
309+
return {
310+
contextUrl: null,
311+
document: CONTEXT,
312+
documentUrl: url
313+
};
314+
}
315+
throw new Error(`Refused to load URL "${url}".`);
316+
};
317+
318+
const typeTable = new Map(TYPE_TABLE);
319+
320+
const contextTable = new Map(STRING_TABLE);
321+
contextTable.set(CONTEXT_URL, 0x8000);
322+
typeTable.set('context', contextTable);
323+
324+
const cborldBytes = await encode({
325+
jsonldDocument,
326+
format: 'cbor-ld-1.0',
327+
registryEntryId: 2,
328+
documentLoader,
329+
typeTableLoader: () => typeTable
330+
});
331+
expect(cborldBytes).equalBytes('d9cb1d8202a20019800018681a6070bb5f');
332+
});
333+
198334
it('should encode xsd dateTime with type table when possible', async () => {
199335
const CONTEXT_URL = 'urn:foo';
200336
const CONTEXT = {

0 commit comments

Comments
 (0)