fix(cli): reject unknown fields in manifest entry specs (fail-loud on typos)
`ManifestScript`, `ManifestRoute`, and every trigger spec (`KvTriggerSpec`…`QueueTriggerSpec`) lacked `#[serde(deny_unknown_fields)]`, while their parent structs all had it. So a typo inside a `[[triggers.kv]]` / `[[routes]]` / `[[scripts]]` entry (e.g. `collection` for `collection_glob`, `methid` for `method`) was silently dropped instead of erroring — the manifest applied with the mistyped directive missing. Add the attribute to the 9 entry structs (all fields are exhaustive, so `pull`-emitted TOML still round-trips — verified against the full journey suite, 151/151). Pinned by a new `unknown_key_in_an_entry_spec_is_rejected` unit test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -372,6 +372,7 @@ impl CollectionDecl {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ManifestScript {
|
||||
pub name: String,
|
||||
/// Path to the `.rhai` source, relative to the manifest's directory.
|
||||
@@ -397,6 +398,7 @@ pub struct ManifestScript {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ManifestRoute {
|
||||
/// Name of the script this route binds to.
|
||||
pub script: String,
|
||||
@@ -459,6 +461,7 @@ impl ManifestTriggers {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct KvTriggerSpec {
|
||||
pub script: String,
|
||||
pub collection_glob: String,
|
||||
@@ -479,6 +482,7 @@ pub struct KvTriggerSpec {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DocsTriggerSpec {
|
||||
pub script: String,
|
||||
pub collection_glob: String,
|
||||
@@ -497,6 +501,7 @@ pub struct DocsTriggerSpec {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct FilesTriggerSpec {
|
||||
pub script: String,
|
||||
pub collection_glob: String,
|
||||
@@ -515,6 +520,7 @@ pub struct FilesTriggerSpec {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CronTriggerSpec {
|
||||
pub script: String,
|
||||
/// 6-field cron expression (with seconds).
|
||||
@@ -528,6 +534,7 @@ pub struct CronTriggerSpec {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PubsubTriggerSpec {
|
||||
pub script: String,
|
||||
pub topic_pattern: String,
|
||||
@@ -546,6 +553,7 @@ pub struct PubsubTriggerSpec {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct EmailTriggerSpec {
|
||||
pub script: String,
|
||||
/// Name of the secret (set via `pic secret set`) holding the inbound
|
||||
@@ -558,6 +566,7 @@ pub struct EmailTriggerSpec {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct QueueTriggerSpec {
|
||||
pub script: String,
|
||||
pub queue_name: String,
|
||||
@@ -1080,6 +1089,31 @@ mod tests {
|
||||
assert_eq!(m.project.unwrap().parent_group.as_deref(), Some("acme"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_key_in_an_entry_spec_is_rejected() {
|
||||
// Entry specs (scripts/routes/triggers) are `deny_unknown_fields`, so a
|
||||
// typo inside a `[[…]]` entry is a hard error, not silently dropped.
|
||||
// A misspelled trigger field (`collection` for `collection_glob`):
|
||||
Manifest::parse(
|
||||
"[app]\nslug = \"a\"\nname = \"A\"\n\n\
|
||||
[[triggers.kv]]\nscript = \"h\"\ncollection = \"c\"\n",
|
||||
)
|
||||
.expect_err("a misspelled [[triggers.kv]] field must be rejected");
|
||||
// A misspelled route field:
|
||||
Manifest::parse(
|
||||
"[app]\nslug = \"a\"\nname = \"A\"\n\n\
|
||||
[[routes]]\nscript = \"h\"\nhost_kind = \"any\"\npath_kind = \"exact\"\n\
|
||||
path = \"/x\"\nmethid = \"GET\"\n",
|
||||
)
|
||||
.expect_err("a misspelled [[routes]] field must be rejected");
|
||||
// The correctly-spelled form still parses.
|
||||
Manifest::parse(
|
||||
"[app]\nslug = \"a\"\nname = \"A\"\n\n\
|
||||
[[triggers.kv]]\nscript = \"h\"\ncollection_glob = \"c\"\n",
|
||||
)
|
||||
.expect("a correct [[triggers.kv]] entry parses");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlay_vars_override_base_per_key() {
|
||||
let mut base = sample();
|
||||
|
||||
Reference in New Issue
Block a user