use std::collections::HashSet;
use super::types::FieldResolver;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LeafAnchor {
Direct,
Prefixed(String),
}
impl FieldResolver {
pub fn anchor_leaf(&self, leaf: &str) -> Option<LeafAnchor> {
match self.result_field_oracle_knows(leaf) {
None | Some(true) => Some(LeafAnchor::Direct),
Some(false) => self.anchor_leaf_via_result_fields(leaf, &self.result_fields),
}
}
fn anchor_leaf_via_result_fields(&self, leaf: &str, result_fields: &HashSet<String>) -> Option<LeafAnchor> {
result_fields.iter().find_map(|prefix| {
let prefix_path = if self.is_collection_root(prefix) {
format!("{prefix}[0]")
} else {
prefix.clone()
};
let candidate = format!("{prefix_path}.{leaf}");
(self.result_field_oracle_knows(&candidate) == Some(true)).then_some(LeafAnchor::Prefixed(prefix_path))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ir::{FieldDef, TypeDef, TypeRef};
use std::collections::HashMap;
fn envelope_document_metadata_type_defs() -> Vec<TypeDef> {
vec![
TypeDef {
name: "Envelope".to_string(),
fields: vec![FieldDef {
name: "results".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::Named("Document".to_string()))),
..FieldDef::default()
}],
..TypeDef::default()
},
TypeDef {
name: "Document".to_string(),
fields: vec![
FieldDef {
name: "chunks".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::Named("Chunk".to_string()))),
..FieldDef::default()
},
FieldDef {
name: "metadata".to_string(),
ty: TypeRef::Named("Metadata".to_string()),
..FieldDef::default()
},
],
..TypeDef::default()
},
TypeDef {
name: "Metadata".to_string(),
fields: vec![FieldDef {
name: "output_format".to_string(),
ty: TypeRef::String,
..FieldDef::default()
}],
..TypeDef::default()
},
]
}
fn envelope_resolver(result_fields: &[&str]) -> FieldResolver {
let type_defs = envelope_document_metadata_type_defs();
let result_field_map = FieldResolver::ir_result_field_facts(&type_defs, "rust");
let collection_map = FieldResolver::ir_collection_fields(&type_defs);
let (reachable, excluded, optional) = FieldResolver::ir_field_sets(&type_defs);
let result_fields: HashSet<String> = result_fields.iter().map(|s| s.to_string()).collect();
FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&result_fields,
&HashSet::new(),
&HashSet::new(),
)
.with_ir_result_fields(result_field_map, Some("Envelope".to_string()))
.with_ir_collection_map(collection_map, Some("Envelope".to_string()))
.with_ir_fields(reachable, excluded, optional)
}
#[test]
fn envelope_prefix_reaches_a_leaf_declared_on_the_nested_type() {
let resolver = envelope_resolver(&["results"]);
assert_eq!(
resolver.anchor_leaf("chunks"),
Some(LeafAnchor::Prefixed("results[0]".to_string()))
);
}
#[test]
fn envelope_prefix_reaches_a_leaf_nested_two_hops_deep() {
let resolver = envelope_resolver(&["results"]);
assert_eq!(
resolver.anchor_leaf("metadata.output_format"),
Some(LeafAnchor::Prefixed("results[0]".to_string()))
);
}
#[test]
fn root_declaring_the_leaf_directly_stays_direct() {
let resolver = envelope_resolver(&["results"]);
assert_eq!(resolver.anchor_leaf("results"), Some(LeafAnchor::Direct));
}
#[test]
fn genuinely_unreachable_leaf_is_still_refused() {
let resolver = envelope_resolver(&["results"]);
assert_eq!(resolver.anchor_leaf("not_a_real_field"), None);
}
#[test]
fn a_result_fields_entry_that_does_not_reach_the_leaf_is_not_used() {
let resolver = envelope_resolver(&["results", "unrelated"]);
assert_eq!(
resolver.anchor_leaf("chunks"),
Some(LeafAnchor::Prefixed("results[0]".to_string()))
);
}
#[test]
fn no_anchored_root_type_keeps_the_permissive_default() {
let type_defs = envelope_document_metadata_type_defs();
let result_field_map = FieldResolver::ir_result_field_facts(&type_defs, "rust");
let (reachable, excluded, optional) = FieldResolver::ir_field_sets(&type_defs);
let resolver = FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
.with_ir_result_fields(result_field_map, None)
.with_ir_fields(reachable, excluded, optional);
assert_eq!(resolver.anchor_leaf("chunks"), Some(LeafAnchor::Direct));
}
}