Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ pub struct Entry {
pub kind: Option<EntryKind>,
}

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(try_from = "RawEntryKind")]
pub enum EntryKind {
Integer { min: Option<i64>, max: Option<i64> },
Float { min: Option<f64>, max: Option<f64> },
Expand All @@ -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<i64>, max: Option<i64> },
Float { min: Option<f64>, max: Option<f64> },
}

impl TryFrom<RawEntryKind> for EntryKind {
type Error = String;

fn try_from(raw: RawEntryKind) -> Result<Self, Self::Error> {
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.
Expand Down
40 changes: 40 additions & 0 deletions src/schema/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
}