66 lines
1.9 KiB
Go
66 lines
1.9 KiB
Go
package source
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// DigestDocument returns a deterministic digest of the canonical source
|
|
// document content. The existing Digest field is excluded from its own digest.
|
|
func DigestDocument(doc *SourceDocument) (string, error) {
|
|
if doc == nil {
|
|
return "", fmt.Errorf("source document must not be nil")
|
|
}
|
|
payload := struct {
|
|
ID string `json:"id"`
|
|
Kind string `json:"kind"`
|
|
Format string `json:"format"`
|
|
Units []SourceUnit `json:"units"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
}{
|
|
ID: doc.ID,
|
|
Kind: doc.Kind,
|
|
Format: doc.Format,
|
|
Units: doc.Units,
|
|
Metadata: doc.Metadata,
|
|
}
|
|
encoded, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return "", fmt.Errorf("encode source document for digest: %w", err)
|
|
}
|
|
sum := sha256.Sum256(encoded)
|
|
return "sha256:" + hex.EncodeToString(sum[:]), nil
|
|
}
|
|
|
|
// DigestChunk returns a deterministic digest of a chunk, including its source
|
|
// provenance, content, units, and metadata.
|
|
func DigestChunk(chunk Chunk) (string, error) {
|
|
payload := struct {
|
|
ID string `json:"id"`
|
|
SourceID string `json:"source_id"`
|
|
Index int `json:"index"`
|
|
Ref SourceRef `json:"ref"`
|
|
Content []byte `json:"content"`
|
|
MediaType string `json:"media_type"`
|
|
Units []SourceUnit `json:"units"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
}{
|
|
ID: chunk.ID,
|
|
SourceID: chunk.SourceID,
|
|
Index: chunk.Index,
|
|
Ref: chunk.Ref,
|
|
Content: chunk.Content,
|
|
MediaType: chunk.MediaType,
|
|
Units: chunk.Units,
|
|
Metadata: chunk.Metadata,
|
|
}
|
|
encoded, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return "", fmt.Errorf("encode source chunk for digest: %w", err)
|
|
}
|
|
sum := sha256.Sum256(encoded)
|
|
return "sha256:" + hex.EncodeToString(sum[:]), nil
|
|
}
|