-
Notifications
You must be signed in to change notification settings - Fork 23
/
get-codec-for-cid.ts
126 lines (109 loc) · 3.82 KB
/
get-codec-for-cid.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { CID } from 'multiformats'
import codecImporter from './codec-importer.js'
import getCodecNameFromCode from './get-codec-name-from-code.js'
import { isPBNode } from './guards.js'
import { ensureLeadingSlash } from './helpers.js'
import type { ResolveType } from '../types.js'
import type { PBLink, PBNode } from '@ipld/dag-pb'
interface CodecWrapper<DecodedType = any> {
decode(bytes: Uint8Array): DecodedType
resolve(path: string, bytes: Uint8Array): Promise<ResolveType<DecodedType>>
}
interface DecodeFn<T = any> {
(bytes: Uint8Array): T
}
/**
* Resolve a path in a decoded object
*
* @see https://github.com/ipld/js-blockcodec-to-ipld-format/blob/13ad9bc518e232527078e27f0807ad3392b33698/src/index.js#L38-L55
* @param decodeFn
* @returns
*/
const resolveFn = (decodeFn: DecodeFn) => (buf: Uint8Array, path: string): ResolveType => {
let value = decodeFn(buf)
const entries = path.split('/').filter(x => x)
while (entries.length > 0) {
const entry = entries.shift()
if (entry == null) {
throw new Error('Not found')
}
value = value[entry]
if (typeof value === 'undefined') {
throw new Error('Not found')
}
if (CID.asCID(value) != null) {
const remainderPath = entries.length > 0 ? ensureLeadingSlash(entries.join('/')) : ''
return { value, remainderPath }
}
}
return { value, remainderPath: '' }
}
interface CodecResolverFn {
(node: PBNode | unknown, path: string): Promise<ResolveType<PBNode | PBLink>>
}
// #WhenAddingNewCodec (maybe)
const codecResolverMap: Record<string, CodecResolverFn> = {
'dag-pb': async (node, path) => {
if (!isPBNode(node)) {
throw new Error('node is not a PBNode')
}
const pathSegments = path.split('/')
let firstPathSegment = pathSegments.splice(1, 1)[0]
if (firstPathSegment === 'Links') {
firstPathSegment = `${firstPathSegment}/${pathSegments.splice(1, 1)[0]}`
}
let remainderPath = pathSegments.join('/')
let link = node.Links.find((link: PBLink) => link.Name === firstPathSegment)
if ((link == null) && firstPathSegment?.includes('Links')) {
const linkIndex = Number(firstPathSegment.split('/')[1])
link = node.Links[linkIndex]
}
let value = node as PBNode | PBLink
if (link != null) {
value = link
} else {
// we didn't find a link, add the firstPathSegment back to remainderPath.
remainderPath = [firstPathSegment, ...pathSegments].join('/').replace('//', '/')
}
return {
value,
remainderPath
}
}
}
export default async function getCodecForCid (cid: CID): Promise<CodecWrapper> {
// determine the codec code for the CID
const codecCode = cid.code
if (codecCode === undefined) {
throw new Error(`CID codec code is undefined for CID '${cid.toString()}'`)
}
const codecName = getCodecNameFromCode(codecCode)
const codec = await codecImporter(codecCode)
const decode = (bytes: Uint8Array): unknown => {
if (codec.decode != null) {
return codec.decode(bytes)
}
throw new Error(`codec ${codecCode}=${codecName} does not have a decode function`)
}
return {
decode,
resolve: async (path: string, bytes: Uint8Array) => {
if (codecResolverMap[codecName] != null) {
try {
return await codecResolverMap[codecName](decode(bytes), path)
} catch (err) {
console.error(err)
console.error('error resolving path for cid with codecResolverMap', cid, path)
// allow the resolver to fail and fall through to the next resolver
}
}
try {
const resolverFn = resolveFn(decode)
return resolverFn(bytes, path)
} catch (err) {
console.error('error resolving path for cid with resolveFn', cid, path)
}
throw new Error(`codec ${codecCode}=${codecName} does not have a resolve function`)
}
}
}