Skip to main content

dlin_core/parser/
mod.rs

1pub mod cache;
2pub mod columns;
3pub mod discovery;
4pub mod jinja;
5pub mod manifest;
6pub mod project;
7pub mod sql;
8#[allow(dead_code)]
9pub mod yaml_schema;
10
11use anyhow::Result;
12use serde::de::DeserializeOwned;
13
14/// Parse YAML content tolerating duplicate mapping keys (last value wins).
15///
16/// If duplicate keys are detected, a warning is emitted (suppressible via `--quiet`)
17/// and the content is re-parsed with `DuplicateKeyPolicy::LastWins` to match
18/// PyYAML's behavior. The result is deserialized via `serde_json::Value` as an
19/// intermediate to avoid serde's struct-level duplicate field rejection.
20pub fn yaml_from_str<T: DeserializeOwned>(content: &str, location: &str) -> Result<T> {
21    let value: serde_json::Value = match serde_saphyr::from_str(content) {
22        Ok(v) => v,
23        Err(e) => {
24            if is_duplicate_key_error(&e) {
25                let key = extract_duplicate_key(&e).unwrap_or("unknown");
26                crate::warn!(
27                    "duplicate YAML key '{}' in {} (using last value)",
28                    key,
29                    location,
30                );
31                let options = serde_saphyr::options! {
32                    duplicate_keys: serde_saphyr::options::DuplicateKeyPolicy::LastWins
33                };
34                serde_saphyr::from_str_with_options(content, options)?
35            } else {
36                return Err(e.into());
37            }
38        }
39    };
40    if value.is_null() {
41        // Empty YAML documents deserialize as null; use Default for the target type.
42        return Ok(serde_json::from_value(serde_json::Value::Object(
43            Default::default(),
44        ))?);
45    }
46    Ok(serde_json::from_value(value)?)
47}
48
49/// Check whether a `serde_saphyr::Error` is a duplicate mapping key error.
50fn is_duplicate_key_error(e: &serde_saphyr::Error) -> bool {
51    match e {
52        serde_saphyr::Error::DuplicateMappingKey { .. } => true,
53        serde_saphyr::Error::WithSnippet { error, .. } => is_duplicate_key_error(error),
54        _ => false,
55    }
56}
57
58/// Extract the duplicate key name from a `serde_saphyr::Error`, if available.
59fn extract_duplicate_key(e: &serde_saphyr::Error) -> Option<&str> {
60    match e {
61        serde_saphyr::Error::DuplicateMappingKey { key, .. } => key.as_deref(),
62        serde_saphyr::Error::WithSnippet { error, .. } => extract_duplicate_key(error),
63        _ => None,
64    }
65}