Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cdx-cli/src/commands/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub fn run(file1: &Path, file2: &Path, config: &OutputConfig) -> Result<()> {
}
}

#[allow(clippy::too_many_lines)]
#[allow(clippy::too_many_lines)] // sequential field-by-field comparison — splitting would scatter related diff logic
fn collect_differences(doc1: &Document, doc2: &Document) -> Vec<DiffItem> {
let mut differences = Vec::new();
let manifest_a = doc1.manifest();
Expand Down
2 changes: 1 addition & 1 deletion cdx-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ fn main() {
}
}

#[allow(clippy::too_many_lines)]
#[allow(clippy::too_many_lines)] // flat match dispatching each CLI subcommand — no shared logic to extract
fn run_command(command: Commands, output_config: &output::OutputConfig) -> Result<()> {
match command {
Commands::Create {
Expand Down
4 changes: 2 additions & 2 deletions cdx-core/src/content/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,7 @@ struct InlineIdOnly<'a> {
}

impl Serialize for Block {
#[allow(clippy::too_many_lines)]
#[allow(clippy::too_many_lines)] // mechanical match over 20+ block variants — splitting would obscure the dispatch
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::Error;

Expand Down Expand Up @@ -1408,7 +1408,7 @@ impl Serialize for Block {
}

impl<'de> Deserialize<'de> for Block {
#[allow(clippy::too_many_lines)]
#[allow(clippy::too_many_lines)] // mechanical match over 20+ block type strings — splitting would obscure the dispatch
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
// Deserialize into a generic Value first, then dispatch based on "type"
let mut value = serde_json::Value::deserialize(deserializer)?;
Expand Down
4 changes: 2 additions & 2 deletions cdx-core/src/content/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ impl Serialize for Mark {
}

impl<'de> Deserialize<'de> for Mark {
#[allow(clippy::too_many_lines)]
#[allow(clippy::too_many_lines)] // mechanical dispatch across 15+ mark variants — splitting would obscure the mapping
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct MarkVisitor;

Expand Down Expand Up @@ -541,7 +541,7 @@ impl<'de> Deserialize<'de> for Mark {
}

// Complex marks are objects with a "type" field
#[allow(clippy::too_many_lines)]
#[allow(clippy::too_many_lines)] // field extraction + type dispatch for 15+ mark variants in one pass
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Mark, A::Error> {
let mut type_str: Option<String> = None;
let mut fields = serde_json::Map::new();
Expand Down
36 changes: 28 additions & 8 deletions cdx-core/src/document/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,40 @@ use crate::{DocumentId, Hasher, Result};
use super::Document;

impl Document {
/// Compute the document ID from content.
/// Compute the document ID from content and identity metadata.
///
/// The document ID is computed by hashing the canonicalized semantic content layer.
/// This covers only the content blocks and their structure, not presentation/layout
/// information. Presentation layers have their own hashes in the manifest.
/// Per spec §06 §4.1, the document ID is computed by hashing the canonicalized
/// semantic identity of the document. This includes:
///
/// - **Content blocks** (the document's structural content)
/// - **Identity metadata**: title, creator, subject, description, language
///
/// The hash explicitly **excludes** presentation layers, signatures, phantom
/// data, form data, and collaboration data — these are non-identity concerns
/// with their own integrity mechanisms.
///
/// # Errors
///
/// Returns an error if canonicalization fails.
pub fn compute_id(&self) -> Result<DocumentId> {
// Serialize content to canonical JSON
let content_json = serde_json::to_vec(&self.content)?;
let canonical =
json_canon::to_string(&serde_json::from_slice::<serde_json::Value>(&content_json)?)?;
// Build a hashable structure combining content + identity metadata.
// Per spec §06 §4.1, the hash boundary includes content blocks and
// the subset of Dublin Core metadata that defines document identity.
let content_value = serde_json::to_value(&self.content)?;
let metadata_value = serde_json::json!({
"title": self.dublin_core.terms.title,
"creator": serde_json::to_value(&self.dublin_core.terms.creator)?,
"subject": serde_json::to_value(&self.dublin_core.terms.subject)?,
"description": self.dublin_core.terms.description,
"language": self.dublin_core.terms.language,
});

let hashable = serde_json::json!({
"content": content_value,
"metadata": metadata_value,
});

let canonical = json_canon::to_string(&hashable)?;

Ok(Hasher::hash(
self.manifest.hash_algorithm,
Expand Down
2 changes: 1 addition & 1 deletion cdx-core/src/security/ml_dsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl MlDsaSigner {
/// # Errors
///
/// Returns an error if key generation fails.
#[allow(clippy::missing_panics_doc)]
#[allow(clippy::missing_panics_doc)] // getrandom::SysRng only fails on misconfigured systems
pub fn generate(signer_info: SignerInfo) -> Result<(Self, Vec<u8>)> {
use ml_dsa::KeyGen;

Expand Down
Loading