diff --git a/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template b/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template index aaa4220d9b..70e3138fdd 100644 --- a/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template +++ b/npm/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template @@ -13,6 +13,10 @@ export class <%= name %>Service { const responseType = isBlob ? 'Blob' : body.responseType; const httpResponseType = body.httpResponseType; const acceptHeader = body.acceptHeader; + const headerEntries = []; + if (acceptHeader) { headerEntries.push("Accept: '" + acceptHeader + "'"); } + if (body.contentTypeHeader) { headerEntries.push("'Content-Type': '" + body.contentTypeHeader + "'"); } + const headers = headerEntries.length ? '{ ' + headerEntries.join(', ') + ' }' : ''; const resourceParameters = signature.parameters.filter(p => p.name !== 'config'); const resourceType = resourceParameters.length ? `{ ${resourceParameters.map(p => `${p.name}${p.optional}: ${p.type}`).join('; ')} }` @@ -33,8 +37,8 @@ export class <%= name %>Service { method: '<%= body.method %>',<% if (httpResponseType && httpResponseType !== 'json') { %> responseType: '<%= httpResponseType %>',<% } %><% - if (acceptHeader) { %> - headers: { Accept: '<%= acceptHeader %>' },<% } %> + if (headers) { %> + headers: <%= headers %>,<% } %> url: <%= body.url %>,<% if (body.dictParamVar && !body.params.length) { %> params: <%= body.dictParamVar %>,<% } %><% @@ -53,8 +57,8 @@ export class <%= name %>Service { method: '<%= body.method %>',<% if (httpResponseType && httpResponseType !== 'json') { %> responseType: '<%= httpResponseType %>',<% } %><% - if (acceptHeader) { %> - headers: { Accept: '<%= acceptHeader %>' },<% } %> + if (headers) { %> + headers: <%= headers %>,<% } %> url: <%= body.url %>,<% if (body.dictParamVar && !body.params.length) { %> params: <%= body.dictParamVar %>,<% } %><% diff --git a/npm/ng-packs/packages/schematics/src/models/method.ts b/npm/ng-packs/packages/schematics/src/models/method.ts index c160359770..aa55077a2c 100644 --- a/npm/ng-packs/packages/schematics/src/models/method.ts +++ b/npm/ng-packs/packages/schematics/src/models/method.ts @@ -47,6 +47,7 @@ export class Body { responseType: string; httpResponseType?: 'json' | 'text' | 'blob' | 'arraybuffer'; acceptHeader?: string; + contentTypeHeader?: string; url: string; registerActionParameter = (param: ParameterInBody) => { @@ -68,7 +69,15 @@ export class Body { this.params.push(paramName === value ? value : `${getParamName(paramName)}: ${value}`); break; case eBindingSourceId.FormFile: + this.body = value; + break; case eBindingSourceId.Body: + /* Angular sends a plain string body as text/plain, but the endpoint expects a JSON string. */ + if (param.typeSimple === 'string') { + this.body = `JSON.stringify(${value})`; + this.contentTypeHeader = 'application/json'; + break; + } this.body = value; break; case eBindingSourceId.Path: diff --git a/npm/ng-packs/packages/schematics/src/tests/action-to-body-mapper.spec.ts b/npm/ng-packs/packages/schematics/src/tests/action-to-body-mapper.spec.ts index 9e35e9ef8c..021046fb57 100644 --- a/npm/ng-packs/packages/schematics/src/tests/action-to-body-mapper.spec.ts +++ b/npm/ng-packs/packages/schematics/src/tests/action-to-body-mapper.spec.ts @@ -76,6 +76,50 @@ describe('createActionToBodyMapper — string return value', () => { }); }); +describe('createActionToBodyMapper — body parameters', () => { + const mapBody = createActionToBodyMapper(); + + test('string body parameter is JSON encoded and gets an application/json content type', () => { + const body = mapBody( + buildAction({ + httpMethod: 'POST', + parameters: [ + { + nameOnMethod: 'connectionString', + name: 'connectionString', + type: 'System.String', + typeSimple: 'string', + bindingSourceId: eBindingSourceId.Body, + }, + ], + } as Partial), + ); + + expect(body.body).toBe('JSON.stringify(connectionString)'); + expect(body.contentTypeHeader).toBe('application/json'); + }); + + test('object body parameter is passed as is', () => { + const body = mapBody( + buildAction({ + httpMethod: 'POST', + parameters: [ + { + nameOnMethod: 'input', + name: 'input', + type: 'My.Project.UserDto', + typeSimple: 'My.Project.UserDto', + bindingSourceId: eBindingSourceId.Body, + }, + ], + } as Partial), + ); + + expect(body.body).toBe('input'); + expect(body.contentTypeHeader).toBeUndefined(); + }); +}); + describe('createActionToBodyMapper — IRemoteStreamContent return value', () => { const mapBody = createActionToBodyMapper(); diff --git a/npm/ng-packs/packages/schematics/src/tests/proxy-service-template-render.spec.ts b/npm/ng-packs/packages/schematics/src/tests/proxy-service-template-render.spec.ts index 611c0ef599..10cdbd5da8 100644 --- a/npm/ng-packs/packages/schematics/src/tests/proxy-service-template-render.spec.ts +++ b/npm/ng-packs/packages/schematics/src/tests/proxy-service-template-render.spec.ts @@ -70,6 +70,7 @@ interface MockBody { responseTypeWithNamespace: string; httpResponseType?: string; acceptHeader?: string; + contentTypeHeader?: string; body?: string; params: string[]; dictParamVar?: string; @@ -85,6 +86,7 @@ function makeBody(overrides: Partial): MockBody { responseTypeWithNamespace: 'any', httpResponseType: undefined, acceptHeader: undefined, + contentTypeHeader: undefined, body: undefined, params: [], dictParamVar: undefined, @@ -106,6 +108,33 @@ describe('proxy service template — rendered output', () => { expect(output).not.toContain('headers:'); }); + test('string body emits a content type header and a JSON encoded body', () => { + const output = render(buildContext({ + method: 'POST', + body: 'JSON.stringify(connectionString)', + contentTypeHeader: 'application/json', + responseType: 'boolean', + responseTypeWithNamespace: 'boolean', + })); + + expect(output).toContain("headers: { 'Content-Type': 'application/json' }"); + expect(output).toContain('body: JSON.stringify(connectionString)'); + }); + + test('accept and content type headers are emitted together', () => { + const output = render(buildContext({ + method: 'POST', + body: 'JSON.stringify(value)', + contentTypeHeader: 'application/json', + acceptHeader: 'text/plain', + httpResponseType: 'text', + responseType: 'string', + responseTypeWithNamespace: 'string', + })); + + expect(output).toContain("headers: { Accept: 'text/plain', 'Content-Type': 'application/json' }"); + }); + test('resource api mode emits requestResource helper for GET methods', () => { const ctx = buildContext({ responseType: 'MyDto', diff --git a/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/generate-proxy.json b/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/generate-proxy.json index bb4cf82675..79b44f205a 100644 --- a/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/generate-proxy.json +++ b/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/generate-proxy.json @@ -493,7 +493,7 @@ "isOptional": false, "defaultValue": null, "constraintTypes": null, - "bindingSourceId": "ModelBinding", + "bindingSourceId": "Body", "descriptorName": "" } ], diff --git a/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts b/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts index 28174f8ad8..7995594b38 100644 --- a/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts +++ b/npm/ng-packs/packages/tenant-management/proxy/src/lib/proxy/tenant.service.ts @@ -67,8 +67,9 @@ export class TenantService { updateDefaultConnectionString = (id: string, defaultConnectionString: string) => this.restService.request({ method: 'PUT', + headers: { 'Content-Type': 'application/json' }, url: `/api/multi-tenancy/tenants/${id}/default-connection-string`, - params: { defaultConnectionString }, + body: JSON.stringify(defaultConnectionString), }, { apiName: this.apiName }); }