use crate::core::ir::{ErrorDef, MethodDef};
use crate::e2e::fixture::Assertion;
pub(crate) fn introspection_method<'a>(
errors: &'a [ErrorDef],
sub_field: &str,
) -> Option<(&'a ErrorDef, &'a MethodDef)> {
errors
.iter()
.find_map(|error| error.methods.iter().find(|m| m.name == sub_field).map(|m| (error, m)))
}
pub(crate) fn resolvable_equals_error_field<'a>(
assertion: &Assertion,
errors: &'a [ErrorDef],
) -> Option<(&'a ErrorDef, &'a MethodDef)> {
if assertion.assertion_type != "equals" {
return None;
}
let sub_field = assertion.field.as_deref()?.strip_prefix("error.")?;
introspection_method(errors, sub_field)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ir::{ErrorVariant, ReceiverKind, TypeRef};
fn method(name: &str) -> MethodDef {
MethodDef {
name: name.to_string(),
params: Vec::new(),
return_type: TypeRef::Primitive(crate::core::ir::PrimitiveType::U16),
is_async: false,
is_static: false,
error_type: None,
doc: String::new(),
receiver: Some(ReceiverKind::Ref),
cfg: None,
sanitized: false,
trait_source: None,
returns_ref: false,
returns_cow: false,
return_newtype_wrapper: None,
has_default_impl: false,
binding_excluded: false,
binding_exclusion_reason: None,
version: Default::default(),
}
}
fn error_with_methods(name: &str, methods: Vec<&str>) -> ErrorDef {
ErrorDef {
name: name.to_string(),
rust_path: format!("demo::{name}"),
original_rust_path: String::new(),
variants: vec![ErrorVariant::default()],
doc: String::new(),
methods: methods.into_iter().map(method).collect(),
binding_excluded: false,
binding_exclusion_reason: None,
version: Default::default(),
}
}
fn equals_assertion(field: &str) -> Assertion {
Assertion {
assertion_type: "equals".to_string(),
field: Some(field.to_string()),
value: Some(serde_json::json!(429)),
..Assertion::default()
}
}
#[test]
fn introspection_method_finds_a_whitelisted_method() {
let errors = vec![error_with_methods("SampleError", vec!["status_code", "is_transient"])];
let (found_error, found_method) = introspection_method(&errors, "status_code").expect("must resolve");
assert_eq!(found_error.name, "SampleError");
assert_eq!(found_method.name, "status_code");
}
#[test]
fn introspection_method_returns_none_for_an_unwhitelisted_field() {
let errors = vec![error_with_methods("SampleError", vec!["status_code"])];
assert!(introspection_method(&errors, "retry_after").is_none());
}
#[test]
fn introspection_method_returns_none_when_no_errors_declared() {
assert!(introspection_method(&[], "status_code").is_none());
}
#[test]
fn introspection_method_first_match_wins_across_multiple_error_types() {
let errors = vec![
error_with_methods("FirstError", vec!["status_code"]),
error_with_methods("SecondError", vec!["status_code"]),
];
let (found_error, _) = introspection_method(&errors, "status_code").expect("must resolve");
assert_eq!(
found_error.name, "FirstError",
"must prefer the first declared error type"
);
}
#[test]
fn resolvable_equals_error_field_matches_equals_on_a_whitelisted_field() {
let errors = vec![error_with_methods("SampleError", vec!["status_code"])];
let assertion = equals_assertion("error.status_code");
let (found_error, found_method) = resolvable_equals_error_field(&assertion, &errors).expect("must resolve");
assert_eq!(found_error.name, "SampleError");
assert_eq!(found_method.name, "status_code");
}
#[test]
fn resolvable_equals_error_field_rejects_a_non_equals_assertion_type() {
let errors = vec![error_with_methods("SampleError", vec!["status_code"])];
let mut assertion = equals_assertion("error.status_code");
assertion.assertion_type = "greater_than".to_string();
assert!(
resolvable_equals_error_field(&assertion, &errors).is_none(),
"only `equals` is rendered through this path today"
);
}
#[test]
fn resolvable_equals_error_field_rejects_a_field_outside_the_error_namespace() {
let errors = vec![error_with_methods("SampleError", vec!["status_code"])];
let assertion = equals_assertion("status_code");
assert!(
resolvable_equals_error_field(&assertion, &errors).is_none(),
"a bare `status_code` field (no `error.` prefix) targets the Ok value, not the error"
);
}
#[test]
fn resolvable_equals_error_field_rejects_an_unwhitelisted_field() {
let errors = vec![error_with_methods("SampleError", vec!["status_code"])];
let assertion = equals_assertion("error.retry_after");
assert!(resolvable_equals_error_field(&assertion, &errors).is_none());
}
#[test]
fn resolvable_equals_error_field_rejects_a_fieldless_assertion() {
let errors = vec![error_with_methods("SampleError", vec!["status_code"])];
let assertion = Assertion {
assertion_type: "equals".to_string(),
field: None,
value: Some(serde_json::json!(429)),
..Assertion::default()
};
assert!(resolvable_equals_error_field(&assertion, &errors).is_none());
}
}