Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 86 additions & 9 deletions cid.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as bytes from 'multiformats/bytes.js'
import withIs from 'class-is'

const readonly = (object, key, value) => {
Object.defineProperty(object, key, {
Expand All @@ -9,6 +8,36 @@ const readonly = (object, key, value) => {
})
}

// ESM does not support importing package.json where this version info
// should come from. To workaround it version is copied here.
const version = '0.0.0-dev'
// Start throwing exceptions on major version bump
const deprecate = (range, message) => {
if (range.test(version)) {
console.warn(message)
/* c8 ignore next 3 */
} else {
throw new Error(message)
}
}

const IS_CID_DEPRECATION =
`CID.isCID(v) is deprecated and will be removed in the next major release.
Following code pattern:

if (CID.isCID(value)) {
doSomethingWithCID(value)
}

Is replaced with:

const cid = CID.asCID(value)
if (cid) {
// Make sure to use cid instead of value
doSomethingWithCID(cid)
}
`

export default multiformats => {
const { multibase, varint, multihash } = multiformats
const parse = buff => {
Expand All @@ -22,6 +51,9 @@ export default multiformats => {
...multihash
])
}

const cidSymbol = Symbol.for('@ipld/js-cid/CID')

class CID {
constructor (cid, ...args) {
Object.defineProperty(this, '_baseCache', {
Expand All @@ -30,7 +62,7 @@ export default multiformats => {
enumerable: false
})
readonly(this, 'asCID', this)
if (_CID.isCID(cid)) {
if (cid != null && cid[cidSymbol] === true) {
readonly(this, 'version', cid.version)
readonly(this, 'multihash', bytes.coerce(cid.multihash))
readonly(this, 'buffer', bytes.coerce(cid.buffer))
Expand Down Expand Up @@ -104,11 +136,11 @@ export default multiformats => {
throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0')
}

return new _CID(0, this.code, this.multihash)
return new CID(0, this.code, this.multihash)
}

toV1 () {
return new _CID(1, this.code, this.multihash)
return new CID(1, this.code, this.multihash)
}

get toBaseEncodedString () {
Expand Down Expand Up @@ -146,11 +178,56 @@ export default multiformats => {
this.version === other.version &&
bytes.equals(this.multihash, other.multihash)
}

get [Symbol.toStringTag] () {
return 'CID'
}

get [cidSymbol] () {
return true
}

/**
* Takes any input `value` and returns a `CID` instance if it was
* a `CID` otherwise returns `null`. If `value` is instanceof `CID`
* it will return value back. If `value` is not instance of this CID
* class, but is compatible CID it will return new instance of this
* `CID` class. Otherwise returs null.
*
* This allows two different incompatible versions of CID library to
* co-exist and interop as long as binary interface is compatible.
* @param {any} value
* @returns {CID|null}
*/
static asCID (value) {
// If value is instance of CID then we're all set.
if (value instanceof CID) {
return value
// If value isn't instance of this CID class but `this.asCID === this` is
// true it is CID instance coming from a different implemnetation (diff
// version or duplicate). In that case we rebase it to this `CID`
// implemnetation so caller is guaranteed to get instance with expected
// API.
} else if (value != null && value.asCID === value) {
const { version, code, multihash } = value
return new CID(version, code, multihash)
// If value is a CID from older implementation that used to be tagged via
// symbol we still rebase it to the this `CID` implementation by
// delegating that to a constructor.
} else if (value != null && value[cidSymbol] === true) {
return new CID(value)
// Otherwise value is not a CID (or an incompatible version of it) in
// which case we return `null`.
} else {
return null
}
}

static isCID (value) {
deprecate(/^0\.0/, IS_CID_DEPRECATION)
return !!(value && value[cidSymbol])
}
}

const _CID = withIs(CID, {
className: 'CID',
symbolName: '@ipld/js-cid/CID'
})
return _CID
return CID
}
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@
"base-x": "^3.0.8",
"buffer": "^5.6.0",
"cids": "^0.8.3",
"class-is": "^1.1.0",
"varint": "^5.0.0"
},
"directories": {
Expand All @@ -114,4 +113,4 @@
"url": "https://github.com/multiformats/js-multiformats/issues"
},
"homepage": "https://github.com/multiformats/js-multiformats#readme"
}
}
51 changes: 51 additions & 0 deletions test/test-cid.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,57 @@ describe('CID', () => {
const cid = new CID(1, 112, hash)
assert.ok(OLDCID.isCID(cid))
})

test('asCID', () => {
class IncompatibleCID {
constructor (version, code, multihash) {
this.version = version
this.code = code
this.multihash = multihash
this.asCID = this
}

get [Symbol.for('@ipld/js-cid/CID')] () {
return true
}
}

const version = 1
const code = 112
const multihash = hash

const incompatibleCID = new IncompatibleCID(version, code, multihash)
assert.ok(CID.isCID(incompatibleCID))
assert.strictEqual(incompatibleCID.toString(), '[object Object]')
assert.strictEqual(typeof incompatibleCID.toV0, 'undefined')

const cid1 = CID.asCID(incompatibleCID)
assert.ok(cid1 instanceof CID)
assert.strictEqual(cid1.code, code)
assert.strictEqual(cid1.version, version)
assert.strictEqual(cid1.multihash, multihash)

const cid2 = CID.asCID({ version, code, multihash })
assert.strictEqual(cid2, null)

const duckCID = { version, code, multihash }
duckCID.asCID = duckCID
const cid3 = CID.asCID(duckCID)
assert.ok(cid3 instanceof CID)
assert.strictEqual(cid3.code, code)
assert.strictEqual(cid3.version, version)
assert.strictEqual(cid3.multihash, multihash)

const cid4 = CID.asCID(cid3)
assert.strictEqual(cid3, cid4)

const cid5 = CID.asCID(new OLDCID(1, 'raw', Buffer.from(hash)))
assert.ok(cid5 instanceof CID)
assert.strictEqual(cid5.version, 1)
same(cid5.multihash, hash)
assert.strictEqual(cid5.code, 85)
})

test('new CID from old CID', () => {
const cid = new CID(new OLDCID(1, 'raw', Buffer.from(hash)))
same(cid.version, 1)
Expand Down