1pub mod brew;
21pub mod c;
22pub mod client;
23pub mod csharp;
24pub mod dart;
25mod dart_visitors;
26pub mod elixir;
27pub mod gleam;
28pub mod go;
29pub mod java;
30pub mod kotlin;
31pub mod kotlin_android;
32pub mod php;
33pub mod python;
34pub mod r;
35pub mod ruby;
36pub mod rust;
37pub mod streaming_assertions;
38pub mod swift;
39pub mod typescript;
40pub mod wasm;
41pub mod zig;
42mod zig_visitors;
43
44use crate::config::E2eConfig;
45use crate::fixture::{Fixture, FixtureGroup};
46use alef_core::backend::GeneratedFile;
47use alef_core::config::ResolvedCrateConfig;
48use alef_core::ir::TypeDef;
49use anyhow::Result;
50
51pub(crate) fn should_include_fixture(fixture: &Fixture, language: &str, e2e_config: &E2eConfig) -> bool {
62 if !e2e_config.exclude_categories.is_empty() && e2e_config.exclude_categories.contains(&fixture.resolved_category())
63 {
64 return false;
65 }
66 if let Some(skip) = &fixture.skip {
67 if skip.should_skip(language) {
68 return false;
69 }
70 }
71 let call_config = e2e_config.resolve_call_for_fixture(fixture.call.as_deref(), &fixture.input);
72 if call_config.skip_languages.iter().any(|l| l == language) {
74 return false;
75 }
76 if call_config.function.is_empty() && !call_config.overrides.contains_key(language) {
77 return false;
78 }
79 true
80}
81
82pub(crate) fn transform_json_keys_for_language(value: &serde_json::Value, wire_case: &str) -> serde_json::Value {
92 use heck::{ToKebabCase, ToLowerCamelCase, ToPascalCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase};
93 let rewrite_key: fn(&str) -> String = match wire_case {
94 "camelCase" => |k| k.to_lower_camel_case(),
95 "PascalCase" => |k| k.to_pascal_case(),
96 "SCREAMING_SNAKE_CASE" => |k| k.to_shouty_snake_case(),
97 "kebab-case" => |k| k.to_kebab_case(),
98 "SCREAMING-KEBAB-CASE" => |k| k.to_shouty_kebab_case(),
99 _ => |k| k.to_snake_case(),
100 };
101 fn walk(value: &serde_json::Value, rewrite_key: fn(&str) -> String) -> serde_json::Value {
102 match value {
103 serde_json::Value::Object(obj) => {
104 let new_obj: serde_json::Map<String, serde_json::Value> = obj
105 .iter()
106 .map(|(k, v)| (rewrite_key(k), walk(v, rewrite_key)))
107 .collect();
108 serde_json::Value::Object(new_obj)
109 }
110 serde_json::Value::Array(arr) => {
111 serde_json::Value::Array(arr.iter().map(|v| walk(v, rewrite_key)).collect())
112 }
113 other => other.clone(),
114 }
115 }
116 walk(value, rewrite_key)
117}
118
119pub trait E2eCodegen: Send + Sync {
121 fn generate(
128 &self,
129 groups: &[FixtureGroup],
130 e2e_config: &E2eConfig,
131 config: &ResolvedCrateConfig,
132 type_defs: &[TypeDef],
133 ) -> Result<Vec<GeneratedFile>>;
134
135 fn language_name(&self) -> &'static str;
137}
138
139pub fn all_generators() -> Vec<Box<dyn E2eCodegen>> {
141 vec![
142 Box::new(rust::RustE2eCodegen),
143 Box::new(python::PythonE2eCodegen),
144 Box::new(typescript::TypeScriptCodegen),
145 Box::new(go::GoCodegen),
146 Box::new(java::JavaCodegen),
147 Box::new(kotlin::KotlinE2eCodegen),
148 Box::new(kotlin_android::KotlinAndroidE2eCodegen),
149 Box::new(csharp::CSharpCodegen),
150 Box::new(php::PhpCodegen),
151 Box::new(ruby::RubyCodegen),
152 Box::new(elixir::ElixirCodegen),
153 Box::new(gleam::GleamE2eCodegen),
154 Box::new(r::RCodegen),
155 Box::new(wasm::WasmCodegen),
156 Box::new(c::CCodegen),
157 Box::new(zig::ZigE2eCodegen),
158 Box::new(dart::DartE2eCodegen),
159 Box::new(swift::SwiftE2eCodegen),
160 Box::new(brew::BrewCodegen),
161 ]
162}
163
164pub fn generators_for(languages: &[String]) -> Vec<Box<dyn E2eCodegen>> {
166 all_generators()
167 .into_iter()
168 .filter(|g| languages.iter().any(|l| l == g.language_name()))
169 .collect()
170}
171
172pub(crate) fn resolve_field<'a>(input: &'a serde_json::Value, field_path: &str) -> &'a serde_json::Value {
178 if field_path == "input" {
180 return input;
181 }
182 let path = field_path.strip_prefix("input.").unwrap_or(field_path);
183 let mut current = input;
184 for part in path.split('.') {
185 current = current.get(part).unwrap_or(&serde_json::Value::Null);
186 }
187 current
188}