use std::collections::{HashMap, HashSet};
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::Fixture;
pub(super) fn resolve_function_name(e2e_config: &E2eConfig) -> String {
resolve_function_name_for_call(&e2e_config.call)
}
pub(super) fn resolve_function_name_for_call(call_config: &crate::e2e::config::CallConfig) -> String {
call_config
.overrides
.get("python")
.and_then(|o| o.function.clone())
.unwrap_or_else(|| call_config.function.clone())
}
pub(super) fn resolve_module(e2e_config: &E2eConfig) -> String {
e2e_config
.call
.overrides
.get("python")
.and_then(|o| o.module.clone())
.unwrap_or_else(|| e2e_config.call.module.replace('-', "_"))
}
pub(super) fn resolve_options_type(e2e_config: &E2eConfig) -> Option<String> {
e2e_config
.call
.overrides
.get("python")
.and_then(|o| o.options_type.clone())
}
pub(super) fn resolve_client_factory(e2e_config: &E2eConfig) -> Option<String> {
e2e_config
.call
.overrides
.get("python")
.and_then(|o| o.client_factory.clone())
}
pub(super) fn resolve_options_via(e2e_config: &E2eConfig) -> &str {
e2e_config
.call
.overrides
.get("python")
.and_then(|o| o.options_via.as_deref())
.unwrap_or("kwargs")
}
pub(super) fn core_to_binding_convertible_types(
type_defs: &[crate::core::ir::TypeDef],
enums: &[crate::core::ir::EnumDef],
) -> ahash::AHashSet<String> {
let surface = crate::core::ir::ApiSurface {
types: type_defs.to_vec(),
enums: enums.to_vec(),
..Default::default()
};
crate::codegen::conversions::core_to_binding_convertible_types(&surface, &[])
}
pub(super) fn pyo3_would_inject_from_json(
name: &str,
type_defs: &[crate::core::ir::TypeDef],
convertible_types: &ahash::AHashSet<String>,
crate_has_serde: bool,
) -> bool {
type_defs
.iter()
.find(|t| t.name == name)
.is_some_and(|t| crate::codegen::conversions::pyo3_from_json_eligible(t, crate_has_serde, convertible_types))
}
pub(super) fn effective_options_via_for_type<'a>(
options_via: &'a str,
options_type: Option<&str>,
type_defs: &[crate::core::ir::TypeDef],
convertible_types: &ahash::AHashSet<String>,
crate_has_serde: bool,
) -> &'a str {
if options_via != "from_json" {
return options_via;
}
let is_declared = options_type
.is_some_and(|name| pyo3_would_inject_from_json(name, type_defs, convertible_types, crate_has_serde));
if is_declared { options_via } else { "kwargs" }
}
pub(super) fn resolve_enum_fields(e2e_config: &E2eConfig) -> &HashMap<String, String> {
static EMPTY: std::sync::LazyLock<HashMap<String, String>> = std::sync::LazyLock::new(HashMap::new);
e2e_config
.call
.overrides
.get("python")
.map(|o| &o.enum_fields)
.unwrap_or(&EMPTY)
}
pub(super) fn resolve_assert_enum_fields(call_config: &crate::e2e::config::CallConfig) -> &HashMap<String, String> {
static EMPTY: std::sync::LazyLock<HashMap<String, String>> = std::sync::LazyLock::new(HashMap::new);
call_config
.overrides
.get("python")
.map(|o| &o.assert_enum_fields)
.unwrap_or(&EMPTY)
}
pub(super) fn resolve_handle_nested_types(e2e_config: &E2eConfig) -> &HashMap<String, String> {
static EMPTY: std::sync::LazyLock<HashMap<String, String>> = std::sync::LazyLock::new(HashMap::new);
e2e_config
.call
.overrides
.get("python")
.map(|o| &o.handle_nested_types)
.unwrap_or(&EMPTY)
}
pub(super) fn resolve_handle_dict_types(e2e_config: &E2eConfig) -> &HashSet<String> {
static EMPTY: std::sync::LazyLock<HashSet<String>> = std::sync::LazyLock::new(HashSet::new);
e2e_config
.call
.overrides
.get("python")
.map(|o| &o.handle_dict_types)
.unwrap_or(&EMPTY)
}
pub(super) fn is_skipped(fixture: &Fixture, language: &str) -> bool {
fixture.skip.as_ref().is_some_and(|s| s.should_skip(language))
}
pub(super) enum BytesKind {
FilePath,
InlineText,
Base64,
}
pub(super) fn classify_bytes_value(s: &str) -> BytesKind {
if s.starts_with('<') || s.starts_with('{') || s.starts_with('[') || s.contains(' ') {
return BytesKind::InlineText;
}
let first = s.chars().next().unwrap_or('\0');
if (first.is_ascii_alphanumeric() || first == '_')
&& let Some(slash_pos) = s.find('/')
&& slash_pos > 0
{
let after_slash = &s[slash_pos + 1..];
if after_slash.contains('.') && !after_slash.is_empty() {
return BytesKind::FilePath;
}
}
BytesKind::Base64
}
pub(super) fn python_method_helper_import(method_name: &str) -> Option<String> {
match method_name {
"has_error_nodes" => Some("tree_has_error_nodes".to_string()),
"error_count" | "tree_error_count" => Some("tree_error_count".to_string()),
"tree_to_sexp" => Some("tree_to_sexp".to_string()),
"contains_node_type" => Some("tree_contains_node_type".to_string()),
"find_nodes_by_type" => Some("find_nodes_by_type".to_string()),
"run_query" => Some("run_query".to_string()),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_bytes_value_html_is_inline() {
matches!(classify_bytes_value("<!DOCTYPE html>"), BytesKind::InlineText);
}
#[test]
fn classify_bytes_value_pdf_path_is_file_path() {
matches!(classify_bytes_value("pdf/fake_memo.pdf"), BytesKind::FilePath);
}
#[test]
fn classify_bytes_value_base64_is_base64() {
matches!(classify_bytes_value("/9j/4AAQSkZJRgABAQEASABIAAD"), BytesKind::Base64);
}
#[test]
fn pyo3_would_inject_from_json_true_when_all_three_conditions_hold() {
let type_defs = vec![crate::core::ir::TypeDef {
name: "WidgetRequest".to_string(),
has_serde: true,
..Default::default()
}];
let convertible = core_to_binding_convertible_types(&type_defs, &[]);
assert!(
convertible.contains("WidgetRequest"),
"test setup: a plain fieldless type must be convertible"
);
assert!(pyo3_would_inject_from_json(
"WidgetRequest",
&type_defs,
&convertible,
true
));
}
#[test]
fn pyo3_would_inject_from_json_false_when_type_lacks_serde() {
let type_defs = vec![crate::core::ir::TypeDef {
name: "WidgetRequest".to_string(),
has_serde: false,
..Default::default()
}];
let convertible = core_to_binding_convertible_types(&type_defs, &[]);
assert!(!pyo3_would_inject_from_json(
"WidgetRequest",
&type_defs,
&convertible,
true
));
}
#[test]
fn pyo3_would_inject_from_json_false_when_type_has_serde_but_is_not_convertible() {
use crate::core::ir::{FieldDef, TypeDef, TypeRef};
let type_defs = vec![TypeDef {
name: "WidgetRequest".to_string(),
has_serde: true,
fields: vec![FieldDef {
name: "extra".to_string(),
ty: TypeRef::Named("UnresolvedExternalType".to_string()),
..Default::default()
}],
..Default::default()
}];
let convertible = core_to_binding_convertible_types(&type_defs, &[]);
assert!(
!convertible.contains("WidgetRequest"),
"test setup: an unresolved field type must fail convertibility"
);
assert!(!pyo3_would_inject_from_json(
"WidgetRequest",
&type_defs,
&convertible,
true
));
}
#[test]
fn pyo3_would_inject_from_json_false_when_crate_lacks_serde() {
let type_defs = vec![crate::core::ir::TypeDef {
name: "WidgetRequest".to_string(),
has_serde: true,
..Default::default()
}];
let convertible = core_to_binding_convertible_types(&type_defs, &[]);
assert!(
convertible.contains("WidgetRequest"),
"test setup: a plain fieldless type must be convertible"
);
assert!(!pyo3_would_inject_from_json(
"WidgetRequest",
&type_defs,
&convertible,
false
));
}
#[test]
fn effective_options_via_for_type_keeps_from_json_when_pyo3_would_inject_it() {
let type_defs = vec![crate::core::ir::TypeDef {
name: "WidgetRequest".to_string(),
has_serde: true,
..Default::default()
}];
let convertible = core_to_binding_convertible_types(&type_defs, &[]);
assert!(pyo3_would_inject_from_json(
"WidgetRequest",
&type_defs,
&convertible,
true
));
assert_eq!(
effective_options_via_for_type("from_json", Some("WidgetRequest"), &type_defs, &convertible, true),
"from_json"
);
}
#[test]
fn effective_options_via_for_type_downgrades_to_kwargs_when_type_lacks_serde() {
let type_defs = vec![crate::core::ir::TypeDef {
name: "WidgetRequest".to_string(),
has_serde: false,
..Default::default()
}];
let convertible = core_to_binding_convertible_types(&type_defs, &[]);
assert_eq!(
effective_options_via_for_type("from_json", Some("WidgetRequest"), &type_defs, &convertible, true),
"kwargs"
);
}
#[test]
fn effective_options_via_for_type_downgrades_to_kwargs_when_crate_lacks_serde() {
let type_defs = vec![crate::core::ir::TypeDef {
name: "WidgetRequest".to_string(),
has_serde: true,
..Default::default()
}];
let convertible = core_to_binding_convertible_types(&type_defs, &[]);
assert_eq!(
effective_options_via_for_type("from_json", Some("WidgetRequest"), &type_defs, &convertible, false),
"kwargs"
);
}
#[test]
fn effective_options_via_for_type_downgrades_to_kwargs_when_type_is_unknown() {
let convertible = core_to_binding_convertible_types(&[], &[]);
assert_eq!(
effective_options_via_for_type("from_json", Some("WidgetRequest"), &[], &convertible, true),
"kwargs"
);
}
#[test]
fn effective_options_via_for_type_leaves_non_from_json_values_untouched() {
let convertible = core_to_binding_convertible_types(&[], &[]);
assert_eq!(
effective_options_via_for_type("dict", None, &[], &convertible, true),
"dict"
);
assert_eq!(
effective_options_via_for_type("kwargs", None, &[], &convertible, true),
"kwargs"
);
}
#[test]
fn python_method_helper_import_recognizes_has_error_nodes() {
assert_eq!(
python_method_helper_import("has_error_nodes"),
Some("tree_has_error_nodes".to_string())
);
}
#[test]
fn python_method_helper_import_returns_none_for_plain_method() {
assert!(python_method_helper_import("root_child_count").is_none());
}
}