Skip to content

Commit 63e1124

Browse files
committed
feat: export functions to make custom matchers
To allow creating custom matchers, export all the necessary functions from the `/utils` path.
1 parent 3d8210d commit 63e1124

File tree

3 files changed

+218
-204
lines changed

3 files changed

+218
-204
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
".": {
2727
"types": "./dist/src/index.d.ts",
2828
"import": "./dist/src/index.js"
29+
},
30+
"./utils": {
31+
"types": "./dist/src/utils.d.ts",
32+
"import": "./dist/src/utils.js"
2933
}
3034
},
3135
"eslintConfig": {

src/index.ts

Lines changed: 9 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -33,224 +33,29 @@
3333
*/
3434

3535
import { isIPv4, isIPv6 } from '@chainsafe/is-ip'
36-
import { type Multiaddr } from '@multiformats/multiaddr'
37-
import { base58btc } from 'multiformats/bases/base58'
38-
import { base64url } from 'multiformats/bases/base64'
39-
40-
/**
41-
* Split a multiaddr into path components
42-
*/
43-
const toParts = (ma: Multiaddr): string[] => {
44-
return ma.toString().split('/').slice(1)
45-
}
36+
import { and, or, literal, string, peerId, optional, fmt, func, number, certhash } from './utils.js'
37+
import type { Multiaddr } from '@multiformats/multiaddr'
4638

4739
/**
4840
* A matcher accepts multiaddr components and either fails to match and returns
4941
* false or returns a sublist of unmatched components
5042
*/
51-
interface Matcher {
43+
export interface Matcher {
5244
match(parts: string[]): string[] | false
5345
pattern: string
5446
}
5547

56-
const func = (fn: (val: string) => boolean): Matcher => {
57-
return {
58-
match: (vals) => {
59-
if (vals.length < 1) {
60-
return false
61-
}
62-
63-
if (fn(vals[0])) {
64-
return vals.slice(1)
65-
}
66-
67-
return false
68-
},
69-
pattern: 'fn'
70-
}
71-
}
72-
73-
const literal = (str: string): Matcher => {
74-
return {
75-
match: (vals) => func((val) => val === str).match(vals),
76-
pattern: str
77-
}
78-
}
79-
80-
const string = (): Matcher => {
81-
return {
82-
match: (vals) => func((val) => typeof val === 'string').match(vals),
83-
pattern: '{string}'
84-
}
85-
}
86-
87-
const number = (): Matcher => {
88-
return {
89-
match: (vals) => func((val) => !isNaN(parseInt(val))).match(vals),
90-
pattern: '{number}'
91-
}
92-
}
93-
94-
const peerId = (): Matcher => {
95-
return {
96-
match: (vals) => {
97-
if (vals.length < 2) {
98-
return false
99-
}
100-
101-
if (vals[0] !== 'p2p' && vals[0] !== 'ipfs') {
102-
return false
103-
}
104-
105-
// Q is RSA, 1 is Ed25519 or Secp256k1
106-
if (vals[1].startsWith('Q') || vals[1].startsWith('1')) {
107-
try {
108-
base58btc.decode(`z${vals[1]}`)
109-
} catch (err) {
110-
return false
111-
}
112-
} else {
113-
return false
114-
}
115-
116-
return vals.slice(2)
117-
},
118-
pattern: '/p2p/{peerid}'
119-
}
120-
}
121-
122-
const certhash = (): Matcher => {
123-
return {
124-
match: (vals) => {
125-
if (vals.length < 2) {
126-
return false
127-
}
128-
129-
if (vals[0] !== 'certhash') {
130-
return false
131-
}
132-
133-
try {
134-
base64url.decode(vals[1])
135-
} catch {
136-
return false
137-
}
138-
139-
return vals.slice(2)
140-
},
141-
pattern: '/certhash/{certhash}'
142-
}
143-
}
144-
145-
const optional = (matcher: Matcher): Matcher => {
146-
return {
147-
match: (vals) => {
148-
const result = matcher.match(vals)
149-
150-
if (result === false) {
151-
return vals
152-
}
153-
154-
return result
155-
},
156-
pattern: `optional(${matcher.pattern})`
157-
}
158-
}
159-
160-
const or = (...matchers: Matcher[]): Matcher => {
161-
return {
162-
match: (vals) => {
163-
let matches: string[] | undefined
164-
165-
for (const matcher of matchers) {
166-
const result = matcher.match(vals)
167-
168-
// no match
169-
if (result === false) {
170-
continue
171-
}
172-
173-
// choose greediest matcher
174-
if (matches == null || result.length < matches.length) {
175-
matches = result
176-
}
177-
}
178-
179-
if (matches == null) {
180-
return false
181-
}
182-
183-
return matches
184-
},
185-
pattern: `or(${matchers.map(m => m.pattern).join(', ')})`
186-
}
187-
}
188-
189-
const and = (...matchers: Matcher[]): Matcher => {
190-
return {
191-
match: (vals) => {
192-
for (const matcher of matchers) {
193-
// pass what's left of the array
194-
const result = matcher.match(vals)
195-
196-
// no match
197-
if (result === false) {
198-
return false
199-
}
200-
201-
vals = result
202-
}
203-
204-
return vals
205-
},
206-
pattern: `and(${matchers.map(m => m.pattern).join(', ')})`
207-
}
208-
}
209-
210-
function fmt (...matchers: Matcher[]): MultiaddrMatcher {
211-
function match (ma: Multiaddr): string[] | false {
212-
let parts = toParts(ma)
213-
214-
for (const matcher of matchers) {
215-
const result = matcher.match(parts)
216-
217-
if (result === false) {
218-
return false
219-
}
220-
221-
parts = result
222-
}
223-
224-
return parts
225-
}
226-
227-
function matches (ma: Multiaddr): boolean {
228-
const result = match(ma)
229-
230-
return result !== false
231-
}
232-
233-
function exactMatch (ma: Multiaddr): boolean {
234-
const result = match(ma)
235-
236-
if (result === false) {
237-
return false
238-
}
239-
240-
return result.length === 0
241-
}
242-
243-
return {
244-
matches,
245-
exactMatch
246-
}
247-
}
248-
24948
/**
25049
* A MultiaddrMatcher allows interpreting a multiaddr as a certain type of
25150
* multiaddr
25251
*/
25352
export interface MultiaddrMatcher {
53+
/**
54+
* The matchers that make up this MultiaddrMatcher - useful if you want to
55+
* make your own custom matchers
56+
*/
57+
matchers: Matcher[]
58+
25459
/**
25560
* Returns true if the passed multiaddr can be treated as this type of
25661
* multiaddr

0 commit comments

Comments
 (0)