Skip to content

Commit 156f0b1

Browse files
authored
handle multiple (per resp code) return types (#3)
* handle multiple (per resp code) return types Implements a way to handle per HTTP response code return types. * also return http response from client api call
1 parent 2b10895 commit 156f0b1

24 files changed

+618
-345
lines changed

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,20 +63,25 @@ Validations are imposed in the constructor and `setproperty!` methods of models.
6363

6464
Each client API set is generated into a file named `api_<apiname>.jl`. It is represented as a `struct` and the APIs under it are generated as methods. An API set can be constructed by providing the OpenAPI client instance that it can use for communication.
6565

66-
The required API parameters are generated as regular function arguments. Optional parameters are generated as keyword arguments. Method documentation is generated with description, parameter information and return value. Two variants of the API are generated. The first variant is suitable for calling synchronously and returns a single instance of the result struct.
66+
The required API parameters are generated as regular function arguments. Optional parameters are generated as keyword arguments. Method documentation is generated with description, parameter information and return value. Two variants of the API are generated. The first variant is suitable for calling synchronously. It returns a tuple of the result struct and the HTTP response.
6767

6868
```julia
6969
# example synchronous API that returns an Order instance
70-
getOrderById(api::StoreApi, orderId::Int64)
70+
getOrderById(api::StoreApi, orderId::Int64) -> (result, http_response)
7171
```
7272

7373
The second variant is suitable for asynchronous calls to methods that return chunked transfer encoded responses, where in the API streams the response objects into an output channel.
7474

7575
```julia
7676
# example asynchronous API that streams matching Pet instances into response_stream
77-
findPetsByStatus(api::PetApi, response_stream::Channel, status::Vector{String})
77+
findPetsByStatus(api::PetApi, response_stream::Channel, status::Vector{String}) -> (response_stream, http_response)
7878
```
7979

80+
The HTTP response returned from the API calls, have these properties:
81+
- `status`: integer status code
82+
- `message`: http message corresponding to status code
83+
- `headers`: http response headers as `Vector{Pair{String,String}}`
84+
8085
A client context holds common information to be used across APIs. It also holds a connection to the server and uses that across API calls.
8186
The client context needs to be passed as the first parameter of all API calls. It can be created as:
8287

src/client.jl

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ using TimeZones
99
using LibCURL
1010

1111
import Base: convert, show, summary, getproperty, setproperty!, iterate
12-
import ..OpenAPI: APIModel, APIClientImpl, OpenAPIException, to_json, from_json, validate_property, property_type
12+
import ..OpenAPI: APIModel, APIClientImpl, OpenAPIException, InvocationException, to_json, from_json, validate_property, property_type
1313
import ..OpenAPI: str2zoneddatetime, str2datetime, str2date
1414

1515
# collection formats (OpenAPI v2)
@@ -42,6 +42,50 @@ struct ApiException <: Exception
4242
end
4343
end
4444

45+
"""
46+
Represents the raw HTTP provol response from the server.
47+
Properties available:
48+
- status: the HTTP status code
49+
- message: the HTTP status message
50+
- headers: the HTTP headers
51+
- raw: the raw response ( as a Downloads.Response object)
52+
"""
53+
struct ApiResponse
54+
raw::Downloads.Response
55+
end
56+
57+
function Base.getproperty(resp::ApiResponse, name::Symbol)
58+
raw = getfield(resp, :raw)
59+
if name === :status
60+
return raw.status
61+
elseif name === :message
62+
return raw.message
63+
elseif name === :headers
64+
return raw.headers
65+
else
66+
return getfield(resp, name)
67+
end
68+
end
69+
70+
function get_api_return_type(return_types::Dict{Regex,Type}, ::Nothing, response_data::String)
71+
# this is the async case, where we do not have the response code yet
72+
# in such cases we look for the 200 response code
73+
return get_api_return_type(return_types, 200, response_data)
74+
end
75+
function get_api_return_type(return_types::Dict{Regex,Type}, response_code::Integer, response_data::String)
76+
default_response_code = 0
77+
for code in string.([response_code, default_response_code])
78+
for (re, rt) in return_types
79+
if match(re, code) !== nothing
80+
return rt
81+
end
82+
end
83+
end
84+
# if no specific return type was defined, we assume that:
85+
# - if response code is 2xx, then we make the method call return nothing
86+
# - otherwise we make it throw an ApiException
87+
return (200 <= response_code <=206) ? Nothing : nothing # first(return_types)[2]
88+
end
4589
struct Client
4690
root::String
4791
headers::Dict{String,String}
@@ -54,7 +98,7 @@ struct Client
5498

5599
function Client(root::String;
56100
headers::Dict{String,String}=Dict{String,String}(),
57-
get_return_type::Function=(default,data)->default,
101+
get_return_type::Function=get_api_return_type,
58102
long_polling_timeout::Int=DEFAULT_LONGPOLL_TIMEOUT_SECS,
59103
timeout::Int=DEFAULT_TIMEOUT_SECS,
60104
pre_request_hook::Function=noop_pre_request_hook)
@@ -96,7 +140,7 @@ end
96140
struct Ctx
97141
client::Client
98142
method::String
99-
return_type::Type
143+
return_types::Dict{Regex,Type}
100144
resource::String
101145
auth::Vector{String}
102146

@@ -110,12 +154,13 @@ struct Ctx
110154
curl_mime_upload::Ref{Any}
111155
pre_request_hook::Function
112156

113-
function Ctx(client::Client, method::String, return_type, resource::String, auth, body=nothing;
157+
function Ctx(client::Client, method::String, return_types::Dict{Regex,Type}, resource::String, auth, body=nothing;
114158
timeout::Int=client.timeout[],
115-
pre_request_hook::Function=client.pre_request_hook)
159+
pre_request_hook::Function=client.pre_request_hook,
160+
)
116161
resource = client.root * resource
117162
headers = copy(client.headers)
118-
new(client, method, return_type, resource, auth, Dict{String,String}(), Dict{String,String}(), headers, Dict{String,String}(), Dict{String,String}(), body, timeout, Ref{Any}(nothing), pre_request_hook)
163+
new(client, method, return_types, resource, auth, Dict{String,String}(), Dict{String,String}(), headers, Dict{String,String}(), Dict{String,String}(), body, timeout, Ref{Any}(nothing), pre_request_hook)
119164
end
120165
end
121166

@@ -325,7 +370,7 @@ function do_request(ctx::Ctx, stream::Bool=false; stream_to::Union{Channel,Nothi
325370
@async begin
326371
try
327372
for chunk in ChunkReader(output)
328-
return_type = ctx.client.get_return_type(ctx.return_type, String(copy(chunk)))
373+
return_type = ctx.client.get_return_type(ctx.return_types, nothing, String(copy(chunk)))
329374
data = response(return_type, resp, chunk)
330375
put!(stream_to, data)
331376
end
@@ -377,19 +422,22 @@ function exec(ctx::Ctx, stream_to::Union{Channel,Nothing}=nothing)
377422

378423
if resp === nothing
379424
# request was interrupted
380-
return nothing
425+
throw(InvocationException("request was interrupted"))
381426
end
382427

383-
if isa(resp, Downloads.RequestError) || !(200 <= resp.status <= 206)
428+
if isa(resp, Downloads.RequestError)
384429
throw(ApiException(resp))
385430
end
386431

387432
if stream
388-
return resp
433+
return stream_to, ApiResponse(resp)
389434
else
390435
data = read(output)
391-
return_type = ctx.client.get_return_type(ctx.return_type, String(copy(data)))
392-
return response(return_type, resp, data)
436+
return_type = ctx.client.get_return_type(ctx.return_types, resp.status, String(copy(data)))
437+
if isnothing(return_type)
438+
return nothing, ApiResponse(resp)
439+
end
440+
return response(return_type, resp, data), ApiResponse(resp)
393441
end
394442
end
395443

src/commontypes.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@ end
66
struct ValidationException <: Exception
77
reason::String
88
end
9+
struct InvocationException <: Exception
10+
reason::String
11+
end
912

1013
property_type(::Type{T}, name::Symbol) where {T<:APIModel} = error("invalid type $T")
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
6.1.0-SNAPSHOT
1+
6.2.1-SNAPSHOT

test/client/petstore_v2/petstore/docs/PetApi.md

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Method | HTTP request | Description
1515

1616

1717
# **add_pet**
18-
> add_pet(_api::PetApi, body::Pet; _mediaType=nothing) <br/>
19-
> add_pet(_api::PetApi, response_stream::Channel, body::Pet; _mediaType=nothing)
18+
> add_pet(_api::PetApi, body::Pet; _mediaType=nothing) -> Nothing, OpenAPI.Clients.ApiResponse <br/>
19+
> add_pet(_api::PetApi, response_stream::Channel, body::Pet; _mediaType=nothing) -> Channel{ Nothing }, OpenAPI.Clients.ApiResponse
2020
2121
Add a new pet to the store
2222

@@ -29,7 +29,7 @@ Name | Type | Description | Notes
2929

3030
### Return type
3131

32-
nothing
32+
Nothing
3333

3434
### Authorization
3535

@@ -43,8 +43,8 @@ Name | Type | Description | Notes
4343
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
4444

4545
# **delete_pet**
46-
> delete_pet(_api::PetApi, pet_id::Int64; api_key=nothing, _mediaType=nothing) <br/>
47-
> delete_pet(_api::PetApi, response_stream::Channel, pet_id::Int64; api_key=nothing, _mediaType=nothing)
46+
> delete_pet(_api::PetApi, pet_id::Int64; api_key=nothing, _mediaType=nothing) -> Nothing, OpenAPI.Clients.ApiResponse <br/>
47+
> delete_pet(_api::PetApi, response_stream::Channel, pet_id::Int64; api_key=nothing, _mediaType=nothing) -> Channel{ Nothing }, OpenAPI.Clients.ApiResponse
4848
4949
Deletes a pet
5050

@@ -63,7 +63,7 @@ Name | Type | Description | Notes
6363

6464
### Return type
6565

66-
nothing
66+
Nothing
6767

6868
### Authorization
6969

@@ -77,8 +77,8 @@ Name | Type | Description | Notes
7777
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
7878

7979
# **find_pets_by_status**
80-
> find_pets_by_status(_api::PetApi, status::Vector{String}; _mediaType=nothing) -> Vector{Pet} <br/>
81-
> find_pets_by_status(_api::PetApi, response_stream::Channel, status::Vector{String}; _mediaType=nothing) -> Vector{Pet}
80+
> find_pets_by_status(_api::PetApi, status::Vector{String}; _mediaType=nothing) -> Vector{Pet}, OpenAPI.Clients.ApiResponse <br/>
81+
> find_pets_by_status(_api::PetApi, response_stream::Channel, status::Vector{String}; _mediaType=nothing) -> Channel{ Vector{Pet} }, OpenAPI.Clients.ApiResponse
8282
8383
Finds Pets by status
8484

@@ -107,8 +107,8 @@ Name | Type | Description | Notes
107107
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
108108

109109
# **find_pets_by_tags**
110-
> find_pets_by_tags(_api::PetApi, tags::Vector{String}; _mediaType=nothing) -> Vector{Pet} <br/>
111-
> find_pets_by_tags(_api::PetApi, response_stream::Channel, tags::Vector{String}; _mediaType=nothing) -> Vector{Pet}
110+
> find_pets_by_tags(_api::PetApi, tags::Vector{String}; _mediaType=nothing) -> Vector{Pet}, OpenAPI.Clients.ApiResponse <br/>
111+
> find_pets_by_tags(_api::PetApi, response_stream::Channel, tags::Vector{String}; _mediaType=nothing) -> Channel{ Vector{Pet} }, OpenAPI.Clients.ApiResponse
112112
113113
Finds Pets by tags
114114

@@ -137,8 +137,8 @@ Name | Type | Description | Notes
137137
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
138138

139139
# **get_pet_by_id**
140-
> get_pet_by_id(_api::PetApi, pet_id::Int64; _mediaType=nothing) -> Pet <br/>
141-
> get_pet_by_id(_api::PetApi, response_stream::Channel, pet_id::Int64; _mediaType=nothing) -> Pet
140+
> get_pet_by_id(_api::PetApi, pet_id::Int64; _mediaType=nothing) -> Pet, OpenAPI.Clients.ApiResponse <br/>
141+
> get_pet_by_id(_api::PetApi, response_stream::Channel, pet_id::Int64; _mediaType=nothing) -> Channel{ Pet }, OpenAPI.Clients.ApiResponse
142142
143143
Find pet by ID
144144

@@ -167,8 +167,8 @@ Name | Type | Description | Notes
167167
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
168168

169169
# **update_pet**
170-
> update_pet(_api::PetApi, body::Pet; _mediaType=nothing) <br/>
171-
> update_pet(_api::PetApi, response_stream::Channel, body::Pet; _mediaType=nothing)
170+
> update_pet(_api::PetApi, body::Pet; _mediaType=nothing) -> Nothing, OpenAPI.Clients.ApiResponse <br/>
171+
> update_pet(_api::PetApi, response_stream::Channel, body::Pet; _mediaType=nothing) -> Channel{ Nothing }, OpenAPI.Clients.ApiResponse
172172
173173
Update an existing pet
174174

@@ -181,7 +181,7 @@ Name | Type | Description | Notes
181181

182182
### Return type
183183

184-
nothing
184+
Nothing
185185

186186
### Authorization
187187

@@ -195,8 +195,8 @@ Name | Type | Description | Notes
195195
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
196196

197197
# **update_pet_with_form**
198-
> update_pet_with_form(_api::PetApi, pet_id::Int64; name=nothing, status=nothing, _mediaType=nothing) <br/>
199-
> update_pet_with_form(_api::PetApi, response_stream::Channel, pet_id::Int64; name=nothing, status=nothing, _mediaType=nothing)
198+
> update_pet_with_form(_api::PetApi, pet_id::Int64; name=nothing, status=nothing, _mediaType=nothing) -> Nothing, OpenAPI.Clients.ApiResponse <br/>
199+
> update_pet_with_form(_api::PetApi, response_stream::Channel, pet_id::Int64; name=nothing, status=nothing, _mediaType=nothing) -> Channel{ Nothing }, OpenAPI.Clients.ApiResponse
200200
201201
Updates a pet in the store with form data
202202

@@ -216,7 +216,7 @@ Name | Type | Description | Notes
216216

217217
### Return type
218218

219-
nothing
219+
Nothing
220220

221221
### Authorization
222222

@@ -230,8 +230,8 @@ Name | Type | Description | Notes
230230
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
231231

232232
# **upload_file**
233-
> upload_file(_api::PetApi, pet_id::Int64; additional_metadata=nothing, file=nothing, _mediaType=nothing) -> ApiResponse <br/>
234-
> upload_file(_api::PetApi, response_stream::Channel, pet_id::Int64; additional_metadata=nothing, file=nothing, _mediaType=nothing) -> ApiResponse
233+
> upload_file(_api::PetApi, pet_id::Int64; additional_metadata=nothing, file=nothing, _mediaType=nothing) -> ApiResponse, OpenAPI.Clients.ApiResponse <br/>
234+
> upload_file(_api::PetApi, response_stream::Channel, pet_id::Int64; additional_metadata=nothing, file=nothing, _mediaType=nothing) -> Channel{ ApiResponse }, OpenAPI.Clients.ApiResponse
235235
236236
uploads an image
237237

test/client/petstore_v2/petstore/docs/StoreApi.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ Method | HTTP request | Description
1111

1212

1313
# **delete_order**
14-
> delete_order(_api::StoreApi, order_id::Int64; _mediaType=nothing) <br/>
15-
> delete_order(_api::StoreApi, response_stream::Channel, order_id::Int64; _mediaType=nothing)
14+
> delete_order(_api::StoreApi, order_id::Int64; _mediaType=nothing) -> Nothing, OpenAPI.Clients.ApiResponse <br/>
15+
> delete_order(_api::StoreApi, response_stream::Channel, order_id::Int64; _mediaType=nothing) -> Channel{ Nothing }, OpenAPI.Clients.ApiResponse
1616
1717
Delete purchase order by ID
1818

@@ -27,7 +27,7 @@ Name | Type | Description | Notes
2727

2828
### Return type
2929

30-
nothing
30+
Nothing
3131

3232
### Authorization
3333

@@ -41,8 +41,8 @@ No authorization required
4141
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
4242

4343
# **get_inventory**
44-
> get_inventory(_api::StoreApi; _mediaType=nothing) -> Dict{String, Int64} <br/>
45-
> get_inventory(_api::StoreApi, response_stream::Channel; _mediaType=nothing) -> Dict{String, Int64}
44+
> get_inventory(_api::StoreApi; _mediaType=nothing) -> Dict{String, Int64}, OpenAPI.Clients.ApiResponse <br/>
45+
> get_inventory(_api::StoreApi, response_stream::Channel; _mediaType=nothing) -> Channel{ Dict{String, Int64} }, OpenAPI.Clients.ApiResponse
4646
4747
Returns pet inventories by status
4848

@@ -67,8 +67,8 @@ This endpoint does not need any parameter.
6767
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
6868

6969
# **get_order_by_id**
70-
> get_order_by_id(_api::StoreApi, order_id::Int64; _mediaType=nothing) -> Order <br/>
71-
> get_order_by_id(_api::StoreApi, response_stream::Channel, order_id::Int64; _mediaType=nothing) -> Order
70+
> get_order_by_id(_api::StoreApi, order_id::Int64; _mediaType=nothing) -> Order, OpenAPI.Clients.ApiResponse <br/>
71+
> get_order_by_id(_api::StoreApi, response_stream::Channel, order_id::Int64; _mediaType=nothing) -> Channel{ Order }, OpenAPI.Clients.ApiResponse
7272
7373
Find purchase order by ID
7474

@@ -97,8 +97,8 @@ No authorization required
9797
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
9898

9999
# **place_order**
100-
> place_order(_api::StoreApi, body::Order; _mediaType=nothing) -> Order <br/>
101-
> place_order(_api::StoreApi, response_stream::Channel, body::Order; _mediaType=nothing) -> Order
100+
> place_order(_api::StoreApi, body::Order; _mediaType=nothing) -> Order, OpenAPI.Clients.ApiResponse <br/>
101+
> place_order(_api::StoreApi, response_stream::Channel, body::Order; _mediaType=nothing) -> Channel{ Order }, OpenAPI.Clients.ApiResponse
102102
103103
Place an order for a pet
104104

0 commit comments

Comments
 (0)