@devlearning/swagger-generator 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,208 @@
1
- Add task on package.json
1
+ # Swagger Generator for Angular & Next.js
2
2
 
3
- "swgen": "swgen http://localhost:5208/swagger/v1/swagger.json src/app/core/autogenerated"
3
+ This tool automates the generation of API clients for Angular and Next.js using a Swagger (OpenAPI) specification. It creates TypeScript models and service classes, making API integration seamless and type-safe.
4
4
 
5
- this generation work with momentjs
5
+ ## Features
6
6
 
7
- specify url of your swagger.json and path destination of generated files (in this example you must create path core/autogenerated)
7
+ - 🚀 **Automatic Model Generation** Converts Swagger schemas into TypeScript interfaces or classes.
8
+ - 🔥 **API Client Generation** – Creates service methods for making API calls with authentication, headers, and request parameters.
9
+ - 🎯 **Framework-Specific Output**:
10
+ - **Angular** – Generates injectable services using `HttpClient`.
11
+ - **Next.js** – Provides fetch-based or Axios-based API functions, optimized for both server-side and client-side usage.
12
+ - ✅ **Strong Typing** – Ensures type safety for API requests and responses.
13
+ - ⚡ **Customization** – Configurable options for method naming, error handling, and request structure.
14
+ - 🔄 **Auto-Sync with Backend** – Keeps API clients up-to-date with backend changes.
15
+
16
+ ## Installation
17
+
18
+ ```sh
19
+ npm i @devlearning/swagger-generator --save-dev
20
+ ```
21
+
22
+
23
+ add npm script:
24
+
25
+ "swgen": "swgen http://localhost:7550/swagger/ApiGateway/swagger.json src/app/core/autogenerated angular moment"
26
+
27
+ params:
28
+ - url of swagger.json: not https
29
+ - output path for autogenerated files: src/app/core/autogenerated
30
+ - target framework/library: angular/next
31
+ - type of date management:
32
+ - angular only support momentjs, sorry :(
33
+ - nextjs only support date-fns
34
+
35
+ create output path like this: src/app/core/autogenerated
36
+
37
+ ## Angular configuration
38
+
39
+ create ApiService like this
40
+ ```ts
41
+ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
42
+ import { Injectable } from '@angular/core';
43
+ import { ApiAutogeneratedService } from '../autogenerated/api.autogenerated';
44
+ import { environment } from 'src/environments/environment';
45
+ import { ResponseError } from '../models/response-error';
46
+ import { throwError } from 'rxjs';
47
+ import * as moment from 'moment-timezone';
48
+
49
+ @Injectable({
50
+ providedIn: 'root',
51
+ })
52
+ export class ApiService extends ApiAutogeneratedService {
53
+
54
+ private _dateTimeFormat = /^((19|20)[0-9][0-9])[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])[T]([01]?[0-9]|[2][0-3])[:]([0-5][0-9])[:]([0-5][0-9])[.]([0-9][0-9][0-9])([+|-]([01][0-9]|[2][0-3])[:]([0-5][0-9])){0,1}$/;
55
+ private _timeFormat = /^([01]?[0-9]|[2][0-3])[:]([0-5][0-9])[:]([0-5][0-9])[.]([0-9][0-9][0-9])$/;
56
+ private _ianaName: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
57
+ private _dateTimeFormatString = "YYYY-MM-DDTHH:mm:ss.SSSZ";
58
+ private _timeFormatString = "HH:mm:ss.SSS";
59
+
60
+ public get ianaName() { return this._ianaName; }
61
+
62
+ constructor(
63
+ public override _http: HttpClient,
64
+ ) {
65
+ super(_http, environment.BASE_URL);
66
+ }
67
+
68
+ protected override _momentToString(moment: moment.Moment) {
69
+ return (<moment.Moment>moment).format(this._dateTimeFormatString);
70
+ }
71
+
72
+ protected override _handleRequest<T>(request: T) {
73
+ if (request === null || request === undefined) {
74
+ return request;
75
+ }
76
+ if (typeof request !== 'object') {
77
+ return request;
78
+ }
79
+
80
+ var clonedRequest = { ...request };
81
+
82
+ for (const key of Object.keys(clonedRequest)) {
83
+ const value = (<any>clonedRequest)[key];
84
+ if (moment.isMoment(value)) {
85
+ (<any>clonedRequest)[key] = this._momentToString(value);
86
+ } else if (typeof value === 'object') {
87
+ this._handleRequest(value);
88
+ }
89
+ }
90
+
91
+ return clonedRequest;
92
+ }
93
+
94
+ protected override _handleMultipart<T>(request: T): FormData {
95
+ const formData = new FormData();
96
+ if (request === null || request === undefined) {
97
+ return formData;
98
+ }
99
+
100
+ for (const key of Object.keys(request)) {
101
+ const value = (<any>request)[key];
102
+ if (value instanceof File) {
103
+ formData.append(key, value, value.name);
104
+ } else {
105
+ formData.append(key, value.toString());
106
+ }
107
+ }
108
+
109
+ return formData;
110
+ }
111
+
112
+ public override _handleResponse<T>(response: T) {
113
+ if (response === null || response === undefined) {
114
+ return response;
115
+ }
116
+ if (typeof response !== 'object') {
117
+ return response;
118
+ }
119
+
120
+ for (const key of Object.keys(response)) {
121
+ const value = (<any>response)[key];
122
+ if (this._isDateString(value)) {
123
+ (<any>response)[key] = moment.tz(value, this._dateTimeFormatString, this._ianaName);
124
+ } else if (this._isTimeString(value)) {
125
+ (<any>response)[key] = moment.tz(value, this._timeFormatString, this._ianaName);
126
+ } else if (typeof value === 'object') {
127
+ this._handleResponse(value);
128
+ }
129
+ }
130
+
131
+ return response;
132
+ }
133
+
134
+ protected override _handleError(error: any, obs: any) {
135
+ let responseError = new ResponseError();
136
+ if (error.error instanceof Error) {
137
+ console.error('Api - an error occurred:', error.error.message);
138
+ responseError.message = error.error.message;
139
+ } else if (error instanceof HttpErrorResponse) {
140
+ responseError = ResponseError.CreateFromHttpErrorResponse(error);
141
+ }
142
+
143
+ console.error(`Api - error code ${error.status} message ${responseError.message} ${responseError.stacktrace}`);
144
+
145
+ return throwError(() => responseError);
146
+ }
147
+
148
+ private _isDateString(value: string) {
149
+ if (value === null || value === undefined) {
150
+ return false;
151
+ }
152
+ if (typeof value === 'string') {
153
+ return this._dateTimeFormat.test(value);
154
+ }
155
+ return false;
156
+ }
157
+
158
+ private _isTimeString(value: string) {
159
+ if (value === null || value === undefined) {
160
+ return false;
161
+ }
162
+ if (typeof value === 'string') {
163
+ return this._timeFormat.test(value);
164
+ }
165
+ return false;
166
+ }
167
+ }
168
+ ```
169
+
170
+
171
+ ```ts
172
+ import { HttpErrorResponse } from '@angular/common/http';
173
+
174
+ export class ResponseError {
175
+ public url: string | null;
176
+ public status: number | null;
177
+ public message: string | null;
178
+ public stacktrace: string | null;
179
+
180
+ constructor() {
181
+ this.url = null;
182
+ this.status = null;
183
+ this.message = null;
184
+ this.stacktrace = null;
185
+ }
186
+
187
+ static CreateFromHttpErrorResponse(httpErrorResponse: HttpErrorResponse) {
188
+ let responseError = new ResponseError();
189
+
190
+ if (httpErrorResponse.error != null && !(httpErrorResponse.error instanceof ProgressEvent)) {
191
+ if (httpErrorResponse.error.hasOwnProperty('message')) {
192
+ responseError.message = httpErrorResponse.error.message;
193
+ }
194
+
195
+ if (httpErrorResponse.error.hasOwnProperty('stacktrace')) {
196
+ responseError.stacktrace = httpErrorResponse.error.stacktrace;
197
+ }
198
+ } else {
199
+ responseError.message = httpErrorResponse.message;
200
+ }
201
+
202
+ responseError.status = httpErrorResponse.status;
203
+ responseError.url = httpErrorResponse.url;
204
+
205
+ return responseError;
206
+ }
207
+ }
208
+ ```
package/package.json CHANGED
@@ -1,19 +1,25 @@
1
1
  {
2
2
  "name": "@devlearning/swagger-generator",
3
- "version": "1.0.4",
4
- "description": "",
3
+ "version": "1.0.6",
4
+ "description": "Swagger generator apis and models for Angular and NextJS",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "dev": "ts-node --esm ./src/index.ts http://localhost:7550/swagger/ApiGateway/swagger.json autogeneration/output",
9
- "debug-angular": "npx tsx src/index.ts http://localhost:5208/swagger/v1/swagger.json autogeneration/output angular moment",
10
- "debug-nextjs": "npx tsx src/index.ts http://localhost:5208/swagger/v1/swagger.json autogeneration/output next date-fns",
9
+ "debug-angular": "npx tsx src/index.ts http://localhost:7550/swagger/ApiGateway/swagger.json autogeneration/output angular moment",
10
+ "debug-nextjs": "npx tsx src/index.ts http://localhost:7550/swagger/ApiGateway/swagger.json autogeneration/output next date-fns",
11
11
  "deploy": "npx tsc && npm publish"
12
12
  },
13
13
  "bin": {
14
14
  "swgen": "./dist/index.js"
15
15
  },
16
- "keywords": [],
16
+ "keywords": [
17
+ "swagger",
18
+ "angular",
19
+ "nextjs",
20
+ "api",
21
+ "generator"
22
+ ],
17
23
  "author": "",
18
24
  "license": "ISC",
19
25
  "dependencies": {
package/src/generator.ts CHANGED
@@ -15,6 +15,7 @@ import { ModelNextJsWriter } from './generators-writers/nextjs/model-nextjs-writ
15
15
  import { ModelAngularWriter } from './generators-writers/angular/model-angular-writer.js';
16
16
  import { PropertyDto } from './models/property-dto.js';
17
17
  import { EnumValueDto } from './models/enum-value-dto.js';
18
+ import pkg from '../package.json' assert { type: 'json' };
18
19
 
19
20
  const contentTypeApplicationJson = 'application/json';
20
21
  const contentTypeMultipartFormData = 'multipart/form-data';
@@ -52,6 +53,8 @@ export class Generator {
52
53
  }
53
54
 
54
55
  generate() {
56
+ console.info(`%c[Swagger API Generator] %cVersion ${pkg.version}`, 'color: #4CAF50; font-weight: bold;');
57
+
55
58
  console.info('%c[Swagger API Generator] %cStarting to parse Swagger JSON file...', 'color: #4CAF50; font-weight: bold;', 'color: #2196F3;');
56
59
 
57
60
  this.computeApi();
@@ -67,7 +70,7 @@ export class Generator {
67
70
  this._barApis.start(Object.getOwnPropertyNames(this._swagger.paths).length, 0);
68
71
  for (let index = 0; index < Object.getOwnPropertyNames(this._swagger.paths).length; index++) {
69
72
  const apiName = Object.getOwnPropertyNames(this._swagger.paths)[index];
70
- this._barApis.update(index, { message: `Elaborando api... ${apiName}` });
73
+ this._barApis.update(index, { message: `Apis parsing... ${apiName}` });
71
74
  const apiSwaggerMethodKey = this._swagger.paths[apiName];
72
75
  const apiMethod = Object.getOwnPropertyNames(apiSwaggerMethodKey)[0];
73
76
  const apiSwaggerMethod = apiSwaggerMethodKey[apiMethod];
@@ -108,7 +111,7 @@ export class Generator {
108
111
  let parametersRefType = apiSwaggerMethod.parameters?.filter(x => x.in == 'query' && x.schema?.$ref != null).map(x => x.schema.$ref.replace('#/components/schemas/', ''))
109
112
  if (parametersRefType) {
110
113
  usedTypes = usedTypes.concat(parametersRefType);
111
- this._barModels.update(index, { message: `Elaborando model... ${usedTypes}` });
114
+ this._barModels.update(index, { message: `Models parsing... ${usedTypes}` });
112
115
 
113
116
  if (apiSwaggerMethod.responses[200].content[contentTypeApplicationJson]?.schema.$ref != null) {
114
117
  usedTypes.push(apiSwaggerMethod.responses[200].content[contentTypeApplicationJson].schema.$ref.replace('#/components/schemas/', ''));
@@ -170,7 +173,7 @@ export class Generator {
170
173
  }
171
174
 
172
175
  generateApi() {
173
- this._barApis.update(this._apis.length, { message: `Inizio scrittura api...` });
176
+ this._barApis.update(this._apis.length, { message: `Api generating...` });
174
177
 
175
178
  if (this._outputFormat == 'angular') {
176
179
  const apiWriter = new ApiAngularWriter(this._outputDirectory);
@@ -180,14 +183,14 @@ export class Generator {
180
183
  apiWriter.write(this._apis);
181
184
  }
182
185
 
183
- this._barApis.update(this._apis.length, { message: `Scrittura api completata` });
186
+ this._barApis.update(this._apis.length, { message: `Api gerated` });
184
187
 
185
188
  this._barApis.stop();
186
189
  }
187
190
 
188
191
 
189
192
  generateModel() {
190
- this._barModels.update(this._apis.length, { message: `Inizio scrittura model...` });
193
+ this._barModels.update(this._apis.length, { message: `Model generation...` });
191
194
 
192
195
  if (this._outputFormat == 'angular') {
193
196
  const apiWriter = new ModelAngularWriter(this._outputDirectory);
@@ -197,7 +200,7 @@ export class Generator {
197
200
  apiWriter.write(this._models);
198
201
  }
199
202
 
200
- this._barModels.update(this._apis.length, { message: `Scrittura model completata` });
203
+ this._barModels.update(this._apis.length, { message: `Model generated` });
201
204
 
202
205
  this._barModels.stop();
203
206
  }
@@ -51,7 +51,8 @@ export class ApiAngularWriter {
51
51
  let parametersString = '';
52
52
 
53
53
  api.parameters.forEach(parameter => {
54
- parametersString += `${parameter.name}${parameter.nullable ? '?' : ''}: ${parameter.typeName}, `;
54
+ const prefixType = parameter.isEnum || !parameter.isNativeType ? 'Models.' : '';
55
+ parametersString += `${parameter.name}${parameter.nullable ? '?' : ''}: ${prefixType}${parameter.typeName}, `;
55
56
  });
56
57
 
57
58
  if (api.parameters.length > 0)
@@ -61,7 +62,8 @@ export class ApiAngularWriter {
61
62
  }
62
63
 
63
64
  private _returnType(api: ApiDto) {
64
- return api.returnType ? api.returnType.typeName : 'any';
65
+ const prefixType = !api.returnType?.isNativeType ? 'Models.' : '';
66
+ return api.returnType ? `${prefixType}${api.returnType.typeName}` : 'any';
65
67
  }
66
68
 
67
69
  private _queryParametersPreparation(api: ApiDto) {
package/tsconfig.json CHANGED
@@ -4,6 +4,7 @@
4
4
  "module": "NodeNext",
5
5
  "baseUrl": "./src",
6
6
  "moduleResolution": "NodeNext",
7
+ "resolveJsonModule": true,
7
8
  "paths": {
8
9
  "@src/*": [
9
10
  "*"