Skip to content

Commit 9a89478

Browse files
committed
feat(serdes): Validation and Serialization according to OpenAPI schema processed by Ajv 🎉
1 parent 5bdda16 commit 9a89478

File tree

5 files changed

+184
-220
lines changed

5 files changed

+184
-220
lines changed

config-node.yaml

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@ additionalProperties:
77
files:
88
logger.mustache:
99
templateType: SupportingFiles
10-
destinationFilename: logger.ts
10+
destinationFilename: logger.ts
11+
model/validation.mustache:
12+
folder: models
13+
templateType: SupportingFiles
14+
destinationFilename: validation.ts

sdk-template-overrides/typescript/model/ObjectSerializer.mustache

+13-217
Original file line numberDiff line numberDiff line change
@@ -4,151 +4,37 @@ export * from '{{{ importPath }}}{{extensionForDeno}}';
44
{{/model}}
55
{{/models}}
66

7-
{{#models}}
8-
{{#model}}
9-
import { {{classname}}{{#hasEnums}}{{#vars}}{{#isEnum}}, {{classname}}{{enumName}} {{/isEnum}} {{/vars}}{{/hasEnums}} } from '{{{ importPath }}}{{extensionForDeno}}';
10-
{{/model}}
11-
{{/models}}
127
import { dateFromRFC3339String, dateToRFC3339String, UnparsedObject } from "../util{{extensionForDeno}}";
13-
import { logger } from "../logger{{extensionForDeno}}";
8+
import { validateAndSerialize, parseAndLog, enumsMap } from "./validation{{extensionForDeno}}";
149

1510
/* tslint:disable:no-unused-variable */
1611
const primitives = [
1712
"string",
1813
"boolean",
19-
"double",
20-
"integer",
21-
"long",
22-
"float",
2314
"number",
24-
"any"
15+
"object"
2516
];
2617

27-
const ARRAY_PREFIX = "Array<";
28-
const MAP_PREFIX = "{ [key: string]: ";
29-
const TUPLE_PREFIX = "[";
30-
3118
const supportedMediaTypes: { [mediaType: string]: number } = {
3219
"application/json": Infinity,
3320
"application/octet-stream": 0,
3421
"application/x-www-form-urlencoded": 0
3522
}
3623

37-
38-
let enumsMap: Set<string> = new Set<string>([
39-
{{#models}}
40-
{{#model}}
41-
{{#isEnum}}
42-
"{{classname}}{{enumName}}",
43-
{{/isEnum}}
44-
{{#hasEnums}}
45-
{{#vars}}
46-
{{#isEnum}}
47-
"{{classname}}{{enumName}}",
48-
{{/isEnum}}
49-
{{/vars}}
50-
{{/hasEnums}}
51-
{{/model}}
52-
{{/models}}
53-
]);
54-
55-
let typeMap: {[index: string]: any} = {
56-
{{#models}}
57-
{{#model}}
58-
{{^isEnum}}
59-
"{{classname}}": {{classname}},
60-
{{/isEnum}}
61-
{{/model}}
62-
{{/models}}
63-
}
64-
65-
let oneOfMap: {[index: string]: string[]} = {
66-
{{#models}}
67-
{{#model}}
68-
{{#oneOf}}
69-
{{#-first}}
70-
"{{#lambda.pascalcase}}{{name}}{{/lambda.pascalcase}}": [{{#oneOf}}{{{#dataType}}}"{{{.}}}"{{^-last}}, {{/-last}}{{{/dataType}}}{{/oneOf}}],
71-
{{/-first}}
72-
{{/oneOf}}
73-
{{/model}}
74-
{{/models}}
75-
};
76-
7724
export class ObjectSerializer {
78-
public static findCorrectType(data: any, expectedType: string) {
79-
if (data == undefined) {
80-
return expectedType;
81-
} else if (primitives.indexOf(expectedType.toLowerCase()) !== -1) {
82-
return expectedType;
83-
} else if (expectedType === "Date") {
84-
return expectedType;
85-
} else {
86-
if (enumsMap.has(expectedType)) {
87-
return expectedType;
88-
}
89-
90-
if (!typeMap[expectedType]) {
91-
return expectedType; // w/e we don't know the type
92-
}
93-
94-
// Check the discriminator
95-
let discriminatorProperty = typeMap[expectedType].discriminator;
96-
if (discriminatorProperty == null) {
97-
return expectedType; // the type does not have a discriminator. use it.
98-
} else {
99-
if (data[discriminatorProperty]) {
100-
var discriminatorType = data[discriminatorProperty];
101-
if(typeMap[discriminatorType]){
102-
return discriminatorType; // use the type given in the discriminator
103-
} else {
104-
return expectedType; // discriminator did not map to a type
105-
}
106-
} else {
107-
return expectedType; // discriminator was not present (or an empty string)
108-
}
109-
}
110-
}
111-
}
112-
11325
public static serialize(data: any, type: string, format: string) {
114-
if (data == undefined || type == "any") {
26+
if (data == undefined || type === "any") {
11527
return data;
11628
} else if (data instanceof UnparsedObject) {
11729
return data._data;
118-
} else if (primitives.includes(type.toLowerCase()) && typeof data == type.toLowerCase()) {
119-
return data;
120-
} else if (type.startsWith(ARRAY_PREFIX)) {
121-
if (!Array.isArray(data)) {
122-
throw new TypeError(`mismatch types '${data}' and '${type}'`);
123-
}
124-
// Array<Type> => Type
125-
const subType: string = type.substring(ARRAY_PREFIX.length, type.length - 1);
126-
const transformedData: any[] = [];
127-
for (const element of data) {
128-
transformedData.push(ObjectSerializer.serialize(element, subType, format));
129-
}
130-
return transformedData;
131-
} else if (type.startsWith(TUPLE_PREFIX)) {
132-
// We only support homegeneus tuples
133-
const subType: string = type.substring(TUPLE_PREFIX.length, type.length - 1).split(", ")[0];
134-
const transformedData: any[] = [];
135-
for (const element of data) {
136-
transformedData.push(ObjectSerializer.serialize(element, subType, format));
137-
}
138-
return transformedData;
139-
} else if (type.startsWith(MAP_PREFIX)) {
140-
// { [key: string]: Type; } => Type
141-
const subType: string = type.substring(MAP_PREFIX.length, type.length - 3);
142-
const transformedData: { [key: string]: any } = {};
143-
for (const key in data) {
144-
transformedData[key] = ObjectSerializer.serialize(data[key], subType, format);
145-
}
146-
return transformedData;
30+
} else if (primitives.includes(type.toLowerCase())) {
31+
if (typeof data === type.toLowerCase()) return data;
32+
throw new TypeError(`mismatch types '${data}' and '${type}'`);
14733
} else if (type === "Date") {
148-
if ("string" == typeof data) {
34+
if ("string" === typeof data) {
14935
return data;
15036
}
151-
if (format == "date" || format == "date-time") {
37+
if (format === "date" || format === "date-time") {
15238
return dateToRFC3339String(data)
15339
} else {
15440
return data.toISOString();
@@ -157,115 +43,25 @@ export class ObjectSerializer {
15743
if (enumsMap.has(type)) {
15844
return data;
15945
}
160-
if (oneOfMap[type]) {
161-
const oneOfs: any[] = [];
162-
for (const oneOf of oneOfMap[type]) {
163-
try {
164-
oneOfs.push(ObjectSerializer.serialize(data, oneOf, format));
165-
} catch (e) {
166-
logger.debug(`could not serialize ${oneOf} (${e})`)
167-
}
168-
}
169-
if (oneOfs.length > 1) {
170-
throw new TypeError(`${data} matches multiple types from ${oneOfMap[type]} ${oneOfs}`);
171-
}
172-
if (oneOfs.length == 0) {
173-
throw new TypeError(`${data} doesn't match any type from ${oneOfMap[type]} ${oneOfs}`);
174-
}
175-
return oneOfs[0];
176-
}
17746

178-
if (!typeMap[type]) { // in case we dont know the type
179-
return data;
180-
}
181-
182-
// Get the actual type of this object
183-
type = this.findCorrectType(data, type);
184-
185-
// get the map for the correct type.
186-
let attributeTypes = typeMap[type].getAttributeTypeMap();
187-
let instance: {[index: string]: any} = {};
188-
for (let index in attributeTypes) {
189-
let attributeType = attributeTypes[index];
190-
instance[attributeType.baseName] = ObjectSerializer.serialize(data[attributeType.baseName], attributeType.type, attributeType.format);
191-
}
192-
return instance;
47+
return validateAndSerialize(data, type);
19348
}
19449
}
19550

19651
public static deserialize(data: any, type: string, format: string) {
19752
// polymorphism may change the actual type.
198-
type = ObjectSerializer.findCorrectType(data, type);
19953
if (data == undefined || type == "any") {
20054
return data;
20155
} else if (primitives.includes(type.toLowerCase()) && typeof data == type.toLowerCase()) {
20256
return data;
203-
} else if (type.startsWith(ARRAY_PREFIX)) {
204-
// Assert the passed data is Array type
205-
if (!Array.isArray(data)) {
206-
throw new TypeError(`mismatch types '${data}' and '${type}'`);
207-
}
208-
// Array<Type> => Type
209-
const subType: string = type.substring(ARRAY_PREFIX.length, type.length - 1);
210-
const transformedData: any[] = [];
211-
for (const element of data) {
212-
transformedData.push(ObjectSerializer.deserialize(element, subType, format));
213-
}
214-
return transformedData;
215-
} else if (type.startsWith(TUPLE_PREFIX)) {
216-
// [Type,...] => Type
217-
const subType: string = type.substring(TUPLE_PREFIX.length, type.length - 1).split(", ")[0];
218-
const transformedData: any[] = [];
219-
for (const element of data) {
220-
transformedData.push(ObjectSerializer.deserialize(element, subType, format));
221-
}
222-
return transformedData;
223-
} else if (type.startsWith(MAP_PREFIX)) {
224-
// { [key: string]: Type; } => Type
225-
const subType: string = type.substring(MAP_PREFIX.length, type.length - 3);
226-
const transformedData: { [key: string]: any } = {};
227-
for (const key in data) {
228-
transformedData[key] = ObjectSerializer.deserialize(data[key], subType, format);
229-
}
230-
return transformedData;
23157
} else if (type === "Date") {
23258
return dateFromRFC3339String(data)
23359
} else {
23460
if (enumsMap.has(type)) {// is Enum
23561
return data;
23662
}
237-
if (oneOfMap[type]) {
238-
const oneOfs: any[] = [];
239-
for (const oneOf of oneOfMap[type]) {
240-
try {
241-
const d = ObjectSerializer.deserialize(data, oneOf, format);
242-
if (!d?._unparsed) {
243-
oneOfs.push(d);
244-
}
245-
} catch (e) {
246-
logger.debug(`could not deserialize ${oneOf} (${e})`)
247-
}
248-
249-
}
250-
if (oneOfs.length != 1) {
251-
return new UnparsedObject(data);
252-
}
253-
return oneOfs[0];
254-
}
255-
256-
if (!typeMap[type]) { // dont know the type
257-
return data;
258-
}
259-
let instance = new typeMap[type]();
260-
let attributeTypes = typeMap[type].getAttributeTypeMap();
261-
for (let index in attributeTypes) {
262-
let attributeType = attributeTypes[index];
263-
let value = ObjectSerializer.deserialize(data[attributeType.baseName], attributeType.type, attributeType.format);
264-
if (value !== undefined) {
265-
instance[attributeType.name] = value;
266-
}
267-
}
268-
return instance;
63+
64+
return parseAndLog(data, type);
26965
}
27066
}
27167

@@ -321,7 +117,7 @@ export class ObjectSerializer {
321117
}
322118

323119
if (mediaType === "application/json") {
324-
return JSON.stringify(data);
120+
return String(data);
325121
}
326122

327123
throw new Error("The mediaType " + mediaType + " is not supported by ObjectSerializer.stringify.");
@@ -340,7 +136,7 @@ export class ObjectSerializer {
340136
}
341137

342138
if (mediaType === "application/json") {
343-
return JSON.parse(rawData);
139+
return rawData;
344140
}
345141

346142
if (mediaType === "text/html") {

sdk-template-overrides/typescript/model/model.mustache

+4-2
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ export class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{
3030
{{/discriminator}}
3131

3232
{{^isArray}}
33-
static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string}> = [
33+
static readonly attributeTypeMap: Array<{name: string, baseName: string, type: string, format: string, required: boolean, nullable: boolean}> = [
3434
{{#vars}}
3535
{
3636
"name": "{{name}}",
3737
"baseName": "{{baseName}}",
3838
"type": "{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}",
39-
"format": "{{dataFormat}}"
39+
"format": "{{dataFormat}}",
40+
"required": {{required}},
41+
"nullable": {{isNullable}}
4042
}{{^-last}},
4143
{{/-last}}
4244
{{/vars}}

0 commit comments

Comments
 (0)