- Abstracted model validation and properties

This commit is contained in:
Ferdi Koomen 2019-11-17 20:50:36 +01:00
parent d6131dc780
commit ea703d27dc
38 changed files with 293 additions and 286 deletions

View File

@ -1,15 +1,18 @@
import { ModelProperty } from './ModelProperty';
import { Enum } from './Enum';
import { Validation } from './Validation';
export interface Model {
name: string;
type: string;
base: string;
template: string | null;
description: string | null;
validation: string | null;
extends: string[];
template?: string;
description?: string;
readOnly?: boolean;
required?: boolean;
nullable?: boolean;
imports: string[];
extends: string[];
enum: Enum[];
properties: ModelProperty[];
properties: Model[];
validation?: Validation;
}

View File

@ -1,11 +0,0 @@
export interface ModelProperty {
name: string;
type: string;
base: string;
template: string | null;
readOnly: boolean;
required: boolean;
nullable: boolean;
description: string | null;
validation: string | null;
}

View File

@ -4,8 +4,8 @@ import { OperationParameters } from './OperationParameters';
export interface Operation extends OperationParameters {
service: string;
name: string;
summary: string | null;
description: string | null;
summary?: string;
description?: string;
deprecated: boolean;
method: string;
path: string;

View File

@ -4,9 +4,9 @@ export interface OperationParameter {
name: string;
type: string;
base: string;
template: string | null;
description: string | null;
default: any | undefined;
template?: string;
description?: string;
default?: any;
required: boolean;
nullable: boolean;
imports: string[];

View File

@ -7,5 +7,5 @@ export interface OperationParameters {
parametersQuery: OperationParameter[];
parametersForm: OperationParameter[];
parametersHeader: OperationParameter[];
parametersBody: OperationParameter | null;
parametersBody?: OperationParameter;
}

View File

@ -3,6 +3,6 @@ export interface OperationResponse {
text: string;
type: string;
base: string;
template: string | null;
template?: string;
imports: string[];
}

View File

@ -1,6 +1,6 @@
export interface Type {
type: string;
base: string;
template: string | null;
template?: string;
imports: string[];
}

6
src/client/interfaces/Validation.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
export interface Validation {
type: 'ref' | 'type' | 'enum' | 'array' | 'dictionary' | 'property' | 'model';
childType?: string | null;
childBase?: string | null;
childValidation?: Validation;
}

View File

@ -5,8 +5,8 @@ import { OpenApiItems } from '../interfaces/OpenApiItems';
export interface ArrayType {
type: string;
base: string;
template: string | null;
default: any | undefined;
template?: string;
default?: any;
imports: string[];
}
@ -14,7 +14,6 @@ export function getArrayType(items: OpenApiItems): ArrayType {
const result: ArrayType = {
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
template: null,
default: items.default,
imports: [],
};

View File

@ -1,6 +1,6 @@
import { EOL } from 'os';
export function getComment(comment: string | undefined): string | null {
export function getComment(comment: string | undefined): string | undefined {
if (comment) {
return comment
.split(/(\r\n|\n|\r)+/g)
@ -9,5 +9,5 @@ export function getComment(comment: string | undefined): string | null {
.join(EOL)
.replace(/(\r\n|\n|\r)+/g, '$1 * ');
}
return null;
return undefined;
}

View File

@ -1,36 +1,24 @@
import {OpenApi} from '../interfaces/OpenApi';
import {OpenApiSchema} from '../interfaces/OpenApiSchema';
import {getComment} from './getComment';
import {getType} from './getType';
import {Model} from '../../../client/interfaces/Model';
import {PrimaryType} from './constants';
import {getEnumType} from './getEnumType';
import {getEnum} from './getEnum';
import {getEnumFromDescription} from './getEnumFromDescription';
import {getTypeFromProperties} from './getTypeFromProperties';
import {getValidationForRef} from './getValidationForRef';
import {getValidationForEnum} from './getValidationForEnum';
import {getValidationForArrayRef} from './getValidationForArrayRef';
import {getValidationForType} from './getValidationForType';
import {getValidationForArray} from './getValidationForArray';
import {getValidationForProperties} from './getValidationForProperties';
import { OpenApi } from '../interfaces/OpenApi';
import { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { getComment } from './getComment';
import { getType } from './getType';
import { Model } from '../../../client/interfaces/Model';
import { PrimaryType } from './constants';
import { getEnumType } from './getEnumType';
import { getEnum } from './getEnum';
import { getEnumFromDescription } from './getEnumFromDescription';
import { getTypeFromProperties } from './getTypeFromProperties';
import { getModelProperties } from './getModelProperties';
export function getModel(openApi: OpenApi, definition: OpenApiSchema, name: string = ''): Model {
// TODO: Properties now contain ALL properties, so we need to filter out enums
// before we render the file, plus we need to calculate the final TYPE of a model
// by checking all the properties!
// After this we also need to calculate the validation
// this should all be done in a cleanup / prepare phase, not in this parsing phase
const result: Model = {
name,
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
template: null,
description: getComment(definition.description),
validation: null,
extends: [],
readOnly: definition.readOnly,
imports: [],
extends: [],
enum: [],
properties: [],
};
@ -41,7 +29,9 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, name: stri
result.base = definitionRef.base;
result.template = definitionRef.template;
result.imports.push(...definitionRef.imports);
result.validation = getValidationForRef(definitionRef);
result.validation = {
type: 'ref',
};
return result;
}
@ -52,7 +42,9 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, name: stri
result.type = getEnumType(enumerators);
result.base = PrimaryType.STRING;
result.enum.push(...enumerators);
result.validation = getValidationForEnum(name, enumerators);
result.validation = {
type: 'enum',
};
return result;
}
return result;
@ -65,7 +57,9 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, name: stri
result.type = getEnumType(enumerators);
result.base = PrimaryType.NUMBER;
result.enum.push(...enumerators);
result.validation = getValidationForEnum(name, enumerators);
result.validation = {
type: 'enum',
};
return result;
}
return result;
@ -80,117 +74,27 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, name: stri
result.base = arrayItems.base;
result.template = arrayItems.template;
result.imports.push(...arrayItems.imports);
result.validation = getValidationForArrayRef(arrayItems);
result.validation = {
type: 'array',
childType: arrayItems.type,
childBase: arrayItems.base,
};
} else {
const arrayItems = getModel(openApi, definition.items);
result.type = `${arrayItems.type}[]`;
result.base = arrayItems.base;
result.template = arrayItems.template;
result.imports.push(...arrayItems.imports);
result.validation = getValidationForArray(name, arrayItems);
result.validation = {
type: 'array',
childType: arrayItems.type,
childBase: arrayItems.base,
childValidation: arrayItems.validation,
};
}
return result;
}
// Check if this model extends other models
if (definition.allOf) {
definition.allOf.forEach(parent => {
if (parent.$ref) {
const parentRef = getType(parent.$ref);
result.extends.push(parentRef.type);
result.imports.push(parentRef.base);
}
if (parent.type === 'object' && parent.properties) {
for (const propertyName in parent.properties) {
if (parent.properties.hasOwnProperty(propertyName)) {
const property = parent.properties[propertyName];
const propertyRequired = !!(parent.required && parent.required.includes(propertyName));
const propertyReadOnly = !!property.readOnly;
if (property.$ref) {
const prop = getType(property.$ref);
result.base = PrimaryType.OBJECT;
result.imports.push(...prop.imports);
result.properties.push({
name: propertyName,
type: prop.type,
base: prop.base,
template: prop.template,
readOnly: propertyReadOnly,
required: propertyRequired,
nullable: false,
description: property.description || null,
validation: getValidationForRef(prop),
});
} else {
const prop = getModel(openApi, property);
result.base = PrimaryType.OBJECT;
result.imports.push(...prop.imports);
result.properties.push({
name: propertyName,
type: prop.type,
base: prop.base,
template: prop.template,
readOnly: propertyReadOnly,
required: propertyRequired,
nullable: false,
description: property.description || null,
validation: prop.validation,
});
}
}
}
}
});
result.type = getTypeFromProperties(result.properties);
result.base = PrimaryType.OBJECT;
result.validation = getValidationForProperties(name, result.properties, result.extends);
}
if (definition.type === 'object' && definition.properties) {
for (const propertyName in definition.properties) {
if (definition.properties.hasOwnProperty(propertyName)) {
const property = definition.properties[propertyName];
const propertyRequired = !!(definition.required && definition.required.includes(propertyName));
const propertyReadOnly = !!property.readOnly;
if (property.$ref) {
const prop = getType(property.$ref);
result.base = PrimaryType.OBJECT;
result.imports.push(...prop.imports);
result.properties.push({
name: propertyName,
type: prop.type,
base: prop.base,
template: prop.template,
readOnly: propertyReadOnly,
required: propertyRequired,
nullable: false,
description: property.description || null,
validation: getValidationForRef(prop),
});
} else {
const prop = getModel(openApi, property);
result.base = PrimaryType.OBJECT;
result.imports.push(...prop.imports);
result.properties.push({
name: propertyName,
type: prop.type,
base: prop.base,
template: prop.template,
readOnly: propertyReadOnly,
required: propertyRequired,
nullable: false,
description: property.description || null,
validation: prop.validation,
});
}
}
}
result.type = getTypeFromProperties(result.properties);
result.base = PrimaryType.OBJECT;
result.validation = getValidationForProperties(name, result.properties, result.extends);
return result;
}
// If a property has additionalProperties, then it likely to be a dictionary type.
// In that case parse the related property and assume it lives inside a string
// based dictionary: { [key:string]: MyType }
@ -201,28 +105,74 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, name: stri
result.base = 'Dictionary';
result.template = additionalProperties.type;
result.imports.push(...additionalProperties.imports);
result.imports.push('Dictionary');
console.log(name, 'Dictionary', result.type);
result.validation = {
type: 'dictionary',
childType: additionalProperties.type,
childBase: additionalProperties.base,
};
} else {
const additionalProperties = getModel(openApi, definition.additionalProperties);
result.type = `Dictionary<${additionalProperties.type}>`;
result.base = 'Dictionary';
result.template = additionalProperties.type;
result.imports.push(...additionalProperties.imports);
result.imports.push('Dictionary');
console.log(name, 'Dictionary', result.type);
result.validation = {
type: 'dictionary',
childType: additionalProperties.type,
childBase: additionalProperties.base,
childValidation: additionalProperties.validation,
};
}
return result;
}
// Check if this model extends other models
if (definition.allOf) {
definition.allOf.forEach(parent => {
if (parent.$ref) {
const parentRef = getType(parent.$ref);
result.extends.push(parentRef.type);
result.imports.push(parentRef.base);
}
if (parent.type === 'object' && parent.properties) {
const properties = getModelProperties(openApi, parent);
properties.forEach(property => {
result.properties.push(property);
result.imports.push(...property.imports);
});
}
});
result.type = getTypeFromProperties(result.properties);
result.base = PrimaryType.OBJECT;
result.validation = {
type: 'model',
};
}
if (definition.type === 'object' && definition.properties) {
const properties = getModelProperties(openApi, definition);
properties.forEach(property => {
result.properties.push(property);
result.imports.push(...property.imports);
});
result.type = getTypeFromProperties(result.properties);
result.base = PrimaryType.OBJECT;
result.validation = {
type: 'model',
};
return result;
}
// If the schema has a type than it can be a basic or generic type.
if (definition.type !== 'object' && definition.type) {
if (definition.type) {
const definitionType = getType(definition.type);
result.type = definitionType.type;
result.base = definitionType.base;
result.template = definitionType.template;
result.imports.push(...definitionType.imports);
result.validation = getValidationForType(definitionType);
result.validation = {
type: 'type',
};
return result;
}

View File

@ -0,0 +1,62 @@
import { OpenApi } from '../interfaces/OpenApi';
import { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { getComment } from './getComment';
import { getType } from './getType';
import { Model } from '../../../client/interfaces/Model';
import { OpenApiReference } from '../interfaces/OpenApiReference';
import { getModel } from './getModel';
export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema & OpenApiReference): Model[] {
const result: Model[] = [];
for (const propertyName in definition.properties) {
if (definition.properties.hasOwnProperty(propertyName)) {
const property = definition.properties[propertyName];
const propertyRequired = definition.required && definition.required.includes(propertyName);
const propertyReadOnly = property.readOnly;
if (property.$ref) {
const prop = getType(property.$ref);
result.push({
name: propertyName,
type: prop.type,
base: prop.base,
template: prop.template,
description: getComment(definition.description),
readOnly: propertyReadOnly,
required: propertyRequired,
imports: prop.imports,
extends: [],
enum: [],
properties: [],
validation: {
type: 'property',
childType: prop.type,
childBase: prop.base,
},
});
} else {
const prop = getModel(openApi, property);
result.push({
name: propertyName,
type: prop.type,
base: prop.base,
template: prop.template,
description: property.description,
readOnly: propertyReadOnly,
required: propertyRequired,
imports: prop.imports,
extends: prop.extends,
enum: prop.enum,
properties: prop.properties,
validation: {
type: 'property',
childType: prop.type,
childBase: prop.base,
childValidation: prop.validation,
},
});
}
}
}
return result;
}

View File

@ -15,7 +15,6 @@ describe('getModelTemplate', () => {
const template = getModelTemplate({
type: 'string',
base: 'string',
template: null,
imports: [],
});
expect(template).toEqual('');

View File

@ -7,5 +7,5 @@ import { Type } from '../../../client/interfaces/Type';
* @returns The model template type (<T> or empty).
*/
export function getModelTemplate(modelClass: Type): string {
return modelClass.template !== null ? '<T>' : '';
return modelClass.template ? '<T>' : '';
}

View File

@ -1,12 +0,0 @@
import { EOL } from 'os';
import { ModelProperty } from '../../../client/interfaces/ModelProperty';
export function getModelValidation(name: string, properties: ModelProperty[]): string {
return [
`yup.object().shape({`,
// ...properties.map(property => {
// return ` ${property.name}: ${property.validation},`;
// }),
`}).noUnknown()`,
].join(EOL);
}

View File

@ -33,7 +33,6 @@ export function getOperation(openApi: OpenApi, url: string, method: string, op:
parametersQuery: [],
parametersForm: [],
parametersHeader: [],
parametersBody: null,
imports: [],
errors: [],
result: PrimaryType.VOID,

View File

@ -12,7 +12,6 @@ export function getOperationParameter(openApi: OpenApi, parameter: OpenApiParame
name: getOperationParameterName(parameter.name),
type: 'any',
base: 'any',
template: null,
description: getComment(parameter.description),
default: parameter.default,
required: parameter.required || false,

View File

@ -18,7 +18,6 @@ export function getOperationParameters(openApi: OpenApi, parameters: (OpenApiPar
parametersQuery: [],
parametersForm: [],
parametersHeader: [],
parametersBody: null,
};
// Iterate over the parameters

View File

@ -2,25 +2,17 @@ import { PrimaryType } from './constants';
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
export function getOperationResponse(responses: OperationResponse[]): OperationResponse {
const response: OperationResponse = {
// Fetch the first valid (2XX range) response code and return that type.
const result = responses.find(response => response.code && response.code >= 200 && response.code < 300);
if (result) {
return result;
}
return {
code: 200,
text: '',
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
template: null,
imports: [],
};
// Fetch the first valid (2XX range) response code and return that type.
const result = responses.find(response => response.code && response.code >= 200 && response.code < 300);
if (result) {
response.code = result.code;
response.text = result.text;
response.type = result.type;
response.base = result.base;
response.template = result.template;
response.imports.push(...result.imports);
}
return response;
}

View File

@ -25,7 +25,6 @@ export function getOperationResponses(openApi: OpenApi, responses: OpenApiRespon
text: response.description || '',
type: 'any',
base: 'any',
template: null,
imports: [],
};

View File

@ -2,31 +2,31 @@ import { getType } from './getType';
describe('getType', () => {
it('should convert int', () => {
const type = getType('int', null);
const type = getType('int');
expect(type.type).toEqual('number');
expect(type.base).toEqual('number');
expect(type.template).toEqual(null);
expect(type.template).toEqual(undefined);
expect(type.imports).toEqual([]);
});
it('should convert string', () => {
const type = getType('String', null);
const type = getType('String');
expect(type.type).toEqual('string');
expect(type.base).toEqual('string');
expect(type.template).toEqual(null);
expect(type.template).toEqual(undefined);
expect(type.imports).toEqual([]);
});
it('should convert string array', () => {
const type = getType('Array[String]', null);
const type = getType('Array[String]');
expect(type.type).toEqual('string[]');
expect(type.base).toEqual('string');
expect(type.template).toEqual(null);
expect(type.template).toEqual(undefined);
expect(type.imports).toEqual([]);
});
it('should convert template with primary', () => {
const type = getType('#/definitions/Link[String]', null);
const type = getType('#/definitions/Link[String]');
expect(type.type).toEqual('Link<string>');
expect(type.base).toEqual('Link');
expect(type.template).toEqual('string');
@ -34,7 +34,7 @@ describe('getType', () => {
});
it('should convert template with model', () => {
const type = getType('#/definitions/Link[Model]', null);
const type = getType('#/definitions/Link[Model]');
expect(type.type).toEqual('Link<Model>');
expect(type.base).toEqual('Link');
expect(type.template).toEqual('Model');
@ -42,7 +42,7 @@ describe('getType', () => {
});
it('should have double imports', () => {
const type = getType('#/definitions/Link[Link]', null);
const type = getType('#/definitions/Link[Link]');
expect(type.type).toEqual('Link<Link>');
expect(type.base).toEqual('Link');
expect(type.template).toEqual('Link');
@ -53,7 +53,7 @@ describe('getType', () => {
const type = getType('#/definitions/Link', 'Link');
expect(type.type).toEqual('T');
expect(type.base).toEqual('T');
expect(type.template).toEqual(null);
expect(type.template).toEqual(undefined);
expect(type.imports).toEqual([]);
});
});

View File

@ -8,11 +8,10 @@ import { PrimaryType } from './constants';
* @param value String value like "integer" or "Link[Model]".
* @param template Optional template class from parent (needed to process generics)
*/
export function getType(value: string | undefined, template: string | null = null): Type {
export function getType(value: string | undefined, template?: string): Type {
const result: Type = {
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
template: null,
imports: [],
};

View File

@ -1,7 +1,7 @@
import { EOL } from 'os';
import { ModelProperty } from '../../../client/interfaces/ModelProperty';
import { Model } from '../../../client/interfaces/Model';
export function getTypeFromProperties(properties: ModelProperty[]): string {
export function getTypeFromProperties(properties: Model[]): string {
return [
`{`,
...properties.map(property => {

View File

@ -0,0 +1,16 @@
import { Model } from '../../../client/interfaces/Model';
import { EOL } from 'os';
export function getValidationForDictionary(name: string, model: Model): string {
/* prettier-ignore */
return [
`yup.lazy<Dictionary<${model.type}>>(value =>`,
`yup.object<Dictionary<${model.type}>>().shape(`,
`Object.entries(value).reduce((obj, item) => ({`,
`...obj,`,
`[item[0]]: ${model.validation ? model.validation : 'yup.mixed()'},`,
`}), {})`,
`)`,
`)`
].join(EOL);
}

View File

@ -0,0 +1,16 @@
import { Type } from '../../../client/interfaces/Type';
import { EOL } from 'os';
export function getValidationForDictionaryRef(type: Type): string {
/* prettier-ignore */
return [
`yup.lazy<Dictionary<${type.type}>>(value =>`,
`yup.object<Dictionary<${type.type}>>().shape(`,
`Object.entries(value).reduce((obj, item) => ({`,
`...obj,`,
`[item[0]]: ${type.base}.schema,`,
`}), {})`,
`)`,
`)`
].join(EOL);
}

View File

@ -1,11 +1,11 @@
import { EOL } from 'os';
import { ModelProperty } from '../../../client/interfaces/ModelProperty';
import { Model } from '../../../client/interfaces/Model';
export function getValidationForProperties(name: string, properties: ModelProperty[], extendClasses: string[]): string {
export function getValidationForProperties(name: string, model: Model): string {
return [
...extendClasses.map(extendClass => `${extendClass}.schema.concat(`),
...model.extends.map(extend => `${extend}.schema.concat(`),
`yup.object${name ? `<${name}>` : ''}().shape({`,
...properties.map(property => {
...model.properties.map(property => {
let validation = '';
validation = `${validation}${property.name}: yup.lazy(() => ${property.validation}.default(undefined))`;
validation = `${validation}${property.required ? '.required()' : ''}`;
@ -13,6 +13,6 @@ export function getValidationForProperties(name: string, properties: ModelProper
return `${validation},`;
}),
`}).noUnknown()`,
...extendClasses.map(() => `)`),
...model.extends.map(() => `)`),
].join(EOL);
}

View File

@ -1,12 +1,20 @@
import { getSortedImports } from './getSortedImports';
import { Model } from '../client/interfaces/Model';
export function exportModel(model: Model): any {
return {
...model,
imports: getSortedImports(model.imports).filter(name => {
return model.name !== name;
}),
imports: model.imports
.filter(name => {
return model.name !== name;
})
.filter((name, index, arr) => {
return arr.indexOf(name) === index;
})
.sort((a, b) => {
const nameA = a.toLowerCase();
const nameB = b.toLowerCase();
return nameA.localeCompare(nameB);
}),
properties: model.properties
.filter((property, index, arr) => {
return arr.findIndex(item => item.name === property.name) === index;

View File

@ -1,13 +1,21 @@
import { Service } from '../client/interfaces/Service';
import { getSortedImports } from './getSortedImports';
export function exportService(service: Service): any {
const names = new Map<string, number>();
return {
...service,
imports: getSortedImports(service.imports).filter(name => {
return service.name !== name;
}),
imports: service.imports
.filter(name => {
return service.name !== name;
})
.filter((name, index, arr) => {
return arr.indexOf(name) === index;
})
.sort((a, b) => {
const nameA = a.toLowerCase();
const nameB = b.toLowerCase();
return nameA.localeCompare(nameB);
}),
operations: service.operations
.map(operation => {
const name = operation.name;

View File

@ -1,18 +1,18 @@
import { getSortedModels } from './getSortedModels';
import { getModelNames } from './getModelNames';
import { Model } from '../client/interfaces/Model';
describe('getSortedModels', () => {
describe('getModelNames', () => {
it('should return sorted list', () => {
const models = new Map<string, Model>();
models.set('John', {
name: 'John',
type: 'John',
base: 'John',
template: null,
description: null,
validation: null,
extends: [],
readOnly: false,
required: false,
nullable: false,
imports: [],
extends: [],
enum: [],
properties: [],
});
@ -20,11 +20,11 @@ describe('getSortedModels', () => {
name: 'Jane',
type: 'Jane',
base: 'Jane',
template: null,
description: null,
validation: null,
extends: [],
readOnly: false,
required: false,
nullable: false,
imports: [],
extends: [],
enum: [],
properties: [],
});
@ -32,16 +32,16 @@ describe('getSortedModels', () => {
name: 'Doe',
type: 'Doe',
base: 'Doe',
template: null,
description: null,
validation: null,
extends: [],
readOnly: false,
required: false,
nullable: false,
imports: [],
extends: [],
enum: [],
properties: [],
});
expect(getSortedModels(new Map<string, Model>())).toEqual([]);
expect(getSortedModels(models)).toEqual(['Doe', 'Jane', 'John']);
expect(getModelNames(new Map<string, Model>())).toEqual([]);
expect(getModelNames(models)).toEqual(['Doe', 'Jane', 'John']);
});
});

View File

@ -1,6 +1,6 @@
import { Model } from '../client/interfaces/Model';
export function getSortedModels(models: Map<string, Model>): string[] {
export function getModelNames(models: Map<string, Model>): string[] {
return Array.from(models.values())
.sort((a, b) => {
const nameA = a.name.toLowerCase();

View File

@ -1,7 +1,7 @@
import { getSortedServices } from './getSortedServices';
import { getServiceNames } from './getServiceNames';
import { Service } from '../client/interfaces/Service';
describe('getSortedServices', () => {
describe('getServiceNames', () => {
it('should return sorted list', () => {
const services = new Map<string, Service>();
services.set('John', {
@ -20,7 +20,7 @@ describe('getSortedServices', () => {
imports: [],
});
expect(getSortedServices(new Map<string, Service>())).toEqual([]);
expect(getSortedServices(services)).toEqual(['Doe', 'Jane', 'John']);
expect(getServiceNames(new Map<string, Service>())).toEqual([]);
expect(getServiceNames(services)).toEqual(['Doe', 'Jane', 'John']);
});
});

View File

@ -1,6 +1,6 @@
import { Service } from '../client/interfaces/Service';
export function getSortedServices(services: Map<string, Service>): string[] {
export function getServiceNames(services: Map<string, Service>): string[] {
return Array.from(services.values())
.sort((a, b) => {
const nameA = a.name.toLowerCase();

View File

@ -1,14 +0,0 @@
import { getSortedImports } from './getSortedImports';
describe('getSortedImports', () => {
it('should return sorted list', () => {
const arr = ['a', 'b', 'c'];
expect(getSortedImports([])).toEqual([]);
expect(getSortedImports([...arr])).toEqual(arr);
expect(getSortedImports([...arr].reverse())).toEqual(arr);
expect(getSortedImports([' ', ...arr])).toEqual(arr);
expect(getSortedImports([...arr, ' '])).toEqual(arr);
expect(getSortedImports([...arr, ...arr])).toEqual(arr);
});
});

View File

@ -1,17 +0,0 @@
/**
* Sort a list of strings and filter out any duplicates.
* @param imports List of strings.
*/
export function getSortedImports(imports: string[]): string[] {
return imports
.filter(name => name)
.filter(name => name.trim())
.filter((name, index, arr) => {
return arr.indexOf(name) === index;
})
.sort((a, b) => {
const nameA = a.toLowerCase();
const nameB = b.toLowerCase();
return nameA.localeCompare(nameB, 'en');
});
}

View File

@ -2,8 +2,8 @@ import { Client } from '../client/interfaces/Client';
import * as handlebars from 'handlebars';
import * as fs from 'fs';
import * as path from 'path';
import { getSortedModels } from './getSortedModels';
import { getSortedServices } from './getSortedServices';
import { getModelNames } from './getModelNames';
import { getServiceNames } from './getServiceNames';
import { Language } from '../index';
import { getFileName } from './getFileName';
@ -24,8 +24,8 @@ export function writeClientIndex(client: Client, language: Language, template: h
template({
server: client.server,
version: client.version,
models: getSortedModels(client.models),
services: getSortedServices(client.services),
models: getModelNames(client.models),
services: getServiceNames(client.services),
})
);
} catch (e) {

View File

@ -14,11 +14,11 @@ describe('writeClientModels', () => {
name: 'Item',
type: 'Item',
base: 'Item',
template: null,
description: null,
validation: null,
extends: [],
readOnly: false,
required: false,
nullable: false,
imports: [],
extends: [],
enum: [],
properties: [],
});

View File

@ -102,6 +102,13 @@
"type": "string"
}
},
"DictionaryWithReference": {
"description": "This is a string reference",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ModelWithString"
}
},
"DictionaryWithArray": {
"description": "This is a complex dictionary",
"type": "object",