Skip to content

Commit 81e85c8

Browse files
authored
feat: add json resolver (#400)
The json codec is included in the multiformats module so no extra deps are needed. Similar to #397
1 parent 6fd12b9 commit 81e85c8

File tree

6 files changed

+120
-119
lines changed

6 files changed

+120
-119
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,12 @@
11
import * as dagCbor from '@ipld/dag-cbor'
2-
import errCode from 'err-code'
3-
import { CID } from 'multiformats/cid'
2+
import { resolveObjectPath } from '../utils/resolve-object-path.js'
43
import type { Resolver } from '../index.js'
54

65
const resolve: Resolver = async (cid, name, path, toResolve, resolve, depth, blockstore, options) => {
76
const block = await blockstore.get(cid, options)
87
const object = dagCbor.decode<any>(block)
9-
let subObject = object
10-
let subPath = path
118

12-
while (toResolve.length > 0) {
13-
const prop = toResolve[0]
14-
15-
if (prop in subObject) {
16-
// remove the bit of the path we have resolved
17-
toResolve.shift()
18-
subPath = `${subPath}/${prop}`
19-
20-
const subObjectCid = CID.asCID(subObject[prop])
21-
if (subObjectCid != null) {
22-
return {
23-
entry: {
24-
type: 'object',
25-
name,
26-
path,
27-
cid,
28-
node: block,
29-
depth,
30-
size: BigInt(block.length),
31-
content: async function * () {
32-
yield object
33-
}
34-
},
35-
next: {
36-
cid: subObjectCid,
37-
name: prop,
38-
path: subPath,
39-
toResolve
40-
}
41-
}
42-
}
43-
44-
subObject = subObject[prop]
45-
} else {
46-
// cannot resolve further
47-
throw errCode(new Error(`No property named ${prop} found in cbor node ${cid}`), 'ERR_NO_PROP')
48-
}
49-
}
50-
51-
return {
52-
entry: {
53-
type: 'object',
54-
name,
55-
path,
56-
cid,
57-
node: block,
58-
depth,
59-
size: BigInt(block.length),
60-
content: async function * () {
61-
yield object
62-
}
63-
}
64-
}
9+
return resolveObjectPath(object, block, cid, name, path, toResolve, depth)
6510
}
6611

6712
export default resolve
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,12 @@
11
import * as dagJson from '@ipld/dag-json'
2-
import errCode from 'err-code'
3-
import { CID } from 'multiformats/cid'
2+
import { resolveObjectPath } from '../utils/resolve-object-path.js'
43
import type { Resolver } from '../index.js'
54

65
const resolve: Resolver = async (cid, name, path, toResolve, resolve, depth, blockstore, options) => {
76
const block = await blockstore.get(cid, options)
87
const object = dagJson.decode<any>(block)
9-
let subObject = object
10-
let subPath = path
118

12-
while (toResolve.length > 0) {
13-
const prop = toResolve[0]
14-
15-
if (prop in subObject) {
16-
// remove the bit of the path we have resolved
17-
toResolve.shift()
18-
subPath = `${subPath}/${prop}`
19-
20-
const subObjectCid = CID.asCID(subObject[prop])
21-
if (subObjectCid != null) {
22-
return {
23-
entry: {
24-
type: 'object',
25-
name,
26-
path,
27-
cid,
28-
node: block,
29-
depth,
30-
size: BigInt(block.length),
31-
content: async function * () {
32-
yield object
33-
}
34-
},
35-
next: {
36-
cid: subObjectCid,
37-
name: prop,
38-
path: subPath,
39-
toResolve
40-
}
41-
}
42-
}
43-
44-
subObject = subObject[prop]
45-
} else {
46-
// cannot resolve further
47-
throw errCode(new Error(`No property named ${prop} found in dag-json node ${cid}`), 'ERR_NO_PROP')
48-
}
49-
}
50-
51-
return {
52-
entry: {
53-
type: 'object',
54-
name,
55-
path,
56-
cid,
57-
node: block,
58-
depth,
59-
size: BigInt(block.length),
60-
content: async function * () {
61-
yield object
62-
}
63-
}
64-
}
9+
return resolveObjectPath(object, block, cid, name, path, toResolve, depth)
6510
}
6611

6712
export default resolve

packages/ipfs-unixfs-exporter/src/resolvers/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import * as dagCbor from '@ipld/dag-cbor'
22
import * as dagJson from '@ipld/dag-json'
33
import * as dagPb from '@ipld/dag-pb'
44
import errCode from 'err-code'
5+
import * as json from 'multiformats/codecs/json'
56
import * as raw from 'multiformats/codecs/raw'
67
import { identity } from 'multiformats/hashes/identity'
78
import dagCborResolver from './dag-cbor.js'
89
import dagJsonResolver from './dag-json.js'
910
import identifyResolver from './identity.js'
11+
import jsonResolver from './json.js'
1012
import rawResolver from './raw.js'
1113
import dagPbResolver from './unixfs-v1/index.js'
1214
import type { Resolve, Resolver } from '../index.js'
@@ -16,7 +18,8 @@ const resolvers: Record<number, Resolver> = {
1618
[raw.code]: rawResolver,
1719
[dagCbor.code]: dagCborResolver,
1820
[dagJson.code]: dagJsonResolver,
19-
[identity.code]: identifyResolver
21+
[identity.code]: identifyResolver,
22+
[json.code]: jsonResolver
2023
}
2124

2225
const resolve: Resolve = async (cid, name, path, toResolve, depth, blockstore, options) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as json from 'multiformats/codecs/json'
2+
import { resolveObjectPath } from '../utils/resolve-object-path.js'
3+
import type { Resolver } from '../index.js'
4+
5+
const resolve: Resolver = async (cid, name, path, toResolve, resolve, depth, blockstore, options) => {
6+
const block = await blockstore.get(cid, options)
7+
const object = json.decode<any>(block)
8+
9+
return resolveObjectPath(object, block, cid, name, path, toResolve, depth)
10+
}
11+
12+
export default resolve
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import errCode from 'err-code'
2+
import { CID } from 'multiformats/cid'
3+
import type { ResolveResult } from '../index.js'
4+
5+
export function resolveObjectPath (object: any, block: Uint8Array, cid: CID, name: string, path: string, toResolve: string[], depth: number): ResolveResult {
6+
let subObject = object
7+
let subPath = path
8+
9+
while (toResolve.length > 0) {
10+
const prop = toResolve[0]
11+
12+
if (prop in subObject) {
13+
// remove the bit of the path we have resolved
14+
toResolve.shift()
15+
subPath = `${subPath}/${prop}`
16+
17+
const subObjectCid = CID.asCID(subObject[prop])
18+
if (subObjectCid != null) {
19+
return {
20+
entry: {
21+
type: 'object',
22+
name,
23+
path,
24+
cid,
25+
node: block,
26+
depth,
27+
size: BigInt(block.length),
28+
content: async function * () {
29+
yield object
30+
}
31+
},
32+
next: {
33+
cid: subObjectCid,
34+
name: prop,
35+
path: subPath,
36+
toResolve
37+
}
38+
}
39+
}
40+
41+
subObject = subObject[prop]
42+
} else {
43+
// cannot resolve further
44+
throw errCode(new Error(`No property named ${prop} found in node ${cid}`), 'ERR_NO_PROP')
45+
}
46+
}
47+
48+
return {
49+
entry: {
50+
type: 'object',
51+
name,
52+
path,
53+
cid,
54+
node: block,
55+
depth,
56+
size: BigInt(block.length),
57+
content: async function * () {
58+
yield object
59+
}
60+
}
61+
}
62+
}

packages/ipfs-unixfs-exporter/test/exporter.spec.ts

+38-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import first from 'it-first'
1717
import last from 'it-last'
1818
import toBuffer from 'it-to-buffer'
1919
import { CID } from 'multiformats/cid'
20+
import * as json from 'multiformats/codecs/json'
2021
import * as raw from 'multiformats/codecs/raw'
2122
import { identity } from 'multiformats/hashes/identity'
2223
import { sha256 } from 'multiformats/hashes/sha2'
@@ -978,7 +979,7 @@ describe('exporter', () => {
978979
expect(data).to.deep.equal(smallFile)
979980
})
980981

981-
it('errors when exporting a non-existent key from a cbor node', async () => {
982+
it('errors when exporting a non-existent key from a dag-cbor node', async () => {
982983
const node = {
983984
foo: 'bar'
984985
}
@@ -994,7 +995,7 @@ describe('exporter', () => {
994995
}
995996
})
996997

997-
it('exports a cbor node', async () => {
998+
it('exports a dag-cbor node', async () => {
998999
const node = {
9991000
foo: 'bar'
10001001
}
@@ -1011,7 +1012,7 @@ describe('exporter', () => {
10111012
return expect(first(exported.content())).to.eventually.deep.equal(node)
10121013
})
10131014

1014-
it('errors when exporting a non-existent key from a json node', async () => {
1015+
it('errors when exporting a non-existent key from a dag-json node', async () => {
10151016
const node = {
10161017
foo: 'bar'
10171018
}
@@ -1027,7 +1028,7 @@ describe('exporter', () => {
10271028
}
10281029
})
10291030

1030-
it('exports a json node', async () => {
1031+
it('exports a dag-json node', async () => {
10311032
const node = {
10321033
foo: 'bar'
10331034
}
@@ -1044,6 +1045,39 @@ describe('exporter', () => {
10441045
return expect(first(exported.content())).to.eventually.deep.equal(node)
10451046
})
10461047

1048+
it('errors when exporting a non-existent key from a json node', async () => {
1049+
const node = {
1050+
foo: 'bar'
1051+
}
1052+
1053+
const jsonBlock = json.encode(node)
1054+
const cid = CID.createV1(json.code, await sha256.digest(jsonBlock))
1055+
await block.put(cid, jsonBlock)
1056+
1057+
try {
1058+
await exporter(`${cid}/baz`, block)
1059+
} catch (err: any) {
1060+
expect(err.code).to.equal('ERR_NO_PROP')
1061+
}
1062+
})
1063+
1064+
it('exports a json node', async () => {
1065+
const node = {
1066+
foo: 'bar'
1067+
}
1068+
1069+
const jsonBlock = json.encode(node)
1070+
const cid = CID.createV1(json.code, await sha256.digest(jsonBlock))
1071+
await block.put(cid, jsonBlock)
1072+
const exported = await exporter(`${cid}`, block)
1073+
1074+
if (exported.type !== 'object') {
1075+
throw new Error('Unexpected type')
1076+
}
1077+
1078+
return expect(first(exported.content())).to.eventually.deep.equal(node)
1079+
})
1080+
10471081
it('errors when exporting a node with no resolver', async () => {
10481082
const cid = CID.create(1, 0x78, CID.parse('zdj7WkRPAX9o9nb9zPbXzwG7JEs78uyhwbUs8JSUayB98DWWY').multihash)
10491083

0 commit comments

Comments
 (0)