@atproto/syntax 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/aturi.d.ts.map +1 -1
- package/dist/aturi.js +14 -7
- package/dist/aturi.js.map +1 -1
- package/dist/aturi_validation.d.ts +137 -4
- package/dist/aturi_validation.d.ts.map +1 -1
- package/dist/aturi_validation.js +176 -94
- package/dist/aturi_validation.js.map +1 -1
- package/dist/datetime.d.ts +5 -0
- package/dist/datetime.d.ts.map +1 -1
- package/dist/datetime.js +37 -7
- package/dist/datetime.js.map +1 -1
- package/dist/lib/result.d.ts +12 -0
- package/dist/lib/result.d.ts.map +1 -0
- package/dist/lib/result.js +11 -0
- package/dist/lib/result.js.map +1 -0
- package/dist/nsid.d.ts +3 -10
- package/dist/nsid.d.ts.map +1 -1
- package/dist/nsid.js +15 -62
- package/dist/nsid.js.map +1 -1
- package/package.json +1 -1
- package/src/aturi.ts +13 -7
- package/src/aturi_validation.ts +279 -110
- package/src/datetime.ts +41 -8
- package/src/lib/result.ts +11 -0
- package/src/nsid.ts +20 -56
- package/tests/aturi-string.test.ts +211 -0
- package/tests/aturi.test.ts +59 -198
- package/tests/datetime.test.ts +22 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @atproto/syntax
|
|
2
2
|
|
|
3
|
+
## 0.5.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#4806](https://github.com/bluesky-social/atproto/pull/4806) [`26d793a`](https://github.com/bluesky-social/atproto/commit/26d793af95a6fb3a50f9b2a97187d8ac4fecf676) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Avoid trailing slashes when stringifying an AtUri
|
|
8
|
+
|
|
9
|
+
- [#4806](https://github.com/bluesky-social/atproto/pull/4806) [`26d793a`](https://github.com/bluesky-social/atproto/commit/26d793af95a6fb3a50f9b2a97187d8ac4fecf676) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Align implementation of `ensureValidAtUri` and `ensureValidAtUriRegex`: consistently apply length and fragment charset restrictions
|
|
10
|
+
|
|
11
|
+
- [#4806](https://github.com/bluesky-social/atproto/pull/4806) [`26d793a`](https://github.com/bluesky-social/atproto/commit/26d793af95a6fb3a50f9b2a97187d8ac4fecf676) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Improve performances of `AtUriString` validation
|
|
12
|
+
|
|
13
|
+
- [#4760](https://github.com/bluesky-social/atproto/pull/4760) [`55d06de`](https://github.com/bluesky-social/atproto/commit/55d06de80a1506908a04ed5c0834986cb5783797) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Fixes a bug where `normalizeDatetime` would return different results for the same input depending on the timezone of the machine it was run on. This caused tests to fail when run in different environments. The fix consists of attempting more consistent parsing strategies first (appending "Z" or " UTC" to the input) before falling back to parsing "as is", which can yield different results depending on the local timezone. The function's documentation has also been updated to reflect this behavior.
|
|
14
|
+
|
|
15
|
+
- [#4806](https://github.com/bluesky-social/atproto/pull/4806) [`26d793a`](https://github.com/bluesky-social/atproto/commit/26d793af95a6fb3a50f9b2a97187d8ac4fecf676) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add missing fragment to `AtUriString` template literal type
|
|
16
|
+
|
|
17
|
+
## 0.5.3
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- [#4815](https://github.com/bluesky-social/atproto/pull/4815) [`3711454`](https://github.com/bluesky-social/atproto/commit/371145432178b6c8c411f1289c266314cc7ec592) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Rewrite of tests
|
|
22
|
+
|
|
3
23
|
## 0.5.2
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/dist/aturi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aturi.d.ts","sourceRoot":"","sources":["../src/aturi.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAGnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAmB,MAAM,UAAU,CAAA;AACrD,OAAO,EAAE,UAAU,EAAmB,MAAM,WAAW,CAAA;AACvD,OAAO,EAAE,eAAe,EAAwB,MAAM,gBAAgB,CAAA;AAEtE,cAAc,uBAAuB,CAAA;AAGrC,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,SAAS,EACT,UAAU,EACV,eAAe,GAChB,CAAA;AAED,eAAO,MAAM,aAAa,QAEyE,CAAA;AAInG,qBAAa,KAAK;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,kBAAkB,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,eAAe,CAAA;gBAEjB,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK;IAgB9C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAOnE,IAAI,QAAQ,WAEX;IAED,IAAI,MAAM,gEAET;IAED,IAAI,GAAG,IAAI,SAAS,CAInB;IAED,IAAI,QAAQ,IAAI,kBAAkB,CAEjC;IAED,IAAI,QAAQ,CAAC,CAAC,EAAE,MAAM,EAGrB;IAED,IAAI,MAAM,IAII,MAAM,CAFnB;IAED,IAAI,MAAM,CAAC,CAAC,EAAE,MAAM,EAEnB;IAED,IAAI,UAAU,IAUI,MAAM,CARvB;IAED,IAAI,cAAc,IAAI,UAAU,CAI/B;IAED,IAAI,UAAU,CAAC,CAAC,EAAE,MAAM,EAGvB;IAED,qBAAqB,CAAC,CAAC,EAAE,MAAM;IAM/B,IAAI,IAAI,IAUI,MAAM,CARjB;IAED,IAAI,QAAQ,IAAI,eAAe,CAI9B;IAED,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,EAGjB;IAED,eAAe,CAAC,CAAC,EAAE,MAAM;IAOzB,IAAI,IAAI,gBAEP;IAED,QAAQ,IAAI,WAAW;
|
|
1
|
+
{"version":3,"file":"aturi.d.ts","sourceRoot":"","sources":["../src/aturi.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAGnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAmB,MAAM,UAAU,CAAA;AACrD,OAAO,EAAE,UAAU,EAAmB,MAAM,WAAW,CAAA;AACvD,OAAO,EAAE,eAAe,EAAwB,MAAM,gBAAgB,CAAA;AAEtE,cAAc,uBAAuB,CAAA;AAGrC,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,SAAS,EACT,UAAU,EACV,eAAe,GAChB,CAAA;AAED,eAAO,MAAM,aAAa,QAEyE,CAAA;AAInG,qBAAa,KAAK;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,kBAAkB,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,eAAe,CAAA;gBAEjB,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK;IAgB9C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAOnE,IAAI,QAAQ,WAEX;IAED,IAAI,MAAM,gEAET;IAED,IAAI,GAAG,IAAI,SAAS,CAInB;IAED,IAAI,QAAQ,IAAI,kBAAkB,CAEjC;IAED,IAAI,QAAQ,CAAC,CAAC,EAAE,MAAM,EAGrB;IAED,IAAI,MAAM,IAII,MAAM,CAFnB;IAED,IAAI,MAAM,CAAC,CAAC,EAAE,MAAM,EAEnB;IAED,IAAI,UAAU,IAUI,MAAM,CARvB;IAED,IAAI,cAAc,IAAI,UAAU,CAI/B;IAED,IAAI,UAAU,CAAC,CAAC,EAAE,MAAM,EAGvB;IAED,qBAAqB,CAAC,CAAC,EAAE,MAAM;IAM/B,IAAI,IAAI,IAUI,MAAM,CARjB;IAED,IAAI,QAAQ,IAAI,eAAe,CAI9B;IAED,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,EAGjB;IAED,eAAe,CAAC,CAAC,EAAE,MAAM;IAOzB,IAAI,IAAI,gBAEP;IAED,QAAQ,IAAI,WAAW;CAqBxB"}
|
package/dist/aturi.js
CHANGED
|
@@ -101,19 +101,26 @@ class AtUri {
|
|
|
101
101
|
return this.toString();
|
|
102
102
|
}
|
|
103
103
|
toString() {
|
|
104
|
-
let
|
|
105
|
-
if (!
|
|
106
|
-
|
|
104
|
+
let pathname = this.pathname;
|
|
105
|
+
if (pathname && !pathname.startsWith('/')) {
|
|
106
|
+
pathname = `/${pathname}`;
|
|
107
|
+
}
|
|
108
|
+
while (pathname.endsWith('/')) {
|
|
109
|
+
pathname = pathname.slice(0, -1);
|
|
107
110
|
}
|
|
108
111
|
let qs = '';
|
|
109
112
|
if (this.searchParams.size) {
|
|
110
113
|
qs = `?${this.searchParams.toString()}`;
|
|
111
114
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
// @NOTE We keep the hash as-is, even if it doesn't start with a '/'.
|
|
116
|
+
let fragment = this.hash;
|
|
117
|
+
if (fragment === '#') {
|
|
118
|
+
fragment = '';
|
|
119
|
+
}
|
|
120
|
+
else if (fragment && !fragment.startsWith('#')) {
|
|
121
|
+
fragment = `#${fragment}`;
|
|
115
122
|
}
|
|
116
|
-
return `at://${this.host}${
|
|
123
|
+
return `at://${this.host}${pathname}${qs}${fragment}`;
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
126
|
exports.AtUri = AtUri;
|
package/dist/aturi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aturi.js","sourceRoot":"","sources":["../src/aturi.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"aturi.js","sourceRoot":"","sources":["../src/aturi.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AtIdentifierString,\n ensureValidAtIdentifier,\n isDidIdentifier,\n} from './at-identifier.js'\nimport { AtUriString } from './aturi_validation.js'\nimport { DidString, InvalidDidError } from './did.js'\nimport { NsidString, ensureValidNsid } from './nsid.js'\nimport { RecordKeyString, ensureValidRecordKey } from './recordkey.js'\n\nexport * from './aturi_validation.js'\n\n// Re-export types used in public interface\nexport type {\n AtIdentifierString,\n AtUriString,\n DidString,\n NsidString,\n RecordKeyString,\n}\n\nexport const ATP_URI_REGEX =\n // proto- --did-------------- --name---------------- --path---- --query-- --hash--\n /^(at:\\/\\/)?((?:did:[a-z0-9:%-]+)|(?:[a-z0-9][a-z0-9.:-]*))(\\/[^?#\\s]*)?(\\?[^#\\s]+)?(#[^\\s]+)?$/i\n// --path----- --query-- --hash--\nconst RELATIVE_REGEX = /^(\\/[^?#\\s]*)?(\\?[^#\\s]+)?(#[^\\s]+)?$/i\n\nexport class AtUri {\n hash: string\n host: AtIdentifierString\n pathname: string\n searchParams: URLSearchParams\n\n constructor(uri: string, base?: string | AtUri) {\n const parsed =\n base !== undefined\n ? typeof base === 'string'\n ? Object.assign(parse(base), parseRelative(uri))\n : Object.assign({ host: base.host }, parseRelative(uri))\n : parse(uri)\n\n ensureValidAtIdentifier(parsed.host)\n\n this.hash = parsed.hash ?? ''\n this.host = parsed.host\n this.pathname = parsed.pathname ?? ''\n this.searchParams = parsed.searchParams\n }\n\n static make(handleOrDid: string, collection?: string, rkey?: string) {\n let str = handleOrDid\n if (collection) str += '/' + collection\n if (rkey) str += '/' + rkey\n return new AtUri(str)\n }\n\n get protocol() {\n return 'at:'\n }\n\n get origin() {\n return `at://${this.host}` as const\n }\n\n get did(): DidString {\n const { host } = this\n if (isDidIdentifier(host)) return host\n throw new InvalidDidError(`AtUri \"${this}\" does not have a DID hostname`)\n }\n\n get hostname(): AtIdentifierString {\n return this.host\n }\n\n set hostname(v: string) {\n ensureValidAtIdentifier(v)\n this.host = v\n }\n\n get search() {\n return this.searchParams.toString()\n }\n\n set search(v: string) {\n this.searchParams = new URLSearchParams(v)\n }\n\n get collection() {\n return this.pathname.split('/').filter(Boolean)[0] || ''\n }\n\n get collectionSafe(): NsidString {\n const { collection } = this\n ensureValidNsid(collection)\n return collection\n }\n\n set collection(v: string) {\n ensureValidNsid(v)\n this.unsafelySetCollection(v)\n }\n\n unsafelySetCollection(v: string) {\n const parts = this.pathname.split('/').filter(Boolean)\n parts[0] = v\n this.pathname = parts.join('/')\n }\n\n get rkey() {\n return this.pathname.split('/').filter(Boolean)[1] || ''\n }\n\n get rkeySafe(): RecordKeyString {\n const { rkey } = this\n ensureValidRecordKey(rkey)\n return rkey\n }\n\n set rkey(v: string) {\n ensureValidRecordKey(v)\n this.unsafelySetRkey(v)\n }\n\n unsafelySetRkey(v: string) {\n const parts = this.pathname.split('/').filter(Boolean)\n parts[0] ||= 'undefined'\n parts[1] = v\n this.pathname = parts.join('/')\n }\n\n get href() {\n return this.toString()\n }\n\n toString(): AtUriString {\n let pathname = this.pathname\n if (pathname && !pathname.startsWith('/')) {\n pathname = `/${pathname}`\n }\n while (pathname.endsWith('/')) {\n pathname = pathname.slice(0, -1)\n }\n let qs = ''\n if (this.searchParams.size) {\n qs = `?${this.searchParams.toString()}`\n }\n // @NOTE We keep the hash as-is, even if it doesn't start with a '/'.\n let fragment = this.hash\n if (fragment === '#') {\n fragment = ''\n } else if (fragment && !fragment.startsWith('#')) {\n fragment = `#${fragment}`\n }\n return `at://${this.host}${pathname}${qs}${fragment}` as AtUriString\n }\n}\n\nfunction parse(str: string) {\n const match = str.match(ATP_URI_REGEX) as null | {\n 0: string\n 1: string | undefined // proto\n 2: string // host\n 3: string | undefined // path\n 4: string | undefined // query\n 5: string | undefined // hash\n }\n\n if (!match) {\n throw new Error(`Invalid AT uri: ${str}`)\n }\n\n return {\n host: match[2],\n hash: match[5],\n pathname: match[3],\n searchParams: new URLSearchParams(match[4]),\n }\n}\n\nfunction parseRelative(str: string) {\n const match = str.match(RELATIVE_REGEX) as null | {\n 0: string\n 1: string | undefined // path\n 2: string | undefined // query\n 3: string | undefined // hash\n }\n\n if (!match) {\n throw new Error(`Invalid path: ${str}`)\n }\n\n return {\n hash: match[3],\n pathname: match[1],\n searchParams: new URLSearchParams(match[2]),\n }\n}\n"]}
|
|
@@ -1,7 +1,140 @@
|
|
|
1
1
|
import { AtIdentifierString } from './at-identifier.js';
|
|
2
|
+
import { Result } from './lib/result.js';
|
|
2
3
|
import { NsidString } from './nsid.js';
|
|
3
|
-
export type
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export type AtUriStringBase = `at://${AtIdentifierString}` | `at://${AtIdentifierString}/${NsidString}` | `at://${AtIdentifierString}/${NsidString}/${string}`;
|
|
5
|
+
export type AtUriStringFragment = `#/${string}`;
|
|
6
|
+
/**
|
|
7
|
+
* A URI string as used to point at resources in the AT Protocol
|
|
8
|
+
*
|
|
9
|
+
* The full, general structure of an AT URI is:
|
|
10
|
+
*
|
|
11
|
+
* ```bnf
|
|
12
|
+
* AT-URI = "at://" AUTHORITY [ PATH ] [ "?" QUERY ] [ "#" FRAGMENT ]
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* The authority part of the URI can be either a handle or a DID, indicating the
|
|
16
|
+
* identity associated with the repository. In current atproto Lexicon use, the
|
|
17
|
+
* query and fragment parts are not yet supported, and only a fixed pattern of
|
|
18
|
+
* paths are allowed:
|
|
19
|
+
*
|
|
20
|
+
* ```bnf
|
|
21
|
+
* AT-URI = "at://" AUTHORITY [ "/" COLLECTION [ "/" RKEY ] ]
|
|
22
|
+
*
|
|
23
|
+
* AUTHORITY = HANDLE | DID
|
|
24
|
+
* COLLECTION = NSID
|
|
25
|
+
* RKEY = RECORD-KEY
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* The authority section is required, and should be normalized.
|
|
29
|
+
*
|
|
30
|
+
* AT URI strings must respect the following syntax (as prescribed by the AT
|
|
31
|
+
* protocol specification):
|
|
32
|
+
*
|
|
33
|
+
* - The overall URI is restricted to a subset of ASCII characters
|
|
34
|
+
* - For reference below, the set of unreserved characters, as defined in [RFC-3986](https://www.rfc-editor.org/rfc/rfc3986), includes alphanumeric (`A-Za-z0-9`), period, hyphen, underscore, and tilde (`.-_~`)
|
|
35
|
+
* - Maximum overall length is 8 kilobytes (which may be shortened in the future)
|
|
36
|
+
* - Hex-encoding of characters is permitted (but in practice not necessary and should be avoided to keep the URI normalized and human-readable)
|
|
37
|
+
* - The URI scheme is `at`, and an authority part preceded with double slashes is always required. AT URIs always start with `at://`.
|
|
38
|
+
* - An authority section is required and must be non-empty. the authority can be either an atproto Handle, or a DID meeting the restrictions for use with atproto. The authority part can *not* be interpreted as a host:port pair, because of the use of colon characters (`:`) in DIDs. Colons and unreserved characters should not be escaped in DIDs, but other reserved characters (including `#`, `/`, `$`, `&`, `@`) must be escaped.
|
|
39
|
+
* - Note that none of the current "blessed" DID methods for atproto allow these characters in DID identifiers
|
|
40
|
+
* - An optional path section may follow the authority. The path may contain multiple segments separated by a single slash (`/`). Generic URI path normalization rules may be used.
|
|
41
|
+
* - An optional query part is allowed, following generic URI syntax restrictions
|
|
42
|
+
* - An optional fragment part is allowed, using JSON Path syntax
|
|
43
|
+
*
|
|
44
|
+
* @example "at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.actor.profile/self"
|
|
45
|
+
*
|
|
46
|
+
* @see {@link https://atproto.com/specs/at-uri-scheme AT protocol - AT URI Scheme}
|
|
47
|
+
*/
|
|
48
|
+
export type AtUriString = AtUriStringBase | `${AtUriStringBase}${AtUriStringFragment}`;
|
|
49
|
+
/**
|
|
50
|
+
* Type guard that checks if a value is a valid {@link AtUriString}
|
|
51
|
+
*
|
|
52
|
+
* @see {@link AtUriString}
|
|
53
|
+
*/
|
|
54
|
+
export declare function isAtUriString<I>(input: I, options?: Omit<ParseAtUriStringOptions, 'detailed'>): input is I & AtUriString;
|
|
55
|
+
/**
|
|
56
|
+
* Returns the input if it is a valid {@link AtUriString} format string, or
|
|
57
|
+
* `undefined` if it is not.
|
|
58
|
+
*
|
|
59
|
+
* @see {@link AtUriString}
|
|
60
|
+
*/
|
|
61
|
+
export declare function ifAtUriString<I>(input: I, options?: Omit<ParseAtUriStringOptions, 'detailed'>): undefined | (I & AtUriString);
|
|
62
|
+
/**
|
|
63
|
+
* Casts a string to an {@link AtUriString} if it is a valid AT URI format
|
|
64
|
+
* string, throwing an error if it is not.
|
|
65
|
+
*
|
|
66
|
+
* @throws InvalidAtUriError if the input string does not meet the atproto AT URI format requirements.
|
|
67
|
+
* @see {@link AtUriString}
|
|
68
|
+
*/
|
|
69
|
+
export declare function asAtUriString<I>(input: I, options?: ParseAtUriStringOptions): I & AtUriString;
|
|
70
|
+
/**
|
|
71
|
+
* Assert the validity of an {@link AtUriString}, throwing an error if the
|
|
72
|
+
* {@link input} is not a valid AT URI.
|
|
73
|
+
*
|
|
74
|
+
* @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}
|
|
75
|
+
*/
|
|
76
|
+
export declare function assertAtUriString<I>(input: I, options?: ParseAtUriStringOptions): asserts input is I & AtUriString;
|
|
77
|
+
/**
|
|
78
|
+
* Assert the **non-strict** validity of an {@link AtUriString}, throwing a
|
|
79
|
+
* detailed error if the {@link input} is not a valid AT URI.
|
|
80
|
+
*
|
|
81
|
+
* @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}
|
|
82
|
+
* @deprecated use {@link assertAtUriString} with `{ strict: false }` option instead
|
|
83
|
+
*/
|
|
84
|
+
export declare function ensureValidAtUri<I>(input: I): asserts input is I & AtUriString;
|
|
85
|
+
/**
|
|
86
|
+
* Assert the (non-strict!) validity of an {@link AtUriString}, throwing an
|
|
87
|
+
* error if the {@link input} is not a valid AT URI.
|
|
88
|
+
*
|
|
89
|
+
* @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}
|
|
90
|
+
* @deprecated use {@link assertAtUriString} with `{ strict: false }` option instead
|
|
91
|
+
*/
|
|
92
|
+
export declare function ensureValidAtUriRegex<I>(input: I): asserts input is I & AtUriString;
|
|
93
|
+
/**
|
|
94
|
+
* Type guard that checks if a value is a valid {@link AtUriString} format
|
|
95
|
+
* string, without enforcing strict record key validation. This is useful for
|
|
96
|
+
* cases where you want to allow a wider range of valid ATURIs, such as when
|
|
97
|
+
* validating user input or when the record key is not relevant.
|
|
98
|
+
*
|
|
99
|
+
* @deprecated use {@link isAtUriString} with `{ strict: false }` option instead
|
|
100
|
+
*/
|
|
101
|
+
export declare function isValidAtUri<I>(input: I): input is I & AtUriString;
|
|
102
|
+
export declare class InvalidAtUriError extends Error {
|
|
103
|
+
}
|
|
104
|
+
export type ParseAtUriStringOptions = {
|
|
105
|
+
/**
|
|
106
|
+
* If true, the parser will enforce that the record key (rkey) part of the URI
|
|
107
|
+
* is a valid record key (validated by {@link isValidRecordKey}). If false,
|
|
108
|
+
* any non-empty string of allowed chars will be accepted as a record key.
|
|
109
|
+
*
|
|
110
|
+
* @default true
|
|
111
|
+
*/
|
|
112
|
+
strict?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* If true, the parser will return detailed error messages for why a string is
|
|
115
|
+
* not a valid AT URI. This option has no effect on the behavior of
|
|
116
|
+
* {@link isAtUriString}, which will always return false for invalid strings
|
|
117
|
+
* regardless of this option.
|
|
118
|
+
*
|
|
119
|
+
* @default false
|
|
120
|
+
*/
|
|
121
|
+
detailed?: boolean;
|
|
122
|
+
};
|
|
123
|
+
export type AtUriParts = {
|
|
124
|
+
authority: AtIdentifierString;
|
|
125
|
+
query?: string;
|
|
126
|
+
hash?: string;
|
|
127
|
+
} & ({
|
|
128
|
+
collection?: NsidString;
|
|
129
|
+
rkey?: undefined;
|
|
130
|
+
} | {
|
|
131
|
+
collection: NsidString;
|
|
132
|
+
rkey?: string;
|
|
133
|
+
});
|
|
134
|
+
/**
|
|
135
|
+
* Parses a valid {@link AtUriString} into a {@link AtUriParts} object, or
|
|
136
|
+
* returns a failure with a detailed error message if the string is not a valid
|
|
137
|
+
* {@link AtUriString}.
|
|
138
|
+
*/
|
|
139
|
+
export declare function parseAtUriString(input: unknown, options?: ParseAtUriStringOptions): Result<AtUriParts>;
|
|
7
140
|
//# sourceMappingURL=aturi_validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aturi_validation.d.ts","sourceRoot":"","sources":["../src/aturi_validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"aturi_validation.d.ts","sourceRoot":"","sources":["../src/aturi_validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAwB,MAAM,oBAAoB,CAAA;AAC7E,OAAO,EAAE,MAAM,EAAoB,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,UAAU,EAAe,MAAM,WAAW,CAAA;AAGnD,MAAM,MAAM,eAAe,GACvB,QAAQ,kBAAkB,EAAE,GAC5B,QAAQ,kBAAkB,IAAI,UAAU,EAAE,GAC1C,QAAQ,kBAAkB,IAAI,UAAU,IAAI,MAAM,EAAE,CAAA;AAExD,MAAM,MAAM,mBAAmB,GAAG,KAAK,MAAM,EAAE,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,MAAM,WAAW,GACnB,eAAe,GACf,GAAG,eAAe,GAAG,mBAAmB,EAAE,CAAA;AAE9C;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,GAClD,KAAK,IAAI,CAAC,GAAG,WAAW,CAE1B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,GAClD,SAAS,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC,CAE/B;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,uBAAuB,GAChC,CAAC,GAAG,WAAW,CAGjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,WAAW,CASlC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,WAAW,CAElC;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,WAAW,CAElC;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,WAAW,CAElE;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,kBAAkB,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,CACA;IAAE,UAAU,CAAC,EAAE,UAAU,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,GAC7C;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAC5C,CAAA;AAMD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,uBAAuB,GAChC,MAAM,CAAC,UAAU,CAAC,CAsFpB"}
|
package/dist/aturi_validation.js
CHANGED
|
@@ -1,118 +1,200 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InvalidAtUriError = void 0;
|
|
4
|
+
exports.isAtUriString = isAtUriString;
|
|
5
|
+
exports.ifAtUriString = ifAtUriString;
|
|
6
|
+
exports.asAtUriString = asAtUriString;
|
|
7
|
+
exports.assertAtUriString = assertAtUriString;
|
|
3
8
|
exports.ensureValidAtUri = ensureValidAtUri;
|
|
4
9
|
exports.ensureValidAtUriRegex = ensureValidAtUriRegex;
|
|
5
10
|
exports.isValidAtUri = isValidAtUri;
|
|
11
|
+
exports.parseAtUriString = parseAtUriString;
|
|
6
12
|
const at_identifier_js_1 = require("./at-identifier.js");
|
|
7
|
-
const
|
|
8
|
-
const handle_js_1 = require("./handle.js");
|
|
13
|
+
const result_js_1 = require("./lib/result.js");
|
|
9
14
|
const nsid_js_1 = require("./nsid.js");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
const recordkey_js_1 = require("./recordkey.js");
|
|
16
|
+
/**
|
|
17
|
+
* Type guard that checks if a value is a valid {@link AtUriString}
|
|
18
|
+
*
|
|
19
|
+
* @see {@link AtUriString}
|
|
20
|
+
*/
|
|
21
|
+
function isAtUriString(input, options) {
|
|
22
|
+
return parseAtUriString(input, options).success;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns the input if it is a valid {@link AtUriString} format string, or
|
|
26
|
+
* `undefined` if it is not.
|
|
27
|
+
*
|
|
28
|
+
* @see {@link AtUriString}
|
|
29
|
+
*/
|
|
30
|
+
function ifAtUriString(input, options) {
|
|
31
|
+
return isAtUriString(input, options) ? input : undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Casts a string to an {@link AtUriString} if it is a valid AT URI format
|
|
35
|
+
* string, throwing an error if it is not.
|
|
36
|
+
*
|
|
37
|
+
* @throws InvalidAtUriError if the input string does not meet the atproto AT URI format requirements.
|
|
38
|
+
* @see {@link AtUriString}
|
|
39
|
+
*/
|
|
40
|
+
function asAtUriString(input, options) {
|
|
41
|
+
assertAtUriString(input, options);
|
|
42
|
+
return input;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Assert the validity of an {@link AtUriString}, throwing an error if the
|
|
46
|
+
* {@link input} is not a valid AT URI.
|
|
47
|
+
*
|
|
48
|
+
* @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}
|
|
49
|
+
*/
|
|
50
|
+
function assertAtUriString(input, options) {
|
|
51
|
+
// Optimistically use faster isAtUriString(), throwing a detailed error only
|
|
52
|
+
// in case of failure. This check, and the fact that the code after it always
|
|
53
|
+
// throws, also ensures that isAtUriString() and assertAtUriString()'s
|
|
54
|
+
// behavior are always consistent.
|
|
55
|
+
const result = parseAtUriString(input, options);
|
|
56
|
+
if (!result.success) {
|
|
57
|
+
throw new InvalidAtUriError(result.message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Assert the **non-strict** validity of an {@link AtUriString}, throwing a
|
|
62
|
+
* detailed error if the {@link input} is not a valid AT URI.
|
|
63
|
+
*
|
|
64
|
+
* @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}
|
|
65
|
+
* @deprecated use {@link assertAtUriString} with `{ strict: false }` option instead
|
|
66
|
+
*/
|
|
23
67
|
function ensureValidAtUri(input) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
68
|
+
assertAtUriString(input, { strict: false, detailed: true });
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Assert the (non-strict!) validity of an {@link AtUriString}, throwing an
|
|
72
|
+
* error if the {@link input} is not a valid AT URI.
|
|
73
|
+
*
|
|
74
|
+
* @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}
|
|
75
|
+
* @deprecated use {@link assertAtUriString} with `{ strict: false }` option instead
|
|
76
|
+
*/
|
|
77
|
+
function ensureValidAtUriRegex(input) {
|
|
78
|
+
assertAtUriString(input, { strict: false, detailed: false });
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Type guard that checks if a value is a valid {@link AtUriString} format
|
|
82
|
+
* string, without enforcing strict record key validation. This is useful for
|
|
83
|
+
* cases where you want to allow a wider range of valid ATURIs, such as when
|
|
84
|
+
* validating user input or when the record key is not relevant.
|
|
85
|
+
*
|
|
86
|
+
* @deprecated use {@link isAtUriString} with `{ strict: false }` option instead
|
|
87
|
+
*/
|
|
88
|
+
function isValidAtUri(input) {
|
|
89
|
+
return isAtUriString(input, { strict: false });
|
|
90
|
+
}
|
|
91
|
+
class InvalidAtUriError extends Error {
|
|
92
|
+
}
|
|
93
|
+
exports.InvalidAtUriError = InvalidAtUriError;
|
|
94
|
+
const INVALID_CHAR_REGEXP = /[^a-zA-Z0-9._~:@!$&'()*+,;=%/\\[\]#?-]/;
|
|
95
|
+
const AT_URI_REGEXP = /^(?<uri>at:\/\/(?<authority>[^/?#\s]+)(?:\/(?<collection>[^/?#\s]+)(?:\/(?<rkey>[^/?#\s]+))?)?(?<trailingSlash>\/)?)(?:\?(?<query>[^#\s]*))?(?:#(?<hash>[^\s]*))?$/;
|
|
96
|
+
/**
|
|
97
|
+
* Parses a valid {@link AtUriString} into a {@link AtUriParts} object, or
|
|
98
|
+
* returns a failure with a detailed error message if the string is not a valid
|
|
99
|
+
* {@link AtUriString}.
|
|
100
|
+
*/
|
|
101
|
+
function parseAtUriString(input, options) {
|
|
102
|
+
if (typeof input !== 'string') {
|
|
103
|
+
return (0, result_js_1.failure)('ATURI must be a string');
|
|
104
|
+
}
|
|
105
|
+
if (input.length > 8192) {
|
|
106
|
+
return (0, result_js_1.failure)('ATURI exceeds maximum length');
|
|
107
|
+
}
|
|
108
|
+
const invalidChar = input.match(INVALID_CHAR_REGEXP);
|
|
109
|
+
if (invalidChar) {
|
|
110
|
+
return (0, result_js_1.failure)('Disallowed characters in ATURI (ASCII)');
|
|
111
|
+
}
|
|
112
|
+
const match = input.match(AT_URI_REGEXP);
|
|
113
|
+
const groups = match?.groups;
|
|
114
|
+
if (!groups) {
|
|
115
|
+
// Regex validation failed, but we don't know exactly why. Provide more
|
|
116
|
+
// detailed error messages if the "detailed" option is set, falling back to
|
|
117
|
+
// a generic error.
|
|
118
|
+
if (options?.detailed) {
|
|
119
|
+
if (!input.startsWith('at://')) {
|
|
120
|
+
return (0, result_js_1.failure)('ATURI must start with "at://"');
|
|
121
|
+
}
|
|
122
|
+
if (input.includes(' ')) {
|
|
123
|
+
return (0, result_js_1.failure)('ATURI can not contain spaces');
|
|
124
|
+
}
|
|
125
|
+
if (input.includes('//', 5)) {
|
|
126
|
+
return (0, result_js_1.failure)('ATURI can not have empty path segments');
|
|
127
|
+
}
|
|
128
|
+
const pathStart = input.indexOf('/', 5); // after "at://"
|
|
129
|
+
if (pathStart !== -1) {
|
|
130
|
+
const fragmentIndex = input.indexOf('#');
|
|
131
|
+
const pathEnd = fragmentIndex !== -1 ? fragmentIndex : input.length;
|
|
132
|
+
const secondSlash = input.indexOf('/', pathStart + 1);
|
|
133
|
+
if (secondSlash !== -1 && secondSlash !== pathEnd - 1) {
|
|
134
|
+
return (0, result_js_1.failure)('ATURI can not have more than two path segments');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
28
137
|
}
|
|
29
|
-
|
|
30
|
-
|
|
138
|
+
return (0, result_js_1.failure)('ATURI does not match expected format');
|
|
139
|
+
}
|
|
140
|
+
// @NOTE Percent-encoding is allowed by the AT URI specification, but any
|
|
141
|
+
// percent-encoded characters appearing in the collection NSID or record key
|
|
142
|
+
// will effectively be rejected by the isValidNsid and isValidRecordKey
|
|
143
|
+
// validators. Since these values are defined to be plain ASCII identifiers,
|
|
144
|
+
// this legacy behavior is beneficial: it ensures that normalized
|
|
145
|
+
// (non-percent-encoded) values are always used, as prescribed by the spec.
|
|
146
|
+
if (!(0, at_identifier_js_1.isAtIdentifierString)(groups.authority)) {
|
|
147
|
+
return (0, result_js_1.failure)('ATURI has invalid authority');
|
|
148
|
+
}
|
|
149
|
+
if (groups.collection != null && !(0, nsid_js_1.isValidNsid)(groups.collection)) {
|
|
150
|
+
return (0, result_js_1.failure)('ATURI has invalid collection');
|
|
151
|
+
}
|
|
152
|
+
if (groups.hash != null) {
|
|
153
|
+
const result = parseJsonPointer(groups.hash, options);
|
|
154
|
+
if (result.success) {
|
|
155
|
+
groups.hash = result.value;
|
|
31
156
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!/^\/[a-zA-Z0-9._~:@!$&')(*+,;=%[\]/-]*$/.test(fragment)) {
|
|
35
|
-
throw new Error('Disallowed characters in ATURI fragment (ASCII)');
|
|
157
|
+
else {
|
|
158
|
+
return (0, result_js_1.failure)(`ATURI has invalid fragment (${result.message})`);
|
|
36
159
|
}
|
|
37
160
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
if (!uri.startsWith('at://')) {
|
|
43
|
-
throw new Error('ATURI must start with "at://"');
|
|
44
|
-
}
|
|
45
|
-
// check that all chars are boring ASCII
|
|
46
|
-
if (!/^[a-zA-Z0-9._~:@!$&')(*+,;=%/-]*$/.test(uri)) {
|
|
47
|
-
throw new Error('Disallowed characters in ATURI (ASCII)');
|
|
48
|
-
}
|
|
49
|
-
const authorityEnd = uri.indexOf('/', 5);
|
|
50
|
-
const authority = authorityEnd === -1 ? uri.slice(5) : uri.slice(5, authorityEnd);
|
|
51
|
-
try {
|
|
52
|
-
(0, at_identifier_js_1.ensureValidAtIdentifier)(authority);
|
|
53
|
-
}
|
|
54
|
-
catch (cause) {
|
|
55
|
-
throw new Error('ATURI authority must be a valid handle or DID', { cause });
|
|
56
|
-
}
|
|
57
|
-
const collectionStart = authorityEnd === -1 ? -1 : authorityEnd + 1;
|
|
58
|
-
const collectionEnd = collectionStart === -1 ? -1 : uri.indexOf('/', collectionStart);
|
|
59
|
-
if (collectionStart !== -1) {
|
|
60
|
-
const collection = collectionEnd === -1
|
|
61
|
-
? uri.slice(collectionStart)
|
|
62
|
-
: uri.slice(collectionStart, collectionEnd);
|
|
63
|
-
if (collection.length === 0) {
|
|
64
|
-
throw new Error('ATURI can not have a slash after authority without a path segment');
|
|
161
|
+
if (options?.strict !== false) {
|
|
162
|
+
if (groups.trailingSlash != null) {
|
|
163
|
+
return (0, result_js_1.failure)('ATURI can not have a trailing slash');
|
|
65
164
|
}
|
|
66
|
-
if (
|
|
67
|
-
|
|
165
|
+
if (groups.query != null) {
|
|
166
|
+
return (0, result_js_1.failure)('ATURI query part is not allowed');
|
|
68
167
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const recordKeyEnd = recordKeyStart === -1 ? -1 : uri.indexOf('/', recordKeyStart);
|
|
72
|
-
if (recordKeyStart !== -1) {
|
|
73
|
-
if (recordKeyStart === uri.length) {
|
|
74
|
-
throw new Error('ATURI can not have a slash after collection, unless record key is provided');
|
|
168
|
+
if (groups.rkey != null && !(0, recordkey_js_1.isValidRecordKey)(groups.rkey)) {
|
|
169
|
+
return (0, result_js_1.failure)('ATURI has invalid record key');
|
|
75
170
|
}
|
|
76
|
-
// would validate rkey here, but there are basically no constraints!
|
|
77
|
-
}
|
|
78
|
-
if (recordKeyEnd !== -1) {
|
|
79
|
-
throw new Error('ATURI path can have at most two parts, and no trailing slash');
|
|
80
171
|
}
|
|
172
|
+
return (0, result_js_1.success)(groups);
|
|
81
173
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
catch {
|
|
99
|
-
throw new Error('ATURI authority must be a valid handle or DID');
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (groups.collection && !(0, nsid_js_1.isValidNsid)(groups.collection)) {
|
|
103
|
-
throw new Error('ATURI collection path segment must be a valid NSID');
|
|
104
|
-
}
|
|
105
|
-
if (input.length > 8 * 1024) {
|
|
106
|
-
throw new Error('ATURI is far too long');
|
|
107
|
-
}
|
|
174
|
+
const BASIC_JSON_POINTER_REGEXP = /^\/[a-zA-Z0-9._~:@!$&')(*+,;=%[\]/-]*$/;
|
|
175
|
+
/**
|
|
176
|
+
* Checks if a string is a valid JSON pointer (RFC-6901) with the allowed chars
|
|
177
|
+
* for ATURI fragments. This is a very loose validation that only checks the
|
|
178
|
+
* basic syntax and charset.
|
|
179
|
+
*/
|
|
180
|
+
function parseJsonPointer(value, options) {
|
|
181
|
+
if (!BASIC_JSON_POINTER_REGEXP.test(value)) {
|
|
182
|
+
return (0, result_js_1.failure)('Invalid JSON pointer');
|
|
183
|
+
}
|
|
184
|
+
const result = parsePercentEncoding(value);
|
|
185
|
+
// In non-strict mode, we allow invalid percent-encoding in the fragment
|
|
186
|
+
if (!result.success && options?.strict === false) {
|
|
187
|
+
return (0, result_js_1.success)(value);
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
108
190
|
}
|
|
109
|
-
function
|
|
191
|
+
function parsePercentEncoding(value) {
|
|
110
192
|
try {
|
|
111
|
-
|
|
193
|
+
return (0, result_js_1.success)(decodeURIComponent(value));
|
|
112
194
|
}
|
|
113
195
|
catch {
|
|
114
|
-
|
|
196
|
+
// decodeURIComponent throws if the percent-encoding is invalid (e.g. "%FF")
|
|
197
|
+
return (0, result_js_1.failure)('Invalid percent-encoding');
|
|
115
198
|
}
|
|
116
|
-
return true;
|
|
117
199
|
}
|
|
118
200
|
//# sourceMappingURL=aturi_validation.js.map
|