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
pub use ;
/// Parses raw bytes into a [`LocatedValue`] tree for one format.
///
/// Implement this to add a new configuration format. This is the second pipeline stage: it turns
/// the raw bytes a loader produced into a typed, source-located value tree for merging.
///
/// # Contract
///
/// - [`parse`](Parse::parse) returns one [`LocatedValue`] tree per payload. `source` is the
/// source kind (e.g. `"file"`) and `resource` the path/identifier the bytes came from.
/// - Every node in the tree — including the root — should carry a [`tanzim_value::Location`] that
/// points back to the source, resource, and line/column, so downstream error messages can show
/// users exactly where a bad value came from. Use [`tanzim_value::Location::at`] to build them.
/// - [`supported_format_list`](Parse::supported_format_list) may return several extensions
/// for one parser (e.g. `["yml", "yaml"]`). When a payload carries no format hint, selection
/// instead falls back to probing — see Auto-detection below.
///
/// # Auto-detection
///
/// When a payload's `format` hint is `None`, the parse stage calls
/// [`is_format_supported`][Parse::is_format_supported] on each registered
/// parser in order. Return `Some(true)` if confident, `Some(false)` to skip, or `None`
/// if unsure (another parser may then claim the bytes).
///
/// # Choosing an error
///
/// Failures are reported with [`tanzim_value::Error`]; every variant except `Parse` carries a
/// [`Location`](tanzim_value::Location):
///
/// - [`Error::InvalidUtf8`] — the bytes aren't valid UTF-8.
/// - [`Error::Parse`] — a syntax or structural error; set `location` when you can pinpoint it,
/// otherwise `None`.
/// - [`Error::UnsupportedNull`] — the input contained a null the config model doesn't represent.
/// - [`Error::UnsupportedType`] — a value of a type that has no configuration representation
/// (e.g. a date-time).
///
/// # Registering
///
/// Pass an instance to `tanzim::Config::with_parser`. The pipeline picks a parser by the payload's
/// format hint when present, otherwise it probes each parser with
/// [`is_format_supported`](Parse::is_format_supported). For a one-off parser you don't want
/// to define a type for, use [`closure::Closure`] instead of implementing this trait.
///
/// # Example — custom CSV parser
///
/// ```rust
/// use tanzim_parse::{Parse, Error, LocatedValue, Value};
/// use tanzim_value::{Location, Map};
///
/// struct CsvParser;
///
/// impl Parse for CsvParser {
/// fn name(&self) -> &str { "csv" }
/// fn supported_format_list(&self) -> Vec<String> { vec!["csv".into()] }
/// fn is_format_supported(&self, bytes: &[u8]) -> Option<bool> {
/// Some(bytes.contains(&b','))
/// }
/// fn parse(&self, source: &str, resource: &str, bytes: &[u8])
/// -> Result<LocatedValue, Error>
/// {
/// let text = std::str::from_utf8(bytes).map_err(|_| Error::InvalidUtf8 {
/// location: Location::at(source, resource, None, None, None),
/// })?;
/// let mut map = Map::new();
/// for (line_idx, line) in text.lines().enumerate() {
/// if let Some((key, val)) = line.split_once(',') {
/// let loc = Location::at(source, resource, Some(line_idx + 1), None, None);
/// map.insert(key.trim().to_string(), LocatedValue {
/// value: Value::String(val.trim().to_string()),
/// location: loc,
/// });
/// }
/// }
/// let root_loc = Location::at(source, resource, None, None, None);
/// Ok(LocatedValue { value: Value::Map(map), location: root_loc })
/// }
/// }
///
/// let value = CsvParser
/// .parse("file", "config.csv", b"host,127.0.0.1\nport,8080\n")
/// .unwrap();
///
/// let map = value.value.as_map().unwrap();
/// assert_eq!(map.get("host").unwrap().value.as_string().unwrap(), "127.0.0.1");
/// assert_eq!(map.get("port").unwrap().value.as_string().unwrap(), "8080");
/// // `port` is a string — this parser stores every field verbatim.
/// ```