Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RestError to have details that point to swagger specified error shape #5437

Merged
merged 10 commits into from
Oct 19, 2019
9 changes: 9 additions & 0 deletions sdk/core/core-http/lib/policies/deserializationPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,17 @@ export function deserializeResponseBody(jsonContentTypes: string[], xmlContentTy
: [];
}
error.body = operationSpec.serializer.deserialize(defaultResponseBodyMapper, valueToDeserialize, "error.body");
error.parsedBody = error.body;
}
}

if (parsedResponse.headers && defaultResponseSpec.headersMapper) {
error.parsedHeaders = operationSpec.serializer.deserialize(
defaultResponseSpec.headersMapper,
parsedResponse.headers.rawHeaders(),
"operationRes.parsedHeaders"
);
}
} catch (defaultError) {
error.message = `Error \"${defaultError.message}\" occurred in deserializing the responseBody - \"${parsedResponse.bodyAsText}\" for the default response.`;
}
Expand Down
14 changes: 13 additions & 1 deletion sdk/core/core-http/lib/restError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,25 @@ export class RestError extends Error {
request?: WebResource;
response?: HttpOperationResponse;
body?: any;
constructor(message: string, code?: string, statusCode?: number, request?: WebResource, response?: HttpOperationResponse, body?: any) {
parsedBody?: unknown;
parsedHeaders?: { [key: string]: unknown };

constructor(
message: string,
code?: string,
statusCode?: number,
request?: WebResource,
response?: HttpOperationResponse,
body?: any,
parsedHeaders?: { [key: string]: unknown }
) {
super(message);
this.code = code;
this.statusCode = statusCode;
this.request = request;
this.response = response;
this.body = body;
this.parsedHeaders = parsedHeaders;

Object.setPrototypeOf(this, RestError.prototype);
}
Expand Down
21 changes: 18 additions & 3 deletions sdk/core/core-http/lib/serviceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export class ServiceClient {
* @param {OperationSpec} operationSpec The OperationSpec to use to populate the httpRequest.
* @param {ServiceCallback} callback The callback to call when the response is received.
*/
sendOperationRequest(operationArguments: OperationArguments, operationSpec: OperationSpec, callback?: ServiceCallback<any>): Promise<RestResponse> {
async sendOperationRequest(operationArguments: OperationArguments, operationSpec: OperationSpec, callback?: ServiceCallback<any>): Promise<RestResponse> {
if (typeof operationArguments.options === "function") {
callback = operationArguments.options;
operationArguments.options = undefined;
Expand Down Expand Up @@ -353,8 +353,23 @@ export class ServiceClient {
httpRequest.streamResponseBody = isStreamOperation(operationSpec);
}

result = this.sendRequest(httpRequest)
.then(res => flattenResponse(res, operationSpec.responses[res.status]));
let rawResponse: HttpOperationResponse;
let sendRequestError;
try {
rawResponse = await this.sendRequest(httpRequest)
} catch (error) {
sendRequestError = error;
}
if (sendRequestError) {
result = Promise.reject(
flattenResponse(
sendRequestError,
operationSpec.responses[sendRequestError.statusCode] || operationSpec.responses["default"]
)
)
} else {
result = Promise.resolve(flattenResponse(rawResponse!, operationSpec.responses[rawResponse!.status]));
}
} catch (error) {
result = Promise.reject(error);
}
Expand Down
66 changes: 65 additions & 1 deletion sdk/core/core-http/test/policies/deserializationPolicyTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { assert } from "chai";
import { HttpHeaders } from "../../lib/httpHeaders";
import { HttpOperationResponse } from "../../lib/httpOperationResponse";
import { HttpClient, OperationSpec, Serializer } from "../../lib/coreHttp";
import { HttpClient, OperationSpec, Serializer, CompositeMapper } from "../../lib/coreHttp";
import { DeserializationPolicy, deserializationPolicy, deserializeResponseBody, defaultJsonContentTypes, defaultXmlContentTypes } from "../../lib/policies/deserializationPolicy";
import { RequestPolicy, RequestPolicyOptions } from "../../lib/policies/requestPolicy";
import { WebResource } from "../../lib/webResource";
Expand Down Expand Up @@ -95,6 +95,70 @@ describe("deserializationPolicy", function () {
});

describe(`parse(HttpOperationResponse)`, () => {
it(`with default response headers`, async function () {
const BodyMapper: CompositeMapper = {
serializedName: "getproperties-body",
type: {
name: "Composite",
className: "PropertiesBody",
modelProperties: {
message: {
type: {
name: "String"
}
}
}
}
};

const HeadersMapper: CompositeMapper = {
serializedName: "getproperties-headers",
type: {
name: "Composite",
className: "PropertiesHeaders",
modelProperties: {
errorCode: {
serializedName: "x-ms-error-code",
type: {
name: "String"
}
}
}
}
};

const serializer = new Serializer(HeadersMapper, true);

const operationSpec: OperationSpec = {
httpMethod: "GET",
responses: {
default: {
headersMapper: HeadersMapper,
bodyMapper: BodyMapper
}
},
serializer
};

const response: HttpOperationResponse = {
request: createRequest(operationSpec),
status: 500,
headers: new HttpHeaders({
"x-ms-error-code": "InvalidResourceNameHeader"
}),
bodyAsText: '{"message": "InvalidResourceNameBody"}'
};

try {
await deserializeResponse(response);
assert.fail();
} catch (e) {
assert(e);
assert.strictEqual(e.parsedHeaders.errorCode, "InvalidResourceNameHeader");
assert.strictEqual(e.message, "InvalidResourceNameBody");
}
});

it(`with no response headers or body`, async function () {
const response: HttpOperationResponse = {
request: createRequest(),
Expand Down
76 changes: 74 additions & 2 deletions sdk/core/core-http/test/serviceClientTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import { assert } from "chai";
import { HttpClient } from "../lib/httpClient";
import { QueryCollectionFormat } from "../lib/queryCollectionFormat";
import { DictionaryMapper, MapperType, Serializer, Mapper } from "../lib/serializer";
import { DictionaryMapper, MapperType, Serializer, Mapper, CompositeMapper } from "../lib/serializer";
import { serializeRequestBody, ServiceClient, getOperationArgumentValueFromParameterPath } from "../lib/serviceClient";
import { WebResource } from "../lib/webResource";
import { OperationArguments, HttpHeaders, deserializationPolicy, RestResponse, isNode } from "../lib/coreHttp";
import { OperationArguments, HttpHeaders, deserializationPolicy, RestResponse, isNode, OperationSpec } from "../lib/coreHttp";
import { ParameterPath } from "../lib/operationParameter";

describe("ServiceClient", function () {
Expand Down Expand Up @@ -231,6 +231,78 @@ describe("ServiceClient", function () {
assert.deepStrictEqual(res.slice(), [1, 2, 3]);
});

it("should deserialize error response headers", async function(){
const BodyMapper: CompositeMapper = {
serializedName: "getproperties-body",
type: {
name: "Composite",
className: "PropertiesBody",
modelProperties: {
message: {
type: {
name: "String"
}
}
}
}
};

const HeadersMapper: CompositeMapper = {
serializedName: "getproperties-headers",
type: {
name: "Composite",
className: "PropertiesHeaders",
modelProperties: {
errorCode: {
serializedName: "x-ms-error-code",
type: {
name: "String"
}
}
}
}
};

const serializer = new Serializer(HeadersMapper, true);

const operationSpec: OperationSpec = {
httpMethod: "GET",
responses: {
default: {
headersMapper: HeadersMapper,
bodyMapper: BodyMapper
}
},
baseUrl: "httpbin.org",
serializer
};

let request = new WebResource();
request.operationSpec = operationSpec;

const httpClient: HttpClient = {
sendRequest: req => {
request = req;
return Promise.resolve({ request, status: 500, headers: new HttpHeaders({
"x-ms-error-code": "InvalidResourceNameHeader"
}), bodyAsText: '{"message": "InvalidResourceNameBody"}' });
}
};

const client = new ServiceClient(undefined, {
httpClient,
requestPolicyFactories: [deserializationPolicy()]
});

try {
await client.sendOperationRequest({}, operationSpec);
assert.strictEqual(true, false);
} catch (ex) {
assert.strictEqual(ex.errorCode, "InvalidResourceNameHeader");
assert.strictEqual(ex.message, "InvalidResourceNameBody");
}
});

it("should use userAgent header name value from options", async function () {
const httpClient: HttpClient = {
sendRequest: (request: WebResource) => {
Expand Down