Specified non-successful status codes should throw HttpOperationException<> with specific schema
See original GitHub issueI have a controller called UserController with the following method:
[HttpPost]
[ResponseType(typeof(UserResource))]
[Route("api/v1/users")]
[ValidateRequest]
public async Task<HttpResponseMessage> AddUser(AddUserInput body) { }
ValidateRequest
is my own attribute that returns a 400 Bad Request
or a 422 Unprocessable Entity
based on if the body
parameter is null or the model state is invalid, respectively. They each provide their own resource in the response (ErrorResponse
and ValidationResponse
, respectively). I’ve configured Swashbuckle to generate the following Swagger json file:
"/api/v1/users": {
"post": {
"tags": [
"User"
],
"summary": "Adds a user.",
"operationId": "User_AddUser",
"consumes": [
"application/json",
"text/json"
],
"produces": [
"application/json",
"text/json"
],
"parameters": [
{
"name": "body",
"in": "body",
"description": "The body of the request.",
"required": true,
"schema": {
"$ref": "#/definitions/AddUserInput"
}
}
],
"responses": {
"201": {
"description": "The user was added successfully.",
"schema": {
"$ref": "#/definitions/UserResource"
}
},
"400": {
"description": "Request body is either empty or could not be properly parsed.",
"schema": {
"$ref": "#/definitions/ErrorResource"
}
},
"422": {
"description": "\r\nRequest body contains invalid fields. An error code will specify what is wrong with\r\nthe field. The possible validation error codes are:\r\n\r\n<table class='status-code-table'>\r\n<thead>\r\n <tr>\r\n <th>Code</th>\r\n <th>Description</th>\r\n </tr>\r\n</thead>\r\n<tbody>\r\n <tr>\r\n <td><code>missing_field</code></td>\r\n <td>This means a required field on a resource has not been set.</td>\r\n </tr>\r\n <tr>\r\n <td><code>invalid</code></td>\r\n <td>This means the formatting of a field is invalid.</td>\r\n </tr>\r\n <tr>\r\n <td><code>already_exists</code></td>\r\n <td>\r\n This means another resource has the same value as this field. This can happen in\r\n resources that must have some unique key (such as Label names).\r\n </td>\r\n </tr>\r\n</tbody>\r\n</table>",
"schema": {
"$ref": "#/definitions/ValidationResource"
}
},
"default": {
"schema": {
"$ref": "#/definitions/ErrorResource"
}
}
},
"deprecated": false
}
}
My expectation is that the AutoRest generator would generate the following method signature:
public async Task<HttpOperationResponse<UserResource>> AddUserWithOperationResponseAsync(AddUserInput body, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) {}
My other expectation is that if a non-successful status code is returned from the REST API, that this method would throw either a HttpOperationException<ErrorResource>
or a HttpOperationException<ValidationResource>
. Unfortunately, since I’ve specified that both 400 Bad Request
and 422 Unprocessable Entity
are possible responses, the generator thinks that they are valid (i.e. successful) operations so it’s generating this method signature:
public async Task<HttpOperationResponse<object>> AddUserWithOperationResponseAsync(AddUserInput body, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) {}
My issues with this:
- The client loses all strongly typing of
UserResource
when it really is a successful call (i.e.201 Created
). I understand that if there are multiple success status codes with different resource types (schemas), then returningobject
makes sense. - Not only does a developer using the client have to wrap a try/catch around the operation to catch
HttpOperationException<ErrorResource>
because it’s the default response, but they now also have to write their own logic when the method doesn’t throw an exception in order to figure out what the response code is and then deserialize the response’s content accordingly. It would be much easier if the developer could just catchHttpOperationException<ErrorResource>
andHttpOperationException<ValidationResource>
separately. And if the method returns without exception, they could assume they have a validUserResource
object.
Can the generator be changed (or an option added) so that all non-successful status codes returned from REST API throw a strongly-typed HttpOperationException<>
based on the schema?
Issue Analytics
- State:
- Created 8 years ago
- Comments:5 (2 by maintainers)
@matt-gibbs Thanks for the quick reply, Matt. I added those error responses to my Swagger file because I want the users of my API to know specifically what a specific error looks like (an unhandled exception error will be different than a validation error). I read that section of the specification as well and found it vague concerning errors. Quoting from the swagger specification:
I personally assumed that this meant there would only be one successful response and that all other defined responses would be errors. But I think treating any defined 2xx responses as success and 4xx and 5xx as errors would be the best option. There could be an argument whether 1xx and 3xx should be treated as successes or errors.
I think throwing for default is great and definitely something worth preserving. What I would like to suggest is the following:
HttpOperationException<ErrorResource>
).2xx
, then return the referenced schema (in my case,UserResource
).2xx
responses defined with _different_ schemas, then returnobject
.4xx
or5xx
then throwHttpOperationException<>
with the referenced schema (in my caseHttpOperationException<ErrorResource>
for 400 andHttpOperationException<ValidationResource>
for 422). You already generate separate code paths for different “successful” status codes, so something similar could be done for different “error” status codes. A common base type for errors or adding vendor extensions should not be necessary.4xx
or5xx
and a schema _is not_ provided, then throwHttpOperationException
(non-generic).On another note, awesome work on this this project! I’ve been able to automatically generate and build an API client project using the OWIN TestServer (to generate the swagger file) and AutoRest.exe (to generate the client code). I then reference that client project in my integration tests to test against my API locally. It’s been working great! My suggestions above would help me with testing the appropriate error responses.
Hello guys, sorry to comment on old issue, but I couldn’t find any reference for this to a recent issue. Has this been resolved?