Giter VIP home page Giter VIP logo

ng-openapi-gen's Introduction

ng-openapi-gen: An OpenAPI 3 code generator for Angular

Build status Test status

This project is a NPM module that generates model interfaces and web service clients from an OpenApi 3 specification. The generated classes follow the principles of Angular. The generated code is compatible with Angular 12+.

For a generator for Swagger 2.0, use ng-swagger-gen instead.

Highlights

  • It should be easy to use and to integrate with Angular CLI;
  • It should support OpenAPI specifications in both JSON and YAML formats;
  • Each tag in the OpenAPI specification generates an Angular @Injectable() service;
  • An Angular @NgModule() is generated, which provides all services;
  • It should be easy to access the original HttpResponse, for example, to read headers. This is achieved by generating a variant suffixed with $Response for each generated method;
  • OpenAPI supports combinations of request body and response content types. For each combination, a distinct method is generated;
  • It should be possible to specify a subset of services to generate. Only the models actually used by that subset should be generated;
  • It should be easy to specify a root URL for the web service endpoints;
  • Generated files should compile using strict TypeScript compiler flags, such as noUnusedLocals and noUnusedParameters;
  • For large APIs it is possible to generate only functions for each API operation, and not entire services. This allows for tree-shakable code to be generated, resulting in lower bundle sizes.

Limitations

  • Only standard OpenAPI 3 descriptions will be generated. ng-swagger-gen allows several extensions, specially types from JSON schema, but they are out of scope for ng-openapi-gen. There is, however, support for a few vendor extensions;
  • Servers per operation are not supported;
  • Only the first server is used as a default root URL in the configuration;
  • No data transformation is ever performed before sending / after returning data. This means that a property of type string and format date-time will always be generated as string, not Date. Otherwise every API call would need to have a processing that would traverse the returned object graph before sending the request to replace all date properties by Date. The same applies to sent requests. Such operations are out of scope for ng-openapi-gen;

Relationship with ng-swagger-gen

This project uses the same philosophy as ng-swagger-gen, and was built by the same team. We've learned a lot with ng-swagger-gen and have applied all the acquired knowledge to build ng-openapi-gen.

There were several reasons to not build a new major version of ng-swagger-gen that supports OpenAPI 3, but instead, to create a new project. The main differences between ng-openapi-gen and ng-swagger-gen are:

  • The first, more obvious and more important is the specification version, OpenAPI 3 vs Swagger 2;
  • The generator itself is written in TypeScript, which should be easier to maintain;
  • There is an extensive test suite for the generator;
  • The command-line arguments are more robust, derived directly from the JSON schema definition for the configuration file, easily allowing to override any specific configuration on CLI.
  • Root enumerations (schemas of type = string | number | integer) can be generated as TypeScript's enum's. This is enabled by default. Inline enums are not, because it would require another type to be exported in the container type.

Installing and running

You may want to install ng-openapi-gen globally or just on your project. Here is an example for a global setup:

$ npm install -g ng-openapi-gen
$ ng-openapi-gen --input my-api.yaml --output my-app/src/app/api

Alternatively you can use the generator directly from within your build-script:

import $RefParser from 'json-schema-ref-parser';
import { NgOpenApiGen } from 'ng-openapi-gen';

const options = {
  input: "my-api.json",
  output: "my-app/src/app/api",
}

// load the openapi-spec and resolve all $refs
const RefParser = new $RefParser();
const openApi = await RefParser.bundle(options.input, {
  dereference: { circular: false }
});

const ngOpenGen = new NgOpenApiGen(openApi, options);
ngOpenGen.generate();

This will expect the file my-api.yaml (or my-api.json) to be in the current directory, and will generate the files on my-app/src/app/api.

Configuration file and CLI arguments

If the file ng-openapi-gen.json exists in the current directory, it will be read. Alternatively, you can run ng-openapi-gen --config my-config.json (could also be -c) to specify a different configuration file, or even specify the input / output as ng-openapi-gen -i input.yaml or ng-openapi-gen -i input.yaml -o /tmp/generation. The only required configuration property is input, which specified the OpenAPI specification file. The default output is src/app/api.

For a list with all possible configuration options, see the JSON schema file. You can also run ng-openapi-gen --help to see all available options. Each option in the JSON schema can be passed in as a CLI argument, both in camel case, like --includeTags tag1,tag2,tag3, or in kebab case, like --exclude-tags tag1,tag2,tag3.

Here is an example of a configuration file:

{
  "$schema": "node_modules/ng-openapi-gen/ng-openapi-gen-schema.json",
  "input": "my-file.json",
  "output": "out/person-place",
  "ignoreUnusedModels": false
}

Specifying the root URL / web service endpoint

The easiest way to specify a custom root URL (web service endpoint URL) is to use forRoot method of ApiModule and set the rootUrl property from there.

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    HttpClientModule,
    ApiModule.forRoot({ rootUrl: 'https://www.example.com/api' }),
  ],
  bootstrap: [
    AppComponent
  ]
})
export class AppModule { }

Alternatively, you can inject the ApiConfiguration instance in some service or component, such as the AppComponent and set the rootUrl property there.

Using functional API calls

Starting with version 0.50.0, ng-openapi-gen generates a function with the implementation of each actual API call. The generated services delegate to such functions.

However, it is possible to disable the entire services generation, which will avoid the need to include all such services in the application. As a result, the application will be more tree-shakable, resulting in smaller bundle sizes. This is specially true for large 3rd party APIs, in which, for example, a single service (OpenAPI tag) has many methods, but only a few are actually used. Combined with the option "enumStyle": "alias", the footprint of the API generation will be minimal.

Each generated function receives the following arguments:

  • Angular's HttpClient instance;
  • The API rootUrl (the operation knowns the relative URL, and will use this root URL to build the full endpoint path);
  • The actual operation parameters. If it has no parameters or all parameters are optional, the params option will be optional as well;
  • The optional http context.

Clients can directly call the function providing the given parameters. However, to make the process smoother, it is also possible to generate a general service specifically to invoke such functions. Its generation is disabled by default, but can be enabled by setting the option "apiService": "ApiService" (or another name your prefer). With this, a single @Injectable service is generated. It will provide the functions with the HttpClient and rootUrl (from ApiConfiguration).

It then provides 2 methods for invoking the functions:

  • invoke: Calls the function and returns the response body;
  • invoke$Response: Calls the function and returns the entire response, so additional metadata can be read, such as status code or headers.

Here is an example class using the ApiService:

import { Directive, OnInit, inject } from '@angular/core';
import { ApiService } from 'src/api/api.service';
import { getResults } from 'src/api/fn/api/get-results';
import { Result } from 'src/api/models';
import { Observable } from 'rxjs';

@Directive()
export class ApiFnComponent implements OnInit {
  results$!: Observable<Result[]>;

  apiService = inject(ApiService);

  ngOnInit() {
    // getResults is the operation function. The second argument is the actual parameters passed to the function
    this.results$ = this.apiService.invoke(getResults, { limit: 10 });
  }
}

Passing request headers / customizing the request

To pass request headers, such as authorization or API keys, as well as having a centralized error handling, a standard HttpInterceptor should be used. It is basically an @Injectable that is called before each request, and can customize both requests and responses.

Here is an example:

@Injectable()
export class ApiInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Apply the headers
    req = req.clone({
      setHeaders: {
        'ApiToken': '1234567890'
      }
    });

    // Also handle errors globally
    return next.handle(req).pipe(
      tap(x => x, err => {
        // Handle this err
        console.error(`Error performing request, status code = ${err.status}`);
      })
    );
  }
}

Then, both the HttpInterceptor implementation and the injection token HTTP_INTERCEPTORS pointing to it must be provided in your application module, like this:

import { NgModule, Provider, forwardRef } from '@angular/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http';

import { ApiInterceptor } from './api.interceptor';

export const API_INTERCEPTOR_PROVIDER: Provider = {
  provide: HTTP_INTERCEPTORS,
  useExisting: forwardRef(() => ApiInterceptor),
  multi: true
};

@NgModule({
  providers: [
    ApiInterceptor,
    API_INTERCEPTOR_PROVIDER
  ]
})
export class AppModule {}

Finer control over specific requests can also be achieved, such as:

  • Set the immediate next request to use a BASIC authentication for login, and the subsequent ones to use a session key in another request header;
  • Set the next request to not use the default error handling, and handle errors directly in the calling code.

To do so, just create another shared @Injectable(), for example, called ApiRequestConfiguration, which has state for such special cases. Then inject it on both the HttpInterceptor and in the client code that makes requests. Here is an example for such class for controlling the authentication:

import { Injectable } from '@angular/core';
import { HttpRequest } from '@angular/common/http';

/**
 * Configuration for the performed HTTP requests
 */
@Injectable()
export class ApiRequestConfiguration {
  private nextAuthHeader: string;
  private nextAuthValue: string;

  /** Set to basic authentication */
  basic(user: string, password: string): void {
    this.nextAuthHeader = 'Authorization';
    this.nextAuthValue = 'Basic ' + btoa(user + ':' + password);
  }

  /** Set to session key */
  session(sessionKey: string): void {
    this.nextAuthHeader = 'Session';
    this.nextAuthValue = sessionKey;
  }

  /** Clear any authentication headers (to be called after logout) */
  clear(): void {
    this.nextAuthHeader = null;
    this.nextAuthValue = null;
  }

  /** Apply the current authorization headers to the given request */
  apply(req: HttpRequest<any>): HttpRequest<any> {
    const headers = {};
    if (this.nextAuthHeader) {
      headers[this.nextAuthHeader] = this.nextAuthValue;
    }
    // Apply the headers to the request
    return req.clone({
      setHeaders: headers
    });
  }
}

Then change the ApiInterceptor class to call the apply method. And, of course, add ApiRequestConfiguration to your module providers and inject it on your components or services.

Setting up a node script

Regardless If your Angular project was generated or is managed by Angular CLI, or you have started your project with some other seed (for example, using webpack directly), you can setup a script to make sure the generated API classes are consistent with the swagger descriptor.

To do so, create the ng-openapi-gen.json configuration file and add the following scripts to your package.json:

{
  "scripts": {
    "ng-openapi-gen": "ng-openapi-gen",
    "start": "npm run ng-openapi-gen && npm run ng -- serve",
    "build": "npm run ng-openapi-gen && npm run ng -- build -prod"
  }
}

This way whenever you run npm start or npm run build, the API classes will be generated before actually serving / building your application.

Also, if you use several configuration files, you can specify multiple times the call to ng-openapi-gen, like:

{
  "scripts": {
    "ng-openapi-gen": "ng-openapi-gen",
    "generate.api1": "npm run ng-openapi-gen -c api1.json",
    "generate.api2": "npm run ng-openapi-gen -c api2.json",
    "generate": "npm run generate.api1 && npm run generate.api2",
    "start": "npm run generate && npm run ng -- serve",
    "build": "npm run generate && npm run ng -- build -prod"
  }
}

Supported vendor extensions

Besides the OpenAPI 3 specification, the following vendor extensions are supported:

  • x-operation-name: Defined in LoopBack, this extension can be used in operations to specify the actual method name. The operationId is required to be unique among all tags, but with this extension, a shorter method name can be used per tag (service). Example:
paths:
  /users:
    get:
      tags:
        - Users
      operationId: listUsers
      x-operation-name: list
      # ... 
  /places:
    get:
      tags:
        - Places
      operationId: listPlaces
      x-operation-name: list
      # ...
  • x-enumNames: Generated by NSwag, this extension allows schemas which are enumerations to customize the enum names. It must be an array with the same length as the actual enum values. Example:
components:
  schemas:
    HttpStatusCode:
      type: integer
      enum:
        - 200
        - 404
        - 500
      x-enumNames:
        - OK
        - NOT_FOUND
        - INTERNAL_SERVER_ERROR

Customizing templates

You can customize the Handlebars templates by copying the desired files from the templates folder (only the ones you need to customize) to some folder in your project, and then reference it in the configuration file.

For example, to make objects extend a base interface, copy the object.handlebars file to your src/templates folder. Then, in ng-openapi-gen.json file, set the following: "templates": "src/templates". Finally, the customized src/templates/object.handlebars would look like the following (based on the 0.17.2 version, subject to change in the future):

{{^hasSuperClasses}}import { BaseModel } from
'app/base-model';{{/hasSuperClasses}}

export interface {{typeName}}
{{#hasSuperClasses}} extends {{#superClasses}}{{{.}}}{{^@last}},
{{/@last}}{{/superClasses}}{{/hasSuperClasses}}
{{^hasSuperClasses}} extends BaseModel{{/hasSuperClasses}}
{
{{#properties}}
{{{tsComments}}}{{{identifier}}}{{^required}}?{{/required}}: {{{type}}};
{{/properties}}
{{#additionalPropertiesType}}

[key: string]: {{{.}}};
{{/additionalPropertiesType}}
}

Custom Handlebars helpers

You can integrate your own Handlebar helpers for custom templates. To do so simply provide a handlebars.js file in the same directory as your templates that exports a function that recieves the Handlebars instance that will be used when generating the code from your templates.

module.exports = function(handlebars) {
  // Adding a custom handlebars helper: loud
  handlebars.registerHelper('loud', function (aString) {
    return aString.toUpperCase()
  });
};

Developing and contributing

The generator itself is written in TypeScript. When building, the code is transpiled to JavaScript in the dist folder. And the dist folder is the one that gets published to NPM. Even to prevent publishing from the wrong path, the package.json file has "private": true, which gets replaced by false in the build process.

On the other hand, for developing / running tests, jasmine-ts is used, so the tests run directly from TypeScript. There's even a committed VisualStudio Code debug configuration for tests.

After developing the changes, to link the module and test it with other node projects, run the following:

npm run build
cd dist
npm link

At that point, the globally available ng-openapi-gen will be the one compiled to the dist folder.

ng-openapi-gen's People

Contributors

briceruzand avatar coutin avatar danmcgee-soda avatar deniszholob avatar dlid avatar ejuke avatar elarocque avatar erikhofer avatar fdipuma avatar fennekit avatar filip-codes avatar hbunjes avatar hmil avatar ilbonte avatar jeanmeche avatar jurgenf avatar karsa-mistmere avatar koenekelschot avatar lonli-lokli avatar luisfpg avatar mac2000 avatar marcellkiss avatar marcogiovinazzi avatar mojoaxel avatar nomadesoft avatar remkoboschker avatar spajker7 avatar tajnymag avatar therealgi avatar tommck avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ng-openapi-gen's Issues

Unique names SDK

I was wondering why names have to be unique in the generated SDK even though they belong to their own class.

Duplicate operation id 'get'. Assuming id get_1 for operation '/employees.get'. Duplicate operation id 'create'. Assuming id create_1 for operation '/employees.post'. Duplicate operation id 'getById'. Assuming id getById_1 for operation '/employees/{id}.get

Due to this we cannot use generic interfaces as wrappers for the SDK.

simple non-nullable boolean being generated as optional

I have a simple interface with a non-nullable boolean value but ng-openapi-gen is converting this to an optional field in the output.

input:

      "Foo.Bar.FooInfo": {
        "type": "object",
        "properties": {
          "isTrue": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },

output:

interface FooInfo {
   isTrue?: boolean;
}

There is nothing optional about isTrue. it should not have ?: on the declaration

Add defaultTag and prefix config

Hello,

I need to use defaultTag and prefix config to make me able to generate multiple api instances into one project.

{
  "$schema": "node_modules/ng-openapi-gen/ng-openapi-gen-schema.json",
  "input": "https://gist.githubusercontent.com/danielflower/5c5ae8a46a0a49aee508690c19b33ada/raw/b06ff4d9764b5800424f6a21a40158c35277ee65/petstore.json",
  "output": "./src/api",
  "ignoreUnusedModels": false,
  "prefix": "ApiDude",       <----
  "defaultTag": "ApiDude"    <----
}

Thank's in advance

no models generated

when running the generator, no models are created, although they are referenced in the api code

in api/services/api.service.ts

import { Alias } from '../models/alias';
import { Entity } from '../models/entity';

but there is no folder called models . There is an empty models.ts file

what am I missing ?

Interfaces class name with first char lowerCase

Hi,

every times i generate from yaml or json, the interfaces generated have the class name lowerCase on first char.

Example:

export interface addressFK {
fkIdIndirizzo?: string;
}

Expected Example:

export interface AddressFK {
fkIdIndirizzo?: string;
}

Can someone help me? Is that a bug?

Update:

Sorry, my swagger was declared wrong. like:

"addressFK": {
"type": "object",
"properties": {
"fkIdIndirizzo": {
"type": "string",
"description": "address foreign key"
}
}
},

it need to be:

"AddressFK": {
"type": "object",
"properties": {
"fkIdIndirizzo": {
"type": "string",
"description": "address foreign key"
}
}
},

I can force that from generator?

Improve the file generation by generating in a sibling folder and synchronizing changes instead of removing the output folder

When running ng-openapi-gen while the project is open in VS Code, I often get file contention issues.

The error looks something like this:

Error on API generation from http://localhost:8081/NameMatcher/NameMatcherService/swagger/v1/swagger.json: Error: EPERM: operation not permitted, mkdir 'D:\ICM\GIT\AirStack\AirApp\src\App\apps\air-app\src\app\ms\subscription\models'

If you then open Explorer and try to access the models folder, Explorer will complain that the models folder is not accessible, with an error description of Access Denied.

The only workaround is to close VS Code first and then run ng-openapi-gen.

This issue did not happen with ng-swagger-gen.

What about adding an option to not delete the various sub directories in the auto-generated output directory?

Correctly handle media types with +json suffix

Hello thanks for this amazing tool. I have a issue based on my open api yaml

When all services are generated they pass to the requestBuilder a object with reponseType blob, the reques runs fine in the network tab on chrome


Edit

Searching in the code in operation-vartiant.ts i can see the problem. This behavior is all about the custom response types like application/iway-csr-api-customer-response-v1-hal+json this is very common in a tons of restful api, maybe we can fix it if parse the content searching a substring with json to avoid blob in this cases?


Raw Generated service:

/**
   * This method provides access to only to the response body.
   * To access the full response (for headers, for example), `showSearchSettings$Response()` instead.
   *
   * This method doesn't expect any response body
   */
  showSearchSettings(params: {

    /**
     * Orgnanization Code or tenan ID.
     */
    xOrganizationCode: string;

  }): Observable<SearchSettingResource> {

    return this.showSearchSettings$Response(params).pipe(
      map((r: StrictHttpResponse<SearchSettingResource>) => r.body as SearchSettingResource)
    );
  }

  /**
   * Path part for operation modifySearchSettings
   */
  static readonly ModifySearchSettingsPath = '/admin/search';

  /**
   * This method provides access to the full `HttpResponse`, allowing access to response headers.
   * To access only the response body, use `modifySearchSettings()` instead.
   *
   * This method sends `application/iway-csr-api-settings-search-put-v1-hal+json` and handles response body of type `application/iway-csr-api-settings-search-put-v1-hal+json`
   */
  modifySearchSettings$Response(params: {

    /**
     * Orgnanization Code or tenan ID.
     */
    xOrganizationCode: string;

    body?: SearchSetting
  }): Observable<StrictHttpResponse<SearchSettingResource>> {

    const rb = new RequestBuilder(this.rootUrl, '/admin/search', 'put');
    if (params) {

      rb.header('X-Organization-Code', params.xOrganizationCode);
      rb.body(params.body, 'application/iway-csr-api-settings-search-put-v1-hal+json');
    }
    return this.http.request(rb.build({
      responseType: 'blob',
      accept: 'application/iway-csr-api-settings-search-response-v1-hal+json'
    })).pipe(
      filter((r: any) => r instanceof HttpResponse),
      map((r: HttpResponse<any>) => {
        return r as StrictHttpResponse<SearchSettingResource>;
      })
    );
  }

Calling the service from controller returns a blob object

this.adminDomainService.showSearchSettings({xOrganizationCode:'Intraway'})
      .subscribe((response: any) => {
        console.log(response) // Blob {size: 305, type: "application/iway-csr-api-settings-search-response-v1-hal+json"}
      })

I change it to json and works fine now, but i need to change the response builder every time i do a new openapi build

Here is my openapi yaml:

csr.yaml.tar.gz

Type resolution fails for allOf references

For example, the following model generates the other property with empty type:

MyObject:
  type: object
  properties:
    other:
      allOf:
        - $ref: '#/components/schemas/OtherObject'

There is no accept on HttpRequest

Hi,

I'm trying to use this tool, but get errors during build:

[ERROR] ERROR in src/app/generated/api/request-builder.ts(176,82): error TS2345: Argument of type '{ accept?: string; responseType?: "text" | "blob" | "arraybuffer" | "json"; reportProgress?: bool...' is not assignable to parameter of type '{ headers?: HttpHeaders; reportProgress?: boolean; params?: HttpParams; responseType?: "text" | "...'.
[ERROR]   Object literal may only specify known properties, and 'accept' does not exist in type '{ headers?: HttpHeaders; reportProgress?: boolean; params?: HttpParams; responseType?: "text" | "...'.
[ERROR] 

For some reason "accept" is not a valid property of HttpRequest options param in ng6 - 8, but it is used in your "request-builder.ts".

Optional query arguments

Arguments that are posted in params can be optional, which is nice. But there's no handling if they're optional or not when sending a request.
This leads for a invalid query where the rest api back end can't handle the request.

For example:

getListOfNumbers$Response(params?: {
 id?: number,
 typeOfNumber?: string
}): Observable<StrictHttpResponse<Array<number>> {
 const rb = new RequestBuilder(...);
 if (params) {
  rb.query('id', params.id);
  rb.query('typeOfNumber', params.typeOfNumber);
 }
 // More code here...
}

If we only request the id, the url would look something like this: http://backend-domain/numbers?id=1&typeOfNumber=

A proposal is to check these optional arguments like this:

getListOfNumbers$Response(params?: {
 id?: number,
 typeOfNumber?: string
}): Observable<StrictHttpResponse<Array<number>> {
const rb = new RequestBuilder(...);
if (params) {
 if (params.id) {
   rb.query('id', params.id);
 }
 if (params.typeOfNumber) {
   rb.query('typeOfNumber', params.typeOfNumber);
 }  
}
// More code here...
}

Add support for model namespaces

According to OAI/OpenAPI-Specification#1362 namespaces are "supported" and tooling should handle them gracefully.

Given this schema with a namespace

{
  "openapi": "3.0.0",
  // omitted
  "components": {
    "schemas": {
      "MY_NAMESPACE.FooBar": {
        // omitted
      }
    }
  }
}

Generates this TS model

export interface MY_NAMESPACE.FooBar  {}

. is not an allowed character in an interface.

https://github.com/openapitools/openapi-generator-cli handles this by just generating

export interface MYNAMESPACEFooBar  {}

rootUrl '/' generates incorrect request urls

I noticed setting rootUrl: '/' generates incorrect urls, e.g. an endpoint with /api/posts gets an xhr request of https://posts.

This is for a multi-tenant site where the base url can't be injected into the build easily.

FormData send all form data values as Blob

The issue seems to be related to the formDataValue function in request-builder.ts (requestBuilder.handlebars). If I check the differences between ng-swagger-gen and ng-openapi-gen when sending my request, the primitive types and JSON objects are sent as non-blob in the request contrary to what I found here in the following function.

private formDataValue(value: any): any {
    if (value === null || value === undefined) {
      return null;
    }
    if (value instanceof Blob) {
      return value;
    }
    if (typeof value === 'object') {
      return new Blob([JSON.stringify(value)], { type: 'application/json' });
    }
    return new Blob([String(value)], { type: 'text/plain' });
  }

Changing it to something around those lines seems to have fixed my issue:

private formDataValue(value: any): any {
    if (value === null || value === undefined) {
      return null;
    }
    if (value instanceof Blob) {
      return value;
    }
    
    if (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'object') {
      return JSON.stringify(value);
    }
   try {
      var o = JSON.parse(value);
      if (o && typeof o === 'object') {
        return JSON.stringify(o);
      }
    } catch {
    }
    return new Blob([String(value)], { type: 'text/plain' });
  }

How could we improve my dummy code and fix this in a cleaner way?

Not all models are generated when modelPrefix is given

Hi @luisfpg Thanks for the patch for #13 . But, i'm afraid this did not fully fix the issue. It generates certain models and ignores some of them. To reproduce please try this..

Download https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v3.0/petstore.yaml and store it as petstore.yaml
run ng-openapi-gen -i petstore.yaml -o petstoreClient
Please Note: in the petstoreClient/models you will see 3 models. Good! This is the expected behaviour.

But....
Now to repro the error, use the following conf file

$ cat conf.json 

{
    "module": "MyPetStore",
    "configuration": "MyPetStoreClientConfiguration",
    "servicePrefix": "MyPetStore",
    "serviceIndex": "my-pet-store",
    "modelIndex": "my-pet-store-models",
    "modelPrefix" : "TestPrefix"
}

now try ng-openapi-gen -i petstore.yaml -o petstoreClientWithPrefix -c conf.json
you will see only 1 file in petstoreClient/models

[Dropped] Simplify generated methods with function overloads

Now we always generate both FooApi.operation and FooApi.operation$Response.
Instead, we can use function overloads to generate a single operation that can return either the body or the response.
For operations that declare multiple response content types, we could also add the expected content type as argument, and generate the correct result type.

Not adding reference of Models used

I generated the TS code from my server OpenAPI.

Where I have 2 models

/* tslint:disable */
import { Test } from './test';
export interface User  {
  EmpCode: string;
  EmpId: number;
  Login?: null | Login;
  Name: string;
  test?: Test;
}

and

/* tslint:disable */
export interface Login  {
  Password?: null | string;
  UserName?: null | string;
}

As you can see it is not adding reference of Login into User, only an enum type Test is added.

Enums are not correctly generated

Hi,

I recently observed that it is generating Enums as following:

/* tslint:disable */

/**
 * 10 = NationalHoliday
 * 20 = Festival
 */
export enum HolidayType {
  10 = 10,
  20 = 20
}

Where it is using the numerical part only, it would be really helpful if you could replace the LHS part with the actual String representation as shown in commented code above.

Thanks

Syntax error on generate services if operationId is not a valid identifier

{
  "openapi": "3.0.0",
  "info": {
    "title": "LoopBack Application",
    "version": "1.0.0"
  },
  "paths": {
    "/login": {
      "post": {
        "x-controller-name": "LoginController",
        "x-operation-name": "login",
        "tags": [
          "LoginController"
        ],
        "responses": {
          "200": {
            "description": "Token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "description": "The input of login function",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "username",
                  "password"
                ],
                "properties": {
                  "username": {
                    "type": "string"
                  },
                  "password": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "operationId": "LoginController.login"
      }
    }
  },
  "servers": [
    {
      "url": "http://10.0.0.126:3000"
    }
  ],
  "components": {
    "schemas": {
      "User": {
        "title": "User",
        "properties": {
          "id": {
            "type": "string"
          },
          "username": {
            "type": "string"
          },
          "password": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "username",
          "password"
        ]
      },
      "UserPartial": {
        "title": "UserPartial",
        "properties": {
          "id": {
            "type": "string"
          },
          "username": {
            "type": "string"
          },
          "password": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}

Generate service:

/* tslint:disable */
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { BaseService } from '../base-service';
import { ApiConfiguration } from '../api-configuration';
import { StrictHttpResponse } from '../strict-http-response';
import { RequestBuilder } from '../request-builder';
import { Observable } from 'rxjs';
import { map, filter } from 'rxjs/operators';


@Injectable({
  providedIn: 'root',
})
export class LoginControllerService extends BaseService {
  constructor(
    config: ApiConfiguration,
    http: HttpClient
  ) {
    super(config, http);
  }

  /**
   * Path part for operation LoginController.login
   */
  static readonly LoginController.loginPath = '/login';

  /**
   * This method provides access to the full `HttpResponse`, allowing access to response headers.
   * To access only the response body, use `LoginController.login()` instead.
   *
   * This method sends `application/json` and handles response body of type `application/json`
   */
  LoginController.login$Response(params: {

  
  /**
   * The input of login function
   */
  body: { 'username': string, 'password': string }
  }): Observable<StrictHttpResponse<{ 'token': string }>> {

    const rb = new RequestBuilder(this.rootUrl, LoginControllerService.LoginController.loginPath, 'post');
    if (params) {


      rb.body(params.body, 'application/json');
    }
    return this.http.request(rb.build({
      responseType: 'json',
      accept: 'application/json'
    })).pipe(
      filter((r: any) => r instanceof HttpResponse),
      map((r: HttpResponse<any>) => {
        return r as StrictHttpResponse<{ 'token': string }>;
      })
    );
  }

  /**
   * This method provides access to only to the response body.
   * To access the full response (for headers, for example), `LoginController.login$Response()` instead.
   *
   * This method sends `application/json` and handles response body of type `application/json`
   */
  LoginController.login(params: {

  
  /**
   * The input of login function
   */
  body: { 'username': string, 'password': string }
  }): Observable<{ 'token': string }> {

    return this.LoginController.login$Response(params).pipe(
      map((r: StrictHttpResponse<{ 'token': string }>) => r.body as { 'token': string })
    );
  }

}

Get error:

src/app/api/services/login-controller.service.ts:26:34 - error TS1005: ';' expected.
26   static readonly LoginController.loginPath = '/login';

src/app/api/services/login-controller.service.ts:34:18 - error TS1005: ';' expected.                                                                                                                                                          
34   LoginController.login$Response(params: {
src/app/api/services/login-controller.service.ts:66:18 - error TS1005: ';' expected.                                                                                                                                                          
66   LoginController.login(params: {

Query and header parameters with value 0 are dropped

Query parameters with the value 0 are being converted to an empty string in the RequestBuilder.

The checks httpHeaders.append(param, (item || '') and httpHeaders.append(param, (value || '') are causing the problem.

Don't collect model dependencies from oneOf

We have an openapi file

{
    "x-generator": "NSwag v12.0.12.0 (NJsonSchema v9.13.15.0 (Newtonsoft.Json v11.0.0.0))",
    "openapi": "3.0.0",
    "info": {
        "title": "Our API 1.0",
        "version": "1.0"
    },
    "consumes": ["application/json-patch+json", "application/json", "text/json", "application/*+json"],
    "servers": [
        {
            "url": "http://localhost:60367"
        }
    ],
    "paths": {
        "/api/v1/Modules/{id}": {
            "post": {
                "tags": ["Modules"],
                "operationId": "ApiV1ModulesByIdPost",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "guid"
                        },
                        "x-position": 1
                    }
                ],
                "requestBody": {
                    "x-name": "contractData",
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/ContractDto"
                            }
                        }
                    },
                    "required": true,
                    "x-position": 2
                },
                "responses": {
                    "200": {
                        "description": ""
                    },
                    "400": {
                        "description": "",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "string",
                                    "nullable": true
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "string",
                                    "nullable": true
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "string",
                                    "nullable": true
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "ContractDto": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                    "id": {
                        "type": "string",
                        "format": "guid",
                        "nullable": true
                    },
                    "modules": {
                        "type": "array",
                        "nullable": true,
                        "items": {
                            "$ref": "#/components/schemas/Module"
                        }
                    }
                }
            },
            "Module": {
                "type": "object",
                "x-abstract": true,
                "additionalProperties": false,
                "properties": {
                    "inUse": {
                        "type": "boolean"
                    },
                    "naam": {
                        "type": "string",
                        "nullable": true
                    }
                },
                "oneOf": [
                    {
                        "$ref": "#/components/schemas/Submodule1"
                    },
                    {
                        "$ref": "#/components/schemas/Submodule2"
                    }
                ]
            },
            "Submodule1": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/Module"
                    },
                    {
                        "type": "object",
                        "additionalProperties": false
                    }
                ]
            },
            "Submodule2": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/Module"
                    },
                    {
                        "type": "object",
                        "additionalProperties": false
                    }
                ]
            }
        }
    }
}

ng-openapi-gen generates the following model for Module:

import { Submodule1 } from './submodule-1';
import { Submodule2 } from './submodule-2';
export interface Module  {
  inUse?: boolean;
  naam?: null | string;
}

The typescript compiler will nog compile because Submodule1 and Submodule2 are imported but not used.

Is this the intended behaviour?

Allow running without a configuration file, and add mnemonics -i / -o for --input / --output

Hello, is it possible to have same CLI options as ng-swagger-gen ?
I want to use same config only by switching the library.

I want to use it like this : ng-openapi-gen --gen-confi -i https://gist.githubusercontent.com/kinlane/43934f44fd591a6ee59a45267d9e3066/raw/47f6d7115dd17749c408c55861638ffc22d995f4/sample-openapi-30.yaml -o ./src/api -c ng-swagger-gen-api.json

Thank's in advance.

Error: ENOTEMPTY: directory not empty, rmdir './**/*'

I get an error after the successful generation of the API.
Error on API generation from https://localhost:5002/swagger/v1/swagger.json: Error: ENOTEMPTY: directory not empty, rmdir './src/app/core/api$'

I think it's because you are using the fs-extra package.

image

Support x-operation-name extension

Today if the operationId is repeated add a suffix to the name for example:

users:
    get:
        operationId: find
        tags:
            - users
organizations:
    get:
        operationId: find
        tags:
            - organizations

this create two services users with the method find and the service organizations with the method find_1. Maybe we can change this to make the combination of tag and operationId unique to prevent the suffix in this cases?

Problem with `items: $ref:` for array model.

Source

https://gist.githubusercontent.com/kinlane/43934f44fd591a6ee59a45267d9e3066/raw/47f6d7115dd17749c408c55861638ffc22d995f4/sample-openapi-30.yaml

Expected

pets.ts :

/* tslint:disable */
import { Pet } from './pet';
export type Pets = Pet[];

Got

pets.ts :

/* tslint:disable */
import { Pet } from './pet';
export type Pets = ; <--- missing value

Bug: Required fields in subclass are optional in generated interface

Take this schema snippet:

Entity:
      title: Entity
      type: object
      description: An abstract object with base properties
      required:
        - id
        - displayName
      properties:
        id:
          $ref: "#/components/schemas/UUID"
        displayName:
          type: string
          minLength: 3
          maxLength: 100

ReferencableEntity:
      title: ReferencableEntity
      description: An abstract object with base properties and a property that is an immutable unique textual reference that can be used in code with impunity
      allOf:
        - $ref: "#/components/schemas/Entity"
        - type: "object"
        - required:
          - uniqueRef
        - properties:
            uniqueRef:
              type: string
              pattern: ^([a-z][a-z0-9]*)(-[a-z0-9]+)*$
              minLength: 3
              maxLength: 100
              description: lower kebab-case string similar to the displayName
              example: roles-read

generated interface:

/* tslint:disable */
import { Entity } from './entity';

/**
 * An abstract object with base properties and a property that is an immutable unique textual reference that can be used in code with impunity
 */
export interface ReferencableEntity extends Entity {

  /**
   * lower kebab-case string similar to the displayName
   */
  uniqueRef?: string;
}

Now it is possible to add required at a number of levels to which I profess no knowledge of any differential, but swagger ui is more than happy with the spec:

inheritance-required

model referenced in oneOf in path is ignored

The model referenced in the path below is being ignored. It is referenced correctly in the service definition. But the model itself is not generated. I'm using 0.5.2

 "/api/v1/Rollen": {
            "get": {
                "tags": ["Rollen"],
                "operationId": "ApiV1RollenGet",
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "nullable": true,
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/RolIndexResult"
                                        }
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        },

Unused pathVar on services

The template operationPath.handlebars has declared the service path, but in operationResponse.handlebars the method is redeclared again suing operation.path . Basically the static readonly var declared on operationPath.handlebars is never used

/* tslint:disable */
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { BaseService } from '../base-service';
import { ApiConfiguration } from '../api-configuration';
import { StrictHttpResponse } from '../strict-http-response';
import { RequestBuilder } from '../request-builder';
import { Observable } from 'rxjs';
import { map, filter } from 'rxjs/operators';

import { ConnectorPluginResources } from '../models/connector-plugin-resources';


/**
 * Admin connector
 */
@Injectable({
  providedIn: 'root',
})
export class AdminConnectorsService extends BaseService {
  constructor(
    config: ApiConfiguration,
    http: HttpClient
  ) {
    super(config, http);
  }

  /**
   * Path part for operation getAllConnectorPlugins
   */
  static readonly GetAllConnectorPluginsPath = '/admin/connector-manager/connector-plugins';

  /**
   * This method provides access to the full `HttpResponse`, allowing access to response headers.
   * To access only the response body, use `getAllConnectorPlugins()` instead.
   *
   * This method doesn't expect any response body
   */
  getAllConnectorPlugins$Response(params?: {

  }
): Observable<StrictHttpResponse<ConnectorPluginResources>> {

    const rb = new RequestBuilder(this.rootUrl, '/admin/connector-manager/connector-plugins', 'get');
    if (params) {

    }
    return this.http.request(rb.build({
      responseType: 'json',
      accept: 'application/iway-csr-api-connector-plugins-response-v1-hal+json'
    })).pipe(
      filter((r: any) => r instanceof HttpResponse),
      map((r: HttpResponse<any>) => {
        return r as StrictHttpResponse<ConnectorPluginResources>;
      })
    );
  }

  /**
   * This method provides access to only to the response body.
   * To access the full response (for headers, for example), `getAllConnectorPlugins$Response()` instead.
   *
   * This method doesn't expect any response body
   */
  getAllConnectorPlugins(params?: {

  }
): Observable<ConnectorPluginResources> {

    return this.getAllConnectorPlugins$Response(params).pipe(
      map((r: StrictHttpResponse<ConnectorPluginResources>) => r.body as ConnectorPluginResources)
    );
  }

}

Why Method Name is Camel Case ?

hi,

when my json is :

"/Api/Applications/New": { "get": { "tags": [ "Applications" ], "operationId": "GetNew", "responses": { "200": { "description": "Success", "content": { "text/plain": { "schema": { "$ref": "#/components/schemas/ApplicationEditableVMFormlyVMFeedBack" } }, "application/json": { "schema": { "$ref": "#/components/schemas/ApplicationEditableVMFormlyVMFeedBack" } }, "text/json": { "schema": { "$ref": "#/components/schemas/ApplicationEditableVMFormlyVMFeedBack" } } } } } } }, ...

Generater method name is: getNew$json

how to change method name like ng-swagger : GetNew ?

generated model interfaces are lowercase

I tried this generator on our openapi spec and it works quite well. Thanks.

I just wondering why the generated model interfaces are lowercase (with my openapi spec). I guess there is an encoding of the typeName missing here:

this.typeName = name;

I fixed that by replacing
this.typeName = name;
with
this.typeName = typeName(name);

Duplicate identifier 'id'

After generating models and services, when I run either "npm start" or "npm run build" I've got several errors on different lines like below:

Error in src/app/api/services/disability.service.ts:242:5 - error TS2300: Duplicate identifier 'id'.
getDisabilityByIdRoute$Plain$Response(params: { id?: string; id: string; }): Observable<StrictHttpResponse<Disability>> { ...

note: I used https://editor.swagger.io/# to convert my JSON file to YAML.

Angular CLI: 8.3.19
Node: 12.7.0
OS: win32 x64
Angular: 8.2.14

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.