Skip to content
Open
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
10 changes: 9 additions & 1 deletion lib/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ export class Content {
public mediaType: string,
public spec: MediaTypeObject,
public options: Options,
public openApi: OpenAPIObject) {
public openApi: OpenAPIObject,
method?: string) {
const readOnly =
(method && ['post', 'put'].includes(method) && options.experimental) ||
false;
this.type = tsType(spec.schema, options, openApi);

if (readOnly) {
this.type = `Utils.Writable<${this.type}>`;
}
}
}
8 changes: 8 additions & 0 deletions lib/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ export class Globals {
requestBuilderFile: string;
responseClass: string;
responseFile: string;
utilsFile?: string;
moduleClass?: string;
moduleFile?: string;
modelIndexFile?: string;
serviceIndexFile?: string;
rootUrl?: string;
experimental?: boolean;

constructor(options: Options) {
this.configurationClass = options.configuration || 'ApiConfiguration';
Expand All @@ -40,6 +42,8 @@ export class Globals {
this.requestBuilderFile = fileName(this.requestBuilderClass);
this.responseClass = options.response || 'StrictHttpResponse';
this.responseFile = fileName(this.responseClass);
this.experimental = options.experimental || false;

if (options.module !== false && options.module !== '') {
this.moduleClass = options.module === true || options.module === undefined ? 'ApiModule' : options.module;
// Angular's best practices demands xxx.module.ts, not xxx-module.ts
Expand All @@ -51,6 +55,10 @@ export class Globals {
if (options.modelIndex !== false && options.modelIndex !== '') {
this.modelIndexFile = options.modelIndex === true || options.modelIndex === undefined ? 'models' : options.modelIndex;
}

if (this.experimental) {
this.utilsFile = 'utils';
}
}

}
7 changes: 7 additions & 0 deletions lib/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export class Model extends GenType {
properties: Property[];
additionalPropertiesType: string;

readOnly: boolean;

constructor(public openApi: OpenAPIObject, name: string, public schema: SchemaObject, options: Options) {
super(name, unqualifiedName, options);

Expand Down Expand Up @@ -61,6 +63,8 @@ export class Model extends GenType {
this.isObject = (type === 'object' || !!schema.properties) && !schema.nullable && !hasAllOf && !hasOneOf;
this.isSimple = !this.isObject && !this.isEnum;

this.readOnly = schema.readOnly || false;

if (this.isObject) {
// Object
const propertiesByName = new Map<string, Property>();
Expand Down Expand Up @@ -95,6 +99,7 @@ export class Model extends GenType {
// An object definition
const properties = schema.properties || {};
const required = schema.required || [];
const readOnly = schema.readOnly || false;
const propNames = Object.keys(properties);
// When there are additional properties, we need an union of all types for it.
// See https://github.com/cyclosproject/ng-openapi-gen/issues/68
Expand Down Expand Up @@ -122,6 +127,8 @@ export class Model extends GenType {
appendType(propType);
this.additionalPropertiesType = [...propTypes].sort().join(' | ');
}

this.readOnly = readOnly;
}
if (schema.allOf) {
schema.allOf.forEach(s => this.collectObject(s, propertiesByName));
Expand Down
5 changes: 5 additions & 0 deletions lib/ng-openapi-gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ export class NgOpenApiGen {
this.write('index', { ...general, modelIndex }, 'index');
}

if (this.globals.utilsFile) {
this.write('utils', general, this.globals.utilsFile);
}

// Now synchronize the temp to the output folder
syncDirs(this.tempDir, this.outDir, this.options.removeStaleFiles !== false, this.logger);

Expand All @@ -150,6 +154,7 @@ export class NgOpenApiGen {
private initHandlebars() {
this.handlebarsManager = new HandlebarsManager();
this.handlebarsManager.readCustomJsFile(this.options);
this.handlebarsManager.instance.registerHelper('isWriteMethod', (a) => String(a).toLowerCase() !== 'get');
}

private readTemplates() {
Expand Down
2 changes: 1 addition & 1 deletion lib/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export class Operation {
const result: Content[] = [];
if (desc) {
for (const type of Object.keys(desc)) {
result.push(new Content(type, desc[type] as MediaTypeObject, this.options, this.openApi));
result.push(new Content(type, desc[type] as MediaTypeObject, this.options, this.openApi, this.method));
}
}
return result;
Expand Down
3 changes: 3 additions & 0 deletions lib/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ export interface Options {
};
};

/** Activates experimental features like readOnly */
experimental?: boolean;

/** When specified, will create temporary files in system temporary folder instead of next to output folder. */
useTempDir?: boolean;

Expand Down
3 changes: 3 additions & 0 deletions lib/property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export class Property {
tsComments: string;
type: string;

public readOnly: boolean;

constructor(
public model: Model,
public name: string,
Expand All @@ -22,6 +24,7 @@ export class Property {

this.type = tsType(this.schema, options, openApi, model);
this.identifier = escapeId(this.name);
this.readOnly = (schema as SchemaObject).readOnly || false;
const description = (schema as SchemaObject).description || '';
this.tsComments = tsComments(description, 1, (schema as SchemaObject).deprecated);
}
Expand Down
5 changes: 5 additions & 0 deletions ng-openapi-gen-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@
"default": false,
"type": "boolean"
},
"experimental": {
"description": "When specified, activates experimental features like readOnly ",
"default": false,
"type": "boolean"
},
"customizedResponseType": {
"type": "object",
"description": "Defines which paths to use which responseType, commonly used when build-in deduction can't fullfill your needs",
Expand Down
1 change: 1 addition & 0 deletions templates/fn.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { {{@root.responseClass}} } from '{{pathToRoot}}{{@root.responseFile}}';
import { {{@root.requestBuilderClass}} } from '{{pathToRoot}}{{@root.requestBuilderFile}}';
{{#if (isWriteMethod operation.method)}}{{#if utilsFile}}import * as Utils from '../../{{{utilsFile}}}'; {{/if}}{{/if}}

{{#imports}}import { {{{typeName}}}{{#useAlias}} as {{{qualifiedName}}}{{/useAlias}} } from '{{{@root.pathToRoot}}}{{{fullPath}}}';
{{/imports}}
Expand Down
4 changes: 2 additions & 2 deletions templates/object.handlebars
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export interface {{typeName}} {
{{#properties}}
{{{tsComments}}}{{{identifier}}}{{^required}}?{{/required}}: {{{type}}};
{{{tsComments}}}{{#required}}{{#readOnly}}readonly {{/readOnly}}{{/required}}{{{identifier}}}{{^required}}?{{/required}}: {{{type}}};
{{/properties}}
{{#additionalPropertiesType}}

[key: string]: {{{.}}};
{{/additionalPropertiesType}}
}
}
31 changes: 31 additions & 0 deletions templates/utils.handlebars
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
type KeysOfType<T, SelectedType> = {
[key in keyof T]: SelectedType extends T[key] ? key : never;
}[keyof T];

type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2) extends <
T
>() => T extends Y ? 1 : 2
? A
: B;

type WritableKeys<T> = {
[P in keyof T]: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>;
}[keyof T];

type WritableObject<T> = {
[K in WritableKeys<T>]: T[K];
};

type OptionalPropertyOf<T> = Exclude<
{
[K in keyof T]: T extends Record<K, T[K]> ? never : K;
}[keyof T],
undefined
>;

export type WriteableNoOptional<T> = Omit<
WritableObject<T>,
OptionalPropertyOf<T>
>;
export type Writable<T> = WriteableNoOptional<T> &
Pick<T, OptionalPropertyOf<T>>;
14 changes: 14 additions & 0 deletions test/cmd-args.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ describe('cmd-args.ts', () => {
expect(options.customizedResponseType).toEqual({});
});

it('should customizedResponseType be overrided by cmd\'s if both config and args contains customizedResponseType', () => {
const sysArgs = [
'--input',
'abc',
'--customizedResponseType',
'{}',
'--config',
'test/cmd-args-test-config.json',
'--experimental',
'true',
];
const options = parseOptions(sysArgs);
expect(options.experimental).toEqual(true);
});
});

});
8 changes: 8 additions & 0 deletions test/petstore-experimental.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "../ng-openapi-gen-schema.json",
"input": "petstore-experimental.json",
"output": "out/petstore-experimental",
"modelPrefix": "Petstore",
"modelSuffix": "Model",
"experimental": true
}
Loading