unicity-aos/aos-ce · error · serde_json::Error

canonical document exceeds bound

Error message

canonical document exceeds bound

What it means

parse_canonical rejects any input larger than MAX_CANONICAL_DOCUMENT_BYTES before parsing, returning a serde_json::Error wrapping an InvalidData io error with the message "canonical document exceeds bound". The library enforces a hard size cap to guarantee bounded memory use when parsing and canonicalizing untrusted canonical JSON documents.

Solutions

  1. Measure input.len() before calling and split, compress, or reject oversized documents upstream.
  2. Check the configured MAX_CANONICAL_DOCUMENT_BYTES constant and, if legitimately too small for your documents, raise it in the source.
  3. Remove extraneous bytes (trailing whitespace, appended blobs) from the input before parsing.
  4. Match on the returned serde_json::Error's io kind InvalidData with this message to surface a clear 'payload too large' error to the caller.

Example fix

// before
let value: MyDoc = parse_canonical(&raw)?;
// after
const MAX: usize = MAX_CANONICAL_DOCUMENT_BYTES;
if raw.len() > MAX {
    return Err(MyError::PayloadTooLarge(raw.len(), MAX));
}
let value: MyDoc = parse_canonical(&raw)?;
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_within_bound(input: &[u8]) -> Result<(), String> {
    if input.len() > MAX_CANONICAL_DOCUMENT_BYTES {
        return Err(format!("input is {} bytes, limit is {}", input.len(), MAX_CANONICAL_DOCUMENT_BYTES));
    }
    Ok(())
}

Try / catch

match parse_canonical::<T>(&input) {
    Err(e) if e.io_error_kind() == Some(std::io::ErrorKind::InvalidData) => /* payload too large: report sizes and reject upstream */,
    other => other?,
}

Prevention

When it happens

Trigger: Calling parse_canonical::<T>() with an input byte slice whose length exceeds MAX_CANONICAL_DOCUMENT_BYTES. Detected before any JSON parsing occurs.

Common situations: Accidentally passing a whole file to parse_canonical when it contains multiple concatenated documents or appended data; a client sending an unexpectedly large canonical payload; test fixtures growing past the limit after schema additions (seen by tests like exact_canonical_fixtures_round_trip and canonical_json_rejects_duplicate_oversized_and_unknown_input).

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13). Data as JSON: /api/errors/f6f2c9af99948648. Report an issue: GitHub.

Appendix: source

Thrown at capsules/capsule-surface-model/src/canonical.rs:174

pub fn canonical_bytes<T: Serialize>(value: &T) -> Result<Vec<u8>, serde_json::Error> {
    let raw = serde_json::to_vec(value)?;
    let parsed = serde_json::from_slice::<CanonicalJson>(&raw).map_err(|error| {
        serde_json::Error::io(std::io::Error::new(std::io::ErrorKind::InvalidData, error))
    })?;
    serde_json::to_vec(&parsed)
}

/// Serialize to deterministic canonical JSON text.
pub fn canonical_string<T: Serialize>(value: &T) -> Result<String, serde_json::Error> {
    canonical_bytes(value).map(|bytes| String::from_utf8(bytes).expect("JSON is UTF-8"))
}

/// Parse once for duplicate keys, canonicalize, then deserialize the typed value.
pub fn parse_canonical<T: serde::de::DeserializeOwned>(
    input: &[u8],
) -> Result<T, serde_json::Error> {
    if input.len() > MAX_CANONICAL_DOCUMENT_BYTES {
        return Err(serde_json::Error::io(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "canonical document exceeds bound",
        )));
    }
    let parsed = serde_json::from_slice::<CanonicalJson>(input)?;
    let canonical = serde_json::to_vec(&parsed)?;
    serde_json::from_slice(&canonical)
}

/// Canonical digest for one or more ordered semantic values.
pub fn digest_parts<T: Serialize>(parts: &T) -> Result<String, serde_json::Error> {
    Ok(blake3::hash(&canonical_bytes(parts)?).to_hex().to_string())
}

/// Validate a BLAKE3 digest in its lowercase hexadecimal form.
pub fn valid_blake3_digest(value: &str) -> bool {
    value.len() == 64
        && value

View on GitHub (pinned to f6f22024fb)