Skip to content

Commit e727aab

Browse files
authored
Merge pull request #6 from polywrap/kris/uri-authority-inference
feat: Improved URI Inference
2 parents 69daf7e + ac7caa2 commit e727aab

8 files changed

Lines changed: 139 additions & 67 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Polywrap Origin (0.11.0)
22
## Features
3+
**`@polywrap/core-js`:**
4+
* [PR-6](https://github.com/polywrap/javascript-client/pull/6) **Improved URI Inference**
5+
* Non-wrap URI schemes can now be used (ex: `https://domain.com/path`). The non-wrap scheme will be used as the authority, and all other contents will be shifted into the path.
6+
* Examples:
7+
* `https://domain.com/path` into `wrap://https/domain.com/path`
8+
* `ipfs://QmHASH` into `wrap://ipfs/QmHASH`
9+
310
**`@polywrap/client-config-builder-js`:**
411
* [PR-45](https://github.com/polywrap/javascript-client/pull/45) **Modular Config Bundles**
512
* The `DefaultBundle` has been broken apart into two separate bundles: `sys` and `web3`.

packages/core/README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -339,15 +339,23 @@ export interface UriConfig {
339339
```ts
340340
/**
341341
* A Polywrap URI. Some examples of valid URIs are:
342+
* wrap://https/domain.com
342343
* wrap://ipfs/QmHASH
343-
* wrap://ens/sub.dimain.eth
344-
* wrap://fs/directory/file.txt
345-
* wrap://uns/domain.crypto
344+
* wrap://ens/sub.domain.eth
345+
* wrap://file/directory/file.txt
346+
*
347+
* Some example short-hand URIs (utilizing inference):
348+
* ipfs/QmHASH -> wrap://ipfs/QmHASH
349+
* https://domain.com -> wrap://https/domain.com
350+
*
351+
* URI inference is performed in the following ways:
352+
* 1. If wrap:// is missing, it will be added.
353+
* 2. If non-wrap schema exists, it becomes the authority.
346354
*
347355
* Breaking down the various parts of the URI, as it applies
348356
* to [the URI standard](https://tools.ietf.org/html/rfc3986#section-3):
349357
* **wrap://** - URI Scheme: differentiates Polywrap URIs.
350-
* **ipfs/** - URI Authority: allows the Polywrap URI resolution algorithm to determine an authoritative URI resolver.
358+
* **ens/** - URI Authority: allows the Polywrap URI resolution algorithm to determine an authoritative URI resolver.
351359
* **sub.domain.eth** - URI Path: tells the Authority where the Wrapper resides.
352360
*/
353361
export class Uri {

packages/core/src/__tests__/Uri.spec.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,11 @@ describe("Uri", () => {
1515
});
1616

1717
it("Fails if an authority is not present", () => {
18-
expect(() => new Uri("wrap://path")).toThrowError(/URI is malformed,/);
18+
expect(() => new Uri("wrap://path")).toThrowError(/URI authority is missing,/);
1919
});
2020

2121
it("Fails if a path is not present", () => {
22-
expect(() => new Uri("wrap://authority/")).toThrowError(/URI is malformed,/);
23-
});
24-
25-
it("Fails if scheme is not at the beginning", () => {
26-
expect(() => new Uri("path/wrap://something")).toThrowError(
27-
/The wrap:\/\/ scheme must/
28-
);
22+
expect(() => new Uri("wrap://authority/")).toThrowError(/URI path is missing,/);
2923
});
3024

3125
it("Fails with an empty string", () => {
@@ -50,4 +44,38 @@ describe("Uri", () => {
5044
path: "uri",
5145
});
5246
});
47+
48+
it("Infers common URI authorities", () => {
49+
let uri = new Uri("https://domain.com/path/to/thing");
50+
expect(uri.uri).toEqual("wrap://https/domain.com/path/to/thing");
51+
expect(uri.authority).toEqual("https");
52+
expect(uri.path).toEqual("domain.com/path/to/thing");
53+
54+
uri = new Uri("http://domain.com/path/to/thing");
55+
expect(uri.uri).toEqual("wrap://http/domain.com/path/to/thing");
56+
expect(uri.authority).toEqual("http");
57+
expect(uri.path).toEqual("domain.com/path/to/thing");
58+
59+
uri = new Uri("ipfs://QmaM318ABUXDhc5eZGGbmDxkb2ZgnbLxigm5TyZcCsh1Kw");
60+
expect(uri.uri).toEqual("wrap://ipfs/QmaM318ABUXDhc5eZGGbmDxkb2ZgnbLxigm5TyZcCsh1Kw");
61+
expect(uri.authority).toEqual("ipfs");
62+
expect(uri.path).toEqual("QmaM318ABUXDhc5eZGGbmDxkb2ZgnbLxigm5TyZcCsh1Kw");
63+
64+
uri = new Uri("ens://domain.eth");
65+
expect(uri.uri).toEqual("wrap://ens/domain.eth");
66+
expect(uri.authority).toEqual("ens");
67+
expect(uri.path).toEqual("domain.eth");
68+
69+
uri = new Uri("ens://domain.eth:pkg@1.0.0");
70+
expect(uri.uri).toEqual("wrap://ens/domain.eth:pkg@1.0.0");
71+
expect(uri.authority).toEqual("ens");
72+
expect(uri.path).toEqual("domain.eth:pkg@1.0.0");
73+
});
74+
75+
it("Handles cases where the scheme delimiter is nested under an authority", () => {
76+
const uri = new Uri("authority/something?uri=wrap://something/something2");
77+
expect(uri.uri).toEqual("wrap://authority/something?uri=wrap://something/something2");
78+
expect(uri.authority).toEqual("authority");
79+
expect(uri.path).toEqual("something?uri=wrap://something/something2");
80+
});
5381
});

packages/core/src/types/Uri.ts

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,23 @@ export interface UriConfig {
1717
// $start: Uri
1818
/**
1919
* A Polywrap URI. Some examples of valid URIs are:
20+
* wrap://https/domain.com
2021
* wrap://ipfs/QmHASH
21-
* wrap://ens/sub.dimain.eth
22-
* wrap://fs/directory/file.txt
23-
* wrap://uns/domain.crypto
22+
* wrap://ens/sub.domain.eth
23+
* wrap://file/directory/file.txt
24+
*
25+
* Some example short-hand URIs (utilizing inference):
26+
* ipfs/QmHASH -> wrap://ipfs/QmHASH
27+
* https://domain.com -> wrap://https/domain.com
28+
*
29+
* URI inference is performed in the following ways:
30+
* 1. If wrap:// is missing, it will be added.
31+
* 2. If non-wrap schema exists, it becomes the authority.
2432
*
2533
* Breaking down the various parts of the URI, as it applies
2634
* to [the URI standard](https://tools.ietf.org/html/rfc3986#section-3):
2735
* **wrap://** - URI Scheme: differentiates Polywrap URIs.
28-
* **ipfs/** - URI Authority: allows the Polywrap URI resolution algorithm to determine an authoritative URI resolver.
36+
* **ens/** - URI Authority: allows the Polywrap URI resolution algorithm to determine an authoritative URI resolver.
2937
* **sub.domain.eth** - URI Path: tells the Authority where the Wrapper resides.
3038
*/
3139
export class Uri {
@@ -107,60 +115,87 @@ export class Uri {
107115
* @param uri - a string representation of a wrap URI
108116
* @returns A Result containing a UriConfig, if successful, or an error
109117
*/
110-
public static parseUri(uri: string): Result<UriConfig, Error> /* $ */ {
111-
if (!uri) {
112-
return ResultErr(Error("The provided URI is empty"));
118+
public static parseUri(input: string): Result<UriConfig, Error> /* $ */ {
119+
const authorityDelimiter = "/";
120+
const schemeDelimiter = "://";
121+
const wrapScheme = "wrap://";
122+
123+
const validUriExamples =
124+
"wrap://ipfs/QmHASH\n" +
125+
"wrap://ens/domain.eth\n" +
126+
"ipfs/QmHASH\n" +
127+
"ens/domain.eth\n" +
128+
"https://domain.com/path\n\n";
129+
130+
if (!input) {
131+
return ResultErr(
132+
Error(
133+
"The provided URI is empty, here are some examples of valid URIs:\n" +
134+
validUriExamples
135+
)
136+
);
113137
}
114138

115-
let processed = uri;
139+
let processedUri = input.trim();
116140

117-
// Trim preceding '/' characters
118-
while (processed[0] === "/") {
119-
processed = processed.substring(1);
141+
// Remove leading "/"
142+
if (processedUri.startsWith(authorityDelimiter)) {
143+
processedUri = processedUri.substring(1);
120144
}
121145

122-
// Check for the wrap:// scheme, add if it isn't there
123-
const wrapSchemeIdx = processed.indexOf("wrap://");
124-
125-
// If it's missing the wrap:// scheme, add it
126-
if (wrapSchemeIdx === -1) {
127-
processed = "wrap://" + processed;
146+
// Check if the string starts with a non-wrap URI scheme
147+
if (!processedUri.startsWith(wrapScheme)) {
148+
const schemeIndex = processedUri.indexOf(schemeDelimiter);
149+
const authorityIndex = processedUri.indexOf(authorityDelimiter);
150+
if (schemeIndex !== -1) {
151+
// Make sure the string before the scheme doesn't contain an authority
152+
if (!(authorityIndex !== -1 && schemeIndex > authorityIndex)) {
153+
processedUri =
154+
processedUri.substring(0, schemeIndex) +
155+
"/" +
156+
processedUri.substring(schemeIndex + schemeDelimiter.length);
157+
}
158+
}
159+
} else {
160+
processedUri = processedUri.substring(wrapScheme.length);
128161
}
129162

130-
// If the wrap:// is not in the beginning, return an error
131-
if (wrapSchemeIdx > -1 && wrapSchemeIdx !== 0) {
163+
// Split the string into parts, using "/" as a delimeter
164+
const parts = processedUri.split(authorityDelimiter);
165+
166+
if (parts.length < 2) {
132167
return ResultErr(
133-
Error("The wrap:// scheme must be at the beginning of the URI string")
168+
Error(
169+
`URI authority is missing, here are some examples of valid URIs:\n` +
170+
validUriExamples +
171+
`Invalid URI Received: ${input}`
172+
)
134173
);
135174
}
136175

137-
// Extract the authoriy & path
138-
const result = processed.match(/wrap:\/\/([a-z][a-z0-9-_]+)\/(.*)/);
139-
let uriParts: string[];
140-
141-
// Remove all empty strings
142-
if (result) {
143-
uriParts = result.filter((str) => !!str);
144-
} else {
145-
uriParts = [];
146-
}
176+
// Extract the authority and path
177+
const authority = parts[0];
178+
const path = parts.slice(1).join("/");
147179

148-
if (uriParts.length !== 3) {
180+
if (!path) {
149181
return ResultErr(
150182
Error(
151-
`URI is malformed, here are some examples of valid URIs:\n` +
152-
`wrap://ipfs/QmHASH\n` +
153-
`wrap://ens/domain.eth\n` +
154-
`ens/domain.eth\n\n` +
155-
`Invalid URI Received: ${uri}`
183+
`URI path is missing, here are some examples of valid URIs:\n` +
184+
validUriExamples +
185+
`Invalid URI Received: ${input}`
156186
)
157187
);
158188
}
159189

190+
// Add "wrap://" if not already present
191+
if (!processedUri.startsWith("wrap://")) {
192+
processedUri = "wrap://" + processedUri;
193+
}
194+
160195
return ResultOk({
161-
uri: processed,
162-
authority: uriParts[1],
163-
path: uriParts[2],
196+
uri: processedUri,
197+
authority,
198+
path,
164199
});
165200
}
166201

packages/core/src/types/WrapError.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CleanResolutionStep } from "../algorithms";
2+
import { RegExpGroups } from "../utils";
23

34
export type ErrorSource = Readonly<{
45
file?: string;
@@ -52,12 +53,6 @@ export interface WrapErrorOptions {
5253
innerError?: WrapError;
5354
}
5455

55-
type RegExpGroups<T extends string> =
56-
| (RegExpExecArray & {
57-
groups?: { [name in T]: string | undefined } | { [key: string]: string };
58-
})
59-
| null;
60-
6156
export class WrapError extends Error {
6257
readonly name: string = "WrapError";
6358
readonly code: WrapErrorCode;
@@ -174,7 +169,7 @@ export class WrapError extends Error {
174169
| "resolutionStack"
175170
| "cause"
176171
> = WrapError.re.exec(error);
177-
if (!result) {
172+
if (!result || !result.groups) {
178173
return undefined;
179174
}
180175
const {
@@ -188,8 +183,7 @@ export class WrapError extends Error {
188183
col,
189184
resolutionStack: resolutionStackStr,
190185
cause,
191-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
192-
} = result.groups!;
186+
} = result.groups;
193187

194188
const code = parseInt(codeStr as string);
195189

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export type RegExpGroups<T extends string> =
2+
| (RegExpExecArray & {
3+
groups?: { [name in T]: string | undefined } | { [key: string]: string };
4+
})
5+
| null;

packages/core/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from "./combinePaths";
22
export * from "./getEnvFromResolutionPath";
33
export * from "./is-buffer";
44
export * from "./typesHandler";
5+
export * from "./RegExpGroups";

packages/plugin/src/utils/getErrorSource.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import { ErrorSource } from "@polywrap/core-js";
2-
3-
type RegExpGroups<T extends string> =
4-
| (RegExpExecArray & {
5-
groups?: { [name in T]: string | undefined } | { [key: string]: string };
6-
})
7-
| null;
1+
import { ErrorSource, RegExpGroups } from "@polywrap/core-js";
82

93
const re = /\((?<file>.*):(?<row>\d+):(?<col>\d+)\)$/;
104

0 commit comments

Comments
 (0)