use crate::core::config::ResolvedCrateConfig;
use crate::e2e::escape::escape_elixir;
use heck::ToSnakeCase;
use std::collections::HashMap;
use super::stubs::emit_test_backend;
use super::values::json_to_elixir;
pub(super) struct ElixirArgsContext<'a> {
pub(super) module_path: &'a str,
pub(super) options_type: Option<&'a str>,
pub(super) options_default_fn: Option<&'a str>,
pub(super) enum_fields: &'a HashMap<String, String>,
pub(super) fixture: &'a crate::e2e::fixture::Fixture,
pub(super) _handle_struct_type: Option<&'a str>,
pub(super) _handle_atom_list_fields: &'a std::collections::HashSet<String>,
pub(super) test_documents_path: &'a str,
pub(super) adapter_request_type: Option<&'a str>,
pub(super) enums: &'a [crate::core::ir::EnumDef],
pub(super) config: &'a ResolvedCrateConfig,
pub(super) type_defs: &'a [crate::core::ir::TypeDef],
pub(super) force_keyword_args: bool,
}
pub(super) fn build_args_and_setup(
input: &serde_json::Value,
args: &[crate::e2e::config::ArgMapping],
context: ElixirArgsContext<'_>,
) -> (Vec<String>, String, String) {
if args.is_empty() {
return build_empty_args_result(input);
}
let mut setup_lines: Vec<String> = Vec::new();
let mut parts: Vec<String> = Vec::new();
let mut teardown_block = String::new();
let use_keyword_form_for_optional_args = compute_trailing_keyword_count(args) >= 2;
let ctx = ArgEmitContext {
input,
module_path: context.module_path,
options_type: context.options_type,
options_default_fn: context.options_default_fn,
enum_fields: context.enum_fields,
fixture: context.fixture,
test_documents_path: context.test_documents_path,
adapter_request_type: context.adapter_request_type,
enums: context.enums,
config: context.config,
type_defs: context.type_defs,
use_keyword_form_for_optional_args,
};
for arg in args {
if arg.arg_type == "mock_url" {
push_mock_url_arg(arg, &ctx, &mut setup_lines, &mut parts);
continue;
}
if arg.arg_type == "mock_url_list" {
push_mock_url_list_arg(arg, &ctx, &mut setup_lines, &mut parts);
continue;
}
if arg.arg_type == "handle" {
push_handle_arg(arg, &ctx, &mut setup_lines, &mut parts);
continue;
}
if arg.arg_type == "test_backend" {
push_test_backend_arg(arg, &ctx, &mut setup_lines, &mut parts, &mut teardown_block);
continue;
}
let val = if arg.field == "input" {
Some(input.get("extract_input").unwrap_or(input))
} else {
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
input.get(field)
};
push_value_arg(arg, val, &ctx, &mut setup_lines, &mut parts);
}
let args_string = finalize_args_string(parts, args, context.force_keyword_args);
(setup_lines, args_string, teardown_block)
}
fn build_empty_args_result(input: &serde_json::Value) -> (Vec<String>, String, String) {
let cleaned_input = match input {
serde_json::Value::Object(m) => {
let mut cleaned = m.clone();
cleaned.remove("setup");
if cleaned.is_empty() {
serde_json::Value::Null
} else {
serde_json::Value::Object(cleaned)
}
}
other => other.clone(),
};
let is_empty_input = matches!(cleaned_input, serde_json::Value::Null);
if is_empty_input {
return (Vec::new(), String::new(), String::new());
}
(Vec::new(), json_to_elixir(&cleaned_input), String::new())
}
fn compute_trailing_keyword_count(args: &[crate::e2e::config::ArgMapping]) -> usize {
args.iter()
.rev()
.take_while(|a| a.optional)
.filter(|a| {
a.arg_type != "mock_url" && a.arg_type != "mock_url_list" && a.arg_type != "handle"
})
.count()
}
fn finalize_args_string(
parts: Vec<String>,
args: &[crate::e2e::config::ArgMapping],
force_keyword_args: bool,
) -> String {
let has_test_backend = args
.iter()
.any(|a| a.arg_type == "test_backend" || a.arg_type == "string");
if force_keyword_args && !has_test_backend {
return parts
.into_iter()
.zip(args.iter())
.map(|(part, arg)| {
let prefix = format!("{}: ", arg.name);
if part.starts_with(&prefix) {
part
} else {
format!("{}: {part}", arg.name)
}
})
.collect::<Vec<_>>()
.join(", ");
}
let mut positional_args = Vec::new();
let mut keyword_args = Vec::new();
for part in parts {
let is_keyword = part.contains(": ") && !part.starts_with('"');
if is_keyword {
keyword_args.push(part);
} else {
positional_args.push(part);
}
}
let mut final_args = positional_args;
final_args.extend(keyword_args);
final_args.join(", ")
}
struct ArgEmitContext<'a> {
input: &'a serde_json::Value,
module_path: &'a str,
options_type: Option<&'a str>,
options_default_fn: Option<&'a str>,
enum_fields: &'a HashMap<String, String>,
fixture: &'a crate::e2e::fixture::Fixture,
test_documents_path: &'a str,
adapter_request_type: Option<&'a str>,
enums: &'a [crate::core::ir::EnumDef],
config: &'a ResolvedCrateConfig,
type_defs: &'a [crate::core::ir::TypeDef],
use_keyword_form_for_optional_args: bool,
}
fn push_mock_url_arg(
arg: &crate::e2e::config::ArgMapping,
ctx: &ArgEmitContext<'_>,
setup_lines: &mut Vec<String>,
parts: &mut Vec<String>,
) {
let fixture_id = &ctx.fixture.id;
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
let value = ctx.input.get(field).unwrap_or(&serde_json::Value::Null);
if let Some(url) = crate::e2e::codegen::preserved_url_literal(ctx.fixture.preserve_input_urls, value) {
setup_lines.push(format!("{} = \"{}\"", arg.name, escape_elixir(url)));
} else if ctx.fixture.has_host_root_route() {
let env_key = format!("MOCK_SERVER_{}", fixture_id.to_uppercase());
setup_lines.push(format!(
"{} = System.get_env(\"{env_key}\") || (System.get_env(\"MOCK_SERVER_URL\") || \"\") <> \"/fixtures/{fixture_id}\"",
arg.name,
));
} else {
setup_lines.push(format!(
"{} = (System.get_env(\"MOCK_SERVER_URL\") || \"\") <> \"/fixtures/{fixture_id}\"",
arg.name,
));
}
if let Some(req_type) = ctx.adapter_request_type {
let req_var = format!("{}_req", arg.name);
setup_lines.push(format!(
"{req_var} = %{}.{req_type}{{url: {}}}",
ctx.module_path, arg.name
));
parts.push(req_var);
} else {
parts.push(arg.name.clone());
}
}
fn push_mock_url_list_arg(
arg: &crate::e2e::config::ArgMapping,
ctx: &ArgEmitContext<'_>,
setup_lines: &mut Vec<String>,
parts: &mut Vec<String>,
) {
let fixture_id = &ctx.fixture.id;
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
let val = ctx.input.get(field).unwrap_or(&serde_json::Value::Null);
let name = &arg.name;
if let Some(urls) = crate::e2e::codegen::preserved_url_list(ctx.fixture.preserve_input_urls, val) {
let literals: Vec<String> = urls.iter().map(|url| format!("\"{}\"", escape_elixir(url))).collect();
setup_lines.push(format!("{name} = [{}]", literals.join(", ")));
parts.push(name.clone());
return;
}
let env_key = format!("MOCK_SERVER_{}", fixture_id.to_uppercase());
let paths: Vec<String> = if let Some(arr) = val.as_array() {
arr.iter()
.filter_map(|v| v.as_str().map(|s| format!("\"{}\"", escape_elixir(s))))
.collect()
} else {
Vec::new()
};
let paths_literal = paths.join(", ");
setup_lines.push(format!(
"{name}_base = System.get_env(\"{env_key}\") || ((System.get_env(\"MOCK_SERVER_URL\") || \"\") <> \"/fixtures/{fixture_id}\")"
));
setup_lines.push(format!(
"{name} = Enum.map([{paths_literal}], fn p -> if String.starts_with?(p, \"http\"), do: p, else: {name}_base <> p end)"
));
parts.push(name.clone());
}
fn push_handle_arg(
arg: &crate::e2e::config::ArgMapping,
ctx: &ArgEmitContext<'_>,
setup_lines: &mut Vec<String>,
parts: &mut Vec<String>,
) {
let constructor_name = format!("create_{}", arg.name.to_snake_case());
let config_value = if arg.field == "input" {
ctx.input
} else {
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
ctx.input.get(field).unwrap_or(&serde_json::Value::Null)
};
let name = &arg.name;
if config_value.is_null() || config_value.is_object() && config_value.as_object().is_some_and(|o| o.is_empty()) {
setup_lines.push(format!("{{:ok, {name}}} = {}.{constructor_name}(nil)", ctx.module_path));
} else {
let json_str = serde_json::to_string(config_value).unwrap_or_else(|_| "{}".to_string());
let escaped = escape_elixir(&json_str);
setup_lines.push(format!("{name}_config = \"{escaped}\""));
setup_lines.push(format!(
"{{:ok, {name}}} = {}.{constructor_name}({name}_config)",
ctx.module_path
));
}
parts.push(arg.name.clone());
}
fn push_test_backend_arg(
arg: &crate::e2e::config::ArgMapping,
ctx: &ArgEmitContext<'_>,
setup_lines: &mut Vec<String>,
parts: &mut Vec<String>,
teardown_block: &mut String,
) {
if let Some(trait_name) = &arg.trait_name
&& let Some(trait_bridge) = ctx.config.trait_bridges.iter().find(|tb| tb.trait_name == *trait_name)
{
let mut methods: Vec<&crate::core::ir::MethodDef> = ctx
.type_defs
.iter()
.find(|t| t.name == *trait_name)
.map(|t| t.methods.iter().collect())
.unwrap_or_default();
if let Some(super_trait) = &trait_bridge.super_trait
&& let Some(super_type) = ctx.type_defs.iter().find(|t| &t.name == super_trait)
{
for method in &super_type.methods {
if !methods.iter().any(|m| m.name == method.name) {
methods.push(method);
}
}
}
let elixir_nif_module = format!("{}.Native", ctx.module_path);
let emission = emit_test_backend(trait_bridge, &methods, ctx.fixture, &elixir_nif_module, ctx.module_path);
teardown_block.push_str(&emission.teardown_block);
if let Some(pos) = emission.setup_block.find("__TRAIT_BRIDGE_MODULE_DEFS_END__") {
let marker_end = emission.setup_block[pos + 32..]
.find('\n')
.map(|i| pos + 32 + i + 1)
.unwrap_or_else(|| emission.setup_block.len());
let test_setup = emission.setup_block[marker_end..].trim_start().to_string();
if !test_setup.is_empty() {
setup_lines.push(test_setup);
}
} else {
setup_lines.push(emission.setup_block);
}
parts.push(emission.arg_expr);
if trait_bridge.register_fn.is_some() {
let backend_name = extract_backend_name_from_input(&ctx.fixture.input, &ctx.fixture.id);
parts.push(format!("\"{}\"", escape_elixir(&backend_name)));
}
return;
}
panic!(
"Elixir e2e generator: fixture `{}` declares a `test_backend` arg `{}` with trait `{:?}`, but either it has no `trait_name` configured or no `[[crates.trait_bridges]]` entry matches it; cannot generate an Elixir stub without a resolvable trait bridge",
ctx.fixture.id, arg.name, arg.trait_name
);
}
fn push_value_arg(
arg: &crate::e2e::config::ArgMapping,
val: Option<&serde_json::Value>,
ctx: &ArgEmitContext<'_>,
setup_lines: &mut Vec<String>,
parts: &mut Vec<String>,
) {
match val {
None | Some(serde_json::Value::Null) if arg.optional => {
}
None | Some(serde_json::Value::Null) => {
let default_val = match arg.arg_type.as_str() {
"string" => "\"\"".to_string(),
"int" | "integer" => "0".to_string(),
"float" | "number" => "0.0".to_string(),
"bool" | "boolean" => "false".to_string(),
_ => "nil".to_string(),
};
parts.push(default_val);
}
Some(v) => push_present_value_arg(arg, v, ctx, setup_lines, parts),
}
}
fn push_present_value_arg(
arg: &crate::e2e::config::ArgMapping,
v: &serde_json::Value,
ctx: &ArgEmitContext<'_>,
setup_lines: &mut Vec<String>,
parts: &mut Vec<String>,
) {
if try_push_file_path_value(arg, v, ctx, parts) {
return;
}
if try_push_bytes_value(arg, v, ctx, setup_lines, parts) {
return;
}
if arg.arg_type == "json_object" && !v.is_null() && try_push_json_object_value(arg, v, ctx, setup_lines, parts) {
return;
}
let elixir_val = json_to_elixir(v);
push_arg_value(&arg.name, elixir_val, arg.optional, parts);
}
fn try_push_file_path_value(
arg: &crate::e2e::config::ArgMapping,
v: &serde_json::Value,
ctx: &ArgEmitContext<'_>,
parts: &mut Vec<String>,
) -> bool {
if arg.arg_type != "file_path" {
return false;
}
let Some(path_str) = v.as_str() else {
return false;
};
let full_path = format!("{}/{path_str}", ctx.test_documents_path);
let formatted = format!("\"{}\"", escape_elixir(&full_path));
push_arg_value(&arg.name, formatted, arg.optional, parts);
true
}
fn try_push_bytes_value(
arg: &crate::e2e::config::ArgMapping,
v: &serde_json::Value,
ctx: &ArgEmitContext<'_>,
setup_lines: &mut Vec<String>,
parts: &mut Vec<String>,
) -> bool {
if arg.arg_type != "bytes" {
return false;
}
let Some(raw) = v.as_str() else {
return false;
};
let var_name = &arg.name;
if raw.starts_with('<') || raw.starts_with('{') || raw.starts_with('[') || raw.contains(' ') {
let formatted = format!("\"{}\"", escape_elixir(raw));
push_arg_value(&arg.name, formatted, arg.optional, parts);
return true;
}
let first = raw.chars().next().unwrap_or('\0');
let is_file_path = (first.is_ascii_alphanumeric() || first == '_')
&& raw
.find('/')
.is_some_and(|slash_pos| slash_pos > 0 && raw[slash_pos + 1..].contains('.'));
if is_file_path {
let full_path = format!("{}/{raw}", ctx.test_documents_path);
let escaped = escape_elixir(&full_path);
setup_lines.push(format!("{var_name} = :binary.bin_to_list(File.read!(\"{escaped}\"))"));
push_arg_value(&arg.name, var_name.to_string(), arg.optional, parts);
return true;
}
setup_lines.push(format!(
"{var_name} = :binary.bin_to_list(Base.decode64!(\"{}\", padding: false))",
escape_elixir(raw)
));
push_arg_value(&arg.name, var_name.to_string(), arg.optional, parts);
true
}
fn try_push_json_object_value(
arg: &crate::e2e::config::ArgMapping,
v: &serde_json::Value,
ctx: &ArgEmitContext<'_>,
setup_lines: &mut Vec<String>,
parts: &mut Vec<String>,
) -> bool {
let object_type = crate::e2e::codegen::recipe::json_object_constructor_type(arg, ctx.options_type, v);
if let (Some(_opts_type), Some(options_fn), Some(obj)) = (object_type, ctx.options_default_fn, v.as_object()) {
push_options_via_default_fn(arg, obj, options_fn, ctx, setup_lines, parts);
return true;
}
if let (Some(opts_type), None, Some(_obj)) = (object_type, ctx.options_default_fn, v.as_object()) {
push_options_struct_literal(arg, opts_type, v, ctx, setup_lines, parts);
return true;
}
if let Some(elem_type) = &arg.element_type
&& try_push_element_type_array(arg, v, elem_type, ctx, setup_lines, parts)
{
return true;
}
if !v.is_null() {
let json_str = serde_json::to_string(v).unwrap_or_else(|_| "{}".to_string());
let escaped = escape_elixir(&json_str);
let formatted = format!("\"{escaped}\"");
push_arg_value(
&arg.name,
formatted,
ctx.use_keyword_form_for_optional_args && arg.optional,
parts,
);
return true;
}
false
}
fn push_options_via_default_fn(
arg: &crate::e2e::config::ArgMapping,
obj: &serde_json::Map<String, serde_json::Value>,
options_fn: &str,
ctx: &ArgEmitContext<'_>,
setup_lines: &mut Vec<String>,
parts: &mut Vec<String>,
) {
let options_var = format!("{}_value", arg.name);
setup_lines.push(format!("{options_var} = {}.{options_fn}()", ctx.module_path));
for (k, vv) in obj.iter() {
let snake_key = k.to_snake_case();
let elixir_val = if let Some(_enum_type) = ctx.enum_fields.get(k) {
if let Some(s) = vv.as_str() {
let snake_val = s.to_snake_case();
format!(":{snake_val}")
} else {
json_to_elixir(vv)
}
} else {
json_to_elixir(vv)
};
setup_lines.push(format!(
"{options_var} = %{{{options_var} | {snake_key}: {elixir_val}}}"
));
}
parts.push(format!("{}: {options_var}", arg.name));
}
fn push_options_struct_literal(
arg: &crate::e2e::config::ArgMapping,
opts_type: &str,
v: &serde_json::Value,
ctx: &ArgEmitContext<'_>,
setup_lines: &mut Vec<String>,
parts: &mut Vec<String>,
) {
let obj = v.as_object().expect("caller verified v is a JSON object");
let fixture_id = &ctx.fixture.id;
let options_var = format!("{}_value", arg.name);
let as_keyword = ctx.use_keyword_form_for_optional_args && arg.optional;
if crate::e2e::codegen::value_contains_mock_url_placeholder(v) {
let env_key = crate::e2e::codegen::mock_url_env_key(fixture_id);
let base_var = format!("{}_mock_base_url", arg.name);
setup_lines.push(format!(
"{base_var} = System.get_env(\"{env_key}\") || \"#{{System.get_env(\"MOCK_SERVER_URL\")}}/fixtures/{fixture_id}\""
));
let fields = render_struct_fields(
obj,
ctx.enum_fields,
Some(&base_var),
&ctx.fixture.docs_files_for_arg(&arg.field),
);
setup_lines.push(format!("{options_var} = %{}.{opts_type}{{{fields}}}", ctx.module_path));
push_arg_value(&arg.name, options_var, as_keyword, parts);
return;
}
let fields = render_struct_fields(obj, ctx.enum_fields, None, &ctx.fixture.docs_files_for_arg(&arg.field));
setup_lines.push(format!("{options_var} = %{}.{opts_type}{{{fields}}}", ctx.module_path));
push_arg_value(&arg.name, options_var, as_keyword, parts);
}
fn try_push_element_type_array(
arg: &crate::e2e::config::ArgMapping,
v: &serde_json::Value,
elem_type: &String,
ctx: &ArgEmitContext<'_>,
setup_lines: &mut Vec<String>,
parts: &mut Vec<String>,
) -> bool {
if v.is_array()
&& let Some(enum_def) = ctx.enums.iter().find(|e| &e.name == elem_type && e.serde_tag.is_some())
{
let formatted = emit_tagged_enum_array(v, enum_def, ctx.enums);
push_arg_value(&arg.name, formatted, arg.optional, parts);
return true;
}
if v.is_array() {
let fixture_id = &ctx.fixture.id;
if crate::e2e::codegen::value_contains_mock_url_placeholder(v) {
let env_key = crate::e2e::codegen::mock_url_env_key(fixture_id);
let base_var = format!("{}_mock_base_url", arg.name);
let json_var = format!("{}_json", arg.name);
let value_var = format!("{}_value", arg.name);
let formatted = json_to_elixir(v);
setup_lines.push(format!(
"{base_var} = System.get_env(\"{env_key}\") || \"#{{System.get_env(\"MOCK_SERVER_URL\")}}/fixtures/{fixture_id}\""
));
setup_lines.push(format!(
"{json_var} = Jason.encode!({formatted}) |> String.replace(\"{}\", {base_var})",
crate::e2e::codegen::MOCK_URL_PLACEHOLDER
));
setup_lines.push(format!("{value_var} = Jason.decode!({json_var})"));
push_arg_value(&arg.name, value_var, arg.optional, parts);
return true;
}
let formatted = json_to_elixir(v);
push_arg_value(&arg.name, formatted, arg.optional, parts);
return true;
}
false
}
fn push_arg_value(name: &str, value: String, as_keyword: bool, parts: &mut Vec<String>) {
if as_keyword {
parts.push(format!("{name}: {value}"));
} else {
parts.push(value);
}
}
fn render_struct_fields(
obj: &serde_json::Map<String, serde_json::Value>,
enum_fields: &HashMap<String, String>,
mock_base_var: Option<&str>,
docs_files: &[crate::e2e::fixture::FixtureDocsFileInput],
) -> String {
obj.iter()
.map(|(k, vv)| {
let snake_key = k.to_snake_case();
let pointer = format!("/{k}");
let elixir_val = if let Some(file) = docs_files.iter().find(|file| file.field == pointer) {
crate::e2e::template_env::render(
"elixir/docs_file_read.jinja",
minijinja::context! { path => escape_elixir(&file.path) },
)
.trim_end()
.to_string()
} else if enum_fields.contains_key(k) {
if let Some(s) = vv.as_str() {
let snake_val = s.to_snake_case();
format!(":{snake_val}")
} else {
render_elixir_value(vv, mock_base_var)
}
} else {
render_elixir_value(vv, mock_base_var)
};
format!("{snake_key}: {elixir_val}")
})
.collect::<Vec<_>>()
.join(", ")
}
fn render_elixir_value(value: &serde_json::Value, mock_base_var: Option<&str>) -> String {
if let Some(base_var) = mock_base_var
&& crate::e2e::codegen::value_contains_mock_url_placeholder(value)
{
match value {
serde_json::Value::String(s) => format!(
"String.replace(\"{}\", \"{}\", {base_var})",
escape_elixir(s),
crate::e2e::codegen::MOCK_URL_PLACEHOLDER
),
_ => {
let value_literal = json_to_elixir(value);
format!(
"Jason.decode!(Jason.encode!({value_literal}) |> String.replace(\"{}\", {base_var}), keys: :atoms)",
crate::e2e::codegen::MOCK_URL_PLACEHOLDER
)
}
}
} else {
json_to_elixir(value)
}
}
pub(super) fn match_unit_enum_atom(value: &serde_json::Value, enum_def: &crate::core::ir::EnumDef) -> Option<String> {
let s = value.as_str()?;
if enum_def.variants.iter().any(|v| !v.fields.is_empty()) {
return None;
}
for variant in &enum_def.variants {
let wire_tag = crate::codegen::naming::wire_variant_value(
&variant.name,
variant.serde_rename.as_deref(),
enum_def.serde_rename_all.as_deref(),
);
if wire_tag == s {
return Some(format!(":{}", variant.name.to_snake_case()));
}
}
None
}
fn emit_tagged_enum_array(
value: &serde_json::Value,
enum_def: &crate::core::ir::EnumDef,
all_enums: &[crate::core::ir::EnumDef],
) -> String {
let arr = match value.as_array() {
Some(a) => a,
None => return json_to_elixir(value),
};
let tag_key = crate::codegen::serde_enum_repr::tagged_object_tag_key(enum_def);
let mut elements: Vec<String> = Vec::with_capacity(arr.len());
for item in arr {
let obj = match item.as_object() {
Some(o) => o,
None => {
elements.push(json_to_elixir(item));
continue;
}
};
let tag_value = obj.get(tag_key).and_then(|v| v.as_str()).unwrap_or("");
let matched = enum_def.variants.iter().find(|variant| {
let wire_tag = crate::codegen::naming::wire_variant_value(
&variant.name,
variant.serde_rename.as_deref(),
enum_def.serde_rename_all.as_deref(),
);
wire_tag == tag_value
});
let Some(variant) = matched else {
elements.push(json_to_elixir(item));
continue;
};
let variant_atom = format!(":{}", variant.name.to_snake_case());
if variant.fields.is_empty() {
elements.push(variant_atom);
continue;
}
let mut field_strs: Vec<String> = Vec::with_capacity(variant.fields.len());
for field in &variant.fields {
let wire_field = field.serde_rename.as_deref().unwrap_or(&field.name);
let rust_field_atom = field.name.clone();
let emitted_val = if let Some(field_val) = obj.get(wire_field) {
if let crate::core::ir::TypeRef::Named(type_name) = &field.ty {
all_enums
.iter()
.find(|e| &e.name == type_name && e.serde_tag.is_none())
.and_then(|nested| match_unit_enum_atom(field_val, nested))
.unwrap_or_else(|| json_to_elixir(field_val))
} else {
json_to_elixir(field_val)
}
} else if field.optional {
"nil".to_string()
} else {
continue;
};
field_strs.push(format!("{rust_field_atom}: {emitted_val}"));
}
let map_body = field_strs.join(", ");
elements.push(format!("{{{variant_atom}, %{{{map_body}}}}}"));
}
format!("[{}]", elements.join(", "))
}
fn extract_backend_name_from_input(input: &serde_json::Value, fallback: &str) -> String {
if let Some(obj) = input.as_object() {
if let Some(s) = obj.get("name").and_then(|v| v.as_str()) {
return s.to_string();
}
for v in obj.values() {
if let Some(inner) = v.as_object()
&& let Some(s) = inner.get("name").and_then(|v| v.as_str())
{
return s.to_string();
}
}
for v in obj.values() {
if let Some(s) = v.as_str() {
return s.to_string();
}
}
}
fallback.to_string()
}