Skip to content

Commit

Permalink
Merge pull request OpenAPITools#2 from Goutte/feat-gdscript-tls
Browse files Browse the repository at this point in the history
Support for TLS
  • Loading branch information
Goutte authored Apr 13, 2023
2 parents 13d3c5c + ff3f218 commit a3b2e99
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("README.handlebars", "", "README.md"));

// Ensure we're using the appropriate template engine, and configure it while we're at it.
// We had to use handlebars because the truthy value of mustache includes `""` and `"null"`,
// We had to use handlebars because the truthy values of mustache include `""` and `"null"`,
// and things went south for default values and examples (but descriptions were OK, somehow)
TemplatingEngineAdapter templatingEngine = getTemplatingEngine();
if (templatingEngine instanceof HandlebarsEngineAdapter) {
Expand Down Expand Up @@ -315,21 +315,6 @@ public String modelDocFileFolder() {
}


// When example is "null" (with quotes), mustache's {{#example}} triggers
// Not sure why this happens on {{#example}} but not {{#description}}
// Perhaps I'm just using mustache wrong… Anyway, it's voodoo.
// Also, even with this fix, {{#example}} still triggers.
// → That just because of how mustache works. (false or [] only)
// → I'll switch to handlebars.
// → Pebble would perhaps help reduce injections, with custom escaping filters for each context:
// → Comments
// → Variable names
// → Function names
// → Quoted
// → Typed values (int, etc.)
// → Handlebars also has a discrepancy between description and example, both 'null'
// → We need this (hot?)fix in the end.
// → Or not, there's a {{#hasExamples}} we could perhaps use
@Override
public String toExampleValue(Schema schema) {
if (schema.getExample() != null) {
Expand All @@ -339,7 +324,6 @@ public String toExampleValue(Schema schema) {
return "";
}

// → Same code smell, I'm probably handling this wrong.
@Override
public String toDefaultValue(Schema schema) {
if (schema.getDefault() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class_name {{>partials/api_base_class_name}}
# Every property/method defined here may collide with userland,
# so these are all listed and excluded in our CodeGen Java file.
# We want to keep the amount of renaming to a minimum, though.
# Therefore, we use the bee_ prefix, even if awkward.
# Therefore, we use the bee_ and _bee_ prefixes, even if awkward.


const BEE_CONTENT_TYPE_TEXT := "text/plain"
Expand Down Expand Up @@ -49,7 +49,8 @@ var bee_client: HTTPClient:


# General configuration that can be shared across Api instances for convenience.
# If no configuration was provided, we'll lazily make one with defaults as best we can.
# If no configuration was provided, we'll lazily make one with defaults,
# but you probably want to make your own with your own domain and scheme.
var bee_config: {{>partials/api_config_class_name}}:
set(value):
bee_config = value
Expand All @@ -60,12 +61,12 @@ var bee_config: {{>partials/api_config_class_name}}:
return bee_config


func bee_next_loop_iteration():
# I can't find `idle_frame` in 4-beta3, but we probably want idle_frame here
func _bee_next_loop_iteration():
# I can't find `idle_frame` in 4.0, but we probably want idle_frame here
return Engine.get_main_loop().process_frame


func bee_connect_client_if_needed(
func _bee_connect_client_if_needed(
on_success: Callable, # func()
on_failure: Callable, # func(error: {{>partials/api_error_class_name}})
#finally: Callable,
Expand All @@ -84,14 +85,15 @@ func bee_connect_client_if_needed(
on_success.call()

var connecting := self.bee_client.connect_to_host(
self.bee_config.host, self.bee_config.port
self.bee_config.host, self.bee_config.port, self.bee_config.tls_options
)
if connecting != OK:
var error := {{>partials/api_error_class_name}}.new()
error.internal_code = connecting
error.identifier = "apibee.connect_to_host.failure"
error.message = "%s: failed to connect to `%s' port %d with error %d" % [
self.bee_name, self.bee_config.host, self.bee_config.port, connecting
error.message = "%s: failed to connect to `%s' port `%d' with error: %s" % [
self.bee_name, self.bee_config.host, self.bee_config.port,
_bee_httpclient_status_string(connecting),
]
on_failure.call(error)
return
Expand All @@ -106,22 +108,23 @@ func bee_connect_client_if_needed(
self.bee_config.log_debug("Connecting…")
if self.bee_config.polling_interval_ms:
OS.delay_msec(self.bee_config.polling_interval_ms)
await bee_next_loop_iteration()
await _bee_next_loop_iteration()

if self.bee_client.get_status() != HTTPClient.STATUS_CONNECTED:
var connected := self.bee_client.get_status()
if connected != HTTPClient.STATUS_CONNECTED:
var error := {{>partials/api_error_class_name}}.new()
error.internal_code = connecting
error.internal_code = connected as Error
error.identifier = "apibee.connect_to_host.wrong_status"
error.message = "%s: failed to connect to `%s' port %d, with client status %d" % [
self.bee_name, self.bee_config.host, self.bee_config.port, self.bee_client.get_status()
error.message = "%s: failed to connect to `%s' port `%d' : %s" % [
self.bee_name, self.bee_config.host, self.bee_config.port,
_bee_httpclient_status_string(connected),
]
on_failure.call(error)
return

on_success.call()


# @protected
func bee_request(
method: int, # one of HTTPClient.METHOD_XXXXX
path: String,
Expand All @@ -135,7 +138,7 @@ func bee_request(
# Denormalization is handled in each generated API endpoint in the on_success callable of this method.
# This is because this method already has plethora of parameters and we don't want even more.

bee_request_text(
_bee_request_text(
method, path, headers, query, body,
func(responseText, responseCode, responseHeaders):
var mime: String = responseHeaders['Mime']
Expand Down Expand Up @@ -176,8 +179,7 @@ func bee_request(
)


# @protected
func bee_request_text(
func _bee_request_text(
method: int, # one of HTTPClient.METHOD_XXXXX
path: String,
headers: Dictionary,
Expand All @@ -186,18 +188,17 @@ func bee_request_text(
on_success: Callable, # func(responseText: String, responseCode: int, responseHeaders: Dictionary)
on_failure: Callable, # func(error: {{>partials/api_error_class_name}})
):
bee_connect_client_if_needed(
_bee_connect_client_if_needed(
func():
bee_do_request_text(method, path, headers, query, body, on_success, on_failure)
_bee_do_request_text(method, path, headers, query, body, on_success, on_failure)
,
func(error):
on_failure.call(error)
,
)


# @protected
func bee_do_request_text(
func _bee_do_request_text(
method: int, # one of HTTPClient.METHOD_XXXXX
path: String,
headers: Dictionary,
Expand Down Expand Up @@ -227,7 +228,7 @@ func bee_do_request_text(
body_normalized = body.bee_normalize()

var body_serialized := ""
var content_type := self.bee_get_content_type(headers)
var content_type := self._bee_get_content_type(headers)
if content_type == BEE_CONTENT_TYPE_JSON:
body_serialized = JSON.stringify(body_normalized)
elif content_type == BEE_CONTENT_TYPE_FORM:
Expand All @@ -246,6 +247,12 @@ func bee_do_request_text(
for key in headers:
headers_for_godot.append("%s: %s" % [key, headers[key]])

self.bee_config.log_info("%s: REQUEST %s %s" % [self.bee_name, method, path_queried])
if not headers.is_empty():
self.bee_config.log_debug("→ HEADERS: %s" % [str(headers)])
if body_serialized:
self.bee_config.log_debug("→ BODY: \n%s" % [body_serialized])

var requesting := self.bee_client.request(method, path_queried, headers_for_godot, body_serialized)
if requesting != OK:
var error := {{>partials/api_error_class_name}}.new()
Expand All @@ -263,14 +270,13 @@ func bee_do_request_text(
self.bee_config.log_debug("Requesting…")
if self.bee_config.polling_interval_ms:
OS.delay_msec(self.bee_config.polling_interval_ms)
await bee_next_loop_iteration()
# if OS.has_feature("web") or async:
await _bee_next_loop_iteration()

if not self.bee_client.has_response():
var error := {{>partials/api_error_class_name}}.new()
error.identifier = "apibee.request.no_response"
error.message = "%s: request to `%s' yielded no response whatsoever." % [
self.bee_name, path
error.message = "%s: request to `%s' returned no response whatsoever. (status=%d)" % [
self.bee_name, path, self.bee_client.get_status(),
]
on_failure.call(error)
return
Expand All @@ -293,16 +299,10 @@ func bee_do_request_text(
if chunk.size() == 0: # Got nothing, wait for buffers to fill a bit.
if self.bee_config.polling_interval_ms:
OS.delay_usec(self.bee_config.polling_interval_ms)
await bee_next_loop_iteration()
await _bee_next_loop_iteration()
else: # Yummy data has arrived
response_bytes = response_bytes + chunk

self.bee_config.log_info("%s: REQUEST %s %s" % [self.bee_name, method, path_queried])
if not headers.is_empty():
self.bee_config.log_debug("→ HEADERS: %s" % [str(headers)])
if body_serialized:
self.bee_config.log_debug("→ BODY: \n%s" % [body_serialized])

self.bee_config.log_info("%s: RESPONSE %d (%d bytes)" % [
self.bee_name, response_code, response_bytes.size()
])
Expand Down Expand Up @@ -331,7 +331,7 @@ func bee_do_request_text(
self.bee_name, path, response_code
]
error.message += "\n%s" % [
bee_format_error_response(response_text)
_bee_format_error_response(response_text)
]
on_failure.call(error)
return
Expand All @@ -343,7 +343,7 @@ func bee_do_request_text(
self.bee_name, path, response_code
]
error.message += "\n%s" % [
bee_format_error_response(response_text)
_bee_format_error_response(response_text)
]
on_failure.call(error)
return
Expand All @@ -363,7 +363,7 @@ func bee_do_request_text(
on_success.call(response_text, response_code, response_headers)


func bee_convert_http_method(method: String) -> int:
func _bee_convert_http_method(method: String) -> int:
match method:
'GET': return HTTPClient.METHOD_GET
'POST': return HTTPClient.METHOD_POST
Expand All @@ -382,23 +382,23 @@ func bee_convert_http_method(method: String) -> int:
return HTTPClient.METHOD_GET


func bee_urlize_path_param(anything) -> String:
var serialized := bee_escape_path_param(str(anything))
func _bee_urlize_path_param(anything) -> String:
var serialized := _bee_escape_path_param(str(anything))
return serialized


func bee_escape_path_param(value: String) -> String:
func _bee_escape_path_param(value: String) -> String:
# TODO: escape for URL
return value


func bee_get_content_type(headers: Dictionary) -> String:
func _bee_get_content_type(headers: Dictionary) -> String:
if headers.has("Content-Type"):
return headers["Content-Type"]
return BEE_PRODUCIBLE_CONTENT_TYPES[0]


func bee_format_error_response(response: String) -> String:
func _bee_format_error_response(response: String) -> String:
# TODO: handle other (de)serialization schemes
var parser := JSON.new()
var parsing := parser.parse(response)
Expand All @@ -414,3 +414,58 @@ func bee_format_error_response(response: String) -> String:
else:
return response
return s


func _bee_httpclient_status_info(status: int) -> Dictionary:
# At some point Godot ought to natively implement this and we won't need this "shim" anymore.
match status:
HTTPClient.STATUS_DISCONNECTED: return {
"name": "STATUS_DISCONNECTED",
"description": "Disconnected from the server."
}
HTTPClient.STATUS_RESOLVING: return {
"name": "STATUS_RESOLVING",
"description": "Currently resolving the hostname for the given URL into an IP."
}
HTTPClient.STATUS_CANT_RESOLVE: return {
"name": "STATUS_CANT_RESOLVE",
"description": "DNS failure: Can't resolve the hostname for the given URL."
}
HTTPClient.STATUS_CONNECTING: return {
"name": "STATUS_CONNECTING",
"description": "Currently connecting to server."
}
HTTPClient.STATUS_CANT_CONNECT: return {
"name": "STATUS_CANT_CONNECT",
"description": "Can't connect to the server."
}
HTTPClient.STATUS_CONNECTED: return {
"name": "STATUS_CONNECTED",
"description": "Connection established."
}
HTTPClient.STATUS_REQUESTING: return {
"name": "STATUS_REQUESTING",
"description": "Currently sending request."
}
HTTPClient.STATUS_BODY: return {
"name": "STATUS_BODY",
"description": "HTTP body received."
}
HTTPClient.STATUS_CONNECTION_ERROR: return {
"name": "STATUS_CONNECTION_ERROR",
"description": "Error in HTTP connection."
}
HTTPClient.STATUS_TLS_HANDSHAKE_ERROR: return {
"name": "STATUS_TLS_HANDSHAKE_ERROR",
"description": "Error in TLS handshake."
}
return {
"name": "UNKNOWN (%d)" % status,
"description": "Unknown HTTPClient status."
}


func _bee_httpclient_status_string(status: int) -> String:
var info := _bee_httpclient_status_info(status)
return "%s (%s)" % [info["description"], info["name"]]

Loading

0 comments on commit a3b2e99

Please sign in to comment.