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
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ updates:
schedule:
interval: daily
time: "10:00"
open-pull-requests-limit: 10
open-pull-requests-limit: 20
commit-message:
prefix: "deps"
prefix-development: "deps(dev)"
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# @multiformats/multiaddr-matcher

[![multiformats.io](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://multiformats.io)
[![codecov](https://img.shields.io/codecov/c/github/multiformats/js-multiaddr-matcher.svg?style=flat-square)](https://codecov.io/gh/multiformats/js-multiaddr-matcher)
[![CI](https://img.shields.io/github/actions/workflow/status/multiformats/js-multiaddr-matcher/js-test-and-release.yml?branch=main\&style=flat-square)](https://github.com/multiformats/js-multiaddr-matcher/actions/workflows/js-test-and-release.yml?query=branch%3Amain)
Expand All @@ -6,6 +8,21 @@

# About

<!--

!IMPORTANT!

Everything in this README between "# About" and "# Install" is automatically
generated and will be overwritten the next time the doc generator is run.

To make changes to this section, please update the @packageDocumentation section
of src/index.js or src/index.ts

To experiment with formatting, please run "npm run docs" from the root of this
repo and examine the changes made.

-->

This module exports various matchers that can be used to infer the type of a
passed multiaddr.

Expand Down Expand Up @@ -44,7 +61,7 @@ $ npm i @multiformats/multiaddr-matcher

## Browser `<script>` tag

Loading this module through a script tag will make it's exports available as `MultiformatsMultiaddrMatcher` in the global namespace.
Loading this module through a script tag will make its exports available as `MultiformatsMultiaddrMatcher` in the global namespace.

```html
<script src="https://unpkg.com/@multiformats/multiaddr-matcher/dist/index.min.js"></script>
Expand All @@ -58,8 +75,8 @@ Loading this module through a script tag will make it's exports available as `Mu

Licensed under either of

- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)
- Apache 2.0, ([LICENSE-APACHE](https://github.com/multiformats/js-multiaddr-matcher/LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT ([LICENSE-MIT](https://github.com/multiformats/js-multiaddr-matcher/LICENSE-MIT) / <http://opensource.org/licenses/MIT>)

# Contribution

Expand Down
24 changes: 24 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,31 @@
"bugs": {
"url": "https://github.com/multiformats/js-multiaddr-matcher/issues"
},
"publishConfig": {
"access": "public",
"provenance": true
},
"keywords": [
"multiaddr"
],
"type": "module",
"types": "./dist/src/index.d.ts",
"typesVersions": {
"*": {
"*": [
"*",
"dist/*",
"dist/src/*",
"dist/src/*/index"
],
"src/*": [
"*",
"dist/*",
"dist/src/*",
"dist/src/*/index"
]
}
},
"files": [
"src",
"dist",
Expand All @@ -26,6 +46,10 @@
".": {
"types": "./dist/src/index.d.ts",
"import": "./dist/src/index.js"
},
"./utils": {
"types": "./dist/src/utils.d.ts",
"import": "./dist/src/utils.js"
}
},
"eslintConfig": {
Expand Down
213 changes: 9 additions & 204 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,224 +33,29 @@
*/

import { isIPv4, isIPv6 } from '@chainsafe/is-ip'
import { type Multiaddr } from '@multiformats/multiaddr'
import { base58btc } from 'multiformats/bases/base58'
import { base64url } from 'multiformats/bases/base64'

/**
* Split a multiaddr into path components
*/
const toParts = (ma: Multiaddr): string[] => {
return ma.toString().split('/').slice(1)
}
import { and, or, literal, string, peerId, optional, fmt, func, number, certhash } from './utils.js'
import type { Multiaddr } from '@multiformats/multiaddr'

/**
* A matcher accepts multiaddr components and either fails to match and returns
* false or returns a sublist of unmatched components
*/
interface Matcher {
export interface Matcher {
match(parts: string[]): string[] | false
pattern: string
}

const func = (fn: (val: string) => boolean): Matcher => {
return {
match: (vals) => {
if (vals.length < 1) {
return false
}

if (fn(vals[0])) {
return vals.slice(1)
}

return false
},
pattern: 'fn'
}
}

const literal = (str: string): Matcher => {
return {
match: (vals) => func((val) => val === str).match(vals),
pattern: str
}
}

const string = (): Matcher => {
return {
match: (vals) => func((val) => typeof val === 'string').match(vals),
pattern: '{string}'
}
}

const number = (): Matcher => {
return {
match: (vals) => func((val) => !isNaN(parseInt(val))).match(vals),
pattern: '{number}'
}
}

const peerId = (): Matcher => {
return {
match: (vals) => {
if (vals.length < 2) {
return false
}

if (vals[0] !== 'p2p' && vals[0] !== 'ipfs') {
return false
}

// Q is RSA, 1 is Ed25519 or Secp256k1
if (vals[1].startsWith('Q') || vals[1].startsWith('1')) {
try {
base58btc.decode(`z${vals[1]}`)
} catch (err) {
return false
}
} else {
return false
}

return vals.slice(2)
},
pattern: '/p2p/{peerid}'
}
}

const certhash = (): Matcher => {
return {
match: (vals) => {
if (vals.length < 2) {
return false
}

if (vals[0] !== 'certhash') {
return false
}

try {
base64url.decode(vals[1])
} catch {
return false
}

return vals.slice(2)
},
pattern: '/certhash/{certhash}'
}
}

const optional = (matcher: Matcher): Matcher => {
return {
match: (vals) => {
const result = matcher.match(vals)

if (result === false) {
return vals
}

return result
},
pattern: `optional(${matcher.pattern})`
}
}

const or = (...matchers: Matcher[]): Matcher => {
return {
match: (vals) => {
let matches: string[] | undefined

for (const matcher of matchers) {
const result = matcher.match(vals)

// no match
if (result === false) {
continue
}

// choose greediest matcher
if (matches == null || result.length < matches.length) {
matches = result
}
}

if (matches == null) {
return false
}

return matches
},
pattern: `or(${matchers.map(m => m.pattern).join(', ')})`
}
}

const and = (...matchers: Matcher[]): Matcher => {
return {
match: (vals) => {
for (const matcher of matchers) {
// pass what's left of the array
const result = matcher.match(vals)

// no match
if (result === false) {
return false
}

vals = result
}

return vals
},
pattern: `and(${matchers.map(m => m.pattern).join(', ')})`
}
}

function fmt (...matchers: Matcher[]): MultiaddrMatcher {
function match (ma: Multiaddr): string[] | false {
let parts = toParts(ma)

for (const matcher of matchers) {
const result = matcher.match(parts)

if (result === false) {
return false
}

parts = result
}

return parts
}

function matches (ma: Multiaddr): boolean {
const result = match(ma)

return result !== false
}

function exactMatch (ma: Multiaddr): boolean {
const result = match(ma)

if (result === false) {
return false
}

return result.length === 0
}

return {
matches,
exactMatch
}
}

/**
* A MultiaddrMatcher allows interpreting a multiaddr as a certain type of
* multiaddr
*/
export interface MultiaddrMatcher {
/**
* The matchers that make up this MultiaddrMatcher - useful if you want to
* make your own custom matchers
*/
matchers: Matcher[]

/**
* Returns true if the passed multiaddr can be treated as this type of
* multiaddr
Expand Down
Loading
Loading