///|
/// Structural metrics for logging, admission control, and telemetry.
pub(all) struct CborDocumentStats {
nodes : Int
containers : Int
max_depth : Int
text_bytes : Int
binary_bytes : Int
encoded_bytes : Int
} derive(Eq, Debug)
///|
fn add_document_stats(
left : CborDocumentStats,
right : CborDocumentStats,
) -> CborDocumentStats {
{
nodes: left.nodes + right.nodes,
containers: left.containers + right.containers,
max_depth: if left.max_depth > right.max_depth {
left.max_depth
} else {
right.max_depth
},
text_bytes: left.text_bytes + right.text_bytes,
binary_bytes: left.binary_bytes + right.binary_bytes,
encoded_bytes: left.encoded_bytes + right.encoded_bytes,
}
}
///|
fn document_stats_at(value : CborValue, depth : Int) -> CborDocumentStats {
let base = {
nodes: 1,
containers: 0,
max_depth: depth,
text_bytes: 0,
binary_bytes: 0,
encoded_bytes: encode(value).length(),
}
match value {
Text(text) => { ..base, text_bytes: text.length() }
Bytes(bytes) => { ..base, binary_bytes: bytes.length() }
Array(items) => {
let mut result = { ..base, containers: 1 }
for item in items {
result = add_document_stats(result, document_stats_at(item, depth + 1))
}
result
}
Map(entries) => {
let mut result = { ..base, containers: 1 }
for entry in entries {
result = add_document_stats(
result,
document_stats_at(entry.0, depth + 1),
)
result = add_document_stats(
result,
document_stats_at(entry.1, depth + 1),
)
}
result
}
Tag(_, item) => {
let child = document_stats_at(item, depth + 1)
{ ..add_document_stats(base, child), containers: 1 }
}
_ => base
}
}
///|
/// Compute node, container, depth, payload, and encoded-size metrics.
pub fn cbor_document_stats(value : CborValue) -> CborDocumentStats {
document_stats_at(value, 1)
}
///|
/// Return the encoded byte size of a document.
pub fn cbor_document_encoded_size(value : CborValue) -> Int {
encode(value).length()
}
///|
/// Return true when the document fits both structural budgets.
pub fn cbor_document_within_budget(
value : CborValue,
max_nodes : Int,
max_encoded_bytes : Int,
max_depth : Int,
) -> Bool {
let stats = cbor_document_stats(value)
stats.nodes <= max_nodes &&
stats.encoded_bytes <= max_encoded_bytes &&
stats.max_depth <= max_depth
}
///|
fn document_contains_text_at(value : CborValue, target : String) -> Bool {
match value {
Text(text) => text == target
Array(items) => {
let mut found = false
for item in items {
if document_contains_text_at(item, target) {
found = true
}
}
found
}
Map(entries) => {
let mut found = false
for entry in entries {
if document_contains_text_at(entry.0, target) ||
document_contains_text_at(entry.1, target) {
found = true
}
}
found
}
Tag(_, item) => document_contains_text_at(item, target)
_ => false
}
}
///|
/// Search both object keys and values for an exact text value.
pub fn cbor_document_contains_text(value : CborValue, target : String) -> Bool {
document_contains_text_at(value, target)
}
///|
fn redact_at(
value : CborValue,
fields : Array[String],
replacement : CborValue,
) -> CborValue {
match value {
Array(items) => {
let result = []
for item in items {
result.push(redact_at(item, fields, replacement))
}
Array(result)
}
Map(entries) => {
let result = []
for entry in entries {
match entry.0 {
Text(name) => {
let mut should_redact = false
for field in fields {
if field == name {
should_redact = true
}
}
result.push(
(
entry.0,
if should_redact {
replacement
} else {
redact_at(entry.1, fields, replacement)
},
),
)
}
_ =>
result.push(
(
redact_at(entry.0, fields, replacement),
redact_at(entry.1, fields, replacement),
),
)
}
}
Map(result)
}
Tag(tag, item) => Tag(tag, redact_at(item, fields, replacement))
_ => value
}
}
///|
/// Recursively replace values whose object key is in a sensitive-field list.
pub fn cbor_redact_fields(
value : CborValue,
fields : Array[String],
replacement : CborValue,
) -> CborValue {
redact_at(value, fields, replacement)
}
///|
fn collect_leaf_paths_at(
value : CborValue,
path : String,
max_depth : Int,
result : Array[String],
) -> Unit {
if cbor_value_depth(value) > max_depth {
result.push(path + "[depth-limit]")
return
}
match value {
Array(items) =>
for i = 0; i < items.length(); i = i + 1 {
collect_leaf_paths_at(
items[i],
path + "[" + i.to_string() + "]",
max_depth,
result,
)
}
Map(entries) =>
for entry in entries {
match entry.0 {
Text(name) =>
collect_leaf_paths_at(entry.1, path + "." + name, max_depth, result)
_ =>
collect_leaf_paths_at(entry.1, path + ".[key]", max_depth, result)
}
}
Tag(_, item) =>
collect_leaf_paths_at(item, path + ".tag", max_depth, result)
_ => result.push(path)
}
}
///|
/// Collect leaf paths for audit logs without exposing leaf contents.
pub fn cbor_document_leaf_paths(
value : CborValue,
max_depth : Int,
) -> Array[String] {
let result = []
collect_leaf_paths_at(
value,
"$",
if max_depth < 1 {
1
} else {
max_depth
},
result,
)
result
}
///|
/// Return the number of map entries, including nested objects.
pub fn cbor_document_field_count(value : CborValue) -> Int {
match value {
Map(entries) => {
let mut count = entries.length()
for entry in entries {
count = count + cbor_document_field_count(entry.0)
count = count + cbor_document_field_count(entry.1)
}
count
}
Array(items) => {
let mut count = 0
for item in items {
count = count + cbor_document_field_count(item)
}
count
}
Tag(_, item) => cbor_document_field_count(item)
_ => 0
}
}
///|
/// Return the number of byte-string payloads in a document.
pub fn cbor_document_binary_count(value : CborValue) -> Int {
match value {
Bytes(_) => 1
Array(items) => {
let mut count = 0
for item in items {
count = count + cbor_document_binary_count(item)
}
count
}
Map(entries) => {
let mut count = 0
for entry in entries {
count = count + cbor_document_binary_count(entry.0)
count = count + cbor_document_binary_count(entry.1)
}
count
}
Tag(_, item) => cbor_document_binary_count(item)
_ => 0
}
}
///|
/// Return the number of text values in a document.
pub fn cbor_document_text_count(value : CborValue) -> Int {
match value {
Text(_) => 1
Array(items) => {
let mut count = 0
for item in items {
count = count + cbor_document_text_count(item)
}
count
}
Map(entries) => {
let mut count = 0
for entry in entries {
count = count + cbor_document_text_count(entry.0)
count = count + cbor_document_text_count(entry.1)
}
count
}
Tag(_, item) => cbor_document_text_count(item)
_ => 0
}
}
///|
/// Return the largest byte-string payload length in a document.
pub fn cbor_document_largest_binary(value : CborValue) -> Int {
match value {
Bytes(bytes) => bytes.length()
Array(items) => {
let mut largest = 0
for item in items {
let size = cbor_document_largest_binary(item)
if size > largest {
largest = size
}
}
largest
}
Map(entries) => {
let mut largest = 0
for entry in entries {
let key_size = cbor_document_largest_binary(entry.0)
let value_size = cbor_document_largest_binary(entry.1)
if key_size > largest {
largest = key_size
}
if value_size > largest {
largest = value_size
}
}
largest
}
Tag(_, item) => cbor_document_largest_binary(item)
_ => 0
}
}
///|
/// Count numeric leaves, including integer and floating-point values.
pub fn cbor_document_numeric_count(value : CborValue) -> Int {
match value {
Integer(_) | Unsigned(_) | Float64(_) => 1
Array(items) => {
let mut count = 0
for item in items {
count = count + cbor_document_numeric_count(item)
}
count
}
Map(entries) => {
let mut count = 0
for entry in entries {
count = count + cbor_document_numeric_count(entry.0)
count = count + cbor_document_numeric_count(entry.1)
}
count
}
Tag(_, item) => cbor_document_numeric_count(item)
_ => 0
}
}
///|
/// Count tagged values to support application-specific extension audits.
pub fn cbor_document_tag_count(value : CborValue) -> Int {
match value {
Tag(_, item) => 1 + cbor_document_tag_count(item)
Array(items) => {
let mut count = 0
for item in items {
count = count + cbor_document_tag_count(item)
}
count
}
Map(entries) => {
let mut count = 0
for entry in entries {
count = count + cbor_document_tag_count(entry.0)
count = count + cbor_document_tag_count(entry.1)
}
count
}
_ => 0
}
}
///|
/// Create a stable summary for an observability event.
pub fn cbor_document_summary(value : CborValue) -> String {
let stats = cbor_document_stats(value)
"nodes=\{stats.nodes} containers=\{stats.containers} depth=\{stats.max_depth} encoded=\{stats.encoded_bytes} text_bytes=\{stats.text_bytes} binary_bytes=\{stats.binary_bytes}"
}
///|
/// Return a redacted preview bounded by a character budget.
pub fn cbor_redacted_preview(
value : CborValue,
fields : Array[String],
max_chars : Int,
) -> String {
cbor_query_preview(
cbor_redact_fields(value, fields, Text("")),
max_chars,
)
}