diff --git a/packages/cli/generators/openapi/spec-helper.js b/packages/cli/generators/openapi/spec-helper.js index 544227f13f18..38b2eebf89a8 100644 --- a/packages/cli/generators/openapi/spec-helper.js +++ b/packages/cli/generators/openapi/spec-helper.js @@ -354,7 +354,8 @@ function buildMethodSpec(controllerSpec, op, options) { methodParameters[methodName] = []; if (parameters) { parameters.forEach(param => { - methodParameters[methodName].push(param.name); + const {argName} = buildParameter(param, {}); + methodParameters[methodName].push(argName); }); } if (op.spec.requestBody) { diff --git a/packages/cli/snapshots/unit/openapi/controller-spec.unit.snapshots.js b/packages/cli/snapshots/unit/openapi/controller-spec.unit.snapshots.js index 2fed24c3c070..d9226ce54bc6 100644 --- a/packages/cli/snapshots/unit/openapi/controller-spec.unit.snapshots.js +++ b/packages/cli/snapshots/unit/openapi/controller-spec.unit.snapshots.js @@ -8,5 +8,5 @@ 'use strict'; exports[`openapi to controllers/models generates models for customer 1`] = ` -[{tag:'Customer',decoration:"@api({\\n components: {\\n schemas: {\\n Name: {\\n type: 'string',\\n },\\n profileId: {\\n type: 'string',\\n },\\n AddressList: {\\n items: {\\n $ref: '#/components/schemas/Address',\\n },\\n type: 'array',\\n },\\n Address: {\\n type: 'object',\\n properties: {\\n street: {\\n type: 'string',\\n },\\n city: {\\n type: 'string',\\n },\\n state: {\\n type: 'string',\\n },\\n zipCode: {\\n type: 'string',\\n },\\n },\\n additionalProperties: true,\\n },\\n USAddress: {\\n $ref: '#/components/schemas/Address',\\n },\\n Customer: {\\n required: [\\n 'id',\\n ],\\n properties: {\\n id: {\\n type: 'integer',\\n format: 'int64',\\n },\\n 'first-name': {\\n type: 'string',\\n },\\n 'last-name': {\\n $ref: '#/components/schemas/Name',\\n },\\n profiles: {\\n type: 'array',\\n items: {\\n $ref: '#/components/schemas/profileId',\\n },\\n },\\n emails: {\\n type: 'array',\\n items: {\\n type: 'string',\\n },\\n },\\n addresses: {\\n $ref: '#/components/schemas/AddressList',\\n },\\n 'us-office': {\\n $ref: '#/components/schemas/USAddress',\\n },\\n },\\n },\\n },\\n },\\n paths: {},\\n})",description:'Customer resource',className:'CustomerController',serviceClassName:'CustomerService',imports:["import {Customer} from '../models/customer.model';"],methodMapping:{getCustomers:'getCustomers',createCustomer:'createCustomer',findCustomerById:'find customer by id'},serviceClassNameCamelCase:'customerService',methods:[{description:'Returns all customers (/* customers */)',comments:['Returns all customers (/* customers */)','','@param _if if condition','@param limit maximum number of results to return','@param accessToken Access token (/* access_token */)','@returns customer response'],decoration:"@operation('get', '/customers', {\\n tags: [\\n 'Customer',\\n ],\\n description: 'Returns all customers (/* customers */)',\\n operationId: 'getCustomers',\\n parameters: [\\n {\\n name: 'if',\\n in: 'query',\\n description: 'if condition',\\n required: false,\\n style: 'form',\\n schema: {\\n type: 'array',\\n items: {\\n type: 'string',\\n },\\n },\\n },\\n {\\n name: 'limit',\\n in: 'query',\\n description: 'maximum number of results to return',\\n required: false,\\n schema: {\\n type: 'integer',\\n format: 'int32',\\n },\\n },\\n {\\n name: 'access-token',\\n in: 'query',\\n description: 'Access token (/* access_token */)',\\n required: false,\\n schema: {\\n type: 'string',\\n },\\n },\\n ],\\n responses: {\\n '200': {\\n description: 'customer response',\\n content: {\\n 'application/json': {\\n schema: {\\n type: 'array',\\n items: {\\n $ref: '#/components/schemas/Customer',\\n },\\n },\\n },\\n },\\n },\\n },\\n})",signature:"async getCustomers(@param({\\n name: 'if',\\n in: 'query',\\n description: 'if condition',\\n required: false,\\n style: 'form',\\n schema: {\\n type: 'array',\\n items: {\\n type: 'string',\\n },\\n },\\n}) _if: string[] | undefined, @param({\\n name: 'limit',\\n in: 'query',\\n description: 'maximum number of results to return',\\n required: false,\\n schema: {\\n type: 'integer',\\n format: 'int32',\\n },\\n}) limit: number | undefined, @param({\\n name: 'access-token',\\n in: 'query',\\n description: 'Access token (/* access_token */)',\\n required: false,\\n schema: {\\n type: 'string',\\n },\\n}) accessToken: string | undefined): Promise",signatureForInterface:'getCustomers(_if: string[] | undefined, limit: number | undefined, accessToken: string | undefined): Promise',signatureForNamedParams:"getCustomers(params: { if: string[]; limit: number; 'access-token': string }): Promise",implementation:'return this.customerService.getCustomers(if, limit, access-token);'},{description:'Creates a new customer',comments:['Creates a new customer','','@param accessToken Access token (/* access_token */)','@param _requestBody Customer to add','@returns customer response'],decoration:"@operation('post', '/customers', {\\n tags: [\\n 'Customer',\\n ],\\n description: 'Creates a new customer',\\n operationId: 'addCustomer',\\n 'x-operation-name': 'createCustomer',\\n requestBody: {\\n description: 'Customer to add',\\n required: true,\\n content: {\\n 'application/json': {\\n schema: {\\n $ref: '#/components/schemas/Customer',\\n },\\n },\\n },\\n },\\n responses: {\\n '200': {\\n description: 'customer response',\\n content: {\\n 'application/json': {\\n schema: {\\n $ref: '#/components/schemas/Customer',\\n },\\n },\\n },\\n },\\n },\\n parameters: [\\n {\\n name: 'access-token',\\n in: 'query',\\n description: 'Access token (/* access_token */)',\\n required: false,\\n schema: {\\n type: 'string',\\n },\\n },\\n ],\\n})",signature:"async createCustomer(@param({\\n name: 'access-token',\\n in: 'query',\\n description: 'Access token (/* access_token */)',\\n required: false,\\n schema: {\\n type: 'string',\\n },\\n}) accessToken: string | undefined, @requestBody({\\n description: 'Customer to add',\\n required: true,\\n content: {\\n 'application/json': {\\n schema: {\\n $ref: '#/components/schemas/Customer',\\n },\\n },\\n },\\n}) _requestBody: Customer): Promise",signatureForInterface:'createCustomer(accessToken: string | undefined, _requestBody: Customer): Promise',signatureForNamedParams:"createCustomer(params: { 'access-token': string; requestBody: Customer }): Promise",implementation:'return this.customerService.createCustomer(access-token, _requestBody);'},{description:'Returns a customer based on a single ID',comments:['Returns a customer based on a single ID','','@param customerId ID of customer to fetch','@returns customer response'],decoration:"@operation('get', '/customers/{customer_id}', {\\n tags: [\\n 'Customer',\\n ],\\n description: 'Returns a customer based on a single ID',\\n 'x-implementation': \\"return {id: customerId, 'first-name': 'John', 'last-name': 'Smith'};\\",\\n operationId: 'find customer by id',\\n parameters: [\\n {\\n name: 'customer-id',\\n in: 'path',\\n description: 'ID of customer to fetch',\\n required: true,\\n schema: {\\n type: 'integer',\\n format: 'int64',\\n },\\n },\\n ],\\n responses: {\\n '200': {\\n description: 'customer response',\\n content: {\\n 'application/json': {\\n schema: {\\n $ref: '#/components/schemas/Customer',\\n },\\n },\\n },\\n },\\n },\\n})",signature:"async findCustomerById(@param({\\n name: 'customer-id',\\n in: 'path',\\n description: 'ID of customer to fetch',\\n required: true,\\n schema: {\\n type: 'integer',\\n format: 'int64',\\n },\\n}) customerId: number): Promise",signatureForInterface:'findCustomerById(customerId: number): Promise',signatureForNamedParams:'findCustomerById(params: { customer_id: number }): Promise',implementation:"return {id: customerId, 'first-name': 'John', 'last-name': 'Smith'};"}],methodMappingObject:"{\\n getCustomers: 'getCustomers',\\n createCustomer: 'createCustomer',\\n findCustomerById: 'find customer by id',\\n}"}] +[{tag:'Customer',decoration:"@api({\\n components: {\\n schemas: {\\n Name: {\\n type: 'string',\\n },\\n profileId: {\\n type: 'string',\\n },\\n AddressList: {\\n items: {\\n $ref: '#/components/schemas/Address',\\n },\\n type: 'array',\\n },\\n Address: {\\n type: 'object',\\n properties: {\\n street: {\\n type: 'string',\\n },\\n city: {\\n type: 'string',\\n },\\n state: {\\n type: 'string',\\n },\\n zipCode: {\\n type: 'string',\\n },\\n },\\n additionalProperties: true,\\n },\\n USAddress: {\\n $ref: '#/components/schemas/Address',\\n },\\n Customer: {\\n required: [\\n 'id',\\n ],\\n properties: {\\n id: {\\n type: 'integer',\\n format: 'int64',\\n },\\n 'first-name': {\\n type: 'string',\\n },\\n 'last-name': {\\n $ref: '#/components/schemas/Name',\\n },\\n profiles: {\\n type: 'array',\\n items: {\\n $ref: '#/components/schemas/profileId',\\n },\\n },\\n emails: {\\n type: 'array',\\n items: {\\n type: 'string',\\n },\\n },\\n addresses: {\\n $ref: '#/components/schemas/AddressList',\\n },\\n 'us-office': {\\n $ref: '#/components/schemas/USAddress',\\n },\\n },\\n },\\n },\\n },\\n paths: {},\\n})",description:'Customer resource',className:'CustomerController',serviceClassName:'CustomerService',imports:["import {Customer} from '../models/customer.model';"],methodMapping:{getCustomers:'getCustomers',createCustomer:'createCustomer',findCustomerById:'find customer by id'},serviceClassNameCamelCase:'customerService',methods:[{description:'Returns all customers (/* customers */)',comments:['Returns all customers (/* customers */)','','@param _if if condition','@param limit maximum number of results to return','@param accessToken Access token (/* access_token */)','@returns customer response'],decoration:"@operation('get', '/customers', {\\n tags: [\\n 'Customer',\\n ],\\n description: 'Returns all customers (/* customers */)',\\n operationId: 'getCustomers',\\n parameters: [\\n {\\n name: 'if',\\n in: 'query',\\n description: 'if condition',\\n required: false,\\n style: 'form',\\n schema: {\\n type: 'array',\\n items: {\\n type: 'string',\\n },\\n },\\n },\\n {\\n name: 'limit',\\n in: 'query',\\n description: 'maximum number of results to return',\\n required: false,\\n schema: {\\n type: 'integer',\\n format: 'int32',\\n },\\n },\\n {\\n name: 'access-token',\\n in: 'query',\\n description: 'Access token (/* access_token */)',\\n required: false,\\n schema: {\\n type: 'string',\\n },\\n },\\n ],\\n responses: {\\n '200': {\\n description: 'customer response',\\n content: {\\n 'application/json': {\\n schema: {\\n type: 'array',\\n items: {\\n $ref: '#/components/schemas/Customer',\\n },\\n },\\n },\\n },\\n },\\n },\\n})",signature:"async getCustomers(@param({\\n name: 'if',\\n in: 'query',\\n description: 'if condition',\\n required: false,\\n style: 'form',\\n schema: {\\n type: 'array',\\n items: {\\n type: 'string',\\n },\\n },\\n}) _if: string[] | undefined, @param({\\n name: 'limit',\\n in: 'query',\\n description: 'maximum number of results to return',\\n required: false,\\n schema: {\\n type: 'integer',\\n format: 'int32',\\n },\\n}) limit: number | undefined, @param({\\n name: 'access-token',\\n in: 'query',\\n description: 'Access token (/* access_token */)',\\n required: false,\\n schema: {\\n type: 'string',\\n },\\n}) accessToken: string | undefined): Promise",signatureForInterface:'getCustomers(_if: string[] | undefined, limit: number | undefined, accessToken: string | undefined): Promise',signatureForNamedParams:"getCustomers(params: { if: string[]; limit: number; 'access-token': string }): Promise",implementation:'return this.customerService.getCustomers(_if, limit, accessToken);'},{description:'Creates a new customer',comments:['Creates a new customer','','@param accessToken Access token (/* access_token */)','@param _requestBody Customer to add','@returns customer response'],decoration:"@operation('post', '/customers', {\\n tags: [\\n 'Customer',\\n ],\\n description: 'Creates a new customer',\\n operationId: 'addCustomer',\\n 'x-operation-name': 'createCustomer',\\n requestBody: {\\n description: 'Customer to add',\\n required: true,\\n content: {\\n 'application/json': {\\n schema: {\\n $ref: '#/components/schemas/Customer',\\n },\\n },\\n },\\n },\\n responses: {\\n '200': {\\n description: 'customer response',\\n content: {\\n 'application/json': {\\n schema: {\\n $ref: '#/components/schemas/Customer',\\n },\\n },\\n },\\n },\\n },\\n parameters: [\\n {\\n name: 'access-token',\\n in: 'query',\\n description: 'Access token (/* access_token */)',\\n required: false,\\n schema: {\\n type: 'string',\\n },\\n },\\n ],\\n})",signature:"async createCustomer(@param({\\n name: 'access-token',\\n in: 'query',\\n description: 'Access token (/* access_token */)',\\n required: false,\\n schema: {\\n type: 'string',\\n },\\n}) accessToken: string | undefined, @requestBody({\\n description: 'Customer to add',\\n required: true,\\n content: {\\n 'application/json': {\\n schema: {\\n $ref: '#/components/schemas/Customer',\\n },\\n },\\n },\\n}) _requestBody: Customer): Promise",signatureForInterface:'createCustomer(accessToken: string | undefined, _requestBody: Customer): Promise',signatureForNamedParams:"createCustomer(params: { 'access-token': string; requestBody: Customer }): Promise",implementation:'return this.customerService.createCustomer(accessToken, _requestBody);'},{description:'Returns a customer based on a single ID',comments:['Returns a customer based on a single ID','','@param customerId ID of customer to fetch','@returns customer response'],decoration:"@operation('get', '/customers/{customer_id}', {\\n tags: [\\n 'Customer',\\n ],\\n description: 'Returns a customer based on a single ID',\\n 'x-implementation': \\"return {id: customerId, 'first-name': 'John', 'last-name': 'Smith'};\\",\\n operationId: 'find customer by id',\\n parameters: [\\n {\\n name: 'customer-id',\\n in: 'path',\\n description: 'ID of customer to fetch',\\n required: true,\\n schema: {\\n type: 'integer',\\n format: 'int64',\\n },\\n },\\n ],\\n responses: {\\n '200': {\\n description: 'customer response',\\n content: {\\n 'application/json': {\\n schema: {\\n $ref: '#/components/schemas/Customer',\\n },\\n },\\n },\\n },\\n },\\n})",signature:"async findCustomerById(@param({\\n name: 'customer-id',\\n in: 'path',\\n description: 'ID of customer to fetch',\\n required: true,\\n schema: {\\n type: 'integer',\\n format: 'int64',\\n },\\n}) customerId: number): Promise",signatureForInterface:'findCustomerById(customerId: number): Promise',signatureForNamedParams:'findCustomerById(params: { customer_id: number }): Promise',implementation:"return {id: customerId, 'first-name': 'John', 'last-name': 'Smith'};"}],methodMappingObject:"{\\n getCustomers: 'getCustomers',\\n createCustomer: 'createCustomer',\\n findCustomerById: 'find customer by id',\\n}"}] `; diff --git a/packages/cli/test/fixtures/openapi/3.0/hyphenated-params.json b/packages/cli/test/fixtures/openapi/3.0/hyphenated-params.json new file mode 100644 index 000000000000..b914f964bc0d --- /dev/null +++ b/packages/cli/test/fixtures/openapi/3.0/hyphenated-params.json @@ -0,0 +1,77 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Hyphenated Properties API", + "version": "1.0.0" + }, + "paths": { + "/customers": { + "get": { + "operationId": "getCustomers", + "summary": "List customers", + "parameters": [ + { + "name": "limit-number", + "in": "query", + "description": "Maximum number of customers to return", + "required": false, + "schema": { + "type": "integer", + "default": 10 + } + }, + { + "name": "status-code", + "in": "query", + "description": "Filter by customer status", + "required": false, + "schema": { + "type": "string", + "enum": ["active", "inactive"] + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Customer" + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Customer": { + "type": "object", + "properties": { + "customer-id": { + "type": "string", + "example": "cust_001" + }, + "first-name": { + "type": "string", + "example": "John" + }, + "last-name": { + "type": "string", + "example": "Doe" + } + }, + "required": [ + "customer-id", + "first-name" + ] + } + } + } +} diff --git a/packages/cli/test/unit/openapi/controller-spec.unit.js b/packages/cli/test/unit/openapi/controller-spec.unit.js index 7039950f9436..05df241e0186 100644 --- a/packages/cli/test/unit/openapi/controller-spec.unit.js +++ b/packages/cli/test/unit/openapi/controller-spec.unit.js @@ -6,8 +6,8 @@ const json5 = require('json5'); const {loadAndBuildSpec} = require('../../../generators/openapi/spec-loader'); const path = require('path'); - const {expectToMatchSnapshot} = require('../../snapshots'); +const assert = require('assert'); describe('openapi to controllers/models', () => { const customer = path.join( @@ -24,3 +24,32 @@ describe('openapi to controllers/models', () => { ); }); }); + +describe('openapi to controllers with hyphenated parameters', () => { + const hyphenSpec = path.join( + __dirname, + '../../fixtures/openapi/3.0/hyphenated-params.json', + ); + + it('generates correct parameter bindings for hyphenated names', async () => { + const spec = await loadAndBuildSpec(hyphenSpec); + const controllerSpecsString = json5.stringify(spec.controllerSpecs); + + assert( + controllerSpecsString.includes('limitNumber'), + 'Expected controllerSpecsString to include limitNumber', + ); + assert( + controllerSpecsString.includes('statusCode'), + 'Expected controllerSpecsString to include statusCode', + ); + assert( + !controllerSpecsString.includes('limit-number,'), + 'Expected controllerSpecsString not to include raw limit-number', + ); + assert( + !controllerSpecsString.includes('status-code,'), + 'Expected controllerSpecsString not to include raw status-code', + ); + }); +});