Skip to main content

validate_with

Function validate_with 

Source
pub fn validate_with(
    collection: &str,
    record: &Value,
    stance: Stance,
) -> Result<(), ShapeError>
Expand description

Checks a record against the collection it is being written to.

Four rules, in order.

  1. The collection must be a syntactically valid NSID — validate_collection.
  2. A $type carried by the record must agree with it.
  3. The record must be representable in the atproto data model.
  4. The record must satisfy the lexicon that defines the collection, when this server holds one.

The $type check is here because nothing else performs it. jacquard does not verify $type on deserialize, so a record claiming to be a record can be written into the memory collection and read back as a memory, with the lie preserved inside the value. That is a data corruption no later reader can detect, so it is refused at the only point where both facts are in hand. It holds for a foreign collection too: $type and the collection name the same lexicon in atproto, whoever wrote it.

A record with no $type is accepted: the collection already says what it is, and atproto treats the field as redundant inside a repository.

§The data model check is the one that makes storage faithful

A record is stored as JSON and exported as DAG-CBOR inside a Merkle search tree. JSON has values the data model does not — a float, most of all — and under the closed collection set they could only arrive in an undeclared field, which validation deliberately carries through unchecked. Opening the collection set makes every field undeclared, so the check moves to the boundary: a record that cannot be encoded is refused on write rather than accepted and then breaking every export of the repository afterwards. What the model can represent survives byte for byte, which is what the unknown type exists for.

§Why the schema check is conditional

atproto’s Lexicon specification names three stances a service may take: explicit, where every record must resolve to a known schema and satisfy it; optimistic, where a record is validated when a schema is at hand and accepted when it is not; and skip. This server takes the optimistic one, which is the stance a personal data server has to take, because a repository is the account’s and the lexicons are the world’s. Validation is explicit, and that choice held only while the collection set was closed. The set is open now.

use didbot_schema::validate_shape;
use serde_json::json;

// A record under a collection no schema is held for is accepted unchecked.
let foreign = json!({"text": "reading the MST encoder"});
assert!(validate_shape("com.example.thing", &foreign).is_ok());

// The one schema this deployment holds is enforced.
let bad = json!({"did": "did:web:a.example"});
assert!(validate_shape("bot.did.registration", &bad).is_err());