Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

primitive-field

Level: warn · Article: Primitives

Data comes in as primitives. It leaves as domain types. It stays as domain types until it leaves the system. No exceptions.

What it checks

A named struct field whose name says “domain concept” and whose type says “anything”: email: String, user_id: u64, latitude: f64, price: Option<f64>. The names come from a configurable list of words and _-suffixes; bool and char never count.

Wire shapes are skipped. A struct named *Request, *Response, *Row, *Dto and so on is where primitives arrive, and parsing happens right after.

Don’t

#![allow(unused)]
fn main() {
struct User {
    email: String,
    id: String,
    latitude: f64,
}
}

email accepts "not an email". id accepts an order id. latitude accepts 400.

Do

#![allow(unused)]
fn main() {
struct User {
    email: Email,
    id: UserId,
    latitude: Latitude,
}

struct Email(String);

impl Email {
    fn parse(s: &str) -> Result<Self, ValidationError> {
        if !s.contains('@') {
            return Err(ValidationError::MissingAt);
        }
        Ok(Self(s.to_lowercase()))
    }
}
}

Validate once, at construction, at the boundary. Everything past that line is typed and nobody checks again.

Options

[naming]
domain-fields = ["_id", "amount", "email", "latitude", "longitude", "password",
                 "phone", "price", "token", "url"]
boundary-suffixes = ["Body", "Dto", "Params", "Payload", "Query", "Record",
                     "Request", "Response", "Row"]

Silence it

#![allow(unused)]
fn main() {
// rabot: allow(primitive-field) mirrors the vendor's CSV columns; parsed into Reading right after
struct RawReading { latitude: f64, longitude: f64 }
}