Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
391a8da
make all function calls part of api client structs, also introduce wi…
nkzou Oct 19, 2023
6abebd7
cleanup unneeded clones
nkzou Oct 23, 2023
54761b9
fix testing framework to use and initialize api instances. also adds …
nkzou Oct 23, 2023
4aebe1a
tweak rustfmt
nkzou Oct 23, 2023
0b509cd
record/replay/passthrough functionality
nkzou Oct 24, 2023
c0b8f5c
replace some unwraps with descriptive errors
nkzou Oct 25, 2023
5660e12
add transformed cassettes
nkzou Oct 25, 2023
78d5b5c
good start on fixing issues with a lot of non-oneOf apis
nkzou Oct 26, 2023
a2269f8
clean up model generation with skip_serializing_none and lint suggestion
nkzou Oct 27, 2023
2d891cf
forgot about v1
nkzou Oct 31, 2023
a3a6aac
compiling again
nkzou Nov 1, 2023
732b471
Merge branch 'master' into kevinzou/more_apis
nkzou Nov 2, 2023
b01eb17
add header params and array query params, also fix tests
nkzou Nov 3, 2023
49d755a
template cleanup and comments
nkzou Nov 3, 2023
390ea3c
add template helpers for timestamps
nkzou Nov 6, 2023
8bc36cf
fix timestamp parsing
nkzou Nov 8, 2023
65cec43
better body serialization, custom serializer for floats, file handlin…
nkzou Nov 14, 2023
68e4591
fix number and bool parameters, optimize regex and templating with la…
nkzou Nov 17, 2023
c0b39bf
fix indexing and custom runner logic
nkzou Dec 4, 2023
a0e47b1
add cassettes and bdd features
nkzou Dec 5, 2023
b626042
cargo fmt
nkzou Dec 5, 2023
2941b0b
fix typo
nkzou Dec 5, 2023
539e373
bump cucumber version, remove world debug workaround
nkzou Dec 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
18,663 changes: 18,630 additions & 33 deletions .generator/schemas/v1/openapi.yaml

Large diffs are not rendered by default.

8,061 changes: 7,557 additions & 504 deletions .generator/schemas/v2/openapi.yaml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions .generator/src/generator/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ def cli(specs, output):
env.globals["get_type_for_attribute"] = openapi.get_type_for_attribute
env.globals["get_type_for_response"] = openapi.get_type_for_response
env.globals["get_type_for_parameter"] = openapi.get_type_for_parameter
env.globals["get_apis_and_versions"] = openapi.get_apis_and_versions
env.globals["get_type"] = openapi.type_to_rust
env.globals["get_default"] = openapi.get_default
env.globals["get_deprecated"] = openapi.get_deprecated
env.globals["get_container"] = openapi.get_container
env.globals["get_container_type"] = openapi.get_container_type
env.globals["get_type_at_path"] = openapi.get_type_at_path
Expand Down
54 changes: 34 additions & 20 deletions .generator/src/generator/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,21 @@ def get_name(schema, version=None):
if hasattr(schema, "__reference__"):
name = schema.__reference__["$ref"].split("/")[-1]

return f"crate::datadog{version.upper()}::model::{name}" if version else name
return f"crate::datadog{version.upper()}::model::{name}" if version and name else name

def option_wrapper(name, option, nullable):
if option:
name = f"Option<{name}>"
if nullable:
name = f"Option<{name}>"
return name

def type_to_rust(schema, alternative_name=None, render_nullable=False, render_option=True, render_box=False, version=None):
"""Return Rust type name for the type."""

# special case for additionalProperties: true
if schema is True:
return "Value"
if schema is True or schema == {}:
return "serde_json::Value"

if "enum" not in schema:
name = formatter.simple_type(schema, render_nullable=render_nullable, render_option=render_option)
Expand All @@ -42,12 +48,10 @@ def type_to_rust(schema, alternative_name=None, render_nullable=False, render_op
name = get_name(schema, version)
if name:
if "enum" in schema:
if render_box and schema.get("nullable", False):
return f"Box<Option<{name}>>"
return f"Option<{name}>" if render_option else name
return option_wrapper(name, render_option, render_nullable)
if not (schema.get("additionalProperties") and not schema.get("properties")) and schema.get("type", "object") == "object":
inner_type = f"Box<{name}>" if render_box else name
return f"Option<{inner_type}>" if render_option else inner_type
name = f"Box<{name}>" if render_box else name
return option_wrapper(name, render_option, render_nullable)

type_ = schema.get("type")
if type_ is None:
Expand All @@ -70,20 +74,12 @@ def type_to_rust(schema, alternative_name=None, render_nullable=False, render_op
name = f"Option<{name}>"
if schema.get("nullable") and formatter.is_primitive(schema["items"]):
name = formatter.simple_type(schema["items"], render_nullable=render_nullable, render_option=False)
if render_nullable:
# return f"datadog.{prefix}List[{name}]"
# TODO: implement
return "None"
if render_option:
return f"Option<Vec<{name}>>"
return f"Vec<{name}>"
return option_wrapper(f"Vec<{name}>", render_option, render_nullable)
elif type_ == "object":
name = "serde_json::Value"
if "additionalProperties" in schema:
# return "map[string]{}".format(type_to_rust(schema["additionalProperties"]))
return "None"
if render_option:
return f"Option<Box<{alternative_name}>>"
return f"Box<{alternative_name}>"
name = type_to_rust(schema["additionalProperties"], render_nullable=render_nullable, render_option=False, version=version)
return option_wrapper(f"std::collections::HashMap<String, {name}>", render_option, render_nullable)

raise ValueError(f"Unknown type {type_}")

Expand Down Expand Up @@ -128,6 +124,16 @@ def responses_by_types(operation, version):
return result.items()


def get_apis_and_versions(all_apis):
result = {}
for version, api in all_apis.items():
for name, _ in api.items():
if name not in result:
result[name] = []
result[name].append(version)
return result.items()


def child_models(schema, alternative_name=None, seen=None, parent=None):
seen = seen or set()
current_name = get_name(schema)
Expand Down Expand Up @@ -418,6 +424,14 @@ def get_container(operation, attribute_path, container_name="o[0]"):
return f'{container_name}.{formatter.attribute_path(attribute_path)}'


def get_deprecated(schema):
if "properties" in schema:
for property in schema["properties"].values():
if property.get("deprecated", False):
return True
return False


def get_container_type(operation, attribute_path, stop=None):
attrs = attribute_path.split(".")[:stop]
for name, parameter in parameters(operation):
Expand Down
218 changes: 50 additions & 168 deletions .generator/src/generator/templates/api.j2
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{% include "partial_header.j2" %}

use reqwest;
use serde::{Serialize, Deserialize};
use crate::datadog::*;

{% for path, method, operation in operations|sort(attribute="2.operationId", case_sensitive=True) %}
Expand Down Expand Up @@ -72,6 +73,7 @@ impl {{ structName }} {
{% for path, method, operation in operations|sort(attribute="2.operationId", case_sensitive=True) %}
{%- set httpMethod = method.upper() %}
{%- set returnType = operation|return_type(version) %}
{%- set formParameter = operation|form_parameter %}
{% if operation.description is defined %}
{{ operation.description | block_comment }}
{%- endif %}
Expand All @@ -88,7 +90,7 @@ impl {{ structName }} {
pub async fn {{operation.operationId | snake_case}}_with_http_info(&self{% for name, parameter in operation|parameters %}{% if loop.first %}, params: {{operation.operationId}}Params{% endif %}{% endfor %}) -> Result<ResponseContent<{% if returnType %}{{returnType}}{% else %}(){% endif %}>, Error<{{operation.operationId}}Error>> {
let local_configuration = &self.config;

// unbox the parameters
// unbox and build parameters
{%- for name, parameter in operation|parameters %}
let {{name|variable_name}} = params.{{name|variable_name}};
{%- endfor %}
Expand All @@ -106,80 +108,40 @@ impl {{ structName }} {
{%- endif %}
{% endfor %});
let mut local_req_builder = local_client.request(reqwest::Method::{{ httpMethod }}, local_uri_str.as_str());
{#
{{#queryParams}}
{{#required}}
{{#isArray}}
local_req_builder = match "{{collectionFormat}}" {
"multi" => local_req_builder.query(&{{{paramName}}}.into_iter().map(|p| ("{{{baseName}}}".to_owned(), p.to_string())).collect::<Vec<(std::string::String, std::string::String)>>()),
_ => local_req_builder.query(&[("{{{baseName}}}", &{{{paramName}}}.into_iter().map(|p| p.to_string()).collect::<Vec<String>>().join(",").to_string())]),

{% for name, parameter in operation|parameters if parameter.in == "query" %}
{%- set schema = parameter | parameter_schema %}
{%- if parameter.required and schema.type == "array" %}
local_req_builder = local_req_builder.query(&[("{{name}}", &{{name|variable_name}}.into_iter().map(|p| p.to_string()).collect::<Vec<String>>().join(",").to_string())]);
{%- elif not parameter.required and schema.type == "array" %}
if let Some(ref local) = {{name|variable_name}} {
local_req_builder = local_req_builder.query(&[("{{name}}", &local.into_iter().map(|p| p.to_string()).collect::<Vec<String>>().join(",").to_string())]);
};
{{/isArray}}
{{^isArray}}
{{^isNullable}}
local_req_builder = local_req_builder.query(&[("{{{baseName}}}", &{{{paramName}}}.to_string())]);
{{/isNullable}}
{{#isNullable}}
{{#isDeepObject}}
if let Some(ref local_str) = {{{paramName}}} {
let params = crate::apis::parse_deep_object("{{{baseName}}}", local_str);
local_req_builder = local_req_builder.query(&params);
{%- elif parameter.required %}
local_req_builder = local_req_builder.query(&[("{{name}}", &{{name|variable_name}}.to_string())]);
{%- else %}
if let Some(ref local_str) = {{name|variable_name}} {
local_req_builder = local_req_builder.query(&[("{{name}}", &local_str.to_string())]);
};
{{/isDeepObject}}
{{^isDeepObject}}
if let Some(ref local_str) = {{{paramName}}} {
local_req_builder = local_req_builder.query(&[("{{{baseName}}}", &local_str.to_string())]);
{%- endif %}
{%- endfor %}

{% for name, parameter in operation|parameters if parameter.in == "header" %}
{%- if not parameter.required %}
if let Some(ref local) = {{name|variable_name}} {
local_req_builder = local_req_builder.header("{{name}}", &local.to_string());
};
{{/isDeepObject}}
{{/isNullable}}
{{/isArray}}
{{/required}}
{{^required}}
if let Some(ref local_str) = {{{paramName}}} {
{{#isArray}}
local_req_builder = match "{{collectionFormat}}" {
"multi" => local_req_builder.query(&local_str.into_iter().map(|p| ("{{{baseName}}}".to_owned(), p.to_string())).collect::<Vec<(std::string::String, std::string::String)>>()),
_ => local_req_builder.query(&[("{{{baseName}}}", &local_str.into_iter().map(|p| p.to_string()).collect::<Vec<String>>().join(",").to_string())]),
};
{{/isArray}}
{{^isArray}}
{{#isDeepObject}}
let params = crate::apis::parse_deep_object("{{{baseName}}}", local_str);
local_req_builder = local_req_builder.query(&params);
{{/isDeepObject}}
{{^isDeepObject}}
local_req_builder = local_req_builder.query(&[("{{{baseName}}}", &local_str.to_string())]);
{{/isDeepObject}}
{{/isArray}}
}
{{/required}}
{{/queryParams}}
#}
{%- else %}
local_req_builder = local_req_builder.header("{{name}}", &{{name|variable_name}}.to_string());
{%- endif %}
{%- endfor %}

// build user agent
if let Some(ref local_user_agent) = local_configuration.user_agent {
local_req_builder = local_req_builder.header(reqwest::header::USER_AGENT, local_user_agent.clone());
}
{#
{{#hasHeaderParams}}
{{#headerParams}}
{{#required}}
{{^isNullable}}
local_req_builder = local_req_builder.header("{{{baseName}}}", {{{paramName}}}{{#isArray}}.join(","){{/isArray}}.to_string());
{{/isNullable}}
{{#isNullable}}
match {{{paramName}}} {
Some(local_param_value) => { local_req_builder = local_req_builder.header("{{{baseName}}}", local_param_value{{#isArray}}.join(","){{/isArray}}.to_string()); },
None => { local_req_builder = local_req_builder.header("{{{baseName}}}", ""); },
}
{{/isNullable}}
{{/required}}
{{^required}}
if let Some(local_param_value) = {{{paramName}}} {
local_req_builder = local_req_builder.header("{{{baseName}}}", local_param_value{{#isArray}}.join(","){{/isArray}}.to_string());
}
{{/required}}
{{/headerParams}}
{{/hasHeaderParams}}
#}

// build auth
{%- set authMethods = operation.security if "security" in operation else openapi.security %}
{%- if authMethods %}
{%- for authMethod in authMethods %}
Expand All @@ -193,109 +155,29 @@ impl {{ structName }} {
{%- endfor %}
{%- endfor %}
{%- endif %}
{#
{{#isMultipart}}
{{#hasFormParams}}

{% if formParameter %}
// build form parameters
{%- if formParameter.required %}
let mut local_form = reqwest::multipart::Form::new();
{{#formParams}}
{{#isFile}}
{{^supportAsync}}
{{#required}}
{{^isNullable}}
local_form = local_form.file("{{{baseName}}}", {{{paramName}}})?;
{{/isNullable}}
{{#isNullable}}
match {{{paramName}}} {
Some(local_param_value) => { local_form = local_form.file("{{{baseName}}}", local_param_value)?; },
None => { unimplemented!("Required nullable form file param not supported"); },
}
{{/isNullable}}
{{/required}}
{{^required}}
if let Some(local_param_value) = {{{paramName}}} {
local_form = local_form.file("{{{baseName}}}", local_param_value)?;
}
{{/required}}
// TODO: support file upload for '{{{baseName}}}' parameter

{{/isFile}}
{{^isFile}}
{{#required}}
{{^isNullable}}
local_form = local_form.text("{{{baseName}}}", {{{paramName}}}{{#isArray}}.into_iter().map(|p| p.to_string()).collect::<Vec<String>>().join(","){{/isArray}}.to_string());
{{/isNullable}}
{{#isNullable}}
match {{{paramName}}} {
Some(local_param_value) => { local_form = local_form.text("{{{baseName}}}", local_param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::<Vec<String>>().join(","){{/isArray}}.to_string()); },
None => { local_form = local_form.text("{{{baseName}}}", ""); },
}
{{/isNullable}}
{{/required}}
{{^required}}
if let Some(local_param_value) = {{{paramName}}} {
local_form = local_form.text("{{{baseName}}}", local_param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::<Vec<String>>().join(","){{/isArray}}.to_string());
}
{{/required}}
{{/isFile}}
{{/formParams}}
local_form = local_form.part("{{formParameter.name}}", reqwest::multipart::Part::bytes({{formParameter.name}}).file_name("{{formParameter.name}}"));
local_req_builder = local_req_builder.multipart(local_form);
{{/hasFormParams}}
{{/isMultipart}}
{{^isMultipart}}
{{#hasFormParams}}
let mut local_form_params = std::collections::HashMap::new();
{{#formParams}}
{{#isFile}}
{{#required}}
{{^isNullable}}
local_form_params.insert("{{{baseName}}}", unimplemented!("File form param not supported with x-www-form-urlencoded content"));
{{/isNullable}}
{{#isNullable}}
match {{{paramName}}} {
Some(local_param_value) => { local_form_params.insert("{{{baseName}}}", unimplemented!("File form param not supported with x-www-form-urlencoded content")); },
None => { unimplemented!("Required nullable file form param not supported with x-www-form-urlencoded content"); },
}
{{/isNullable}}
{{/required}}
{{^required}}
if let Some(local_param_value) = {{{paramName}}} {
local_form_params.insert("{{{baseName}}}", unimplemented!("File form param not supported with x-www-form-urlencoded content"));
}
{{/required}}
{{/isFile}}
{{^isFile}}
{{#required}}
{{^isNullable}}
local_form_params.insert("{{{baseName}}}", {{{paramName}}}{{#isArray}}.into_iter().map(|p| p.to_string()).collect::<Vec<String>>().join(","){{/isArray}}.to_string());
{{/isNullable}}
{{#isNullable}}
match {{{paramName}}} {
Some(local_param_value) => { local_form_params.insert("{{{baseName}}}", local_param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::<Vec<String>>().join(","){{/isArray}}.to_string()); },
None => { local_form_params.insert("{{{baseName}}}", ""); },
}
{{/isNullable}}
{{/required}}
{{^required}}
if let Some(local_param_value) = {{{paramName}}} {
local_form_params.insert("{{{baseName}}}", local_param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::<Vec<String>>().join(","){{/isArray}}.to_string());
}
{{/required}}
{{/isFile}}
{{/formParams}}
local_req_builder = local_req_builder.form(&local_form_params);
{{/hasFormParams}}
{{/isMultipart}}
#}
{%- if operation.requestBody is defined and not formParameter %}
{%- set isBodyOptional = False if "required" in operation.requestBody and operation.requestBody.required else True %}
// body params
{%- if isBodyOptional %}
if {{operation.get("x-codegen-request-body-name", "body")|variable_name}}.is_some() {
local_req_builder = local_req_builder.json(&{{operation.get("x-codegen-request-body-name", "body")|variable_name}}.unwrap());
}
{%- else %}
local_req_builder = local_req_builder.json(&{{operation.get("x-codegen-request-body-name", "body")|variable_name}});
if let Some({{formParameter.name}}) = {{formParameter.name}} {
let mut local_form = reqwest::multipart::Form::new();
local_form = local_form.part("{{formParameter.name}}", reqwest::multipart::Part::bytes({{formParameter.name}}).file_name("{{formParameter.name}}"));
local_req_builder = local_req_builder.multipart(local_form);
};
{%- endif %}
{%- endif %}

{%- if operation.requestBody is defined and not formParameter %}
// build body parameters
let output = Vec::new();
let mut ser = serde_json::Serializer::with_formatter(output, DDFormatter);
if {{operation.get("x-codegen-request-body-name", "body")|variable_name}}.serialize(&mut ser).is_ok() {
local_req_builder = local_req_builder.body(ser.into_inner());
}
{%- endif %}

let local_req = local_req_builder.build()?;
Expand Down
Loading