Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/handlerbars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const createHandlebars = (jsonSchema: $Refs): typeof Handlebars => {
return frame;
};

[array, code, collection, comparison, date, html, i18n, inflection, markdown, math, misc, number, object, path, regex, string, url].forEach(
[array, code, collection, comparison, date, html, i18n, inflection, object, markdown, math, misc, number, object, path, regex, string, url].forEach(
helper => {
handlebars.registerHelper(helper);
},
Expand Down Expand Up @@ -221,7 +221,7 @@ export const createHandlebars = (jsonSchema: $Refs): typeof Handlebars => {

handlebars.log = (level, ...messages) => {
const levels = ['debug', 'info', 'warn', 'error'];
const actualLevel = typeof level === 'string' ? (levels.includes(level) ? level : 'info') : levels.at(level) ?? 'info';
const actualLevel = typeof level === 'string' ? (levels.includes(level) ? level : 'info') : (levels.at(level) ?? 'info');
(handlebars.logger as any)['actualLogger'][actualLevel](...messages);
};

Expand Down
23 changes: 23 additions & 0 deletions src/optimizer/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export class Converter {
return this.convertArray(parsedNode, parsedComponents, components);
} else if (parsed.isUnion(parsedNode)) {
return this.convertUnion(parsedNode, parsedComponents, components);
} else if (parsed.isXor(parsedNode)) {
return this.convertXor(parsedNode, parsedComponents, components);
} else if (parsed.isComposite(parsedNode)) {
return this.convertComposite(parsedNode, parsedComponents, components);
} else if (parsed.isExclusion(parsedNode)) {
Expand Down Expand Up @@ -136,16 +138,37 @@ export class Converter {
if (parsedNode.definitions.length === 1) {
return this.convertParsedNode(parsedNode.definitions[0], parsedComponents, components);
}

let discriminatorPropertyName: string | undefined;
let discriminatorMapping: Record<string, optimized.OptimizedNode> | undefined;

if ('discriminatorPropertyName' in parsedNode && parsedNode.discriminatorPropertyName) {
discriminatorPropertyName = parsedNode.discriminatorPropertyName;

if (parsedNode.discriminatorMapping) {
discriminatorMapping = {};
for (const [key, value] of Object.entries(parsedNode.discriminatorMapping)) {
discriminatorMapping[key] = this.convertParsedNode(value, parsedComponents, components);
}
}
}

return {
...parsedNode,
definitions: parsedNode.definitions.map(p => this.convertParsedNode(p, parsedComponents, components)),
discriminatorPropertyName,
discriminatorMapping,
};
}

private convertUnion(parsedNode: parsed.Union, parsedComponents: parsed.Components, components: optimized.Components) {
return this.compress(parsedNode, parsedComponents, components);
}

private convertXor(parsedNode: parsed.Xor, parsedComponents: parsed.Components, components: optimized.Components) {
return this.compress(parsedNode, parsedComponents, components);
}

private convertComposite(parsedNode: parsed.Composite, parsedComponents: parsed.Components, components: optimized.Components) {
return this.compress(parsedNode, parsedComponents, components);
}
Expand Down
1 change: 1 addition & 0 deletions src/optimizer/nodes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './header.js';
export * from './array.js';
export * from './union.js';
export * from './composite.js';
export * from './xor.js';
export * from './exclusion.js';
export * from './request.js';
export * from './tag.js';
Expand Down
2 changes: 2 additions & 0 deletions src/optimizer/nodes/union.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { OptimizedNode } from './optimized.node.js';

export interface Union extends OptimizedNode {
definitions: OptimizedNode[];
discriminatorPropertyName?: string;
discriminatorMapping?: Record<string, OptimizedNode>;
}

export const isUnion = (value: OptimizedNode): value is Union => {
Expand Down
11 changes: 11 additions & 0 deletions src/optimizer/nodes/xor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { OptimizedNode } from './optimized.node.js';

export interface Xor extends OptimizedNode {
definitions: OptimizedNode[];
discriminatorPropertyName?: string;
discriminatorMapping?: Record<string, OptimizedNode>;
}

export const isXor = (value: OptimizedNode): value is Xor => {
return value.type === 'xor';
};
2 changes: 1 addition & 1 deletion src/optimizer/openapi.optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ export class OpenApiOptimizer {
pathRegex = { ...pathRegex, [name]: definition.format ?? definition.type };
}
});
} else if (optimized.isComposite(found) || optimized.isUnion(found)) {
} else if (optimized.isComposite(found) || optimized.isUnion(found) || optimized.isXor(found)) {
// todo need to recursively check the definitions

found.definitions?.forEach(d => {
Expand Down
29 changes: 28 additions & 1 deletion src/parser/openapi.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -908,13 +908,40 @@ export abstract class OpenApiParser {

const definitions = oneOf.map(x => this.parseSchema(x, schema.type));

let discriminatorPropertyName: string | undefined;
let discriminatorMapping: Record<string, ParsedNode> | undefined;

if (schema.discriminator) {
discriminatorPropertyName = schema.discriminator.propertyName;

if (schema.discriminator.mapping) {
discriminatorMapping = {};
for (const [key, value] of Object.entries(schema.discriminator.mapping)) {
// Parse the reference string into a ParsedNode
const parsedValue = this.parseDiscriminatorReference(value);
discriminatorMapping[key] = parsedValue;
}
}
}

return {
type: 'union',
type: 'xor',
...modifiers,
definitions,
discriminatorPropertyName,
discriminatorMapping,
};
}

private parseDiscriminatorReference(referenceString: string): ParsedNode {
// Create a reference object
const referenceObject: ReferenceObject = {
$ref: referenceString,
};

return this.createReference(referenceObject);
}

private parseNotObject(schema: ReferenceObject | SchemaObject, modifiers: Modifiers): Exclusion {
return {
type: 'exclusion',
Expand Down
4 changes: 4 additions & 0 deletions src/parser/organizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ export class Organizer {
node.definitions.forEach(x => {
this.traverseReferences(originalDocument, components, x);
});
} else if (parsed.isXor(node)) {
node.definitions.forEach(x => {
this.traverseReferences(originalDocument, components, x);
});
}
}

Expand Down
1 change: 1 addition & 0 deletions src/parser/parsed_nodes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './primative.js';
export * from './object.js';
export * from './composite.js';
export * from './union.js';
export * from './xor.js';
export * from './exclusion.js';
export * from './array.js';
export * from './link.js';
Expand Down
2 changes: 1 addition & 1 deletion src/parser/parsed_nodes/modifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const compareModifiers = (a: Modifiers, b: Modifiers): boolean => {
//a.required === b.required &&
//a.enum === b.enum &&
a.nullable === b.nullable &&
//a.discriminator === b.discriminator &&
a.discriminator === b.discriminator &&
a.readOnly === b.readOnly &&
a.writeOnly === b.writeOnly &&
a.example === b.example &&
Expand Down
2 changes: 2 additions & 0 deletions src/parser/parsed_nodes/union.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { ParsedNode } from './parsed.node.js';

export interface Union extends ParsedNode {
definitions: ParsedNode[];
discriminatorPropertyName?: string;
discriminatorMapping?: Record<string, ParsedNode>;
}

export const isUnion = (value: ParsedNode): value is Union => {
Expand Down
11 changes: 11 additions & 0 deletions src/parser/parsed_nodes/xor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ParsedNode } from './parsed.node.js';

export interface Xor extends ParsedNode {
definitions: ParsedNode[];
discriminatorPropertyName?: string;
discriminatorMapping?: Record<string, ParsedNode>;
}

export const isXor = (value: ParsedNode): value is Xor => {
return value.type === 'xor';
};
1 change: 1 addition & 0 deletions templates/server/model.expression.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
{{~#compare type "===" "reference"}}{{& prefix}}{{>lookupReference name=$ref type='models'}}{{& suffix}}{{/compare}}
{{~#compare type "===" "composite"}}{{#if definitions}}{{#forEach definitions}}{{>model.expression .}}{{#unless isLast}}&{{/unless}}{{/forEach}}{{/if}}{{/compare}}
{{~#compare type "===" "union"}}{{#if definitions}}{{#forEach definitions}}{{>model.expression .}}{{#unless isLast}}|{{/unless}}{{/forEach}}{{/if}}{{/compare}}
{{~#compare type "===" "xor"}}{{#if definitions}}{{#forEach definitions}}{{>model.expression .}}{{#unless isLast}}|{{/unless}}{{/forEach}}{{/if}}{{/compare}}
{{~#compare type "===" "responseObject"}}
{
$status: {{status}};
Expand Down
13 changes: 7 additions & 6 deletions templates/server/validation.declaration.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const {{& replace (replace name (toRegex "(-|\.| )+" "g") "_") (toRegex "[_-]([a-z])" "gi") (function "(_, x)=>x.toUpperCase()") }}{{& suffix}}:utilities.ValidatorFn = ({{inputName}}: any): utilities.ValidatorResponse => {
let {{errorsName}} = new Map<string, string | string[]>();
const allowedProperties: string[] = [];
{{>validation.expression . inputName=./inputName errorsName=./errorsName}}
return { {{errorsName}}, allowedProperties};
}
{{#if (and (compare type "===" "xor") discriminatorMapping)}}
{{>validation.declaration.xor.discriminator suffix=suffix inputName=inputName errorsName=errorsName}}
{{else or (compare type "===" "union") (compare type "===" "xor")}}
{{>validation.declaration.union suffix=suffix inputName=inputName errorsName=errorsName}}
{{else}}
{{>validation.declaration.single suffix=suffix inputName=inputName errorsName=errorsName}}
{{/if}}
6 changes: 6 additions & 0 deletions templates/server/validation.declaration.single.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const {{& replace (replace name (toRegex "(-|\.| )+" "g") "_") (toRegex "[_-]([a-z])" "gi") (function "(_, x)=>x.toUpperCase()") }}{{& suffix}}:utilities.ValidatorFn = ({{inputName}}: any): utilities.ValidatorResponse => {
let {{errorsName}} = new Map<string, string | string[]>();
const allowedProperties: string[] = [];
{{>validation.expression . inputName=./inputName errorsName=./errorsName}}
return { {{errorsName}}, allowedProperties};
}
55 changes: 55 additions & 0 deletions templates/server/validation.declaration.union.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

export const {{& replace (replace name (toRegex "(-|\.| )+" "g") "_") (toRegex "[_-]([a-z])" "gi") (function "(_, x)=>x.toUpperCase()") }}{{& suffix}}:utilities.ValidatorFn = ({{inputName}}: any): utilities.ValidatorResponse => {
const validatorArray = new Array<utilities.ValidatorFn>();
const validatorErrorArray = new Array<Map<string, string | string[]>>();
const allowedPropertiesArray = new Array<string[]>();

{{#each definitions}}

validatorArray.push(({{../inputName}}) => {
let {{../errorsName}} = new Map<string, string | string[]>();
let allowedProperties = new Array<string>();
{{>validation.expression this inputName=../inputName errorsName=../errorsName}}

validatorErrorArray.push(validationErrors);
allowedPropertiesArray.push(allowedProperties);
});

{{/each}}
for (let i = 0; i < validatorArray.length; ++i) {
const validatorResponse = validatorArray[i]({{inputName}});
if (validatorResponse.validationErrors.size === 0) {
return validatorResponse;
}
}
// merge all errors and allowedProperties
const mergedValidationErrors = new Map<string, string | string[]>();
const mergedAllowedProperties: string[] = [];

for (let i = 0; i < validatorArray.length; ++i) {
const validatorResponse = validatorArray[i]({{inputName}});
// Early return if any validator has no errors
if (validatorResponse.validationErrors.size === 0) {
return {validationErrors: validatorResponse.validationErrors, allowedProperties: validatorResponse.allowedProperties};
}

for (const [key, value] of validatorResponse.validationErrors.entries()) {
if (mergedValidationErrors.has(key)) {
// Merge error messages for the same key
const existing = mergedValidationErrors.get(key);
if (Array.isArray(existing)) {
mergedValidationErrors.set(key, existing.concat(value));
} else if (existing !== undefined) {
mergedValidationErrors.set(key, [existing, value].flat());
}
} else {
mergedValidationErrors.set(key, value);
}
}
mergedAllowedProperties.push(...validatorResponse.allowedProperties);
}
return {
validationErrors: mergedValidationErrors,
allowedProperties: mergedAllowedProperties
}
};
27 changes: 27 additions & 0 deletions templates/server/validation.declaration.xor.discriminator.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

type {{& replace (replace name (toRegex "(-|\.| )+" "g") "_") (toRegex "[_-]([a-z])" "gi") (function "(_, x)=>x.toUpperCase()") }}{{& suffix}}PropertyName = "base" | "inherited";

export const {{& replace (replace name (toRegex "(-|\.| )+" "g") "_") (toRegex "[_-]([a-z])" "gi") (function "(_, x)=>x.toUpperCase()") }}{{& suffix}}:utilities.ValidatorFn = ({{inputName}}: any): utilities.ValidatorResponse => {
const validatorMap = new Map<string, utilities.ValidatorFn>();
const validatorErrorMap = new Map<{{& replace (replace name (toRegex "(-|\.| )+" "g") "_") (toRegex "[_-]([a-z])" "gi") (function "(_, x)=>x.toUpperCase()") }}{{& suffix}}PropertyName, Map<string, string | string[]>>();
const allowedPropertiesMap = new Map<{{& replace (replace name (toRegex "(-|\.| )+" "g") "_") (toRegex "[_-]([a-z])" "gi") (function "(_, x)=>x.toUpperCase()") }}{{& suffix}}PropertyName, string[]>();

const discriminatorPropertyName = "{{ discriminatorPropertyName}}";
{{#each discriminatorMapping}}
validatorMap.set('{{@key}}', ({{../inputName}}) => {
let {{../errorsName}} = new Map<string, string | string[]>();
let allowedProperties = new Array<string>();
{{>validation.expression this inputName=../inputName errorsName=../errorsName}}

validatorErrorMap.set('{{@key}}', new Map<string, string | string[]>(validatorResponse.validationErrors));
allowedPropertiesMap.set('{{@key}}', validatorResponse.allowedProperties);
});

{{/each}}
const discriminatorValue = {{./inputName}}[discriminatorPropertyName];
const validatorResponse = validatorMap.get(discriminatorValue)({{./inputName}});
return {
validationErrors: validatorResponse.validationErrors,
allowedProperties: validatorResponse.allowedProperties
}
};
2 changes: 1 addition & 1 deletion templates/server/validation.expression.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ if({{safeName}} !== undefined) {
{{/compare}}
{{>format}}

{{else or (compare type "===" "composite") (compare type "===" "union")}}
{{else or (compare type "===" "composite") (compare type "===" "union") (compare type "===" "xor")}}
{{#each definitions}}{{>validation.expression . inputName=../inputName errorsName=../errorsName}}{{/each}}
{{/if~}}

Expand Down
Loading
Loading