use std::collections::BTreeMap;
use serde::Deserialize;
use crate::config::AppConfig;
use super::{error::FlockfileError, format::FlockFormat, parse::parse_into_ignoring};
#[derive(Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename = "Flockfile"))]
#[serde(deny_unknown_fields)]
pub(super) struct RawFlockfile {
#[serde(default, rename = "$schema")]
pub(super) schema: Option<String>,
#[serde(default)]
#[cfg_attr(
feature = "schema",
schemars(with = "Option<BTreeMap<String, serde_json::Value>>")
)]
pub(super) dog: Option<BTreeMap<String, serde::de::IgnoredAny>>,
#[serde(default, rename = "app")]
pub(super) apps: Vec<AppConfig>,
}
impl RawFlockfile {
pub(super) fn new(source: &str, format: FlockFormat) -> Result<Self, FlockfileError> {
let mut unknown = Vec::new();
let raw: RawFlockfile = parse_into_ignoring(source, format, |path| {
if !path.starts_with("dog.") {
unknown.push(path.to_string());
}
})?;
if !unknown.is_empty() {
return Err(FlockfileError::UnknownKeys { keys: unknown });
}
Ok(raw)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::flockfile::file::Flockfile;
use crate::config::flockfile::format::FlockFormat;
#[test]
fn a_dog_table_is_accepted_and_ignored() {
let src = r#"
[dog.deploy]
command = "npm run build"
artifacts = ["dist/app.js"]
[dog.some-other-dog]
anything = { nested = true, count = 3 }
[[app]]
name = "web"
script = "./srv"
"#;
let flock =
Flockfile::parse(src, FlockFormat::Toml).expect("a dog's table is not an error");
assert_eq!(flock.apps.len(), 1);
assert_eq!(flock.apps[0].name, "web");
}
#[test]
fn a_dog_that_is_not_a_table_is_refused() {
for value in ["5", "\"nope\"", "[1, 2]", "true"] {
let src = format!("dog = {value}\n\n[[app]]\nname = \"web\"\nscript = \"./srv\"\n");
assert!(
Flockfile::parse(&src, FlockFormat::Toml).is_err(),
"`dog = {value}` is not a table and must be refused"
);
}
}
#[test]
fn a_key_that_is_not_dog_still_fails() {
let src = r#"
[build]
command = "npm run build"
[[app]]
name = "web"
script = "./srv"
"#;
let err = Flockfile::parse(src, FlockFormat::Toml)
.expect_err("an unknown top-level key must still be refused");
assert!(
format!("{err}").contains("build"),
"the refusal must name the key: {err}"
);
}
#[test]
fn a_schema_key_is_accepted_and_ignored() {
let src = r#"{ "$schema": "./flockfile.schema.json",
"app": [{ "name": "web", "script": "./srv" }] }"#;
let flock = Flockfile::parse(src, FlockFormat::Json).unwrap();
assert_eq!(flock.apps.len(), 1);
}
#[test]
fn one_more_key_is_legal_and_no_others_are() {
let src = r#"{ "schema": "x", "app": [{ "name": "w", "script": "./s" }] }"#;
assert!(
matches!(
Flockfile::parse(src, FlockFormat::Json),
Err(FlockfileError::Json(_))
),
"bare `schema` (no $) must still be an unknown field"
);
}
#[test]
fn a_toml_flockfile_takes_the_key_too() {
let src = "\"$schema\" = \"./flockfile.schema.json\"\n\
[[app]]\nname = \"web\"\nscript = \"./srv\"\n";
assert_eq!(
Flockfile::parse(src, FlockFormat::Toml).unwrap().apps.len(),
1
);
}
}