Skip to content

Commit

Permalink
feat: add json resolver (#400)
Browse files Browse the repository at this point in the history
The json codec is included in the multiformats module so no extra
deps are needed.

Similar to #397
  • Loading branch information
achingbrain authored Feb 2, 2024
1 parent 6fd12b9 commit 81e85c8
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 119 deletions.
59 changes: 2 additions & 57 deletions packages/ipfs-unixfs-exporter/src/resolvers/dag-cbor.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,12 @@
import * as dagCbor from '@ipld/dag-cbor'
import errCode from 'err-code'
import { CID } from 'multiformats/cid'
import { resolveObjectPath } from '../utils/resolve-object-path.js'
import type { Resolver } from '../index.js'

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

while (toResolve.length > 0) {
const prop = toResolve[0]

if (prop in subObject) {
// remove the bit of the path we have resolved
toResolve.shift()
subPath = `${subPath}/${prop}`

const subObjectCid = CID.asCID(subObject[prop])
if (subObjectCid != null) {
return {
entry: {
type: 'object',
name,
path,
cid,
node: block,
depth,
size: BigInt(block.length),
content: async function * () {
yield object
}
},
next: {
cid: subObjectCid,
name: prop,
path: subPath,
toResolve
}
}
}

subObject = subObject[prop]
} else {
// cannot resolve further
throw errCode(new Error(`No property named ${prop} found in cbor node ${cid}`), 'ERR_NO_PROP')
}
}

return {
entry: {
type: 'object',
name,
path,
cid,
node: block,
depth,
size: BigInt(block.length),
content: async function * () {
yield object
}
}
}
return resolveObjectPath(object, block, cid, name, path, toResolve, depth)
}

export default resolve
59 changes: 2 additions & 57 deletions packages/ipfs-unixfs-exporter/src/resolvers/dag-json.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,12 @@
import * as dagJson from '@ipld/dag-json'
import errCode from 'err-code'
import { CID } from 'multiformats/cid'
import { resolveObjectPath } from '../utils/resolve-object-path.js'
import type { Resolver } from '../index.js'

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

while (toResolve.length > 0) {
const prop = toResolve[0]

if (prop in subObject) {
// remove the bit of the path we have resolved
toResolve.shift()
subPath = `${subPath}/${prop}`

const subObjectCid = CID.asCID(subObject[prop])
if (subObjectCid != null) {
return {
entry: {
type: 'object',
name,
path,
cid,
node: block,
depth,
size: BigInt(block.length),
content: async function * () {
yield object
}
},
next: {
cid: subObjectCid,
name: prop,
path: subPath,
toResolve
}
}
}

subObject = subObject[prop]
} else {
// cannot resolve further
throw errCode(new Error(`No property named ${prop} found in dag-json node ${cid}`), 'ERR_NO_PROP')
}
}

return {
entry: {
type: 'object',
name,
path,
cid,
node: block,
depth,
size: BigInt(block.length),
content: async function * () {
yield object
}
}
}
return resolveObjectPath(object, block, cid, name, path, toResolve, depth)
}

export default resolve
5 changes: 4 additions & 1 deletion packages/ipfs-unixfs-exporter/src/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import * as dagCbor from '@ipld/dag-cbor'
import * as dagJson from '@ipld/dag-json'
import * as dagPb from '@ipld/dag-pb'
import errCode from 'err-code'
import * as json from 'multiformats/codecs/json'
import * as raw from 'multiformats/codecs/raw'
import { identity } from 'multiformats/hashes/identity'
import dagCborResolver from './dag-cbor.js'
import dagJsonResolver from './dag-json.js'
import identifyResolver from './identity.js'
import jsonResolver from './json.js'
import rawResolver from './raw.js'
import dagPbResolver from './unixfs-v1/index.js'
import type { Resolve, Resolver } from '../index.js'
Expand All @@ -16,7 +18,8 @@ const resolvers: Record<number, Resolver> = {
[raw.code]: rawResolver,
[dagCbor.code]: dagCborResolver,
[dagJson.code]: dagJsonResolver,
[identity.code]: identifyResolver
[identity.code]: identifyResolver,
[json.code]: jsonResolver
}

const resolve: Resolve = async (cid, name, path, toResolve, depth, blockstore, options) => {
Expand Down
12 changes: 12 additions & 0 deletions packages/ipfs-unixfs-exporter/src/resolvers/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as json from 'multiformats/codecs/json'
import { resolveObjectPath } from '../utils/resolve-object-path.js'
import type { Resolver } from '../index.js'

const resolve: Resolver = async (cid, name, path, toResolve, resolve, depth, blockstore, options) => {
const block = await blockstore.get(cid, options)
const object = json.decode<any>(block)

return resolveObjectPath(object, block, cid, name, path, toResolve, depth)
}

export default resolve
62 changes: 62 additions & 0 deletions packages/ipfs-unixfs-exporter/src/utils/resolve-object-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import errCode from 'err-code'
import { CID } from 'multiformats/cid'
import type { ResolveResult } from '../index.js'

export function resolveObjectPath (object: any, block: Uint8Array, cid: CID, name: string, path: string, toResolve: string[], depth: number): ResolveResult {
let subObject = object
let subPath = path

while (toResolve.length > 0) {
const prop = toResolve[0]

if (prop in subObject) {
// remove the bit of the path we have resolved
toResolve.shift()
subPath = `${subPath}/${prop}`

const subObjectCid = CID.asCID(subObject[prop])
if (subObjectCid != null) {
return {
entry: {
type: 'object',
name,
path,
cid,
node: block,
depth,
size: BigInt(block.length),
content: async function * () {
yield object
}
},
next: {
cid: subObjectCid,
name: prop,
path: subPath,
toResolve
}
}
}

subObject = subObject[prop]
} else {
// cannot resolve further
throw errCode(new Error(`No property named ${prop} found in node ${cid}`), 'ERR_NO_PROP')
}
}

return {
entry: {
type: 'object',
name,
path,
cid,
node: block,
depth,
size: BigInt(block.length),
content: async function * () {
yield object
}
}
}
}
42 changes: 38 additions & 4 deletions packages/ipfs-unixfs-exporter/test/exporter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import first from 'it-first'
import last from 'it-last'
import toBuffer from 'it-to-buffer'
import { CID } from 'multiformats/cid'
import * as json from 'multiformats/codecs/json'
import * as raw from 'multiformats/codecs/raw'
import { identity } from 'multiformats/hashes/identity'
import { sha256 } from 'multiformats/hashes/sha2'
Expand Down Expand Up @@ -978,7 +979,7 @@ describe('exporter', () => {
expect(data).to.deep.equal(smallFile)
})

it('errors when exporting a non-existent key from a cbor node', async () => {
it('errors when exporting a non-existent key from a dag-cbor node', async () => {
const node = {
foo: 'bar'
}
Expand All @@ -994,7 +995,7 @@ describe('exporter', () => {
}
})

it('exports a cbor node', async () => {
it('exports a dag-cbor node', async () => {
const node = {
foo: 'bar'
}
Expand All @@ -1011,7 +1012,7 @@ describe('exporter', () => {
return expect(first(exported.content())).to.eventually.deep.equal(node)
})

it('errors when exporting a non-existent key from a json node', async () => {
it('errors when exporting a non-existent key from a dag-json node', async () => {
const node = {
foo: 'bar'
}
Expand All @@ -1027,7 +1028,7 @@ describe('exporter', () => {
}
})

it('exports a json node', async () => {
it('exports a dag-json node', async () => {
const node = {
foo: 'bar'
}
Expand All @@ -1044,6 +1045,39 @@ describe('exporter', () => {
return expect(first(exported.content())).to.eventually.deep.equal(node)
})

it('errors when exporting a non-existent key from a json node', async () => {
const node = {
foo: 'bar'
}

const jsonBlock = json.encode(node)
const cid = CID.createV1(json.code, await sha256.digest(jsonBlock))
await block.put(cid, jsonBlock)

try {
await exporter(`${cid}/baz`, block)
} catch (err: any) {
expect(err.code).to.equal('ERR_NO_PROP')
}
})

it('exports a json node', async () => {
const node = {
foo: 'bar'
}

const jsonBlock = json.encode(node)
const cid = CID.createV1(json.code, await sha256.digest(jsonBlock))
await block.put(cid, jsonBlock)
const exported = await exporter(`${cid}`, block)

if (exported.type !== 'object') {
throw new Error('Unexpected type')
}

return expect(first(exported.content())).to.eventually.deep.equal(node)
})

it('errors when exporting a node with no resolver', async () => {
const cid = CID.create(1, 0x78, CID.parse('zdj7WkRPAX9o9nb9zPbXzwG7JEs78uyhwbUs8JSUayB98DWWY').multihash)

Expand Down

0 comments on commit 81e85c8

Please sign in to comment.