From 2c26a75a143c81da8dadb43b6fc06ac77436ed48 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Thu, 5 Jun 2025 09:22:22 +0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/e2e/src/__snapshots__/e2e.test.ts.snap | 12 +++--- apps/e2e/src/fixtures.ts | 39 +++++++++--------- .../src/lib/cf-validate-json-schema-object.ts | 10 +++++ packages/json-schema/src/validate.ts | 40 +++++++++++++++---- packages/json-schema/src/validator.ts | 2 +- 5 files changed, 70 insertions(+), 33 deletions(-) diff --git a/apps/e2e/src/__snapshots__/e2e.test.ts.snap b/apps/e2e/src/__snapshots__/e2e.test.ts.snap index 3f8ef470..905b6404 100644 --- a/apps/e2e/src/__snapshots__/e2e.test.ts.snap +++ b/apps/e2e/src/__snapshots__/e2e.test.ts.snap @@ -36,7 +36,7 @@ et est aut quod aut provident voluptas autem voluptas", } `; -exports[`Basic OpenAPI getPost success > 0.0: GET dev/test-basic-openapi/getPost 1`] = ` +exports[`Basic OpenAPI getPost success > 0.0: POST dev/test-basic-openapi/getPost 1`] = ` { "body": "quia et suscipit suscipit recusandae consequuntur expedita et cum @@ -48,7 +48,7 @@ nostrum rerum est autem sunt rem eveniet architecto", } `; -exports[`Basic OpenAPI getPost success > 0.1: GET dev/test-basic-openapi@b6e21206/getPost?postId=1 1`] = ` +exports[`Basic OpenAPI getPost success > 0.1: POST dev/test-basic-openapi@latest/getPost 1`] = ` { "body": "quia et suscipit suscipit recusandae consequuntur expedita et cum @@ -60,7 +60,7 @@ nostrum rerum est autem sunt rem eveniet architecto", } `; -exports[`Basic OpenAPI getPost success > 0.2: GET dev/test-basic-openapi@b6e21206/get_post?postId=1 1`] = ` +exports[`Basic OpenAPI getPost success > 0.2: GET dev/test-basic-openapi/getPost 1`] = ` { "body": "quia et suscipit suscipit recusandae consequuntur expedita et cum @@ -72,7 +72,7 @@ nostrum rerum est autem sunt rem eveniet architecto", } `; -exports[`Basic OpenAPI getPost success > 0.3: GET dev/test-basic-openapi@b6e21206/getPost 1`] = ` +exports[`Basic OpenAPI getPost success > 0.3: GET dev/test-basic-openapi@b6e21206/getPost?postId=1 1`] = ` { "body": "quia et suscipit suscipit recusandae consequuntur expedita et cum @@ -84,7 +84,7 @@ nostrum rerum est autem sunt rem eveniet architecto", } `; -exports[`Basic OpenAPI getPost success > 0.4: POST dev/test-basic-openapi/getPost 1`] = ` +exports[`Basic OpenAPI getPost success > 0.4: GET dev/test-basic-openapi@b6e21206/get_post?postId=1 1`] = ` { "body": "quia et suscipit suscipit recusandae consequuntur expedita et cum @@ -96,7 +96,7 @@ nostrum rerum est autem sunt rem eveniet architecto", } `; -exports[`Basic OpenAPI getPost success > 0.5: POST dev/test-basic-openapi@latest/getPost 1`] = ` +exports[`Basic OpenAPI getPost success > 0.5: GET dev/test-basic-openapi@b6e21206/getPost 1`] = ` { "body": "quia et suscipit suscipit recusandae consequuntur expedita et cum diff --git a/apps/e2e/src/fixtures.ts b/apps/e2e/src/fixtures.ts index 65650887..6aff8f75 100644 --- a/apps/e2e/src/fixtures.ts +++ b/apps/e2e/src/fixtures.ts @@ -48,10 +48,31 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ title: 'Basic OpenAPI getPost success', compareResponseBodies: true, fixtures: [ + { + path: 'dev/test-basic-openapi/getPost', + request: { + method: 'POST', + json: { + postId: 1 + } + } + }, + { + path: 'dev/test-basic-openapi@latest/getPost', + request: { + method: 'POST', + json: { + postId: 1 + } + } + }, { path: 'dev/test-basic-openapi/getPost', request: { searchParams: { + // all of these GET requests implicitly test type coercion since + // `postId` as a query param will be a string, but the tool expects + // an integer. postId: 1 } } @@ -69,24 +90,6 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ postId: 1 } } - }, - { - path: 'dev/test-basic-openapi/getPost', - request: { - method: 'POST', - json: { - postId: 1 - } - } - }, - { - path: 'dev/test-basic-openapi@latest/getPost', - request: { - method: 'POST', - json: { - postId: 1 - } - } } ] }, diff --git a/apps/gateway/src/lib/cf-validate-json-schema-object.ts b/apps/gateway/src/lib/cf-validate-json-schema-object.ts index 0d27f393..8ae14fbc 100644 --- a/apps/gateway/src/lib/cf-validate-json-schema-object.ts +++ b/apps/gateway/src/lib/cf-validate-json-schema-object.ts @@ -40,6 +40,10 @@ export function cfValidateJsonSchemaObject< } } + // Special-case check for additional top-level properties, which is not + // currently handled by `@agentic/json-schema`. + // TODO: In the future, the underlying JSON schema validation should handle + // this for any sub-schema, not just the top-level one. if (schema.properties && !schema.additionalProperties) { const extraProperties = Object.keys(data).filter( (key) => !schema.properties[key] @@ -56,6 +60,12 @@ export function cfValidateJsonSchemaObject< const validator = new Validator({ schema, coerce }) const result = validator.validate(data) if (result.valid) { + // console.log('validate', { + // schema, + // data, + // result: result.instance + // }) + // Return the (possibly) coerced data return result.instance as T } diff --git a/packages/json-schema/src/validate.ts b/packages/json-schema/src/validate.ts index 55e66693..aecf0df2 100644 --- a/packages/json-schema/src/validate.ts +++ b/packages/json-schema/src/validate.ts @@ -123,7 +123,9 @@ export function validate( keywordLocation, evaluated ) - if (!result.valid) { + if (result.valid) { + instance = result.instance + } else { errors.push( { instanceLocation, @@ -160,7 +162,9 @@ export function validate( keywordLocation, evaluated ) - if (!result.valid) { + if (result.valid) { + instance = result.instance + } else { errors.push( { instanceLocation, @@ -285,6 +289,7 @@ export function validate( } } + // TODO: type coercion if ($not !== undefined) { const keywordLocation = `${schemaLocation}/not` const result = validate( @@ -311,6 +316,7 @@ export function validate( const subEvaluateds: Array = [] + // TODO: type coercion if ($anyOf !== undefined) { const keywordLocation = `${schemaLocation}/anyOf` const errorsLength = errors.length @@ -347,6 +353,7 @@ export function validate( } } + // TODO: type coercion if ($allOf !== undefined) { const keywordLocation = `${schemaLocation}/allOf` const errorsLength = errors.length @@ -383,6 +390,7 @@ export function validate( } } + // TODO: type coercion if ($oneOf !== undefined) { const keywordLocation = `${schemaLocation}/oneOf` const errorsLength = errors.length @@ -450,7 +458,9 @@ export function validate( `${schemaLocation}/then`, evaluated ) - if (!thenResult.valid) { + if (thenResult.valid) { + instance = thenResult.instance + } else { errors.push( { instanceLocation, @@ -475,7 +485,9 @@ export function validate( `${schemaLocation}/else`, evaluated ) - if (!elseResult.valid) { + if (elseResult.valid) { + instance = elseResult.instance + } else { errors.push( { instanceLocation, @@ -670,6 +682,7 @@ export function validate( ) if (result.valid) { evaluated[key] = thisEvaluated[key] = true + instance[key] = result.instance } else { stop = shortCircuit errors.push( @@ -709,6 +722,7 @@ export function validate( ) if (result.valid) { evaluated[key] = thisEvaluated[key] = true + instance[key] = result.instance } else { stop = shortCircuit errors.push( @@ -745,6 +759,7 @@ export function validate( ) if (result.valid) { evaluated[key] = true + instance[key] = result.instance } else { stop = shortCircuit errors.push( @@ -776,6 +791,7 @@ export function validate( ) if (result.valid) { evaluated[key] = true + instance[key] = result.instance } else { errors.push( { @@ -862,7 +878,9 @@ export function validate( `${keywordLocation}/${i}` ) evaluated[i] = true - if (!result.valid) { + if (result.valid) { + instance[i] = result.instance + } else { stop = shortCircuit errors.push( { @@ -890,7 +908,9 @@ export function validate( keywordLocation ) evaluated[i] = true - if (!result.valid) { + if (result.valid) { + instance[i] = result.instance + } else { stop = shortCircuit errors.push( { @@ -921,7 +941,9 @@ export function validate( keywordLocation ) evaluated[i] = true - if (!result.valid) { + if (result.valid) { + instance[i] = result.instance + } else if (!result.valid) { stop = shortCircuit errors.push( { @@ -1027,7 +1049,9 @@ export function validate( keywordLocation ) evaluated[i] = true - if (!result.valid) { + if (result.valid) { + instance[i] = result.instance + } else { errors.push( { instanceLocation, diff --git a/packages/json-schema/src/validator.ts b/packages/json-schema/src/validator.ts index d7cd0d91..67fe385c 100644 --- a/packages/json-schema/src/validator.ts +++ b/packages/json-schema/src/validator.ts @@ -29,7 +29,7 @@ export class Validator { public validate(instance: any): ValidationResult { return validate( - instance, + structuredClone(instance), this.schema, this.draft, this.lookup,