diff --git a/packages/js/core/README.md b/packages/js/core/README.md index f122bcdaa5..5f1c4c0743 100644 --- a/packages/js/core/README.md +++ b/packages/js/core/README.md @@ -340,7 +340,7 @@ export interface UriConfig { /** * A Polywrap URI. Some examples of valid URIs are: * wrap://ipfs/QmHASH - * wrap://ens/sub.dimain.eth + * wrap://ens/sub.domain.eth * wrap://fs/directory/file.txt * wrap://uns/domain.crypto * diff --git a/packages/js/core/src/__tests__/Uri.spec.ts b/packages/js/core/src/__tests__/Uri.spec.ts index f38bfa7f1e..778fdde148 100644 --- a/packages/js/core/src/__tests__/Uri.spec.ts +++ b/packages/js/core/src/__tests__/Uri.spec.ts @@ -15,7 +15,7 @@ describe("Uri", () => { }); it("Fails if an authority is not present", () => { - expect(() => new Uri("wrap://path")).toThrowError(/URI is malformed,/); + expect(() => new Uri("wrap://path")).toThrowError(/URI authority is missing,/); }); it("Fails if a path is not present", () => { @@ -50,4 +50,31 @@ describe("Uri", () => { path: "uri", }); }); + + it("Infers common URI authorities", () => { + let uri = new Uri("https://domain.com/path/to/thing"); + expect(uri.uri).toEqual("wrap://https/domain.com/path/to/thing"); + expect(uri.authority).toEqual("https"); + expect(uri.path).toEqual("domain.com/path/to/thing"); + + uri = new Uri("http://domain.com/path/to/thing"); + expect(uri.uri).toEqual("wrap://http/domain.com/path/to/thing"); + expect(uri.authority).toEqual("http"); + expect(uri.path).toEqual("domain.com/path/to/thing"); + + uri = new Uri("ipfs://QmaM318ABUXDhc5eZGGbmDxkb2ZgnbLxigm5TyZcCsh1Kw"); + expect(uri.uri).toEqual("wrap://ipfs/QmaM318ABUXDhc5eZGGbmDxkb2ZgnbLxigm5TyZcCsh1Kw"); + expect(uri.authority).toEqual("ipfs"); + expect(uri.path).toEqual("QmaM318ABUXDhc5eZGGbmDxkb2ZgnbLxigm5TyZcCsh1Kw"); + + uri = new Uri("ens://domain.eth"); + expect(uri.uri).toEqual("wrap://ens/domain.eth"); + expect(uri.authority).toEqual("ens"); + expect(uri.path).toEqual("domain.eth"); + + uri = new Uri("ens://domain.eth:pkg@1.0.0"); + expect(uri.uri).toEqual("wrap://ens/domain.eth:pkg@1.0.0"); + expect(uri.authority).toEqual("ens"); + expect(uri.path).toEqual("domain.eth:pkg@1.0.0"); + }); }); diff --git a/packages/js/core/src/types/Uri.ts b/packages/js/core/src/types/Uri.ts index 755e32a39e..4af5dbe35d 100644 --- a/packages/js/core/src/types/Uri.ts +++ b/packages/js/core/src/types/Uri.ts @@ -1,3 +1,5 @@ +import { RegExpGroups } from "../utils/RegExpGroups"; + import { Result, ResultErr, ResultOk } from "@polywrap/result"; // $start: UriConfig @@ -18,7 +20,7 @@ export interface UriConfig { /** * A Polywrap URI. Some examples of valid URIs are: * wrap://ipfs/QmHASH - * wrap://ens/sub.dimain.eth + * wrap://ens/sub.domain.eth * wrap://fs/directory/file.txt * wrap://uns/domain.crypto * @@ -135,17 +137,10 @@ export class Uri { } // Extract the authoriy & path - const result = processed.match(/wrap:\/\/([a-z][a-z0-9-_]+)\/(.*)/); - let uriParts: string[]; - - // Remove all empty strings - if (result) { - uriParts = result.filter((str) => !!str); - } else { - uriParts = []; - } + const re = /^wrap:\/\/((?[a-z][a-z0-9-_]+)\/)?(?.*)$/; + const result: RegExpGroups<"authority" | "path"> = re.exec(processed); - if (uriParts.length !== 3) { + if (!result || !result.groups || !result.groups.path) { return ResultErr( Error( `URI is malformed, here are some examples of valid URIs:\n` + @@ -157,10 +152,30 @@ export class Uri { ); } + let { authority, path } = result.groups; + + if (!authority) { + const inferred = Uri.inferAuthority(path); + if (!inferred) { + return ResultErr( + Error( + `URI authority is missing, here are some examples of valid URIs:\n` + + `wrap://ipfs/QmHASH\n` + + `wrap://ens/domain.eth\n` + + `ens/domain.eth\n\n` + + `Invalid URI Received: ${uri}` + ) + ); + } + authority = inferred.authority; + path = inferred.path; + processed = `wrap://${authority}/${path}`; + } + return ResultOk({ uri: processed, - authority: uriParts[1], - path: uriParts[2], + authority, + path, }); } @@ -194,4 +209,17 @@ export class Uri { public toJSON(): string /* $ */ { return this._config.uri; } + + private static inferAuthority(_path: string): UriConfig | undefined { + const re = /^(?[a-z][a-z0-9-_]+):\/\/(?.*)$/; + const result: RegExpGroups<"authority" | "path"> = re.exec(_path); + + if (!result || !result.groups) { + return undefined; + } + const authority = result.groups.authority as string; + const path = result.groups.path as string; + + return { authority, path, uri: `wrap://${authority}/${path}` }; + } } diff --git a/packages/js/core/src/types/WrapError.ts b/packages/js/core/src/types/WrapError.ts index 38641858b1..af9c827917 100644 --- a/packages/js/core/src/types/WrapError.ts +++ b/packages/js/core/src/types/WrapError.ts @@ -1,4 +1,5 @@ import { CleanResolutionStep } from "../algorithms"; +import { RegExpGroups } from "../utils/RegExpGroups"; export type ErrorSource = Readonly<{ file?: string; @@ -52,12 +53,6 @@ export interface WrapErrorOptions { innerError?: WrapError; } -type RegExpGroups = - | (RegExpExecArray & { - groups?: { [name in T]: string | undefined } | { [key: string]: string }; - }) - | null; - export class WrapError extends Error { readonly name: string = "WrapError"; readonly code: WrapErrorCode; @@ -174,7 +169,7 @@ export class WrapError extends Error { | "resolutionStack" | "cause" > = WrapError.re.exec(error); - if (!result) { + if (!result || !result.groups) { return undefined; } const { @@ -188,8 +183,7 @@ export class WrapError extends Error { col, resolutionStack: resolutionStackStr, cause, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - } = result.groups!; + } = result.groups; const code = parseInt(codeStr as string); diff --git a/packages/js/core/src/utils/RegExpGroups.ts b/packages/js/core/src/utils/RegExpGroups.ts new file mode 100644 index 0000000000..35e7fa063f --- /dev/null +++ b/packages/js/core/src/utils/RegExpGroups.ts @@ -0,0 +1,5 @@ +export type RegExpGroups = + | (RegExpExecArray & { + groups?: { [name in T]: string | undefined } | { [key: string]: string }; + }) + | null; diff --git a/packages/js/core/src/utils/index.ts b/packages/js/core/src/utils/index.ts index f52298232c..ca4c0fa290 100644 --- a/packages/js/core/src/utils/index.ts +++ b/packages/js/core/src/utils/index.ts @@ -2,3 +2,4 @@ export * from "./combinePaths"; export * from "./getEnvFromUriHistory"; export * from "./is-buffer"; export * from "./typesHandler"; +export * from "./RegExpGroups"; diff --git a/packages/js/plugin/src/utils/getErrorSource.ts b/packages/js/plugin/src/utils/getErrorSource.ts index 8b737f432a..f87e497e00 100644 --- a/packages/js/plugin/src/utils/getErrorSource.ts +++ b/packages/js/plugin/src/utils/getErrorSource.ts @@ -1,10 +1,4 @@ -import { ErrorSource } from "@polywrap/core-js"; - -type RegExpGroups = - | (RegExpExecArray & { - groups?: { [name in T]: string | undefined } | { [key: string]: string }; - }) - | null; +import { ErrorSource, RegExpGroups } from "@polywrap/core-js"; const re = /\((?.*):(?\d+):(?\d+)\)$/;