loong-contracts 0.1.2-alpha.1

Internal support crate for Loong: stable shared contracts
Documentation
use std::fmt;
use std::path::PathBuf;

use serde::de::{self, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

#[derive(Clone, PartialEq, Eq, Hash)]
pub enum SecretRef {
    Env { env: String },
    File { file: PathBuf },
    Exec { exec: Vec<String> },
    Inline(String),
}

impl SecretRef {
    pub fn is_configured(&self) -> bool {
        match self {
            Self::Inline(value) => !value.trim().is_empty(),
            Self::Env { .. } | Self::File { .. } | Self::Exec { .. } => true,
        }
    }

    pub fn env_name(&self) -> Option<&str> {
        match self {
            Self::Env { env } => Some(env.as_str()),
            Self::File { .. } | Self::Exec { .. } | Self::Inline(_) => None,
        }
    }

    pub fn explicit_env_name(&self) -> Option<String> {
        match self {
            Self::Env { env } => Some(env.clone()),
            Self::Inline(value) => parse_explicit_env_reference(value.as_str()),
            Self::File { .. } | Self::Exec { .. } => None,
        }
    }

    pub fn inline_value(&self) -> Option<&str> {
        match self {
            Self::Inline(value) => Some(value.as_str()),
            Self::Env { .. } | Self::File { .. } | Self::Exec { .. } => None,
        }
    }

    pub fn inline_literal_value(&self) -> Option<&str> {
        let Self::Inline(value) = self else {
            return None;
        };

        if parse_explicit_env_reference(value.as_str()).is_some() {
            return None;
        }

        let trimmed_value = value.trim();
        if trimmed_value.is_empty() {
            return None;
        }

        Some(trimmed_value)
    }
}

impl fmt::Debug for SecretRef {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Env { env } => formatter
                .debug_struct("SecretRef::Env")
                .field("env", env)
                .finish(),
            Self::File { file } => formatter
                .debug_struct("SecretRef::File")
                .field("file", file)
                .finish(),
            Self::Exec { exec } => formatter
                .debug_struct("SecretRef::Exec")
                .field("exec", exec)
                .finish(),
            Self::Inline(_) => formatter.write_str("SecretRef::Inline(<redacted>)"),
        }
    }
}

impl Serialize for SecretRef {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Self::Inline(value) => serializer.serialize_str(value.as_str()),
            Self::Env { env } => {
                let mut map = serializer.serialize_map(Some(1))?;
                map.serialize_entry("env", env)?;
                map.end()
            }
            Self::File { file } => {
                let mut map = serializer.serialize_map(Some(1))?;
                map.serialize_entry("file", file)?;
                map.end()
            }
            Self::Exec { exec } => {
                let mut map = serializer.serialize_map(Some(1))?;
                map.serialize_entry("exec", exec)?;
                map.end()
            }
        }
    }
}

impl<'de> Deserialize<'de> for SecretRef {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_any(SecretRefVisitor)
    }
}

struct SecretRefVisitor;

impl<'de> Visitor<'de> for SecretRefVisitor {
    type Value = SecretRef;

    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("a string or a table with exactly one of `env`, `file`, or `exec`")
    }

    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(classify_string_secret_ref(value))
    }

    fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(classify_owned_string_secret_ref(value))
    }

    fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
    where
        A: MapAccess<'de>,
    {
        let table = SecretRefTable::deserialize(de::value::MapAccessDeserializer::new(map))?;

        let env = normalize_non_empty_string(table.env);
        let file = normalize_non_empty_string(table.file);
        let exec = table.exec;

        match (env, file, exec) {
            (Some(env), None, None) => Ok(SecretRef::Env { env }),
            (None, Some(file), None) => {
                let path = PathBuf::from(file);
                Ok(SecretRef::File { file: path })
            }
            (None, None, Some(exec)) => {
                validate_exec_command(exec.as_slice()).map_err(de::Error::custom)?;
                Ok(SecretRef::Exec { exec })
            }
            (None, None, None) => Err(de::Error::custom(
                "secret reference table must contain one of `env`, `file`, or `exec`",
            )),
            _ => Err(de::Error::custom(
                "secret reference table must contain exactly one of `env`, `file`, or `exec`",
            )),
        }
    }
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct SecretRefTable {
    #[serde(default)]
    env: Option<String>,
    #[serde(default)]
    file: Option<String>,
    #[serde(default)]
    exec: Option<Vec<String>>,
}

fn classify_owned_string_secret_ref(value: String) -> SecretRef {
    let explicit_env_name = parse_explicit_env_reference(value.as_str());
    if let Some(env_name) = explicit_env_name {
        return SecretRef::Env { env: env_name };
    }
    SecretRef::Inline(value)
}

fn classify_string_secret_ref(value: &str) -> SecretRef {
    let explicit_env_name = parse_explicit_env_reference(value);
    if let Some(env_name) = explicit_env_name {
        return SecretRef::Env { env: env_name };
    }
    SecretRef::Inline(value.to_owned())
}

fn normalize_non_empty_string(raw: Option<String>) -> Option<String> {
    let value = raw?;
    let trimmed = value.trim();
    if trimmed.is_empty() {
        return None;
    }
    Some(trimmed.to_owned())
}

fn validate_exec_command(exec: &[String]) -> Result<(), &'static str> {
    let Some(program) = exec.first() else {
        return Err("secret exec command must not be empty");
    };
    let trimmed_program = program.trim();
    if trimmed_program.is_empty() {
        return Err("secret exec command program must not be empty");
    }
    Ok(())
}

fn parse_explicit_env_reference(raw: &str) -> Option<String> {
    parse_dollar_env_reference(raw)
        .or_else(|| parse_env_prefix_reference(raw))
        .or_else(|| parse_percent_env_reference(raw))
}

fn parse_dollar_env_reference(raw: &str) -> Option<String> {
    let trimmed = raw.trim();
    let stripped = trimmed.strip_prefix('$')?;
    let stripped = stripped.trim();
    if stripped.is_empty() {
        return None;
    }

    let wrapped = stripped.strip_prefix('{');
    let candidate = wrapped
        .and_then(|value| value.strip_suffix('}'))
        .map(str::trim)
        .unwrap_or(stripped);

    normalize_env_name(candidate)
}

fn parse_env_prefix_reference(raw: &str) -> Option<String> {
    let trimmed = raw.trim();
    let prefix = trimmed.get(..4)?;
    if !prefix.eq_ignore_ascii_case("env:") {
        return None;
    }

    let candidate = trimmed.get(4..)?;
    normalize_env_name(candidate)
}

fn parse_percent_env_reference(raw: &str) -> Option<String> {
    let trimmed = raw.trim();
    let body = trimmed.strip_prefix('%')?;
    let body = body.strip_suffix('%')?;
    normalize_env_name(body)
}

fn normalize_env_name(raw: &str) -> Option<String> {
    let trimmed = raw.trim();
    if trimmed.is_empty() {
        return None;
    }
    if !looks_like_compatible_env_name(trimmed) {
        return None;
    }
    Some(trimmed.to_owned())
}

fn looks_like_compatible_env_name(raw: &str) -> bool {
    let mut chars = raw.chars();
    let Some(first) = chars.next() else {
        return false;
    };
    if !first.is_ascii_alphabetic() && first != '_' {
        return false;
    }
    chars.all(|character| character.is_ascii_alphanumeric() || character == '_')
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn deserializes_env_prefixed_strings_as_env_refs() {
        let parsed =
            serde_json::from_str::<SecretRef>("\"env:OPENAI_API_KEY\"").expect("parse env ref");

        assert_eq!(
            parsed,
            SecretRef::Env {
                env: "OPENAI_API_KEY".to_owned(),
            }
        );
    }

    #[test]
    fn deserializing_non_ascii_inline_string_does_not_panic() {
        let parsed = serde_json::from_str::<SecretRef>("\"हéx\"").expect("parse inline secret");

        assert_eq!(parsed, SecretRef::Inline("हéx".to_owned()));
    }
}