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