Skip to content

Commit

Permalink
Configurable server variables (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
nkzou authored Feb 28, 2024
1 parent ed3224c commit ace13d4
Show file tree
Hide file tree
Showing 85 changed files with 6,910 additions and 5,554 deletions.
21 changes: 9 additions & 12 deletions .generator/src/generator/templates/api.j2
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,11 @@ impl {{ structName }} {
{{ operation.description | block_comment }}
{%- endif %}
pub async fn {{operation.operationId | snake_case}}_with_http_info(&self{% for name, parameter in requiredParams %}, {{name|variable_name}}: {{ get_type_for_parameter(parameter, version) }}{% endfor %}{% if operation|has_optional_parameter %}, params: {{operation.operationId}}OptionalParams{% endif %}) -> Result<ResponseContent<{% if returnType %}{{returnType}}{% else %}(){% endif %}>, Error<{{operation.operationId}}Error>> {
let local_configuration = &self.config;
let operation_id = "{{ version }}.{{ operation.operationId | snake_case }}";
{%- if "x-unstable" in operation %}
let operation_id = "{{ version }}.{{ operation.operationId | snake_case }}".to_string();
if self.config.is_unstable_operation_enabled(&operation_id) {
warn!("Using unstable operation {}", operation_id);
if local_configuration.is_unstable_operation_enabled(operation_id) {
warn!("Using unstable operation {operation_id}");
} else {
let local_error = UnstableOperationDisabledError {
msg: "Operation '{{ version }}.{{ operation.operationId | snake_case }}' is not enabled".to_string(),
Expand All @@ -121,8 +122,6 @@ impl {{ structName }} {
}
{%- endif %}

let local_configuration = &self.config;

{% for name, parameter in operation|parameters if parameter.required != true %}
{%- if loop.first %}
// unbox and build optional parameters
Expand All @@ -133,8 +132,8 @@ impl {{ structName }} {
let local_client = &local_configuration.client;

let local_uri_str = format!(
"{}{{path}}",
local_configuration.base_path
"{}{{path}}",
local_configuration.get_operation_host(operation_id)
{%- for name, parameter in operation|parameters if parameter.in == "path" %}, {{ name|variable_name }}=
{%- if parameter.schema.type == "string" %}
urlencode({{ name|variable_name }}{% if not parameter.required %}.unwrap(){% elif parameter.schema.nullable %}.unwrap(){% endif %}{% if parameter.schema.type == "array" %}.join(",").as_ref(){% endif %})
Expand Down Expand Up @@ -172,9 +171,7 @@ impl {{ structName }} {
{%- 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());
}
local_req_builder = local_req_builder.header(reqwest::header::USER_AGENT, local_configuration.user_agent.clone());

// build auth
{%- set authMethods = operation.security if "security" in operation else openapi.security %}
Expand All @@ -183,8 +180,8 @@ impl {{ structName }} {
{%- for name in authMethod %}
{%- set schema = openapi.components.securitySchemes[name] %}
{%- if schema.type == "apiKey" and schema.in != "cookie" %}
if let Some(ref local_apikey) = local_configuration.{{name|variable_name}} {
local_req_builder = local_req_builder.header("{{schema.name}}", local_apikey);
if let Some(local_key) = local_configuration.auth_keys.get("{{ name }}") {
local_req_builder = local_req_builder.header("{{schema.name}}", &local_key.key);
};
{%- endif %}
{%- endfor %}
Expand Down
216 changes: 168 additions & 48 deletions .generator/src/generator/templates/configuration.j2
Original file line number Diff line number Diff line change
@@ -1,30 +1,87 @@
{% include "partial_header.j2" %}
use lazy_static::lazy_static;
use std::env;
use std::collections::HashMap;
use log::warn;

#[derive(Debug, Clone)]
pub struct Configuration {
pub base_path: String,
pub user_agent: Option<String>,
pub client: reqwest_middleware::ClientWithMiddleware,
{%- set authMethods = openapi.security %}
{%- if authMethods %}
{%- for authMethod in authMethods %}
{%- for name in authMethod %}
{%- set schema = openapi.components.securitySchemes[name] %}
{%- if schema.type == "apiKey" and schema.in != "cookie" %}
pub {{name|variable_name}}: Option<String>,
{%- endif %}
{%- endfor %}
{%- endfor %}
{%- endif %}
unstable_operations: HashMap<String, bool>,
pub struct ServerVariable {
pub description: String,
pub default_value: String,
pub enum_values: Vec<String>,
}

#[derive(Debug, Clone)]
pub struct ServerConfiguration {
pub url: String,
pub description: String,
pub variables: HashMap<String, ServerVariable>,
}

impl ServerConfiguration {
pub fn get_url(&self, variables: &HashMap<String, String>) -> String {
let mut url = self.url.clone();
for (name, variable) in &self.variables {
let value = variables.get(name).unwrap_or(&variable.default_value);
if !variable.enum_values.contains(value) && !variable.enum_values.is_empty() {
panic!("Value {value} for variable {name} is not in the enum values");
}
url = url.replace(&format!("{{ '{{{name}}}' }}"), &value);
}
url
}
}

#[derive(Debug, Clone)]
pub struct APIKey {
pub key: String,
pub prefix: String,
}

#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct Configuration {
pub(crate) user_agent: String,
pub(crate) client: reqwest_middleware::ClientWithMiddleware,
pub(crate) unstable_operations: HashMap<String, bool>,
pub(crate) auth_keys: HashMap<String, APIKey>,
pub server_index: usize,
pub server_variables: HashMap<String, String>,
pub server_operation_index: HashMap<String, usize>,
pub server_operation_variables: HashMap<String, HashMap<String, String>>,
}

impl Configuration {
pub fn new() -> Configuration {
Configuration::default()
pub fn new() -> Self {
Self::default()
}

pub fn client(&mut self, client: reqwest_middleware::ClientWithMiddleware) {
self.client = client;
}

pub fn get_operation_host(&self, operation_str: &str) -> String {
let operation = operation_str.to_string();
if let Some(servers) = OPERATION_SERVERS.get(&operation) {
let server_index = self
.server_operation_index
.get(&operation)
.cloned()
.unwrap_or(0);
return servers
.get(server_index)
.expect(&format!("Server index for operation {operation} not found"))
.get_url(
&self
.server_operation_variables
.get(&operation)
.unwrap_or(&HashMap::new()),
);
}
SERVERS
.get(self.server_index)
.expect("Server index not found.")
.get_url(&self.server_variables)
}

pub fn set_unstable_operation_enabled(&mut self, operation: &str, enabled: bool) -> bool {
Expand All @@ -33,11 +90,7 @@ impl Configuration {
return true;
}

warn!(
"Operation {} is not an unstable operation, can't enable/disable",
operation
);

warn!("Operation {operation} is not an unstable operation, can't enable/disable");
false
}

Expand All @@ -46,11 +99,7 @@ impl Configuration {
return self.unstable_operations.get(operation).unwrap().clone();
}

warn!(
"Operation {} is not an unstable operation, is always enabled",
operation
);

warn!("Operation {operation} is not an unstable operation, is always enabled");
false
}

Expand All @@ -61,11 +110,22 @@ impl Configuration {

false
}

pub fn set_auth_key(&mut self, operation_str: &str, api_key: APIKey) {
self.auth_keys.insert(operation_str.to_string(), api_key);
}
}

impl Default for Configuration {
fn default() -> Self {
let http_client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new());
let user_agent = format!(
"datadog-api-client-rust/{} (rust {}; os {}; arch {})",
option_env!("CARGO_PKG_VERSION").unwrap_or("?"),
option_env!("DD_RUSTC_VERSION").unwrap_or("?"),
env::consts::OS,
env::consts::ARCH,
);
let unstable_operations = HashMap::from([
{%- for version, api in apis.items() %}
{%- for operations in api.values() %}
Expand All @@ -77,29 +137,89 @@ impl Default for Configuration {
{%- endfor %}
{%- endfor %}
]);
let mut auth_keys: HashMap<String, APIKey> = HashMap::new();
{%- set authMethods = openapi.security %}
{%- if authMethods %}
{%- for authMethod in authMethods %}
{%- for name in authMethod %}
{%- set schema = openapi.components.securitySchemes[name] %}
{%- if schema.type == "apiKey" and schema.in != "cookie" %}
auth_keys.insert(
"{{ name }}".to_owned(),
APIKey {
key: env::var("{{ schema.get("x-env-name") }}").unwrap_or_default(),
prefix: "".to_owned(),
},
);
{%- endif %}
{%- endfor %}
{%- endfor %}
{%- endif %}

Configuration {
base_path: "https://api.datadoghq.com".to_owned(),
user_agent: Some(format!(
"datadog-api-client-rust/{} (rust {}; os {}; arch {})",
option_env!("CARGO_PKG_VERSION").unwrap_or("?"),
option_env!("DD_RUSTC_VERSION").unwrap_or("?"),
env::consts::OS,
env::consts::ARCH,
)),
Self {
user_agent,
client: http_client.build(),
{%- set authMethods = openapi.security %}
{%- if authMethods %}
{%- for authMethod in authMethods %}
{%- for name in authMethod %}
{%- set schema = openapi.components.securitySchemes[name] %}
{%- if schema.type == "apiKey" and schema.in != "cookie" %}
{{name|variable_name}}: env::var("{{ schema.get("x-env-name") }}").ok(),
{%- endif %}
{%- endfor %}
{%- endfor %}
{%- endif %}
unstable_operations,
auth_keys,
server_index: 0,
server_variables: HashMap::new(),
server_operation_index: HashMap::new(),
server_operation_variables: HashMap::new(),
}
}
}

{%- macro server_configuration(server) -%}
ServerConfiguration {
url: "{{ server.url }}".into(),
description: "{{ server.description|default("No description provided") }}".into(),
variables: HashMap::from([
{%- for name, variable in server.get("variables", {}).items() %}
(
"{{ name }}".into(),
ServerVariable {
description: "{{ variable.description|default("No description provided") }}".into(),
default_value: "{{ variable.default }}".into(),
enum_values: vec![
{%- for value in variable.enum %}
"{{ value }}".into(),
{%- endfor %}
],
},
),
{%- endfor %}
]),
},
{%- endmacro %}

lazy_static! {
static ref SERVERS: Vec<ServerConfiguration> = {
vec![
{%- for server in openapi.servers %}
{{ server_configuration(server) }}
{%- endfor %}
]
};
static ref OPERATION_SERVERS: HashMap<String, Vec<ServerConfiguration>> = {
HashMap::from([
{%- for version, spec in all_specs.items() %}
{%- for path in spec.paths.values() %}
{%- for operation in path.values() %}
{%- for server in operation.servers %}
{% if loop.first %}
(
"{{ version }}.{{ operation.operationId | snake_case }}".into(),
vec![
{%- endif %}
{{ server_configuration(server) }}
{%- if loop.last %}
],
),
{%- endif %}
{%- endfor %}
{%- endfor %}
{%- endfor %}
{%- endfor %}
])
};
}
2 changes: 1 addition & 1 deletion .generator/src/generator/templates/function_mappings.j2
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ fn test_{{version}}_{{ operation['operationId'] | snake_case }}(world: &mut Data
world.response.object = serde_json::to_value(entity).unwrap();
}
},
_ => panic!("error parsing response: {}", error),
_ => panic!("error parsing response: {error}"),
};
}
};
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ license = "Apache-2.0"
edition = "2021"

[dependencies]
lazy_static = "1.4.0"
log = "0.4.20"
reqwest = { version = "^0.11", features = ["multipart"] }
reqwest-middleware = "0.1.6"
Expand All @@ -30,7 +31,6 @@ rvcr = { git = "https://github.com/nkzou/rvcr.git", rev = "b8f84bc0dfacd539fdc6f
vcr-cassette = "^2.0"
sha256 = "1.4.0"
tokio = { version = "1.10", features = ["macros", "rt-multi-thread", "time"] }
lazy_static = "1.4.0"
minijinja = "1.0.10"
convert_case = "0.6.0"

Expand Down
Loading

0 comments on commit ace13d4

Please sign in to comment.