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)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct ManifestScript {
|
pub struct ManifestScript {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// Path to the `.rhai` source, relative to the manifest's directory.
|
/// Path to the `.rhai` source, relative to the manifest's directory.
|
||||||
@@ -397,6 +398,7 @@ pub struct ManifestScript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct ManifestRoute {
|
pub struct ManifestRoute {
|
||||||
/// Name of the script this route binds to.
|
/// Name of the script this route binds to.
|
||||||
pub script: String,
|
pub script: String,
|
||||||
@@ -459,6 +461,7 @@ impl ManifestTriggers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct KvTriggerSpec {
|
pub struct KvTriggerSpec {
|
||||||
pub script: String,
|
pub script: String,
|
||||||
pub collection_glob: String,
|
pub collection_glob: String,
|
||||||
@@ -479,6 +482,7 @@ pub struct KvTriggerSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct DocsTriggerSpec {
|
pub struct DocsTriggerSpec {
|
||||||
pub script: String,
|
pub script: String,
|
||||||
pub collection_glob: String,
|
pub collection_glob: String,
|
||||||
@@ -497,6 +501,7 @@ pub struct DocsTriggerSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct FilesTriggerSpec {
|
pub struct FilesTriggerSpec {
|
||||||
pub script: String,
|
pub script: String,
|
||||||
pub collection_glob: String,
|
pub collection_glob: String,
|
||||||
@@ -515,6 +520,7 @@ pub struct FilesTriggerSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct CronTriggerSpec {
|
pub struct CronTriggerSpec {
|
||||||
pub script: String,
|
pub script: String,
|
||||||
/// 6-field cron expression (with seconds).
|
/// 6-field cron expression (with seconds).
|
||||||
@@ -528,6 +534,7 @@ pub struct CronTriggerSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct PubsubTriggerSpec {
|
pub struct PubsubTriggerSpec {
|
||||||
pub script: String,
|
pub script: String,
|
||||||
pub topic_pattern: String,
|
pub topic_pattern: String,
|
||||||
@@ -546,6 +553,7 @@ pub struct PubsubTriggerSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct EmailTriggerSpec {
|
pub struct EmailTriggerSpec {
|
||||||
pub script: String,
|
pub script: String,
|
||||||
/// Name of the secret (set via `pic secret set`) holding the inbound
|
/// 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)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct QueueTriggerSpec {
|
pub struct QueueTriggerSpec {
|
||||||
pub script: String,
|
pub script: String,
|
||||||
pub queue_name: String,
|
pub queue_name: String,
|
||||||
@@ -1080,6 +1089,31 @@ mod tests {
|
|||||||
assert_eq!(m.project.unwrap().parent_group.as_deref(), Some("acme"));
|
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]
|
#[test]
|
||||||
fn overlay_vars_override_base_per_key() {
|
fn overlay_vars_override_base_per_key() {
|
||||||
let mut base = sample();
|
let mut base = sample();
|
||||||
|
|||||||
Reference in New Issue
Block a user