diff --git a/src/codec.ts b/src/codec.ts index 0c870401..e907ff9c 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -3,7 +3,7 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import varint from 'varint' import { convertToBytes, convertToString } from './convert.js' import { getProtocol } from './protocols-table.js' -import type { StringTuple, Tuple, Protocol } from './index.js' +import type { StringTuple, Tuple, Protocol, MultiaddrInputString, MultiaddrInputStringResult } from './index.js' /** * string -> [[str name, str addr]... ] @@ -66,19 +66,28 @@ export function stringTuplesToString (tuples: StringTuple[]): string { } /** - * [[str name, str addr]... ] -> [[int code, Uint8Array]... ] + * [[str name, str addr]... ] -> {tuples: [[int code, Uint8Array]... ], path: str} + * The logic to get path is the same to DefaultMultiaddr.getPath() */ -export function stringTuplesToTuples (tuples: Array): Tuple[] { - return tuples.map((tup) => { +export function stringTuplesToTuples (stringTuples: Array): Omit { + let path: string | null | undefined + const tuples = stringTuples.map((tup) => { if (!Array.isArray(tup)) { tup = [tup] } const proto = protoFromTuple(tup) - if (tup.length > 1) { - return [proto.code, convertToBytes(proto.code, tup[1])] + const tuple: Tuple = (tup.length > 1) ? [proto.code, convertToBytes(proto.code, tup[1])] : [proto.code] + if (path === undefined && proto.path === true) { + path = tuple[1] != null ? convertToString(proto.code, tuple[1]) : null } - return [proto.code] + return tuple }) + + if (path === undefined) { + path = null + } + + return { tuples, path } } /** @@ -169,21 +178,21 @@ export function bytesToString (buf: Uint8Array): string { } /** - * String -> Uint8Array + * MultiaddrInputString -> MultiaddrInputStringResult */ -export function stringToBytes (str: string): Uint8Array { +export function parseMultiaddrInputString (str: MultiaddrInputString): MultiaddrInputStringResult { str = cleanPath(str) - const a = stringToStringTuples(str) - const b = stringTuplesToTuples(a) + const stringTuples = stringToStringTuples(str) + const { tuples, path } = stringTuplesToTuples(stringTuples) - return tuplesToBytes(b) + return { bytes: tuplesToBytes(tuples), tuples, path } } /** - * String -> Uint8Array + * MultiaddrInputString -> MultiaddrInputStringResult */ -export function fromString (str: string): Uint8Array { - return stringToBytes(str) +export function fromMultiaddrInputString (str: MultiaddrInputString): MultiaddrInputStringResult { + return parseMultiaddrInputString(str) } /** diff --git a/src/index.ts b/src/index.ts index e65323b9..f1ec0bd0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -60,10 +60,15 @@ export interface NodeAddress { port: number } +/** + * Represent a multiaaddr input string + */ +export type MultiaddrInputString = string + /** * These types can be parsed into a {@link Multiaddr} object */ -export type MultiaddrInput = string | Multiaddr | Uint8Array | null +export type MultiaddrInput = MultiaddrInputString | Multiaddr | Uint8Array | null /** * A Resolver is a function that takes a {@link Multiaddr} and resolves it into one @@ -81,6 +86,13 @@ export type Tuple = [number, Uint8Array?] */ export type StringTuple = [number, string?] +/** The result of parsing a MultiaddrInput string */ +export interface MultiaddrInputStringResult { + bytes: Uint8Array + tuples: Tuple[] + path: string | null +} + /** * Allows aborting long-lived operations */ @@ -513,7 +525,10 @@ class DefaultMultiaddr implements Multiaddr { if (addr.length > 0 && addr.charAt(0) !== '/') { throw new Error(`multiaddr "${addr}" must start with a "/"`) } - this.bytes = codec.fromString(addr) + const { bytes, tuples, path } = codec.fromMultiaddrInputString(addr) + this.bytes = bytes + this.#tuples = tuples + this.#path = path } else if (isMultiaddr(addr)) { // Multiaddr this.bytes = codec.fromBytes(addr.bytes) // validate + copy buffer } else { diff --git a/test/codec.spec.ts b/test/codec.spec.ts index b5413012..6f4377cd 100644 --- a/test/codec.spec.ts +++ b/test/codec.spec.ts @@ -3,6 +3,7 @@ import { expect } from 'aegir/chai' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import varint from 'varint' import * as codec from '../src/codec.js' +import { convertToBytes } from '../src/convert.js' describe('codec', () => { describe('.stringToStringTuples', () => { @@ -16,13 +17,21 @@ describe('codec', () => { }) describe('.stringTuplesToTuples', () => { - it('handles non array tuples', () => { - expect( - codec.stringTuplesToTuples([['ip4', '0.0.0.0'], 'utp']) - ).to.eql( - [[4, Uint8Array.from([0, 0, 0, 0])], [302]] - ) - }) + const testCases: Array<{ name: string, stringTuples: Array, tuples: Array<[number, Uint8Array?]>, path: string | null }> = [ + { name: 'handles non array tuples', stringTuples: [['ip4', '0.0.0.0'], 'utp'], tuples: [[4, Uint8Array.from([0, 0, 0, 0])], [302]], path: null }, + { name: 'handle not null path', stringTuples: [['unix', '/tmp/p2p.sock']], tuples: [[400, convertToBytes(400, '/tmp/p2p.sock')]], path: '/tmp/p2p.sock' }, + { name: 'should return the 1st path', stringTuples: [['unix', '/tmp/p2p.sock'], ['unix', '/tmp2/p2p.sock']], tuples: [[400, convertToBytes(400, '/tmp/p2p.sock')], [400, convertToBytes(400, '/tmp2/p2p.sock')]], path: '/tmp/p2p.sock' } + ] + + for (const { name, stringTuples, tuples, path } of testCases) { + it(name, () => { + expect( + codec.stringTuplesToTuples(stringTuples) + ).to.eql( + { tuples, path } + ) + }) + } }) describe('.tuplesToStringTuples', () => {