pub struct Smit(/* private fields */);Expand description
A short marker a dog attaches to a sheep for shep flock to paint.
shep stores one and prints it. It does not parse it, has no opinion about
what it means, and never will: ▲ main@a1b2c3 is a deploy tool’s
sentence, not shep’s, and keeping it that way is what makes this a general
mechanism rather than one feature’s field.
§The rule
A smit is non-empty once whitespace is discounted, at most
Self::MAX_CHARS characters, and carries no char::is_control
character — \u{1b} included, which is_control already covers and which
is named separately in this doc anyway, because it is the one an attacker
reaches for and a reader should not have to know the classification to see
that it is handled.
Refused, never repaired. The text is stored exactly as it arrived: shep
does not trim it, strip from it, or otherwise hand back something the
publisher did not send. crate::kv’s key grammar and value cap set the
same precedent for the same kind of value, and the publisher here is a
program, so a refusal it can see and fix beats mangling it cannot.
§Why the cap counts characters
Self::MAX_CHARS is a count of chars, not of bytes and not of display
columns. Bytes would refuse a legitimate CJK smit at roughly a third of
its apparent length. Display columns are what a table renderer measures,
but they depend on the terminal and on a width table this parser has no
business carrying. Characters are the honest thing a validator can promise
cheaply. 48 is measured against the reference smit ▲ main@a1b2c3 at
thirteen: room for a long branch name, without letting one column swallow
the table.
§Why validation lives here rather than at the renderer
shep’s own output::width::sanitize_cell deliberately KEEPS a
well-formed CSI sequence, because shep’s colouring is made of them, so it
is not a guard against a third party’s string. Refusing here means shep flock, shep describe, --format json, the lookout, the MCP tool schema
and every bus subscriber are safe by construction instead of six places
each remembering.
Debug is derived, and that is the deliberate decision (IR-41): a smit
carries no environment and no secret. It is a string a dog asked to have
painted in public, so redacting it would hide the thing an operator is
debugging.
§Example
use shep_core::protocol::Smit;
assert_eq!("▲ main@a1b2c3".parse::<Smit>()?.as_str(), "▲ main@a1b2c3");
assert!("\u{1b}[2Jgone".parse::<Smit>().is_err()); // no escapesImplementations§
Trait Implementations§
Source§impl<'de> Deserialize<'de> for Smit
Hand-written rather than derived, and that is the whole security property.
impl<'de> Deserialize<'de> for Smit
Hand-written rather than derived, and that is the whole security property.
A derived impl would accept anything a String accepts, so a smit
carrying \u{1b}[2J would reach the daemon’s memory and every listing
built from it. docs/dogs.md tells dog authors to speak this wire
directly, so a dog written in another language never runs
core::str::FromStr — the daemon has to validate what it decodes, not
trust that it was constructed properly.
Source§fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
impl Eq for Smit
Source§impl FromStr for Smit
impl FromStr for Smit
Source§fn from_str(text: &str) -> Result<Self, Self::Err>
fn from_str(text: &str) -> Result<Self, Self::Err>
§Errors
SmitError::Empty— nothing but whitespace.SmitError::TooLong— overSelf::MAX_CHARScharacters.SmitError::Unprintable— a control character,\u{1b}included.
impl StructuralPartialEq for Smit
Auto Trait Implementations§
impl Freeze for Smit
impl RefUnwindSafe for Smit
impl Send for Smit
impl Sync for Smit
impl Unpin for Smit
impl UnsafeUnpin for Smit
impl UnwindSafe for Smit
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.