@atproto/lexicon
Advanced tools
Comparing version 0.4.6-next.5 to 0.4.6
# @atproto/lexicon | ||
## 0.4.6 | ||
### Patch Changes | ||
- [#3220](https://github.com/bluesky-social/atproto/pull/3220) [`61dc0d60e`](https://github.com/bluesky-social/atproto/commit/61dc0d60e19b88c6427a54c6d95a391b5f4da7bd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Apply new linting rules regarding import order | ||
- Updated dependencies [[`61dc0d60e`](https://github.com/bluesky-social/atproto/commit/61dc0d60e19b88c6427a54c6d95a391b5f4da7bd), [`8a30e0ed9`](https://github.com/bluesky-social/atproto/commit/8a30e0ed9239cb2037d54fb98e70e8b0cfbc3e39)]: | ||
- @atproto/common-web@0.4.0 | ||
- @atproto/syntax@0.3.2 | ||
## 0.4.5 | ||
@@ -4,0 +14,0 @@ |
"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 }); | ||
@@ -7,3 +30,3 @@ exports.Lexicons = void 0; | ||
const validation_1 = require("./validation"); | ||
const complex_1 = require("./validators/complex"); | ||
const ComplexValidators = __importStar(require("./validators/complex")); | ||
/** | ||
@@ -105,12 +128,12 @@ * A collection of compiled lexicons. | ||
validate(lexUri, value) { | ||
lexUri = (0, util_1.toLexUri)(lexUri); | ||
const def = this.getDefOrThrow(lexUri, ['record', 'object']); | ||
if (!(0, types_1.isObj)(value)) { | ||
throw new types_1.ValidationError(`Value must be an object`); | ||
} | ||
const lexUriNormalized = (0, util_1.toLexUri)(lexUri); | ||
const def = this.getDefOrThrow(lexUriNormalized, ['record', 'object']); | ||
if (def.type === 'record') { | ||
return (0, complex_1.object)(this, 'Record', def.record, value); | ||
return ComplexValidators.object(this, 'Record', def.record, value); | ||
} | ||
else if (def.type === 'object') { | ||
return (0, complex_1.object)(this, 'Object', def, value); | ||
return ComplexValidators.object(this, 'Object', def, value); | ||
} | ||
@@ -126,17 +149,14 @@ else { | ||
assertValidRecord(lexUri, value) { | ||
lexUri = (0, util_1.toLexUri)(lexUri); | ||
const def = this.getDefOrThrow(lexUri, ['record']); | ||
if (!(0, types_1.isObj)(value)) { | ||
throw new types_1.ValidationError(`Record must be an object`); | ||
} | ||
if (!('$type' in value)) { | ||
if (!(0, types_1.hasProp)(value, '$type') || typeof value.$type !== 'string') { | ||
throw new types_1.ValidationError(`Record/$type must be a string`); | ||
} | ||
const { $type } = value; | ||
if (typeof $type !== 'string') { | ||
throw new types_1.ValidationError(`Record/$type must be a string`); | ||
const $type = value.$type || ''; | ||
if ((0, util_1.toLexUri)($type) !== lexUri) { | ||
throw new types_1.ValidationError(`Invalid $type: must be ${lexUri}, got ${$type}`); | ||
} | ||
const lexUriNormalized = (0, util_1.toLexUri)(lexUri); | ||
if ((0, util_1.toLexUri)($type) !== lexUriNormalized) { | ||
throw new types_1.ValidationError(`Invalid $type: must be ${lexUriNormalized}, got ${$type}`); | ||
} | ||
const def = this.getDefOrThrow(lexUriNormalized, ['record']); | ||
return (0, validation_1.assertValidRecord)(this, def, value); | ||
@@ -143,0 +163,0 @@ } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.LexiconDefNotFoundError = exports.InvalidLexiconError = exports.ValidationError = exports.lexiconDoc = exports.lexUserType = exports.lexRecord = exports.lexXrpcSubscription = exports.lexXrpcProcedure = exports.lexXrpcQuery = exports.lexXrpcError = exports.lexXrpcSubscriptionMessage = exports.lexXrpcBody = exports.lexXrpcParameters = exports.lexObject = exports.lexToken = exports.lexPrimitiveArray = exports.lexArray = exports.lexBlob = exports.lexRefVariant = exports.lexRefUnion = exports.lexRef = exports.lexIpldType = exports.lexCidLink = exports.lexBytes = exports.lexPrimitive = exports.lexUnknown = exports.lexString = exports.lexStringFormat = exports.lexInteger = exports.lexBoolean = void 0; | ||
exports.LexiconDefNotFoundError = exports.InvalidLexiconError = exports.ValidationError = exports.discriminatedObject = exports.lexiconDoc = exports.lexUserType = exports.lexRecord = exports.lexXrpcSubscription = exports.lexXrpcProcedure = exports.lexXrpcQuery = exports.lexXrpcError = exports.lexXrpcSubscriptionMessage = exports.lexXrpcBody = exports.lexXrpcParameters = exports.lexObject = exports.lexToken = exports.lexPrimitiveArray = exports.lexArray = exports.lexBlob = exports.lexRefVariant = exports.lexRefUnion = exports.lexRef = exports.lexIpldType = exports.lexCidLink = exports.lexBytes = exports.lexPrimitive = exports.lexUnknown = exports.lexString = exports.lexStringFormat = exports.lexInteger = exports.lexBoolean = void 0; | ||
exports.isValidLexiconDoc = isValidLexiconDoc; | ||
exports.isObj = isObj; | ||
exports.hasProp = hasProp; | ||
exports.isDiscriminatedObject = isDiscriminatedObject; | ||
@@ -353,8 +354,12 @@ exports.parseLexiconDoc = parseLexiconDoc; | ||
} | ||
function isObj(v) { | ||
return v != null && typeof v === 'object'; | ||
function isObj(obj) { | ||
return obj !== null && typeof obj === 'object'; | ||
} | ||
function isDiscriminatedObject(v) { | ||
return isObj(v) && '$type' in v && typeof v.$type === 'string'; | ||
function hasProp(data, prop) { | ||
return prop in data; | ||
} | ||
exports.discriminatedObject = zod_1.z.object({ $type: zod_1.z.string() }); | ||
function isDiscriminatedObject(value) { | ||
return exports.discriminatedObject.safeParse(value).success; | ||
} | ||
function parseLexiconDoc(v) { | ||
@@ -361,0 +366,0 @@ exports.lexiconDoc.parse(v); |
import { z } from 'zod'; | ||
import { Lexicons } from './lexicons'; | ||
import { LexRefVariant, LexUserType } from './types'; | ||
export declare function toLexUri(str: string, baseUri?: string): string; | ||
export declare function toConcreteTypes(lexicons: Lexicons, def: LexRefVariant | LexUserType): LexUserType[]; | ||
export declare function requiredPropertiesRefinement<ObjectType extends { | ||
@@ -4,0 +7,0 @@ required?: string[]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.toLexUri = toLexUri; | ||
exports.toConcreteTypes = toConcreteTypes; | ||
exports.requiredPropertiesRefinement = requiredPropertiesRefinement; | ||
@@ -21,2 +22,13 @@ const zod_1 = require("zod"); | ||
} | ||
function toConcreteTypes(lexicons, def) { | ||
if (def.type === 'ref') { | ||
return [lexicons.getDefOrThrow(def.ref)]; | ||
} | ||
else if (def.type === 'union') { | ||
return def.refs.map((ref) => lexicons.getDefOrThrow(ref)).flat(); | ||
} | ||
else { | ||
return [def]; | ||
} | ||
} | ||
function requiredPropertiesRefinement(object, ctx) { | ||
@@ -23,0 +35,0 @@ // Required fields check |
@@ -13,2 +13,14 @@ "use strict"; | ||
switch (def.type) { | ||
case 'boolean': | ||
return (0, primitives_1.boolean)(lexicons, path, def, value); | ||
case 'integer': | ||
return (0, primitives_1.integer)(lexicons, path, def, value); | ||
case 'string': | ||
return (0, primitives_1.string)(lexicons, path, def, value); | ||
case 'bytes': | ||
return (0, primitives_1.bytes)(lexicons, path, def, value); | ||
case 'cid-link': | ||
return (0, primitives_1.cidLink)(lexicons, path, def, value); | ||
case 'unknown': | ||
return (0, primitives_1.unknown)(lexicons, path, def, value); | ||
case 'object': | ||
@@ -21,3 +33,6 @@ return object(lexicons, path, def, value); | ||
default: | ||
return (0, primitives_1.validate)(lexicons, path, def, value); | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`Unexpected lexicon type: ${def.type}`), | ||
}; | ||
} | ||
@@ -64,4 +79,5 @@ } | ||
function object(lexicons, path, def, value) { | ||
def = def; | ||
// type | ||
if (!(0, types_1.isObj)(value)) { | ||
if (!value || typeof value !== 'object') { | ||
return { | ||
@@ -72,12 +88,13 @@ success: false, | ||
} | ||
const requiredProps = new Set(def.required); | ||
const nullableProps = new Set(def.nullable); | ||
// properties | ||
let resultValue = value; | ||
if ('properties' in def && def.properties != null) { | ||
if (typeof def.properties === 'object') { | ||
for (const key in def.properties) { | ||
const keyValue = value[key]; | ||
if (keyValue === null && def.nullable?.includes(key)) { | ||
if (value[key] === null && nullableProps.has(key)) { | ||
continue; | ||
} | ||
const propDef = def.properties[key]; | ||
if (keyValue === undefined && !def.required?.includes(key)) { | ||
if (typeof value[key] === 'undefined' && !requiredProps.has(key)) { | ||
// Fast path for non-required undefined props. | ||
@@ -87,3 +104,3 @@ if (propDef.type === 'integer' || | ||
propDef.type === 'string') { | ||
if (propDef.default === undefined) { | ||
if (typeof propDef.default === 'undefined') { | ||
continue; | ||
@@ -98,20 +115,17 @@ } | ||
const propPath = `${path}/${key}`; | ||
const validated = validateOneOf(lexicons, propPath, propDef, keyValue); | ||
const propValue = validated.success ? validated.value : keyValue; | ||
const validated = validateOneOf(lexicons, propPath, propDef, value[key]); | ||
const propValue = validated.success ? validated.value : value[key]; | ||
const propIsUndefined = typeof propValue === 'undefined'; | ||
// Return error for bad validation, giving required rule precedence | ||
if (propValue === undefined) { | ||
if (def.required?.includes(key)) { | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} must have the property "${key}"`), | ||
}; | ||
} | ||
if (propIsUndefined && requiredProps.has(key)) { | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} must have the property "${key}"`), | ||
}; | ||
} | ||
else { | ||
if (!validated.success) { | ||
return validated; | ||
} | ||
else if (!propIsUndefined && !validated.success) { | ||
return validated; | ||
} | ||
// Adjust value based on e.g. applied defaults, cloning shallowly if there was a changed value | ||
if (propValue !== keyValue) { | ||
if (propValue !== value[key]) { | ||
if (resultValue === value) { | ||
@@ -128,3 +142,4 @@ // Lazy shallow clone | ||
function validateOneOf(lexicons, path, def, value, mustBeObj = false) { | ||
let concreteDef; | ||
let error; | ||
let concreteDefs; | ||
if (def.type === 'union') { | ||
@@ -147,14 +162,27 @@ if (!(0, types_1.isDiscriminatedObject)(value)) { | ||
else { | ||
concreteDef = lexicons.getDefOrThrow(value.$type); | ||
concreteDefs = (0, util_1.toConcreteTypes)(lexicons, { | ||
type: 'ref', | ||
ref: value.$type, | ||
}); | ||
} | ||
} | ||
else if (def.type === 'ref') { | ||
concreteDef = lexicons.getDefOrThrow(def.ref); | ||
} | ||
else { | ||
concreteDef = def; | ||
concreteDefs = (0, util_1.toConcreteTypes)(lexicons, def); | ||
} | ||
return mustBeObj | ||
? object(lexicons, path, concreteDef, value) | ||
: validate(lexicons, path, concreteDef, value); | ||
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 }; | ||
} | ||
@@ -169,3 +197,3 @@ // to avoid bugs like #0189 this needs to handle both | ||
if (lexUri.endsWith('#main')) { | ||
return refs.includes(lexUri.slice(0, -5)); | ||
return refs.includes(lexUri.replace('#main', '')); | ||
} | ||
@@ -172,0 +200,0 @@ else { |
@@ -131,9 +131,12 @@ "use strict"; | ||
function tid(path, value) { | ||
if ((0, syntax_1.isValidTid)(value)) { | ||
return { success: true, value }; | ||
try { | ||
(0, syntax_1.ensureValidTid)(value); | ||
} | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} must be a valid TID`), | ||
}; | ||
catch { | ||
return { | ||
success: false, | ||
error: new types_1.ValidationError(`${path} must be a valid TID (timestamp identifier)`), | ||
}; | ||
} | ||
return { success: true, value }; | ||
} | ||
@@ -140,0 +143,0 @@ function recordKey(path, value) { |
import { Lexicons } from '../lexicons'; | ||
import { LexUserType, ValidationResult } from '../types'; | ||
export declare function validate(lexicons: Lexicons, path: string, def: LexUserType, value: unknown): ValidationResult; | ||
export declare function boolean(lexicons: Lexicons, path: string, def: LexUserType, value: unknown): ValidationResult; | ||
export declare function integer(lexicons: Lexicons, path: string, def: LexUserType, value: unknown): ValidationResult; | ||
export declare function string(lexicons: Lexicons, path: string, def: LexUserType, value: unknown): ValidationResult; | ||
export declare function bytes(lexicons: Lexicons, path: string, def: LexUserType, value: unknown): ValidationResult; | ||
export declare function cidLink(lexicons: Lexicons, path: string, def: LexUserType, value: unknown): ValidationResult; | ||
export declare function unknown(lexicons: Lexicons, path: string, def: LexUserType, value: unknown): ValidationResult; | ||
//# sourceMappingURL=primitives.d.ts.map |
@@ -27,2 +27,8 @@ "use strict"; | ||
exports.validate = validate; | ||
exports.boolean = boolean; | ||
exports.integer = integer; | ||
exports.string = string; | ||
exports.bytes = bytes; | ||
exports.cidLink = cidLink; | ||
exports.unknown = unknown; | ||
const cid_1 = require("multiformats/cid"); | ||
@@ -29,0 +35,0 @@ const common_web_1 = require("@atproto/common-web"); |
@@ -6,3 +6,2 @@ /** @type {import('jest').Config} */ | ||
setupFiles: ['<rootDir>/../../jest.setup.ts'], | ||
moduleNameMapper: { '^(\\.\\.?\\/.+)\\.js$': ['$1.ts', '$1.js'] }, | ||
} |
{ | ||
"name": "@atproto/lexicon", | ||
"version": "0.4.6-next.5", | ||
"version": "0.4.6", | ||
"license": "MIT", | ||
@@ -22,4 +22,4 @@ "description": "atproto Lexicon schema language library", | ||
"zod": "^3.23.8", | ||
"@atproto/common-web": "^0.3.2", | ||
"@atproto/syntax": "^0.3.1" | ||
"@atproto/common-web": "^0.4.0", | ||
"@atproto/syntax": "^0.3.2" | ||
}, | ||
@@ -26,0 +26,0 @@ "devDependencies": { |
@@ -9,2 +9,3 @@ import { | ||
ValidationResult, | ||
hasProp, | ||
isObj, | ||
@@ -20,3 +21,3 @@ } from './types' | ||
} from './validation' | ||
import { object as validateObject } from './validators/complex' | ||
import * as ComplexValidators from './validators/complex' | ||
@@ -131,13 +132,11 @@ /** | ||
validate(lexUri: string, value: unknown): ValidationResult { | ||
lexUri = toLexUri(lexUri) | ||
const def = this.getDefOrThrow(lexUri, ['record', 'object']) | ||
if (!isObj(value)) { | ||
throw new ValidationError(`Value must be an object`) | ||
} | ||
const lexUriNormalized = toLexUri(lexUri) | ||
const def = this.getDefOrThrow(lexUriNormalized, ['record', 'object']) | ||
if (def.type === 'record') { | ||
return validateObject(this, 'Record', def.record, value) | ||
return ComplexValidators.object(this, 'Record', def.record, value) | ||
} else if (def.type === 'object') { | ||
return validateObject(this, 'Object', def, value) | ||
return ComplexValidators.object(this, 'Object', def, value) | ||
} else { | ||
@@ -153,21 +152,16 @@ // shouldn't happen | ||
assertValidRecord(lexUri: string, value: unknown) { | ||
lexUri = toLexUri(lexUri) | ||
const def = this.getDefOrThrow(lexUri, ['record']) | ||
if (!isObj(value)) { | ||
throw new ValidationError(`Record must be an object`) | ||
} | ||
if (!('$type' in value)) { | ||
if (!hasProp(value, '$type') || typeof value.$type !== 'string') { | ||
throw new ValidationError(`Record/$type must be a string`) | ||
} | ||
const { $type } = value | ||
if (typeof $type !== 'string') { | ||
throw new ValidationError(`Record/$type must be a string`) | ||
} | ||
const lexUriNormalized = toLexUri(lexUri) | ||
if (toLexUri($type) !== lexUriNormalized) { | ||
const $type = (value as Record<string, string>).$type || '' | ||
if (toLexUri($type) !== lexUri) { | ||
throw new ValidationError( | ||
`Invalid $type: must be ${lexUriNormalized}, got ${$type}`, | ||
`Invalid $type: must be ${lexUri}, got ${$type}`, | ||
) | ||
} | ||
const def = this.getDefOrThrow(lexUriNormalized, ['record']) | ||
return assertValidRecord(this, def as LexRecord, value) | ||
@@ -174,0 +168,0 @@ } |
@@ -450,11 +450,21 @@ import { z } from 'zod' | ||
export function isObj<V>(v: V): v is V & object { | ||
return v != null && typeof v === 'object' | ||
export function isObj(obj: unknown): obj is Record<string, unknown> { | ||
return obj !== null && typeof obj === 'object' | ||
} | ||
export type DiscriminatedObject = { $type: string } | ||
export function isDiscriminatedObject(v: unknown): v is DiscriminatedObject { | ||
return isObj(v) && '$type' in v && typeof v.$type === 'string' | ||
export function hasProp<K extends PropertyKey>( | ||
data: object, | ||
prop: K, | ||
): data is Record<K, unknown> { | ||
return prop in data | ||
} | ||
export const discriminatedObject = z.object({ $type: z.string() }) | ||
export type DiscriminatedObject = z.infer<typeof discriminatedObject> | ||
export function isDiscriminatedObject( | ||
value: unknown, | ||
): value is DiscriminatedObject { | ||
return discriminatedObject.safeParse(value).success | ||
} | ||
export function parseLexiconDoc(v: unknown): LexiconDoc { | ||
@@ -465,6 +475,6 @@ lexiconDoc.parse(v) | ||
export type ValidationResult<V = unknown> = | ||
export type ValidationResult = | ||
| { | ||
success: true | ||
value: V | ||
value: unknown | ||
} | ||
@@ -471,0 +481,0 @@ | { |
import { z } from 'zod' | ||
import { Lexicons } from './lexicons' | ||
import { LexRefVariant, LexUserType } from './types' | ||
@@ -20,2 +22,15 @@ export function toLexUri(str: string, baseUri?: string): string { | ||
export function toConcreteTypes( | ||
lexicons: Lexicons, | ||
def: LexRefVariant | LexUserType, | ||
): LexUserType[] { | ||
if (def.type === 'ref') { | ||
return [lexicons.getDefOrThrow(def.ref)] | ||
} else if (def.type === 'union') { | ||
return def.refs.map((ref) => lexicons.getDefOrThrow(ref)).flat() | ||
} else { | ||
return [def] | ||
} | ||
} | ||
export function requiredPropertiesRefinement< | ||
@@ -22,0 +37,0 @@ ObjectType extends { |
import { Lexicons } from '../lexicons' | ||
import { | ||
LexArray, | ||
LexObject, | ||
LexRefVariant, | ||
@@ -9,7 +10,6 @@ LexUserType, | ||
isDiscriminatedObject, | ||
isObj, | ||
} from '../types' | ||
import { toLexUri } from '../util' | ||
import { toConcreteTypes, toLexUri } from '../util' | ||
import { blob } from './blob' | ||
import { validate as validatePrimitive } from './primitives' | ||
import { boolean, bytes, cidLink, integer, string, unknown } from './primitives' | ||
@@ -23,2 +23,14 @@ export function validate( | ||
switch (def.type) { | ||
case 'boolean': | ||
return boolean(lexicons, path, def, value) | ||
case 'integer': | ||
return integer(lexicons, path, def, value) | ||
case 'string': | ||
return string(lexicons, path, def, value) | ||
case 'bytes': | ||
return bytes(lexicons, path, def, value) | ||
case 'cid-link': | ||
return cidLink(lexicons, path, def, value) | ||
case 'unknown': | ||
return unknown(lexicons, path, def, value) | ||
case 'object': | ||
@@ -31,3 +43,6 @@ return object(lexicons, path, def, value) | ||
default: | ||
return validatePrimitive(lexicons, path, def, value) | ||
return { | ||
success: false, | ||
error: new ValidationError(`Unexpected lexicon type: ${def.type}`), | ||
} | ||
} | ||
@@ -94,4 +109,6 @@ } | ||
): ValidationResult { | ||
def = def as LexObject | ||
// type | ||
if (!isObj(value)) { | ||
if (!value || typeof value !== 'object') { | ||
return { | ||
@@ -103,12 +120,14 @@ success: false, | ||
const requiredProps = new Set(def.required) | ||
const nullableProps = new Set(def.nullable) | ||
// properties | ||
let resultValue = value | ||
if ('properties' in def && def.properties != null) { | ||
if (typeof def.properties === 'object') { | ||
for (const key in def.properties) { | ||
const keyValue = value[key] | ||
if (keyValue === null && def.nullable?.includes(key)) { | ||
if (value[key] === null && nullableProps.has(key)) { | ||
continue | ||
} | ||
const propDef = def.properties[key] | ||
if (keyValue === undefined && !def.required?.includes(key)) { | ||
if (typeof value[key] === 'undefined' && !requiredProps.has(key)) { | ||
// Fast path for non-required undefined props. | ||
@@ -120,3 +139,3 @@ if ( | ||
) { | ||
if (propDef.default === undefined) { | ||
if (typeof propDef.default === 'undefined') { | ||
continue | ||
@@ -130,23 +149,16 @@ } | ||
const propPath = `${path}/${key}` | ||
const validated = validateOneOf(lexicons, propPath, propDef, keyValue) | ||
const propValue = validated.success ? validated.value : keyValue | ||
const validated = validateOneOf(lexicons, propPath, propDef, value[key]) | ||
const propValue = validated.success ? validated.value : value[key] | ||
const propIsUndefined = typeof propValue === 'undefined' | ||
// Return error for bad validation, giving required rule precedence | ||
if (propValue === undefined) { | ||
if (def.required?.includes(key)) { | ||
return { | ||
success: false, | ||
error: new ValidationError( | ||
`${path} must have the property "${key}"`, | ||
), | ||
} | ||
if (propIsUndefined && requiredProps.has(key)) { | ||
return { | ||
success: false, | ||
error: new ValidationError(`${path} must have the property "${key}"`), | ||
} | ||
} else { | ||
if (!validated.success) { | ||
return validated | ||
} | ||
} else if (!propIsUndefined && !validated.success) { | ||
return validated | ||
} | ||
// Adjust value based on e.g. applied defaults, cloning shallowly if there was a changed value | ||
if (propValue !== keyValue) { | ||
if (propValue !== value[key]) { | ||
if (resultValue === value) { | ||
@@ -171,4 +183,5 @@ // Lazy shallow clone | ||
): ValidationResult { | ||
let concreteDef: LexUserType | ||
let error | ||
let concreteDefs | ||
if (def.type === 'union') { | ||
@@ -194,13 +207,29 @@ if (!isDiscriminatedObject(value)) { | ||
} else { | ||
concreteDef = lexicons.getDefOrThrow(value.$type) | ||
concreteDefs = toConcreteTypes(lexicons, { | ||
type: 'ref', | ||
ref: value.$type, | ||
}) | ||
} | ||
} else if (def.type === 'ref') { | ||
concreteDef = lexicons.getDefOrThrow(def.ref) | ||
} else { | ||
concreteDef = def | ||
concreteDefs = toConcreteTypes(lexicons, def) | ||
} | ||
return mustBeObj | ||
? object(lexicons, path, concreteDef, value) | ||
: validate(lexicons, path, concreteDef, value) | ||
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 } | ||
} | ||
@@ -217,3 +246,3 @@ | ||
if (lexUri.endsWith('#main')) { | ||
return refs.includes(lexUri.slice(0, -5)) | ||
return refs.includes(lexUri.replace('#main', '')) | ||
} else { | ||
@@ -220,0 +249,0 @@ return refs.includes(lexUri + '#main') |
@@ -10,3 +10,3 @@ import { isValidISODateString } from 'iso-datestring-validator' | ||
ensureValidRecordKey, | ||
isValidTid, | ||
ensureValidTid, | ||
} from '@atproto/syntax' | ||
@@ -135,10 +135,13 @@ import { ValidationError, ValidationResult } from '../types' | ||
export function tid(path: string, value: string): ValidationResult { | ||
if (isValidTid(value)) { | ||
return { success: true, value } | ||
try { | ||
ensureValidTid(value) | ||
} catch { | ||
return { | ||
success: false, | ||
error: new ValidationError( | ||
`${path} must be a valid TID (timestamp identifier)`, | ||
), | ||
} | ||
} | ||
return { | ||
success: false, | ||
error: new ValidationError(`${path} must be a valid TID`), | ||
} | ||
return { success: true, value } | ||
} | ||
@@ -145,0 +148,0 @@ |
@@ -42,3 +42,3 @@ import { CID } from 'multiformats/cid' | ||
function boolean( | ||
export function boolean( | ||
lexicons: Lexicons, | ||
@@ -81,3 +81,3 @@ path: string, | ||
function integer( | ||
export function integer( | ||
lexicons: Lexicons, | ||
@@ -156,3 +156,3 @@ path: string, | ||
function string( | ||
export function string( | ||
lexicons: Lexicons, | ||
@@ -346,3 +346,3 @@ path: string, | ||
function bytes( | ||
export function bytes( | ||
lexicons: Lexicons, | ||
@@ -389,3 +389,3 @@ path: string, | ||
function cidLink( | ||
export function cidLink( | ||
lexicons: Lexicons, | ||
@@ -406,3 +406,3 @@ path: string, | ||
function unknown( | ||
export function unknown( | ||
lexicons: Lexicons, | ||
@@ -409,0 +409,0 @@ path: string, |
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
1187059
27905
0
+ Added@atproto/common-web@0.4.0(transitive)
- Removed@atproto/common-web@0.3.2(transitive)
Updated@atproto/common-web@^0.4.0
Updated@atproto/syntax@^0.3.2