diff --git a/epiphany-pipeline/src/lib.rs b/epiphany-pipeline/src/lib.rs index 74641d6c..b3752552 100644 --- a/epiphany-pipeline/src/lib.rs +++ b/epiphany-pipeline/src/lib.rs @@ -12,6 +12,12 @@ //! schema describes. Keys are semantic: `pipeline_key` derives them from the //! value, and the write validator refuses any envelope whose key differs. //! +//! Every key is `::`, three segments for every kind with +//! none excused: the root is a `Slug`, the kind is the literal kind name, and +//! the local is `Label`s joined by `.`, so no local part carries a dot and a +//! reader recovers the parts by splitting. A root's local is the constant +//! `self`; a resolution's local is its subject's kind and local. +//! //! **Validation follows the type.** `value_types!` is the single field list: it //! emits the struct and its `Bounded` impl from the same tokens, so a field //! cannot be declared without being validated. Format rules live in the field's @@ -114,7 +120,7 @@ fn hex(field: &str, value: &str, lengths: std::ops::RangeInclusive) -> Re } /// A key label: `[A-Za-z0-9_-]{1,64}`. Dots are excluded so that a composed key -/// segments unambiguously; see `parent_cut` and `KeyParts::key`. +/// segments unambiguously; see `local`. fn label_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { let valid = value .bytes() @@ -523,18 +529,20 @@ pipeline_kinds! { "epiphany.pipeline.hand_off.v1", "EpiphanyPipelineHandOffDocument"; } -/// Parses a full document id. A key and an id are the same string, so this -/// accepts exactly what `pipeline_key` derives: `::` for -/// every kind but the roots and a resolution. The segment count is exact, the -/// kind segment must match `kind`, and both the root and the local segment are -/// validated, so `..`, an empty part, spaces and trailing junk are all refused. +/// Parses a full document id: the grammar read backwards. A key and an id are +/// the same string, so this accepts exactly what `pipeline_key` derives: +/// `::`, three segments for every kind. The kind segment +/// must equal `kind`'s name, the root is a `Slug`, and the local is `Label`s +/// joined by `.` and bounded whole, so `..`, an empty part, spaces and trailing +/// junk are all refused. Returns the root and the local; nothing is inferred +/// from either, and no kind is read any other way. fn pipeline_id<'a>( field: &str, id: &'a str, kind: PipelineKind, ) -> Result<(&'a str, &'a str), PipelineRefusal> { let mut segments = id.split(':'); - let (Some(campaign), Some(name), Some(local), None) = ( + let (Some(root), Some(name), Some(local), None) = ( segments.next(), segments.next(), segments.next(), @@ -545,9 +553,9 @@ fn pipeline_id<'a>( if name != kind.name() { return Err(format_error(field, id)); } - dotted_text(field, campaign)?; + dotted_text(field, root)?; dotted_text(field, local)?; - Ok((campaign, local)) + Ok((root, local)) } /// The local segment of a parent id in `campaign`, of the expected kind. @@ -564,15 +572,15 @@ fn parent_local<'a>( Ok(local) } -/// The cut label inside a parent local `cut-