UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

229 lines (210 loc) 5.59 kB
/** * Copyright IBM Corp. 2024, 2025 */ import { z } from 'zod'; import { BaseModel, IfConditionSchema, RequestSkippedSchema, StopOnFailSchema, } from './shared.schema.js'; import { EnvironmentSchema } from './environment.schema.js'; import { AssertionSchema } from './assertions.schema.js'; const refRule = z .string() .refine((val) => val !== '', { message: '$ref cannot be an empty string', }) .optional(); const apiRefRule = z .union([ z.string().refine((val) => val !== '', { message: '$ref cannot be an empty string', }), z.array( z.string().refine((val) => val !== '', { message: '$ref array items cannot be empty strings', }), ), ]) .optional(); const RawPayloadSchema = z .object({ json: z.string().optional(), js: z.string().optional(), html: z.string().optional(), xml: z.string().optional(), }) .strict() .optional(); const KeyValueSchema = z .array( z.object({ key: z.string(), value: z.any().refine((val) => val !== undefined, { message: 'value is required', }), type: z.string().optional(), }), ) .optional(); export const PayloadUnionSchema = z .object({ raw: RawPayloadSchema, urlEncodedFormData: KeyValueSchema, formData: KeyValueSchema, }) .refine( (data) => { const keys = ['raw', 'urlEncodedFormData', 'formData']; const presentKeys = keys.filter( (key) => data[key as keyof typeof data] !== undefined, ); return presentKeys.length === 1; }, { message: 'Exactly one of raw, urlEncodedFormData, or formData must be provided in payload', }, ); const ApiRefOrEndpointSchema = z .object({ $ref: apiRefRule, $endpoint: z.string().optional(), }) .refine( (data) => (data.$ref && !data.$endpoint) || (!data.$ref && data.$endpoint), { message: 'Either $ref or $endpoint must be provided, but not both in api', path: ['$ref', '$endpoint'], }, ); const AssertionRefSchema = z .object({ $ref: refRule, assertions: z.array(AssertionSchema).optional(), }) .refine( (data) => data == undefined || data.$ref !== undefined || (data.assertions !== undefined && data.assertions.length > 0), { message: 'Either $ref or assertions (non-empty array) with complete data must be provided', path: ['$ref', 'assertions'], }, ); const EnvironmentSpecSchema = z .object({ $ref: refRule, variables: z.array(EnvironmentSchema).optional(), }) .refine( (data) => data == undefined || data.$ref !== undefined || (data.variables !== undefined && data.variables.length > 0), { message: 'Either $ref or variables (non-empty array) must be provided in environment', path: ['$ref', 'variables'], }, ); const BasicAuthSchema = z.object({ username: z.string(), password: z.string(), }); export const AuthSchema = z .object({ noauth: z.boolean().optional(), bearerToken: z.string().optional(), basicAuth: BasicAuthSchema.optional(), }) .refine( (data) => { const keys = ['noauth', 'bearerToken', 'basicAuth']; const present = keys.filter( (key) => data[key as keyof typeof data] !== undefined, ); return present.length <= 1; }, { message: 'Only one of noauth, bearerToken, or basicAuth must be provided in auth', path: ['auth'], }, ); export const TestStepSchema = z.object({ endpoint: z.string().optional(), method: z.string(), if: IfConditionSchema.optional(), stopOnFail: StopOnFailSchema.optional(), skipped: RequestSkippedSchema.optional(), resource: z.string(), headers: z .array( z.object({ key: z.string(), value: z.any().refine((val) => val !== undefined, { message: 'value is required in headers', }), description: z.string().optional(), }), ) .optional(), auth: AuthSchema.optional(), payload: PayloadUnionSchema.optional(), settings: z .object({ sslVerification: z.boolean().optional(), encodeURL: z.boolean().optional(), }) .optional(), parameters: KeyValueSchema.optional(), assertions: z .union([ // New format: array of objects with $ref z.array(AssertionRefSchema.optional()), // Single assertion with direct $ref property AssertionRefSchema, ]) .optional(), var: z .union([ z.string(), z.array( z.union([ z.record(z.string(), z.string()), z.object({ key: z.string(), value: z.string(), }), ]), ), ]) .optional(), }); export const TestSchema = BaseModel.extend({ kind: z.literal('test'), spec: z.object({ // will be the url to make the request api: ApiRefOrEndpointSchema, environment: z .union([ // New format: array of objects with $ref z.array(EnvironmentSpecSchema.optional()), // Single assertion with direct $ref property EnvironmentSpecSchema, ]) .optional(), // tests which will have path and assertions request: z.array(TestStepSchema), }), vcmId: z.string().optional(), }); export type Test = z.infer<typeof TestSchema>; export type Request = z.infer<typeof TestStepSchema>; export type Payload = z.infer<typeof PayloadUnionSchema>; export type AuthOptions = z.infer<typeof AuthSchema>; export type Assertions = z.infer<typeof AssertionRefSchema>;