diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 08fe07fce02..3a27f733887 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -4403,7 +4403,7 @@ export const schemaDict = { properties: { repost: { type: 'string', - ref: 'at-uri', + format: 'at-uri', }, }, }, diff --git a/packages/lexicon/src/types.ts b/packages/lexicon/src/types.ts index 7a4f563046b..06ec0548f02 100644 --- a/packages/lexicon/src/types.ts +++ b/packages/lexicon/src/types.ts @@ -5,23 +5,27 @@ import { requiredPropertiesRefinement } from './util' // primitives // = -export const lexBoolean = z.object({ - type: z.literal('boolean'), - description: z.string().optional(), - default: z.boolean().optional(), - const: z.boolean().optional(), -}) +export const lexBoolean = z + .object({ + type: z.literal('boolean'), + description: z.string().optional(), + default: z.boolean().optional(), + const: z.boolean().optional(), + }) + .strict() export type LexBoolean = z.infer -export const lexInteger = z.object({ - type: z.literal('integer'), - description: z.string().optional(), - default: z.number().int().optional(), - minimum: z.number().int().optional(), - maximum: z.number().int().optional(), - enum: z.number().int().array().optional(), - const: z.number().int().optional(), -}) +export const lexInteger = z + .object({ + type: z.literal('integer'), + description: z.string().optional(), + default: z.number().int().optional(), + minimum: z.number().int().optional(), + maximum: z.number().int().optional(), + enum: z.number().int().array().optional(), + const: z.number().int().optional(), + }) + .strict() export type LexInteger = z.infer export const lexStringFormat = z.enum([ @@ -36,25 +40,29 @@ export const lexStringFormat = z.enum([ ]) export type LexStringFormat = z.infer -export const lexString = z.object({ - type: z.literal('string'), - format: lexStringFormat.optional(), - description: z.string().optional(), - default: z.string().optional(), - minLength: z.number().int().optional(), - maxLength: z.number().int().optional(), - minGraphemes: z.number().int().optional(), - maxGraphemes: z.number().int().optional(), - enum: z.string().array().optional(), - const: z.string().optional(), - knownValues: z.string().array().optional(), -}) +export const lexString = z + .object({ + type: z.literal('string'), + format: lexStringFormat.optional(), + description: z.string().optional(), + default: z.string().optional(), + minLength: z.number().int().optional(), + maxLength: z.number().int().optional(), + minGraphemes: z.number().int().optional(), + maxGraphemes: z.number().int().optional(), + enum: z.string().array().optional(), + const: z.string().optional(), + knownValues: z.string().array().optional(), + }) + .strict() export type LexString = z.infer -export const lexUnknown = z.object({ - type: z.literal('unknown'), - description: z.string().optional(), -}) +export const lexUnknown = z + .object({ + type: z.literal('unknown'), + description: z.string().optional(), + }) + .strict() export type LexUnknown = z.infer export const lexPrimitive = z.discriminatedUnion('type', [ @@ -68,18 +76,22 @@ export type LexPrimitive = z.infer // ipld types // = -export const lexBytes = z.object({ - type: z.literal('bytes'), - description: z.string().optional(), - maxLength: z.number().optional(), - minLength: z.number().optional(), -}) +export const lexBytes = z + .object({ + type: z.literal('bytes'), + description: z.string().optional(), + maxLength: z.number().optional(), + minLength: z.number().optional(), + }) + .strict() export type LexBytes = z.infer -export const lexCidLink = z.object({ - type: z.literal('cid-link'), - description: z.string().optional(), -}) +export const lexCidLink = z + .object({ + type: z.literal('cid-link'), + description: z.string().optional(), + }) + .strict() export type LexCidLink = z.infer export const lexIpldType = z.discriminatedUnion('type', [lexBytes, lexCidLink]) @@ -88,19 +100,23 @@ export type LexIpldType = z.infer // references // = -export const lexRef = z.object({ - type: z.literal('ref'), - description: z.string().optional(), - ref: z.string(), -}) +export const lexRef = z + .object({ + type: z.literal('ref'), + description: z.string().optional(), + ref: z.string(), + }) + .strict() export type LexRef = z.infer -export const lexRefUnion = z.object({ - type: z.literal('union'), - description: z.string().optional(), - refs: z.string().array(), - closed: z.boolean().optional(), -}) +export const lexRefUnion = z + .object({ + type: z.literal('union'), + description: z.string().optional(), + refs: z.string().array(), + closed: z.boolean().optional(), + }) + .strict() export type LexRefUnion = z.infer export const lexRefVariant = z.discriminatedUnion('type', [lexRef, lexRefUnion]) @@ -109,37 +125,45 @@ export type LexRefVariant = z.infer // blobs // = -export const lexBlob = z.object({ - type: z.literal('blob'), - description: z.string().optional(), - accept: z.string().array().optional(), - maxSize: z.number().optional(), -}) +export const lexBlob = z + .object({ + type: z.literal('blob'), + description: z.string().optional(), + accept: z.string().array().optional(), + maxSize: z.number().optional(), + }) + .strict() export type LexBlob = z.infer // complex types // = -export const lexArray = z.object({ - type: z.literal('array'), - description: z.string().optional(), - items: z.union([lexPrimitive, lexIpldType, lexBlob, lexRefVariant]), - minLength: z.number().int().optional(), - maxLength: z.number().int().optional(), -}) +export const lexArray = z + .object({ + type: z.literal('array'), + description: z.string().optional(), + items: z.union([lexPrimitive, lexIpldType, lexBlob, lexRefVariant]), + minLength: z.number().int().optional(), + maxLength: z.number().int().optional(), + }) + .strict() export type LexArray = z.infer export const lexPrimitiveArray = lexArray.merge( - z.object({ - items: lexPrimitive, - }), + z + .object({ + items: lexPrimitive, + }) + .strict(), ) export type LexPrimitiveArray = z.infer -export const lexToken = z.object({ - type: z.literal('token'), - description: z.string().optional(), -}) +export const lexToken = z + .object({ + type: z.literal('token'), + description: z.string().optional(), + }) + .strict() export type LexToken = z.infer export const lexObject = z @@ -154,6 +178,7 @@ export const lexObject = z ) .optional(), }) + .strict() .superRefine(requiredPropertiesRefinement) export type LexObject = z.infer @@ -167,68 +192,83 @@ export const lexXrpcParameters = z required: z.string().array().optional(), properties: z.record(z.union([lexPrimitive, lexPrimitiveArray])), }) + .strict() .superRefine(requiredPropertiesRefinement) export type LexXrpcParameters = z.infer -export const lexXrpcBody = z.object({ - description: z.string().optional(), - encoding: z.string(), - schema: z.union([lexRefVariant, lexObject]).optional(), -}) +export const lexXrpcBody = z + .object({ + description: z.string().optional(), + encoding: z.string(), + schema: z.union([lexRefVariant, lexObject]).optional(), + }) + .strict() export type LexXrpcBody = z.infer -export const lexXrpcSubscriptionMessage = z.object({ - description: z.string().optional(), - schema: z.union([lexRefVariant, lexObject]).optional(), -}) +export const lexXrpcSubscriptionMessage = z + .object({ + description: z.string().optional(), + schema: z.union([lexRefVariant, lexObject]).optional(), + }) + .strict() export type LexXrpcSubscriptionMessage = z.infer< typeof lexXrpcSubscriptionMessage > -export const lexXrpcError = z.object({ - name: z.string(), - description: z.string().optional(), -}) +export const lexXrpcError = z + .object({ + name: z.string(), + description: z.string().optional(), + }) + .strict() export type LexXrpcError = z.infer -export const lexXrpcQuery = z.object({ - type: z.literal('query'), - description: z.string().optional(), - parameters: lexXrpcParameters.optional(), - output: lexXrpcBody.optional(), - errors: lexXrpcError.array().optional(), -}) +export const lexXrpcQuery = z + .object({ + type: z.literal('query'), + description: z.string().optional(), + parameters: lexXrpcParameters.optional(), + output: lexXrpcBody.optional(), + errors: lexXrpcError.array().optional(), + }) + .strict() export type LexXrpcQuery = z.infer -export const lexXrpcProcedure = z.object({ - type: z.literal('procedure'), - description: z.string().optional(), - parameters: lexXrpcParameters.optional(), - input: lexXrpcBody.optional(), - output: lexXrpcBody.optional(), - errors: lexXrpcError.array().optional(), -}) +export const lexXrpcProcedure = z + .object({ + type: z.literal('procedure'), + description: z.string().optional(), + parameters: lexXrpcParameters.optional(), + input: lexXrpcBody.optional(), + output: lexXrpcBody.optional(), + errors: lexXrpcError.array().optional(), + }) + .strict() export type LexXrpcProcedure = z.infer -export const lexXrpcSubscription = z.object({ - type: z.literal('subscription'), - description: z.string().optional(), - parameters: lexXrpcParameters.optional(), - message: lexXrpcSubscriptionMessage.optional(), - infos: lexXrpcError.array().optional(), - errors: lexXrpcError.array().optional(), -}) +export const lexXrpcSubscription = z + .object({ + type: z.literal('subscription'), + description: z.string().optional(), + parameters: lexXrpcParameters.optional(), + message: lexXrpcSubscriptionMessage.optional(), + infos: lexXrpcError.array().optional(), + errors: lexXrpcError.array().optional(), + }) + .strict() export type LexXrpcSubscription = z.infer // database // = -export const lexRecord = z.object({ - type: z.literal('record'), - description: z.string().optional(), - key: z.string().optional(), - record: lexObject, -}) +export const lexRecord = z + .object({ + type: z.literal('record'), + description: z.string().optional(), + key: z.string().optional(), + record: lexObject, + }) + .strict() export type LexRecord = z.infer // core @@ -331,6 +371,7 @@ export const lexiconDoc = z description: z.string().optional(), defs: z.record(lexUserType), }) + .strict() .superRefine((doc, ctx) => { for (const defId in doc.defs) { const def = doc.defs[defId] diff --git a/packages/lexicon/tests/general.test.ts b/packages/lexicon/tests/general.test.ts index 50d91453f8b..8dc43e9b943 100644 --- a/packages/lexicon/tests/general.test.ts +++ b/packages/lexicon/tests/general.test.ts @@ -101,6 +101,22 @@ describe('General validation', () => { lexiconDoc.parse(schema) }).toThrow('Required field \\"foo\\" not defined') }) + it('fails when unknown fields are present', () => { + const schema = { + lexicon: 1, + id: 'com.example.unknownFields', + defs: { + test: { + type: 'object', + foo: 3, + }, + }, + } + + expect(() => { + lexiconDoc.parse(schema) + }).toThrow("Unrecognized key(s) in object: 'foo'") + }) it('fails lexicon parsing when uri is invalid', () => { const schema = { lexicon: 1,