1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use super::*;
use crate::rust::fmt::*;
use crate::rust::format;
use crate::rust::prelude::*;
use crate::traversal::*;
use crate::*;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FullLocation<'s, E: CustomExtension> {
    pub start_offset: usize,
    pub end_offset: usize,
    pub ancestor_path: Vec<(ContainerState<E::CustomTraversal>, ContainerType<'s>)>,
    pub current_value_info: Option<CurrentValueInfo<E>>,
}

impl<'s, E: CustomExtension> FullLocation<'s, E> {
    /// This enables a full path to be provided in an error message, which can have a debug such as:
    /// EG: `MyStruct.hello[0]->MyEnum::Option2{1}.inner[0]->MyEnum::Option1{0}.[0]->Map[0].Value->Array[0]->Tuple.[0]->Enum::{6}.[0]->Tuple.[1]->Map[0].Key`
    ///
    /// As much information is extracted from the Type as possible, falling back to data from the value model
    /// if the Type is Any.
    pub fn path_to_string(&self, schema: &Schema<E::CustomSchema>) -> String {
        let mut buf = String::new();
        let mut is_first = true;
        for (container_state, container_type) in self.ancestor_path.iter() {
            if is_first {
                is_first = false;
            } else {
                write!(buf, "->").unwrap();
            }
            let type_id = container_type.self_type();
            let metadata = schema.resolve_type_metadata(type_id);
            let type_name = metadata
                .and_then(|m| m.get_name())
                .unwrap_or_else(|| container_state.container_header.value_kind_name());
            let current_child_index = container_state.current_child_index;
            let header = container_state.container_header;
            match header {
                ContainerHeader::EnumVariant(variant_header) => {
                    let variant_data = metadata.and_then(|v| match &v.child_names {
                        Some(ChildNames::EnumVariants(variants)) => {
                            variants.get(&variant_header.variant)
                        }
                        _ => None,
                    });
                    let variant_part = variant_data
                        .and_then(|d| d.get_name())
                        .map(|variant_name| {
                            format!("::{{{}|{}}}", variant_header.variant, variant_name,)
                        })
                        .unwrap_or_else(|| format!("::{{{}}}", variant_header.variant));
                    let field_part = if let Some(child_index) = current_child_index {
                        variant_data
                            .and_then(|d| match &d.child_names {
                                Some(ChildNames::NamedFields(fields)) => fields.get(child_index),
                                _ => None,
                            })
                            .map(|field_name| format!(".[{}|{}]", child_index, field_name))
                            .unwrap_or_else(|| format!(".[{}]", child_index))
                    } else {
                        format!("")
                    };
                    write!(buf, "{}{}{}", type_name, variant_part, field_part).unwrap();
                }
                ContainerHeader::Tuple(_) => {
                    let field_part = if let Some(child_index) = current_child_index {
                        metadata
                            .and_then(|d| match &d.child_names {
                                Some(ChildNames::NamedFields(fields)) => fields.get(child_index),
                                _ => None,
                            })
                            .map(|field_name| format!(".[{}|{}]", child_index, field_name))
                            .unwrap_or_else(|| format!(".[{}]", child_index))
                    } else {
                        format!("")
                    };
                    write!(buf, "{}{}", type_name, field_part).unwrap();
                }
                ContainerHeader::Array(_) => {
                    let field_part = if let Some(child_index) = current_child_index {
                        format!(".[{}]", child_index)
                    } else {
                        format!("")
                    };
                    write!(buf, "{}{}", type_name, field_part).unwrap();
                }
                ContainerHeader::Map(_) => {
                    let field_part = if let Some(child_index) = current_child_index {
                        let entry_index = child_index / 2;
                        let key_or_value = if child_index % 2 == 0 { "Key" } else { "Value" };
                        format!(".[{}].{}", entry_index, key_or_value)
                    } else {
                        format!("")
                    };

                    write!(buf, "{}{}", type_name, field_part).unwrap();
                }
            }
        }
        if let Some(current_value_info) = &self.current_value_info {
            if !is_first {
                write!(buf, "->").unwrap();
            }
            let type_kind = schema
                .resolve_type_kind(current_value_info.type_id)
                .expect("Type index not found in given schema");
            let metadata = if !matches!(type_kind, TypeKind::Any) {
                schema.resolve_type_metadata(current_value_info.type_id)
            } else {
                None
            };
            let type_name = metadata
                .and_then(|m| m.get_name_string())
                .unwrap_or_else(|| current_value_info.value_kind.to_string());
            if let Some(variant) = current_value_info.variant {
                let variant_data = metadata.and_then(|v| match &v.child_names {
                    Some(ChildNames::EnumVariants(variants)) => variants.get(&variant),
                    _ => None,
                });
                let variant_part = variant_data
                    .and_then(|d| d.get_name())
                    .map(|variant_name| format!("::{{{}|{}}}", variant, variant_name,))
                    .unwrap_or_else(|| format!("::{{{}}}", variant));
                write!(buf, "{}{}", type_name, variant_part).unwrap();
            } else {
                write!(buf, "{}", type_name).unwrap();
            }
        }
        buf
    }
}