Skip to content
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @digitalbazaar/cborld ChangeLog

## 8.0.1 - 2025-05-dd

### Fixed
- Ensure omitted `"@id"` values in term definitions are resolved using the
term key if possible (for keys that are CURIEs or absolute URLs).

## 8.0.0 - 2025-04-24

### Changed
Expand Down
18 changes: 16 additions & 2 deletions lib/ActiveContext.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
* Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved.
*/
import {CborldError} from './CborldError.js';

Expand Down Expand Up @@ -155,19 +155,33 @@ function _resolveCurie({activeTermMap, context, possibleCurie}) {
}

function _resolveCuries({activeTermMap, context, newTermMap}) {
for(const def of newTermMap.values()) {
for(const [key, def] of newTermMap.entries()) {
const id = def['@id'];
const type = def['@type'];
if(id !== undefined) {
def['@id'] = _resolveCurie({
activeTermMap, context, possibleCurie: id
});
} else {
// if `key` is a CURIE/absolute URL, then "@id" can be computed
const resolved = _resolveCurie({
activeTermMap, context, possibleCurie: key
});
if(resolved.includes(':')) {
def['@id'] = resolved;
}
}
if(type !== undefined) {
def['@type'] = _resolveCurie({
activeTermMap, context, possibleCurie: type
});
}
if(typeof def['@id'] !== 'string') {
throw new CborldError(
'ERR_INVALID_TERM_DEFINITION',
`Invalid JSON-LD term definition for "${key}"; the "@id" value ` +
'could not be determined.');
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions lib/ContextLoader.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
* Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved.
*/
import {FIRST_CUSTOM_TERM_ID, KEYWORDS_TABLE, reverseMap} from './tables.js';
import {CborldError} from './CborldError.js';
Expand Down Expand Up @@ -114,11 +114,11 @@ export class ContextLoader {
// normalize definition to an object
if(typeof def === 'string') {
def = {'@id': def};
} else if(!(typeof def === 'object' && typeof def['@id'] === 'string')) {
} else if(Object.prototype.toString.call(def) !== '[object Object]') {
throw new CborldError(
'ERR_INVALID_TERM_DEFINITION',
`Invalid JSON-LD term definition for "${key}"; it must be ` +
'a string or an object with "@id".');
'a string or an object.');
}

// set term definition
Expand Down
208 changes: 172 additions & 36 deletions tests/encode.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,43 +59,41 @@ describe('cborld encode', () => {
expect(cborldBytes).equalBytes('d9cb1d8202a0');
});

it('should fail to encode with no typeTableLoader id found',
async () => {
const jsonldDocument = {};
let result;
let error;
try {
result = await encode({
jsonldDocument,
format: 'cbor-ld-1.0',
registryEntryId: 2,
typeTableLoader: _makeTypeTableLoader([])
});
} catch(e) {
error = e;
}
expect(result).to.eql(undefined);
expect(error?.code).to.eql('ERR_NO_TYPETABLE');
});
it('should fail to encode with no typeTableLoader id found', async () => {
const jsonldDocument = {};
let result;
let error;
try {
result = await encode({
jsonldDocument,
format: 'cbor-ld-1.0',
registryEntryId: 2,
typeTableLoader: _makeTypeTableLoader([])
});
} catch(e) {
error = e;
}
expect(result).to.eql(undefined);
expect(error?.code).to.eql('ERR_NO_TYPETABLE');
});

it('should fail with typeTable',
async () => {
const jsonldDocument = {};
let result;
let error;
try {
result = await encode({
jsonldDocument,
format: 'cbor-ld-1.0',
registryEntryId: 1,
typeTable: new Map()
});
} catch(e) {
error = e;
}
expect(result).to.eql(undefined);
expect(error?.name).to.eql('TypeError');
});
it('should fail with typeTable', async () => {
const jsonldDocument = {};
let result;
let error;
try {
result = await encode({
jsonldDocument,
format: 'cbor-ld-1.0',
registryEntryId: 1,
typeTable: new Map()
});
} catch(e) {
error = e;
}
expect(result).to.eql(undefined);
expect(error?.name).to.eql('TypeError');
});

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

it('should fail with non-object term definition', async () => {
const CONTEXT_URL = 'urn:foo';
const CONTEXT = {
'@context': {
foo: []
}
};
const jsonldDocument = {
'@context': CONTEXT_URL,
foo: 'anything'
};

const documentLoader = url => {
if(url === CONTEXT_URL) {
return {
contextUrl: null,
document: CONTEXT,
documentUrl: url
};
}
throw new Error(`Refused to load URL "${url}".`);
};

const typeTable = new Map(TYPE_TABLE);

const contextTable = new Map(STRING_TABLE);
contextTable.set(CONTEXT_URL, 0x8000);
typeTable.set('context', contextTable);

let result;
let error;
try {
result = await encode({
jsonldDocument,
format: 'cbor-ld-1.0',
registryEntryId: 2,
documentLoader,
typeTableLoader: () => typeTable
});
} catch(e) {
error = e;
}
expect(result).to.eql(undefined);
expect(error?.name).to.eql('CborldError');
});

it('should fail with non-CURIE term with no "@id"', async () => {
const CONTEXT_URL = 'urn:foo';
const CONTEXT = {
'@context': {
nonCurie: {
'@type': 'urn:anything'
}
}
};
const jsonldDocument = {
'@context': CONTEXT_URL,
nonCurie: 'anything'
};

const documentLoader = url => {
if(url === CONTEXT_URL) {
return {
contextUrl: null,
document: CONTEXT,
documentUrl: url
};
}
throw new Error(`Refused to load URL "${url}".`);
};

const typeTable = new Map(TYPE_TABLE);

const contextTable = new Map(STRING_TABLE);
contextTable.set(CONTEXT_URL, 0x8000);
typeTable.set('context', contextTable);

let result;
let error;
try {
result = await encode({
jsonldDocument,
format: 'cbor-ld-1.0',
registryEntryId: 2,
documentLoader,
typeTableLoader: () => typeTable
});
} catch(e) {
error = e;
}
expect(result).to.eql(undefined);
expect(error?.name).to.eql('CborldError');
});

it('should encode xsd dateTime when using a prefix', async () => {
const CONTEXT_URL = 'urn:foo';
const CONTEXT = {
Expand Down Expand Up @@ -195,6 +287,50 @@ describe('cborld encode', () => {
expect(cborldBytes).equalBytes('d9cb1d8202a20019800018661a6070bb5f');
});

it('should pass with CURIE term with no "@id"', async () => {
const CONTEXT_URL = 'urn:foo';
const CONTEXT = {
'@context': {
arbitraryPrefix: 'http://www.w3.org/2001/XMLSchema#',
ex: 'https://test.example#',
'ex:foo': {
'@type': 'arbitraryPrefix:dateTime'
}
}
};
const date = '2021-04-09T20:38:55Z';
const jsonldDocument = {
'@context': CONTEXT_URL,
'ex:foo': date
};

const documentLoader = url => {
if(url === CONTEXT_URL) {
return {
contextUrl: null,
document: CONTEXT,
documentUrl: url
};
}
throw new Error(`Refused to load URL "${url}".`);
};

const typeTable = new Map(TYPE_TABLE);

const contextTable = new Map(STRING_TABLE);
contextTable.set(CONTEXT_URL, 0x8000);
typeTable.set('context', contextTable);

const cborldBytes = await encode({
jsonldDocument,
format: 'cbor-ld-1.0',
registryEntryId: 2,
documentLoader,
typeTableLoader: () => typeTable
});
expect(cborldBytes).equalBytes('d9cb1d8202a20019800018681a6070bb5f');
});

it('should encode xsd dateTime with type table when possible', async () => {
const CONTEXT_URL = 'urn:foo';
const CONTEXT = {
Expand Down