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

feat: add partial implementation for ipfs.resolve #1455

Merged
merged 10 commits into from
Aug 22, 2018
24 changes: 24 additions & 0 deletions src/cli/commands/resolve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict'

const print = require('../utils').print

module.exports = {
command: 'resolve <name>',

description: 'Resolve the value of names to IPFS',

builder: {
recursive: {
alias: 'r',
type: 'boolean',
default: false
}
},

handler (argv) {
argv.ipfs.resolve(argv.name, { recursive: argv.recursive }, (err, res) => {
if (err) throw err
print(res)
})
}
}
1 change: 1 addition & 0 deletions src/core/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ exports.dns = require('./dns')
exports.key = require('./key')
exports.stats = require('./stats')
exports.mfs = require('./mfs')
exports.resolve = require('./resolve')
86 changes: 86 additions & 0 deletions src/core/components/resolve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use strict'

const promisify = require('promisify-es6')
const isIpfs = require('is-ipfs')
const setImmediate = require('async/setImmediate')
const doUntil = require('async/doUntil')
const CID = require('cids')

module.exports = (self) => {
return promisify((name, opts, cb) => {
if (typeof opts === 'function') {
cb = opts
opts = {}
}

opts = opts || {}

if (!isIpfs.path(name)) {
return setImmediate(() => cb(new Error('invalid argument')))
}

// TODO remove this and update subsequent code when IPNS is implemented
if (!isIpfs.ipfsPath(name)) {
return setImmediate(() => cb(new Error('resolve non-IPFS names is not implemented')))
}

const split = name.split('/') // ['', 'ipfs', 'hash', ...path]
const cid = new CID(split[2])

if (split.length === 3) {
return setImmediate(() => cb(null, name))
}

const path = split.slice(3).join('/')

resolve(cid, path, (err, cid) => {
if (err) return cb(err)
if (!cid) return cb(new Error('found non-link at given path'))
cb(null, `/ipfs/${cid.toBaseEncodedString(opts.cidBase)}`)
})
})

// Resolve the given CID + path to a CID.
function resolve (cid, path, callback) {
let value

doUntil(
(cb) => {
self.block.get(cid, (err, block) => {
if (err) return cb(err)

const r = self._ipld.resolvers[cid.codec]

if (!r) {
return cb(new Error(`No resolver found for codec "${cid.codec}"`))
}

r.resolver.resolve(block.data, path, (err, result) => {
if (err) return cb(err)
value = result.value
path = result.remainderPath
cb()
})
})
},
() => {
const endReached = !path || path === '/'

if (endReached) {
return true
}

if (value) {
cid = new CID(value['/'])
}

return false
},
(err) => {
if (err) return callback(err)
if (value && value['/']) return callback(null, new CID(value['/']))
callback()
}
)
}
}
1 change: 1 addition & 0 deletions src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class IPFS extends EventEmitter {
this.dns = components.dns(this)
this.key = components.key(this)
this.stats = components.stats(this)
this.resolve = components.resolve(this)

if (this._options.EXPERIMENTAL.pubsub) {
this.log('EXPERIMENTAL pubsub is enabled')
Expand Down
1 change: 1 addition & 0 deletions src/http/api/resources/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ exports.pubsub = require('./pubsub')
exports.dns = require('./dns')
exports.key = require('./key')
exports.stats = require('./stats')
exports.resolve = require('./resolve')
37 changes: 37 additions & 0 deletions src/http/api/resources/resolve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict'

const Joi = require('joi')
const debug = require('debug')

const log = debug('jsipfs:http-api:resolve')
log.error = debug('jsipfs:http-api:resolve:error')

module.exports = {
validate: {
query: Joi.object().keys({
r: Joi.alternatives()
.when('recursive', {
is: Joi.any().exist(),
then: Joi.any().forbidden(),
otherwise: Joi.boolean()
}),
recursive: Joi.boolean(),
arg: Joi.string().required()
}).unknown()
},
handler (request, reply) {
const ipfs = request.server.app.ipfs
const name = request.query.arg
const recursive = request.query.r || request.query.recursive || false

log(name, { recursive })

ipfs.resolve(name, { recursive }, (err, res) => {
if (err) {
log.error(err)
return reply({ Message: err.message, Code: 0 }).code(500)
}
reply({ Path: res })
})
}
}
1 change: 1 addition & 0 deletions src/http/api/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ module.exports = (server) => {
require('./dns')(server)
require('./key')(server)
require('./stats')(server)
require('./resolve')(server)
}
16 changes: 16 additions & 0 deletions src/http/api/routes/resolve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict'

const resources = require('./../resources')

module.exports = (server) => {
const api = server.select('API')

api.route({
method: '*',
path: '/api/v0/resolve',
config: {
handler: resources.resolve.handler,
validate: resources.resolve.validate
}
})
}
2 changes: 1 addition & 1 deletion test/cli/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
const expect = require('chai').expect
const runOnAndOff = require('../utils/on-and-off')

const commandCount = 77
const commandCount = 78

describe('commands', () => runOnAndOff((thing) => {
let ipfs
Expand Down
56 changes: 56 additions & 0 deletions test/cli/resolve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* eslint-env mocha */
'use strict'

const path = require('path')
const expect = require('chai').expect
const isIpfs = require('is-ipfs')

const runOnAndOff = require('../utils/on-and-off')

describe('resolve', () => runOnAndOff((thing) => {
let ipfs

before(() => {
ipfs = thing.ipfs
})

it('should resolve an IPFS hash', function () {
this.timeout(10 * 1000)

const filePath = path.join(process.cwd(), '/src/init-files/init-docs/readme')
let hash

return ipfs(`add ${filePath}`)
.then((out) => {
hash = out.split(' ')[1]
expect(isIpfs.cid(hash)).to.be.true()
return ipfs(`resolve /ipfs/${hash}`)
})
.then((out) => {
expect(out).to.contain(`/ipfs/${hash}`)
})
})

it('should resolve an IPFS path link', function () {
this.timeout(10 * 1000)

const filePath = path.join(process.cwd(), '/src/init-files/init-docs/readme')
let fileHash, rootHash

return ipfs(`add ${filePath} --wrap-with-directory`)
.then((out) => {
const lines = out.split('\n')

fileHash = lines[0].split(' ')[1]
rootHash = lines[1].split(' ')[1]

expect(isIpfs.cid(fileHash)).to.be.true()
expect(isIpfs.cid(rootHash)).to.be.true()

return ipfs(`resolve /ipfs/${rootHash}/readme`)
})
.then((out) => {
expect(out).to.contain(`/ipfs/${fileHash}`)
})
})
}))
8 changes: 6 additions & 2 deletions test/core/interface.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,12 @@ describe('interface-ipfs-core tests', () => {
}), {
skip: [
{
name: 'resolve',
reason: 'TODO: not implemented'
name: 'should resolve an IPNS DNS link',
reason: 'TODO IPNS not implemented yet'
},
{
name: 'should resolve IPNS link recursively',
reason: 'TODO IPNS not implemented yet'
}
]
})
Expand Down
8 changes: 6 additions & 2 deletions test/http-api/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ describe('interface-ipfs-core over ipfs-api tests', () => {
}), {
skip: [
{
name: 'resolve',
reason: 'TODO: not implemented'
name: 'should resolve an IPNS DNS link',
reason: 'TODO IPNS not implemented yet'
},
{
name: 'should resolve IPNS link recursively',
reason: 'TODO IPNS not implemented yet'
}
]
})
Expand Down