Suite of AT Protocol TypeScript libraries built on web standards
20
fork

Configure Feed

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

at main 126 lines 3.3 kB view raw
1import { 2 assertStringFormat, 3 type InferStringFormat, 4 type StringFormat, 5} from "../core/string-format.ts"; 6import { 7 Schema, 8 type ValidationResult, 9 type ValidatorContext, 10} from "../validation.ts"; 11import { graphemeLen, utf8Len } from "../data/strings.ts"; 12import { asCid } from "../data/cid.ts"; 13import { TokenSchema } from "./token.ts"; 14 15export type StringSchemaOptions = { 16 default?: string; 17 format?: StringFormat; 18 minLength?: number; 19 maxLength?: number; 20 minGraphemes?: number; 21 maxGraphemes?: number; 22}; 23 24export type StringSchemaOutput<Options> = Options extends 25 { format: infer F extends StringFormat } ? InferStringFormat<F> 26 : string; 27 28export class StringSchema< 29 const Options extends StringSchemaOptions, 30> extends Schema<StringSchemaOutput<Options>> { 31 constructor(readonly options: Options) { 32 super(); 33 } 34 35 validateInContext( 36 input: unknown = this.options.default, 37 ctx: ValidatorContext, 38 ): ValidationResult<StringSchemaOutput<Options>> { 39 const { options } = this; 40 41 const str = coerceToString(input); 42 if (str == null) { 43 return ctx.issueInvalidType(input, "string"); 44 } 45 46 let lazyUtf8Len: number; 47 48 const { minLength } = options; 49 if (minLength != null) { 50 if ((lazyUtf8Len ??= utf8Len(str)) < minLength) { 51 return ctx.issueTooSmall(str, "string", minLength, lazyUtf8Len); 52 } 53 } 54 55 const { maxLength } = options; 56 if (maxLength != null) { 57 if (str.length * 3 <= maxLength) { 58 // too small to exceed maxLength 59 } else if ((lazyUtf8Len ??= utf8Len(str)) > maxLength) { 60 return ctx.issueTooBig(str, "string", maxLength, lazyUtf8Len); 61 } 62 } 63 64 let lazyGraphLen: number; 65 66 const { minGraphemes } = options; 67 if (minGraphemes != null) { 68 if (str.length < minGraphemes) { 69 return ctx.issueTooSmall(str, "grapheme", minGraphemes, str.length); 70 } else if ((lazyGraphLen ??= graphemeLen(str)) < minGraphemes) { 71 return ctx.issueTooSmall(str, "grapheme", minGraphemes, lazyGraphLen); 72 } 73 } 74 75 const { maxGraphemes } = options; 76 if (maxGraphemes != null) { 77 if ((lazyGraphLen ??= graphemeLen(str)) > maxGraphemes) { 78 return ctx.issueTooBig(str, "grapheme", maxGraphemes, lazyGraphLen); 79 } 80 } 81 82 if (options.format !== undefined) { 83 try { 84 assertStringFormat(str, options.format); 85 } catch (err) { 86 const message = err instanceof Error ? err.message : undefined; 87 return ctx.issueInvalidFormat(str, options.format, message); 88 } 89 } 90 91 return ctx.success(str as StringSchemaOutput<Options>); 92 } 93} 94 95export function coerceToString(input: unknown): string | null { 96 switch (typeof input) { 97 case "string": 98 return input; 99 case "object": { 100 if (input == null) return null; 101 102 if (input instanceof TokenSchema) { 103 return input.toString(); 104 } 105 106 if (input instanceof Date) { 107 if (Number.isNaN(input.getTime())) return null; 108 return input.toISOString(); 109 } 110 111 if (input instanceof URL) { 112 return input.toString(); 113 } 114 115 const cid = asCid(input); 116 if (cid) return cid.toString(); 117 118 if (input instanceof String) { 119 return input.valueOf(); 120 } 121 } 122 // falls through 123 default: 124 return null; 125 } 126}