You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The new version 1.x is no longer dealing with different success responses.
I had one response type corresponding to 202 and one to 200, however after migrating to openapi 1.x it fails when the type of the 202 is returned.
It searches for a success response, and finds the wrong one for 200, and then fails with a schema validation failed response, which makes sense as it compared the wrong schemas.
How is one supposed to return the different return types if not directly via return? is there a way to give the status 202 together with the return type?
Similarly, I also have the case where I would like to return a ClientError response in the 400 realm. When I look at the code in _server_response, also this probably will fail, looking for a 200 schema without actually checking the type, which clearly corresponds to 400 type.
EDIT: A suggestion
instead of the _success_response function, I expected rather something like the following:
function _response_by_type(responses, result)
# first try to find type
for response in responses
for media in response.media
if media.type == typeof(result)
return response
end
end
end
# fall back to default selector
for response in responses
selector = uppercase(response.selector)
if selector == "DEFAULT"
return response
end
end
# fall back to nothing
return nothing
end
Here my current full workaround when including the OpenAPI generated Server. Works
_replace_success_response(ex) = ex
function_replace_success_response(ex::Expr)
# _success_response(operation.responses) -> _response_by_type(operation.responses, result)if ex.head ==:call&& ex.args[1] ==:_success_responsereturnExpr(:call, :_response_by_type, ex.args[2], :result)
end# Recursively process childrenreturnExpr(ex.head, map(_replace_success_response, ex.args)...)
endinclude(_replace_success_response, "../../openapi/APIServer.jl")
@eval APIServer beginfunction_response_by_type(responses, result)
# first try to find typefor response in responses
for media in response.media
if media.type ==typeof(result)
return response
endendend# fall back to default selectorfor response in responses
selector =uppercase(response.selector)
if selector =="DEFAULT"return response
endend# fall back to nothingreturnnothingendend
a handler that returns an Accepted is checked against the Done schema, and validation fails. That is what you are seeing.
This is not new in 1.x. The 0.2 server did the same thing: any plain return value went out as a 200. What changed is that 1.x now validates the response body before sending it, so the mismatch shows up as an error instead of a 200 with the wrong body. We hit the same thing when porting our own services, and the answer so far has been the escape hatch that already exists: return an HTTP.Response yourself.
functionsubmit(request, body)
ifneeds_processing(body)
return HTTP.Response(202, ["Content-Type"=>"application/json"], JSON.json(Accepted(...)))
endreturnDone(...) # encoded and validated as the 200end
That works, but you lose validation for that branch and have to encode the body by hand. So we agree this deserves a proper fix.
Why picking by type is not enough
The _response_by_type idea works for your document, but it falls over in common cases:
Two statuses often document the same type. 200 and 201 both returning a Widget is normal.
Many responses are plain strings, byte arrays or free-form objects, so there is no type to tell them apart.
A handler returning a Dict or a Vector matches nothing, and the fallback to default would then send an error-shaped body as the status of the default response.
So the status has to come from the handler, not be guessed from the value.
Suggested shape
Let the handler say the status explicitly, and keep everything else the same:
functionsubmit(request, body)
ifneeds_processing(body)
return OpenAPI.Reply(202, Accepted(...)) # name open for discussionendreturnDone(...) # plain value still means "first 2xx"endfunctionget_item(request, id)
item =lookup(id)
item ===nothing&&return OpenAPI.Reply(404, ClientError(message ="no such item"))
return item
end
_server_response then looks up the response for that status (exact code first, then 4XX-style ranges, then default, which the runtime already does for clients in _select_response), validates the value against that response's schema, and sends it with that status. A plain value keeps today's behaviour, so existing servers do not change. Returning an HTTP.Response stays as the escape hatch for anything else.
This covers both of your cases, the 202 and the 400, with one mechanism, and the body is still validated.
What the PR would need
The wrapper type in the runtime and the status lookup in the generated _server_response.
A clear error when the status is not documented for that operation.
A round-trip test in test/servergen.jl with a 200/202 pair and a 4xx typed error.
A short section in docs/src/servers.md with an example like the one above.
One more thing worth fixing on the way: the generated handler signature lists the union of all documented body types as the return type. That reads as if the server dispatches on the type, which it does not, and it is probably part of why the type-based fix looked natural. Happy to discuss the wrapper's name and whether a (status, value) tuple would be nicer than a struct.
Activity
schlichtanders commentedon Sep 17, 2026
Here my current full workaround when including the OpenAPI generated Server. Works
Is a pullrequest welcome?
tanmaykm commentedon Sep 19, 2026
Thanks for the clear report and the workaround. Yes, a PR is welcome. Some notes first so it lands in a shape we can keep.
What happens today
The generated server always picks the first documented
2xxresponse and encodes the returned value against it. So witha handler that returns an
Acceptedis checked against theDoneschema, and validation fails. That is what you are seeing.This is not new in 1.x. The 0.2 server did the same thing: any plain return value went out as a
200. What changed is that 1.x now validates the response body before sending it, so the mismatch shows up as an error instead of a200with the wrong body. We hit the same thing when porting our own services, and the answer so far has been the escape hatch that already exists: return anHTTP.Responseyourself.That works, but you lose validation for that branch and have to encode the body by hand. So we agree this deserves a proper fix.
Why picking by type is not enough
The
_response_by_typeidea works for your document, but it falls over in common cases:200and201both returning aWidgetis normal.Dictor aVectormatches nothing, and the fallback todefaultwould then send an error-shaped body as the status of thedefaultresponse.So the status has to come from the handler, not be guessed from the value.
Suggested shape
Let the handler say the status explicitly, and keep everything else the same:
_server_responsethen looks up the response for that status (exact code first, then4XX-style ranges, thendefault, which the runtime already does for clients in_select_response), validates the value against that response's schema, and sends it with that status. A plain value keeps today's behaviour, so existing servers do not change. Returning anHTTP.Responsestays as the escape hatch for anything else.This covers both of your cases, the
202and the400, with one mechanism, and the body is still validated.What the PR would need
_server_response.test/servergen.jlwith a200/202pair and a4xxtyped error.docs/src/servers.mdwith an example like the one above.One more thing worth fixing on the way: the generated handler signature lists the union of all documented body types as the return type. That reads as if the server dispatches on the type, which it does not, and it is probably part of why the type-based fix looked natural. Happy to discuss the wrapper's name and whether a
(status, value)tuple would be nicer than a struct.increment minor version to 1.2.0 (#130)