Skip to content

Commit 611a533

Browse files
aaqilnizdhmlau
authored andcommitted
fix(cli): openapi generator implementation for controllers
This PR adds implementation to controllers Signed-off-by: Muhammad Aaqil <aaqilniz@yahoo.com>
1 parent 8833232 commit 611a533

10 files changed

Lines changed: 177 additions & 87 deletions

docs/site/OpenAPI-generator.md

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ lb4 openapi [<url>] [options]
3131
- `--validate`: Validate the OpenAPI spec. Default: `false`.
3232
- `--promote-anonymous-schemas`: Promote anonymous schemas as models classes.
3333
Default: `false`.
34-
- `--client`: Generate client-side service proxies for the OpenAPI spec Default:
35-
`false`.
34+
- `--client`: Generate client-side service proxies and controllers with
35+
implementation for the OpenAPI spec Default: `false`.
3636
- `--datasource`: A valid datasource name
3737
- `--positional`: A flag to control if service methods use positional parameters
3838
or an object with named properties. Default: `true`.
@@ -520,6 +520,98 @@ export class CustomerServiceProvider implements Provider<CustomerService> {
520520
}
521521
````
522522

523+
Along with service interface and proxy, controller implementation for each tag
524+
is generated by consuming the generated services.
525+
526+
{% include code-caption.html content="src/services/customer.controller.ts"
527+
%}
528+
529+
```
530+
import {operation, param} from '@loopback/rest';
531+
import {DateTime} from '../models/date-time.model';
532+
import {inject} from '@loopback/core';
533+
import {CustomerService} from '../services';'
534+
/**
535+
* The controller class is generated from OpenAPI spec with operations tagged
536+
* by account
537+
*
538+
*/
539+
export class AccountController {
540+
constructor(@inject('services.CustomerServic')
541+
protected customerService: CustomerService) {}
542+
543+
/**
544+
* Get list of carts.
545+
*/
546+
@operation('get', '/account.cart.list.json')
547+
async accountCartList(
548+
@param({name: 'params', in: 'query'}) params: string,
549+
@param({name: 'exclude', in: 'query'}) exclude: string,
550+
@param({name: 'request_from_date', in: 'query'}) request_from_date: string,
551+
@param({name: 'request_to_date', in: 'query'}) request_to_date: string,
552+
): Promise<{
553+
result?: {
554+
carts?: {
555+
cart_id?: string;
556+
id?: string;
557+
store_key?: string;
558+
total_calls?: string;
559+
url?: string;
560+
}[];
561+
carts_count?: number;
562+
};
563+
return_code?: number;
564+
return_message?: string;
565+
}> {
566+
return this.customerService.accountCartList(params, exclude, request_from_date, request_to_date);
567+
}
568+
569+
/**
570+
* Update configs in the API2Cart database.
571+
*/
572+
@operation('put', '/account.config.update.json')
573+
async accountConfigUpdate(
574+
@param({name: 'db_tables_prefix', in: 'query'}) db_tables_prefix: string,
575+
@param({name: 'client_id', in: 'query'}) client_id: string,
576+
@param({name: 'bridge_url', in: 'query'}) bridge_url: string,
577+
@param({name: 'store_root', in: 'query'}) store_root: string,
578+
@param({name: 'shared_secret', in: 'query'}) shared_secret: string,
579+
): Promise<{
580+
result?: {
581+
updated_items?: number;
582+
};
583+
return_code?: number;
584+
return_message?: string;
585+
}> {
586+
return this.customerService.accountConfigUpdate(db_tables_prefix, client_id, bridge_url, store_roo, shared_secret);
587+
}
588+
589+
/**
590+
* List webhooks that was not delivered to the callback.
591+
*/
592+
@operation('get', '/account.failed_webhooks.json')
593+
async accountFailedWebhooks(
594+
@param({name: 'count', in: 'query'}) count: number,
595+
@param({name: 'start', in: 'query'}) start: number,
596+
@param({name: 'ids', in: 'query'}) ids: string,
597+
): Promise<{
598+
result?: {
599+
all_failed_webhook?: string;
600+
webhook?: {
601+
entity_id?: string;
602+
time?: DateTime;
603+
webhook_id?: number;
604+
}[];
605+
};
606+
return_code?: number;
607+
return_message?: string;
608+
}> {
609+
return this.customerService.accountFailedWebhooks(count, start, ids);
610+
}
611+
}
612+
613+
```
614+
523615
## OpenAPI Examples
524616

525617
Try out the following specs or your own with `lb4 openapi`:

packages/cli/generators/openapi/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,12 +292,13 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
292292
});
293293
}
294294

295-
_generateControllers() {
295+
_generateControllers(withImplementation) {
296296
const source = this.templatePath(
297297
'src/controllers/controller-template.ts.ejs',
298298
);
299299
for (const c of this.selectedControllers) {
300300
const controllerFile = c.fileName;
301+
c.withImplementation = withImplementation;
301302
if (debug.enabled) {
302303
debug(`Artifact output filename set to: ${controllerFile}`);
303304
}
@@ -478,7 +479,7 @@ module.exports = class OpenApiGenerator extends BaseGenerator {
478479
this._generateModels();
479480
await this._updateIndex(MODEL);
480481
if (this.options.server !== false) {
481-
this._generateControllers();
482+
this._generateControllers(this.options.client);
482483
await this._updateIndex(CONTROLLER);
483484
}
484485
if (this.options.client === true) {

packages/cli/generators/openapi/spec-helper.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,24 @@ function buildMethodSpec(controllerSpec, op, options) {
349349
if (op.spec['x-implementation']) {
350350
methodSpec.implementation = op.spec['x-implementation'];
351351
}
352+
if (!methodSpec.implementation) {
353+
const methodParameters = {};
354+
methodParameters[methodName] = [];
355+
if (parameters) {
356+
parameters.forEach(param => {
357+
methodParameters[methodName].push(param.name);
358+
});
359+
}
360+
if (op.spec.requestBody) {
361+
methodParameters[methodName].push('_requestBody');
362+
}
363+
controllerSpec.serviceClassNameCamelCase = camelCase(
364+
controllerSpec.serviceClassName,
365+
);
366+
methodSpec.implementation = `return this.${
367+
controllerSpec.serviceClassNameCamelCase
368+
}.${methodName}(${methodParameters[methodName].join(', ')});`;
369+
}
352370
return methodSpec;
353371

354372
/**

packages/cli/generators/openapi/templates/src/controllers/controller-template.ts.ejs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import {api, operation, param, requestBody} from '@loopback/rest';
1+
import {api, operation, param, requestBody} from '@loopback/rest';<%if (withImplementation) { %>
2+
import {inject} from '@loopback/core';
3+
4+
import {<%- serviceClassName %>} from '../services';
5+
<% } %>
26
<%_
37
imports.forEach(i => {
48
-%>
@@ -18,9 +22,11 @@ imports.forEach(i => {
1822
<%_ } -%>
1923
*/
2024
<%- decoration %>
21-
export class <%- className %> {
22-
constructor() {}
23-
25+
export class <%- className %> {<% if(withImplementation){ %>
26+
constructor(@inject('services.<%- serviceClassName %>')
27+
protected <%- serviceClassNameCamelCase %>: <%- serviceClassName %>) {}
28+
<% } else { %>
29+
constructor() {} <% } %>
2430
<%_ for (const m of methods) { -%>
2531
/**
2632
<%_ for (const c of m.comments) {
@@ -35,9 +41,8 @@ export class <%- className %> {
3541
*/
3642
<%- m.decoration %>
3743
<%- m.signature %> {
38-
<%- m.implementation || "throw new Error('Not implemented');" %>
44+
<% if(withImplementation){ %> <%- m.implementation %> <% } else{ %> throw new Error('Not implemented'); <% } %>
3945
}
40-
4146
<%_ } -%>
4247
}
4348

packages/cli/snapshots/integration/generators/openapi-client.integration.snapshots.js

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,10 @@ export * from './open-api.controller';
431431

432432
exports[`openapi-generator with --client allows baseModel option 2`] = `
433433
import {api, operation, param, requestBody} from '@loopback/rest';
434+
import {inject} from '@loopback/core';
435+
436+
import {OpenApiService} from '../services';
437+
434438
import {Pet} from '../models/pet.model';
435439
import {NewPet} from '../models/new-pet.model';
436440
@@ -493,8 +497,9 @@ import {NewPet} from '../models/new-pet.model';
493497
paths: {},
494498
})
495499
export class OpenApiController {
496-
constructor() {}
497-
500+
constructor(@inject('services.OpenApiService')
501+
protected openApiService: OpenApiService) {}
502+
498503
/**
499504
* Returns all pets from the system that the user has access to
500505
Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem
@@ -630,9 +635,8 @@ pulvinar elit eu, euismod sapien.
630635
// eslint-disable-next-line @typescript-eslint/no-explicit-any
631636
[additionalProperty: string]: any;
632637
} | undefined): Promise<Pet[]> {
633-
throw new Error('Not implemented');
638+
return this.openApiService.findPets(tags, limit, where);
634639
}
635-
636640
/**
637641
* Creates a new pet in the store. Duplicates are allowed
638642
*
@@ -687,9 +691,8 @@ pulvinar elit eu, euismod sapien.
687691
},
688692
},
689693
}) _requestBody: NewPet): Promise<Pet> {
690-
throw new Error('Not implemented');
694+
return this.openApiService.addPet(_requestBody);
691695
}
692-
693696
/**
694697
* Returns a user based on a single ID, if the user does not have access to the
695698
pet
@@ -745,9 +748,8 @@ pet
745748
format: 'int64',
746749
},
747750
}) id: number): Promise<Pet> {
748-
throw new Error('Not implemented');
751+
return this.openApiService.findPetById(id);
749752
}
750-
751753
/**
752754
* deletes a single pet based on the ID supplied
753755
*
@@ -794,9 +796,8 @@ pet
794796
format: 'int64',
795797
},
796798
}) id: number): Promise<unknown> {
797-
throw new Error('Not implemented');
799+
return this.openApiService.deletePet(id);
798800
}
799-
800801
}
801802
802803
@@ -1136,8 +1137,7 @@ import {NewPet} from '../models/new-pet.model';
11361137
paths: {},
11371138
})
11381139
export class OpenApiController {
1139-
constructor() {}
1140-
1140+
constructor() {}
11411141
/**
11421142
* Returns all pets from the system that the user has access to
11431143
Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem
@@ -1273,9 +1273,8 @@ pulvinar elit eu, euismod sapien.
12731273
// eslint-disable-next-line @typescript-eslint/no-explicit-any
12741274
[additionalProperty: string]: any;
12751275
} | undefined): Promise<Pet[]> {
1276-
throw new Error('Not implemented');
1276+
throw new Error('Not implemented');
12771277
}
1278-
12791278
/**
12801279
* Creates a new pet in the store. Duplicates are allowed
12811280
*
@@ -1330,9 +1329,8 @@ pulvinar elit eu, euismod sapien.
13301329
},
13311330
},
13321331
}) _requestBody: NewPet): Promise<Pet> {
1333-
throw new Error('Not implemented');
1332+
throw new Error('Not implemented');
13341333
}
1335-
13361334
/**
13371335
* Returns a user based on a single ID, if the user does not have access to the
13381336
pet
@@ -1388,9 +1386,8 @@ pet
13881386
format: 'int64',
13891387
},
13901388
}) id: number): Promise<Pet> {
1391-
throw new Error('Not implemented');
1389+
throw new Error('Not implemented');
13921390
}
1393-
13941391
/**
13951392
* deletes a single pet based on the ID supplied
13961393
*
@@ -1437,9 +1434,8 @@ pet
14371434
format: 'int64',
14381435
},
14391436
}) id: number): Promise<unknown> {
1440-
throw new Error('Not implemented');
1437+
throw new Error('Not implemented');
14411438
}
1442-
14431439
}
14441440
14451441
@@ -1834,6 +1830,10 @@ export * from './open-api.controller';
18341830

18351831
exports[`openapi-generator with --client generates all files for both server and client 2`] = `
18361832
import {api, operation, param, requestBody} from '@loopback/rest';
1833+
import {inject} from '@loopback/core';
1834+
1835+
import {OpenApiService} from '../services';
1836+
18371837
import {Pet} from '../models/pet.model';
18381838
import {NewPet} from '../models/new-pet.model';
18391839
@@ -1896,8 +1896,9 @@ import {NewPet} from '../models/new-pet.model';
18961896
paths: {},
18971897
})
18981898
export class OpenApiController {
1899-
constructor() {}
1900-
1899+
constructor(@inject('services.OpenApiService')
1900+
protected openApiService: OpenApiService) {}
1901+
19011902
/**
19021903
* Returns all pets from the system that the user has access to
19031904
Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem
@@ -2033,9 +2034,8 @@ pulvinar elit eu, euismod sapien.
20332034
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20342035
[additionalProperty: string]: any;
20352036
} | undefined): Promise<Pet[]> {
2036-
throw new Error('Not implemented');
2037+
return this.openApiService.findPets(tags, limit, where);
20372038
}
2038-
20392039
/**
20402040
* Creates a new pet in the store. Duplicates are allowed
20412041
*
@@ -2090,9 +2090,8 @@ pulvinar elit eu, euismod sapien.
20902090
},
20912091
},
20922092
}) _requestBody: NewPet): Promise<Pet> {
2093-
throw new Error('Not implemented');
2093+
return this.openApiService.addPet(_requestBody);
20942094
}
2095-
20962095
/**
20972096
* Returns a user based on a single ID, if the user does not have access to the
20982097
pet
@@ -2148,9 +2147,8 @@ pet
21482147
format: 'int64',
21492148
},
21502149
}) id: number): Promise<Pet> {
2151-
throw new Error('Not implemented');
2150+
return this.openApiService.findPetById(id);
21522151
}
2153-
21542152
/**
21552153
* deletes a single pet based on the ID supplied
21562154
*
@@ -2197,9 +2195,8 @@ pet
21972195
format: 'int64',
21982196
},
21992197
}) id: number): Promise<unknown> {
2200-
throw new Error('Not implemented');
2198+
return this.openApiService.deletePet(id);
22012199
}
2202-
22032200
}
22042201
22052202

0 commit comments

Comments
 (0)