[fix] allow loading files (#3517)

I messed up the schema validator for loading files.

### Change Type

<!--  Please select a 'Scope' label ️ -->

- [x] `sdk` — Changes the tldraw SDK
- [ ] `dotcom` — Changes the tldraw.com web app
- [ ] `docs` — Changes to the documentation, examples, or templates.
- [ ] `vs code` — Changes to the vscode plugin
- [ ] `internal` — Does not affect user-facing stuff

<!--  Please select a 'Type' label ️ -->

- [x] `bugfix` — Bug fix
- [ ] `feature` — New feature
- [ ] `improvement` — Improving existing features
- [ ] `chore` — Updating dependencies, other boring stuff
- [ ] `galaxy brain` — Architectural changes
- [ ] `tests` — Changes to any test code
- [ ] `tools` — Changes to infrastructure, CI, internal scripts,
debugging tools, etc.
- [ ] `dunno` — I don't know
This commit is contained in:
David Sheldrick 2024-04-17 20:38:31 +01:00 committed by GitHub
parent f70fd2729d
commit 625f4abc3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 80 additions and 14 deletions

View file

@ -62,7 +62,7 @@ const schemaV2 = T.object<SerializedSchemaV2>({
const tldrawFileValidator: T.Validator<TldrawFile> = T.object({ const tldrawFileValidator: T.Validator<TldrawFile> = T.object({
tldrawFileFormatVersion: T.nonZeroInteger, tldrawFileFormatVersion: T.nonZeroInteger,
schema: T.union('schemaVersion', { schema: T.numberUnion('schemaVersion', {
1: schemaV1, 1: schemaV1,
2: schemaV2, 2: schemaV2,
}), }),

View file

@ -83,6 +83,9 @@ function nullable<T>(validator: Validatable<T>): Validator<null | T>;
// @public // @public
const number: Validator<number>; const number: Validator<number>;
// @internal (undocumented)
function numberUnion<Key extends string, Config extends UnionValidatorConfig<Key, Config>>(key: Key, config: Config): UnionValidator<Key, Config>;
// @public // @public
function object<Shape extends object>(config: { function object<Shape extends object>(config: {
readonly [K in keyof Shape]: Validatable<Shape[K]>; readonly [K in keyof Shape]: Validatable<Shape[K]>;
@ -134,6 +137,7 @@ declare namespace T {
jsonDict, jsonDict,
dict, dict,
union, union,
numberUnion,
model, model,
setEnum, setEnum,
optional, optional,
@ -178,7 +182,7 @@ function union<Key extends string, Config extends UnionValidatorConfig<Key, Conf
// @public (undocumented) // @public (undocumented)
export class UnionValidator<Key extends string, Config extends UnionValidatorConfig<Key, Config>, UnknownValue = never> extends Validator<TypeOf<Config[keyof Config]> | UnknownValue> { export class UnionValidator<Key extends string, Config extends UnionValidatorConfig<Key, Config>, UnknownValue = never> extends Validator<TypeOf<Config[keyof Config]> | UnknownValue> {
constructor(key: Key, config: Config, unknownValueValidation: (value: object, variant: string) => UnknownValue); constructor(key: Key, config: Config, unknownValueValidation: (value: object, variant: string) => UnknownValue, useNumberKeys: boolean);
// (undocumented) // (undocumented)
validateUnknownVariants<Unknown>(unknownValueValidation: (value: object, variant: string) => Unknown): UnionValidator<Key, Config, Unknown>; validateUnknownVariants<Unknown>(unknownValueValidation: (value: object, variant: string) => Unknown): UnionValidator<Key, Config, Unknown>;
} }

View file

@ -3027,6 +3027,14 @@
"kind": "Content", "kind": "Content",
"text": "(value: object, variant: string) => UnknownValue" "text": "(value: object, variant: string) => UnknownValue"
}, },
{
"kind": "Content",
"text": ", useNumberKeys: "
},
{
"kind": "Content",
"text": "boolean"
},
{ {
"kind": "Content", "kind": "Content",
"text": ");" "text": ");"
@ -3059,6 +3067,14 @@
"endIndex": 6 "endIndex": 6
}, },
"isOptional": false "isOptional": false
},
{
"parameterName": "useNumberKeys",
"parameterTypeTokenRange": {
"startIndex": 7,
"endIndex": 8
},
"isOptional": false
} }
] ]
}, },
@ -4260,6 +4276,14 @@
"kind": "Content", "kind": "Content",
"text": "(value: object, variant: string) => UnknownValue" "text": "(value: object, variant: string) => UnknownValue"
}, },
{
"kind": "Content",
"text": ", useNumberKeys: "
},
{
"kind": "Content",
"text": "boolean"
},
{ {
"kind": "Content", "kind": "Content",
"text": ");" "text": ");"
@ -4292,6 +4316,14 @@
"endIndex": 6 "endIndex": 6
}, },
"isOptional": false "isOptional": false
},
{
"parameterName": "useNumberKeys",
"parameterTypeTokenRange": {
"startIndex": 7,
"endIndex": 8
},
"isOptional": false
} }
] ]
}, },

View file

@ -394,7 +394,8 @@ export class UnionValidator<
constructor( constructor(
private readonly key: Key, private readonly key: Key,
private readonly config: Config, private readonly config: Config,
private readonly unknownValueValidation: (value: object, variant: string) => UnknownValue private readonly unknownValueValidation: (value: object, variant: string) => UnknownValue,
private readonly useNumberKeys: boolean
) { ) {
super( super(
(input) => { (input) => {
@ -442,11 +443,13 @@ export class UnionValidator<
matchingSchema: Validatable<any> | undefined matchingSchema: Validatable<any> | undefined
variant: string variant: string
} { } {
const variant = getOwnProperty(object, this.key) as keyof Config | undefined const variant = getOwnProperty(object, this.key) as string & keyof Config
if (typeof variant !== 'string') { if (!this.useNumberKeys && typeof variant !== 'string') {
throw new ValidationError( throw new ValidationError(
`Expected a string for key "${this.key}", got ${typeToString(variant)}` `Expected a string for key "${this.key}", got ${typeToString(variant)}`
) )
} else if (this.useNumberKeys && !Number.isFinite(Number(variant))) {
throw new ValidationError(`Expected a number for key "${this.key}", got "${variant as any}"`)
} }
const matchingSchema = hasOwnProperty(this.config, variant) ? this.config[variant] : undefined const matchingSchema = hasOwnProperty(this.config, variant) ? this.config[variant] : undefined
@ -456,7 +459,7 @@ export class UnionValidator<
validateUnknownVariants<Unknown>( validateUnknownVariants<Unknown>(
unknownValueValidation: (value: object, variant: string) => Unknown unknownValueValidation: (value: object, variant: string) => Unknown
): UnionValidator<Key, Config, Unknown> { ): UnionValidator<Key, Config, Unknown> {
return new UnionValidator(this.key, this.config, unknownValueValidation) return new UnionValidator(this.key, this.config, unknownValueValidation, this.useNumberKeys)
} }
} }
@ -829,14 +832,41 @@ export function union<Key extends string, Config extends UnionValidatorConfig<Ke
key: Key, key: Key,
config: Config config: Config
): UnionValidator<Key, Config> { ): UnionValidator<Key, Config> {
return new UnionValidator(key, config, (unknownValue, unknownVariant) => { return new UnionValidator(
key,
config,
(unknownValue, unknownVariant) => {
throw new ValidationError( throw new ValidationError(
`Expected one of ${Object.keys(config) `Expected one of ${Object.keys(config)
.map((key) => JSON.stringify(key)) .map((key) => JSON.stringify(key))
.join(' or ')}, got ${JSON.stringify(unknownVariant)}`, .join(' or ')}, got ${JSON.stringify(unknownVariant)}`,
[key] [key]
) )
}) },
false
)
}
/**
* @internal
*/
export function numberUnion<Key extends string, Config extends UnionValidatorConfig<Key, Config>>(
key: Key,
config: Config
): UnionValidator<Key, Config> {
return new UnionValidator(
key,
config,
(unknownValue, unknownVariant) => {
throw new ValidationError(
`Expected one of ${Object.keys(config)
.map((key) => JSON.stringify(key))
.join(' or ')}, got ${JSON.stringify(unknownVariant)}`,
[key]
)
},
true
)
} }
/** /**