a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm
101
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(lexicons)!: make at-uri parsing throw

Mary a86b3beb b3cdf4b6

+37 -37
+5
.changeset/lucky-numbers-work.md
··· 1 + --- 2 + '@atcute/lexicons': major 3 + --- 4 + 5 + make at-uri parsing throw
+20 -21
packages/lexicons/lexicons/lib/syntax/at-uri.test.ts
··· 1 - import { assert, describe, expect, it } from 'vitest'; 1 + import { describe, expect, it } from 'vitest'; 2 2 3 3 import { 4 - isResourceUri, 5 - parseResourceUri, 6 4 isCanonicalResourceUri, 5 + isResourceUri, 7 6 parseCanonicalResourceUri, 7 + parseResourceUri, 8 8 } from './at-uri.js'; 9 9 10 10 describe('resourceUri validation', () => { ··· 47 47 ]; 48 48 for (const str of validCases) { 49 49 expect(isResourceUri(str), str).toBe(true); 50 - 51 - expect(parseResourceUri(str).ok, str).toBe(true); 50 + expect(() => parseResourceUri(str), str).not.toThrow(); 52 51 } 53 52 54 53 const invalidCases = [ ··· 106 105 ]; 107 106 for (const str of invalidCases) { 108 107 expect(isResourceUri(str), str).toBe(false); 109 - 110 - expect(parseResourceUri(str).ok, str).toBe(false); 108 + expect(() => parseResourceUri(str), str).toThrow(); 111 109 } 112 110 113 111 expect(isResourceUri(null)).toBe(false); ··· 116 114 it('parses valid at-uris', () => { 117 115 const result = parseResourceUri('at://did:plc:asdf123/com.atproto.feed.post/record'); 118 116 119 - assert(result.ok); 120 - expect(result.value).toEqual({ 117 + expect(result).toEqual({ 121 118 repo: 'did:plc:asdf123', 122 119 collection: 'com.atproto.feed.post', 123 120 rkey: 'record', ··· 127 124 128 125 it('parses at-uri with fragment', () => { 129 126 const result = parseResourceUri('at://did:plc:asdf123/com.atproto.feed.post/record#/fragment'); 130 - assert(result.ok); 131 - expect(result.value).toEqual({ 127 + 128 + expect(result).toEqual({ 132 129 repo: 'did:plc:asdf123', 133 130 collection: 'com.atproto.feed.post', 134 131 rkey: 'record', ··· 137 134 }); 138 135 139 136 it('returns error for invalid at-uri', () => { 140 - const result = parseResourceUri('invalid-uri'); 141 - assert(!result.ok); 142 - expect(result.error).toContain('invalid at-uri'); 137 + expect(() => parseResourceUri('invalid-uri')).toThrowErrorMatchingInlineSnapshot( 138 + `[SyntaxError: invalid at-uri: invalid-uri]`, 139 + ); 143 140 }); 144 141 }); 145 142 ··· 152 149 for (const str of validCases) { 153 150 expect(isCanonicalResourceUri(str), str).toBe(true); 154 151 155 - expect(parseCanonicalResourceUri(str).ok, str).toBe(true); 152 + expect(() => parseCanonicalResourceUri(str), str).not.toThrow(); 156 153 } 157 154 158 155 const invalidCases = [ ··· 164 161 for (const str of invalidCases) { 165 162 expect(isCanonicalResourceUri(str), str).toBe(false); 166 163 167 - expect(parseCanonicalResourceUri(str).ok, str).toBe(false); 164 + expect(() => parseCanonicalResourceUri(str), str).toThrow(); 168 165 } 169 166 170 167 expect(isCanonicalResourceUri(null)).toBe(false); ··· 172 169 173 170 it('parses valid canonical at-uris', () => { 174 171 const result = parseCanonicalResourceUri('at://did:plc:asdf123/com.atproto.feed.post/record'); 175 - assert(result.ok); 176 - expect(result.value).toEqual({ 172 + 173 + expect(result).toEqual({ 177 174 repo: 'did:plc:asdf123', 178 175 collection: 'com.atproto.feed.post', 179 176 rkey: 'record', ··· 182 179 }); 183 180 184 181 it('returns error for invalid canonical at-uri', () => { 185 - const result = parseCanonicalResourceUri('at://user.bsky.social/com.atproto.feed.post/record'); 186 - assert(!result.ok); 187 - expect(result.error).toContain('invalid repo in canonical-at-uri'); 182 + expect(() => { 183 + return parseCanonicalResourceUri('at://user.bsky.social/com.atproto.feed.post/record'); 184 + }).toThrowErrorMatchingInlineSnapshot( 185 + `[SyntaxError: invalid repo in canonical-at-uri: user.bsky.social]`, 186 + ); 188 187 }); 189 188 });
+12 -14
packages/lexicons/lexicons/lib/syntax/at-uri.ts
··· 3 3 import { isNsid, type Nsid } from './nsid.js'; 4 4 import { isRecordKey, type RecordKey } from './record-key.js'; 5 5 6 - import { type Result } from '../utils.js'; 7 - 8 6 /** 9 7 * represents a general AT Protocol URI, representing either an entire 10 8 * repository, a specific collection within a repository, or a record. ··· 41 39 }; 42 40 43 41 // #__NO_SIDE_EFFECTS__ 44 - export const parseResourceUri = (input: string): Result<ParsedResourceUri, string> => { 42 + export const parseResourceUri = (input: string): ParsedResourceUri => { 45 43 const match = ATURI_RE.exec(input); 46 44 if (match === null) { 47 - return { ok: false, error: `invalid at-uri: ${input}` }; 45 + throw new SyntaxError(`invalid at-uri: ${input}`); 48 46 } 49 47 50 48 const [, r, c, k, f] = match; 51 49 52 50 if (!isActorIdentifier(r)) { 53 - return { ok: false, error: `invalid repo in at-uri: ${r}` }; 51 + throw new SyntaxError(`invalid repo in at-uri: ${r}`); 54 52 } 55 53 56 54 if (c !== undefined && !isNsid(c)) { 57 - return { ok: false, error: `invalid collection in at-uri: ${c}` }; 55 + throw new SyntaxError(`invalid collection in at-uri: ${c}`); 58 56 } 59 57 60 58 if (k !== undefined && !isRecordKey(k)) { 61 - return { ok: false, error: `invalid rkey in at-uri: ${k}` }; 59 + throw new SyntaxError(`invalid rkey in at-uri: ${k}`); 62 60 } 63 61 64 - return { ok: true, value: { repo: r, collection: c, rkey: k, fragment: f } }; 62 + return { repo: r, collection: c, rkey: k, fragment: f }; 65 63 }; 66 64 67 65 /** ··· 97 95 }; 98 96 99 97 // #__NO_SIDE_EFFECTS__ 100 - export const parseCanonicalResourceUri = (input: string): Result<ParsedCanonicalResourceUri, string> => { 98 + export const parseCanonicalResourceUri = (input: string): ParsedCanonicalResourceUri => { 101 99 const match = ATURI_RE.exec(input); 102 100 if (match === null) { 103 - return { ok: false, error: `invalid canonical-at-uri: ${input}` }; 101 + throw new SyntaxError(`invalid canonical-at-uri: ${input}`); 104 102 } 105 103 106 104 const [, r, c, k, f] = match; 107 105 108 106 if (!isDid(r)) { 109 - return { ok: false, error: `invalid repo in canonical-at-uri: ${r}` }; 107 + throw new SyntaxError(`invalid repo in canonical-at-uri: ${r}`); 110 108 } 111 109 112 110 if (!isNsid(c)) { 113 - return { ok: false, error: `invalid collection in canonical-at-uri: ${c}` }; 111 + throw new SyntaxError(`invalid collection in canonical-at-uri: ${c}`); 114 112 } 115 113 116 114 if (!isRecordKey(k)) { 117 - return { ok: false, error: `invalid rkey in canonical-at-uri: ${k}` }; 115 + throw new SyntaxError(`invalid rkey in canonical-at-uri: ${k}`); 118 116 } 119 117 120 - return { ok: true, value: { repo: r, collection: c, rkey: k, fragment: f } }; 118 + return { repo: r, collection: c, rkey: k, fragment: f }; 121 119 };
-2
packages/lexicons/lexicons/lib/utils.ts
··· 1 1 import { DEV } from 'esm-env'; 2 2 3 - export type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }; 4 - 5 3 export const assert: { (condition: any, message?: string): asserts condition } = (condition, message) => { 6 4 if (!condition) { 7 5 if (DEV) {