diff --git a/package-lock.json b/package-lock.json index 46675b68..eaf43ff3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@asyncapi/avro-schema-parser": "^3.0.24", - "@asyncapi/modelina": "^6.0.0-next.7", + "@asyncapi/modelina": "^6.0.0-next.10", "@asyncapi/openapi-schema-parser": "^3.0.24", "@asyncapi/parser": "^3.4.0", "@asyncapi/protobuf-schema-parser": "^3.5.1", @@ -172,9 +172,9 @@ } }, "node_modules/@asyncapi/modelina": { - "version": "6.0.0-next.7", - "resolved": "https://registry.npmjs.org/@asyncapi/modelina/-/modelina-6.0.0-next.7.tgz", - "integrity": "sha512-+32lv/8YN3Fr7qizR/Eu/JTPNqYVVH7OzXVen1847xd18Og2+xcYtiHfyBi++/AHgfi2MsEM6XlgR58ZNS1bzQ==", + "version": "6.0.0-next.10", + "resolved": "https://registry.npmjs.org/@asyncapi/modelina/-/modelina-6.0.0-next.10.tgz", + "integrity": "sha512-wF0bAUzzbPfNI33TTEg2Oc4mZwgHCWYHu7dvs2GaJ2/UMmCLBZaGHZcx3g3tgfSyCmk7+ivU7ZyZ7NmtILaTUA==", "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.1.0", diff --git a/package.json b/package.json index 6ff0cee9..94a126d4 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "bugs": "https://github.com/the-codegen-project/cli/issues", "dependencies": { "@asyncapi/avro-schema-parser": "^3.0.24", - "@asyncapi/modelina": "^6.0.0-next.7", + "@asyncapi/modelina": "^6.0.0-next.10", "@asyncapi/openapi-schema-parser": "^3.0.24", "@asyncapi/parser": "^3.4.0", "@asyncapi/protobuf-schema-parser": "^3.5.1", diff --git a/src/codegen/modelina/presets/primitives.ts b/src/codegen/modelina/presets/primitives.ts index d150db5d..8bcf2f83 100644 --- a/src/codegen/modelina/presets/primitives.ts +++ b/src/codegen/modelina/presets/primitives.ts @@ -4,7 +4,9 @@ import { ConstrainedIntegerModel, ConstrainedFloatModel, ConstrainedBooleanModel, - ConstrainedArrayModel + ConstrainedArrayModel, + ConstrainedAnyModel, + ConstrainedUnionModel } from '@asyncapi/modelina'; import {TypeScriptRenderer} from '@asyncapi/modelina/lib/types/generators/typescript/TypeScriptRenderer'; import { @@ -32,6 +34,55 @@ function isPrimitiveModel(model: ConstrainedMetaModel): boolean { ); } +/** + * Check if the model is a null type (ConstrainedAnyModel with type: null in schema) + * Modelina converts null types to ConstrainedAnyModel + */ +function isNullModel(model: ConstrainedMetaModel): boolean { + if (!(model instanceof ConstrainedAnyModel)) { + return false; + } + // Check if the original input schema has type: null + const originalInput = model.originalInput; + return originalInput && originalInput.type === 'null'; +} + +/** + * Check if the model is a date format string type (date or date-time) + * Note: 'time' format is NOT included because RFC 3339 time strings like "14:30:00" + * are not valid Date constructor arguments in JavaScript and would produce Invalid Date. + * Time values should remain as strings. + */ +function isDateFormatModel(model: ConstrainedMetaModel): boolean { + if (!(model instanceof ConstrainedStringModel)) { + return false; + } + const format = model.originalInput?.format; + return format === 'date' || format === 'date-time'; +} + +/** + * Render marshal function for null type + */ +function renderNullMarshal(model: ConstrainedMetaModel): string { + return `export function marshal(payload: null): string { + return JSON.stringify(payload); +}`; +} + +/** + * Render unmarshal function for null type + */ +function renderNullUnmarshal(model: ConstrainedMetaModel): string { + return `export function unmarshal(json: string): null { + const parsed = JSON.parse(json); + if (parsed !== null) { + throw new Error('Expected null value'); + } + return null; +}`; +} + /** * Render marshal function for primitive types */ @@ -52,6 +103,27 @@ function renderPrimitiveUnmarshal(model: ConstrainedMetaModel): string { }`; } +/** + * Render unmarshal function for date format types + * Converts JSON string to JavaScript Date object + */ +function renderDateUnmarshal(model: ConstrainedMetaModel): string { + return `export function unmarshal(json: string): ${model.name} { + const parsed = JSON.parse(json); + return new Date(parsed); +}`; +} + +/** + * Render marshal function for date format types + * Note: JSON.stringify(Date) calls Date.toJSON() which returns ISO string + */ +function renderDateMarshal(model: ConstrainedMetaModel): string { + return `export function marshal(payload: ${model.name}): string { + return JSON.stringify(payload); +}`; +} + /** * Render marshal function for array types */ @@ -86,11 +158,15 @@ function renderArrayMarshal(model: ConstrainedArrayModel): string { function renderArrayUnmarshal(model: ConstrainedArrayModel): string { const valueModel = model.valueModel; - // Check if array items have an unmarshal method (object types) + // Check if array items have an unmarshal method (only object types do) + // Exclude primitives, nested arrays, and union types - they don't have unmarshal methods + // Union types are just type aliases without static unmarshal methods const hasItemUnmarshal = valueModel.type !== 'string' && valueModel.type !== 'number' && - valueModel.type !== 'boolean'; + valueModel.type !== 'boolean' && + !(valueModel instanceof ConstrainedArrayModel) && + !(valueModel instanceof ConstrainedUnionModel); if (hasItemUnmarshal) { const itemTypeName = valueModel.name; @@ -145,10 +221,19 @@ export function createPrimitivesPreset( }) { // Handle primitive types (string, integer, float, boolean) if (isPrimitiveModel(model)) { + // Use date-specific marshal/unmarshal for date formats + const isDate = isDateFormatModel(model); + const unmarshalFunc = isDate + ? renderDateUnmarshal(model) + : renderPrimitiveUnmarshal(model); + const marshalFunc = isDate + ? renderDateMarshal(model) + : renderPrimitiveMarshal(model); + return `${content} -${renderPrimitiveUnmarshal(model)} -${renderPrimitiveMarshal(model)} +${unmarshalFunc} +${marshalFunc} ${options.includeValidation ? generateTypescriptValidationCode({model, renderer, asClassMethods: false, context: context as any}) : ''} `; } @@ -163,6 +248,16 @@ ${options.includeValidation ? generateTypescriptValidationCode({model, renderer, `; } + // Handle null types (ConstrainedAnyModel with type: null in original schema) + if (isNullModel(model)) { + return `${content} + +${renderNullUnmarshal(model)} +${renderNullMarshal(model)} +${options.includeValidation ? generateTypescriptValidationCode({model, renderer, asClassMethods: false, context: context as any}) : ''} +`; + } + return content; } } diff --git a/src/codegen/modelina/presets/validation.ts b/src/codegen/modelina/presets/validation.ts index 1fbfc75b..40218394 100644 --- a/src/codegen/modelina/presets/validation.ts +++ b/src/codegen/modelina/presets/validation.ts @@ -12,6 +12,7 @@ export function safeStringify(value: any): string { let depth = 0; const maxDepth = 255; const maxRepetitions = 5; // Allow up to 5 repetitions of the same object + let isRoot = true; // eslint-disable-next-line sonarjs/cognitive-complexity function stringify(val: any, currentPath: any[] = []): any { @@ -50,6 +51,8 @@ export function safeStringify(value: any): string { depth++; const newPath = [...currentPath, val]; + const atRoot = isRoot; + isRoot = false; let result: any; @@ -57,6 +60,13 @@ export function safeStringify(value: any): string { result = val.map((item) => stringify(item, newPath)); } else { result = {}; + // Check if this is a root-level schema with oneOf/anyOf that has conflicting type:object + // Modelina adds type:object to union models, but this conflicts with primitive oneOf/anyOf + const hasCompositionKeyword = + val.oneOf !== undefined || val.anyOf !== undefined; + const hasConflictingObjectType = + val.type === 'object' && hasCompositionKeyword; + for (const [key, value] of Object.entries(val)) { // Skip extension properties if ( @@ -68,6 +78,10 @@ export function safeStringify(value: any): string { ) { continue; } + // Skip type:object at root level when there's oneOf/anyOf with primitives + if (atRoot && key === 'type' && hasConflictingObjectType) { + continue; + } // eslint-disable-next-line security/detect-object-injection result[key] = stringify(value, newPath); } @@ -128,6 +142,9 @@ export function generateTypescriptValidationCode({ return `${schemaProperty} = ${safeStringify(model.originalInput)}; ${methodPrefix}validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? ${createValidatorCall} return { diff --git a/test/codegen/configurations.spec.ts b/test/codegen/configurations.spec.ts index 8546bfa5..af6d8a7b 100644 --- a/test/codegen/configurations.spec.ts +++ b/test/codegen/configurations.spec.ts @@ -60,13 +60,10 @@ describe('configuration manager', () => { const { config } = await loadAndRealizeConfigFile(CONFIG_YAML); expect(config.inputType).toEqual('asyncapi'); }); - /** - * Cannot run this in this Jest environment, had to manually test it. - * - * TODO - */ + // TypeScript config files require ts-node/tsx for dynamic imports. + // This works at runtime (CLI with tsx), but Jest can't load TS files dynamically. // eslint-disable-next-line jest/no-disabled-tests - it.skip('should work with correct TS config', async () => { + it.skip('should work with correct TS config (requires ts-node/tsx runtime)', async () => { const { config } = await loadAndRealizeConfigFile(CONFIG_TS); expect(config.inputType).toEqual('asyncapi'); }); diff --git a/test/codegen/generators/typescript/__snapshots__/headers.spec.ts.snap b/test/codegen/generators/typescript/__snapshots__/headers.spec.ts.snap index 0091889a..c3a31365 100644 --- a/test/codegen/generators/typescript/__snapshots__/headers.spec.ts.snap +++ b/test/codegen/generators/typescript/__snapshots__/headers.spec.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`headers typescript should work with OpenAPI 2.0 inputs 1`] = ` "import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; @@ -39,6 +39,9 @@ class DeletePetHeaders { public static theCodeGenSchema = {"type":"object","additionalProperties":false,"properties":{"api_key":{"type":"string"}},"$id":"DeletePetHeaders","$schema":"http://json-schema.org/draft-07/schema"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { @@ -97,6 +100,9 @@ class DeletePetHeaders { public static theCodeGenSchema = {"type":"object","additionalProperties":false,"properties":{"api_key":{"type":"string"}},"$id":"DeletePetHeaders","$schema":"http://json-schema.org/draft-07/schema"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { @@ -155,6 +161,9 @@ class DeletePetHeaders { public static theCodeGenSchema = {"type":"object","additionalProperties":false,"properties":{"api_key":{"type":"string"}},"$id":"DeletePetHeaders","$schema":"http://json-schema.org/draft-07/schema"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { @@ -247,6 +256,9 @@ class SimpleObjectHeaders { public static theCodeGenSchema = {"type":"object","properties":{"displayName":{"type":"string","description":"Name of the user"},"email":{"type":"string","format":"email","description":"Email of the user"}},"$id":"SimpleObjectHeaders","$schema":"http://json-schema.org/draft-07/schema"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/codegen/generators/typescript/__snapshots__/payload.spec.ts.snap b/test/codegen/generators/typescript/__snapshots__/payload.spec.ts.snap index 124f2247..238b8197 100644 --- a/test/codegen/generators/typescript/__snapshots__/payload.spec.ts.snap +++ b/test/codegen/generators/typescript/__snapshots__/payload.spec.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`payloads typescript should not render validation functions 1`] = ` "import {SimpleObject} from './SimpleObject'; @@ -116,9 +116,12 @@ return payload.marshal(); return JSON.stringify(payload); } -export const theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"type":{"const":"SimpleObject"},"displayName":{"type":"string","description":"Name of the user"},"email":{"type":"string","format":"email","description":"Email of the user"}},"$id":"SimpleObject"},{"type":"boolean","$id":"Boolean"},{"type":"string","$id":"String"}],"$id":"UnionPayload"}; +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"type":{"const":"SimpleObject"},"displayName":{"type":"string","description":"Name of the user"},"email":{"type":"string","format":"email","description":"Email of the user"}},"$id":"SimpleObject"},{"type":"boolean","$id":"Boolean"},{"type":"string","$id":"String"}],"$id":"UnionPayload"}; export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? createValidator(context) return { @@ -205,6 +208,9 @@ class SimpleObject2 { public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"displayName":{"type":"string","description":"Name of the user"},"email":{"type":"string","format":"email","description":"Email of the user"}},"$id":"SimpleObject2"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { @@ -297,6 +303,9 @@ class SimpleObject { public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"type":{"const":"SimpleObject"},"displayName":{"type":"string","description":"Name of the user"},"email":{"type":"string","format":"email","description":"Email of the user"}},"$id":"SimpleObject"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/asyncapi-payload-types.json b/test/runtime/asyncapi-payload-types.json new file mode 100644 index 00000000..5c8ba0a7 --- /dev/null +++ b/test/runtime/asyncapi-payload-types.json @@ -0,0 +1,637 @@ +{ + "asyncapi": "3.0.0", + "info": { + "title": "Comprehensive Payload Type Testing", + "version": "1.0.0", + "description": "Tests all JSON Schema payload type variations for runtime verification" + }, + "channels": { + "stringPlain": { + "address": "test/primitives/string-plain", + "messages": { "StringPlain": { "$ref": "#/components/messages/StringPlain" } } + }, + "stringMinLength": { + "address": "test/primitives/string-min-length", + "messages": { "StringWithMinLength": { "$ref": "#/components/messages/StringWithMinLength" } } + }, + "stringMaxLength": { + "address": "test/primitives/string-max-length", + "messages": { "StringWithMaxLength": { "$ref": "#/components/messages/StringWithMaxLength" } } + }, + "stringPattern": { + "address": "test/primitives/string-pattern", + "messages": { "StringWithPattern": { "$ref": "#/components/messages/StringWithPattern" } } + }, + "numberPlain": { + "address": "test/primitives/number-plain", + "messages": { "NumberPlain": { "$ref": "#/components/messages/NumberPlain" } } + }, + "numberMinimum": { + "address": "test/primitives/number-minimum", + "messages": { "NumberWithMinimum": { "$ref": "#/components/messages/NumberWithMinimum" } } + }, + "numberMaximum": { + "address": "test/primitives/number-maximum", + "messages": { "NumberWithMaximum": { "$ref": "#/components/messages/NumberWithMaximum" } } + }, + "numberRange": { + "address": "test/primitives/number-range", + "messages": { "NumberWithRange": { "$ref": "#/components/messages/NumberWithRange" } } + }, + "integerPlain": { + "address": "test/primitives/integer-plain", + "messages": { "IntegerPlain": { "$ref": "#/components/messages/IntegerPlain" } } + }, + "integerRange": { + "address": "test/primitives/integer-range", + "messages": { "IntegerWithRange": { "$ref": "#/components/messages/IntegerWithRange" } } + }, + "booleanPlain": { + "address": "test/primitives/boolean-plain", + "messages": { "BooleanPlain": { "$ref": "#/components/messages/BooleanPlain" } } + }, + "nullPlain": { + "address": "test/primitives/null-plain", + "messages": { "NullPlain": { "$ref": "#/components/messages/NullPlain" } } + }, + "singleStringEnum": { + "address": "test/enums/single-string", + "messages": { "SingleStringEnum": { "$ref": "#/components/messages/SingleStringEnumMessage" } } + }, + "multipleStringEnum": { + "address": "test/enums/multiple-string", + "messages": { "MultipleStringEnum": { "$ref": "#/components/messages/MultipleStringEnumMessage" } } + }, + "integerEnum": { + "address": "test/enums/integer", + "messages": { "IntegerEnum": { "$ref": "#/components/messages/IntegerEnumMessage" } } + }, + "mixedTypeEnum": { + "address": "test/enums/mixed", + "messages": { "MixedTypeEnum": { "$ref": "#/components/messages/MixedTypeEnumMessage" } } + }, + "nullableEnum": { + "address": "test/enums/nullable", + "messages": { "NullableEnum": { "$ref": "#/components/messages/NullableEnumMessage" } } + }, + "simpleObject": { + "address": "test/objects/simple", + "messages": { "SimpleObject": { "$ref": "#/components/messages/SimpleObjectMessage" } } + }, + "objectRequired": { + "address": "test/objects/required", + "messages": { "ObjectWithRequired": { "$ref": "#/components/messages/ObjectWithRequiredMessage" } } + }, + "objectOptional": { + "address": "test/objects/optional", + "messages": { "ObjectWithOptional": { "$ref": "#/components/messages/ObjectWithOptionalMessage" } } + }, + "nestedObject": { + "address": "test/objects/nested", + "messages": { "NestedObject": { "$ref": "#/components/messages/NestedObjectMessage" } } + }, + "deeplyNestedObject": { + "address": "test/objects/deeply-nested", + "messages": { "DeeplyNestedObject": { "$ref": "#/components/messages/DeeplyNestedObjectMessage" } } + }, + "objectAdditionalTrue": { + "address": "test/objects/additional-true", + "messages": { "ObjectAdditionalPropsTrue": { "$ref": "#/components/messages/ObjectAdditionalPropsTrueMessage" } } + }, + "objectAdditionalFalse": { + "address": "test/objects/additional-false", + "messages": { "ObjectAdditionalPropsFalse": { "$ref": "#/components/messages/ObjectAdditionalPropsFalseMessage" } } + }, + "objectAdditionalSchema": { + "address": "test/objects/additional-schema", + "messages": { "ObjectAdditionalPropsSchema": { "$ref": "#/components/messages/ObjectAdditionalPropsSchemaMessage" } } + }, + "objectDefaults": { + "address": "test/objects/defaults", + "messages": { "ObjectWithDefaults": { "$ref": "#/components/messages/ObjectWithDefaultsMessage" } } + }, + "stringArray": { + "address": "test/arrays/string", + "messages": { "StringArray": { "$ref": "#/components/messages/StringArrayMessage" } } + }, + "objectArray": { + "address": "test/arrays/object", + "messages": { "ObjectArray": { "$ref": "#/components/messages/ObjectArrayMessage" } } + }, + "nestedArray": { + "address": "test/arrays/nested", + "messages": { "NestedArray": { "$ref": "#/components/messages/NestedArrayMessage" } } + }, + "arrayMinItems": { + "address": "test/arrays/min-items", + "messages": { "ArrayWithMinItems": { "$ref": "#/components/messages/ArrayWithMinItemsMessage" } } + }, + "arrayMaxItems": { + "address": "test/arrays/max-items", + "messages": { "ArrayWithMaxItems": { "$ref": "#/components/messages/ArrayWithMaxItemsMessage" } } + }, + "arrayUniqueItems": { + "address": "test/arrays/unique-items", + "messages": { "ArrayWithUniqueItems": { "$ref": "#/components/messages/ArrayWithUniqueItemsMessage" } } + }, + "tupleArray": { + "address": "test/arrays/tuple", + "messages": { "TupleArray": { "$ref": "#/components/messages/TupleArrayMessage" } } + }, + "oneOfTwoTypes": { + "address": "test/composition/oneof-two", + "messages": { "OneOfTwoTypes": { "$ref": "#/components/messages/OneOfTwoTypesMessage" } } + }, + "oneOfThreeTypes": { + "address": "test/composition/oneof-three", + "messages": { "OneOfThreeTypes": { "$ref": "#/components/messages/OneOfThreeTypesMessage" } } + }, + "oneOfDiscriminator": { + "address": "test/composition/oneof-discriminator", + "messages": { "OneOfWithDiscriminator": { "$ref": "#/components/messages/OneOfWithDiscriminatorMessage" } } + }, + "anyOfTwoTypes": { + "address": "test/composition/anyof-two", + "messages": { "AnyOfTwoTypes": { "$ref": "#/components/messages/AnyOfTwoTypesMessage" } } + }, + "allOfTwoTypes": { + "address": "test/composition/allof-two", + "messages": { "AllOfTwoTypes": { "$ref": "#/components/messages/AllOfTwoTypesMessage" } } + }, + "allOfThreeTypes": { + "address": "test/composition/allof-three", + "messages": { "AllOfThreeTypes": { "$ref": "#/components/messages/AllOfThreeTypesMessage" } } + }, + "formatEmail": { + "address": "test/formats/email", + "messages": { "FormatEmail": { "$ref": "#/components/messages/FormatEmailMessage" } } + }, + "formatUri": { + "address": "test/formats/uri", + "messages": { "FormatUri": { "$ref": "#/components/messages/FormatUriMessage" } } + }, + "formatUuid": { + "address": "test/formats/uuid", + "messages": { "FormatUuid": { "$ref": "#/components/messages/FormatUuidMessage" } } + }, + "formatDate": { + "address": "test/formats/date", + "messages": { "FormatDate": { "$ref": "#/components/messages/FormatDateMessage" } } + }, + "formatDateTime": { + "address": "test/formats/datetime", + "messages": { "FormatDateTime": { "$ref": "#/components/messages/FormatDateTimeMessage" } } + }, + "formatTime": { + "address": "test/formats/time", + "messages": { "FormatTime": { "$ref": "#/components/messages/FormatTimeMessage" } } + }, + "formatHostname": { + "address": "test/formats/hostname", + "messages": { "FormatHostname": { "$ref": "#/components/messages/FormatHostnameMessage" } } + }, + "formatIpv4": { + "address": "test/formats/ipv4", + "messages": { "FormatIpv4": { "$ref": "#/components/messages/FormatIpv4Message" } } + }, + "formatIpv6": { + "address": "test/formats/ipv6", + "messages": { "FormatIpv6": { "$ref": "#/components/messages/FormatIpv6Message" } } + }, + "objectFormats": { + "address": "test/formats/object", + "messages": { "ObjectWithFormats": { "$ref": "#/components/messages/ObjectWithFormatsMessage" } } + } + }, + "operations": { + "sendStringPlain": { "action": "send", "channel": { "$ref": "#/channels/stringPlain" } }, + "receiveStringPlain": { "action": "receive", "channel": { "$ref": "#/channels/stringPlain" } }, + "sendSimpleObject": { "action": "send", "channel": { "$ref": "#/channels/simpleObject" } }, + "receiveSimpleObject": { "action": "receive", "channel": { "$ref": "#/channels/simpleObject" } } + }, + "components": { + "messages": { + "StringPlain": { "payload": { "$ref": "#/components/schemas/StringPlain" } }, + "StringWithMinLength": { "payload": { "$ref": "#/components/schemas/StringWithMinLength" } }, + "StringWithMaxLength": { "payload": { "$ref": "#/components/schemas/StringWithMaxLength" } }, + "StringWithPattern": { "payload": { "$ref": "#/components/schemas/StringWithPattern" } }, + "NumberPlain": { "payload": { "$ref": "#/components/schemas/NumberPlain" } }, + "NumberWithMinimum": { "payload": { "$ref": "#/components/schemas/NumberWithMinimum" } }, + "NumberWithMaximum": { "payload": { "$ref": "#/components/schemas/NumberWithMaximum" } }, + "NumberWithRange": { "payload": { "$ref": "#/components/schemas/NumberWithRange" } }, + "IntegerPlain": { "payload": { "$ref": "#/components/schemas/IntegerPlain" } }, + "IntegerWithRange": { "payload": { "$ref": "#/components/schemas/IntegerWithRange" } }, + "BooleanPlain": { "payload": { "$ref": "#/components/schemas/BooleanPlain" } }, + "NullPlain": { "payload": { "$ref": "#/components/schemas/NullPlain" } }, + "SingleStringEnumMessage": { "payload": { "$ref": "#/components/schemas/SingleStringEnum" } }, + "MultipleStringEnumMessage": { "payload": { "$ref": "#/components/schemas/MultipleStringEnum" } }, + "IntegerEnumMessage": { "payload": { "$ref": "#/components/schemas/IntegerEnum" } }, + "MixedTypeEnumMessage": { "payload": { "$ref": "#/components/schemas/MixedTypeEnum" } }, + "NullableEnumMessage": { "payload": { "$ref": "#/components/schemas/NullableEnum" } }, + "SimpleObjectMessage": { "payload": { "$ref": "#/components/schemas/SimpleObject" } }, + "ObjectWithRequiredMessage": { "payload": { "$ref": "#/components/schemas/ObjectWithRequired" } }, + "ObjectWithOptionalMessage": { "payload": { "$ref": "#/components/schemas/ObjectWithOptional" } }, + "NestedObjectMessage": { "payload": { "$ref": "#/components/schemas/NestedObject" } }, + "DeeplyNestedObjectMessage": { "payload": { "$ref": "#/components/schemas/DeeplyNestedObject" } }, + "ObjectAdditionalPropsTrueMessage": { "payload": { "$ref": "#/components/schemas/ObjectAdditionalPropsTrue" } }, + "ObjectAdditionalPropsFalseMessage": { "payload": { "$ref": "#/components/schemas/ObjectAdditionalPropsFalse" } }, + "ObjectAdditionalPropsSchemaMessage": { "payload": { "$ref": "#/components/schemas/ObjectAdditionalPropsSchema" } }, + "ObjectWithDefaultsMessage": { "payload": { "$ref": "#/components/schemas/ObjectWithDefaults" } }, + "StringArrayMessage": { "payload": { "$ref": "#/components/schemas/StringArray" } }, + "ObjectArrayMessage": { "payload": { "$ref": "#/components/schemas/ObjectArray" } }, + "NestedArrayMessage": { "payload": { "$ref": "#/components/schemas/NestedArray" } }, + "ArrayWithMinItemsMessage": { "payload": { "$ref": "#/components/schemas/ArrayWithMinItems" } }, + "ArrayWithMaxItemsMessage": { "payload": { "$ref": "#/components/schemas/ArrayWithMaxItems" } }, + "ArrayWithUniqueItemsMessage": { "payload": { "$ref": "#/components/schemas/ArrayWithUniqueItems" } }, + "TupleArrayMessage": { "payload": { "$ref": "#/components/schemas/TupleArray" } }, + "OneOfTwoTypesMessage": { "payload": { "$ref": "#/components/schemas/OneOfTwoTypes" } }, + "OneOfThreeTypesMessage": { "payload": { "$ref": "#/components/schemas/OneOfThreeTypes" } }, + "OneOfWithDiscriminatorMessage": { "payload": { "$ref": "#/components/schemas/OneOfWithDiscriminator" } }, + "AnyOfTwoTypesMessage": { "payload": { "$ref": "#/components/schemas/AnyOfTwoTypes" } }, + "AllOfTwoTypesMessage": { "payload": { "$ref": "#/components/schemas/AllOfTwoTypes" } }, + "AllOfThreeTypesMessage": { "payload": { "$ref": "#/components/schemas/AllOfThreeTypes" } }, + "FormatEmailMessage": { "payload": { "$ref": "#/components/schemas/FormatEmail" } }, + "FormatUriMessage": { "payload": { "$ref": "#/components/schemas/FormatUri" } }, + "FormatUuidMessage": { "payload": { "$ref": "#/components/schemas/FormatUuid" } }, + "FormatDateMessage": { "payload": { "$ref": "#/components/schemas/FormatDate" } }, + "FormatDateTimeMessage": { "payload": { "$ref": "#/components/schemas/FormatDateTime" } }, + "FormatTimeMessage": { "payload": { "$ref": "#/components/schemas/FormatTime" } }, + "FormatHostnameMessage": { "payload": { "$ref": "#/components/schemas/FormatHostname" } }, + "FormatIpv4Message": { "payload": { "$ref": "#/components/schemas/FormatIpv4" } }, + "FormatIpv6Message": { "payload": { "$ref": "#/components/schemas/FormatIpv6" } }, + "ObjectWithFormatsMessage": { "payload": { "$ref": "#/components/schemas/ObjectWithFormats" } } + }, + "schemas": { + "StringPlain": { + "type": "string", + "description": "A plain string type" + }, + "StringWithMinLength": { + "type": "string", + "minLength": 3, + "description": "String with minimum length of 3" + }, + "StringWithMaxLength": { + "type": "string", + "maxLength": 50, + "description": "String with maximum length of 50" + }, + "StringWithPattern": { + "type": "string", + "pattern": "^[A-Z][a-z]+$", + "description": "String matching capitalized word pattern" + }, + "NumberPlain": { + "type": "number", + "description": "A plain number type" + }, + "NumberWithMinimum": { + "type": "number", + "minimum": 0, + "description": "Number with minimum of 0" + }, + "NumberWithMaximum": { + "type": "number", + "maximum": 100, + "description": "Number with maximum of 100" + }, + "NumberWithRange": { + "type": "number", + "exclusiveMinimum": 0, + "exclusiveMaximum": 100, + "description": "Number strictly between 0 and 100" + }, + "IntegerPlain": { + "type": "integer", + "description": "A plain integer type" + }, + "IntegerWithRange": { + "type": "integer", + "minimum": 1, + "maximum": 10, + "description": "Integer between 1 and 10" + }, + "BooleanPlain": { + "type": "boolean", + "description": "A plain boolean type" + }, + "NullPlain": { + "type": "null", + "description": "A null type" + }, + "SingleStringEnum": { + "type": "string", + "enum": ["active"], + "description": "Single value enum" + }, + "MultipleStringEnum": { + "type": "string", + "enum": ["pending", "active", "inactive", "deleted"], + "description": "Multiple value string enum" + }, + "IntegerEnum": { + "type": "integer", + "enum": [1, 2, 3, 4, 5], + "description": "Integer enum" + }, + "MixedTypeEnum": { + "enum": ["string_value", 123, true], + "description": "Mixed type enum" + }, + "NullableEnum": { + "enum": ["option_a", "option_b", null], + "description": "Nullable enum" + }, + "SimpleObject": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "age": { "type": "integer" } + }, + "description": "Simple object with two properties" + }, + "ObjectWithRequired": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" }, + "optional_field": { "type": "string" } + }, + "required": ["id", "name"], + "description": "Object with required and optional properties" + }, + "ObjectWithOptional": { + "type": "object", + "properties": { + "field1": { "type": "string" }, + "field2": { "type": "string" }, + "field3": { "type": "string" } + }, + "description": "Object with all optional properties" + }, + "NestedObject": { + "type": "object", + "properties": { + "outer": { + "type": "object", + "properties": { + "inner": { "type": "string" } + } + } + }, + "description": "Object with one level of nesting" + }, + "DeeplyNestedObject": { + "type": "object", + "properties": { + "level1": { + "type": "object", + "properties": { + "level2": { + "type": "object", + "properties": { + "level3": { + "type": "object", + "properties": { + "value": { "type": "string" } + } + } + } + } + } + } + }, + "description": "Object with 3 levels of nesting" + }, + "ObjectAdditionalPropsTrue": { + "type": "object", + "properties": { + "known": { "type": "string" } + }, + "additionalProperties": true, + "description": "Object that allows additional properties" + }, + "ObjectAdditionalPropsFalse": { + "type": "object", + "properties": { + "known": { "type": "string" } + }, + "additionalProperties": false, + "description": "Object that disallows additional properties" + }, + "ObjectAdditionalPropsSchema": { + "type": "object", + "properties": { + "known": { "type": "string" } + }, + "additionalProperties": { "type": "integer" }, + "description": "Object with typed additional properties (integers)" + }, + "ObjectWithDefaults": { + "type": "object", + "properties": { + "status": { "type": "string", "default": "pending" }, + "count": { "type": "integer", "default": 0 }, + "enabled": { "type": "boolean", "default": true } + }, + "description": "Object with default values" + }, + "StringArray": { + "type": "array", + "items": { "type": "string" }, + "description": "Array of strings" + }, + "ObjectArray": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" } + } + }, + "description": "Array of objects" + }, + "NestedArray": { + "type": "array", + "items": { + "type": "array", + "items": { "type": "string" } + }, + "description": "2D array (array of string arrays)" + }, + "ArrayWithMinItems": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "description": "Array with minimum 1 item" + }, + "ArrayWithMaxItems": { + "type": "array", + "items": { "type": "string" }, + "maxItems": 5, + "description": "Array with maximum 5 items" + }, + "ArrayWithUniqueItems": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "description": "Array requiring unique items" + }, + "TupleArray": { + "type": "array", + "items": [ + { "type": "string" }, + { "type": "integer" }, + { "type": "boolean" } + ], + "description": "Tuple with fixed position types" + }, + "OneOfTwoTypes": { + "oneOf": [ + { "type": "string" }, + { "type": "integer" } + ], + "description": "OneOf with string or integer" + }, + "OneOfThreeTypes": { + "oneOf": [ + { "type": "string" }, + { "type": "integer" }, + { + "type": "object", + "properties": { "value": { "type": "string" } } + } + ], + "description": "OneOf with three type options" + }, + "OneOfWithDiscriminator": { + "oneOf": [ + { "$ref": "#/components/schemas/DogPayload" }, + { "$ref": "#/components/schemas/CatPayload" } + ], + "discriminator": "petType", + "description": "OneOf with discriminator property" + }, + "DogPayload": { + "type": "object", + "properties": { + "petType": { "const": "dog" }, + "breed": { "type": "string" }, + "barkVolume": { "type": "integer" } + }, + "required": ["petType"] + }, + "CatPayload": { + "type": "object", + "properties": { + "petType": { "const": "cat" }, + "breed": { "type": "string" }, + "meowPitch": { "type": "string" } + }, + "required": ["petType"] + }, + "AnyOfTwoTypes": { + "anyOf": [ + { "type": "object", "properties": { "name": { "type": "string" } } }, + { "type": "object", "properties": { "id": { "type": "integer" } } } + ], + "description": "AnyOf with two object schemas" + }, + "AllOfTwoTypes": { + "allOf": [ + { "$ref": "#/components/schemas/BaseEntity" }, + { "$ref": "#/components/schemas/TimestampMixin" } + ], + "description": "AllOf combining base entity and timestamp" + }, + "AllOfThreeTypes": { + "allOf": [ + { "$ref": "#/components/schemas/BaseEntity" }, + { "$ref": "#/components/schemas/TimestampMixin" }, + { "$ref": "#/components/schemas/AuditMixin" } + ], + "description": "AllOf combining three schemas" + }, + "BaseEntity": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" } + }, + "required": ["id"] + }, + "TimestampMixin": { + "type": "object", + "properties": { + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" } + } + }, + "AuditMixin": { + "type": "object", + "properties": { + "createdBy": { "type": "string" }, + "updatedBy": { "type": "string" } + } + }, + "FormatEmail": { + "type": "string", + "format": "email", + "description": "String with email format" + }, + "FormatUri": { + "type": "string", + "format": "uri", + "description": "String with URI format" + }, + "FormatUuid": { + "type": "string", + "format": "uuid", + "description": "String with UUID format" + }, + "FormatDate": { + "type": "string", + "format": "date", + "description": "String with date format (YYYY-MM-DD)" + }, + "FormatDateTime": { + "type": "string", + "format": "date-time", + "description": "String with date-time format (ISO 8601)" + }, + "FormatTime": { + "type": "string", + "format": "time", + "description": "String with time format (HH:MM:SS)" + }, + "FormatHostname": { + "type": "string", + "format": "hostname", + "description": "String with hostname format" + }, + "FormatIpv4": { + "type": "string", + "format": "ipv4", + "description": "String with IPv4 format" + }, + "FormatIpv6": { + "type": "string", + "format": "ipv6", + "description": "String with IPv6 format" + }, + "ObjectWithFormats": { + "type": "object", + "properties": { + "email": { "type": "string", "format": "email" }, + "website": { "type": "string", "format": "uri" }, + "userId": { "type": "string", "format": "uuid" }, + "birthDate": { "type": "string", "format": "date" }, + "lastLogin": { "type": "string", "format": "date-time" }, + "serverIp": { "type": "string", "format": "ipv4" } + }, + "description": "Object with multiple format-validated properties" + } + } + } +} diff --git a/test/runtime/typescript/codegen-payload-types.mjs b/test/runtime/typescript/codegen-payload-types.mjs new file mode 100644 index 00000000..185b15c7 --- /dev/null +++ b/test/runtime/typescript/codegen-payload-types.mjs @@ -0,0 +1,17 @@ +/** @type {import("../../../dist").TheCodegenConfiguration} **/ +export default { + inputType: 'asyncapi', + inputPath: '../asyncapi-payload-types.json', + language: 'typescript', + generators: [ + { + preset: 'payloads', + outputPath: './src/payload-types/payloads', + serializationType: 'json', + }, + { + preset: 'types', + outputPath: './src/payload-types', + } + ] +}; diff --git a/test/runtime/typescript/package.json b/test/runtime/typescript/package.json index a2d539ec..6210ca80 100644 --- a/test/runtime/typescript/package.json +++ b/test/runtime/typescript/package.json @@ -11,10 +11,12 @@ "test:eventsource": "jest -- ./test/channels/regular/eventsource.spec.ts", "test:websocket": "jest -- ./test/channels/regular/websocket.spec.ts", "test:http": "jest -- ./test/channels/request_reply/http_client/", - "generate": "npm run generate:regular && npm run generate:request:reply && npm run generate:openapi", + "test:payload-types": "jest -- ./test/payload-types/", + "generate": "npm run generate:regular && npm run generate:request:reply && npm run generate:openapi && npm run generate:payload-types", "generate:request:reply": "cross-env CODEGEN_TELEMETRY_DISABLED=1 node ../../../bin/run.mjs generate ./codegen-request-reply.mjs", "generate:regular": "cross-env CODEGEN_TELEMETRY_DISABLED=1 node ../../../bin/run.mjs generate ./codegen-regular.mjs", "generate:openapi": "cross-env CODEGEN_TELEMETRY_DISABLED=1 node ../../../bin/run.mjs generate ./codegen-openapi.mjs", + "generate:payload-types": "cross-env CODEGEN_TELEMETRY_DISABLED=1 node ../../../bin/run.mjs generate ./codegen-payload-types.mjs", "debug:generate": "node --inspect-brk ../../../bin/run.mjs generate" }, "dependencies": { diff --git a/test/runtime/typescript/src/channels/amqp.ts b/test/runtime/typescript/src/channels/amqp.ts index ae8cee5e..85ce7bdd 100644 --- a/test/runtime/typescript/src/channels/amqp.ts +++ b/test/runtime/typescript/src/channels/amqp.ts @@ -2,7 +2,7 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; -import {AnonymousSchema_9} from './../payloads/AnonymousSchema_9'; +import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import * as Amqp from 'amqplib'; diff --git a/test/runtime/typescript/src/channels/event_source.ts b/test/runtime/typescript/src/channels/event_source.ts index 61e8622f..088e6ea3 100644 --- a/test/runtime/typescript/src/channels/event_source.ts +++ b/test/runtime/typescript/src/channels/event_source.ts @@ -2,7 +2,7 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; -import {AnonymousSchema_9} from './../payloads/AnonymousSchema_9'; +import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import { NextFunction, Request, Response, Router } from 'express'; diff --git a/test/runtime/typescript/src/channels/kafka.ts b/test/runtime/typescript/src/channels/kafka.ts index 7c48276a..bad1d185 100644 --- a/test/runtime/typescript/src/channels/kafka.ts +++ b/test/runtime/typescript/src/channels/kafka.ts @@ -2,7 +2,7 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; -import {AnonymousSchema_9} from './../payloads/AnonymousSchema_9'; +import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import * as Kafka from 'kafkajs'; diff --git a/test/runtime/typescript/src/channels/mqtt.ts b/test/runtime/typescript/src/channels/mqtt.ts index 796eb1e6..d2eaa442 100644 --- a/test/runtime/typescript/src/channels/mqtt.ts +++ b/test/runtime/typescript/src/channels/mqtt.ts @@ -2,7 +2,7 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; -import {AnonymousSchema_9} from './../payloads/AnonymousSchema_9'; +import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import * as Mqtt from 'mqtt'; diff --git a/test/runtime/typescript/src/channels/nats.ts b/test/runtime/typescript/src/channels/nats.ts index e3007d66..3b3f3b7d 100644 --- a/test/runtime/typescript/src/channels/nats.ts +++ b/test/runtime/typescript/src/channels/nats.ts @@ -2,7 +2,7 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; -import {AnonymousSchema_9} from './../payloads/AnonymousSchema_9'; +import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import * as Nats from 'nats'; diff --git a/test/runtime/typescript/src/channels/websocket.ts b/test/runtime/typescript/src/channels/websocket.ts index a69fb42f..a380254d 100644 --- a/test/runtime/typescript/src/channels/websocket.ts +++ b/test/runtime/typescript/src/channels/websocket.ts @@ -2,7 +2,7 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; -import {AnonymousSchema_9} from './../payloads/AnonymousSchema_9'; +import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import * as WebSocket from 'ws'; diff --git a/test/runtime/typescript/src/client/NatsClient.ts b/test/runtime/typescript/src/client/NatsClient.ts index 30452a7f..74cacaf8 100644 --- a/test/runtime/typescript/src/client/NatsClient.ts +++ b/test/runtime/typescript/src/client/NatsClient.ts @@ -2,12 +2,12 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; -import {AnonymousSchema_9} from './../payloads/AnonymousSchema_9'; +import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; export {UserSignedUp}; export {StringMessageModule}; export {ArrayMessageModule}; export {UnionMessageModule}; -export {AnonymousSchema_9}; +export {UnionPayloadOneOfOption2}; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; export {UserSignedupParameters}; diff --git a/test/runtime/typescript/src/headers/UserSignedUpHeaders.ts b/test/runtime/typescript/src/headers/UserSignedUpHeaders.ts index d3baa43d..ec1df2e5 100644 --- a/test/runtime/typescript/src/headers/UserSignedUpHeaders.ts +++ b/test/runtime/typescript/src/headers/UserSignedUpHeaders.ts @@ -55,6 +55,9 @@ class UserSignedUpHeaders { public static theCodeGenSchema = {"type":"object","properties":{"x-test-header":{"type":"string","description":"Test header"}},"$id":"UserSignedUpHeaders","$schema":"http://json-schema.org/draft-07/schema"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/openapi/headers/FindPetsByStatusAndCategoryHeaders.ts b/test/runtime/typescript/src/openapi/headers/FindPetsByStatusAndCategoryHeaders.ts index b41ffc31..b478b60c 100644 --- a/test/runtime/typescript/src/openapi/headers/FindPetsByStatusAndCategoryHeaders.ts +++ b/test/runtime/typescript/src/openapi/headers/FindPetsByStatusAndCategoryHeaders.ts @@ -54,6 +54,9 @@ class FindPetsByStatusAndCategoryHeaders { public static theCodeGenSchema = {"type":"object","additionalProperties":false,"properties":{"X-Request-ID":{"type":"string","format":"uuid","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","description":"Unique request identifier for tracing"},"Accept-Language":{"type":"string","pattern":"^[a-z]{2}(-[A-Z]{2})?$","default":"en-US","description":"Preferred language for response messages"}},"$id":"FindPetsByStatusAndCategoryHeaders","$schema":"http://json-schema.org/draft-07/schema"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/openapi/payloads/APet.ts b/test/runtime/typescript/src/openapi/payloads/APet.ts index 6cf026d2..08310c77 100644 --- a/test/runtime/typescript/src/openapi/payloads/APet.ts +++ b/test/runtime/typescript/src/openapi/payloads/APet.ts @@ -57,7 +57,7 @@ class APet { json += `"id": ${typeof this.id === 'number' || typeof this.id === 'boolean' ? this.id : JSON.stringify(this.id)},`; } if(this.category !== undefined) { - json += `"category": ${this.category.marshal()},`; + json += `"category": ${this.category && typeof this.category === 'object' && 'marshal' in this.category && typeof this.category.marshal === 'function' ? this.category.marshal() : JSON.stringify(this.category)},`; } if(this.name !== undefined) { json += `"name": ${typeof this.name === 'number' || typeof this.name === 'boolean' ? this.name : JSON.stringify(this.name)},`; @@ -72,7 +72,7 @@ class APet { if(this.tags !== undefined) { let tagsJsonValues: any[] = []; for (const unionItem of this.tags) { - tagsJsonValues.push(`${unionItem.marshal()}`); + tagsJsonValues.push(`${unionItem && typeof unionItem === 'object' && 'marshal' in unionItem && typeof unionItem.marshal === 'function' ? unionItem.marshal() : JSON.stringify(unionItem)}`); } json += `"tags": [${tagsJsonValues.join(',')}],`; } @@ -108,7 +108,7 @@ class APet { } if (obj["tags"] !== undefined) { instance.tags = obj["tags"] == null - ? null + ? undefined : obj["tags"].map((item: any) => PetTag.unmarshal(item)); } if (obj["status"] !== undefined) { @@ -125,6 +125,9 @@ class APet { public static theCodeGenSchema = {"title":"a Pet","description":"A pet for sale in the pet store","type":"object","required":["name","photoUrls"],"properties":{"id":{"type":"integer","format":"int64"},"category":{"title":"Pet category","description":"A category for a pet","type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string","pattern":"^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$"}},"xml":{"name":"Category"}},"name":{"type":"string","example":"doggie"},"photoUrls":{"type":"array","xml":{"name":"photoUrl","wrapped":true},"items":{"type":"string"}},"tags":{"type":"array","xml":{"name":"tag","wrapped":true},"items":{"title":"Pet Tag","description":"A tag for a pet","type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Tag"}}},"status":{"type":"string","description":"pet status in the store","deprecated":true,"enum":["available","pending","sold"]}},"xml":{"name":"Pet"},"$id":"Pet","$schema":"http://json-schema.org/draft-07/schema"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/openapi/payloads/AUser.ts b/test/runtime/typescript/src/openapi/payloads/AUser.ts index 4aaafe88..7a489f68 100644 --- a/test/runtime/typescript/src/openapi/payloads/AUser.ts +++ b/test/runtime/typescript/src/openapi/payloads/AUser.ts @@ -136,6 +136,9 @@ class AUser { public static theCodeGenSchema = {"title":"a User","description":"A User who is purchasing from the pet store","type":"object","properties":{"id":{"type":"integer","format":"int64"},"username":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"type":"integer","format":"int32","description":"User Status"}},"xml":{"name":"User"},"$id":"User","$schema":"http://json-schema.org/draft-07/schema"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/openapi/payloads/AnUploadedResponse.ts b/test/runtime/typescript/src/openapi/payloads/AnUploadedResponse.ts index 1733bcbc..5387abb1 100644 --- a/test/runtime/typescript/src/openapi/payloads/AnUploadedResponse.ts +++ b/test/runtime/typescript/src/openapi/payloads/AnUploadedResponse.ts @@ -76,6 +76,9 @@ class AnUploadedResponse { public static theCodeGenSchema = {"title":"An uploaded response","description":"Describes the result of uploading an image resource","type":"object","properties":{"code":{"type":"integer","format":"int32"},"type":{"type":"string"},"message":{"type":"string"}},"$id":"ApiResponse","$schema":"http://json-schema.org/draft-07/schema"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/openapi/payloads/FindPetsByStatusAndCategoryResponse_200.ts b/test/runtime/typescript/src/openapi/payloads/FindPetsByStatusAndCategoryResponse_200.ts index 119f0774..1b53a587 100644 --- a/test/runtime/typescript/src/openapi/payloads/FindPetsByStatusAndCategoryResponse_200.ts +++ b/test/runtime/typescript/src/openapi/payloads/FindPetsByStatusAndCategoryResponse_200.ts @@ -23,6 +23,9 @@ export function marshal(payload: FindPetsByStatusAndCategoryResponse_200): strin export const theCodeGenSchema = {"type":"array","items":{"title":"a Pet","description":"A pet for sale in the pet store","type":"object","required":["name","photoUrls"],"properties":{"id":{"type":"integer","format":"int64"},"category":{"title":"Pet category","description":"A category for a pet","type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string","pattern":"^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$"}},"xml":{"name":"Category"}},"name":{"type":"string","example":"doggie"},"photoUrls":{"type":"array","xml":{"name":"photoUrl","wrapped":true},"items":{"type":"string"}},"tags":{"type":"array","xml":{"name":"tag","wrapped":true},"items":{"title":"Pet Tag","description":"A tag for a pet","type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Tag"}}},"status":{"type":"string","description":"pet status in the store","deprecated":true,"enum":["available","pending","sold"]}},"xml":{"name":"Pet"}},"$id":"findPetsByStatusAndCategory_Response_200","$schema":"http://json-schema.org/draft-07/schema"}; export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? createValidator(context) return { diff --git a/test/runtime/typescript/src/openapi/payloads/PetCategory.ts b/test/runtime/typescript/src/openapi/payloads/PetCategory.ts index d0499bce..950f1dc5 100644 --- a/test/runtime/typescript/src/openapi/payloads/PetCategory.ts +++ b/test/runtime/typescript/src/openapi/payloads/PetCategory.ts @@ -64,6 +64,9 @@ class PetCategory { public static theCodeGenSchema = {"title":"Pet category","description":"A category for a pet","type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string","pattern":"^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$"}},"xml":{"name":"Category"}}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/openapi/payloads/PetOrder.ts b/test/runtime/typescript/src/openapi/payloads/PetOrder.ts index 23e1f6fa..bb55856f 100644 --- a/test/runtime/typescript/src/openapi/payloads/PetOrder.ts +++ b/test/runtime/typescript/src/openapi/payloads/PetOrder.ts @@ -5,7 +5,7 @@ class PetOrder { private _id?: number; private _petId?: number; private _quantity?: number; - private _shipDate?: string; + private _shipDate?: Date; private _status?: Status; private _complete?: boolean; private _additionalProperties?: Record; @@ -14,7 +14,7 @@ class PetOrder { id?: number, petId?: number, quantity?: number, - shipDate?: string, + shipDate?: Date, status?: Status, complete?: boolean, additionalProperties?: Record, @@ -37,8 +37,8 @@ class PetOrder { get quantity(): number | undefined { return this._quantity; } set quantity(quantity: number | undefined) { this._quantity = quantity; } - get shipDate(): string | undefined { return this._shipDate; } - set shipDate(shipDate: string | undefined) { this._shipDate = shipDate; } + get shipDate(): Date | undefined { return this._shipDate; } + set shipDate(shipDate: Date | undefined) { this._shipDate = shipDate; } get status(): Status | undefined { return this._status; } set status(status: Status | undefined) { this._status = status; } @@ -94,7 +94,7 @@ class PetOrder { instance.quantity = obj["quantity"]; } if (obj["shipDate"] !== undefined) { - instance.shipDate = obj["shipDate"]; + instance.shipDate = obj["shipDate"] == null ? undefined : new Date(obj["shipDate"]); } if (obj["status"] !== undefined) { instance.status = obj["status"]; @@ -113,6 +113,9 @@ class PetOrder { public static theCodeGenSchema = {"title":"Pet Order","description":"An order for a pets from the pet store","type":"object","properties":{"id":{"type":"integer","format":"int64"},"petId":{"type":"integer","format":"int64"},"quantity":{"type":"integer","format":"int32"},"shipDate":{"type":"string","format":"date-time"},"status":{"type":"string","description":"Order Status","enum":["placed","approved","delivered"]},"complete":{"type":"boolean","default":false}},"xml":{"name":"Order"},"$id":"Order","$schema":"http://json-schema.org/draft-07/schema"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/openapi/payloads/PetTag.ts b/test/runtime/typescript/src/openapi/payloads/PetTag.ts index 90859573..171c6018 100644 --- a/test/runtime/typescript/src/openapi/payloads/PetTag.ts +++ b/test/runtime/typescript/src/openapi/payloads/PetTag.ts @@ -64,6 +64,9 @@ class PetTag { public static theCodeGenSchema = {"title":"Pet Tag","description":"A tag for a pet","type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Tag"}}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/payload-types/Types.ts b/test/runtime/typescript/src/payload-types/Types.ts new file mode 100644 index 00000000..33e429b2 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/Types.ts @@ -0,0 +1,261 @@ +export type Topics = 'test/primitives/string-plain' | 'test/primitives/string-min-length' | 'test/primitives/string-max-length' | 'test/primitives/string-pattern' | 'test/primitives/number-plain' | 'test/primitives/number-minimum' | 'test/primitives/number-maximum' | 'test/primitives/number-range' | 'test/primitives/integer-plain' | 'test/primitives/integer-range' | 'test/primitives/boolean-plain' | 'test/primitives/null-plain' | 'test/enums/single-string' | 'test/enums/multiple-string' | 'test/enums/integer' | 'test/enums/mixed' | 'test/enums/nullable' | 'test/objects/simple' | 'test/objects/required' | 'test/objects/optional' | 'test/objects/nested' | 'test/objects/deeply-nested' | 'test/objects/additional-true' | 'test/objects/additional-false' | 'test/objects/additional-schema' | 'test/objects/defaults' | 'test/arrays/string' | 'test/arrays/object' | 'test/arrays/nested' | 'test/arrays/min-items' | 'test/arrays/max-items' | 'test/arrays/unique-items' | 'test/arrays/tuple' | 'test/composition/oneof-two' | 'test/composition/oneof-three' | 'test/composition/oneof-discriminator' | 'test/composition/anyof-two' | 'test/composition/allof-two' | 'test/composition/allof-three' | 'test/formats/email' | 'test/formats/uri' | 'test/formats/uuid' | 'test/formats/date' | 'test/formats/datetime' | 'test/formats/time' | 'test/formats/hostname' | 'test/formats/ipv4' | 'test/formats/ipv6' | 'test/formats/object'; +export type TopicIds = 'stringPlain' | 'stringMinLength' | 'stringMaxLength' | 'stringPattern' | 'numberPlain' | 'numberMinimum' | 'numberMaximum' | 'numberRange' | 'integerPlain' | 'integerRange' | 'booleanPlain' | 'nullPlain' | 'singleStringEnum' | 'multipleStringEnum' | 'integerEnum' | 'mixedTypeEnum' | 'nullableEnum' | 'simpleObject' | 'objectRequired' | 'objectOptional' | 'nestedObject' | 'deeplyNestedObject' | 'objectAdditionalTrue' | 'objectAdditionalFalse' | 'objectAdditionalSchema' | 'objectDefaults' | 'stringArray' | 'objectArray' | 'nestedArray' | 'arrayMinItems' | 'arrayMaxItems' | 'arrayUniqueItems' | 'tupleArray' | 'oneOfTwoTypes' | 'oneOfThreeTypes' | 'oneOfDiscriminator' | 'anyOfTwoTypes' | 'allOfTwoTypes' | 'allOfThreeTypes' | 'formatEmail' | 'formatUri' | 'formatUuid' | 'formatDate' | 'formatDateTime' | 'formatTime' | 'formatHostname' | 'formatIpv4' | 'formatIpv6' | 'objectFormats'; +export function ToTopicIds(topic: Topics): TopicIds { + switch (topic) { + case 'test/primitives/string-plain': + return 'stringPlain'; + case 'test/primitives/string-min-length': + return 'stringMinLength'; + case 'test/primitives/string-max-length': + return 'stringMaxLength'; + case 'test/primitives/string-pattern': + return 'stringPattern'; + case 'test/primitives/number-plain': + return 'numberPlain'; + case 'test/primitives/number-minimum': + return 'numberMinimum'; + case 'test/primitives/number-maximum': + return 'numberMaximum'; + case 'test/primitives/number-range': + return 'numberRange'; + case 'test/primitives/integer-plain': + return 'integerPlain'; + case 'test/primitives/integer-range': + return 'integerRange'; + case 'test/primitives/boolean-plain': + return 'booleanPlain'; + case 'test/primitives/null-plain': + return 'nullPlain'; + case 'test/enums/single-string': + return 'singleStringEnum'; + case 'test/enums/multiple-string': + return 'multipleStringEnum'; + case 'test/enums/integer': + return 'integerEnum'; + case 'test/enums/mixed': + return 'mixedTypeEnum'; + case 'test/enums/nullable': + return 'nullableEnum'; + case 'test/objects/simple': + return 'simpleObject'; + case 'test/objects/required': + return 'objectRequired'; + case 'test/objects/optional': + return 'objectOptional'; + case 'test/objects/nested': + return 'nestedObject'; + case 'test/objects/deeply-nested': + return 'deeplyNestedObject'; + case 'test/objects/additional-true': + return 'objectAdditionalTrue'; + case 'test/objects/additional-false': + return 'objectAdditionalFalse'; + case 'test/objects/additional-schema': + return 'objectAdditionalSchema'; + case 'test/objects/defaults': + return 'objectDefaults'; + case 'test/arrays/string': + return 'stringArray'; + case 'test/arrays/object': + return 'objectArray'; + case 'test/arrays/nested': + return 'nestedArray'; + case 'test/arrays/min-items': + return 'arrayMinItems'; + case 'test/arrays/max-items': + return 'arrayMaxItems'; + case 'test/arrays/unique-items': + return 'arrayUniqueItems'; + case 'test/arrays/tuple': + return 'tupleArray'; + case 'test/composition/oneof-two': + return 'oneOfTwoTypes'; + case 'test/composition/oneof-three': + return 'oneOfThreeTypes'; + case 'test/composition/oneof-discriminator': + return 'oneOfDiscriminator'; + case 'test/composition/anyof-two': + return 'anyOfTwoTypes'; + case 'test/composition/allof-two': + return 'allOfTwoTypes'; + case 'test/composition/allof-three': + return 'allOfThreeTypes'; + case 'test/formats/email': + return 'formatEmail'; + case 'test/formats/uri': + return 'formatUri'; + case 'test/formats/uuid': + return 'formatUuid'; + case 'test/formats/date': + return 'formatDate'; + case 'test/formats/datetime': + return 'formatDateTime'; + case 'test/formats/time': + return 'formatTime'; + case 'test/formats/hostname': + return 'formatHostname'; + case 'test/formats/ipv4': + return 'formatIpv4'; + case 'test/formats/ipv6': + return 'formatIpv6'; + case 'test/formats/object': + return 'objectFormats'; + default: + throw new Error('Unknown topic: ' + topic); + } +} +export function ToTopics(topicId: TopicIds): Topics { + switch (topicId) { + case 'stringPlain': + return 'test/primitives/string-plain'; + case 'stringMinLength': + return 'test/primitives/string-min-length'; + case 'stringMaxLength': + return 'test/primitives/string-max-length'; + case 'stringPattern': + return 'test/primitives/string-pattern'; + case 'numberPlain': + return 'test/primitives/number-plain'; + case 'numberMinimum': + return 'test/primitives/number-minimum'; + case 'numberMaximum': + return 'test/primitives/number-maximum'; + case 'numberRange': + return 'test/primitives/number-range'; + case 'integerPlain': + return 'test/primitives/integer-plain'; + case 'integerRange': + return 'test/primitives/integer-range'; + case 'booleanPlain': + return 'test/primitives/boolean-plain'; + case 'nullPlain': + return 'test/primitives/null-plain'; + case 'singleStringEnum': + return 'test/enums/single-string'; + case 'multipleStringEnum': + return 'test/enums/multiple-string'; + case 'integerEnum': + return 'test/enums/integer'; + case 'mixedTypeEnum': + return 'test/enums/mixed'; + case 'nullableEnum': + return 'test/enums/nullable'; + case 'simpleObject': + return 'test/objects/simple'; + case 'objectRequired': + return 'test/objects/required'; + case 'objectOptional': + return 'test/objects/optional'; + case 'nestedObject': + return 'test/objects/nested'; + case 'deeplyNestedObject': + return 'test/objects/deeply-nested'; + case 'objectAdditionalTrue': + return 'test/objects/additional-true'; + case 'objectAdditionalFalse': + return 'test/objects/additional-false'; + case 'objectAdditionalSchema': + return 'test/objects/additional-schema'; + case 'objectDefaults': + return 'test/objects/defaults'; + case 'stringArray': + return 'test/arrays/string'; + case 'objectArray': + return 'test/arrays/object'; + case 'nestedArray': + return 'test/arrays/nested'; + case 'arrayMinItems': + return 'test/arrays/min-items'; + case 'arrayMaxItems': + return 'test/arrays/max-items'; + case 'arrayUniqueItems': + return 'test/arrays/unique-items'; + case 'tupleArray': + return 'test/arrays/tuple'; + case 'oneOfTwoTypes': + return 'test/composition/oneof-two'; + case 'oneOfThreeTypes': + return 'test/composition/oneof-three'; + case 'oneOfDiscriminator': + return 'test/composition/oneof-discriminator'; + case 'anyOfTwoTypes': + return 'test/composition/anyof-two'; + case 'allOfTwoTypes': + return 'test/composition/allof-two'; + case 'allOfThreeTypes': + return 'test/composition/allof-three'; + case 'formatEmail': + return 'test/formats/email'; + case 'formatUri': + return 'test/formats/uri'; + case 'formatUuid': + return 'test/formats/uuid'; + case 'formatDate': + return 'test/formats/date'; + case 'formatDateTime': + return 'test/formats/datetime'; + case 'formatTime': + return 'test/formats/time'; + case 'formatHostname': + return 'test/formats/hostname'; + case 'formatIpv4': + return 'test/formats/ipv4'; + case 'formatIpv6': + return 'test/formats/ipv6'; + case 'objectFormats': + return 'test/formats/object'; + default: + throw new Error('Unknown topic ID: ' + topicId); + } +} +export const TopicsMap: Record = { + 'stringPlain': 'test/primitives/string-plain', + 'stringMinLength': 'test/primitives/string-min-length', + 'stringMaxLength': 'test/primitives/string-max-length', + 'stringPattern': 'test/primitives/string-pattern', + 'numberPlain': 'test/primitives/number-plain', + 'numberMinimum': 'test/primitives/number-minimum', + 'numberMaximum': 'test/primitives/number-maximum', + 'numberRange': 'test/primitives/number-range', + 'integerPlain': 'test/primitives/integer-plain', + 'integerRange': 'test/primitives/integer-range', + 'booleanPlain': 'test/primitives/boolean-plain', + 'nullPlain': 'test/primitives/null-plain', + 'singleStringEnum': 'test/enums/single-string', + 'multipleStringEnum': 'test/enums/multiple-string', + 'integerEnum': 'test/enums/integer', + 'mixedTypeEnum': 'test/enums/mixed', + 'nullableEnum': 'test/enums/nullable', + 'simpleObject': 'test/objects/simple', + 'objectRequired': 'test/objects/required', + 'objectOptional': 'test/objects/optional', + 'nestedObject': 'test/objects/nested', + 'deeplyNestedObject': 'test/objects/deeply-nested', + 'objectAdditionalTrue': 'test/objects/additional-true', + 'objectAdditionalFalse': 'test/objects/additional-false', + 'objectAdditionalSchema': 'test/objects/additional-schema', + 'objectDefaults': 'test/objects/defaults', + 'stringArray': 'test/arrays/string', + 'objectArray': 'test/arrays/object', + 'nestedArray': 'test/arrays/nested', + 'arrayMinItems': 'test/arrays/min-items', + 'arrayMaxItems': 'test/arrays/max-items', + 'arrayUniqueItems': 'test/arrays/unique-items', + 'tupleArray': 'test/arrays/tuple', + 'oneOfTwoTypes': 'test/composition/oneof-two', + 'oneOfThreeTypes': 'test/composition/oneof-three', + 'oneOfDiscriminator': 'test/composition/oneof-discriminator', + 'anyOfTwoTypes': 'test/composition/anyof-two', + 'allOfTwoTypes': 'test/composition/allof-two', + 'allOfThreeTypes': 'test/composition/allof-three', + 'formatEmail': 'test/formats/email', + 'formatUri': 'test/formats/uri', + 'formatUuid': 'test/formats/uuid', + 'formatDate': 'test/formats/date', + 'formatDateTime': 'test/formats/datetime', + 'formatTime': 'test/formats/time', + 'formatHostname': 'test/formats/hostname', + 'formatIpv4': 'test/formats/ipv4', + 'formatIpv6': 'test/formats/ipv6', + 'objectFormats': 'test/formats/object' +}; diff --git a/test/runtime/typescript/src/payload-types/payloads/AllOfThreeTypes.ts b/test/runtime/typescript/src/payload-types/payloads/AllOfThreeTypes.ts new file mode 100644 index 00000000..9f8450b7 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/AllOfThreeTypes.ts @@ -0,0 +1,134 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class AllOfThreeTypes { + private _id: string; + private _name?: string; + private _createdAt?: Date; + private _updatedAt?: Date; + private _createdBy?: string; + private _updatedBy?: string; + private _additionalProperties?: Record; + + constructor(input: { + id: string, + name?: string, + createdAt?: Date, + updatedAt?: Date, + createdBy?: string, + updatedBy?: string, + additionalProperties?: Record, + }) { + this._id = input.id; + this._name = input.name; + this._createdAt = input.createdAt; + this._updatedAt = input.updatedAt; + this._createdBy = input.createdBy; + this._updatedBy = input.updatedBy; + this._additionalProperties = input.additionalProperties; + } + + get id(): string { return this._id; } + set id(id: string) { this._id = id; } + + get name(): string | undefined { return this._name; } + set name(name: string | undefined) { this._name = name; } + + get createdAt(): Date | undefined { return this._createdAt; } + set createdAt(createdAt: Date | undefined) { this._createdAt = createdAt; } + + get updatedAt(): Date | undefined { return this._updatedAt; } + set updatedAt(updatedAt: Date | undefined) { this._updatedAt = updatedAt; } + + get createdBy(): string | undefined { return this._createdBy; } + set createdBy(createdBy: string | undefined) { this._createdBy = createdBy; } + + get updatedBy(): string | undefined { return this._updatedBy; } + set updatedBy(updatedBy: string | undefined) { this._updatedBy = updatedBy; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.id !== undefined) { + json += `"id": ${typeof this.id === 'number' || typeof this.id === 'boolean' ? this.id : JSON.stringify(this.id)},`; + } + if(this.name !== undefined) { + json += `"name": ${typeof this.name === 'number' || typeof this.name === 'boolean' ? this.name : JSON.stringify(this.name)},`; + } + if(this.createdAt !== undefined) { + json += `"createdAt": ${typeof this.createdAt === 'number' || typeof this.createdAt === 'boolean' ? this.createdAt : JSON.stringify(this.createdAt)},`; + } + if(this.updatedAt !== undefined) { + json += `"updatedAt": ${typeof this.updatedAt === 'number' || typeof this.updatedAt === 'boolean' ? this.updatedAt : JSON.stringify(this.updatedAt)},`; + } + if(this.createdBy !== undefined) { + json += `"createdBy": ${typeof this.createdBy === 'number' || typeof this.createdBy === 'boolean' ? this.createdBy : JSON.stringify(this.createdBy)},`; + } + if(this.updatedBy !== undefined) { + json += `"updatedBy": ${typeof this.updatedBy === 'number' || typeof this.updatedBy === 'boolean' ? this.updatedBy : JSON.stringify(this.updatedBy)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["id","name","createdAt","updatedAt","createdBy","updatedBy","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): AllOfThreeTypes { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new AllOfThreeTypes({} as any); + + if (obj["id"] !== undefined) { + instance.id = obj["id"]; + } + if (obj["name"] !== undefined) { + instance.name = obj["name"]; + } + if (obj["createdAt"] !== undefined) { + instance.createdAt = obj["createdAt"] == null ? undefined : new Date(obj["createdAt"]); + } + if (obj["updatedAt"] !== undefined) { + instance.updatedAt = obj["updatedAt"] == null ? undefined : new Date(obj["updatedAt"]); + } + if (obj["createdBy"] !== undefined) { + instance.createdBy = obj["createdBy"]; + } + if (obj["updatedBy"] !== undefined) { + instance.updatedBy = obj["updatedBy"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["id","name","createdAt","updatedAt","createdBy","updatedBy","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","allOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id"]},{"type":"object","properties":{"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},{"type":"object","properties":{"createdBy":{"type":"string"},"updatedBy":{"type":"string"}}}],"description":"AllOf combining three schemas","$id":"AllOfThreeTypes"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { AllOfThreeTypes }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/AllOfTwoTypes.ts b/test/runtime/typescript/src/payload-types/payloads/AllOfTwoTypes.ts new file mode 100644 index 00000000..ab9defd3 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/AllOfTwoTypes.ts @@ -0,0 +1,110 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class AllOfTwoTypes { + private _id: string; + private _name?: string; + private _createdAt?: Date; + private _updatedAt?: Date; + private _additionalProperties?: Record; + + constructor(input: { + id: string, + name?: string, + createdAt?: Date, + updatedAt?: Date, + additionalProperties?: Record, + }) { + this._id = input.id; + this._name = input.name; + this._createdAt = input.createdAt; + this._updatedAt = input.updatedAt; + this._additionalProperties = input.additionalProperties; + } + + get id(): string { return this._id; } + set id(id: string) { this._id = id; } + + get name(): string | undefined { return this._name; } + set name(name: string | undefined) { this._name = name; } + + get createdAt(): Date | undefined { return this._createdAt; } + set createdAt(createdAt: Date | undefined) { this._createdAt = createdAt; } + + get updatedAt(): Date | undefined { return this._updatedAt; } + set updatedAt(updatedAt: Date | undefined) { this._updatedAt = updatedAt; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.id !== undefined) { + json += `"id": ${typeof this.id === 'number' || typeof this.id === 'boolean' ? this.id : JSON.stringify(this.id)},`; + } + if(this.name !== undefined) { + json += `"name": ${typeof this.name === 'number' || typeof this.name === 'boolean' ? this.name : JSON.stringify(this.name)},`; + } + if(this.createdAt !== undefined) { + json += `"createdAt": ${typeof this.createdAt === 'number' || typeof this.createdAt === 'boolean' ? this.createdAt : JSON.stringify(this.createdAt)},`; + } + if(this.updatedAt !== undefined) { + json += `"updatedAt": ${typeof this.updatedAt === 'number' || typeof this.updatedAt === 'boolean' ? this.updatedAt : JSON.stringify(this.updatedAt)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["id","name","createdAt","updatedAt","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): AllOfTwoTypes { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new AllOfTwoTypes({} as any); + + if (obj["id"] !== undefined) { + instance.id = obj["id"]; + } + if (obj["name"] !== undefined) { + instance.name = obj["name"]; + } + if (obj["createdAt"] !== undefined) { + instance.createdAt = obj["createdAt"] == null ? undefined : new Date(obj["createdAt"]); + } + if (obj["updatedAt"] !== undefined) { + instance.updatedAt = obj["updatedAt"] == null ? undefined : new Date(obj["updatedAt"]); + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["id","name","createdAt","updatedAt","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","allOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id"]},{"type":"object","properties":{"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}],"description":"AllOf combining base entity and timestamp","$id":"AllOfTwoTypes"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { AllOfTwoTypes }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypes.ts b/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypes.ts new file mode 100644 index 00000000..0b1b3044 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypes.ts @@ -0,0 +1,43 @@ +import {AnyOfTwoTypesAnyOfOption0} from './AnyOfTwoTypesAnyOfOption0'; +import {AnyOfTwoTypesAnyOfOption1} from './AnyOfTwoTypesAnyOfOption1'; +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type AnyOfTwoTypes = AnyOfTwoTypesAnyOfOption0 | AnyOfTwoTypesAnyOfOption1; + +export function unmarshal(json: any): AnyOfTwoTypes { + + return JSON.parse(json); +} +export function marshal(payload: AnyOfTwoTypes) { + if(payload instanceof AnyOfTwoTypesAnyOfOption0) { +return payload.marshal(); +} +if(payload instanceof AnyOfTwoTypesAnyOfOption1) { +return payload.marshal(); +} + return JSON.stringify(payload); +} + +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","anyOf":[{"type":"object","properties":{"name":{"type":"string"}}},{"type":"object","properties":{"id":{"type":"integer"}}}],"description":"AnyOf with two object schemas","$id":"AnyOfTwoTypes"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { AnyOfTwoTypes }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypesAnyOfOption0.ts b/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypesAnyOfOption0.ts new file mode 100644 index 00000000..1e7ce2c1 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypesAnyOfOption0.ts @@ -0,0 +1,74 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class AnyOfTwoTypesAnyOfOption0 { + private _name?: string; + private _additionalProperties?: Record; + + constructor(input: { + name?: string, + additionalProperties?: Record, + }) { + this._name = input.name; + this._additionalProperties = input.additionalProperties; + } + + get name(): string | undefined { return this._name; } + set name(name: string | undefined) { this._name = name; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.name !== undefined) { + json += `"name": ${typeof this.name === 'number' || typeof this.name === 'boolean' ? this.name : JSON.stringify(this.name)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["name","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): AnyOfTwoTypesAnyOfOption0 { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new AnyOfTwoTypesAnyOfOption0({} as any); + + if (obj["name"] !== undefined) { + instance.name = obj["name"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["name","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","properties":{"name":{"type":"string"}}}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { AnyOfTwoTypesAnyOfOption0 }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypesAnyOfOption1.ts b/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypesAnyOfOption1.ts new file mode 100644 index 00000000..2ead14dc --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypesAnyOfOption1.ts @@ -0,0 +1,74 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class AnyOfTwoTypesAnyOfOption1 { + private _id?: number; + private _additionalProperties?: Record; + + constructor(input: { + id?: number, + additionalProperties?: Record, + }) { + this._id = input.id; + this._additionalProperties = input.additionalProperties; + } + + get id(): number | undefined { return this._id; } + set id(id: number | undefined) { this._id = id; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.id !== undefined) { + json += `"id": ${typeof this.id === 'number' || typeof this.id === 'boolean' ? this.id : JSON.stringify(this.id)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["id","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): AnyOfTwoTypesAnyOfOption1 { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new AnyOfTwoTypesAnyOfOption1({} as any); + + if (obj["id"] !== undefined) { + instance.id = obj["id"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["id","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","properties":{"id":{"type":"integer"}}}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { AnyOfTwoTypesAnyOfOption1 }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ArrayWithMaxItems.ts b/test/runtime/typescript/src/payload-types/payloads/ArrayWithMaxItems.ts new file mode 100644 index 00000000..84bdce6f --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ArrayWithMaxItems.ts @@ -0,0 +1,36 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type ArrayWithMaxItems = string[]; + +export function unmarshal(json: string | any[]): ArrayWithMaxItems { + if (typeof json === 'string') { + return JSON.parse(json) as ArrayWithMaxItems; + } + return json as ArrayWithMaxItems; +} +export function marshal(payload: ArrayWithMaxItems): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"array","$schema":"http://json-schema.org/draft-07/schema","items":{"type":"string"},"maxItems":5,"description":"Array with maximum 5 items","$id":"ArrayWithMaxItems"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { ArrayWithMaxItems }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ArrayWithMinItems.ts b/test/runtime/typescript/src/payload-types/payloads/ArrayWithMinItems.ts new file mode 100644 index 00000000..af5b5ebe --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ArrayWithMinItems.ts @@ -0,0 +1,36 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type ArrayWithMinItems = string[]; + +export function unmarshal(json: string | any[]): ArrayWithMinItems { + if (typeof json === 'string') { + return JSON.parse(json) as ArrayWithMinItems; + } + return json as ArrayWithMinItems; +} +export function marshal(payload: ArrayWithMinItems): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"array","$schema":"http://json-schema.org/draft-07/schema","items":{"type":"string"},"minItems":1,"description":"Array with minimum 1 item","$id":"ArrayWithMinItems"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { ArrayWithMinItems }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ArrayWithUniqueItems.ts b/test/runtime/typescript/src/payload-types/payloads/ArrayWithUniqueItems.ts new file mode 100644 index 00000000..41e58bf9 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ArrayWithUniqueItems.ts @@ -0,0 +1,36 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type ArrayWithUniqueItems = string[]; + +export function unmarshal(json: string | any[]): ArrayWithUniqueItems { + if (typeof json === 'string') { + return JSON.parse(json) as ArrayWithUniqueItems; + } + return json as ArrayWithUniqueItems; +} +export function marshal(payload: ArrayWithUniqueItems): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"array","$schema":"http://json-schema.org/draft-07/schema","items":{"type":"string"},"uniqueItems":true,"description":"Array requiring unique items","$id":"ArrayWithUniqueItems"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { ArrayWithUniqueItems }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/BooleanPlain.ts b/test/runtime/typescript/src/payload-types/payloads/BooleanPlain.ts new file mode 100644 index 00000000..fa701520 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/BooleanPlain.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type BooleanPlain = boolean; + +export function unmarshal(json: string): BooleanPlain { + return JSON.parse(json) as BooleanPlain; +} +export function marshal(payload: BooleanPlain): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"boolean","$schema":"http://json-schema.org/draft-07/schema","description":"A plain boolean type","$id":"BooleanPlain"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { BooleanPlain }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/CatPayload.ts b/test/runtime/typescript/src/payload-types/payloads/CatPayload.ts new file mode 100644 index 00000000..a26415b2 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/CatPayload.ts @@ -0,0 +1,92 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class CatPayload { + private _petType: 'cat' = 'cat'; + private _breed?: string; + private _meowPitch?: string; + private _additionalProperties?: Record; + + constructor(input: { + breed?: string, + meowPitch?: string, + additionalProperties?: Record, + }) { + this._breed = input.breed; + this._meowPitch = input.meowPitch; + this._additionalProperties = input.additionalProperties; + } + + get petType(): 'cat' { return this._petType; } + + get breed(): string | undefined { return this._breed; } + set breed(breed: string | undefined) { this._breed = breed; } + + get meowPitch(): string | undefined { return this._meowPitch; } + set meowPitch(meowPitch: string | undefined) { this._meowPitch = meowPitch; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.petType !== undefined) { + json += `"petType": ${typeof this.petType === 'number' || typeof this.petType === 'boolean' ? this.petType : JSON.stringify(this.petType)},`; + } + if(this.breed !== undefined) { + json += `"breed": ${typeof this.breed === 'number' || typeof this.breed === 'boolean' ? this.breed : JSON.stringify(this.breed)},`; + } + if(this.meowPitch !== undefined) { + json += `"meowPitch": ${typeof this.meowPitch === 'number' || typeof this.meowPitch === 'boolean' ? this.meowPitch : JSON.stringify(this.meowPitch)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["petType","breed","meowPitch","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): CatPayload { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new CatPayload({} as any); + + if (obj["breed"] !== undefined) { + instance.breed = obj["breed"]; + } + if (obj["meowPitch"] !== undefined) { + instance.meowPitch = obj["meowPitch"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["petType","breed","meowPitch","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","properties":{"petType":{"const":"cat"},"breed":{"type":"string"},"meowPitch":{"type":"string"}},"required":["petType"]}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { CatPayload }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObject.ts b/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObject.ts new file mode 100644 index 00000000..4b5b2cf5 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObject.ts @@ -0,0 +1,75 @@ +import {DeeplyNestedObjectLevel1} from './DeeplyNestedObjectLevel1'; +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class DeeplyNestedObject { + private _level1?: DeeplyNestedObjectLevel1; + private _additionalProperties?: Record; + + constructor(input: { + level1?: DeeplyNestedObjectLevel1, + additionalProperties?: Record, + }) { + this._level1 = input.level1; + this._additionalProperties = input.additionalProperties; + } + + get level1(): DeeplyNestedObjectLevel1 | undefined { return this._level1; } + set level1(level1: DeeplyNestedObjectLevel1 | undefined) { this._level1 = level1; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.level1 !== undefined) { + json += `"level1": ${this.level1 && typeof this.level1 === 'object' && 'marshal' in this.level1 && typeof this.level1.marshal === 'function' ? this.level1.marshal() : JSON.stringify(this.level1)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["level1","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): DeeplyNestedObject { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new DeeplyNestedObject({} as any); + + if (obj["level1"] !== undefined) { + instance.level1 = DeeplyNestedObjectLevel1.unmarshal(obj["level1"]); + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["level1","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"level1":{"type":"object","properties":{"level2":{"type":"object","properties":{"level3":{"type":"object","properties":{"value":{"type":"string"}}}}}}}},"description":"Object with 3 levels of nesting","$id":"DeeplyNestedObject"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { DeeplyNestedObject }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObjectLevel1.ts b/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObjectLevel1.ts new file mode 100644 index 00000000..f3bab09f --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObjectLevel1.ts @@ -0,0 +1,75 @@ +import {DeeplyNestedObjectLevel1Level2} from './DeeplyNestedObjectLevel1Level2'; +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class DeeplyNestedObjectLevel1 { + private _level2?: DeeplyNestedObjectLevel1Level2; + private _additionalProperties?: Record; + + constructor(input: { + level2?: DeeplyNestedObjectLevel1Level2, + additionalProperties?: Record, + }) { + this._level2 = input.level2; + this._additionalProperties = input.additionalProperties; + } + + get level2(): DeeplyNestedObjectLevel1Level2 | undefined { return this._level2; } + set level2(level2: DeeplyNestedObjectLevel1Level2 | undefined) { this._level2 = level2; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.level2 !== undefined) { + json += `"level2": ${this.level2 && typeof this.level2 === 'object' && 'marshal' in this.level2 && typeof this.level2.marshal === 'function' ? this.level2.marshal() : JSON.stringify(this.level2)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["level2","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): DeeplyNestedObjectLevel1 { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new DeeplyNestedObjectLevel1({} as any); + + if (obj["level2"] !== undefined) { + instance.level2 = DeeplyNestedObjectLevel1Level2.unmarshal(obj["level2"]); + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["level2","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","properties":{"level2":{"type":"object","properties":{"level3":{"type":"object","properties":{"value":{"type":"string"}}}}}}}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { DeeplyNestedObjectLevel1 }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObjectLevel1Level2.ts b/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObjectLevel1Level2.ts new file mode 100644 index 00000000..4f8b37d0 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObjectLevel1Level2.ts @@ -0,0 +1,75 @@ +import {DeeplyNestedObjectLevel1Level2Level3} from './DeeplyNestedObjectLevel1Level2Level3'; +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class DeeplyNestedObjectLevel1Level2 { + private _level3?: DeeplyNestedObjectLevel1Level2Level3; + private _additionalProperties?: Record; + + constructor(input: { + level3?: DeeplyNestedObjectLevel1Level2Level3, + additionalProperties?: Record, + }) { + this._level3 = input.level3; + this._additionalProperties = input.additionalProperties; + } + + get level3(): DeeplyNestedObjectLevel1Level2Level3 | undefined { return this._level3; } + set level3(level3: DeeplyNestedObjectLevel1Level2Level3 | undefined) { this._level3 = level3; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.level3 !== undefined) { + json += `"level3": ${this.level3 && typeof this.level3 === 'object' && 'marshal' in this.level3 && typeof this.level3.marshal === 'function' ? this.level3.marshal() : JSON.stringify(this.level3)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["level3","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): DeeplyNestedObjectLevel1Level2 { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new DeeplyNestedObjectLevel1Level2({} as any); + + if (obj["level3"] !== undefined) { + instance.level3 = DeeplyNestedObjectLevel1Level2Level3.unmarshal(obj["level3"]); + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["level3","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","properties":{"level3":{"type":"object","properties":{"value":{"type":"string"}}}}}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { DeeplyNestedObjectLevel1Level2 }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObjectLevel1Level2Level3.ts b/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObjectLevel1Level2Level3.ts new file mode 100644 index 00000000..8695b986 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObjectLevel1Level2Level3.ts @@ -0,0 +1,74 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class DeeplyNestedObjectLevel1Level2Level3 { + private _value?: string; + private _additionalProperties?: Record; + + constructor(input: { + value?: string, + additionalProperties?: Record, + }) { + this._value = input.value; + this._additionalProperties = input.additionalProperties; + } + + get value(): string | undefined { return this._value; } + set value(value: string | undefined) { this._value = value; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.value !== undefined) { + json += `"value": ${typeof this.value === 'number' || typeof this.value === 'boolean' ? this.value : JSON.stringify(this.value)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["value","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): DeeplyNestedObjectLevel1Level2Level3 { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new DeeplyNestedObjectLevel1Level2Level3({} as any); + + if (obj["value"] !== undefined) { + instance.value = obj["value"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["value","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","properties":{"value":{"type":"string"}}}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { DeeplyNestedObjectLevel1Level2Level3 }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/DogPayload.ts b/test/runtime/typescript/src/payload-types/payloads/DogPayload.ts new file mode 100644 index 00000000..5cac343c --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/DogPayload.ts @@ -0,0 +1,92 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class DogPayload { + private _petType: 'dog' = 'dog'; + private _breed?: string; + private _barkVolume?: number; + private _additionalProperties?: Record; + + constructor(input: { + breed?: string, + barkVolume?: number, + additionalProperties?: Record, + }) { + this._breed = input.breed; + this._barkVolume = input.barkVolume; + this._additionalProperties = input.additionalProperties; + } + + get petType(): 'dog' { return this._petType; } + + get breed(): string | undefined { return this._breed; } + set breed(breed: string | undefined) { this._breed = breed; } + + get barkVolume(): number | undefined { return this._barkVolume; } + set barkVolume(barkVolume: number | undefined) { this._barkVolume = barkVolume; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.petType !== undefined) { + json += `"petType": ${typeof this.petType === 'number' || typeof this.petType === 'boolean' ? this.petType : JSON.stringify(this.petType)},`; + } + if(this.breed !== undefined) { + json += `"breed": ${typeof this.breed === 'number' || typeof this.breed === 'boolean' ? this.breed : JSON.stringify(this.breed)},`; + } + if(this.barkVolume !== undefined) { + json += `"barkVolume": ${typeof this.barkVolume === 'number' || typeof this.barkVolume === 'boolean' ? this.barkVolume : JSON.stringify(this.barkVolume)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["petType","breed","barkVolume","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): DogPayload { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new DogPayload({} as any); + + if (obj["breed"] !== undefined) { + instance.breed = obj["breed"]; + } + if (obj["barkVolume"] !== undefined) { + instance.barkVolume = obj["barkVolume"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["petType","breed","barkVolume","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","properties":{"petType":{"const":"dog"},"breed":{"type":"string"},"barkVolume":{"type":"integer"}},"required":["petType"]}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { DogPayload }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatDate.ts b/test/runtime/typescript/src/payload-types/payloads/FormatDate.ts new file mode 100644 index 00000000..3323069f --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/FormatDate.ts @@ -0,0 +1,34 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type FormatDate = Date; + +export function unmarshal(json: string): FormatDate { + const parsed = JSON.parse(json); + return new Date(parsed); +} +export function marshal(payload: FormatDate): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","format":"date","description":"String with date format (YYYY-MM-DD)","$id":"FormatDate"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { FormatDate }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatDateTime.ts b/test/runtime/typescript/src/payload-types/payloads/FormatDateTime.ts new file mode 100644 index 00000000..b03fac72 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/FormatDateTime.ts @@ -0,0 +1,34 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type FormatDateTime = Date; + +export function unmarshal(json: string): FormatDateTime { + const parsed = JSON.parse(json); + return new Date(parsed); +} +export function marshal(payload: FormatDateTime): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","format":"date-time","description":"String with date-time format (ISO 8601)","$id":"FormatDateTime"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { FormatDateTime }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatEmail.ts b/test/runtime/typescript/src/payload-types/payloads/FormatEmail.ts new file mode 100644 index 00000000..d49b39f5 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/FormatEmail.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type FormatEmail = string; + +export function unmarshal(json: string): FormatEmail { + return JSON.parse(json) as FormatEmail; +} +export function marshal(payload: FormatEmail): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","format":"email","description":"String with email format","$id":"FormatEmail"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { FormatEmail }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatHostname.ts b/test/runtime/typescript/src/payload-types/payloads/FormatHostname.ts new file mode 100644 index 00000000..a5504598 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/FormatHostname.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type FormatHostname = string; + +export function unmarshal(json: string): FormatHostname { + return JSON.parse(json) as FormatHostname; +} +export function marshal(payload: FormatHostname): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","format":"hostname","description":"String with hostname format","$id":"FormatHostname"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { FormatHostname }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatIpv4.ts b/test/runtime/typescript/src/payload-types/payloads/FormatIpv4.ts new file mode 100644 index 00000000..be361faf --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/FormatIpv4.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type FormatIpv4 = string; + +export function unmarshal(json: string): FormatIpv4 { + return JSON.parse(json) as FormatIpv4; +} +export function marshal(payload: FormatIpv4): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","format":"ipv4","description":"String with IPv4 format","$id":"FormatIpv4"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { FormatIpv4 }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatIpv6.ts b/test/runtime/typescript/src/payload-types/payloads/FormatIpv6.ts new file mode 100644 index 00000000..9f5c3b88 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/FormatIpv6.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type FormatIpv6 = string; + +export function unmarshal(json: string): FormatIpv6 { + return JSON.parse(json) as FormatIpv6; +} +export function marshal(payload: FormatIpv6): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","format":"ipv6","description":"String with IPv6 format","$id":"FormatIpv6"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { FormatIpv6 }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatTime.ts b/test/runtime/typescript/src/payload-types/payloads/FormatTime.ts new file mode 100644 index 00000000..5f216888 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/FormatTime.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type FormatTime = Date; + +export function unmarshal(json: string): FormatTime { + return JSON.parse(json) as FormatTime; +} +export function marshal(payload: FormatTime): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","format":"time","description":"String with time format (HH:MM:SS)","$id":"FormatTime"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { FormatTime }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatUri.ts b/test/runtime/typescript/src/payload-types/payloads/FormatUri.ts new file mode 100644 index 00000000..14458705 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/FormatUri.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type FormatUri = string; + +export function unmarshal(json: string): FormatUri { + return JSON.parse(json) as FormatUri; +} +export function marshal(payload: FormatUri): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","format":"uri","description":"String with URI format","$id":"FormatUri"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { FormatUri }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatUuid.ts b/test/runtime/typescript/src/payload-types/payloads/FormatUuid.ts new file mode 100644 index 00000000..a9317dff --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/FormatUuid.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type FormatUuid = string; + +export function unmarshal(json: string): FormatUuid { + return JSON.parse(json) as FormatUuid; +} +export function marshal(payload: FormatUuid): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","format":"uuid","description":"String with UUID format","$id":"FormatUuid"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { FormatUuid }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/IntegerEnum.ts b/test/runtime/typescript/src/payload-types/payloads/IntegerEnum.ts new file mode 100644 index 00000000..55147808 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/IntegerEnum.ts @@ -0,0 +1,9 @@ + +enum IntegerEnum { + NUMBER_1 = 1, + NUMBER_2 = 2, + NUMBER_3 = 3, + NUMBER_4 = 4, + NUMBER_5 = 5, +} +export { IntegerEnum }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/IntegerPlain.ts b/test/runtime/typescript/src/payload-types/payloads/IntegerPlain.ts new file mode 100644 index 00000000..0185c43e --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/IntegerPlain.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type IntegerPlain = number; + +export function unmarshal(json: string): IntegerPlain { + return JSON.parse(json) as IntegerPlain; +} +export function marshal(payload: IntegerPlain): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"integer","$schema":"http://json-schema.org/draft-07/schema","description":"A plain integer type","$id":"IntegerPlain"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { IntegerPlain }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/IntegerWithRange.ts b/test/runtime/typescript/src/payload-types/payloads/IntegerWithRange.ts new file mode 100644 index 00000000..1905e883 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/IntegerWithRange.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type IntegerWithRange = number; + +export function unmarshal(json: string): IntegerWithRange { + return JSON.parse(json) as IntegerWithRange; +} +export function marshal(payload: IntegerWithRange): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"integer","$schema":"http://json-schema.org/draft-07/schema","minimum":1,"maximum":10,"description":"Integer between 1 and 10","$id":"IntegerWithRange"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { IntegerWithRange }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/MixedTypeEnum.ts b/test/runtime/typescript/src/payload-types/payloads/MixedTypeEnum.ts new file mode 100644 index 00000000..77885d12 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/MixedTypeEnum.ts @@ -0,0 +1,7 @@ + +enum MixedTypeEnum { + STRING_VALUE = "string_value", + NUMBER_123 = 123, + RESERVED_TRUE = "true", +} +export { MixedTypeEnum }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/MultipleStringEnum.ts b/test/runtime/typescript/src/payload-types/payloads/MultipleStringEnum.ts new file mode 100644 index 00000000..086f3704 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/MultipleStringEnum.ts @@ -0,0 +1,8 @@ + +enum MultipleStringEnum { + PENDING = "pending", + ACTIVE = "active", + INACTIVE = "inactive", + DELETED = "deleted", +} +export { MultipleStringEnum }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/NestedArray.ts b/test/runtime/typescript/src/payload-types/payloads/NestedArray.ts new file mode 100644 index 00000000..fb7358f9 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/NestedArray.ts @@ -0,0 +1,41 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type NestedArray = string[][]; + +export function unmarshal(json: string | any[]): NestedArray { + if (typeof json === 'string') { + return JSON.parse(json) as NestedArray; + } + return json as NestedArray; +} +export function marshal(payload: NestedArray): string { + return JSON.stringify(payload.map((item) => { + if (item && typeof item === 'object' && 'marshal' in item && typeof item.marshal === 'function') { + return JSON.parse(item.marshal()); + } + return item; + })); +} +export const theCodeGenSchema = {"type":"array","$schema":"http://json-schema.org/draft-07/schema","items":{"type":"array","items":{"type":"string"}},"description":"2D array (array of string arrays)","$id":"NestedArray"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { NestedArray }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/NestedObject.ts b/test/runtime/typescript/src/payload-types/payloads/NestedObject.ts new file mode 100644 index 00000000..475e1a48 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/NestedObject.ts @@ -0,0 +1,75 @@ +import {NestedObjectOuter} from './NestedObjectOuter'; +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class NestedObject { + private _outer?: NestedObjectOuter; + private _additionalProperties?: Record; + + constructor(input: { + outer?: NestedObjectOuter, + additionalProperties?: Record, + }) { + this._outer = input.outer; + this._additionalProperties = input.additionalProperties; + } + + get outer(): NestedObjectOuter | undefined { return this._outer; } + set outer(outer: NestedObjectOuter | undefined) { this._outer = outer; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.outer !== undefined) { + json += `"outer": ${this.outer && typeof this.outer === 'object' && 'marshal' in this.outer && typeof this.outer.marshal === 'function' ? this.outer.marshal() : JSON.stringify(this.outer)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["outer","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): NestedObject { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new NestedObject({} as any); + + if (obj["outer"] !== undefined) { + instance.outer = NestedObjectOuter.unmarshal(obj["outer"]); + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["outer","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"outer":{"type":"object","properties":{"inner":{"type":"string"}}}},"description":"Object with one level of nesting","$id":"NestedObject"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { NestedObject }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/NestedObjectOuter.ts b/test/runtime/typescript/src/payload-types/payloads/NestedObjectOuter.ts new file mode 100644 index 00000000..4e6ad031 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/NestedObjectOuter.ts @@ -0,0 +1,74 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class NestedObjectOuter { + private _inner?: string; + private _additionalProperties?: Record; + + constructor(input: { + inner?: string, + additionalProperties?: Record, + }) { + this._inner = input.inner; + this._additionalProperties = input.additionalProperties; + } + + get inner(): string | undefined { return this._inner; } + set inner(inner: string | undefined) { this._inner = inner; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.inner !== undefined) { + json += `"inner": ${typeof this.inner === 'number' || typeof this.inner === 'boolean' ? this.inner : JSON.stringify(this.inner)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["inner","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): NestedObjectOuter { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new NestedObjectOuter({} as any); + + if (obj["inner"] !== undefined) { + instance.inner = obj["inner"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["inner","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","properties":{"inner":{"type":"string"}}}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { NestedObjectOuter }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/NullPlain.ts b/test/runtime/typescript/src/payload-types/payloads/NullPlain.ts new file mode 100644 index 00000000..ca5dc05c --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/NullPlain.ts @@ -0,0 +1,37 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type NullPlain = any; + +export function unmarshal(json: string): null { + const parsed = JSON.parse(json); + if (parsed !== null) { + throw new Error('Expected null value'); + } + return null; +} +export function marshal(payload: null): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"null","$schema":"http://json-schema.org/draft-07/schema","description":"A null type","$id":"NullPlain"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { NullPlain }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/NullableEnum.ts b/test/runtime/typescript/src/payload-types/payloads/NullableEnum.ts new file mode 100644 index 00000000..94cae99f --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/NullableEnum.ts @@ -0,0 +1,7 @@ + +enum NullableEnum { + OPTION_A = "option_a", + OPTION_B = "option_b", + RESERVED_NULL = 'null', +} +export { NullableEnum }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/NumberPlain.ts b/test/runtime/typescript/src/payload-types/payloads/NumberPlain.ts new file mode 100644 index 00000000..c17e4f69 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/NumberPlain.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type NumberPlain = number; + +export function unmarshal(json: string): NumberPlain { + return JSON.parse(json) as NumberPlain; +} +export function marshal(payload: NumberPlain): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"number","$schema":"http://json-schema.org/draft-07/schema","description":"A plain number type","$id":"NumberPlain"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { NumberPlain }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/NumberWithMaximum.ts b/test/runtime/typescript/src/payload-types/payloads/NumberWithMaximum.ts new file mode 100644 index 00000000..79d0b2b9 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/NumberWithMaximum.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type NumberWithMaximum = number; + +export function unmarshal(json: string): NumberWithMaximum { + return JSON.parse(json) as NumberWithMaximum; +} +export function marshal(payload: NumberWithMaximum): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"number","$schema":"http://json-schema.org/draft-07/schema","maximum":100,"description":"Number with maximum of 100","$id":"NumberWithMaximum"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { NumberWithMaximum }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/NumberWithMinimum.ts b/test/runtime/typescript/src/payload-types/payloads/NumberWithMinimum.ts new file mode 100644 index 00000000..7228e934 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/NumberWithMinimum.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type NumberWithMinimum = number; + +export function unmarshal(json: string): NumberWithMinimum { + return JSON.parse(json) as NumberWithMinimum; +} +export function marshal(payload: NumberWithMinimum): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"number","$schema":"http://json-schema.org/draft-07/schema","minimum":0,"description":"Number with minimum of 0","$id":"NumberWithMinimum"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { NumberWithMinimum }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/NumberWithRange.ts b/test/runtime/typescript/src/payload-types/payloads/NumberWithRange.ts new file mode 100644 index 00000000..67227a20 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/NumberWithRange.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type NumberWithRange = number; + +export function unmarshal(json: string): NumberWithRange { + return JSON.parse(json) as NumberWithRange; +} +export function marshal(payload: NumberWithRange): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"number","$schema":"http://json-schema.org/draft-07/schema","exclusiveMinimum":0,"exclusiveMaximum":100,"description":"Number strictly between 0 and 100","$id":"NumberWithRange"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { NumberWithRange }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsFalse.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsFalse.ts new file mode 100644 index 00000000..315405ce --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsFalse.ts @@ -0,0 +1,58 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class ObjectAdditionalPropsFalse { + private _known?: string; + + constructor(input: { + known?: string, + }) { + this._known = input.known; + } + + get known(): string | undefined { return this._known; } + set known(known: string | undefined) { this._known = known; } + + public marshal() : string { + let json = '{' + if(this.known !== undefined) { + json += `"known": ${typeof this.known === 'number' || typeof this.known === 'boolean' ? this.known : JSON.stringify(this.known)},`; + } + + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): ObjectAdditionalPropsFalse { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new ObjectAdditionalPropsFalse({} as any); + + if (obj["known"] !== undefined) { + instance.known = obj["known"]; + } + + + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"known":{"type":"string"}},"additionalProperties":false,"description":"Object that disallows additional properties","$id":"ObjectAdditionalPropsFalse"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { ObjectAdditionalPropsFalse }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsSchema.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsSchema.ts new file mode 100644 index 00000000..ee719931 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsSchema.ts @@ -0,0 +1,74 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class ObjectAdditionalPropsSchema { + private _known?: string; + private _additionalProperties?: Record; + + constructor(input: { + known?: string, + additionalProperties?: Record, + }) { + this._known = input.known; + this._additionalProperties = input.additionalProperties; + } + + get known(): string | undefined { return this._known; } + set known(known: string | undefined) { this._known = known; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.known !== undefined) { + json += `"known": ${typeof this.known === 'number' || typeof this.known === 'boolean' ? this.known : JSON.stringify(this.known)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["known","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): ObjectAdditionalPropsSchema { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new ObjectAdditionalPropsSchema({} as any); + + if (obj["known"] !== undefined) { + instance.known = obj["known"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["known","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"known":{"type":"string"}},"additionalProperties":{"type":"integer"},"description":"Object with typed additional properties (integers)","$id":"ObjectAdditionalPropsSchema"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { ObjectAdditionalPropsSchema }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsTrue.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsTrue.ts new file mode 100644 index 00000000..9839bbf6 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsTrue.ts @@ -0,0 +1,74 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class ObjectAdditionalPropsTrue { + private _known?: string; + private _additionalProperties?: Record; + + constructor(input: { + known?: string, + additionalProperties?: Record, + }) { + this._known = input.known; + this._additionalProperties = input.additionalProperties; + } + + get known(): string | undefined { return this._known; } + set known(known: string | undefined) { this._known = known; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.known !== undefined) { + json += `"known": ${typeof this.known === 'number' || typeof this.known === 'boolean' ? this.known : JSON.stringify(this.known)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["known","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): ObjectAdditionalPropsTrue { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new ObjectAdditionalPropsTrue({} as any); + + if (obj["known"] !== undefined) { + instance.known = obj["known"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["known","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"known":{"type":"string"}},"additionalProperties":true,"description":"Object that allows additional properties","$id":"ObjectAdditionalPropsTrue"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { ObjectAdditionalPropsTrue }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectArray.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectArray.ts new file mode 100644 index 00000000..89f1efb3 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectArray.ts @@ -0,0 +1,45 @@ +import {ObjectArrayItem} from './ObjectArrayItem'; +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type ObjectArray = ObjectArrayItem[]; + +export function unmarshal(json: string | any[]): ObjectArray { + const arr = typeof json === 'string' ? JSON.parse(json) : json; + return arr.map((item: any) => { + if (item && typeof item === 'object') { + return ObjectArrayItem.unmarshal(item); + } + return item; + }) as ObjectArray; +} +export function marshal(payload: ObjectArray): string { + return JSON.stringify(payload.map((item) => { + if (item && typeof item === 'object' && 'marshal' in item && typeof item.marshal === 'function') { + return JSON.parse(item.marshal()); + } + return item; + })); +} +export const theCodeGenSchema = {"type":"array","$schema":"http://json-schema.org/draft-07/schema","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}}},"description":"Array of objects","$id":"ObjectArray"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { ObjectArray }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectArrayItem.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectArrayItem.ts new file mode 100644 index 00000000..c97aa84b --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectArrayItem.ts @@ -0,0 +1,86 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class ObjectArrayItem { + private _id?: number; + private _name?: string; + private _additionalProperties?: Record; + + constructor(input: { + id?: number, + name?: string, + additionalProperties?: Record, + }) { + this._id = input.id; + this._name = input.name; + this._additionalProperties = input.additionalProperties; + } + + get id(): number | undefined { return this._id; } + set id(id: number | undefined) { this._id = id; } + + get name(): string | undefined { return this._name; } + set name(name: string | undefined) { this._name = name; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.id !== undefined) { + json += `"id": ${typeof this.id === 'number' || typeof this.id === 'boolean' ? this.id : JSON.stringify(this.id)},`; + } + if(this.name !== undefined) { + json += `"name": ${typeof this.name === 'number' || typeof this.name === 'boolean' ? this.name : JSON.stringify(this.name)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["id","name","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): ObjectArrayItem { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new ObjectArrayItem({} as any); + + if (obj["id"] !== undefined) { + instance.id = obj["id"]; + } + if (obj["name"] !== undefined) { + instance.name = obj["name"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["id","name","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}}}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { ObjectArrayItem }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectWithDefaults.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectWithDefaults.ts new file mode 100644 index 00000000..9543b84e --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectWithDefaults.ts @@ -0,0 +1,98 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class ObjectWithDefaults { + private _status?: string; + private _count?: number; + private _enabled?: boolean; + private _additionalProperties?: Record; + + constructor(input: { + status?: string, + count?: number, + enabled?: boolean, + additionalProperties?: Record, + }) { + this._status = input.status; + this._count = input.count; + this._enabled = input.enabled; + this._additionalProperties = input.additionalProperties; + } + + get status(): string | undefined { return this._status; } + set status(status: string | undefined) { this._status = status; } + + get count(): number | undefined { return this._count; } + set count(count: number | undefined) { this._count = count; } + + get enabled(): boolean | undefined { return this._enabled; } + set enabled(enabled: boolean | undefined) { this._enabled = enabled; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.status !== undefined) { + json += `"status": ${typeof this.status === 'number' || typeof this.status === 'boolean' ? this.status : JSON.stringify(this.status)},`; + } + if(this.count !== undefined) { + json += `"count": ${typeof this.count === 'number' || typeof this.count === 'boolean' ? this.count : JSON.stringify(this.count)},`; + } + if(this.enabled !== undefined) { + json += `"enabled": ${typeof this.enabled === 'number' || typeof this.enabled === 'boolean' ? this.enabled : JSON.stringify(this.enabled)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["status","count","enabled","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): ObjectWithDefaults { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new ObjectWithDefaults({} as any); + + if (obj["status"] !== undefined) { + instance.status = obj["status"]; + } + if (obj["count"] !== undefined) { + instance.count = obj["count"]; + } + if (obj["enabled"] !== undefined) { + instance.enabled = obj["enabled"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["status","count","enabled","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"status":{"type":"string","default":"pending"},"count":{"type":"integer","default":0},"enabled":{"type":"boolean","default":true}},"description":"Object with default values","$id":"ObjectWithDefaults"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { ObjectWithDefaults }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectWithFormats.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectWithFormats.ts new file mode 100644 index 00000000..e596f662 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectWithFormats.ts @@ -0,0 +1,134 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class ObjectWithFormats { + private _email?: string; + private _website?: string; + private _userId?: string; + private _birthDate?: Date; + private _lastLogin?: Date; + private _serverIp?: string; + private _additionalProperties?: Record; + + constructor(input: { + email?: string, + website?: string, + userId?: string, + birthDate?: Date, + lastLogin?: Date, + serverIp?: string, + additionalProperties?: Record, + }) { + this._email = input.email; + this._website = input.website; + this._userId = input.userId; + this._birthDate = input.birthDate; + this._lastLogin = input.lastLogin; + this._serverIp = input.serverIp; + this._additionalProperties = input.additionalProperties; + } + + get email(): string | undefined { return this._email; } + set email(email: string | undefined) { this._email = email; } + + get website(): string | undefined { return this._website; } + set website(website: string | undefined) { this._website = website; } + + get userId(): string | undefined { return this._userId; } + set userId(userId: string | undefined) { this._userId = userId; } + + get birthDate(): Date | undefined { return this._birthDate; } + set birthDate(birthDate: Date | undefined) { this._birthDate = birthDate; } + + get lastLogin(): Date | undefined { return this._lastLogin; } + set lastLogin(lastLogin: Date | undefined) { this._lastLogin = lastLogin; } + + get serverIp(): string | undefined { return this._serverIp; } + set serverIp(serverIp: string | undefined) { this._serverIp = serverIp; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.email !== undefined) { + json += `"email": ${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},`; + } + if(this.website !== undefined) { + json += `"website": ${typeof this.website === 'number' || typeof this.website === 'boolean' ? this.website : JSON.stringify(this.website)},`; + } + if(this.userId !== undefined) { + json += `"userId": ${typeof this.userId === 'number' || typeof this.userId === 'boolean' ? this.userId : JSON.stringify(this.userId)},`; + } + if(this.birthDate !== undefined) { + json += `"birthDate": ${typeof this.birthDate === 'number' || typeof this.birthDate === 'boolean' ? this.birthDate : JSON.stringify(this.birthDate)},`; + } + if(this.lastLogin !== undefined) { + json += `"lastLogin": ${typeof this.lastLogin === 'number' || typeof this.lastLogin === 'boolean' ? this.lastLogin : JSON.stringify(this.lastLogin)},`; + } + if(this.serverIp !== undefined) { + json += `"serverIp": ${typeof this.serverIp === 'number' || typeof this.serverIp === 'boolean' ? this.serverIp : JSON.stringify(this.serverIp)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["email","website","userId","birthDate","lastLogin","serverIp","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): ObjectWithFormats { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new ObjectWithFormats({} as any); + + if (obj["email"] !== undefined) { + instance.email = obj["email"]; + } + if (obj["website"] !== undefined) { + instance.website = obj["website"]; + } + if (obj["userId"] !== undefined) { + instance.userId = obj["userId"]; + } + if (obj["birthDate"] !== undefined) { + instance.birthDate = obj["birthDate"] == null ? undefined : new Date(obj["birthDate"]); + } + if (obj["lastLogin"] !== undefined) { + instance.lastLogin = obj["lastLogin"] == null ? undefined : new Date(obj["lastLogin"]); + } + if (obj["serverIp"] !== undefined) { + instance.serverIp = obj["serverIp"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["email","website","userId","birthDate","lastLogin","serverIp","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"email":{"type":"string","format":"email"},"website":{"type":"string","format":"uri"},"userId":{"type":"string","format":"uuid"},"birthDate":{"type":"string","format":"date"},"lastLogin":{"type":"string","format":"date-time"},"serverIp":{"type":"string","format":"ipv4"}},"description":"Object with multiple format-validated properties","$id":"ObjectWithFormats"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { ObjectWithFormats }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectWithOptional.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectWithOptional.ts new file mode 100644 index 00000000..899b1168 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectWithOptional.ts @@ -0,0 +1,98 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class ObjectWithOptional { + private _field1?: string; + private _field2?: string; + private _field3?: string; + private _additionalProperties?: Record; + + constructor(input: { + field1?: string, + field2?: string, + field3?: string, + additionalProperties?: Record, + }) { + this._field1 = input.field1; + this._field2 = input.field2; + this._field3 = input.field3; + this._additionalProperties = input.additionalProperties; + } + + get field1(): string | undefined { return this._field1; } + set field1(field1: string | undefined) { this._field1 = field1; } + + get field2(): string | undefined { return this._field2; } + set field2(field2: string | undefined) { this._field2 = field2; } + + get field3(): string | undefined { return this._field3; } + set field3(field3: string | undefined) { this._field3 = field3; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.field1 !== undefined) { + json += `"field1": ${typeof this.field1 === 'number' || typeof this.field1 === 'boolean' ? this.field1 : JSON.stringify(this.field1)},`; + } + if(this.field2 !== undefined) { + json += `"field2": ${typeof this.field2 === 'number' || typeof this.field2 === 'boolean' ? this.field2 : JSON.stringify(this.field2)},`; + } + if(this.field3 !== undefined) { + json += `"field3": ${typeof this.field3 === 'number' || typeof this.field3 === 'boolean' ? this.field3 : JSON.stringify(this.field3)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["field1","field2","field3","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): ObjectWithOptional { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new ObjectWithOptional({} as any); + + if (obj["field1"] !== undefined) { + instance.field1 = obj["field1"]; + } + if (obj["field2"] !== undefined) { + instance.field2 = obj["field2"]; + } + if (obj["field3"] !== undefined) { + instance.field3 = obj["field3"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["field1","field2","field3","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"field1":{"type":"string"},"field2":{"type":"string"},"field3":{"type":"string"}},"description":"Object with all optional properties","$id":"ObjectWithOptional"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { ObjectWithOptional }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectWithRequired.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectWithRequired.ts new file mode 100644 index 00000000..2077aaca --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectWithRequired.ts @@ -0,0 +1,98 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class ObjectWithRequired { + private _id: string; + private _name: string; + private _optionalField?: string; + private _additionalProperties?: Record; + + constructor(input: { + id: string, + name: string, + optionalField?: string, + additionalProperties?: Record, + }) { + this._id = input.id; + this._name = input.name; + this._optionalField = input.optionalField; + this._additionalProperties = input.additionalProperties; + } + + get id(): string { return this._id; } + set id(id: string) { this._id = id; } + + get name(): string { return this._name; } + set name(name: string) { this._name = name; } + + get optionalField(): string | undefined { return this._optionalField; } + set optionalField(optionalField: string | undefined) { this._optionalField = optionalField; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.id !== undefined) { + json += `"id": ${typeof this.id === 'number' || typeof this.id === 'boolean' ? this.id : JSON.stringify(this.id)},`; + } + if(this.name !== undefined) { + json += `"name": ${typeof this.name === 'number' || typeof this.name === 'boolean' ? this.name : JSON.stringify(this.name)},`; + } + if(this.optionalField !== undefined) { + json += `"optional_field": ${typeof this.optionalField === 'number' || typeof this.optionalField === 'boolean' ? this.optionalField : JSON.stringify(this.optionalField)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["id","name","optional_field","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): ObjectWithRequired { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new ObjectWithRequired({} as any); + + if (obj["id"] !== undefined) { + instance.id = obj["id"]; + } + if (obj["name"] !== undefined) { + instance.name = obj["name"]; + } + if (obj["optional_field"] !== undefined) { + instance.optionalField = obj["optional_field"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["id","name","optional_field","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"id":{"type":"string"},"name":{"type":"string"},"optional_field":{"type":"string"}},"required":["id","name"],"description":"Object with required and optional properties","$id":"ObjectWithRequired"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { ObjectWithRequired }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/OneOfThreeTypes.ts b/test/runtime/typescript/src/payload-types/payloads/OneOfThreeTypes.ts new file mode 100644 index 00000000..ab75d58e --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/OneOfThreeTypes.ts @@ -0,0 +1,41 @@ +import {OneOfThreeTypesOneOfOption2} from './OneOfThreeTypesOneOfOption2'; +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type OneOfThreeTypes = string | number | OneOfThreeTypesOneOfOption2; + +export function unmarshal(json: any): OneOfThreeTypes { + + return JSON.parse(json); +} +export function marshal(payload: OneOfThreeTypes) { + + +if(payload instanceof OneOfThreeTypesOneOfOption2) { +return payload.marshal(); +} + return JSON.stringify(payload); +} + +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"string"},{"type":"integer"},{"type":"object","properties":{"value":{"type":"string"}}}],"description":"OneOf with three type options","$id":"OneOfThreeTypes"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { OneOfThreeTypes }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/OneOfThreeTypesOneOfOption2.ts b/test/runtime/typescript/src/payload-types/payloads/OneOfThreeTypesOneOfOption2.ts new file mode 100644 index 00000000..31c65654 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/OneOfThreeTypesOneOfOption2.ts @@ -0,0 +1,74 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class OneOfThreeTypesOneOfOption2 { + private _value?: string; + private _additionalProperties?: Record; + + constructor(input: { + value?: string, + additionalProperties?: Record, + }) { + this._value = input.value; + this._additionalProperties = input.additionalProperties; + } + + get value(): string | undefined { return this._value; } + set value(value: string | undefined) { this._value = value; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.value !== undefined) { + json += `"value": ${typeof this.value === 'number' || typeof this.value === 'boolean' ? this.value : JSON.stringify(this.value)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["value","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): OneOfThreeTypesOneOfOption2 { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new OneOfThreeTypesOneOfOption2({} as any); + + if (obj["value"] !== undefined) { + instance.value = obj["value"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["value","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","properties":{"value":{"type":"string"}}}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { OneOfThreeTypesOneOfOption2 }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/OneOfTwoTypes.ts b/test/runtime/typescript/src/payload-types/payloads/OneOfTwoTypes.ts new file mode 100644 index 00000000..e4e98a4d --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/OneOfTwoTypes.ts @@ -0,0 +1,37 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type OneOfTwoTypes = string | number; + +export function unmarshal(json: any): OneOfTwoTypes { + + return JSON.parse(json); +} +export function marshal(payload: OneOfTwoTypes) { + + + return JSON.stringify(payload); +} + +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"string"},{"type":"integer"}],"description":"OneOf with string or integer","$id":"OneOfTwoTypes"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { OneOfTwoTypes }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/OneOfWithDiscriminator.ts b/test/runtime/typescript/src/payload-types/payloads/OneOfWithDiscriminator.ts new file mode 100644 index 00000000..9e969cf3 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/OneOfWithDiscriminator.ts @@ -0,0 +1,50 @@ +import {DogPayload} from './DogPayload'; +import {CatPayload} from './CatPayload'; +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type OneOfWithDiscriminator = DogPayload | CatPayload; + +export function unmarshal(json: any): OneOfWithDiscriminator { + if(typeof json === 'object') { + if(json.petType === 'dog') { + return DogPayload.unmarshal(json); + } + if(json.petType === 'cat') { + return CatPayload.unmarshal(json); + } + } + return JSON.parse(json); +} +export function marshal(payload: OneOfWithDiscriminator) { + if(payload instanceof DogPayload) { +return payload.marshal(); +} +if(payload instanceof CatPayload) { +return payload.marshal(); +} + return JSON.stringify(payload); +} + +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"petType":{"const":"dog"},"breed":{"type":"string"},"barkVolume":{"type":"integer"}},"required":["petType"]},{"type":"object","properties":{"petType":{"const":"cat"},"breed":{"type":"string"},"meowPitch":{"type":"string"}},"required":["petType"]}],"description":"OneOf with discriminator property","$id":"OneOfWithDiscriminator"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { OneOfWithDiscriminator }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/SimpleObject.ts b/test/runtime/typescript/src/payload-types/payloads/SimpleObject.ts new file mode 100644 index 00000000..78c6ec4c --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/SimpleObject.ts @@ -0,0 +1,86 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +class SimpleObject { + private _name?: string; + private _age?: number; + private _additionalProperties?: Record; + + constructor(input: { + name?: string, + age?: number, + additionalProperties?: Record, + }) { + this._name = input.name; + this._age = input.age; + this._additionalProperties = input.additionalProperties; + } + + get name(): string | undefined { return this._name; } + set name(name: string | undefined) { this._name = name; } + + get age(): number | undefined { return this._age; } + set age(age: number | undefined) { this._age = age; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.name !== undefined) { + json += `"name": ${typeof this.name === 'number' || typeof this.name === 'boolean' ? this.name : JSON.stringify(this.name)},`; + } + if(this.age !== undefined) { + json += `"age": ${typeof this.age === 'number' || typeof this.age === 'boolean' ? this.age : JSON.stringify(this.age)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["name","age","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): SimpleObject { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new SimpleObject({} as any); + + if (obj["name"] !== undefined) { + instance.name = obj["name"]; + } + if (obj["age"] !== undefined) { + instance.age = obj["age"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["name","age","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"name":{"type":"string"},"age":{"type":"integer"}},"description":"Simple object with two properties","$id":"SimpleObject"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { SimpleObject }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/SingleStringEnum.ts b/test/runtime/typescript/src/payload-types/payloads/SingleStringEnum.ts new file mode 100644 index 00000000..3d2a1c58 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/SingleStringEnum.ts @@ -0,0 +1,5 @@ + +enum SingleStringEnum { + ACTIVE = "active", +} +export { SingleStringEnum }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/StringArray.ts b/test/runtime/typescript/src/payload-types/payloads/StringArray.ts new file mode 100644 index 00000000..2d414b59 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/StringArray.ts @@ -0,0 +1,36 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type StringArray = string[]; + +export function unmarshal(json: string | any[]): StringArray { + if (typeof json === 'string') { + return JSON.parse(json) as StringArray; + } + return json as StringArray; +} +export function marshal(payload: StringArray): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"array","$schema":"http://json-schema.org/draft-07/schema","items":{"type":"string"},"description":"Array of strings","$id":"StringArray"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { StringArray }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/StringPlain.ts b/test/runtime/typescript/src/payload-types/payloads/StringPlain.ts new file mode 100644 index 00000000..ee648a34 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/StringPlain.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type StringPlain = string; + +export function unmarshal(json: string): StringPlain { + return JSON.parse(json) as StringPlain; +} +export function marshal(payload: StringPlain): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","description":"A plain string type","$id":"StringPlain"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { StringPlain }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/StringWithMaxLength.ts b/test/runtime/typescript/src/payload-types/payloads/StringWithMaxLength.ts new file mode 100644 index 00000000..7a21046b --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/StringWithMaxLength.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type StringWithMaxLength = string; + +export function unmarshal(json: string): StringWithMaxLength { + return JSON.parse(json) as StringWithMaxLength; +} +export function marshal(payload: StringWithMaxLength): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","maxLength":50,"description":"String with maximum length of 50","$id":"StringWithMaxLength"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { StringWithMaxLength }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/StringWithMinLength.ts b/test/runtime/typescript/src/payload-types/payloads/StringWithMinLength.ts new file mode 100644 index 00000000..9edc0735 --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/StringWithMinLength.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type StringWithMinLength = string; + +export function unmarshal(json: string): StringWithMinLength { + return JSON.parse(json) as StringWithMinLength; +} +export function marshal(payload: StringWithMinLength): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","minLength":3,"description":"String with minimum length of 3","$id":"StringWithMinLength"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { StringWithMinLength }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/StringWithPattern.ts b/test/runtime/typescript/src/payload-types/payloads/StringWithPattern.ts new file mode 100644 index 00000000..c25cbeae --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/StringWithPattern.ts @@ -0,0 +1,33 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type StringWithPattern = string; + +export function unmarshal(json: string): StringWithPattern { + return JSON.parse(json) as StringWithPattern; +} +export function marshal(payload: StringWithPattern): string { + return JSON.stringify(payload); +} +export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","pattern":"^[A-Z][a-z]+$","description":"String matching capitalized word pattern","$id":"StringWithPattern"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { StringWithPattern }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payload-types/payloads/TupleArray.ts b/test/runtime/typescript/src/payload-types/payloads/TupleArray.ts new file mode 100644 index 00000000..18ae4b8a --- /dev/null +++ b/test/runtime/typescript/src/payload-types/payloads/TupleArray.ts @@ -0,0 +1,41 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import addFormats from 'ajv-formats'; +type TupleArray = (string | number | boolean | any)[]; + +export function unmarshal(json: string | any[]): TupleArray { + if (typeof json === 'string') { + return JSON.parse(json) as TupleArray; + } + return json as TupleArray; +} +export function marshal(payload: TupleArray): string { + return JSON.stringify(payload.map((item) => { + if (item && typeof item === 'object' && 'marshal' in item && typeof item.marshal === 'function') { + return JSON.parse(item.marshal()); + } + return item; + })); +} +export const theCodeGenSchema = {"type":"array","$schema":"http://json-schema.org/draft-07/schema","items":[{"type":"string"},{"type":"integer"},{"type":"boolean"}],"description":"Tuple with fixed position types","$id":"TupleArray"}; +export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; +} +export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(theCodeGenSchema); + return validate; +} + + +export { TupleArray }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payloads/ArrayMessage.ts b/test/runtime/typescript/src/payloads/ArrayMessage.ts index 32762a49..94834122 100644 --- a/test/runtime/typescript/src/payloads/ArrayMessage.ts +++ b/test/runtime/typescript/src/payloads/ArrayMessage.ts @@ -14,6 +14,9 @@ export function marshal(payload: ArrayMessage): string { export const theCodeGenSchema = {"type":"array","$schema":"http://json-schema.org/draft-07/schema","items":{"type":"string"},"description":"An array of strings payload","$id":"ArrayMessage"}; export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? createValidator(context) return { diff --git a/test/runtime/typescript/src/payloads/StringMessage.ts b/test/runtime/typescript/src/payloads/StringMessage.ts index e651fe4c..0e802d73 100644 --- a/test/runtime/typescript/src/payloads/StringMessage.ts +++ b/test/runtime/typescript/src/payloads/StringMessage.ts @@ -11,6 +11,9 @@ export function marshal(payload: StringMessage): string { export const theCodeGenSchema = {"type":"string","$schema":"http://json-schema.org/draft-07/schema","description":"A simple string payload","$id":"StringMessage"}; export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? createValidator(context) return { diff --git a/test/runtime/typescript/src/payloads/UnionMessage.ts b/test/runtime/typescript/src/payloads/UnionMessage.ts index 71b4060d..2be755a3 100644 --- a/test/runtime/typescript/src/payloads/UnionMessage.ts +++ b/test/runtime/typescript/src/payloads/UnionMessage.ts @@ -1,7 +1,7 @@ -import {AnonymousSchema_9} from './AnonymousSchema_9'; +import {UnionPayloadOneOfOption2} from './UnionPayloadOneOfOption2'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import addFormats from 'ajv-formats'; -type UnionMessage = string | number | AnonymousSchema_9; +type UnionMessage = string | number | UnionPayloadOneOfOption2; export function unmarshal(json: any): UnionMessage { @@ -10,15 +10,18 @@ export function unmarshal(json: any): UnionMessage { export function marshal(payload: UnionMessage) { -if(payload instanceof AnonymousSchema_9) { +if(payload instanceof UnionPayloadOneOfOption2) { return payload.marshal(); } return JSON.stringify(payload); } -export const theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"string"},{"type":"number"},{"type":"object","properties":{"name":{"type":"string"}}}],"description":"A union type payload","$id":"UnionMessage"}; +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"string"},{"type":"number"},{"type":"object","properties":{"name":{"type":"string"}}}],"description":"A union type payload","$id":"UnionMessage"}; export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? createValidator(context) return { diff --git a/test/runtime/typescript/src/payloads/AnonymousSchema_9.ts b/test/runtime/typescript/src/payloads/UnionPayloadOneOfOption2.ts similarity index 86% rename from test/runtime/typescript/src/payloads/AnonymousSchema_9.ts rename to test/runtime/typescript/src/payloads/UnionPayloadOneOfOption2.ts index 911067ff..3f7165c6 100644 --- a/test/runtime/typescript/src/payloads/AnonymousSchema_9.ts +++ b/test/runtime/typescript/src/payloads/UnionPayloadOneOfOption2.ts @@ -1,6 +1,6 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import addFormats from 'ajv-formats'; -class AnonymousSchema_9 { +class UnionPayloadOneOfOption2 { private _name?: string; private _additionalProperties?: Record; @@ -34,9 +34,9 @@ class AnonymousSchema_9 { return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; } - public static unmarshal(json: string | object): AnonymousSchema_9 { + public static unmarshal(json: string | object): UnionPayloadOneOfOption2 { const obj = typeof json === "object" ? json : JSON.parse(json); - const instance = new AnonymousSchema_9({} as any); + const instance = new UnionPayloadOneOfOption2({} as any); if (obj["name"] !== undefined) { instance.name = obj["name"]; @@ -52,6 +52,9 @@ class AnonymousSchema_9 { public static theCodeGenSchema = {"type":"object","properties":{"name":{"type":"string"}}}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { @@ -68,4 +71,4 @@ class AnonymousSchema_9 { } } -export { AnonymousSchema_9 }; \ No newline at end of file +export { UnionPayloadOneOfOption2 }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payloads/UserSignedUp.ts b/test/runtime/typescript/src/payloads/UserSignedUp.ts index 1ad4f615..caff7291 100644 --- a/test/runtime/typescript/src/payloads/UserSignedUp.ts +++ b/test/runtime/typescript/src/payloads/UserSignedUp.ts @@ -64,6 +64,9 @@ class UserSignedUp { public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"display_name":{"type":"string","description":"Name of the user"},"email":{"type":"string","format":"email","description":"Email of the user"}},"$id":"UserSignedUp"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/request-reply/headers/ItemRequestHeaders.ts b/test/runtime/typescript/src/request-reply/headers/ItemRequestHeaders.ts index b4320a4d..7255c457 100644 --- a/test/runtime/typescript/src/request-reply/headers/ItemRequestHeaders.ts +++ b/test/runtime/typescript/src/request-reply/headers/ItemRequestHeaders.ts @@ -70,6 +70,9 @@ class ItemRequestHeaders { public static theCodeGenSchema = {"type":"object","properties":{"x-correlation-id":{"type":"string","description":"Correlation ID for request tracing"},"x-request-id":{"type":"string","description":"Unique request identifier"}},"required":["x-correlation-id"],"$id":"ItemRequestHeaders","$schema":"http://json-schema.org/draft-07/schema"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/request-reply/payloads/GetUserItemReplyPayload.ts b/test/runtime/typescript/src/request-reply/payloads/GetUserItemReplyPayload.ts index 8c3d5058..a53c8fec 100644 --- a/test/runtime/typescript/src/request-reply/payloads/GetUserItemReplyPayload.ts +++ b/test/runtime/typescript/src/request-reply/payloads/GetUserItemReplyPayload.ts @@ -27,9 +27,12 @@ export function unmarshalByStatusCode(json: any, statusCode: number): GetUserIte } throw new Error(`No matching type found for status code: ${statusCode}`); } -export const theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"id":{"type":"string","description":"The item ID"},"userId":{"type":"string","description":"Owner user ID"},"name":{"type":"string","description":"Name of the item"},"description":{"type":"string","description":"Item description"},"quantity":{"type":"integer","description":"Item quantity"}},"$id":"itemResponse"},{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Error code"}},"$id":"notFound"}],"$id":"GetUserItemReplyPayload"}; +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"id":{"type":"string","description":"The item ID"},"userId":{"type":"string","description":"Owner user ID"},"name":{"type":"string","description":"Name of the item"},"description":{"type":"string","description":"Item description"},"quantity":{"type":"integer","description":"Item quantity"}},"$id":"itemResponse"},{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Error code"}},"$id":"notFound"}],"$id":"GetUserItemReplyPayload"}; export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? createValidator(context) return { diff --git a/test/runtime/typescript/src/request-reply/payloads/ItemRequest.ts b/test/runtime/typescript/src/request-reply/payloads/ItemRequest.ts index c1704ecc..25d0a38a 100644 --- a/test/runtime/typescript/src/request-reply/payloads/ItemRequest.ts +++ b/test/runtime/typescript/src/request-reply/payloads/ItemRequest.ts @@ -76,6 +76,9 @@ class ItemRequest { public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"name":{"type":"string","description":"Name of the item"},"description":{"type":"string","description":"Item description"},"quantity":{"type":"integer","description":"Item quantity"}},"required":["name"],"$id":"itemRequest"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/request-reply/payloads/ItemResponse.ts b/test/runtime/typescript/src/request-reply/payloads/ItemResponse.ts index b4b83f0c..ac709d5e 100644 --- a/test/runtime/typescript/src/request-reply/payloads/ItemResponse.ts +++ b/test/runtime/typescript/src/request-reply/payloads/ItemResponse.ts @@ -100,6 +100,9 @@ class ItemResponse { public static theCodeGenSchema = {"type":"object","properties":{"id":{"type":"string","description":"The item ID"},"userId":{"type":"string","description":"Owner user ID"},"name":{"type":"string","description":"Name of the item"},"description":{"type":"string","description":"Item description"},"quantity":{"type":"integer","description":"Item quantity"}},"$id":"itemResponse"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/request-reply/payloads/MultiStatusResponseReplyPayload.ts b/test/runtime/typescript/src/request-reply/payloads/MultiStatusResponseReplyPayload.ts index b3afa687..356932de 100644 --- a/test/runtime/typescript/src/request-reply/payloads/MultiStatusResponseReplyPayload.ts +++ b/test/runtime/typescript/src/request-reply/payloads/MultiStatusResponseReplyPayload.ts @@ -27,9 +27,12 @@ export function unmarshalByStatusCode(json: any, statusCode: number): MultiStatu } throw new Error(`No matching type found for status code: ${statusCode}`); } -export const theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"pong":{"type":"string","description":"pong name"}},"$id":"pong"},{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Error code"}},"$id":"notFound"}],"$id":"MultiStatusResponseReplyPayload"}; +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"pong":{"type":"string","description":"pong name"}},"$id":"pong"},{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Error code"}},"$id":"notFound"}],"$id":"MultiStatusResponseReplyPayload"}; export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? createValidator(context) return { diff --git a/test/runtime/typescript/src/request-reply/payloads/NotFound.ts b/test/runtime/typescript/src/request-reply/payloads/NotFound.ts index e845b836..cc19d241 100644 --- a/test/runtime/typescript/src/request-reply/payloads/NotFound.ts +++ b/test/runtime/typescript/src/request-reply/payloads/NotFound.ts @@ -64,6 +64,9 @@ class NotFound { public static theCodeGenSchema = {"type":"object","properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Error code"}},"$id":"notFound"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/request-reply/payloads/Ping.ts b/test/runtime/typescript/src/request-reply/payloads/Ping.ts index abf94274..3860be78 100644 --- a/test/runtime/typescript/src/request-reply/payloads/Ping.ts +++ b/test/runtime/typescript/src/request-reply/payloads/Ping.ts @@ -52,6 +52,9 @@ class Ping { public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"ping":{"type":"string","description":"ping name"}},"$id":"ping"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/request-reply/payloads/PingPayload.ts b/test/runtime/typescript/src/request-reply/payloads/PingPayload.ts index e8c62509..a7f2c9ff 100644 --- a/test/runtime/typescript/src/request-reply/payloads/PingPayload.ts +++ b/test/runtime/typescript/src/request-reply/payloads/PingPayload.ts @@ -31,9 +31,12 @@ export function unmarshalByStatusCode(json: any, statusCode: number): PingPayloa } throw new Error(`No matching type found for status code: ${statusCode}`); } -export const theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"ping":{"type":"string","description":"ping name"}},"$id":"ping"},{"type":"object","properties":{"pong":{"type":"string","description":"pong name"}},"$id":"pong"},{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Error code"}},"$id":"notFound"}],"$id":"PingPayload"}; +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"ping":{"type":"string","description":"ping name"}},"$id":"ping"},{"type":"object","properties":{"pong":{"type":"string","description":"pong name"}},"$id":"pong"},{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Error code"}},"$id":"notFound"}],"$id":"PingPayload"}; export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? createValidator(context) return { diff --git a/test/runtime/typescript/src/request-reply/payloads/Pong.ts b/test/runtime/typescript/src/request-reply/payloads/Pong.ts index e57f7316..3ebf453d 100644 --- a/test/runtime/typescript/src/request-reply/payloads/Pong.ts +++ b/test/runtime/typescript/src/request-reply/payloads/Pong.ts @@ -52,6 +52,9 @@ class Pong { public static theCodeGenSchema = {"type":"object","properties":{"pong":{"type":"string","description":"pong name"}},"$id":"pong"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? this.createValidator(context) return { diff --git a/test/runtime/typescript/src/request-reply/payloads/UpdateUserItemReplyPayload.ts b/test/runtime/typescript/src/request-reply/payloads/UpdateUserItemReplyPayload.ts index c2faf9b9..1e8a45f8 100644 --- a/test/runtime/typescript/src/request-reply/payloads/UpdateUserItemReplyPayload.ts +++ b/test/runtime/typescript/src/request-reply/payloads/UpdateUserItemReplyPayload.ts @@ -27,9 +27,12 @@ export function unmarshalByStatusCode(json: any, statusCode: number): UpdateUser } throw new Error(`No matching type found for status code: ${statusCode}`); } -export const theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"id":{"type":"string","description":"The item ID"},"userId":{"type":"string","description":"Owner user ID"},"name":{"type":"string","description":"Name of the item"},"description":{"type":"string","description":"Item description"},"quantity":{"type":"integer","description":"Item quantity"}},"$id":"itemResponse"},{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Error code"}},"$id":"notFound"}],"$id":"UpdateUserItemReplyPayload"}; +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"id":{"type":"string","description":"The item ID"},"userId":{"type":"string","description":"Owner user ID"},"name":{"type":"string","description":"Name of the item"},"description":{"type":"string","description":"Item description"},"quantity":{"type":"integer","description":"Item quantity"}},"$id":"itemResponse"},{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Error code"}},"$id":"notFound"}],"$id":"UpdateUserItemReplyPayload"}; export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? createValidator(context) return { diff --git a/test/runtime/typescript/src/request-reply/payloads/UserItemsPayload.ts b/test/runtime/typescript/src/request-reply/payloads/UserItemsPayload.ts index cdb93c5d..86d85cbf 100644 --- a/test/runtime/typescript/src/request-reply/payloads/UserItemsPayload.ts +++ b/test/runtime/typescript/src/request-reply/payloads/UserItemsPayload.ts @@ -31,9 +31,12 @@ export function unmarshalByStatusCode(json: any, statusCode: number): UserItemsP } throw new Error(`No matching type found for status code: ${statusCode}`); } -export const theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"name":{"type":"string","description":"Name of the item"},"description":{"type":"string","description":"Item description"},"quantity":{"type":"integer","description":"Item quantity"}},"required":["name"],"$id":"itemRequest"},{"type":"object","properties":{"id":{"type":"string","description":"The item ID"},"userId":{"type":"string","description":"Owner user ID"},"name":{"type":"string","description":"Name of the item"},"description":{"type":"string","description":"Item description"},"quantity":{"type":"integer","description":"Item quantity"}},"$id":"itemResponse"},{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Error code"}},"$id":"notFound"}],"$id":"UserItemsPayload"}; +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","properties":{"name":{"type":"string","description":"Name of the item"},"description":{"type":"string","description":"Item description"},"quantity":{"type":"integer","description":"Item quantity"}},"required":["name"],"$id":"itemRequest"},{"type":"object","properties":{"id":{"type":"string","description":"The item ID"},"userId":{"type":"string","description":"Owner user ID"},"name":{"type":"string","description":"Name of the item"},"description":{"type":"string","description":"Item description"},"quantity":{"type":"integer","description":"Item quantity"}},"$id":"itemResponse"},{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"code":{"type":"string","description":"Error code"}},"$id":"notFound"}],"$id":"UserItemsPayload"}; export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. const parsedData = typeof data === 'string' ? JSON.parse(data) : data; const validate = ajvValidatorFunction ?? createValidator(context) return { diff --git a/test/runtime/typescript/test/payload-types/arrays.spec.ts b/test/runtime/typescript/test/payload-types/arrays.spec.ts new file mode 100644 index 00000000..73df614b --- /dev/null +++ b/test/runtime/typescript/test/payload-types/arrays.spec.ts @@ -0,0 +1,297 @@ +/** + * Runtime tests for array payload types. + * Tests string arrays, object arrays, nested arrays, and array constraints. + * + * NOTE: Some issues discovered: + * - NestedArray unmarshal has a bug referencing undefined NestedArrayItem + */ +import * as StringArray from '../../src/payload-types/payloads/StringArray'; +import * as ObjectArray from '../../src/payload-types/payloads/ObjectArray'; +import * as NestedArray from '../../src/payload-types/payloads/NestedArray'; +import * as ArrayWithMinItems from '../../src/payload-types/payloads/ArrayWithMinItems'; +import * as ArrayWithMaxItems from '../../src/payload-types/payloads/ArrayWithMaxItems'; +import * as ArrayWithUniqueItems from '../../src/payload-types/payloads/ArrayWithUniqueItems'; +import * as TupleArray from '../../src/payload-types/payloads/TupleArray'; + +describe('Array Types', () => { + describe('StringArray', () => { + test('should marshal string array', () => { + const value: StringArray.StringArray = ['a', 'b', 'c']; + const serialized = StringArray.marshal(value); + expect(serialized).toBe('["a","b","c"]'); + }); + + test('should unmarshal string array from string', () => { + const result = StringArray.unmarshal('["x","y","z"]'); + expect(result).toEqual(['x', 'y', 'z']); + }); + + test('should unmarshal string array from array', () => { + const result = StringArray.unmarshal(['x', 'y', 'z']); + expect(result).toEqual(['x', 'y', 'z']); + }); + + test('should handle empty array', () => { + const value: StringArray.StringArray = []; + const serialized = StringArray.marshal(value); + expect(serialized).toBe('[]'); + const deserialized = StringArray.unmarshal(serialized); + expect(deserialized).toEqual([]); + }); + + test('should handle single item', () => { + const value: StringArray.StringArray = ['only']; + const serialized = StringArray.marshal(value); + expect(serialized).toBe('["only"]'); + }); + + test('should validate string array', () => { + const result = StringArray.validate({ data: ['a', 'b'] }); + expect(result.valid).toBe(true); + }); + + test('should validate empty array', () => { + const result = StringArray.validate({ data: [] }); + expect(result.valid).toBe(true); + }); + + test('should invalidate array with non-string items', () => { + const result = StringArray.validate({ data: [1, 2, 3] }); + expect(result.valid).toBe(false); + }); + + test('should invalidate non-array (pass as object)', () => { + // Pass object directly, not as string to avoid JSON.parse issue + const result = StringArray.validate({ data: { notAnArray: true } }); + expect(result.valid).toBe(false); + }); + }); + + describe('ObjectArray', () => { + test('should marshal object array', () => { + const value: ObjectArray.ObjectArray = [ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' } + ]; + const serialized = ObjectArray.marshal(value); + const parsed = JSON.parse(serialized); + expect(parsed).toHaveLength(2); + expect(parsed[0].id).toBe(1); + expect(parsed[1].name).toBe('Second'); + }); + + test('should unmarshal object array', () => { + const json = '[{"id":1,"name":"Test"},{"id":2,"name":"Other"}]'; + const result = ObjectArray.unmarshal(json); + expect(result).toHaveLength(2); + expect(result[0].id).toBe(1); + expect(result[1].name).toBe('Other'); + }); + + test('should handle empty object array', () => { + const value: ObjectArray.ObjectArray = []; + const serialized = ObjectArray.marshal(value); + expect(serialized).toBe('[]'); + }); + + test('should validate object array', () => { + const result = ObjectArray.validate({ + data: [ + { id: 1, name: 'A' }, + { id: 2, name: 'B' } + ] + }); + expect(result.valid).toBe(true); + }); + + test('should validate empty object array', () => { + const result = ObjectArray.validate({ data: [] }); + expect(result.valid).toBe(true); + }); + + test('should validate object with partial properties', () => { + const result = ObjectArray.validate({ + data: [{ id: 1 }, { name: 'B' }] + }); + expect(result.valid).toBe(true); + }); + }); + + describe('NestedArray', () => { + test('should marshal nested array', () => { + const value: NestedArray.NestedArray = [ + ['a', 'b'], + ['c', 'd', 'e'] + ]; + const serialized = NestedArray.marshal(value); + expect(serialized).toBe('[["a","b"],["c","d","e"]]'); + }); + + test('should unmarshal nested array', () => { + const json = '[["x","y"],["z"]]'; + const result = NestedArray.unmarshal(json); + expect(result).toEqual([['x', 'y'], ['z']]); + }); + + test('should handle empty nested array', () => { + const value: NestedArray.NestedArray = []; + const serialized = NestedArray.marshal(value); + expect(serialized).toBe('[]'); + }); + + test('should handle nested empty arrays', () => { + const value: NestedArray.NestedArray = [[], []]; + const serialized = NestedArray.marshal(value); + expect(serialized).toBe('[[],[]]'); + }); + + test('should validate nested array', () => { + const result = NestedArray.validate({ + data: [['a', 'b'], ['c']] + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate non-nested array', () => { + const result = NestedArray.validate({ + data: ['a', 'b', 'c'] // should be array of arrays + }); + expect(result.valid).toBe(false); + }); + }); + + describe('ArrayWithMinItems', () => { + test('should validate array meeting minItems', () => { + const result = ArrayWithMinItems.validate({ + data: ['item'] + }); + expect(result.valid).toBe(true); + }); + + test('should validate array exceeding minItems', () => { + const result = ArrayWithMinItems.validate({ + data: ['a', 'b', 'c'] + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate empty array', () => { + const result = ArrayWithMinItems.validate({ + data: [] + }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'minItems' }) + ]) + ); + }); + }); + + describe('ArrayWithMaxItems', () => { + test('should validate array within maxItems', () => { + const result = ArrayWithMaxItems.validate({ + data: ['a', 'b'] + }); + expect(result.valid).toBe(true); + }); + + test('should validate array at exact maxItems', () => { + const result = ArrayWithMaxItems.validate({ + data: ['1', '2', '3', '4', '5'] + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate array exceeding maxItems', () => { + const result = ArrayWithMaxItems.validate({ + data: ['1', '2', '3', '4', '5', '6'] + }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'maxItems' }) + ]) + ); + }); + + test('should validate empty array', () => { + const result = ArrayWithMaxItems.validate({ + data: [] + }); + expect(result.valid).toBe(true); + }); + }); + + describe('ArrayWithUniqueItems', () => { + test('should validate array with unique items', () => { + const result = ArrayWithUniqueItems.validate({ + data: ['a', 'b', 'c'] + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate array with duplicate items', () => { + const result = ArrayWithUniqueItems.validate({ + data: ['a', 'b', 'a'] + }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'uniqueItems' }) + ]) + ); + }); + + test('should validate empty array', () => { + const result = ArrayWithUniqueItems.validate({ + data: [] + }); + expect(result.valid).toBe(true); + }); + + test('should validate single item array', () => { + const result = ArrayWithUniqueItems.validate({ + data: ['only'] + }); + expect(result.valid).toBe(true); + }); + }); + + describe('TupleArray', () => { + test('should marshal tuple', () => { + const value: TupleArray.TupleArray = ['hello', 42, true]; + const serialized = TupleArray.marshal(value); + expect(serialized).toBe('["hello",42,true]'); + }); + + test('should unmarshal tuple', () => { + const json = '["test",123,false]'; + const result = TupleArray.unmarshal(json); + expect(result[0]).toBe('test'); + expect(result[1]).toBe(123); + expect(result[2]).toBe(false); + }); + + test('should validate correct tuple', () => { + const result = TupleArray.validate({ + data: ['string', 1, true] + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate tuple with wrong type order', () => { + const result = TupleArray.validate({ + data: [42, 'string', true] // number first instead of string + }); + expect(result.valid).toBe(false); + }); + + test('should invalidate tuple with wrong types', () => { + const result = TupleArray.validate({ + data: ['string', 'string', 'string'] // all strings + }); + expect(result.valid).toBe(false); + }); + }); +}); diff --git a/test/runtime/typescript/test/payload-types/composition.spec.ts b/test/runtime/typescript/test/payload-types/composition.spec.ts new file mode 100644 index 00000000..32d6d5fa --- /dev/null +++ b/test/runtime/typescript/test/payload-types/composition.spec.ts @@ -0,0 +1,364 @@ +/** + * Runtime tests for composition payload types. + * Tests oneOf, anyOf, allOf, and discriminated unions. + * + * NOTE: Some issues discovered: + * - oneOf schemas have conflicting "type":"object" with nested primitive types + */ +import * as OneOfTwoTypes from '../../src/payload-types/payloads/OneOfTwoTypes'; +import * as OneOfThreeTypes from '../../src/payload-types/payloads/OneOfThreeTypes'; +import * as OneOfWithDiscriminator from '../../src/payload-types/payloads/OneOfWithDiscriminator'; +import * as AnyOfTwoTypes from '../../src/payload-types/payloads/AnyOfTwoTypes'; +import { AllOfTwoTypes } from '../../src/payload-types/payloads/AllOfTwoTypes'; +import { AllOfThreeTypes } from '../../src/payload-types/payloads/AllOfThreeTypes'; + +describe('Composition Types', () => { + // oneOf schemas have conflicting "type":"object" at root with primitive types in oneOf + // The AJV strict mode warns about this, but validation still works + // We disable strict mode for these tests by passing { strict: false } to AJV + describe('OneOfTwoTypes (string | integer)', () => { + test('should marshal string value', () => { + const value: OneOfTwoTypes.OneOfTwoTypes = 'hello'; + const serialized = OneOfTwoTypes.marshal(value); + expect(serialized).toBe('"hello"'); + }); + + test('should marshal integer value', () => { + const value: OneOfTwoTypes.OneOfTwoTypes = 42; + const serialized = OneOfTwoTypes.marshal(value); + expect(serialized).toBe('42'); + }); + + test('should unmarshal string value', () => { + const result = OneOfTwoTypes.unmarshal('"test"'); + expect(result).toBe('test'); + }); + + test('should unmarshal integer value', () => { + const result = OneOfTwoTypes.unmarshal('123'); + expect(result).toBe(123); + }); + + test('should validate string', () => { + // Pass as JSON string since validate parses strings + const result = OneOfTwoTypes.validate({ + data: '"hello"', + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should validate integer', () => { + const result = OneOfTwoTypes.validate({ + data: 123, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate boolean (not in oneOf)', () => { + const result = OneOfTwoTypes.validate({ + data: true, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(false); + }); + + test('should invalidate array (not in oneOf)', () => { + const result = OneOfTwoTypes.validate({ + data: [1, 2, 3], + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(false); + }); + + test('should invalidate object (not in oneOf)', () => { + const result = OneOfTwoTypes.validate({ + data: { key: 'value' }, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(false); + }); + }); + + describe('OneOfThreeTypes (string | integer | object)', () => { + test('should marshal string value', () => { + const value: OneOfThreeTypes.OneOfThreeTypes = 'text'; + const serialized = OneOfThreeTypes.marshal(value); + expect(serialized).toBe('"text"'); + }); + + test('should marshal integer value', () => { + const value: OneOfThreeTypes.OneOfThreeTypes = 100; + const serialized = OneOfThreeTypes.marshal(value); + expect(serialized).toBe('100'); + }); + + test('should marshal object value', () => { + const value = { value: 'test' } as OneOfThreeTypes.OneOfThreeTypes; + const serialized = OneOfThreeTypes.marshal(value); + const parsed = JSON.parse(serialized); + expect(parsed.value).toBe('test'); + }); + + test('should validate string', () => { + // Pass as JSON string since validate parses strings + const result = OneOfThreeTypes.validate({ + data: '"hello"', + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should validate integer', () => { + const result = OneOfThreeTypes.validate({ + data: 100, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should validate object', () => { + const result = OneOfThreeTypes.validate({ + data: { value: 'test' }, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should validate empty object', () => { + const result = OneOfThreeTypes.validate({ + data: {}, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate boolean', () => { + const result = OneOfThreeTypes.validate({ + data: true, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(false); + }); + + test('should invalidate array', () => { + const result = OneOfThreeTypes.validate({ + data: [1, 2], + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(false); + }); + }); + + describe('OneOfWithDiscriminator (Dog | Cat)', () => { + test('should marshal dog payload', () => { + const value = { petType: 'dog', breed: 'Labrador', barkVolume: 8 } as OneOfWithDiscriminator.OneOfWithDiscriminator; + const serialized = OneOfWithDiscriminator.marshal(value); + const parsed = JSON.parse(serialized); + expect(parsed.petType).toBe('dog'); + expect(parsed.breed).toBe('Labrador'); + expect(parsed.barkVolume).toBe(8); + }); + + test('should marshal cat payload', () => { + const value = { petType: 'cat', breed: 'Persian', meowPitch: 'high' } as OneOfWithDiscriminator.OneOfWithDiscriminator; + const serialized = OneOfWithDiscriminator.marshal(value); + const parsed = JSON.parse(serialized); + expect(parsed.petType).toBe('cat'); + expect(parsed.breed).toBe('Persian'); + expect(parsed.meowPitch).toBe('high'); + }); + + test('should unmarshal dog payload', () => { + const json = '{"petType":"dog","breed":"Beagle","barkVolume":5}'; + const result = OneOfWithDiscriminator.unmarshal(json); + expect(result.petType).toBe('dog'); + }); + + test('should unmarshal cat payload', () => { + const json = '{"petType":"cat","breed":"Siamese","meowPitch":"low"}'; + const result = OneOfWithDiscriminator.unmarshal(json); + expect(result.petType).toBe('cat'); + }); + + test('should validate dog with discriminator', () => { + const result = OneOfWithDiscriminator.validate({ + data: { petType: 'dog', breed: 'Golden Retriever', barkVolume: 7 }, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should validate cat with discriminator', () => { + const result = OneOfWithDiscriminator.validate({ + data: { petType: 'cat', breed: 'Maine Coon', meowPitch: 'medium' }, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should validate minimal dog', () => { + const result = OneOfWithDiscriminator.validate({ + data: { petType: 'dog' }, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should validate minimal cat', () => { + const result = OneOfWithDiscriminator.validate({ + data: { petType: 'cat' }, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate unknown pet type', () => { + const result = OneOfWithDiscriminator.validate({ + data: { petType: 'bird' }, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(false); + }); + + test('should invalidate missing discriminator', () => { + const result = OneOfWithDiscriminator.validate({ + data: { breed: 'Unknown' }, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(false); + }); + }); + + describe('AnyOfTwoTypes (object with name | object with id)', () => { + test('should validate object with name', () => { + const result = AnyOfTwoTypes.validate({ + data: { name: 'Test' }, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with id', () => { + const result = AnyOfTwoTypes.validate({ + data: { id: 123 }, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with both name and id', () => { + const result = AnyOfTwoTypes.validate({ + data: { name: 'Test', id: 123 }, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + + test('should validate empty object', () => { + // anyOf with optional properties - empty object should match + const result = AnyOfTwoTypes.validate({ + data: {}, + ajvOptions: { strict: false } + }); + expect(result.valid).toBe(true); + }); + }); + + describe('AllOfTwoTypes (BaseEntity + TimestampMixin)', () => { + test('should marshal combined object', () => { + const obj = new AllOfTwoTypes({ + id: 'entity-1', + name: 'Test Entity', + createdAt: '2024-01-15T10:30:00Z', + updatedAt: '2024-01-16T15:45:00Z' + }); + const serialized = obj.marshal(); + const parsed = JSON.parse(serialized); + expect(parsed.id).toBe('entity-1'); + expect(parsed.name).toBe('Test Entity'); + }); + + test('should unmarshal combined object', () => { + const json = '{"id":"test-id","name":"Test","createdAt":"2024-01-01T00:00:00Z","updatedAt":"2024-01-02T00:00:00Z"}'; + const obj = AllOfTwoTypes.unmarshal(json); + expect(obj.id).toBe('test-id'); + expect(obj.name).toBe('Test'); + }); + + test('should validate object with all properties', () => { + const result = AllOfTwoTypes.validate({ + data: { + id: 'abc', + name: 'Test', + createdAt: '2024-01-15T10:30:00Z', + updatedAt: '2024-01-16T15:45:00Z' + } + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with required properties only', () => { + const result = AllOfTwoTypes.validate({ + data: { id: 'abc' } + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate object missing required id', () => { + const result = AllOfTwoTypes.validate({ + data: { name: 'Test' } + }); + expect(result.valid).toBe(false); + }); + }); + + describe('AllOfThreeTypes (BaseEntity + TimestampMixin + AuditMixin)', () => { + test('should marshal fully combined object', () => { + const obj = new AllOfThreeTypes({ + id: 'entity-2', + name: 'Full Entity', + createdAt: '2024-01-15T10:30:00Z', + updatedAt: '2024-01-16T15:45:00Z', + createdBy: 'user-1', + updatedBy: 'user-2' + }); + const serialized = obj.marshal(); + const parsed = JSON.parse(serialized); + expect(parsed.id).toBe('entity-2'); + expect(parsed.name).toBe('Full Entity'); + }); + + test('should validate object with all properties from all schemas', () => { + const result = AllOfThreeTypes.validate({ + data: { + id: 'abc', + name: 'Test', + createdAt: '2024-01-15T10:30:00Z', + updatedAt: '2024-01-16T15:45:00Z', + createdBy: 'admin', + updatedBy: 'user' + } + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with required only', () => { + const result = AllOfThreeTypes.validate({ + data: { id: 'abc' } + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with partial properties', () => { + const result = AllOfThreeTypes.validate({ + data: { + id: 'abc', + createdBy: 'system' + } + }); + expect(result.valid).toBe(true); + }); + }); +}); diff --git a/test/runtime/typescript/test/payload-types/enums.spec.ts b/test/runtime/typescript/test/payload-types/enums.spec.ts new file mode 100644 index 00000000..cb1462ad --- /dev/null +++ b/test/runtime/typescript/test/payload-types/enums.spec.ts @@ -0,0 +1,126 @@ +/** + * Runtime tests for enum payload types. + * + * NOTE: Enum schemas generate as TypeScript enums without marshal/unmarshal/validate + * functions. This file tests the type generation and enum values. + * See tracking.json ISS-001 for details. + */ +import { SingleStringEnum } from '../../src/payload-types/payloads/SingleStringEnum'; +import { MultipleStringEnum } from '../../src/payload-types/payloads/MultipleStringEnum'; +import { IntegerEnum } from '../../src/payload-types/payloads/IntegerEnum'; +import * as MixedTypeEnum from '../../src/payload-types/payloads/MixedTypeEnum'; +import * as NullableEnum from '../../src/payload-types/payloads/NullableEnum'; + +describe('Enum Types', () => { + describe('SingleStringEnum', () => { + test('should have the enum value defined', () => { + expect(SingleStringEnum.ACTIVE).toBe('active'); + }); + + test('should be usable as a type', () => { + const value: SingleStringEnum = SingleStringEnum.ACTIVE; + expect(value).toBe('active'); + }); + + test('should allow string comparison', () => { + const value = SingleStringEnum.ACTIVE; + expect(value === 'active').toBe(true); + }); + }); + + describe('MultipleStringEnum', () => { + test('should have all enum values defined', () => { + expect(MultipleStringEnum.PENDING).toBe('pending'); + expect(MultipleStringEnum.ACTIVE).toBe('active'); + expect(MultipleStringEnum.INACTIVE).toBe('inactive'); + expect(MultipleStringEnum.DELETED).toBe('deleted'); + }); + + test('should be usable as a type for each value', () => { + const pending: MultipleStringEnum = MultipleStringEnum.PENDING; + const active: MultipleStringEnum = MultipleStringEnum.ACTIVE; + const inactive: MultipleStringEnum = MultipleStringEnum.INACTIVE; + const deleted: MultipleStringEnum = MultipleStringEnum.DELETED; + + expect(pending).toBe('pending'); + expect(active).toBe('active'); + expect(inactive).toBe('inactive'); + expect(deleted).toBe('deleted'); + }); + + test('should have 4 enum members', () => { + const values = Object.values(MultipleStringEnum); + expect(values).toHaveLength(4); + }); + }); + + describe('IntegerEnum', () => { + test('should have all integer enum values defined', () => { + expect(IntegerEnum.NUMBER_1).toBe(1); + expect(IntegerEnum.NUMBER_2).toBe(2); + expect(IntegerEnum.NUMBER_3).toBe(3); + expect(IntegerEnum.NUMBER_4).toBe(4); + expect(IntegerEnum.NUMBER_5).toBe(5); + }); + + test('should be usable as a numeric type', () => { + const value: IntegerEnum = IntegerEnum.NUMBER_3; + expect(value).toBe(3); + expect(typeof value).toBe('number'); + }); + + test('should have 5 enum members', () => { + // For numeric enums, Object.values includes both keys and values + // Filter to only numeric values + const values = Object.values(IntegerEnum).filter(v => typeof v === 'number'); + expect(values).toHaveLength(5); + }); + }); + + // MixedTypeEnum converts non-string/number values to strings + // e.g., true becomes "true", 123 stays as 123, "string_value" stays as is + describe('MixedTypeEnum', () => { + test('should have string value', () => { + expect(MixedTypeEnum.MixedTypeEnum.STRING_VALUE).toBe('string_value'); + }); + + test('should have number value', () => { + expect(MixedTypeEnum.MixedTypeEnum.NUMBER_123).toBe(123); + }); + + test('should convert boolean true to string "true"', () => { + // TypeScript enums can't have boolean values, so true becomes "true" + expect(MixedTypeEnum.MixedTypeEnum.RESERVED_TRUE).toBe('true'); + }); + + test('should have 3 values', () => { + const values = Object.values(MixedTypeEnum.MixedTypeEnum); + expect(values).toContain('string_value'); + expect(values).toContain(123); + expect(values).toContain('true'); + }); + }); + + // NullableEnum converts null to string 'null' + describe('NullableEnum', () => { + test('should have option_a value', () => { + expect(NullableEnum.NullableEnum.OPTION_A).toBe('option_a'); + }); + + test('should have option_b value', () => { + expect(NullableEnum.NullableEnum.OPTION_B).toBe('option_b'); + }); + + test('should convert null to string "null"', () => { + // TypeScript enums can't have null values, so null becomes 'null' + expect(NullableEnum.NullableEnum.RESERVED_NULL).toBe('null'); + }); + + test('should have 3 values', () => { + const values = Object.values(NullableEnum.NullableEnum); + expect(values).toContain('option_a'); + expect(values).toContain('option_b'); + expect(values).toContain('null'); + }); + }); +}); diff --git a/test/runtime/typescript/test/payload-types/formats.spec.ts b/test/runtime/typescript/test/payload-types/formats.spec.ts new file mode 100644 index 00000000..666eb21b --- /dev/null +++ b/test/runtime/typescript/test/payload-types/formats.spec.ts @@ -0,0 +1,453 @@ +/** + * Runtime tests for format-validated payload types. + * Tests email, uri, uuid, date, date-time, time, hostname, ipv4, ipv6 formats. + */ +import * as FormatEmail from '../../src/payload-types/payloads/FormatEmail'; +import * as FormatUri from '../../src/payload-types/payloads/FormatUri'; +import * as FormatUuid from '../../src/payload-types/payloads/FormatUuid'; +import * as FormatDate from '../../src/payload-types/payloads/FormatDate'; +import * as FormatDateTime from '../../src/payload-types/payloads/FormatDateTime'; +import * as FormatTime from '../../src/payload-types/payloads/FormatTime'; +import * as FormatHostname from '../../src/payload-types/payloads/FormatHostname'; +import * as FormatIpv4 from '../../src/payload-types/payloads/FormatIpv4'; +import * as FormatIpv6 from '../../src/payload-types/payloads/FormatIpv6'; +import { ObjectWithFormats } from '../../src/payload-types/payloads/ObjectWithFormats'; + +describe('Format Validation', () => { + describe('FormatEmail', () => { + test('should marshal email', () => { + const value: FormatEmail.FormatEmail = 'user@example.com'; + const serialized = FormatEmail.marshal(value); + expect(serialized).toBe('"user@example.com"'); + }); + + test('should unmarshal email', () => { + const result = FormatEmail.unmarshal('"test@test.com"'); + expect(result).toBe('test@test.com'); + }); + + test('should validate correct email', () => { + const result = FormatEmail.validate({ data: '"user@example.com"' }); + expect(result.valid).toBe(true); + }); + + test('should validate email with subdomain', () => { + const result = FormatEmail.validate({ data: '"user@mail.example.com"' }); + expect(result.valid).toBe(true); + }); + + test('should validate email with plus sign', () => { + const result = FormatEmail.validate({ data: '"user+tag@example.com"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate email without @', () => { + const result = FormatEmail.validate({ data: '"userexample.com"' }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'format' }) + ]) + ); + }); + + test('should invalidate email without domain', () => { + const result = FormatEmail.validate({ data: '"user@"' }); + expect(result.valid).toBe(false); + }); + }); + + describe('FormatUri', () => { + test('should marshal URI', () => { + const value: FormatUri.FormatUri = 'https://example.com/path'; + const serialized = FormatUri.marshal(value); + expect(serialized).toBe('"https://example.com/path"'); + }); + + test('should validate https URI', () => { + const result = FormatUri.validate({ data: '"https://example.com"' }); + expect(result.valid).toBe(true); + }); + + test('should validate http URI', () => { + const result = FormatUri.validate({ data: '"http://example.com/path/to/resource"' }); + expect(result.valid).toBe(true); + }); + + test('should validate URI with query params', () => { + const result = FormatUri.validate({ data: '"https://example.com/search?q=test&page=1"' }); + expect(result.valid).toBe(true); + }); + + test('should validate URI with fragment', () => { + const result = FormatUri.validate({ data: '"https://example.com/page#section"' }); + expect(result.valid).toBe(true); + }); + + test('should validate ftp URI', () => { + const result = FormatUri.validate({ data: '"ftp://ftp.example.com/file.txt"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate invalid URI', () => { + const result = FormatUri.validate({ data: '"not a uri"' }); + expect(result.valid).toBe(false); + }); + }); + + describe('FormatUuid', () => { + test('should marshal UUID', () => { + const value: FormatUuid.FormatUuid = '550e8400-e29b-41d4-a716-446655440000'; + const serialized = FormatUuid.marshal(value); + expect(serialized).toBe('"550e8400-e29b-41d4-a716-446655440000"'); + }); + + test('should unmarshal UUID', () => { + const result = FormatUuid.unmarshal('"123e4567-e89b-12d3-a456-426614174000"'); + expect(result).toBe('123e4567-e89b-12d3-a456-426614174000'); + }); + + test('should validate correct UUID v4', () => { + const result = FormatUuid.validate({ data: '"550e8400-e29b-41d4-a716-446655440000"' }); + expect(result.valid).toBe(true); + }); + + test('should validate UUID v1', () => { + const result = FormatUuid.validate({ data: '"6ba7b810-9dad-11d1-80b4-00c04fd430c8"' }); + expect(result.valid).toBe(true); + }); + + test('should validate lowercase UUID', () => { + const result = FormatUuid.validate({ data: '"a1b2c3d4-e5f6-7890-abcd-ef1234567890"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate invalid UUID format', () => { + const result = FormatUuid.validate({ data: '"not-a-uuid"' }); + expect(result.valid).toBe(false); + }); + + test('should invalidate UUID with wrong length', () => { + const result = FormatUuid.validate({ data: '"550e8400-e29b-41d4-a716-44665544000"' }); // one char short + expect(result.valid).toBe(false); + }); + }); + + describe('FormatDate', () => { + test('should marshal Date object', () => { + const value: FormatDate.FormatDate = new Date('2024-01-15'); + const serialized = FormatDate.marshal(value); + expect(serialized).toBe('"2024-01-15T00:00:00.000Z"'); + }); + + test('should unmarshal to Date object', () => { + const result = FormatDate.unmarshal('"2024-12-25"'); + expect(result).toBeInstanceOf(Date); + expect(result.toISOString().slice(0, 10)).toBe('2024-12-25'); + }); + + test('should validate correct date', () => { + const result = FormatDate.validate({ data: '"2024-01-15"' }); + expect(result.valid).toBe(true); + }); + + test('should validate leap year date', () => { + const result = FormatDate.validate({ data: '"2024-02-29"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate invalid date format', () => { + const result = FormatDate.validate({ data: '"15-01-2024"' }); // wrong order + expect(result.valid).toBe(false); + }); + + test('should invalidate date with slashes', () => { + const result = FormatDate.validate({ data: '"2024/01/15"' }); + expect(result.valid).toBe(false); + }); + }); + + describe('FormatDateTime', () => { + test('should marshal Date object', () => { + const value: FormatDateTime.FormatDateTime = new Date('2024-01-15T10:30:00Z'); + const serialized = FormatDateTime.marshal(value); + expect(serialized).toBe('"2024-01-15T10:30:00.000Z"'); + }); + + test('should unmarshal to Date object', () => { + const result = FormatDateTime.unmarshal('"2024-01-15T10:30:00Z"'); + expect(result).toBeInstanceOf(Date); + expect(result.toISOString()).toBe('2024-01-15T10:30:00.000Z'); + }); + + test('should validate ISO 8601 datetime with Z', () => { + const result = FormatDateTime.validate({ data: '"2024-01-15T10:30:00Z"' }); + expect(result.valid).toBe(true); + }); + + test('should validate datetime with timezone offset', () => { + const result = FormatDateTime.validate({ data: '"2024-01-15T10:30:00+05:30"' }); + expect(result.valid).toBe(true); + }); + + test('should validate datetime with milliseconds', () => { + const result = FormatDateTime.validate({ data: '"2024-01-15T10:30:00.123Z"' }); + expect(result.valid).toBe(true); + }); + + test('should validate datetime with negative offset', () => { + const result = FormatDateTime.validate({ data: '"2024-01-15T10:30:00-08:00"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate date-only', () => { + const result = FormatDateTime.validate({ data: '"2024-01-15"' }); + expect(result.valid).toBe(false); + }); + + test('should invalidate invalid datetime', () => { + const result = FormatDateTime.validate({ data: '"not a datetime"' }); + expect(result.valid).toBe(false); + }); + }); + + describe('FormatTime', () => { + test('should marshal Date object', () => { + // Time-only format - use arbitrary date part + const value: FormatTime.FormatTime = new Date('1970-01-01T10:30:00Z'); + const serialized = FormatTime.marshal(value); + // JSON.stringify on Date produces full ISO string + expect(serialized).toBe('"1970-01-01T10:30:00.000Z"'); + }); + + test('should unmarshal to Date object', () => { + // Time string gets wrapped with date context + const result = FormatTime.unmarshal('"10:30:00"'); + expect(result).toBeInstanceOf(Date); + }); + + test('should validate correct time', () => { + const result = FormatTime.validate({ data: '"10:30:00"' }); + expect(result.valid).toBe(true); + }); + + test('should validate midnight', () => { + const result = FormatTime.validate({ data: '"00:00:00"' }); + expect(result.valid).toBe(true); + }); + + test('should validate end of day', () => { + const result = FormatTime.validate({ data: '"23:59:59"' }); + expect(result.valid).toBe(true); + }); + + test('should validate time with timezone', () => { + const result = FormatTime.validate({ data: '"10:30:00Z"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate 24:00:00', () => { + const result = FormatTime.validate({ data: '"24:00:00"' }); + expect(result.valid).toBe(false); + }); + + test('should invalidate invalid time', () => { + const result = FormatTime.validate({ data: '"25:70:99"' }); + expect(result.valid).toBe(false); + }); + }); + + describe('FormatHostname', () => { + test('should marshal hostname', () => { + const value: FormatHostname.FormatHostname = 'example.com'; + const serialized = FormatHostname.marshal(value); + expect(serialized).toBe('"example.com"'); + }); + + test('should validate simple hostname', () => { + const result = FormatHostname.validate({ data: '"example.com"' }); + expect(result.valid).toBe(true); + }); + + test('should validate subdomain hostname', () => { + const result = FormatHostname.validate({ data: '"www.example.com"' }); + expect(result.valid).toBe(true); + }); + + test('should validate deep subdomain', () => { + const result = FormatHostname.validate({ data: '"api.v1.staging.example.com"' }); + expect(result.valid).toBe(true); + }); + + test('should validate localhost', () => { + const result = FormatHostname.validate({ data: '"localhost"' }); + expect(result.valid).toBe(true); + }); + + test('should validate hostname with numbers', () => { + const result = FormatHostname.validate({ data: '"server01.example.com"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate hostname with protocol', () => { + const result = FormatHostname.validate({ data: '"https://example.com"' }); + expect(result.valid).toBe(false); + }); + }); + + describe('FormatIpv4', () => { + test('should marshal IPv4', () => { + const value: FormatIpv4.FormatIpv4 = '192.168.1.1'; + const serialized = FormatIpv4.marshal(value); + expect(serialized).toBe('"192.168.1.1"'); + }); + + test('should validate correct IPv4', () => { + const result = FormatIpv4.validate({ data: '"192.168.1.1"' }); + expect(result.valid).toBe(true); + }); + + test('should validate localhost IPv4', () => { + const result = FormatIpv4.validate({ data: '"127.0.0.1"' }); + expect(result.valid).toBe(true); + }); + + test('should validate broadcast address', () => { + const result = FormatIpv4.validate({ data: '"255.255.255.255"' }); + expect(result.valid).toBe(true); + }); + + test('should validate zeros', () => { + const result = FormatIpv4.validate({ data: '"0.0.0.0"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate IPv4 with out-of-range octet', () => { + const result = FormatIpv4.validate({ data: '"256.1.1.1"' }); + expect(result.valid).toBe(false); + }); + + test('should invalidate IPv4 with too few octets', () => { + const result = FormatIpv4.validate({ data: '"192.168.1"' }); + expect(result.valid).toBe(false); + }); + + test('should invalidate IPv6 as IPv4', () => { + const result = FormatIpv4.validate({ data: '"::1"' }); + expect(result.valid).toBe(false); + }); + }); + + describe('FormatIpv6', () => { + test('should marshal IPv6', () => { + const value: FormatIpv6.FormatIpv6 = '2001:0db8:85a3:0000:0000:8a2e:0370:7334'; + const serialized = FormatIpv6.marshal(value); + expect(serialized).toBe('"2001:0db8:85a3:0000:0000:8a2e:0370:7334"'); + }); + + test('should validate full IPv6', () => { + const result = FormatIpv6.validate({ data: '"2001:0db8:85a3:0000:0000:8a2e:0370:7334"' }); + expect(result.valid).toBe(true); + }); + + test('should validate compressed IPv6', () => { + const result = FormatIpv6.validate({ data: '"2001:db8:85a3::8a2e:370:7334"' }); + expect(result.valid).toBe(true); + }); + + test('should validate loopback IPv6', () => { + const result = FormatIpv6.validate({ data: '"::1"' }); + expect(result.valid).toBe(true); + }); + + test('should validate link-local IPv6', () => { + const result = FormatIpv6.validate({ data: '"fe80::1"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate IPv4 as IPv6', () => { + const result = FormatIpv6.validate({ data: '"192.168.1.1"' }); + expect(result.valid).toBe(false); + }); + + test('should invalidate invalid IPv6', () => { + const result = FormatIpv6.validate({ data: '"not:an:ipv6"' }); + expect(result.valid).toBe(false); + }); + }); + + describe('ObjectWithFormats', () => { + test('should marshal object with format-validated properties', () => { + const obj = new ObjectWithFormats({ + email: 'user@example.com', + website: 'https://example.com', + userId: '550e8400-e29b-41d4-a716-446655440000', + birthDate: '1990-01-15', + lastLogin: '2024-01-15T10:30:00Z', + serverIp: '192.168.1.1' + }); + const serialized = obj.marshal(); + const parsed = JSON.parse(serialized); + expect(parsed.email).toBe('user@example.com'); + expect(parsed.website).toBe('https://example.com'); + }); + + test('should validate object with all valid formats', () => { + const result = ObjectWithFormats.validate({ + data: { + email: 'user@example.com', + website: 'https://example.com', + userId: '550e8400-e29b-41d4-a716-446655440000', + birthDate: '1990-01-15', + lastLogin: '2024-01-15T10:30:00Z', + serverIp: '192.168.1.1' + } + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate object with invalid email', () => { + const result = ObjectWithFormats.validate({ + data: { + email: 'invalid-email', + website: 'https://example.com', + userId: '550e8400-e29b-41d4-a716-446655440000', + birthDate: '1990-01-15', + lastLogin: '2024-01-15T10:30:00Z', + serverIp: '192.168.1.1' + } + }); + expect(result.valid).toBe(false); + }); + + test('should invalidate object with invalid URI', () => { + const result = ObjectWithFormats.validate({ + data: { + email: 'user@example.com', + website: 'not a uri', + userId: '550e8400-e29b-41d4-a716-446655440000', + birthDate: '1990-01-15', + lastLogin: '2024-01-15T10:30:00Z', + serverIp: '192.168.1.1' + } + }); + expect(result.valid).toBe(false); + }); + + test('should validate empty object (all fields optional)', () => { + const result = ObjectWithFormats.validate({ + data: {} + }); + expect(result.valid).toBe(true); + }); + + test('should validate partial object', () => { + const result = ObjectWithFormats.validate({ + data: { + email: 'user@example.com' + } + }); + expect(result.valid).toBe(true); + }); + }); +}); diff --git a/test/runtime/typescript/test/payload-types/objects.spec.ts b/test/runtime/typescript/test/payload-types/objects.spec.ts new file mode 100644 index 00000000..30d94529 --- /dev/null +++ b/test/runtime/typescript/test/payload-types/objects.spec.ts @@ -0,0 +1,374 @@ +/** + * Runtime tests for object payload types. + * Tests simple objects, required/optional properties, nested objects, + * additional properties, and defaults. + * + * NOTE: Some issues discovered: + * - NestedObject/DeeplyNestedObject marshal expects nested .marshal() method + */ +import { SimpleObject } from '../../src/payload-types/payloads/SimpleObject'; +import { ObjectWithRequired } from '../../src/payload-types/payloads/ObjectWithRequired'; +import { ObjectWithOptional } from '../../src/payload-types/payloads/ObjectWithOptional'; +import { NestedObject } from '../../src/payload-types/payloads/NestedObject'; +import { DeeplyNestedObject } from '../../src/payload-types/payloads/DeeplyNestedObject'; +import { ObjectAdditionalPropsTrue } from '../../src/payload-types/payloads/ObjectAdditionalPropsTrue'; +import { ObjectAdditionalPropsFalse } from '../../src/payload-types/payloads/ObjectAdditionalPropsFalse'; +import { ObjectAdditionalPropsSchema } from '../../src/payload-types/payloads/ObjectAdditionalPropsSchema'; +import { ObjectWithDefaults } from '../../src/payload-types/payloads/ObjectWithDefaults'; + +describe('Object Types', () => { + describe('SimpleObject', () => { + test('should marshal simple object', () => { + const obj = new SimpleObject({ name: 'John', age: 30 }); + const serialized = obj.marshal(); + const parsed = JSON.parse(serialized); + expect(parsed.name).toBe('John'); + expect(parsed.age).toBe(30); + }); + + test('should unmarshal simple object', () => { + const json = '{"name":"Jane","age":25}'; + const obj = SimpleObject.unmarshal(json); + expect(obj.name).toBe('Jane'); + expect(obj.age).toBe(25); + }); + + test('should roundtrip correctly', () => { + const original = new SimpleObject({ name: 'Test', age: 42 }); + const serialized = original.marshal(); + const restored = SimpleObject.unmarshal(serialized); + expect(restored.name).toBe(original.name); + expect(restored.age).toBe(original.age); + }); + + test('should validate correct object', () => { + const result = SimpleObject.validate({ + data: { name: 'Test', age: 30 } + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with missing optional properties', () => { + const result = SimpleObject.validate({ + data: {} + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with partial properties', () => { + const result = SimpleObject.validate({ + data: { name: 'Test' } + }); + expect(result.valid).toBe(true); + }); + }); + + describe('ObjectWithRequired', () => { + test('should marshal object with required fields', () => { + const obj = new ObjectWithRequired({ + id: 'abc123', + name: 'Required Test' + }); + const serialized = obj.marshal(); + const parsed = JSON.parse(serialized); + expect(parsed.id).toBe('abc123'); + expect(parsed.name).toBe('Required Test'); + }); + + test('should marshal object with optional field', () => { + const obj = new ObjectWithRequired({ + id: 'abc123', + name: 'Test', + optionalField: 'optional value' + }); + const serialized = obj.marshal(); + const parsed = JSON.parse(serialized); + expect(parsed.optional_field).toBe('optional value'); + }); + + test('should validate object with all required fields', () => { + const result = ObjectWithRequired.validate({ + data: { id: 'abc', name: 'Test' } + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with required and optional fields', () => { + const result = ObjectWithRequired.validate({ + data: { id: 'abc', name: 'Test', optional_field: 'extra' } + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate object missing required id', () => { + const result = ObjectWithRequired.validate({ + data: { name: 'Test' } + }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'required' }) + ]) + ); + }); + + test('should invalidate object missing required name', () => { + const result = ObjectWithRequired.validate({ + data: { id: 'abc' } + }); + expect(result.valid).toBe(false); + }); + + test('should invalidate empty object', () => { + const result = ObjectWithRequired.validate({ + data: {} + }); + expect(result.valid).toBe(false); + }); + }); + + describe('ObjectWithOptional', () => { + test('should marshal object with all fields', () => { + const obj = new ObjectWithOptional({ + field1: 'a', + field2: 'b', + field3: 'c' + }); + const serialized = obj.marshal(); + const parsed = JSON.parse(serialized); + expect(parsed.field1).toBe('a'); + expect(parsed.field2).toBe('b'); + expect(parsed.field3).toBe('c'); + }); + + test('should validate empty object', () => { + const result = ObjectWithOptional.validate({ + data: {} + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with some fields', () => { + const result = ObjectWithOptional.validate({ + data: { field1: 'value' } + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with all fields', () => { + const result = ObjectWithOptional.validate({ + data: { field1: 'a', field2: 'b', field3: 'c' } + }); + expect(result.valid).toBe(true); + }); + }); + + describe('NestedObject', () => { + // Unskipped - fix verifies nested object marshal works + test('should marshal nested object', () => { + const obj = new NestedObject({ outer: { inner: 'test' } }); + const serialized = obj.marshal(); + const parsed = JSON.parse(serialized); + expect(parsed.outer.inner).toBe('test'); + }); + + test('should unmarshal nested object', () => { + const json = '{"outer":{"inner":"test"}}'; + const obj = NestedObject.unmarshal(json); + expect(obj.outer?.inner).toBe('test'); + }); + + // New roundtrip test + test('should roundtrip nested object', () => { + const original = new NestedObject({ outer: { inner: 'roundtrip' } }); + const serialized = original.marshal(); + const restored = NestedObject.unmarshal(serialized); + expect(restored.outer?.inner).toBe('roundtrip'); + }); + + test('should validate nested object', () => { + const result = NestedObject.validate({ + data: { outer: { inner: 'value' } } + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with empty outer', () => { + const result = NestedObject.validate({ + data: { outer: {} } + }); + expect(result.valid).toBe(true); + }); + }); + + describe('DeeplyNestedObject', () => { + // Unskipped - fix verifies deeply nested object marshal works + test('should marshal deeply nested object', () => { + const obj = new DeeplyNestedObject({ + level1: { level2: { level3: { value: 'deep' } } } + }); + const serialized = obj.marshal(); + const parsed = JSON.parse(serialized); + expect(parsed.level1.level2.level3.value).toBe('deep'); + }); + + test('should unmarshal deeply nested object', () => { + const json = '{"level1":{"level2":{"level3":{"value":"test"}}}}'; + const obj = DeeplyNestedObject.unmarshal(json); + expect(obj.level1?.level2?.level3?.value).toBe('test'); + }); + + // Unskipped roundtrip test + test('should roundtrip deeply nested object', () => { + const original = new DeeplyNestedObject({ + level1: { level2: { level3: { value: 'roundtrip' } } } + }); + const serialized = original.marshal(); + const restored = DeeplyNestedObject.unmarshal(serialized); + expect(restored.level1?.level2?.level3?.value).toBe('roundtrip'); + }); + + test('should validate deeply nested structure', () => { + const result = DeeplyNestedObject.validate({ + data: { + level1: { + level2: { + level3: { + value: 'test' + } + } + } + } + }); + expect(result.valid).toBe(true); + }); + }); + + describe('ObjectAdditionalPropsTrue', () => { + test('should marshal object with extra properties', () => { + const obj = new ObjectAdditionalPropsTrue({ + known: 'value' + }); + // Additional properties may be accessible via additionalProperties map + const serialized = obj.marshal(); + const parsed = JSON.parse(serialized); + expect(parsed.known).toBe('value'); + }); + + test('should validate object with known property only', () => { + const result = ObjectAdditionalPropsTrue.validate({ + data: { known: 'value' } + }); + expect(result.valid).toBe(true); + }); + + test('should validate object with extra properties', () => { + const result = ObjectAdditionalPropsTrue.validate({ + data: { known: 'value', extra: 'allowed', another: 123 } + }); + expect(result.valid).toBe(true); + }); + + test('should validate empty object', () => { + const result = ObjectAdditionalPropsTrue.validate({ + data: {} + }); + expect(result.valid).toBe(true); + }); + }); + + describe('ObjectAdditionalPropsFalse', () => { + test('should validate object with only known property', () => { + const result = ObjectAdditionalPropsFalse.validate({ + data: { known: 'value' } + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate object with extra properties', () => { + const result = ObjectAdditionalPropsFalse.validate({ + data: { known: 'value', extra: 'not allowed' } + }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'additionalProperties' }) + ]) + ); + }); + + test('should validate empty object', () => { + const result = ObjectAdditionalPropsFalse.validate({ + data: {} + }); + expect(result.valid).toBe(true); + }); + }); + + describe('ObjectAdditionalPropsSchema', () => { + test('should validate object with integer additional properties', () => { + const result = ObjectAdditionalPropsSchema.validate({ + data: { known: 'value', count: 42, total: 100 } + }); + expect(result.valid).toBe(true); + }); + + test('should invalidate object with non-integer additional properties', () => { + const result = ObjectAdditionalPropsSchema.validate({ + data: { known: 'value', extra: 'string not allowed' } + }); + expect(result.valid).toBe(false); + }); + + test('should validate object with only known property', () => { + const result = ObjectAdditionalPropsSchema.validate({ + data: { known: 'value' } + }); + expect(result.valid).toBe(true); + }); + }); + + describe('ObjectWithDefaults', () => { + test('should marshal object with explicit values', () => { + const obj = new ObjectWithDefaults({ + status: 'active', + count: 10, + enabled: false + }); + const serialized = obj.marshal(); + const parsed = JSON.parse(serialized); + expect(parsed.status).toBe('active'); + expect(parsed.count).toBe(10); + expect(parsed.enabled).toBe(false); + }); + + test('should unmarshal object with explicit values', () => { + const json = '{"status":"complete","count":5,"enabled":true}'; + const obj = ObjectWithDefaults.unmarshal(json); + expect(obj.status).toBe('complete'); + expect(obj.count).toBe(5); + expect(obj.enabled).toBe(true); + }); + + test('should validate object with values', () => { + const result = ObjectWithDefaults.validate({ + data: { status: 'test', count: 1, enabled: true } + }); + expect(result.valid).toBe(true); + }); + + test('should validate empty object (defaults apply)', () => { + const result = ObjectWithDefaults.validate({ + data: {} + }); + expect(result.valid).toBe(true); + }); + + test('should validate partial object', () => { + const result = ObjectWithDefaults.validate({ + data: { status: 'partial' } + }); + expect(result.valid).toBe(true); + }); + }); +}); diff --git a/test/runtime/typescript/test/payload-types/primitives.spec.ts b/test/runtime/typescript/test/payload-types/primitives.spec.ts new file mode 100644 index 00000000..00d4629a --- /dev/null +++ b/test/runtime/typescript/test/payload-types/primitives.spec.ts @@ -0,0 +1,424 @@ +/** + * Runtime tests for primitive payload types. + * Tests string, number, integer, boolean, and null types with various constraints. + * + * NOTE: Some issues discovered: + * - Boolean validation accepts string 'true' due to JSON.parse coercion (documented as intentional) + */ +import * as StringPlain from '../../src/payload-types/payloads/StringPlain'; +import * as StringWithMinLength from '../../src/payload-types/payloads/StringWithMinLength'; +import * as StringWithMaxLength from '../../src/payload-types/payloads/StringWithMaxLength'; +import * as StringWithPattern from '../../src/payload-types/payloads/StringWithPattern'; +import * as NumberPlain from '../../src/payload-types/payloads/NumberPlain'; +import * as NumberWithMinimum from '../../src/payload-types/payloads/NumberWithMinimum'; +import * as NumberWithMaximum from '../../src/payload-types/payloads/NumberWithMaximum'; +import * as NumberWithRange from '../../src/payload-types/payloads/NumberWithRange'; +import * as IntegerPlain from '../../src/payload-types/payloads/IntegerPlain'; +import * as IntegerWithRange from '../../src/payload-types/payloads/IntegerWithRange'; +import * as BooleanPlain from '../../src/payload-types/payloads/BooleanPlain'; +import * as NullPlain from '../../src/payload-types/payloads/NullPlain'; + +describe('Primitive Types', () => { + describe('StringPlain', () => { + test('should marshal a plain string', () => { + const value: StringPlain.StringPlain = 'Hello World'; + const serialized = StringPlain.marshal(value); + expect(serialized).toBe('"Hello World"'); + }); + + test('should unmarshal a plain string', () => { + const serialized = '"Hello World"'; + const result = StringPlain.unmarshal(serialized); + expect(result).toBe('Hello World'); + }); + + test('should roundtrip correctly', () => { + const original: StringPlain.StringPlain = 'Test roundtrip'; + const serialized = StringPlain.marshal(original); + const deserialized = StringPlain.unmarshal(serialized); + expect(deserialized).toBe(original); + }); + + test('should handle empty string', () => { + const value: StringPlain.StringPlain = ''; + const serialized = StringPlain.marshal(value); + expect(serialized).toBe('""'); + const deserialized = StringPlain.unmarshal(serialized); + expect(deserialized).toBe(''); + }); + + test('should handle unicode characters', () => { + const value: StringPlain.StringPlain = 'Hello δΈ–η•Œ 🌍'; + const serialized = StringPlain.marshal(value); + const deserialized = StringPlain.unmarshal(serialized); + expect(deserialized).toBe(value); + }); + + test('should validate string type', () => { + const result = StringPlain.validate({ data: '"valid string"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate non-string', () => { + const result = StringPlain.validate({ data: 123 }); + expect(result.valid).toBe(false); + }); + }); + + describe('StringWithMinLength', () => { + test('should validate string meeting minLength', () => { + const result = StringWithMinLength.validate({ data: '"abc"' }); + expect(result.valid).toBe(true); + }); + + test('should validate string exceeding minLength', () => { + const result = StringWithMinLength.validate({ data: '"abcdef"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate string below minLength', () => { + const result = StringWithMinLength.validate({ data: '"ab"' }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'minLength' }) + ]) + ); + }); + }); + + describe('StringWithMaxLength', () => { + test('should validate string within maxLength', () => { + const result = StringWithMaxLength.validate({ data: '"short"' }); + expect(result.valid).toBe(true); + }); + + test('should validate string at exact maxLength', () => { + const value = '"' + 'a'.repeat(50) + '"'; + const result = StringWithMaxLength.validate({ data: value }); + expect(result.valid).toBe(true); + }); + + test('should invalidate string exceeding maxLength', () => { + const value = '"' + 'a'.repeat(51) + '"'; + const result = StringWithMaxLength.validate({ data: value }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'maxLength' }) + ]) + ); + }); + }); + + describe('StringWithPattern', () => { + test('should validate string matching pattern', () => { + // Pattern: ^[A-Z][a-z]+$ + const result = StringWithPattern.validate({ data: '"Hello"' }); + expect(result.valid).toBe(true); + }); + + test('should validate another matching pattern', () => { + const result = StringWithPattern.validate({ data: '"World"' }); + expect(result.valid).toBe(true); + }); + + test('should invalidate string not matching pattern', () => { + const result = StringWithPattern.validate({ data: '"hello"' }); // lowercase first letter + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'pattern' }) + ]) + ); + }); + + test('should invalidate all uppercase', () => { + const result = StringWithPattern.validate({ data: '"HELLO"' }); + expect(result.valid).toBe(false); + }); + }); + + describe('NumberPlain', () => { + test('should marshal a plain number', () => { + const value: NumberPlain.NumberPlain = 42.5; + const serialized = NumberPlain.marshal(value); + expect(serialized).toBe('42.5'); + }); + + test('should unmarshal a plain number', () => { + const serialized = '42.5'; + const result = NumberPlain.unmarshal(serialized); + expect(result).toBe(42.5); + }); + + test('should handle negative numbers', () => { + const value: NumberPlain.NumberPlain = -123.456; + const serialized = NumberPlain.marshal(value); + const deserialized = NumberPlain.unmarshal(serialized); + expect(deserialized).toBe(value); + }); + + test('should handle zero', () => { + const value: NumberPlain.NumberPlain = 0; + const serialized = NumberPlain.marshal(value); + expect(serialized).toBe('0'); + const deserialized = NumberPlain.unmarshal(serialized); + expect(deserialized).toBe(0); + }); + + test('should validate number type', () => { + const result = NumberPlain.validate({ data: 42 }); + expect(result.valid).toBe(true); + }); + + test('should invalidate string type', () => { + const result = NumberPlain.validate({ data: '"42"' }); + expect(result.valid).toBe(false); + }); + }); + + describe('NumberWithMinimum', () => { + test('should validate number at minimum', () => { + const result = NumberWithMinimum.validate({ data: 0 }); + expect(result.valid).toBe(true); + }); + + test('should validate number above minimum', () => { + const result = NumberWithMinimum.validate({ data: 100 }); + expect(result.valid).toBe(true); + }); + + test('should invalidate number below minimum', () => { + const result = NumberWithMinimum.validate({ data: -1 }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'minimum' }) + ]) + ); + }); + }); + + describe('NumberWithMaximum', () => { + test('should validate number at maximum', () => { + const result = NumberWithMaximum.validate({ data: 100 }); + expect(result.valid).toBe(true); + }); + + test('should validate number below maximum', () => { + const result = NumberWithMaximum.validate({ data: 50 }); + expect(result.valid).toBe(true); + }); + + test('should invalidate number above maximum', () => { + const result = NumberWithMaximum.validate({ data: 101 }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'maximum' }) + ]) + ); + }); + }); + + describe('NumberWithRange (exclusive)', () => { + test('should marshal number in exclusive range', () => { + const value: NumberWithRange.NumberWithRange = 50; + const serialized = NumberWithRange.marshal(value); + expect(serialized).toBe('50'); + }); + + test('should unmarshal number in exclusive range', () => { + const serialized = '50'; + const result = NumberWithRange.unmarshal(serialized); + expect(result).toBe(50); + }); + + test('should validate number strictly within range', () => { + const result = NumberWithRange.validate({ data: 50 }); + expect(result.valid).toBe(true); + }); + + test('should validate number just above exclusive minimum', () => { + const result = NumberWithRange.validate({ data: 0.001 }); + expect(result.valid).toBe(true); + }); + + test('should validate number just below exclusive maximum', () => { + const result = NumberWithRange.validate({ data: 99.999 }); + expect(result.valid).toBe(true); + }); + + test('should invalidate number at exclusive minimum', () => { + const result = NumberWithRange.validate({ data: 0 }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'exclusiveMinimum' }) + ]) + ); + }); + + test('should invalidate number at exclusive maximum', () => { + const result = NumberWithRange.validate({ data: 100 }); + expect(result.valid).toBe(false); + expect(result.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ keyword: 'exclusiveMaximum' }) + ]) + ); + }); + + test('should invalidate number below exclusive minimum', () => { + const result = NumberWithRange.validate({ data: -1 }); + expect(result.valid).toBe(false); + }); + + test('should invalidate number above exclusive maximum', () => { + const result = NumberWithRange.validate({ data: 101 }); + expect(result.valid).toBe(false); + }); + }); + + describe('IntegerPlain', () => { + test('should marshal an integer', () => { + const value: IntegerPlain.IntegerPlain = 42; + const serialized = IntegerPlain.marshal(value); + expect(serialized).toBe('42'); + }); + + test('should unmarshal an integer', () => { + const serialized = '42'; + const result = IntegerPlain.unmarshal(serialized); + expect(result).toBe(42); + }); + + test('should handle negative integers', () => { + const value: IntegerPlain.IntegerPlain = -100; + const serialized = IntegerPlain.marshal(value); + const deserialized = IntegerPlain.unmarshal(serialized); + expect(deserialized).toBe(value); + }); + + test('should validate integer type', () => { + const result = IntegerPlain.validate({ data: 42 }); + expect(result.valid).toBe(true); + }); + + test('should invalidate non-integer number', () => { + const result = IntegerPlain.validate({ data: 42.5 }); + expect(result.valid).toBe(false); + }); + }); + + describe('IntegerWithRange', () => { + test('should validate integer within range', () => { + const result = IntegerWithRange.validate({ data: 5 }); + expect(result.valid).toBe(true); + }); + + test('should validate integer at minimum', () => { + const result = IntegerWithRange.validate({ data: 1 }); + expect(result.valid).toBe(true); + }); + + test('should validate integer at maximum', () => { + const result = IntegerWithRange.validate({ data: 10 }); + expect(result.valid).toBe(true); + }); + + test('should invalidate integer below range', () => { + const result = IntegerWithRange.validate({ data: 0 }); + expect(result.valid).toBe(false); + }); + + test('should invalidate integer above range', () => { + const result = IntegerWithRange.validate({ data: 11 }); + expect(result.valid).toBe(false); + }); + }); + + describe('BooleanPlain', () => { + test('should marshal true', () => { + const value: BooleanPlain.BooleanPlain = true; + const serialized = BooleanPlain.marshal(value); + expect(serialized).toBe('true'); + }); + + test('should marshal false', () => { + const value: BooleanPlain.BooleanPlain = false; + const serialized = BooleanPlain.marshal(value); + expect(serialized).toBe('false'); + }); + + test('should unmarshal true', () => { + const result = BooleanPlain.unmarshal('true'); + expect(result).toBe(true); + }); + + test('should unmarshal false', () => { + const result = BooleanPlain.unmarshal('false'); + expect(result).toBe(false); + }); + + test('should validate boolean true', () => { + const result = BooleanPlain.validate({ data: true }); + expect(result.valid).toBe(true); + }); + + test('should validate boolean false', () => { + const result = BooleanPlain.validate({ data: false }); + expect(result.valid).toBe(true); + }); + + // String 'true' is JSON.parsed to boolean true before validation + // Pass an object that will fail JSON.parse or a non-boolean value + test('should invalidate object with value true', () => { + const result = BooleanPlain.validate({ data: { value: true } }); + expect(result.valid).toBe(false); + }); + + test('should invalidate number 1', () => { + const result = BooleanPlain.validate({ data: 1 }); + expect(result.valid).toBe(false); + }); + }); + + describe('NullPlain', () => { + test('should marshal null', () => { + const serialized = NullPlain.marshal(null); + expect(serialized).toBe('null'); + }); + + test('should unmarshal null', () => { + const result = NullPlain.unmarshal('null'); + expect(result).toBe(null); + }); + + test('should throw when unmarshalling non-null', () => { + expect(() => NullPlain.unmarshal('"not null"')).toThrow('Expected null value'); + }); + + test('should validate null', () => { + const result = NullPlain.validate({ data: null }); + expect(result.valid).toBe(true); + }); + + test('should invalidate non-null string (via marshalled JSON)', () => { + // Pass as JSON string since validate parses strings + const result = NullPlain.validate({ data: '"not null"' }); + expect(result.valid).toBe(false); + }); + + test('should invalidate non-null number', () => { + const result = NullPlain.validate({ data: 0 }); + expect(result.valid).toBe(false); + }); + + test('should invalidate non-null object', () => { + const result = NullPlain.validate({ data: {} }); + expect(result.valid).toBe(false); + }); + }); +});