diff --git a/src/ois.test.ts b/src/ois.test.ts index 3622c25..4e912d0 100644 --- a/src/ois.test.ts +++ b/src/ois.test.ts @@ -13,6 +13,7 @@ import { reservedParametersSchema, packageVersionCompatibleSemverSchema, fixedParameterSchema, + endpointSchema, } from './ois'; import { version as packageVersion } from '../package.json'; @@ -717,3 +718,69 @@ describe('fixedOperationParameters', () => { expect(() => fixedParameterSchema.parse(valueWithObject)).not.toThrow(); }); }); + +describe('processing specification', () => { + it('allows pre-processing and post-processing specifications with different version', () => { + const endpoint1 = { ...loadOisFixture().endpoints[0] }; + endpoint1.preProcessingSpecifications = [ + { + environment: 'Node', + timeoutMs: 5000, + value: 'output = input;', + }, + ]; + endpoint1.postProcessingSpecificationV2 = { + environment: 'Node', + timeoutMs: 5000, + value: '(payload) => payload;', + }; + const endpoint2 = { ...loadOisFixture().endpoints[0] }; + endpoint2.preProcessingSpecificationV2 = { + environment: 'Node', + timeoutMs: 5000, + value: '(payload) => payload;', + }; + endpoint2.postProcessingSpecifications = [ + { + environment: 'Node', + timeoutMs: 5000, + value: 'output = input;', + }, + ]; + + expect(() => endpointSchema.parse(endpoint1)).not.toThrow(); + expect(() => endpointSchema.parse(endpoint2)).not.toThrow(); + }); + + it('throws when conflicting processing specifications are defined', () => { + const endpoint1 = { ...loadOisFixture().endpoints[0] }; + endpoint1.preProcessingSpecifications = [ + { + environment: 'Node', + timeoutMs: 5000, + value: 'output = input;', + }, + ]; + endpoint1.preProcessingSpecificationV2 = { + environment: 'Node', + timeoutMs: 5000, + value: '(payload) => payload;', + }; + const endpoint2 = { ...loadOisFixture().endpoints[0] }; + endpoint2.postProcessingSpecifications = [ + { + environment: 'Node', + timeoutMs: 5000, + value: 'output = input;', + }, + ]; + endpoint2.postProcessingSpecificationV2 = { + environment: 'Node', + timeoutMs: 5000, + value: '(payload) => payload;', + }; + + expect(() => endpointSchema.parse(endpoint1)).toThrow(); + expect(() => endpointSchema.parse(endpoint2)).toThrow(); + }); +}); diff --git a/src/ois.ts b/src/ois.ts index fcc9d34..646e360 100644 --- a/src/ois.ts +++ b/src/ois.ts @@ -276,6 +276,14 @@ export const processingSpecificationSchema = z }) .strict(); +export const processingSpecificationSchemaV2 = z + .object({ + environment: z.literal('Node'), + value: z.string(), + timeoutMs: nonNegativeIntSchema, + }) + .strict(); + const ensureUniqueEndpointParameterNames: SuperRefinement = (parameters, ctx) => { const groups = Object.values(groupBy(parameters, 'name')); const duplicates = groups.filter((group) => group.length > 1).flatMap((group) => group.map((p) => p.name)); @@ -320,12 +328,44 @@ export const endpointSchema = z preProcessingSpecifications: z.array(processingSpecificationSchema).optional(), postProcessingSpecifications: z.array(processingSpecificationSchema).optional(), + // Post-processing only supported processing value, but there are use cases for processing timestamp as well. With + // the original processing specification, users needed to assign processed value to a special output variable and + // the schema supported multiple snippets that are composed together which is not necessary. For further information + // see: https://github.com/api3dao/commons/issues/27. + // + // A new processing implementation is created that addresses all the previously mentioned issues. The schemas remain + // optional, for the same reasons as in the original implementation. + preProcessingSpecificationV2: processingSpecificationSchemaV2.optional(), + postProcessingSpecificationV2: processingSpecificationSchemaV2.optional(), + // The following fields are ignored by Airnode description: z.string().optional(), externalDocs: z.string().optional(), summary: z.string().optional(), }) - .strict(); + .strict() + .superRefine((endpoint, ctx) => { + const { + preProcessingSpecificationV2, + preProcessingSpecifications, + postProcessingSpecificationV2, + postProcessingSpecifications, + } = endpoint; + + if (preProcessingSpecificationV2 && preProcessingSpecifications) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Only one of "preProcessingSpecificationV2" and "preProcessingSpecifications" can be defined', + }); + } + + if (postProcessingSpecificationV2 && postProcessingSpecifications) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Only one of "postProcessingSpecificationV2" and "postProcessingSpecifications" can be defined', + }); + } + }); const ensureSingleParameterUsagePerEndpoint: SuperRefinement<{ endpoints: Endpoint[];