Skip to content

Commit

Permalink
Session handling and better code generation
Browse files Browse the repository at this point in the history
  • Loading branch information
britzl committed Mar 5, 2024
1 parent b7b2dfa commit b1bc6c9
Show file tree
Hide file tree
Showing 13 changed files with 2,499 additions and 702 deletions.
75 changes: 75 additions & 0 deletions codegen/common.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
local async = require "nakama.util.async"

-- cancellation tokens associated with a coroutine
local cancellation_tokens = {}

-- cancel a cancellation token
function M.cancel(token)
assert(token)
token.cancelled = true
end

-- create a cancellation token
-- use this to cancel an ongoing API call or a sequence of API calls
-- @return token Pass the token to a call to nakama.sync() or to any of the API calls
function M.cancellation_token()
local token = {
cancelled = false
}
function token.cancel()
token.cancelled = true
end
return token
end

-- Private
-- Run code within a coroutine
-- @param fn The code to run
-- @param cancellation_token Optional cancellation token to cancel the running code
function M.sync(fn, cancellation_token)
assert(fn)
local co = nil
co = coroutine.create(function()
cancellation_tokens[co] = cancellation_token
fn()
cancellation_tokens[co] = nil
end)
local ok, err = coroutine.resume(co)
if not ok then
log(err)
cancellation_tokens[co] = nil
end
end

-- http request helper used to reduce code duplication in all API functions below
local function http(client, callback, url_path, query_params, method, post_data, retry_policy, cancellation_token, handler_fn)
if callback then
log(url_path, "with callback")
client.engine.http(client.config, url_path, query_params, method, post_data, retry_policy, cancellation_token, function(result)
if not cancellation_token or not cancellation_token.cancelled then
callback(handler_fn(result))
end
end)
else
log(url_path, "with coroutine")
local co = coroutine.running()
assert(co, "You must be running this from withing a coroutine")

-- get cancellation token associated with this coroutine
cancellation_token = cancellation_tokens[co]
if cancellation_token and cancellation_token.cancelled then
cancellation_tokens[co] = nil
return
end

return async(function(done)
client.engine.http(client.config, url_path, query_params, method, post_data, retry_policy, cancellation_token, function(result)
if cancellation_token and cancellation_token.cancelled then
cancellation_tokens[co] = nil
return
end
done(handler_fn(result))
end)
end)
end
end
29 changes: 29 additions & 0 deletions codegen/definitions.lua.mtl
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@

--
-- Enums
--

{{#definitionslist}}
{{#has_enum}}
--- {{name}}
Expand All @@ -9,3 +13,28 @@ M.{{name_upper}}_{{.}} = "{{.}}"

{{/has_enum}}
{{/definitionslist}}

--
-- Objects
--

{{#definitionslist}}
{{#has_properties}}
--- {{name}}
-- {{description}}
{{#properties}}
-- @param {{name_lua}} ({{type_lua}}) {{description}}
{{/properties}}
function M.create_{{name}}({{property_names}})
{{#properties}}
assert(not {{name_lua}} or type({{name_lua}}) == "{{type_lua}}", "Argument '{{name_lua}}' must be 'nil' or of type '{{type_lua}}'")
{{/properties}}
return {
{{#properties}}
["{{name}}"] = {{name_lua}},
{{/properties}}
}
end

{{/has_properties}}
{{/definitionslist}}
47 changes: 34 additions & 13 deletions codegen/generate-rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,29 @@ def parameter_type_to_lua(parameter):
return "boolean"
elif t == "object":
return "table"
elif t == "integer":
return "number"
elif t == "array":
return "table"
return "table"

# convert properties from a dictionary to a list
def fix_properties(api, o):
if "properties" in o:
propertieslist = []
properties = o["properties"]
property_names = []
for prop_name in properties.keys():
prop = properties[prop_name]
type_lua = parameter_type_to_lua(prop)
prop["name"] = prop_name
prop["type_lua"] = type_lua
prop["name_lua"] = prop["name"].replace("@", "") + "_" + type_lua
prop["description"] = prop["description"] if "description" in prop else ""
property_names.append(prop["name_lua"])
fix_description(prop)
propertieslist.append(prop)
o["property_names"] = ",".join(property_names)
o["properties"] = propertieslist

def get_schema(api, o):
Expand Down Expand Up @@ -142,15 +154,18 @@ def fix_parameters(api, o):
updated_parameters.append(parameter)
o["parameters"] = updated_parameters

names = []
# build a list of parameter names and assign parameter types
# we must do this as a separate step since we may have added new parameters
# in the first iteration over the paramters above
parameter_names = []
parameters = o["parameters"]
for parameter in parameters:
type_lua = parameter_type_to_lua(parameter)
parameter["description"] = parameter["description"] if "description" in parameter else ""
parameter["type_lua"] = type_lua
parameter["name_lua"] = parameter["name"] + "_" + type_lua
names.append(parameter["name_lua"])

o["parameter_names"] = ", ".join(names)
parameter["name_lua"] = parameter["name"].replace("@", "") + "_" + type_lua
parameter_names.append(parameter["name_lua"])
o["parameter_names"] = ", ".join(parameter_names)

# convert paths from a dictionary of paths keyed on endpoint
# to a list of paths
Expand Down Expand Up @@ -182,33 +197,39 @@ def fix_definitions(api):
definition = definitions[name]
definition["name"] = camel_to_snake(name)
definition["name_upper"] = name.upper()
definition["has_enum"] = ("enum" in definition)
definition["has_properties"] = ("properties" in definition)
fix_description(definition)
if "enum" in definition:
definition["has_enum"] = True
if "properties" in definition:
definition["has_properties"] = True
fix_properties(api, definition)
fix_properties(api, definition)
definitionslist.append(definition)
api["definitionslist"] = definitionslist


# common
common_lua = read_file("common.lua")

# satori
satori_api = read_as_json("satori.swagger.json")
fix_definitions(satori_api)
fix_paths(satori_api)

satori_paths = render_to_string(satori_api, "paths.lua.mtl")
satori_defs = render_to_string(satori_api, "definitions.lua.mtl")
satori_lua = read_file("satori.lua").replace("%%paths%%", satori_paths).replace("%%definitions%%", satori_defs)
satori_lua = read_file("satori.lua")
satori_lua = satori_lua.replace("%%common%%", common_lua)
satori_lua = satori_lua.replace("%%paths%%", satori_paths)
satori_lua = satori_lua.replace("%%definitions%%", satori_defs)
write_file(satori_lua, "../satori/satori.lua")


# nakama
nakama_api = read_as_json("apigrpc.swagger.json")
fix_definitions(nakama_api)
fix_paths(nakama_api)

nakama_paths = render_to_string(nakama_api, "paths.lua.mtl")
nakama_defs = render_to_string(nakama_api, "definitions.lua.mtl")
nakama_lua = read_file("nakama.lua").replace("%%paths%%", nakama_paths).replace("%%definitions%%", nakama_defs)
nakama_lua = read_file("nakama.lua")
nakama_lua = nakama_lua.replace("%%common%%", common_lua)
nakama_lua = nakama_lua.replace("%%paths%%", nakama_paths)
nakama_lua = nakama_lua.replace("%%definitions%%", nakama_defs)
write_file(nakama_lua, "../nakama/nakama.lua")
37 changes: 12 additions & 25 deletions codegen/nakama.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,23 @@ local socket = require "nakama.socket"

local M = {}

-- helpers for parameter type checking
local function check_array(v) return type(v) == "table" end
local function check_string(v) return type(v) == "string" end
local function check_integer(v) return type(v) == "number" end
local function check_object(v) return type(v) == "table" end
local function check_boolean(v) return type(v) == "boolean" end

--
-- The low level client for the Nakama API.
--

local _config = {}
%%common%%

%%definitions%%

%%paths%%

--- Set Nakama client bearer token.
-- @param client Nakama client.
-- @param bearer_token Authorization bearer token.
function M.set_bearer_token(client, bearer_token)
assert(client, "You must provide a Nakama client")
client.config.bearer_token = bearer_token
end

--- Create a Nakama client instance.
-- @param config A table of configuration options.
Expand Down Expand Up @@ -82,21 +86,4 @@ function M.create_socket(client)
return socket.create(client)
end

--- Set Nakama client bearer token.
-- @param client Nakama client.
-- @param bearer_token Authorization bearer token.
function M.set_bearer_token(client, bearer_token)
assert(client, "You must provide a client")
client.config.bearer_token = bearer_token
end

--
-- Nakama REST API
--

%%definitions%%

%%paths%%


return M
77 changes: 1 addition & 76 deletions codegen/paths.lua.mtl
Original file line number Diff line number Diff line change
@@ -1,83 +1,8 @@
local api_session = require "nakama.session"
local json = require "nakama.util.json"
local async = require "nakama.util.async"
local uri = require "nakama.util.uri"
local uri_encode = uri.encode

-- cancellation tokens associated with a coroutine
local cancellation_tokens = {}

-- cancel a cancellation token
function M.cancel(token)
assert(token)
token.cancelled = true
end

-- create a cancellation token
-- use this to cancel an ongoing API call or a sequence of API calls
-- @return token Pass the token to a call to nakama.sync() or to any of the API calls
function M.cancellation_token()
local token = {
cancelled = false
}
function token.cancel()
token.cancelled = true
end
return token
end

-- Private
-- Run code within a coroutine
-- @param fn The code to run
-- @param cancellation_token Optional cancellation token to cancel the running code
function M.sync(fn, cancellation_token)
assert(fn)
local co = nil
co = coroutine.create(function()
cancellation_tokens[co] = cancellation_token
fn()
cancellation_tokens[co] = nil
end)
local ok, err = coroutine.resume(co)
if not ok then
log(err)
cancellation_tokens[co] = nil
end
end

-- http request helper used to reduce code duplication in all API functions below
local function http(client, callback, url_path, query_params, method, post_data, retry_policy, cancellation_token, handler_fn)
if callback then
log(url_path, "with callback")
client.engine.http(client.config, url_path, query_params, method, post_data, retry_policy, cancellation_token, function(result)
if not cancellation_token or not cancellation_token.cancelled then
callback(handler_fn(result))
end
end)
else
log(url_path, "with coroutine")
local co = coroutine.running()
assert(co, "You must be running this from withing a coroutine")

-- get cancellation token associated with this coroutine
cancellation_token = cancellation_tokens[co]
if cancellation_token and cancellation_token.cancelled then
cancellation_tokens[co] = nil
return
end

return async(function(done)
client.engine.http(client.config, url_path, query_params, method, post_data, retry_policy, cancellation_token, function(result)
if cancellation_token and cancellation_token.cancelled then
cancellation_tokens[co] = nil
return
end
done(handler_fn(result))
end)
end)
end
end

{{#paths}}

--- {{operationId}}
Expand All @@ -97,7 +22,7 @@ function M.{{operationId}}(client{{#has_parameters}}, {{parameter_names}}{{/has_
{{#has_parameters}}
{{#parameters}}
{{#required}}
assert(not {{name_lua}} or check_{{type}}({{name_lua}}), "Argument '{{name_lua}}' must be 'nil' or of type '{{type}}'")
assert(not {{name_lua}} or type({{name_lua}}) == "{{type_lua}}", "Argument '{{name_lua}}' must be 'nil' or of type '{{type}}'")
{{/required}}
{{/parameters}}
{{/has_parameters}}
Expand Down
Loading

0 comments on commit b1bc6c9

Please sign in to comment.