diff --git a/packages/ipfs-unixfs-exporter/src/resolvers/dag-cbor.ts b/packages/ipfs-unixfs-exporter/src/resolvers/dag-cbor.ts index a653a1ad..0631af98 100644 --- a/packages/ipfs-unixfs-exporter/src/resolvers/dag-cbor.ts +++ b/packages/ipfs-unixfs-exporter/src/resolvers/dag-cbor.ts @@ -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(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 diff --git a/packages/ipfs-unixfs-exporter/src/resolvers/dag-json.ts b/packages/ipfs-unixfs-exporter/src/resolvers/dag-json.ts index d701501d..c206d7af 100644 --- a/packages/ipfs-unixfs-exporter/src/resolvers/dag-json.ts +++ b/packages/ipfs-unixfs-exporter/src/resolvers/dag-json.ts @@ -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(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 diff --git a/packages/ipfs-unixfs-exporter/src/resolvers/index.ts b/packages/ipfs-unixfs-exporter/src/resolvers/index.ts index 93038d32..c314fa67 100644 --- a/packages/ipfs-unixfs-exporter/src/resolvers/index.ts +++ b/packages/ipfs-unixfs-exporter/src/resolvers/index.ts @@ -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' @@ -16,7 +18,8 @@ const resolvers: Record = { [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) => { diff --git a/packages/ipfs-unixfs-exporter/src/resolvers/json.ts b/packages/ipfs-unixfs-exporter/src/resolvers/json.ts new file mode 100644 index 00000000..90c5a174 --- /dev/null +++ b/packages/ipfs-unixfs-exporter/src/resolvers/json.ts @@ -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(block) + + return resolveObjectPath(object, block, cid, name, path, toResolve, depth) +} + +export default resolve diff --git a/packages/ipfs-unixfs-exporter/src/utils/resolve-object-path.ts b/packages/ipfs-unixfs-exporter/src/utils/resolve-object-path.ts new file mode 100644 index 00000000..addb7066 --- /dev/null +++ b/packages/ipfs-unixfs-exporter/src/utils/resolve-object-path.ts @@ -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 + } + } + } +} diff --git a/packages/ipfs-unixfs-exporter/test/exporter.spec.ts b/packages/ipfs-unixfs-exporter/test/exporter.spec.ts index a8c5aa74..c1bce7d9 100644 --- a/packages/ipfs-unixfs-exporter/test/exporter.spec.ts +++ b/packages/ipfs-unixfs-exporter/test/exporter.spec.ts @@ -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' @@ -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' } @@ -994,7 +995,7 @@ describe('exporter', () => { } }) - it('exports a cbor node', async () => { + it('exports a dag-cbor node', async () => { const node = { foo: 'bar' } @@ -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' } @@ -1027,7 +1028,7 @@ describe('exporter', () => { } }) - it('exports a json node', async () => { + it('exports a dag-json node', async () => { const node = { foo: 'bar' } @@ -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)