diff --git a/src/schema/mod.rs b/src/schema/mod.rs index fe69d38..e96131b 100644 --- a/src/schema/mod.rs +++ b/src/schema/mod.rs @@ -29,7 +29,8 @@ pub struct Entry { pub kind: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(try_from = "RawEntryKind")] pub enum EntryKind { Integer { min: Option, max: Option }, Float { min: Option, max: Option }, @@ -41,6 +42,62 @@ pub enum EntryKind { Path, } +// Represents the different ways an `EntryKind` can be specified in the input schema. +// +// - A `StructuredKind` is a "kind" that allows for additional parameters. +// - A `Name` is a simple string that maps to a known `EntryKind` variant without parameters. +#[derive(Deserialize)] +#[serde(untagged)] +enum RawEntryKind { + Structured(StructuredKind), + Name(String), +} + +#[derive(Deserialize)] +enum StructuredKind { + Integer { min: Option, max: Option }, + Float { min: Option, max: Option }, +} + +impl TryFrom for EntryKind { + type Error = String; + + fn try_from(raw: RawEntryKind) -> Result { + match raw { + RawEntryKind::Structured(s) => Ok(match s { + StructuredKind::Integer { min, max } => EntryKind::Integer { min, max }, + StructuredKind::Float { min, max } => EntryKind::Float { min, max }, + }), + RawEntryKind::Name(s) => { + // Normalize the input string to allow for case-insensitive and + // underscore-insensitive naming. This is to avoid annoying users with super + // strict naming. + let normalized_name = s.to_lowercase().replace('_', ""); + match normalized_name.as_str() { + "integer" => Ok(EntryKind::Integer { + min: None, + max: None, + }), + "float" => Ok(EntryKind::Float { + min: None, + max: None, + }), + "string" => Ok(EntryKind::String), + "url" => Ok(EntryKind::Url), + "email" => Ok(EntryKind::Email), + "bool" => Ok(EntryKind::Bool), + "ipaddress" => Ok(EntryKind::IpAddress), + "path" => Ok(EntryKind::Path), + other => Err(format!( + "unknown variant '{}', expected one of: integer, float, string, url, email, bool, ipaddress, path", + other + )), + } + } + } + } +} + /// Generates .env file content based on the given schema and existing env values. /// /// For keys with existing non-empty values, those values are preserved. diff --git a/src/schema/tests.rs b/src/schema/tests.rs index de8be38..849d61f 100644 --- a/src/schema/tests.rs +++ b/src/schema/tests.rs @@ -1435,3 +1435,43 @@ mod validate_env_tests { } } } + +mod kind_deserialization_tests { + use crate::schema::{Entry, EntryKind}; + + #[test] + fn test_kind_deserialization() { + let cases = vec![ + ( + "Integer", + EntryKind::Integer { + min: None, + max: None, + }, + ), + ( + "integer", + EntryKind::Integer { + min: None, + max: None, + }, + ), + ("ip_address", EntryKind::IpAddress), + ("IP_ADDRESS", EntryKind::IpAddress), + ("ipaddress", EntryKind::IpAddress), + ]; + + for (kind, ttype) in cases { + let toml_str = format!( + r#" + key = "TEST_VAR" + required = true + kind = "{}" + "#, + kind + ); + let entry: Entry = toml::from_str(&toml_str).expect("Failed to parse TOML"); + assert_eq!(entry.kind, Some(ttype)); + } + } +}