Skip to content

Commit ca09db4

Browse files
committed
Improve safe mode for @graph use cases.
- Better handling for `@graph` top-level empty objects, objects with only ids, relative references, etc.
1 parent b5df97d commit ca09db4

File tree

3 files changed

+308
-38
lines changed

3 files changed

+308
-38
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
updated.
1010
- Test on Node.js 20.x.
1111

12+
### Fixed
13+
- Improve safe mode for `@graph` use cases.
14+
1215
## 8.1.1 - 2023-02-25
1316

1417
### Fixed

lib/expand.js

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -372,48 +372,64 @@ api.expand = async ({
372372
// drop certain top-level objects that do not occur in lists
373373
if(_isObject(rval) &&
374374
!options.keepFreeFloatingNodes && !insideList &&
375-
(activeProperty === null || expandedActiveProperty === '@graph')) {
375+
(activeProperty === null ||
376+
expandedActiveProperty === '@graph' ||
377+
(_getContextValue(activeCtx, activeProperty, '@container') || [])
378+
.includes('@graph')
379+
)) {
376380
// drop empty object, top-level @value/@list, or object with only @id
377-
if(count === 0 || '@value' in rval || '@list' in rval ||
378-
(count === 1 && '@id' in rval)) {
379-
// FIXME
380-
if(options.eventHandler) {
381-
// FIXME: one event or diff event for empty, @v/@l, {@id}?
382-
let code;
383-
let message;
384-
if(count === 0) {
385-
code = 'empty object';
386-
message = 'Dropping empty object.';
387-
} else if('@value' in rval) {
388-
code = 'object with only @value';
389-
message = 'Dropping object with only @value.';
390-
} else if('@list' in rval) {
391-
code = 'object with only @list';
392-
message = 'Dropping object with only @list.';
393-
} else if(count === 1 && '@id' in rval) {
394-
code = 'object with only @id';
395-
message = 'Dropping object with only @id.';
396-
}
397-
_handleEvent({
398-
event: {
399-
type: ['JsonLdEvent'],
400-
code,
401-
level: 'warning',
402-
message,
403-
details: {
404-
value: rval
405-
}
406-
},
407-
options
408-
});
409-
}
410-
rval = null;
411-
}
381+
rval = _dropUnsafeObject({value: rval, count, options});
412382
}
413383

414384
return rval;
415385
};
416386

387+
/**
388+
* Drop empty object, top-level @value/@list, or object with only @id
389+
*/
390+
function _dropUnsafeObject({
391+
value,
392+
count,
393+
options
394+
}) {
395+
if(count === 0 || '@value' in value || '@list' in value ||
396+
(count === 1 && '@id' in value)) {
397+
// FIXME
398+
if(options.eventHandler) {
399+
// FIXME: one event or diff event for empty, @v/@l, {@id}?
400+
let code;
401+
let message;
402+
if(count === 0) {
403+
code = 'empty object';
404+
message = 'Dropping empty object.';
405+
} else if('@value' in value) {
406+
code = 'object with only @value';
407+
message = 'Dropping object with only @value.';
408+
} else if('@list' in value) {
409+
code = 'object with only @list';
410+
message = 'Dropping object with only @list.';
411+
} else if(count === 1 && '@id' in value) {
412+
code = 'object with only @id';
413+
message = 'Dropping object with only @id.';
414+
}
415+
_handleEvent({
416+
event: {
417+
type: ['JsonLdEvent'],
418+
code,
419+
level: 'warning',
420+
message,
421+
details: {
422+
value
423+
}
424+
},
425+
options
426+
});
427+
}
428+
return null;
429+
}
430+
return value;
431+
}
432+
417433
/**
418434
* Expand each key and value of element adding to result
419435
*
@@ -933,8 +949,18 @@ async function _expandObject({
933949
if(container.includes('@graph') &&
934950
!container.some(key => key === '@id' || key === '@index')) {
935951
// ensure expanded values are arrays
936-
expandedValue = _asArray(expandedValue)
937-
.map(v => ({'@graph': _asArray(v)}));
952+
// ensure an array
953+
expandedValue = _asArray(expandedValue);
954+
// check if needs to be dropped
955+
const count = Object.keys(expandedValue[0]).length;
956+
if(!options.isFrame && _dropUnsafeObject({
957+
value: expandedValue[0], count, options
958+
}) === null) {
959+
// skip adding and continue
960+
continue;
961+
}
962+
// convert to graph
963+
expandedValue = expandedValue.map(v => ({'@graph': _asArray(v)}));
938964
}
939965

940966
// FIXME: can this be merged with code above to simplify?

tests/misc.js

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,6 +1815,247 @@ _:b0 <ex:p> "[null]"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON> .
18151815
});
18161816
});
18171817

1818+
it('should emit for @graph with empty object (1)', async () => {
1819+
const input =
1820+
{
1821+
"@context": {
1822+
"p": {
1823+
"@id": "urn:p",
1824+
"@type": "@id",
1825+
"@container": "@graph"
1826+
}
1827+
},
1828+
"@id": "urn:id",
1829+
"p": {}
1830+
}
1831+
;
1832+
const expected = [];
1833+
1834+
await _test({
1835+
type: 'expand',
1836+
input,
1837+
expected,
1838+
eventCodeLog: [
1839+
'empty object',
1840+
'object with only @id'
1841+
],
1842+
testNotSafe: true
1843+
});
1844+
});
1845+
1846+
it('should emit for ok @graph with empty object (2)', async () => {
1847+
const input =
1848+
{
1849+
"@context": {
1850+
"p": {
1851+
"@id": "urn:p",
1852+
"@type": "@id",
1853+
"@container": "@graph"
1854+
},
1855+
"urn:t": {
1856+
"@type": "@id"
1857+
}
1858+
},
1859+
"@id": "urn:id",
1860+
"urn:t": "urn:id",
1861+
"p": {}
1862+
}
1863+
;
1864+
const expected =
1865+
[
1866+
{
1867+
"@id": "urn:id",
1868+
"urn:t": [
1869+
{
1870+
"@id": "urn:id"
1871+
}
1872+
]
1873+
}
1874+
]
1875+
;
1876+
1877+
await _test({
1878+
type: 'expand',
1879+
input,
1880+
expected,
1881+
eventCodeLog: [
1882+
'empty object'
1883+
],
1884+
testNotSafe: true
1885+
});
1886+
});
1887+
1888+
it('should emit for @graph with relative @id (1)', async () => {
1889+
const input =
1890+
{
1891+
"@context": {
1892+
"p": {
1893+
"@id": "urn:p",
1894+
"@type": "@id",
1895+
"@container": "@graph"
1896+
}
1897+
},
1898+
"@id": "urn:id",
1899+
"p": ["rel"]
1900+
}
1901+
;
1902+
const expected = [];
1903+
1904+
await _test({
1905+
type: 'expand',
1906+
input,
1907+
expected,
1908+
eventCodeLog: [
1909+
'object with only @id',
1910+
'object with only @id'
1911+
],
1912+
testNotSafe: true
1913+
});
1914+
});
1915+
1916+
it('should emit for @graph with relative @id (2)', async () => {
1917+
const input =
1918+
{
1919+
"@context": {
1920+
"p": {
1921+
"@id": "urn:p",
1922+
"@type": "@id",
1923+
"@container": "@graph"
1924+
},
1925+
"urn:t": {
1926+
"@type": "@id"
1927+
}
1928+
},
1929+
"@id": "urn:id",
1930+
"urn:t": "urn:id",
1931+
"p": ["rel"]
1932+
}
1933+
;
1934+
const expected =
1935+
[
1936+
{
1937+
"@id": "urn:id",
1938+
"urn:t": [
1939+
{
1940+
"@id": "urn:id"
1941+
}
1942+
]
1943+
}
1944+
]
1945+
;
1946+
1947+
await _test({
1948+
type: 'expand',
1949+
input,
1950+
expected,
1951+
eventCodeLog: [
1952+
'object with only @id',
1953+
],
1954+
testNotSafe: true
1955+
});
1956+
});
1957+
1958+
it('should emit for @graph with relative @id (3)', async () => {
1959+
const input =
1960+
{
1961+
"@context": {
1962+
"p": {
1963+
"@id": "urn:p",
1964+
"@type": "@id",
1965+
"@container": "@graph"
1966+
},
1967+
"urn:t": {
1968+
"@type": "@id"
1969+
}
1970+
},
1971+
"@id": "urn:id",
1972+
"urn:t": "urn:id",
1973+
"p": "rel"
1974+
}
1975+
;
1976+
const expected =
1977+
[
1978+
{
1979+
"@id": "urn:id",
1980+
"urn:t": [
1981+
{
1982+
"@id": "urn:id"
1983+
}
1984+
]
1985+
}
1986+
]
1987+
;
1988+
1989+
await _test({
1990+
type: 'expand',
1991+
input,
1992+
expected,
1993+
eventCodeLog: [
1994+
'object with only @id',
1995+
],
1996+
testNotSafe: true
1997+
});
1998+
});
1999+
2000+
it('should emit for @graph with relative @id (4)', async () => {
2001+
const input =
2002+
{
2003+
"@context": {
2004+
"p": {
2005+
"@id": "urn:p",
2006+
"@type": "@id",
2007+
"@container": "@graph"
2008+
},
2009+
"urn:t": {
2010+
"@type": "@id"
2011+
}
2012+
},
2013+
"@id": "urn:id",
2014+
"urn:t": "urn:id",
2015+
"p": {
2016+
"@id": "rel",
2017+
"urn:t": "urn:id2"
2018+
}
2019+
}
2020+
;
2021+
const expected =
2022+
[
2023+
{
2024+
"@id": "urn:id",
2025+
"urn:t": [
2026+
{
2027+
"@id": "urn:id"
2028+
}
2029+
],
2030+
"urn:p": [
2031+
{
2032+
"@graph": [
2033+
{
2034+
"@id": "rel",
2035+
"urn:t": [
2036+
{
2037+
"@id": "urn:id2"
2038+
}
2039+
]
2040+
}
2041+
]
2042+
}
2043+
]
2044+
}
2045+
]
2046+
;
2047+
2048+
await _test({
2049+
type: 'expand',
2050+
input,
2051+
expected,
2052+
eventCodeLog: [
2053+
'relative @id reference',
2054+
],
2055+
testNotSafe: true
2056+
});
2057+
});
2058+
18182059
it('should emit for null @value', async () => {
18192060
const input =
18202061
{

0 commit comments

Comments
 (0)