@atproto/lexicon
Advanced tools
Comparing version 0.4.0 to 0.4.1-rc.0
# @atproto/lexicon | ||
## 0.4.1-rc.0 | ||
### Patch Changes | ||
- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`2ded0156b`](https://github.com/bluesky-social/atproto/commit/2ded0156b9adf33b9cce66583a375bff922d383b) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add the ability to instantiate a Lexicon from an iterable, and to use a Lexicon as iterable. | ||
- [#2483](https://github.com/bluesky-social/atproto/pull/2483) [`2ded0156b`](https://github.com/bluesky-social/atproto/commit/2ded0156b9adf33b9cce66583a375bff922d383b) Thanks [@matthieusieben](https://github.com/matthieusieben)! - **New Features**: | ||
1. Improved Separation of Concerns: We've restructured the XRPC HTTP call | ||
dispatcher into a distinct class. This means cleaner code organization and | ||
better clarity on responsibilities. | ||
2. Enhanced Evolutivity: With this refactor, the XRPC client is now more | ||
adaptable to various use cases. You can easily extend and customize the | ||
dispatcher perform session management, retries, and more. | ||
**Compatibility**: | ||
Most of the changes introduced in this version are backward-compatible. However, | ||
there are a couple of breaking changes you should be aware of: | ||
- Customizing `fetchHandler`: The ability to customize the fetchHandler on the | ||
XRPC Client and AtpAgent classes has been modified. Please review your code if | ||
you rely on custom fetch handlers. | ||
- Managing Sessions: Previously, you had the ability to manage sessions directly | ||
through AtpAgent instances. Now, session management must be handled through a | ||
dedicated `SessionManager` instance. If you were making authenticated | ||
requests, you'll need to update your code to use explicit session management. | ||
- The `fetch()` method, as well as WhatWG compliant `Request` and `Headers` | ||
constructors, must be globally available in your environment. | ||
## 0.4.0 | ||
@@ -4,0 +34,0 @@ |
@@ -5,7 +5,19 @@ import { LexiconDoc, LexUserType, ValidationResult } from './types'; | ||
*/ | ||
export declare class Lexicons { | ||
export declare class Lexicons implements Iterable<LexiconDoc> { | ||
docs: Map<string, LexiconDoc>; | ||
defs: Map<string, LexUserType>; | ||
constructor(docs?: LexiconDoc[]); | ||
constructor(docs?: Iterable<LexiconDoc>); | ||
/** | ||
* @example clone a lexicon: | ||
* ```ts | ||
* const clone = new Lexicons(originalLexicon) | ||
* ``` | ||
* | ||
* @example get docs array: | ||
* ```ts | ||
* const docs = Array.from(lexicons) | ||
* ``` | ||
*/ | ||
[Symbol.iterator](): Iterator<LexiconDoc>; | ||
/** | ||
* Add a lexicon doc. | ||
@@ -12,0 +24,0 @@ */ |
@@ -48,3 +48,3 @@ "use strict"; | ||
}); | ||
if (docs?.length) { | ||
if (docs) { | ||
for (const doc of docs) { | ||
@@ -56,2 +56,16 @@ this.add(doc); | ||
/** | ||
* @example clone a lexicon: | ||
* ```ts | ||
* const clone = new Lexicons(originalLexicon) | ||
* ``` | ||
* | ||
* @example get docs array: | ||
* ```ts | ||
* const docs = Array.from(lexicons) | ||
* ``` | ||
*/ | ||
[Symbol.iterator]() { | ||
return this.docs.values(); | ||
} | ||
/** | ||
* Add a lexicon doc. | ||
@@ -58,0 +72,0 @@ */ |
@@ -38,2 +38,4 @@ "use strict"; | ||
'language', | ||
'tid', | ||
'record-key', | ||
]); | ||
@@ -40,0 +42,0 @@ exports.lexString = zod_1.z |
@@ -0,7 +1,5 @@ | ||
import { z } from 'zod'; | ||
import { Lexicons } from './lexicons'; | ||
import { LexUserType, LexRefVariant, ValidationResult } from './types'; | ||
import { z } from 'zod'; | ||
import { LexRefVariant, LexUserType } from './types'; | ||
export declare function toLexUri(str: string, baseUri?: string): string; | ||
export declare function validateOneOf(lexicons: Lexicons, path: string, def: LexRefVariant | LexUserType, value: unknown, mustBeObj?: boolean): ValidationResult; | ||
export declare function assertValidOneOf(lexicons: Lexicons, path: string, def: LexRefVariant | LexUserType, value: unknown, mustBeObj?: boolean): unknown; | ||
export declare function toConcreteTypes(lexicons: Lexicons, def: LexRefVariant | LexUserType): LexUserType[]; | ||
@@ -8,0 +6,0 @@ export declare function requiredPropertiesRefinement<ObjectType extends { |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.requiredPropertiesRefinement = exports.toConcreteTypes = exports.assertValidOneOf = exports.validateOneOf = exports.toLexUri = void 0; | ||
const ComplexValidators = __importStar(require("./validators/complex")); | ||
const types_1 = require("./types"); | ||
exports.requiredPropertiesRefinement = exports.toConcreteTypes = exports.toLexUri = void 0; | ||
const zod_1 = require("zod"); | ||
@@ -46,56 +21,2 @@ function toLexUri(str, baseUri) { | ||
exports.toLexUri = toLexUri; | ||
function validateOneOf(lexicons, path, def, value, mustBeObj = false) { | ||
let error; | ||
let concreteDefs; | ||
if (def.type === 'union') { | ||
if (!(0, types_1.isDiscriminatedObject)(value)) { | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} must be an object which includes the "$type" property`), | ||
}; | ||
} | ||
if (!refsContainType(def.refs, value.$type)) { | ||
if (def.closed) { | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} $type must be one of ${def.refs.join(', ')}`), | ||
}; | ||
} | ||
return { success: true, value }; | ||
} | ||
else { | ||
concreteDefs = toConcreteTypes(lexicons, { | ||
type: 'ref', | ||
ref: value.$type, | ||
}); | ||
} | ||
} | ||
else { | ||
concreteDefs = toConcreteTypes(lexicons, def); | ||
} | ||
for (const concreteDef of concreteDefs) { | ||
const result = mustBeObj | ||
? ComplexValidators.object(lexicons, path, concreteDef, value) | ||
: ComplexValidators.validate(lexicons, path, concreteDef, value); | ||
if (result.success) { | ||
return result; | ||
} | ||
error ?? (error = result.error); | ||
} | ||
if (concreteDefs.length > 1) { | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} did not match any of the expected definitions`), | ||
}; | ||
} | ||
return { success: false, error }; | ||
} | ||
exports.validateOneOf = validateOneOf; | ||
function assertValidOneOf(lexicons, path, def, value, mustBeObj = false) { | ||
const res = validateOneOf(lexicons, path, def, value, mustBeObj); | ||
if (!res.success) | ||
throw res.error; | ||
return res.value; | ||
} | ||
exports.assertValidOneOf = assertValidOneOf; | ||
function toConcreteTypes(lexicons, def) { | ||
@@ -145,16 +66,2 @@ if (def.type === 'ref') { | ||
exports.requiredPropertiesRefinement = requiredPropertiesRefinement; | ||
// to avoid bugs like #0189 this needs to handle both | ||
// explicit and implicit #main | ||
const refsContainType = (refs, type) => { | ||
const lexUri = toLexUri(type); | ||
if (refs.includes(lexUri)) { | ||
return true; | ||
} | ||
if (lexUri.endsWith('#main')) { | ||
return refs.includes(lexUri.replace('#main', '')); | ||
} | ||
else { | ||
return refs.includes(lexUri + '#main'); | ||
} | ||
}; | ||
//# sourceMappingURL=util.js.map |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.assertValidXrpcMessage = exports.assertValidXrpcOutput = exports.assertValidXrpcInput = exports.assertValidXrpcParams = exports.assertValidRecord = void 0; | ||
const util_1 = require("./util"); | ||
const ComplexValidators = __importStar(require("./validators/complex")); | ||
const XrpcValidators = __importStar(require("./validators/xrpc")); | ||
const complex_1 = require("./validators/complex"); | ||
const xrpc_1 = require("./validators/xrpc"); | ||
function assertValidRecord(lexicons, def, value) { | ||
const res = ComplexValidators.object(lexicons, 'Record', def.record, value); | ||
const res = (0, complex_1.object)(lexicons, 'Record', def.record, value); | ||
if (!res.success) | ||
@@ -39,3 +15,3 @@ throw res.error; | ||
if (def.parameters) { | ||
const res = XrpcValidators.params(lexicons, 'Params', def.parameters, value); | ||
const res = (0, xrpc_1.params)(lexicons, 'Params', def.parameters, value); | ||
if (!res.success) | ||
@@ -50,3 +26,3 @@ throw res.error; | ||
// loop: all input schema definitions | ||
return (0, util_1.assertValidOneOf)(lexicons, 'Input', def.input.schema, value, true); | ||
return assertValidOneOf(lexicons, 'Input', def.input.schema, value, true); | ||
} | ||
@@ -58,3 +34,3 @@ } | ||
// loop: all output schema definitions | ||
return (0, util_1.assertValidOneOf)(lexicons, 'Output', def.output.schema, value, true); | ||
return assertValidOneOf(lexicons, 'Output', def.output.schema, value, true); | ||
} | ||
@@ -66,6 +42,12 @@ } | ||
// loop: all output schema definitions | ||
return (0, util_1.assertValidOneOf)(lexicons, 'Message', def.message.schema, value, true); | ||
return assertValidOneOf(lexicons, 'Message', def.message.schema, value, true); | ||
} | ||
} | ||
exports.assertValidXrpcMessage = assertValidXrpcMessage; | ||
function assertValidOneOf(lexicons, path, def, value, mustBeObj = false) { | ||
const res = (0, complex_1.validateOneOf)(lexicons, path, def, value, mustBeObj); | ||
if (!res.success) | ||
throw res.error; | ||
return res.value; | ||
} | ||
//# sourceMappingURL=validation.js.map |
import { Lexicons } from '../lexicons'; | ||
import { LexArray, LexUserType, ValidationResult } from '../types'; | ||
import { LexArray, LexRefVariant, LexUserType, ValidationResult } from '../types'; | ||
export declare function validate(lexicons: Lexicons, path: string, def: LexUserType, value: unknown): ValidationResult; | ||
export declare function array(lexicons: Lexicons, path: string, def: LexArray, value: unknown): ValidationResult; | ||
export declare function object(lexicons: Lexicons, path: string, def: LexUserType, value: unknown): ValidationResult; | ||
export declare function validateOneOf(lexicons: Lexicons, path: string, def: LexRefVariant | LexUserType, value: unknown, mustBeObj?: boolean): ValidationResult; | ||
//# sourceMappingURL=complex.d.ts.map |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.object = exports.array = exports.validate = void 0; | ||
exports.validateOneOf = exports.object = exports.array = exports.validate = void 0; | ||
const types_1 = require("../types"); | ||
const util_1 = require("../util"); | ||
const Primitives = __importStar(require("./primitives")); | ||
const Blob = __importStar(require("./blob")); | ||
const blob_1 = require("./blob"); | ||
const primitives_1 = require("./primitives"); | ||
function validate(lexicons, path, def, value) { | ||
switch (def.type) { | ||
case 'boolean': | ||
return Primitives.boolean(lexicons, path, def, value); | ||
return (0, primitives_1.boolean)(lexicons, path, def, value); | ||
case 'integer': | ||
return Primitives.integer(lexicons, path, def, value); | ||
return (0, primitives_1.integer)(lexicons, path, def, value); | ||
case 'string': | ||
return Primitives.string(lexicons, path, def, value); | ||
return (0, primitives_1.string)(lexicons, path, def, value); | ||
case 'bytes': | ||
return Primitives.bytes(lexicons, path, def, value); | ||
return (0, primitives_1.bytes)(lexicons, path, def, value); | ||
case 'cid-link': | ||
return Primitives.cidLink(lexicons, path, def, value); | ||
return (0, primitives_1.cidLink)(lexicons, path, def, value); | ||
case 'unknown': | ||
return Primitives.unknown(lexicons, path, def, value); | ||
return (0, primitives_1.unknown)(lexicons, path, def, value); | ||
case 'object': | ||
@@ -50,3 +27,3 @@ return object(lexicons, path, def, value); | ||
case 'blob': | ||
return Blob.blob(lexicons, path, def, value); | ||
return (0, blob_1.blob)(lexicons, path, def, value); | ||
default: | ||
@@ -91,3 +68,3 @@ return { | ||
const itemPath = `${path}/${i}`; | ||
const res = (0, util_1.validateOneOf)(lexicons, itemPath, itemsDef, itemValue); | ||
const res = validateOneOf(lexicons, itemPath, itemsDef, itemValue); | ||
if (!res.success) { | ||
@@ -119,4 +96,18 @@ return res; | ||
const propDef = def.properties[key]; | ||
if (typeof value[key] === 'undefined' && !requiredProps.has(key)) { | ||
// Fast path for non-required undefined props. | ||
if (propDef.type === 'integer' || | ||
propDef.type === 'boolean' || | ||
propDef.type === 'string') { | ||
if (typeof propDef.default === 'undefined') { | ||
continue; | ||
} | ||
} | ||
else { | ||
// Other types have no defaults. | ||
continue; | ||
} | ||
} | ||
const propPath = `${path}/${key}`; | ||
const validated = (0, util_1.validateOneOf)(lexicons, propPath, propDef, value[key]); | ||
const validated = validateOneOf(lexicons, propPath, propDef, value[key]); | ||
const propValue = validated.success ? validated.value : value[key]; | ||
@@ -147,2 +138,63 @@ const propIsUndefined = typeof propValue === 'undefined'; | ||
exports.object = object; | ||
function validateOneOf(lexicons, path, def, value, mustBeObj = false) { | ||
let error; | ||
let concreteDefs; | ||
if (def.type === 'union') { | ||
if (!(0, types_1.isDiscriminatedObject)(value)) { | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} must be an object which includes the "$type" property`), | ||
}; | ||
} | ||
if (!refsContainType(def.refs, value.$type)) { | ||
if (def.closed) { | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} $type must be one of ${def.refs.join(', ')}`), | ||
}; | ||
} | ||
return { success: true, value }; | ||
} | ||
else { | ||
concreteDefs = (0, util_1.toConcreteTypes)(lexicons, { | ||
type: 'ref', | ||
ref: value.$type, | ||
}); | ||
} | ||
} | ||
else { | ||
concreteDefs = (0, util_1.toConcreteTypes)(lexicons, def); | ||
} | ||
for (const concreteDef of concreteDefs) { | ||
const result = mustBeObj | ||
? object(lexicons, path, concreteDef, value) | ||
: validate(lexicons, path, concreteDef, value); | ||
if (result.success) { | ||
return result; | ||
} | ||
error ?? (error = result.error); | ||
} | ||
if (concreteDefs.length > 1) { | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} did not match any of the expected definitions`), | ||
}; | ||
} | ||
return { success: false, error }; | ||
} | ||
exports.validateOneOf = validateOneOf; | ||
// to avoid bugs like #0189 this needs to handle both | ||
// explicit and implicit #main | ||
const refsContainType = (refs, type) => { | ||
const lexUri = (0, util_1.toLexUri)(type); | ||
if (refs.includes(lexUri)) { | ||
return true; | ||
} | ||
if (lexUri.endsWith('#main')) { | ||
return refs.includes(lexUri.replace('#main', '')); | ||
} | ||
else { | ||
return refs.includes(lexUri + '#main'); | ||
} | ||
}; | ||
//# sourceMappingURL=complex.js.map |
@@ -11,2 +11,4 @@ import { ValidationResult } from '../types'; | ||
export declare function language(path: string, value: string): ValidationResult; | ||
export declare function tid(path: string, value: string): ValidationResult; | ||
export declare function recordKey(path: string, value: string): ValidationResult; | ||
//# sourceMappingURL=formats.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.language = exports.cid = exports.nsid = exports.atIdentifier = exports.handle = exports.did = exports.atUri = exports.uri = exports.datetime = void 0; | ||
exports.recordKey = exports.tid = exports.language = exports.cid = exports.nsid = exports.atIdentifier = exports.handle = exports.did = exports.atUri = exports.uri = exports.datetime = void 0; | ||
const iso_datestring_validator_1 = require("iso-datestring-validator"); | ||
@@ -125,2 +125,28 @@ const cid_1 = require("multiformats/cid"); | ||
exports.language = language; | ||
function tid(path, value) { | ||
try { | ||
(0, syntax_1.ensureValidTid)(value); | ||
} | ||
catch { | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} must be a valid TID (timestamp identifier)`), | ||
}; | ||
} | ||
return { success: true, value }; | ||
} | ||
exports.tid = tid; | ||
function recordKey(path, value) { | ||
try { | ||
(0, syntax_1.ensureValidRecordKey)(value); | ||
} | ||
catch { | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} must be a valid Record Key`), | ||
}; | ||
} | ||
return { success: true, value }; | ||
} | ||
exports.recordKey = recordKey; | ||
//# sourceMappingURL=formats.js.map |
@@ -234,2 +234,6 @@ "use strict"; | ||
return formats.language(path, value); | ||
case 'tid': | ||
return formats.tid(path, value); | ||
case 'record-key': | ||
return formats.recordKey(path, value); | ||
} | ||
@@ -236,0 +240,0 @@ } |
{ | ||
"name": "@atproto/lexicon", | ||
"version": "0.4.0", | ||
"version": "0.4.1-rc.0", | ||
"license": "MIT", | ||
@@ -21,3 +21,3 @@ "description": "atproto Lexicon schema language library", | ||
"multiformats": "^9.9.0", | ||
"zod": "^3.21.4", | ||
"zod": "^3.23.8", | ||
"@atproto/common-web": "^0.3.0", | ||
@@ -24,0 +24,0 @@ "@atproto/syntax": "^0.3.0" |
@@ -25,8 +25,8 @@ import { | ||
*/ | ||
export class Lexicons { | ||
export class Lexicons implements Iterable<LexiconDoc> { | ||
docs: Map<string, LexiconDoc> = new Map() | ||
defs: Map<string, LexUserType> = new Map() | ||
constructor(docs?: LexiconDoc[]) { | ||
if (docs?.length) { | ||
constructor(docs?: Iterable<LexiconDoc>) { | ||
if (docs) { | ||
for (const doc of docs) { | ||
@@ -39,2 +39,17 @@ this.add(doc) | ||
/** | ||
* @example clone a lexicon: | ||
* ```ts | ||
* const clone = new Lexicons(originalLexicon) | ||
* ``` | ||
* | ||
* @example get docs array: | ||
* ```ts | ||
* const docs = Array.from(lexicons) | ||
* ``` | ||
*/ | ||
[Symbol.iterator](): Iterator<LexiconDoc> { | ||
return this.docs.values() | ||
} | ||
/** | ||
* Add a lexicon doc. | ||
@@ -41,0 +56,0 @@ */ |
@@ -41,2 +41,4 @@ import { z } from 'zod' | ||
'language', | ||
'tid', | ||
'record-key', | ||
]) | ||
@@ -43,0 +45,0 @@ export type LexStringFormat = z.infer<typeof lexStringFormat> |
@@ -0,11 +1,4 @@ | ||
import { z } from 'zod' | ||
import { Lexicons } from './lexicons' | ||
import * as ComplexValidators from './validators/complex' | ||
import { | ||
LexUserType, | ||
LexRefVariant, | ||
ValidationError, | ||
ValidationResult, | ||
isDiscriminatedObject, | ||
} from './types' | ||
import { z } from 'zod' | ||
import { LexRefVariant, LexUserType } from './types' | ||
@@ -29,73 +22,2 @@ export function toLexUri(str: string, baseUri?: string): string { | ||
export function validateOneOf( | ||
lexicons: Lexicons, | ||
path: string, | ||
def: LexRefVariant | LexUserType, | ||
value: unknown, | ||
mustBeObj = false, // this is the only type constraint we need currently (used by xrpc body schema validators) | ||
): ValidationResult { | ||
let error | ||
let concreteDefs | ||
if (def.type === 'union') { | ||
if (!isDiscriminatedObject(value)) { | ||
return { | ||
success: false, | ||
error: new ValidationError( | ||
`${path} must be an object which includes the "$type" property`, | ||
), | ||
} | ||
} | ||
if (!refsContainType(def.refs, value.$type)) { | ||
if (def.closed) { | ||
return { | ||
success: false, | ||
error: new ValidationError( | ||
`${path} $type must be one of ${def.refs.join(', ')}`, | ||
), | ||
} | ||
} | ||
return { success: true, value } | ||
} else { | ||
concreteDefs = toConcreteTypes(lexicons, { | ||
type: 'ref', | ||
ref: value.$type, | ||
}) | ||
} | ||
} else { | ||
concreteDefs = toConcreteTypes(lexicons, def) | ||
} | ||
for (const concreteDef of concreteDefs) { | ||
const result = mustBeObj | ||
? ComplexValidators.object(lexicons, path, concreteDef, value) | ||
: ComplexValidators.validate(lexicons, path, concreteDef, value) | ||
if (result.success) { | ||
return result | ||
} | ||
error ??= result.error | ||
} | ||
if (concreteDefs.length > 1) { | ||
return { | ||
success: false, | ||
error: new ValidationError( | ||
`${path} did not match any of the expected definitions`, | ||
), | ||
} | ||
} | ||
return { success: false, error } | ||
} | ||
export function assertValidOneOf( | ||
lexicons: Lexicons, | ||
path: string, | ||
def: LexRefVariant | LexUserType, | ||
value: unknown, | ||
mustBeObj = false, | ||
) { | ||
const res = validateOneOf(lexicons, path, def, value, mustBeObj) | ||
if (!res.success) throw res.error | ||
return res.value | ||
} | ||
export function toConcreteTypes( | ||
@@ -153,16 +75,1 @@ lexicons: Lexicons, | ||
} | ||
// to avoid bugs like #0189 this needs to handle both | ||
// explicit and implicit #main | ||
const refsContainType = (refs: string[], type: string) => { | ||
const lexUri = toLexUri(type) | ||
if (refs.includes(lexUri)) { | ||
return true | ||
} | ||
if (lexUri.endsWith('#main')) { | ||
return refs.includes(lexUri.replace('#main', '')) | ||
} else { | ||
return refs.includes(lexUri + '#main') | ||
} | ||
} |
import { Lexicons } from './lexicons' | ||
import { | ||
LexRecord, | ||
LexRefVariant, | ||
LexUserType, | ||
LexXrpcProcedure, | ||
@@ -8,6 +10,5 @@ LexXrpcQuery, | ||
} from './types' | ||
import { assertValidOneOf } from './util' | ||
import * as ComplexValidators from './validators/complex' | ||
import * as XrpcValidators from './validators/xrpc' | ||
import { object, validateOneOf } from './validators/complex' | ||
import { params } from './validators/xrpc' | ||
@@ -19,3 +20,3 @@ export function assertValidRecord( | ||
) { | ||
const res = ComplexValidators.object(lexicons, 'Record', def.record, value) | ||
const res = object(lexicons, 'Record', def.record, value) | ||
if (!res.success) throw res.error | ||
@@ -31,3 +32,3 @@ return res.value | ||
if (def.parameters) { | ||
const res = XrpcValidators.params(lexicons, 'Params', def.parameters, value) | ||
const res = params(lexicons, 'Params', def.parameters, value) | ||
if (!res.success) throw res.error | ||
@@ -76,1 +77,13 @@ return res.value | ||
} | ||
function assertValidOneOf( | ||
lexicons: Lexicons, | ||
path: string, | ||
def: LexRefVariant | LexUserType, | ||
value: unknown, | ||
mustBeObj = false, | ||
) { | ||
const res = validateOneOf(lexicons, path, def, value, mustBeObj) | ||
if (!res.success) throw res.error | ||
return res.value | ||
} |
@@ -5,10 +5,12 @@ import { Lexicons } from '../lexicons' | ||
LexObject, | ||
LexRefVariant, | ||
LexUserType, | ||
ValidationError, | ||
ValidationResult, | ||
ValidationError, | ||
isDiscriminatedObject, | ||
} from '../types' | ||
import { validateOneOf } from '../util' | ||
import { toConcreteTypes, toLexUri } from '../util' | ||
import * as Primitives from './primitives' | ||
import * as Blob from './blob' | ||
import { blob } from './blob' | ||
import { boolean, integer, string, bytes, cidLink, unknown } from './primitives' | ||
@@ -23,13 +25,13 @@ export function validate( | ||
case 'boolean': | ||
return Primitives.boolean(lexicons, path, def, value) | ||
return boolean(lexicons, path, def, value) | ||
case 'integer': | ||
return Primitives.integer(lexicons, path, def, value) | ||
return integer(lexicons, path, def, value) | ||
case 'string': | ||
return Primitives.string(lexicons, path, def, value) | ||
return string(lexicons, path, def, value) | ||
case 'bytes': | ||
return Primitives.bytes(lexicons, path, def, value) | ||
return bytes(lexicons, path, def, value) | ||
case 'cid-link': | ||
return Primitives.cidLink(lexicons, path, def, value) | ||
return cidLink(lexicons, path, def, value) | ||
case 'unknown': | ||
return Primitives.unknown(lexicons, path, def, value) | ||
return unknown(lexicons, path, def, value) | ||
case 'object': | ||
@@ -40,3 +42,3 @@ return object(lexicons, path, def, value) | ||
case 'blob': | ||
return Blob.blob(lexicons, path, def, value) | ||
return blob(lexicons, path, def, value) | ||
default: | ||
@@ -129,2 +131,17 @@ return { | ||
const propDef = def.properties[key] | ||
if (typeof value[key] === 'undefined' && !requiredProps.has(key)) { | ||
// Fast path for non-required undefined props. | ||
if ( | ||
propDef.type === 'integer' || | ||
propDef.type === 'boolean' || | ||
propDef.type === 'string' | ||
) { | ||
if (typeof propDef.default === 'undefined') { | ||
continue | ||
} | ||
} else { | ||
// Other types have no defaults. | ||
continue | ||
} | ||
} | ||
const propPath = `${path}/${key}` | ||
@@ -156,1 +173,75 @@ const validated = validateOneOf(lexicons, propPath, propDef, value[key]) | ||
} | ||
export function validateOneOf( | ||
lexicons: Lexicons, | ||
path: string, | ||
def: LexRefVariant | LexUserType, | ||
value: unknown, | ||
mustBeObj = false, // this is the only type constraint we need currently (used by xrpc body schema validators) | ||
): ValidationResult { | ||
let error | ||
let concreteDefs | ||
if (def.type === 'union') { | ||
if (!isDiscriminatedObject(value)) { | ||
return { | ||
success: false, | ||
error: new ValidationError( | ||
`${path} must be an object which includes the "$type" property`, | ||
), | ||
} | ||
} | ||
if (!refsContainType(def.refs, value.$type)) { | ||
if (def.closed) { | ||
return { | ||
success: false, | ||
error: new ValidationError( | ||
`${path} $type must be one of ${def.refs.join(', ')}`, | ||
), | ||
} | ||
} | ||
return { success: true, value } | ||
} else { | ||
concreteDefs = toConcreteTypes(lexicons, { | ||
type: 'ref', | ||
ref: value.$type, | ||
}) | ||
} | ||
} else { | ||
concreteDefs = toConcreteTypes(lexicons, def) | ||
} | ||
for (const concreteDef of concreteDefs) { | ||
const result = mustBeObj | ||
? object(lexicons, path, concreteDef, value) | ||
: validate(lexicons, path, concreteDef, value) | ||
if (result.success) { | ||
return result | ||
} | ||
error ??= result.error | ||
} | ||
if (concreteDefs.length > 1) { | ||
return { | ||
success: false, | ||
error: new ValidationError( | ||
`${path} did not match any of the expected definitions`, | ||
), | ||
} | ||
} | ||
return { success: false, error } | ||
} | ||
// to avoid bugs like #0189 this needs to handle both | ||
// explicit and implicit #main | ||
const refsContainType = (refs: string[], type: string) => { | ||
const lexUri = toLexUri(type) | ||
if (refs.includes(lexUri)) { | ||
return true | ||
} | ||
if (lexUri.endsWith('#main')) { | ||
return refs.includes(lexUri.replace('#main', '')) | ||
} else { | ||
return refs.includes(lexUri + '#main') | ||
} | ||
} |
@@ -9,2 +9,4 @@ import { isValidISODateString } from 'iso-datestring-validator' | ||
ensureValidAtUri, | ||
ensureValidTid, | ||
ensureValidRecordKey, | ||
} from '@atproto/syntax' | ||
@@ -126,1 +128,27 @@ import { validateLanguage } from '@atproto/common-web' | ||
} | ||
export function tid(path: string, value: string): ValidationResult { | ||
try { | ||
ensureValidTid(value) | ||
} catch { | ||
return { | ||
success: false, | ||
error: new ValidationError( | ||
`${path} must be a valid TID (timestamp identifier)`, | ||
), | ||
} | ||
} | ||
return { success: true, value } | ||
} | ||
export function recordKey(path: string, value: string): ValidationResult { | ||
try { | ||
ensureValidRecordKey(value) | ||
} catch { | ||
return { | ||
success: false, | ||
error: new ValidationError(`${path} must be a valid Record Key`), | ||
} | ||
} | ||
return { success: true, value } | ||
} |
@@ -268,2 +268,6 @@ import { utf8Len, graphemeLen } from '@atproto/common-web' | ||
return formats.language(path, value) | ||
case 'tid': | ||
return formats.tid(path, value) | ||
case 'record-key': | ||
return formats.recordKey(path, value) | ||
} | ||
@@ -270,0 +274,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1168568
27440
Updatedzod@^3.23.8