Skip to content
This repository has been archived by the owner on Feb 12, 2024. It is now read-only.

Commit

Permalink
feat!: update ipfs for DAG GET API to match go-ipfs@0.10
Browse files Browse the repository at this point in the history
* Don't print new-line character
* Add --output-codec and allow printing of arbitrary output codecs
* Default to dag-json
* Remove funky old protonode wrapper type for dag-pb
* Keep data-enc but only enable it if you're printing a Bytes node
  • Loading branch information
rvagg committed Oct 16, 2021
1 parent 5158b21 commit 71d4718
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 76 deletions.
68 changes: 36 additions & 32 deletions packages/ipfs-cli/src/commands/dag/get.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import parseDuration from 'parse-duration'
import { toCidAndPath } from 'ipfs-core-utils/to-cid-and-path'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import {
stripControlCharacters,
makeEntriesPrintable,
escapeControlCharacters
} from '../../utils.js'
import * as dagPB from '@ipld/dag-pb'
import * as dagCBOR from '@ipld/dag-cbor'
import * as dagJSON from '@ipld/dag-json'
import * as raw from 'multiformats/codecs/raw'

/**
* @template T
* @typedef {import('multiformats/codecs/interface').BlockCodec<number, T>} BlockCodec
*/

const codecs = [dagCBOR, dagJSON, dagPB, raw].reduce((/** @type {Record<string, BlockCodec<any>>} */ m, codec) => {
m[codec.name] = codec
return m
}, /** @type {Record<string, BlockCodec<any>>} */ {})

export default {
command: 'get <cid path>',

Expand All @@ -21,16 +26,16 @@ export default {
type: 'boolean',
default: false
},
'cid-base': {
describe: 'Number base to display CIDs in.',
'output-codec': {
describe: 'Codec to encode data in before displaying.',
type: 'string',
default: 'base58btc'
choices: ['dag-json', 'dag-cbor', 'dag-pb', 'raw'],
default: 'dag-json'
},
'data-enc': {
describe: 'String encoding to display data in.',
describe: 'String encoding to display raw node data in if using "raw" output-codec.',
type: 'string',
choices: ['base16', 'base64', 'base58btc'],
default: 'base64'
choices: ['base16', 'base64', 'base58btc']
},
timeout: {
type: 'string',
Expand All @@ -42,12 +47,12 @@ export default {
* @param {object} argv
* @param {import('../../types').Context} argv.ctx
* @param {string} argv.cidpath
* @param {string} argv.cidBase
* @param {'dag-json' | 'dag-cbor' | 'dag-pb' | 'raw'} argv.outputCodec
* @param {'base16' | 'base64' | 'base58btc'} argv.dataEnc
* @param {boolean} argv.localResolve
* @param {number} argv.timeout
*/
async handler ({ ctx: { ipfs, print }, cidpath, cidBase, dataEnc, localResolve, timeout }) {
async handler ({ ctx: { ipfs, print }, cidpath, dataEnc, outputCodec, localResolve, timeout }) {
const options = {
localResolve,
timeout
Expand All @@ -74,27 +79,26 @@ export default {
}

const node = result.value
const base = await ipfs.bases.getBase(cidBase)

// TODO: just plain dag-json output by default, or use output-codec
if (cid.code === dagPB.code) {
/** @type {import('@ipld/dag-pb').PBNode} */
const dagNode = node

print(JSON.stringify({
data: dagNode.Data ? uint8ArrayToString(node.Data, dataEnc) : undefined,
links: (dagNode.Links || []).map(link => ({
Name: stripControlCharacters(link.Name),
Size: link.Tsize,
Cid: { '/': link.Hash.toString(base.encoder) }
}))
}))
} else if (cid.code === raw.code) {
print(uint8ArrayToString(node, dataEnc))
} else if (cid.code === dagCBOR.code || cid.code === dagJSON.code) {
print(JSON.stringify(makeEntriesPrintable(node, base)))
if (outputCodec === 'raw') {
if (!(node instanceof Uint8Array)) {
print('dag get cannot print a non-bytes node as "raw"')
return
}
if (dataEnc) {
print(uint8ArrayToString(node, dataEnc), false)
} else {
print.write(node)
}
} else {
print(escapeControlCharacters(node.toString()))
const codec = codecs[outputCodec]
if (!codec) {
print(`unsupported codec "${outputCodec}"`)
return
}
// TODO choose codec
const output = codec.encode(node)
print(output, false)
}
}
}
124 changes: 80 additions & 44 deletions packages/ipfs-cli/test/dag.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,10 @@ describe('dag', () => {
}

ipfs.dag.get.withArgs(rawCid, defaultOptions).returns(result)
ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)

const out = await cli(`dag get ${rawCid} --data-enc base16`, { ipfs })
const out = await cli(`dag get ${rawCid} --output-codec raw --data-enc base16`, { ipfs })

expect(out).to.equal(uint8ArrayToString(result.value, 'base16') + '\n')
expect(out).to.equal(uint8ArrayToString(result.value, 'base16'))
})

it('should get a dag-pb node', async () => {
Expand All @@ -67,14 +66,13 @@ describe('dag', () => {
}

ipfs.dag.get.withArgs(dagPbCid, defaultOptions).returns(result)
ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)

const out = await cli(`dag get ${dagPbCid}`, { ipfs })

expect(out).to.equal(`{"data":"AAED","links":[{"Name":"foo","Size":10,"Cid":{"/":"${dagCborCid.toString(base58btc)}"}}]}\n`)
expect(out).to.equal(`{"Data":{"/":{"bytes":"AAED"}},"Links":[{"Hash":{"/":"${dagCborCid.toString()}"},"Name":"foo","Tsize":10}]}`)
})

it('should get a dag-pb node and specify data encoding', async () => {
it('should get a dag-pb node as dag-pb', async () => {
const result = {
value: {
Data: Buffer.from([0, 1, 3]),
Expand All @@ -87,14 +85,13 @@ describe('dag', () => {
}

ipfs.dag.get.withArgs(dagPbCid, defaultOptions).returns(result)
ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)

const out = await cli(`dag get ${dagPbCid} --data-enc base16`, { ipfs })
const out = await cli(`dag get ${dagPbCid} --output-codec dag-pb`, { ipfs, raw: true })

expect(out).to.equal(`{"data":"000103","links":[{"Name":"foo","Size":10,"Cid":{"/":"${dagCborCid.toString(base58btc)}"}}]}\n`)
expect(out).to.deep.equal(Buffer.from('122d0a2401711220b80784f97f67ad80d52575d643044ffb37b20f8d4db32ae59e47b1ac68df20e01203666f6f180a0a03000103', 'hex'))
})

it('should get a dag-pb node and specify CID encoding', async () => {
it('should get a dag-pb node as dag-cbor', async () => {
const result = {
value: {
Data: Buffer.from([0, 1, 3]),
Expand All @@ -107,11 +104,55 @@ describe('dag', () => {
}

ipfs.dag.get.withArgs(dagPbCid, defaultOptions).returns(result)
ipfs.bases.getBase.withArgs('base64').returns(base64)

const out = await cli(`dag get ${dagPbCid} --cid-base base64`, { ipfs })
const out = await cli(`dag get ${dagPbCid} --output-codec dag-cbor`, { ipfs, raw: true })

expect(out).to.deep.equal(Buffer.from('a2644461746143000103654c696e6b7381a36448617368d82a58250001711220b80784f97f67ad80d52575d643044ffb37b20f8d4db32ae59e47b1ac68df20e0644e616d6563666f6f655473697a650a', 'hex'))
})

it('should fail to get a non bytes node with "raw"', async () => {
const result = {
value: {
Data: Buffer.from([0, 1, 3]),
Links: [{
Hash: dagCborCid,
Name: 'foo',
Tsize: 10
}]
}
}

ipfs.dag.get.withArgs(dagPbCid, defaultOptions).returns(result)

const out = await cli(`dag get ${dagPbCid} --output-codec raw --data-enc base16`, { ipfs })

expect(out).to.equal(`{"data":"AAED","links":[{"Name":"foo","Size":10,"Cid":{"/":"${dagCborCid.toString(base64)}"}}]}\n`)
expect(out).to.equal('dag get cannot print a non-bytes node as "raw"\n')
})

it('should get a bytes node of a non-bytes block with "raw"', async () => {
// in this instance we're pretending to path into a 'Data' property of a dag-pb block
const result = {
value: Buffer.from([0, 1, 3])
}

ipfs.dag.get.withArgs(dagPbCid, { ...defaultOptions, path: '/Data' }).returns(result)

const out = await cli(`dag get ${dagPbCid}/Data --output-codec raw --data-enc base16`, { ipfs })

expect(out).to.equal('000103')
})

it('should get raw bytes without data encoding', async () => {
// in this instance we're pretending to path into a 'Data' property of a dag-pb block
const result = {
value: Buffer.from([0, 1, 3])
}

ipfs.dag.get.withArgs(rawCid, defaultOptions).returns(result)

const out = await cli(`dag get ${rawCid} --output-codec raw`, { ipfs })

expect(out).to.equal(Buffer.from([0, 1, 3]).toString())
})

it('should get a dag-cbor node', async () => {
Expand All @@ -122,43 +163,39 @@ describe('dag', () => {
}

ipfs.dag.get.withArgs(dagCborCid, defaultOptions).returns(result)
ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)

const out = await cli(`dag get ${dagCborCid}`, { ipfs })

expect(out).to.equal('{"foo":"bar"}\n')
expect(out).to.equal('{"foo":"bar"}')
})

it('should get a dag-cbor node with a nested CID', async () => {
it('should get a dag-cbor node as dag-cbor', async () => {
const result = {
value: {
foo: 'bar',
baz: dagPbCid
foo: 'bar'
}
}

ipfs.dag.get.withArgs(dagCborCid, defaultOptions).returns(result)
ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)

const out = await cli(`dag get ${dagCborCid}`, { ipfs })
const out = await cli(`dag get ${dagCborCid} --output-codec dag-cbor`, { ipfs, raw: true })

expect(out).to.equal(`{"foo":"bar","baz":{"/":"${dagPbCid}"}}\n`)
expect(out).to.deep.equal(Buffer.from('a163666f6f63626172', 'hex'))
})

it('should get a dag-cbor node with a nested CID and change the encoding', async () => {
it('should get a dag-cbor node with a nested CID', async () => {
const result = {
value: {
foo: 'bar',
baz: rawCid
baz: dagPbCid
}
}

ipfs.dag.get.withArgs(dagCborCid, defaultOptions).returns(result)
ipfs.bases.getBase.withArgs('base64').returns(base64)

const out = await cli(`dag get ${dagCborCid} --cid-base=base64`, { ipfs })
const out = await cli(`dag get ${dagCborCid}`, { ipfs })

expect(out).to.equal(`{"foo":"bar","baz":{"/":"${rawCid.toString(base64)}"}}\n`)
expect(out).to.equal(`{"baz":{"/":"${dagPbCid}"},"foo":"bar"}`)
})

it('should get a node with a deep path', async () => {
Expand All @@ -172,9 +209,9 @@ describe('dag', () => {
path
}).returns(result)

const out = await cli(`dag get ${rawCid}${path} --data-enc base16`, { ipfs })
const out = await cli(`dag get ${rawCid}${path} --output-codec raw --data-enc base16`, { ipfs })

expect(out).to.be.eql(uint8ArrayToString(result.value, 'base16') + '\n')
expect(out).to.be.eql(uint8ArrayToString(result.value, 'base16'))
})

it('should get a node with a deep path and an ipfs prefix', async () => {
Expand All @@ -188,9 +225,9 @@ describe('dag', () => {
path
}).returns(result)

const out = await cli(`dag get /ipfs/${rawCid}${path} --data-enc base16`, { ipfs })
const out = await cli(`dag get /ipfs/${rawCid}${path} --output-codec raw --data-enc base16`, { ipfs })

expect(out).to.be.eql(uint8ArrayToString(result.value, 'base16') + '\n')
expect(out).to.be.eql(uint8ArrayToString(result.value, 'base16'))
})

it('should get a node with local resolve', async () => {
Expand All @@ -203,11 +240,11 @@ describe('dag', () => {
localResolve: true
}).returns(result)

const out = await cli(`dag get ${rawCid} --local-resolve --data-enc base16`, { ipfs })
const out = await cli(`dag get ${rawCid} --local-resolve --output-codec raw --data-enc base16`, { ipfs })

expect(out).to.include('resolving path within the node only\n')
expect(out).to.include('remainder path: n/a\n')
expect(out).to.include(uint8ArrayToString(result.value, 'base16') + '\n')
expect(out).to.include(uint8ArrayToString(result.value, 'base16'))
})

it('should get a node with a timeout', async () => {
Expand All @@ -220,9 +257,9 @@ describe('dag', () => {
timeout: 1000
}).returns(result)

const out = await cli(`dag get ${rawCid} --timeout=1s --data-enc base16`, { ipfs })
const out = await cli(`dag get ${rawCid} --timeout=1s --output-codec raw --data-enc base16`, { ipfs })

expect(out).to.be.eql(uint8ArrayToString(result.value, 'base16') + '\n')
expect(out).to.be.eql(uint8ArrayToString(result.value, 'base16'))
})

it('should strip control characters from dag-pb nodes', async () => {
Expand All @@ -237,14 +274,13 @@ describe('dag', () => {
}

ipfs.dag.get.withArgs(dagPbCid, defaultOptions).returns(result)
ipfs.bases.getBase.withArgs('base58btc').returns(base58btc)

const out = await cli(`dag get ${dagPbCid}`, { ipfs })

expect(out).to.equal(`{"links":[{"Name":"foo.txt","Size":9000,"Cid":{"/":"${dagPbCid.toString(base58btc)}"}}]}\n`)
expect(out).to.equal(`{"Links":[{"Hash":{"/":"${dagPbCid.toString(base58btc)}"},"Name":"foo\\b\\n\\t.txt","Tsize":9000}]}`)
})

it('should strip control characters from dag-cbor nodes', async () => {
it('should not strip control characters from dag-cbor nodes', async () => {
const result = {
value: {
'lo\nl': 'ok\t'
Expand All @@ -255,10 +291,10 @@ describe('dag', () => {

const out = await cli(`dag get ${dagCborCid}`, { ipfs })

expect(out).to.equal('{"lol":"ok"}\n')
expect(out).to.equal('{"lo\\nl":"ok\\t"}')
})

it('should strip control characters from dag-cbor string nodes', async () => {
it('should not strip control characters from dag-cbor string nodes', async () => {
const result = {
value: 'lo\nl'
}
Expand All @@ -267,10 +303,10 @@ describe('dag', () => {

const out = await cli(`dag get ${dagCborCid}`, { ipfs })

expect(out).to.equal('"lol"\n')
expect(out).to.equal('"lo\\nl"')
})

it('should strip control characters from dag-cbor array nodes', async () => {
it('should not strip control characters from dag-cbor array nodes', async () => {
const result = {
value: ['lo\nl']
}
Expand All @@ -279,10 +315,10 @@ describe('dag', () => {

const out = await cli(`dag get ${dagCborCid}`, { ipfs })

expect(out).to.equal('["lol"]\n')
expect(out).to.equal('["lo\\nl"]')
})

it('should strip control characters from dag-cbor nested array nodes', async () => {
it('should not strip control characters from dag-cbor nested array nodes', async () => {
const result = {
value: {
'lo\nl': ['ok\t']
Expand All @@ -293,7 +329,7 @@ describe('dag', () => {

const out = await cli(`dag get ${dagCborCid}`, { ipfs })

expect(out).to.equal('{"lol":["ok"]}\n')
expect(out).to.equal('{"lo\\nl":["ok\\t"]}')
})
})

Expand Down

0 comments on commit 71d4718

Please sign in to comment.