diff --git a/epiphany-pipeline/src/lib.rs b/epiphany-pipeline/src/lib.rs index c4d28f20..1001e1d0 100644 --- a/epiphany-pipeline/src/lib.rs +++ b/epiphany-pipeline/src/lib.rs @@ -190,9 +190,22 @@ fn within(limit: usize) -> impl Fn(&str, &str) -> Result<(), PipelineRefusal> { move |field: &str, value: &str| bound(field, limit, value.len()) } +fn title_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { + if value.is_empty() { + return Err(format_error(field, value)); + } + within(200)(field, value) +} + bounded_text! { /// Text of at most 200 UTF-8 bytes. Short = 200, |field, value| within(200)(field, value); + /// A title: 1 to 200 UTF-8 bytes, never empty. All four leaf titles + /// (campaign, cut spec, question and ruling) carry this type, ruled B + /// under Q-RS1: the epoch moves in this cut regardless, so tightening the + /// two titles that were `Short` costs nothing extra and avoids a second + /// epoch bump later just to make a title required. + Title = 200, title_text; /// Text of at most 1,000 UTF-8 bytes. Line = 1000, |field, value| within(1000)(field, value); /// Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`. @@ -226,6 +239,30 @@ impl Slug { } } +impl OrgRepo { + /// The public door onto the `org_repo` grammar, for a caller outside this + /// crate that holds a bare `OrgRepo` (the organ's `repo` alias, RS-3) and + /// no way to reach `Bounded`. On the pattern of `Slug::validate_slug`: it + /// delegates to `org_repo_text` through `Bounded::validate`, so the + /// grammar keeps one owner and a declared name checked this way refuses + /// exactly what a document's own `OrgRepo` field refuses. + pub fn validate_org_repo(&self) -> Result<(), PipelineRefusal> { + Bounded::validate(self, "org_repo") + } +} + +impl Label { + /// The public door onto the label grammar, for a caller outside this + /// crate that holds a bare `Label` (the organ's `cut` alias, RS-3) and no + /// way to reach `Bounded`. On the pattern of `Slug::validate_slug`: it + /// delegates to `label_text` through `Bounded::validate`, so the grammar + /// keeps one owner and a declared name checked this way refuses exactly + /// what a document's own `Label` field refuses. + pub fn validate_label(&self) -> Result<(), PipelineRefusal> { + Bounded::validate(self, "label") + } +} + /// A calendar date, `YYYY-MM-DD`. #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)] #[serde(transparent)] @@ -341,21 +378,21 @@ value_types! { promise: Option