pub trait ContentAddressable {
// Required method
fn canonical_form(&self) -> Result<Vec<u8>, ContentError>;
// Provided methods
fn content_id(&self) -> Result<ContentId, ContentError> { ... }
fn verify(&self, expected: &ContentId) -> Result<bool, ContentError> { ... }
fn ensure_content_id(
&self,
expected: &ContentId,
) -> Result<(), ContentError> { ... }
fn from_canonical_form(bytes: &[u8]) -> Result<Self, ContentError>
where Self: DeserializeOwned + Sized { ... }
}Expand description
A value that can name itself by its content.
Implementors provide canonical_form; the
content_id and verify methods are
derived from it for free.
§Implementing
For any #[derive(serde::Serialize)] type, the canonical form is just its
canonical dag-cbor encoding, so the implementation is one line:
use content_addressable::{canonical, ContentAddressable, ContentError};
use serde::Serialize;
#[derive(Serialize)]
struct Block {
parent: Option<String>,
payload: Vec<u8>,
}
impl ContentAddressable for Block {
fn canonical_form(&self) -> Result<Vec<u8>, ContentError> {
canonical::to_canonical_dagcbor(self)
}
}
let block = Block { parent: None, payload: vec![1, 2, 3] };
let id = block.content_id().unwrap();
assert!(block.verify(&id).unwrap());Required Methods§
Sourcefn canonical_form(&self) -> Result<Vec<u8>, ContentError>
fn canonical_form(&self) -> Result<Vec<u8>, ContentError>
Produce the deterministic byte representation of this value.
This is the only required method. The canonical bytes must be a
function of the value alone: equal values must produce equal bytes.
The recommended implementation defers to
to_canonical_dagcbor on a
Serialize type, which guarantees that property.
§Errors
Returns ContentError if the value cannot be canonically encoded.
Provided Methods§
Sourcefn content_id(&self) -> Result<ContentId, ContentError>
fn content_id(&self) -> Result<ContentId, ContentError>
Compute this value’s ContentId.
Hashes the canonical_form into a BLAKE3
CIDv1. Override only if you have a faster path that is provably
identical to the default.
§Errors
Propagates any error from canonical_form.
Sourcefn verify(&self, expected: &ContentId) -> Result<bool, ContentError>
fn verify(&self, expected: &ContentId) -> Result<bool, ContentError>
Check whether this value’s content id matches an expected id.
Returns Ok(true) if the recomputed id equals expected, Ok(false)
otherwise. This is the integrity check at the heart of the doctrine:
the value re-derives its own identity and compares it to the claim.
§Mismatch is Ok(false), not an error (FROZEN at 0.1.0)
A mismatch is not an error — it returns Ok(false). A negative
answer to “do these match?” is a successful, expected result, not a
failure to check; forcing it through the Err channel would conflate “I
checked, the answer is no” with “I couldn’t check”. The ?-friendly
Result<bool> also composes cleanly in boolean logic
(if a.verify(&id1)? && b.verify(&id2)? { … }). This return contract is a
frozen part of the 0.1.0 API surface (README gate item #8) —
flipping Ok(false) to an Err arm later would be a breaking change.
If you want a mismatch to short-circuit via ?, use the strict helper
ensure_content_id, which returns
Err(ContentError::VerificationFailed) on mismatch — don’t hand-roll
it.
§Errors
Propagates any error from content_id only (i.e. an
encoding failure in canonical_form); never an Err on a clean
mismatch.
Sourcefn ensure_content_id(&self, expected: &ContentId) -> Result<(), ContentError>
fn ensure_content_id(&self, expected: &ContentId) -> Result<(), ContentError>
Verify, returning an error on mismatch instead of Ok(false).
Like verify, but a mismatch is reported as
Err(ContentError::VerificationFailed) carrying both ids (as their
Display strings, multibase base32-lower b…),
rather than Ok(false). On a match it returns Ok(()). Use this when a
mismatch should short-circuit through ?; use verify
when you want the boolean to compose in further logic.
This is the strict form the crate’s doctrine promises: it makes
ContentError::VerificationFailed a real, reachable, tested error path
rather than a name callers must construct by hand. It is a defaulted trait
method, so every implementor gets it for free. Both verify and this
helper are frozen for the 0.1.0 API surface (README gate item #8).
§Errors
ContentError::VerificationFailedif the recomputed id differs fromexpected.- Otherwise propagates any error from
content_id(e.g. an encoding failure incanonical_form).
Sourcefn from_canonical_form(bytes: &[u8]) -> Result<Self, ContentError>where
Self: DeserializeOwned + Sized,
fn from_canonical_form(bytes: &[u8]) -> Result<Self, ContentError>where
Self: DeserializeOwned + Sized,
Checked ingress: decode a value from its canonical form, proving the
bytes reproduce it — a checked partial inverse of
canonical_form.
This is the door for bytes you did not produce: foreign, stored, or off the wire.
§The enforced invariant
On success, and unconditionally:
let value = T::from_canonical_form(bytes)?;
assert_eq!(value.canonical_form()?, bytes);That is the whole guarantee, and it is a claim about bytes, not about
the codec: it is established by re-encoding the decoded value and
comparing, not by appealing to dag-cbor being canonical. Exact byte
equality is also strictly stronger than comparing two
ContentIds — it assumes no collision resistance.
§Corollary for a lawful implementation
Call an implementation lawful when its
content_id agrees with the trait law —
content_id() == ContentId::from_canonical_bytes(&canonical_form()?),
which the provided default satisfies by construction. For a lawful
implementation the invariant above gives:
value.content_id()? == ContentId::from_canonical_bytes(bytes)This is a corollary, not a guarantee. content_id
is overridable, and an override that is not provably identical to the
default breaks the equation. Nothing here calls content_id, so the
crate cannot detect that. A caller who needs the equation held rather
than assumed checks it, with
ensure_content_id:
value.ensure_content_id(&ContentId::from_canonical_bytes(bytes))?;§Partial: the domain, and which prerequisite failed
It succeeds on exactly those bytes that are
- canonical dag-cbor — otherwise
ContentError::DecodingError(not dag-cbor at all) orContentError::NonCanonical(dag-cbor, but not the canonical encoding). Established generically, before anySelfis constructed, so the result does not lean on this type’s serde impl being well behaved; - directly deserializable as
Self— otherwiseContentError::DecodingError; - reproduced byte-for-byte by the decoded value’s
canonical_form— otherwiseContentError::LossyDecode.
Every failure names the prerequisite that was not met.
§What “inverse” does and does not mean here
On its accepted byte domain, decoding and re-encoding reproduces the original canonical bytes. The returned value therefore has the same canonical representation and content identity; it is not necessarily equal to a previously encoded in-memory value unless the type separately guarantees a value-preserving serde round trip.
Serialize + DeserializeOwned does not establish that. A
#[serde(skip)] field is the clean counterexample: it is absent from the
encoding entirely, so two values differing only in it share identical
canonical bytes, and decoding returns the default rather than what was
encoded. This door correctly accepts that — those bytes really are that
value’s canonical representation. Identity is preserved; the in-memory
value need not be.
A plain #[serde(default)] field is not such a case. It only supplies
a value when one is missing on the way in; ordinary derived serialization
writes it back out, so the re-encoding differs from the input and stage 3
reports ContentError::LossyDecode. It joins #[serde(skip)] here only
if it is also omitted when serializing.
A second, separate limit applies to the shape of the type:
A type may define a canonical_form that is deliberately not its
serde representation — an envelope, a versioned framing, a projection —
and still be perfectly lawful for content addressing: its identity is
well defined and reproducible. But its canonical bytes are then not
directly deserializable as Self unless it also supplies a matching
Deserialize, so from_canonical_form on its own output fails
prerequisite 2 with ContentError::DecodingError.
That is not a defect in the type and does not make its canonical_form
unlawful. It means this method is checked ingress for the common
representation, not a universal inverse: a custom canonical form needs a
custom decoder to match it. What the method never does is guess —
it refuses rather than return a value the bytes do not reproduce.
§Why stage 3 re-encodes through canonical_form
Deliberately, rather than through
to_canonical_dagcbor:
canonical_form is the function that defines this type’s identity, so
it is the only re-encode whose equality proves the invariant. For the
recommended one-line implementation the two coincide; for a type with its
own canonical form they do not, and only this one is sound. It is also why
the bound is DeserializeOwned + Sized and not also Serialize.
For a plain Serialize + Deserialize value that is not
ContentAddressable, use
canonical::from_canonical_dagcbor_checked, whose stage 3 is
to_canonical_dagcbor instead.
§Overriding
Like content_id, override this only if you have a
path that is provably identical. An override that skips a stage does
not weaken the store seam — NodeStoreExt::get_node (feature
unstable-store) deliberately runs the shared body rather than this
method, so a T cannot hand itself a pass — but it does weaken every
direct caller of T::from_canonical_form.
§Availability
Added in 0.1.2 (issue #90) as a defaulted associated function, so every
implementor gets it for free; it carries where Self: Sized, so the trait
stays dyn-compatible. It is the one deliberate exception 0.1.2 makes to
the otherwise frozen 0.1.x Rust API — see docs/STABILITY.md.
§Errors
ContentError::DecodingError— the bytes are not dag-cbor, or do not decode as aSelf(prerequisites 1 and 2).ContentError::NonCanonical— the bytes are dag-cbor but not the canonical encoding (prerequisite 1).ContentError::LossyDecode— the decoded value’scanonical_formdiffers from the input (prerequisite 3).- Anything
canonical_formitself returns, propagated verbatim — typicallyContentError::EncodingError.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".