use super::renderers::kotlin_getter;
use super::types::{PathSegment, PhpGetterMap};
use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
use std::collections::HashSet;
pub(super) fn render_java_with_optionals(
segments: &[PathSegment],
result_var: &str,
optional_fields: &HashSet<String>,
) -> String {
let mut out = result_var.to_string();
let mut path_so_far = String::new();
for (i, seg) in segments.iter().enumerate() {
let is_leaf = i == segments.len() - 1;
match seg {
PathSegment::Field(f) => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(f);
out.push('.');
out.push_str(&f.to_lower_camel_case());
out.push_str("()");
let _ = is_leaf;
let _ = optional_fields;
}
PathSegment::ArrayField { name, index } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(name);
out.push('.');
out.push_str(&name.to_lower_camel_case());
out.push_str(&format!("().get({index})"));
}
PathSegment::MapAccess { field, key } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(field);
out.push('.');
out.push_str(&field.to_lower_camel_case());
let is_numeric = !key.is_empty() && key.chars().all(|c| c.is_ascii_digit());
if is_numeric {
out.push_str(&format!("().get({key})"));
} else {
out.push_str(&format!("().get(\"{key}\")"));
}
}
PathSegment::Length => {
out.push_str(".size()");
}
}
}
out
}
pub(super) fn render_kotlin_with_optionals(
segments: &[PathSegment],
result_var: &str,
optional_fields: &HashSet<String>,
) -> String {
let mut out = result_var.to_string();
let mut path_so_far = String::new();
let mut prev_was_nullable = false;
for seg in segments {
let nav = if prev_was_nullable { "?." } else { "." };
match seg {
PathSegment::Field(f) => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(f);
let is_optional = optional_fields.contains(&path_so_far);
out.push_str(nav);
out.push_str(&kotlin_getter(f));
out.push_str("()");
prev_was_nullable = prev_was_nullable || is_optional;
}
PathSegment::ArrayField { name, index } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(name);
let is_optional = optional_fields.contains(&path_so_far);
out.push_str(nav);
out.push_str(&kotlin_getter(name));
let safe = if prev_was_nullable || is_optional { "?" } else { "" };
if *index == 0 {
out.push_str(&format!("(){safe}.first()"));
} else {
out.push_str(&format!("(){safe}.get({index})"));
}
path_so_far.push_str("[0]");
prev_was_nullable = prev_was_nullable || is_optional;
}
PathSegment::MapAccess { field, key } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(field);
let is_optional = optional_fields.contains(&path_so_far);
out.push_str(nav);
out.push_str(&kotlin_getter(field));
let is_numeric = !key.is_empty() && key.chars().all(|c| c.is_ascii_digit());
if is_numeric {
if prev_was_nullable || is_optional {
out.push_str(&format!("()?.get({key})"));
} else {
out.push_str(&format!("().get({key})"));
}
} else if prev_was_nullable || is_optional {
out.push_str(&format!("()?.get(\"{key}\")"));
} else {
out.push_str(&format!("().get(\"{key}\")"));
}
prev_was_nullable = prev_was_nullable || is_optional;
}
PathSegment::Length => {
let size_nav = if prev_was_nullable { "?" } else { "" };
out.push_str(&format!("{size_nav}.size"));
prev_was_nullable = false;
}
}
}
out
}
pub(super) fn render_kotlin_android_with_optionals(
segments: &[PathSegment],
result_var: &str,
optional_fields: &HashSet<String>,
) -> String {
let mut out = result_var.to_string();
let mut path_so_far = String::new();
let mut prev_was_nullable = false;
for seg in segments {
let nav = if prev_was_nullable { "?." } else { "." };
match seg {
PathSegment::Field(f) => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(f);
let is_optional = optional_fields.contains(&path_so_far);
out.push_str(nav);
out.push_str(&kotlin_getter(f));
prev_was_nullable = prev_was_nullable || is_optional;
}
PathSegment::ArrayField { name, index } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(name);
let is_optional = optional_fields.contains(&path_so_far);
out.push_str(nav);
out.push_str(&kotlin_getter(name));
let safe = if prev_was_nullable || is_optional { "?" } else { "" };
if *index == 0 {
out.push_str(&format!("{safe}.first()"));
} else {
out.push_str(&format!("{safe}.get({index})"));
}
path_so_far.push_str("[0]");
prev_was_nullable = prev_was_nullable || is_optional;
}
PathSegment::MapAccess { field, key } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(field);
let is_optional = optional_fields.contains(&path_so_far);
out.push_str(nav);
out.push_str(&kotlin_getter(field));
let is_numeric = !key.is_empty() && key.chars().all(|c| c.is_ascii_digit());
if is_numeric {
if prev_was_nullable || is_optional {
out.push_str(&format!("?.get({key})"));
} else {
out.push_str(&format!(".get({key})"));
}
} else if prev_was_nullable || is_optional {
out.push_str(&format!("?.get(\"{key}\")"));
} else {
out.push_str(&format!(".get(\"{key}\")"));
}
prev_was_nullable = prev_was_nullable || is_optional;
}
PathSegment::Length => {
let size_nav = if prev_was_nullable { "?" } else { "" };
out.push_str(&format!("{size_nav}.size"));
prev_was_nullable = false;
}
}
}
out
}
pub(super) fn render_kotlin_android(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('.');
out.push_str(&kotlin_getter(f));
}
PathSegment::ArrayField { name, index } => {
out.push('.');
out.push_str(&kotlin_getter(name));
if *index == 0 {
out.push_str(".first()");
} else {
out.push_str(&format!(".get({index})"));
}
}
PathSegment::MapAccess { field, key } => {
out.push('.');
out.push_str(&kotlin_getter(field));
let is_numeric = !key.is_empty() && key.chars().all(|c| c.is_ascii_digit());
if is_numeric {
out.push_str(&format!(".get({key})"));
} else {
out.push_str(&format!(".get(\"{key}\")"));
}
}
PathSegment::Length => {
out.push_str(".size");
}
}
}
out
}
pub(super) fn render_rust_with_optionals(
segments: &[PathSegment],
result_var: &str,
optional_fields: &HashSet<String>,
method_calls: &HashSet<String>,
result_fields: &HashSet<String>,
) -> String {
let mut out = result_var.to_string();
let mut path_so_far = String::new();
for (i, seg) in segments.iter().enumerate() {
let is_leaf = i == segments.len() - 1;
match seg {
PathSegment::Field(f) => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(f);
out.push('.');
out.push_str(&f.to_snake_case());
let is_method = method_calls.contains(&path_so_far) && !result_fields.contains(&path_so_far);
if is_method {
out.push_str("()");
if !is_leaf && optional_fields.contains(&path_so_far) {
out.push_str(".as_ref().unwrap()");
}
} else if !is_leaf && optional_fields.contains(&path_so_far) {
out.push_str(".as_ref().unwrap()");
}
}
PathSegment::ArrayField { name, index } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(name);
out.push('.');
out.push_str(&name.to_snake_case());
let path_with_idx = format!("{path_so_far}[0]");
let is_opt = optional_fields.contains(&path_so_far) || optional_fields.contains(path_with_idx.as_str());
if is_opt {
out.push_str(&format!(".as_ref().unwrap()[{index}]"));
} else {
out.push_str(&format!("[{index}]"));
}
path_so_far.push_str("[0]");
}
PathSegment::MapAccess { field, key } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(field);
out.push('.');
out.push_str(&field.to_snake_case());
if key.chars().all(|c| c.is_ascii_digit()) {
let path_with_idx = format!("{path_so_far}[0]");
let is_opt =
optional_fields.contains(&path_so_far) || optional_fields.contains(path_with_idx.as_str());
if is_opt {
out.push_str(&format!(".as_ref().unwrap()[{key}]"));
} else {
out.push_str(&format!("[{key}]"));
}
path_so_far.push_str("[0]");
} else {
out.push_str(&format!(".get(\"{key}\").map(|s| s.as_str())"));
}
}
PathSegment::Length => {
out.push_str(".len()");
}
}
}
out
}
pub(super) fn render_zig_with_optionals(
segments: &[PathSegment],
result_var: &str,
optional_fields: &HashSet<String>,
method_calls: &HashSet<String>,
) -> String {
let mut out = result_var.to_string();
let mut path_so_far = String::new();
for seg in segments {
match seg {
PathSegment::Field(f) => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(f);
out.push('.');
out.push_str(f);
if !method_calls.contains(&path_so_far) && optional_fields.contains(&path_so_far) {
out.push_str(".?");
}
}
PathSegment::ArrayField { name, index } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(name);
out.push('.');
out.push_str(name);
if !method_calls.contains(&path_so_far) && optional_fields.contains(&path_so_far) {
out.push_str(".?");
}
out.push_str(&format!("[{index}]"));
}
PathSegment::MapAccess { field, key } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(field);
out.push('.');
out.push_str(field);
if !method_calls.contains(&path_so_far) && optional_fields.contains(&path_so_far) {
out.push_str(".?");
}
if key.chars().all(|c| c.is_ascii_digit()) {
out.push_str(&format!("[{key}]"));
} else {
out.push_str(&format!(".get(\"{key}\")"));
}
}
PathSegment::Length => {
out.push_str(".len");
}
}
}
out
}
pub(super) fn render_pascal_dot(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('.');
out.push_str(&f.to_pascal_case());
}
PathSegment::ArrayField { name, index } => {
out.push('.');
out.push_str(&name.to_pascal_case());
out.push_str(&format!("[{index}]"));
}
PathSegment::MapAccess { field, key } => {
out.push('.');
out.push_str(&field.to_pascal_case());
if key.chars().all(|c| c.is_ascii_digit()) {
out.push_str(&format!("[{key}]"));
} else {
out.push_str(&format!("[\"{key}\"]"));
}
}
PathSegment::Length => {
out.push_str(".Count");
}
}
}
out
}
pub(super) fn render_csharp_with_optionals(
segments: &[PathSegment],
result_var: &str,
optional_fields: &HashSet<String>,
) -> String {
let mut out = result_var.to_string();
let mut path_so_far = String::new();
for (i, seg) in segments.iter().enumerate() {
let is_leaf = i == segments.len() - 1;
match seg {
PathSegment::Field(f) => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(f);
out.push('.');
out.push_str(&f.to_pascal_case());
if !is_leaf && optional_fields.contains(&path_so_far) {
out.push('!');
}
}
PathSegment::ArrayField { name, index } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(name);
out.push('.');
out.push_str(&name.to_pascal_case());
out.push_str(&format!("[{index}]"));
}
PathSegment::MapAccess { field, key } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(field);
out.push('.');
out.push_str(&field.to_pascal_case());
if key.chars().all(|c| c.is_ascii_digit()) {
out.push_str(&format!("[{key}]"));
} else {
out.push_str(&format!("[\"{key}\"]"));
}
}
PathSegment::Length => {
out.push_str(".Count");
}
}
}
out
}
pub(super) fn render_php(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push_str("->");
out.push_str(&f.to_lower_camel_case());
}
PathSegment::ArrayField { name, index } => {
out.push_str("->");
out.push_str(&name.to_lower_camel_case());
out.push_str(&format!("[{index}]"));
}
PathSegment::MapAccess { field, key } => {
out.push_str("->");
out.push_str(&field.to_lower_camel_case());
out.push_str(&format!("[\"{key}\"]"));
}
PathSegment::Length => {
let current = std::mem::take(&mut out);
out = format!("count({current})");
}
}
}
out
}
pub(super) fn render_php_with_getters(segments: &[PathSegment], result_var: &str, getter_map: &PhpGetterMap) -> String {
let mut out = result_var.to_string();
let mut current_type: Option<String> = getter_map.root_type.clone();
for seg in segments {
match seg {
PathSegment::Field(f) => {
let camel = f.to_lower_camel_case();
if getter_map.needs_getter(current_type.as_deref(), f.as_str()) {
let getter = format!("get{}", camel.as_str()[..1].to_uppercase() + &camel[1..]);
out.push_str("->");
out.push_str(&getter);
out.push_str("()");
} else {
out.push_str("->");
out.push_str(&camel);
}
current_type = getter_map.advance(current_type.as_deref(), f.as_str());
}
PathSegment::ArrayField { name, index } => {
let camel = name.to_lower_camel_case();
if getter_map.needs_getter(current_type.as_deref(), name.as_str()) {
let getter = format!("get{}", camel.as_str()[..1].to_uppercase() + &camel[1..]);
out.push_str("->");
out.push_str(&getter);
out.push_str("()");
} else {
out.push_str("->");
out.push_str(&camel);
}
out.push_str(&format!("[{index}]"));
current_type = getter_map.advance(current_type.as_deref(), name.as_str());
}
PathSegment::MapAccess { field, key } => {
let camel = field.to_lower_camel_case();
if getter_map.needs_getter(current_type.as_deref(), field.as_str()) {
let getter = format!("get{}", camel.as_str()[..1].to_uppercase() + &camel[1..]);
out.push_str("->");
out.push_str(&getter);
out.push_str("()");
} else {
out.push_str("->");
out.push_str(&camel);
}
out.push_str(&format!("[\"{key}\"]"));
current_type = getter_map.advance(current_type.as_deref(), field.as_str());
}
PathSegment::Length => {
let current = std::mem::take(&mut out);
out = format!("count({current})");
}
}
}
out
}
pub(super) fn render_r(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('$');
out.push_str(f);
}
PathSegment::ArrayField { name, index } => {
out.push('$');
out.push_str(name);
out.push_str(&format!("[[{}]]", index + 1));
}
PathSegment::MapAccess { field, key } => {
out.push('$');
out.push_str(field);
out.push_str(&format!("[[\"{key}\"]]"));
}
PathSegment::Length => {
let current = std::mem::take(&mut out);
out = format!("length({current})");
}
}
}
out
}
pub(super) fn render_c(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
let snake = f.to_snake_case();
let current = std::mem::take(&mut out);
out = format!("result_{snake}({current})");
}
PathSegment::ArrayField { name, index } => {
let snake = name.to_snake_case();
let current = std::mem::take(&mut out);
out = format!("result_{snake}({current})[{index}]");
}
PathSegment::MapAccess { field, key } => {
let snake = field.to_snake_case();
let current = std::mem::take(&mut out);
out = format!("result_{snake}({current})[\"{key}\"]");
}
PathSegment::Length => {
let current = std::mem::take(&mut out);
out = format!("result_{current}_count()");
}
}
}
out
}
pub(super) fn render_dart(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('.');
out.push_str(&f.to_lower_camel_case());
}
PathSegment::ArrayField { name, index } => {
out.push('.');
out.push_str(&name.to_lower_camel_case());
out.push_str(&format!("[{index}]"));
}
PathSegment::MapAccess { field, key } => {
out.push('.');
out.push_str(&field.to_lower_camel_case());
if key.chars().all(|c| c.is_ascii_digit()) {
out.push_str(&format!("[{key}]"));
} else {
out.push_str(&format!("[\"{key}\"]"));
}
}
PathSegment::Length => {
out.push_str(".length");
}
}
}
out
}
pub(super) fn render_dart_with_optionals(
segments: &[PathSegment],
result_var: &str,
optional_fields: &HashSet<String>,
) -> String {
let mut out = result_var.to_string();
let mut path_so_far = String::new();
let mut path_with_indices = String::new();
let mut prev_was_nullable = false;
let is_optional =
|bare: &str, indexed: &str| -> bool { optional_fields.contains(bare) || optional_fields.contains(indexed) };
for seg in segments {
let nav = if prev_was_nullable { "?." } else { "." };
match seg {
PathSegment::Field(f) => {
if !path_so_far.is_empty() {
path_so_far.push('.');
path_with_indices.push('.');
}
path_so_far.push_str(f);
path_with_indices.push_str(f);
let optional = is_optional(&path_so_far, &path_with_indices);
out.push_str(nav);
out.push_str(&f.to_lower_camel_case());
prev_was_nullable = optional;
}
PathSegment::ArrayField { name, index } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
path_with_indices.push('.');
}
path_so_far.push_str(name);
path_with_indices.push_str(name);
let optional = is_optional(&path_so_far, &path_with_indices);
out.push_str(nav);
out.push_str(&name.to_lower_camel_case());
if optional {
out.push('!');
}
out.push_str(&format!("[{index}]"));
path_with_indices.push_str(&format!("[{index}]"));
prev_was_nullable = false;
}
PathSegment::MapAccess { field, key } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
path_with_indices.push('.');
}
path_so_far.push_str(field);
path_with_indices.push_str(field);
let optional = is_optional(&path_so_far, &path_with_indices);
out.push_str(nav);
out.push_str(&field.to_lower_camel_case());
if key.chars().all(|c| c.is_ascii_digit()) {
out.push_str(&format!("[{key}]"));
path_with_indices.push_str(&format!("[{key}]"));
} else {
out.push_str(&format!("[\"{key}\"]"));
path_with_indices.push_str(&format!("[\"{key}\"]"));
}
prev_was_nullable = optional;
}
PathSegment::Length => {
out.push_str(nav);
out.push_str("length");
prev_was_nullable = false;
}
}
}
out
}