Skip to content

Commit c4fda71

Browse files
committed
refacto(gdscript, breaking): use an ApiResponse object in success callbacks
/spent 6d since the beginning
1 parent a0b1e4d commit c4fda71

File tree

16 files changed

+126
-73
lines changed

16 files changed

+126
-73
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GdscriptClientCodegen.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ public void processOpts() {
253253
supportingFiles.add(new SupportingFile("ApiBee.handlebars", "core", toCoreFilename("ApiBee") + ".gd"));
254254
supportingFiles.add(new SupportingFile("ApiError.handlebars", "core", toCoreFilename("ApiError") + ".gd"));
255255
supportingFiles.add(new SupportingFile("ApiConfig.handlebars", "core", toCoreFilename("ApiConfig") + ".gd"));
256+
supportingFiles.add(new SupportingFile("core/ApiResponse.handlebars", "core", toCoreFilename("ApiResponse") + ".gd"));
256257
supportingFiles.add(new SupportingFile("README.handlebars", "", "README.md"));
257258

258259
// Ensure we're using the appropriate template engine, and configure it while we're at it.

modules/openapi-generator/src/main/resources/gdscript/ApiBee.handlebars

Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,37 @@
1-
{{! TODO: would be nice to be able to customize parent class from CLI }}
2-
extends RefCounted
1+
extends {{>partials/api_bee_parent_class}}
32
class_name {{>partials/api_base_class_name}}
43

5-
# Base class for all generated API endpoints.
4+
{{>partials/disclaimer_autogenerated}}
5+
6+
# Base class for all generated API endpoints
7+
# ==========================================
68
#
79
# Every property/method defined here may collide with userland,
810
# so these are all listed and excluded in our CodeGen Java file.
911
# We want to keep the amount of renaming to a minimum, though.
1012
# Therefore, we use the _bzz_ prefix, even if awkward.
1113

1214

13-
const BEE_CONTENT_TYPE_TEXT := "text/plain"
14-
const BEE_CONTENT_TYPE_HTML := "text/html"
15-
const BEE_CONTENT_TYPE_JSON := "application/json"
16-
const BEE_CONTENT_TYPE_FORM := "application/x-www-form-urlencoded"
17-
const BEE_CONTENT_TYPE_JSONLD := "application/json+ld" # unsupported (for now)
18-
const BEE_CONTENT_TYPE_XML := "application/xml" # unsupported (for now)
15+
const BZZ_CONTENT_TYPE_TEXT := "text/plain"
16+
const BZZ_CONTENT_TYPE_HTML := "text/html"
17+
const BZZ_CONTENT_TYPE_JSON := "application/json"
18+
const BZZ_CONTENT_TYPE_FORM := "application/x-www-form-urlencoded"
19+
const BZZ_CONTENT_TYPE_JSONLD := "application/json+ld" # unsupported (for now)
20+
const BZZ_CONTENT_TYPE_XML := "application/xml" # unsupported (for now)
1921

2022
# From this client's point of view.
2123
# Adding a content type here won't magically make the client support it, but you may reorder.
2224
# These are sorted by decreasing preference. (first → preferred)
23-
const BEE_PRODUCIBLE_CONTENT_TYPES := [
24-
BEE_CONTENT_TYPE_JSON,
25-
BEE_CONTENT_TYPE_FORM,
25+
const BZZ_PRODUCIBLE_CONTENT_TYPES := [
26+
BZZ_CONTENT_TYPE_JSON,
27+
BZZ_CONTENT_TYPE_FORM,
2628
]
2729

2830
# From this client's point of view.
2931
# Adding a content type here won't magically make the client support it, but you may reorder.
3032
# These are sorted by decreasing preference. (first → preferred)
31-
const BEE_CONSUMABLE_CONTENT_TYPES := [
32-
BEE_CONTENT_TYPE_JSON,
33+
const BZZ_CONSUMABLE_CONTENT_TYPES := [
34+
BZZ_CONTENT_TYPE_JSON,
3335
]
3436

3537

@@ -56,6 +58,7 @@ var _bzz_config: {{>partials/api_config_class_name}}:
5658
return _bzz_config
5759

5860

61+
# Useful in logs
5962
var _bzz_name: String:
6063
get:
6164
return _bzz_get_api_name()
@@ -130,7 +133,7 @@ func _bzz_connect_client_if_needed(
130133
if connected != HTTPClient.STATUS_CONNECTED:
131134
var error := {{>partials/api_error_class_name}}.new()
132135
error.internal_code = connected as Error
133-
error.identifier = "apibee.connect_to_host.wrong_status"
136+
error.identifier = "apibee.connect_to_host.status_failure"
134137
error.message = "%s: failed to connect to `%s' port `%d' : %s" % [
135138
_bzz_name, self._bzz_config.host, self._bzz_config.port,
136139
_bzz_httpclient_status_string(connected),
@@ -141,32 +144,32 @@ func _bzz_connect_client_if_needed(
141144
on_success.call()
142145

143146

144-
func bzz_request(
147+
func _bzz_request(
145148
method: int, # one of HTTPClient.METHOD_XXXXX
146149
path: String,
147150
headers: Dictionary,
148151
query: Dictionary,
149152
body, # Variant that will be serialized and sent
150-
on_success: Callable, # func(response: Variant, responseCode: int, responseHeaders: Dictionary)
153+
on_success: Callable, # func(response: {{>partials/api_response_class_name}})
151154
on_failure: Callable, # func(error: {{>partials/api_error_class_name}})
152155
):
153156
# This method does not handle full deserialization, it only handles decode and not denormalization.
154157
# Denormalization is handled in each generated API endpoint in the on_success callable of this method.
155-
# This is because this method already has plethora of parameters and we don't want even more.
156158

157159
_bzz_request_text(
158160
method, path, headers, query, body,
159-
func(responseText, responseCode, responseHeaders):
160-
var mime: String = responseHeaders['Mime']
161-
var decodedResponse # Variant
162-
163-
if BEE_CONTENT_TYPE_TEXT == mime:
164-
decodedResponse = responseText
165-
elif BEE_CONTENT_TYPE_HTML == mime:
166-
decodedResponse = responseText
167-
elif BEE_CONTENT_TYPE_JSON == mime:
161+
func(response):
162+
var mime: String = response.headers['Mime']
163+
var decodedBody # Variant
164+
165+
# Isn't there a match/case now in Gdscript?
166+
if BZZ_CONTENT_TYPE_TEXT == mime:
167+
decodedBody = response.body
168+
elif BZZ_CONTENT_TYPE_HTML == mime:
169+
decodedBody = response.body
170+
elif BZZ_CONTENT_TYPE_JSON == mime:
168171
var parser := JSON.new()
169-
var parsing := parser.parse(responseText)
172+
var parsing := parser.parse(response.body)
170173
if OK != parsing:
171174
var error := {{>partials/api_error_class_name}}.new()
172175
error.internal_code = parsing
@@ -176,18 +179,19 @@ func bzz_request(
176179
]
177180
on_failure.call(error)
178181
return
179-
decodedResponse = parser.data
182+
decodedBody = parser.data
180183
else:
181184
var error := {{>partials/api_error_class_name}}.new()
182185
error.internal_code = ERR_INVALID_DATA
183186
error.identifier = "apibee.decode.mime_type_unsupported"
184-
error.message = "%s: mime type `%s' is not supported (yet)" % [
187+
error.message = "%s: mime type `%s' is not supported (yet -- MRs welcome)" % [
185188
_bzz_name, mime
186189
]
187190
on_failure.call(error)
188191
return
189192

190-
on_success.call(decodedResponse, responseCode, responseHeaders)
193+
response.data = decodedBody
194+
on_success.call(response)
191195
,
192196
func(error):
193197
on_failure.call(error)
@@ -201,7 +205,7 @@ func _bzz_request_text(
201205
headers: Dictionary,
202206
query: Dictionary,
203207
body, # Variant that will be serialized
204-
on_success: Callable, # func(responseText: String, responseCode: int, responseHeaders: Dictionary)
208+
on_success: Callable, # func(response: {{>partials/api_response_class_name}})
205209
on_failure: Callable, # func(error: {{>partials/api_error_class_name}})
206210
):
207211
_bzz_connect_client_if_needed(
@@ -220,7 +224,7 @@ func _bzz_do_request_text(
220224
headers: Dictionary,
221225
query: Dictionary,
222226
body, # Variant that will be serialized
223-
on_success: Callable, # func(responseText: String, responseCode: int, responseHeaders: Dictionary)
227+
on_success: Callable, # func(response: {{>partials/api_response_class_name}})
224228
on_failure: Callable, # func(error: {{>partials/api_error_class_name}})
225229
):
226230

@@ -245,9 +249,9 @@ func _bzz_do_request_text(
245249

246250
var body_serialized := ""
247251
var content_type := self._bzz_get_content_type(headers)
248-
if content_type == BEE_CONTENT_TYPE_JSON:
252+
if content_type == BZZ_CONTENT_TYPE_JSON:
249253
body_serialized = JSON.stringify(body_normalized)
250-
elif content_type == BEE_CONTENT_TYPE_FORM:
254+
elif content_type == BZZ_CONTENT_TYPE_FORM:
251255
body_serialized = self._bzz_client.query_string_from_dict(body_normalized)
252256
else:
253257
# TODO: Handle other serialization schemes (json+ld, xml…)
@@ -297,15 +301,18 @@ func _bzz_do_request_text(
297301
on_failure.call(error)
298302
return
299303

300-
var response_code := self._bzz_client.get_response_code()
301-
var response_headers := self._bzz_client.get_response_headers_as_dictionary()
304+
var response := {{>partials/api_response_class_name}}.new()
305+
#response.collect_meta_from_client(self._bzz_client) # to refactor
306+
response.code = self._bzz_client.get_response_code()
307+
response.headers = self._bzz_client.get_response_headers_as_dictionary()
302308
# FIXME: extract from headers "Content-Type": "application/json; charset=utf-8"
303-
# This begs for a HttpResponse class ; wait for Godot?
309+
# Perhaps use a method of {{>partials/api_response_class_name}} for this?
304310
var encoding := "utf-8"
305311
var mime := "application/json"
306-
response_headers['Encoding'] = encoding
307-
response_headers['Mime'] = mime
312+
response.headers['Encoding'] = encoding
313+
response.headers['Mime'] = mime
308314

315+
#response.collect_body_from_client(self._bzz_client, self._bzz_config) # to refactor
309316
# TODO: cap the size of this, perhaps?
310317
var response_bytes := PackedByteArray()
311318

@@ -320,10 +327,10 @@ func _bzz_do_request_text(
320327
response_bytes = response_bytes + chunk
321328

322329
self._bzz_config.log_info("%s: RESPONSE %d (%d bytes)" % [
323-
_bzz_name, response_code, response_bytes.size()
330+
_bzz_name, response.code, response_bytes.size()
324331
])
325-
if not response_headers.is_empty():
326-
self._bzz_config.log_debug("→ HEADERS: %s" % str(response_headers))
332+
if not response.headers.is_empty():
333+
self._bzz_config.log_debug("→ HEADERS: %s" % str(response.headers))
327334

328335
var response_text: String
329336
if encoding == "utf-8":
@@ -337,46 +344,47 @@ func _bzz_do_request_text(
337344

338345
if response_text:
339346
self._bzz_config.log_debug("→ BODY: \n%s" % response_text)
347+
response.body = response_text
340348

341-
if response_code >= 500:
349+
if response.code >= 500:
342350
var error := {{>partials/api_error_class_name}}.new()
343351
error.internal_code = ERR_PRINTER_ON_FIRE
344-
error.response_code = response_code
352+
error.response_code = response.code
345353
error.identifier = "apibee.response.5xx"
346354
error.message = "%s: request to `%s' made the server hiccup with a %d." % [
347-
_bzz_name, path, response_code
355+
_bzz_name, path, response.code
348356
]
349357
error.message += "\n%s" % [
350358
_bzz_format_error_response(response_text)
351359
]
352360
on_failure.call(error)
353361
return
354-
elif response_code >= 400:
362+
elif response.code >= 400:
355363
var error := {{>partials/api_error_class_name}}.new()
356364
error.identifier = "apibee.response.4xx"
357-
error.response_code = response_code
365+
error.response_code = response.code
358366
error.message = "%s: request to `%s' was denied with a %d." % [
359-
_bzz_name, path, response_code
367+
_bzz_name, path, response.code
360368
]
361369
error.message += "\n%s" % [
362370
_bzz_format_error_response(response_text)
363371
]
364372
on_failure.call(error)
365373
return
366-
elif response_code >= 300:
374+
elif response.code >= 300:
367375
var error := {{>partials/api_error_class_name}}.new()
368376
error.identifier = "apibee.response.3xx"
369-
error.response_code = response_code
377+
error.response_code = response.code
370378
error.message = "%s: request to `%s' was redirected with a %d. We do not support redirects in that client yet." % [
371-
_bzz_name, path, response_code
379+
_bzz_name, path, response.code
372380
]
373381
on_failure.call(error)
374382
return
375383

376384
# Should we close ?
377385
#self._bzz_client.close()
378386

379-
on_success.call(response_text, response_code, response_headers)
387+
on_success.call(response)
380388

381389

382390
func _bzz_convert_http_method(method: String) -> int:
@@ -411,7 +419,7 @@ func _bzz_escape_path_param(value: String) -> String:
411419
func _bzz_get_content_type(headers: Dictionary) -> String:
412420
if headers.has("Content-Type"):
413421
return headers["Content-Type"]
414-
return BEE_PRODUCIBLE_CONTENT_TYPES[0]
422+
return BZZ_PRODUCIBLE_CONTENT_TYPES[0]
415423

416424

417425
func _bzz_format_error_response(response: String) -> String:

modules/openapi-generator/src/main/resources/gdscript/README.handlebars

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{{{appDescriptionWithNewLines}}}
55
{{/if}}
66

7-
This Godot 4 addon was automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
7+
This *Godot 4* addon was automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
88

99
{{#if servers}}
1010
− Servers:
@@ -50,6 +50,15 @@ You can now use it anywhere in your code:
5050
{{/with}}
5151

5252

53+
## Customization
54+
55+
You can [override the templates](https://openapi-generator.tech/docs/templating/).
56+
57+
> 1. Grab templates with `openapi-generator author template -g gdscript --library gdscript`
58+
> 2. Hack around
59+
> 3. Regenerate using `--template <dir>` to target your custom templates dir
60+
61+
5362
## Documentation for API Endpoints
5463

5564
All URIs are relative to *{{basePath}}*

modules/openapi-generator/src/main/resources/gdscript/api.handlebars

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ func {{operationIdSnakeCase}}(
127127
{{#if consumes}}
128128
var bzz_mimes_consumable_by_server := [{{#each consumes}}'{{{mediaType}}}'{{#unless @last}}, {{/unless}}{{/each}}]
129129
var bzz_found_producible_mime := false
130-
for bzz_mime in BEE_PRODUCIBLE_CONTENT_TYPES:
130+
for bzz_mime in BZZ_PRODUCIBLE_CONTENT_TYPES:
131131
if bzz_mime in bzz_mimes_consumable_by_server:
132132
bzz_headers["Content-Type"] = bzz_mime
133133
bzz_found_producible_mime = true
@@ -141,7 +141,7 @@ func {{operationIdSnakeCase}}(
141141
{{/if}}
142142
{{#if produces}}
143143
var bzz_mimes_produced_by_server := [{{#each produces}}'{{{mediaType}}}'{{#unless @last}}, {{/unless}}{{/each}}]
144-
for bzz_mime in BEE_CONSUMABLE_CONTENT_TYPES:
144+
for bzz_mime in BZZ_CONSUMABLE_CONTENT_TYPES:
145145
if bzz_mime in bzz_mimes_produced_by_server:
146146
bzz_headers["Accept"] = bzz_mime
147147
break
@@ -168,18 +168,18 @@ func {{operationIdSnakeCase}}(
168168
{{/each}}
169169
{{/if}}
170170

171-
self.bzz_request(
171+
self._bzz_request(
172172
bzz_method, bzz_path, bzz_headers, bzz_query, bzz_body,
173-
func(bzz_result, bzz_code, bzz_headers):
173+
func(bzz_response):
174174
{{#with returnProperty}}
175175
{{#if isArray}}
176-
bzz_result = {{>partials/complex_type}}.bzz_denormalize_multiple(bzz_result)
176+
bzz_response.data = {{>partials/complex_type}}.bzz_denormalize_multiple(bzz_response.data)
177177
{{else if isModel}}
178-
bzz_result = {{>partials/data_type}}.bzz_denormalize_single(bzz_result)
178+
bzz_response.data = {{>partials/data_type}}.bzz_denormalize_single(bzz_response.data)
179179
{{/if}}
180180
{{/with}}
181-
on_success.call(bzz_result)
182-
, # ざわ‥
181+
on_success.call(bzz_response)
182+
,
183183
func(bzz_error):
184184
on_failure.call(bzz_error)
185185
, # ざわ‥
@@ -200,6 +200,7 @@ func {{operationIdSnakeCase}}_threaded(
200200
)
201201
bzz_thread.start(bzz_callable)
202202
return bzz_thread
203+
203204
{{/each}}
204205

205206
{{/with}}

modules/openapi-generator/src/main/resources/gdscript/api_doc_example.handlebars

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ api.{{{operationIdSnakeCase}}}(
2626
{{#each allParams}}
2727
# {{paramName}}{{#if dataType}}: {{dataType}}{{/if}}{{#if defaultValue}} = {{{defaultValue}}}{{/if}}{{#if example}} Eg: {{{example}}}{{/if}}
2828
# {{{description}}}
29-
{{!-- `example` is not available here, not sure why --}}
29+
{{! `example` is not available here, not sure why }}
3030
{{paramName}},
3131
{{/each}}
3232
# On Success
33-
func(result):{{#with returnProperty}} # result is {{>partials/complex_type}}{{/with}}
34-
prints("Success!", "{{operationIdSnakeCase}}", result)
33+
func(response):{{#with returnProperty}} # response is {{>partials/api_response_class_name}}{{/with}}
34+
prints("Success!", "{{operationIdSnakeCase}}", response)
35+
{{#with returnProperty}}assert(response.data is {{>partials/complex_type}}){{/with}}
3536
pass # do things, make stuff
3637
,
3738
# On Error
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
extends {{>partials/api_response_parent_class}}
2+
class_name {{>partials/api_response_class_name}}
3+
4+
# Headers sent back by the server
5+
@export var headers := Dictionary()
6+
7+
# Raw body of this response, in String form (before deserialization)
8+
@export var body := ""
9+
10+
# The HTTP response code, if any. A constant like HTTPClient.RESPONSE_XXXX
11+
@export var code := 0
12+
13+
# Deserialized body (may be pretty much any type)
14+
var data
15+
16+
17+
func _to_string() -> String:
18+
var s := "ApiResponse"
19+
if code:
20+
s += " %d" % code
21+
if body:
22+
s += " %s" % body
23+
return s

0 commit comments

Comments
 (0)