Skip to main content

mlt_core/dump/
decode.rs

1//! Typed decode of a single data blob, for callers that render the values themselves.
2
3use serde::Serialize;
4#[cfg(feature = "unstable-v2")]
5use usize_cast::IntoUsize as _;
6
7use super::model::{BlobInfo, DecodeHint};
8use crate::Decoder;
9use crate::decoder::RawStream;
10
11/// One data blob decoded for display, capped at the caller's value count.
12///
13/// [`render`](super::render()) formats the same payloads as text.
14/// This is the structured form, for the wasm binding and anything else that lays out its own view.
15#[derive(Debug, Clone, PartialEq, Serialize)]
16#[serde(
17    tag = "kind",
18    rename_all = "camelCase",
19    rename_all_fields = "camelCase"
20)]
21pub enum DecodedBlob {
22    /// Values that survive an `f64` round-trip.
23    Numbers {
24        values: Vec<f64>,
25        /// Value count before the cap, or `None` when nothing was dropped.
26        truncated_from: Option<u32>,
27    },
28    /// 64-bit values, which an `f64` would round.
29    #[serde(rename = "bigints")]
30    BigInts {
31        values: Vec<i128>,
32        truncated_from: Option<u32>,
33    },
34    Bools {
35        values: Vec<bool>,
36        truncated_from: Option<u32>,
37    },
38    /// A byte payload that is valid UTF-8.
39    Text { value: String },
40    /// A byte payload that is not.
41    Binary { len: u32 },
42    /// The payload did not decode.
43    Error { message: String },
44}
45
46/// Decode `data` the way `info`'s hint says to, keeping at most `max_values` values.
47///
48/// `max_values` of `0` keeps all of them.
49/// A bytes payload is one value rather than a list, so it crosses whole.
50/// Never panics: a decode failure comes back as [`DecodedBlob::Error`].
51pub fn decode_blob(
52    info: BlobInfo,
53    data: &[u8],
54    max_values: usize,
55    dec: &mut Decoder,
56) -> DecodedBlob {
57    // Bound memory/time per blob, as `render` does.
58    dec.reset_budget();
59    let meta = info.meta;
60    match info.hint {
61        DecodeHint::Presence => match RawStream::new(meta, data).decode_bitvec(dec) {
62            Ok(bits) => bools(bits.iter().by_vals().collect(), max_values),
63            Err(e) => error(&e),
64        },
65        #[cfg(feature = "unstable-v2")]
66        DecodeHint::PresenceCoded(coding) => {
67            use crate::codecs::presence_coding::{Unmetered, read};
68            match read(data, meta.num_values, coding, &mut Unmetered) {
69                Ok((_, bits)) => bools(bits.iter().by_vals().collect(), max_values),
70                Err(e) => DecodedBlob::Error {
71                    message: e.to_string(),
72                },
73            }
74        }
75        #[cfg(feature = "unstable-v2")]
76        DecodeHint::PackedBits => {
77            let n = meta.num_values.into_usize();
78            let available = data.len() * 8;
79            if available < n {
80                return DecodedBlob::Error {
81                    message: format!("needs {n} bits, got {available}"),
82                };
83            }
84            bools(
85                (0..n).map(|i| data[i / 8] >> (i % 8) & 1 == 1).collect(),
86                max_values,
87            )
88        }
89        DecodeHint::Bool => match RawStream::new(meta, data).decode_bools(dec) {
90            Ok(v) => bools(v, max_values),
91            Err(e) => error(&e),
92        },
93        DecodeHint::I32 => numbers(
94            RawStream::new(meta, data).decode_ints::<i32>(dec),
95            max_values,
96        ),
97        DecodeHint::U32 => numbers(
98            RawStream::new(meta, data).decode_ints::<u32>(dec),
99            max_values,
100        ),
101        DecodeHint::F32 => numbers(
102            RawStream::new(meta, data).decode_floats::<f32>(dec),
103            max_values,
104        ),
105        DecodeHint::F64 => numbers(
106            RawStream::new(meta, data).decode_floats::<f64>(dec),
107            max_values,
108        ),
109        DecodeHint::I64 => bigints(
110            RawStream::new(meta, data).decode_ints::<i64>(dec),
111            max_values,
112        ),
113        DecodeHint::U64 => bigints(
114            RawStream::new(meta, data).decode_ints::<u64>(dec),
115            max_values,
116        ),
117        #[cfg(feature = "unstable-v2")]
118        DecodeHint::Alp(params) => bigints(
119            RawStream::new(meta, data).decode_alp_codes(params, dec),
120            max_values,
121        ),
122        DecodeHint::Bytes => match std::str::from_utf8(data) {
123            Ok(s) => DecodedBlob::Text {
124                value: s.to_string(),
125            },
126            Err(_) => DecodedBlob::Binary {
127                len: count(data.len()),
128            },
129        },
130    }
131}
132
133fn numbers<T: Into<f64>>(res: crate::MltResult<Vec<T>>, max_values: usize) -> DecodedBlob {
134    match res {
135        Ok(v) => {
136            let (v, truncated_from) = cap(v, max_values);
137            DecodedBlob::Numbers {
138                values: v.into_iter().map(Into::into).collect(),
139                truncated_from,
140            }
141        }
142        Err(e) => error(&e),
143    }
144}
145
146fn bigints<T: Into<i128>>(res: crate::MltResult<Vec<T>>, max_values: usize) -> DecodedBlob {
147    match res {
148        Ok(v) => {
149            let (v, truncated_from) = cap(v, max_values);
150            DecodedBlob::BigInts {
151                values: v.into_iter().map(Into::into).collect(),
152                truncated_from,
153            }
154        }
155        Err(e) => error(&e),
156    }
157}
158
159fn bools(v: Vec<bool>, max_values: usize) -> DecodedBlob {
160    let (values, truncated_from) = cap(v, max_values);
161    DecodedBlob::Bools {
162        values,
163        truncated_from,
164    }
165}
166
167fn error(e: &crate::MltError) -> DecodedBlob {
168    DecodedBlob::Error {
169        message: e.to_string(),
170    }
171}
172
173/// Keep the first `max_values`, reporting the original count when that drops any.
174fn cap<T>(mut values: Vec<T>, max_values: usize) -> (Vec<T>, Option<u32>) {
175    let total = values.len();
176    if max_values == 0 || total <= max_values {
177        return (values, None);
178    }
179    values.truncate(max_values);
180    (values, Some(count(total)))
181}
182
183fn count(n: usize) -> u32 {
184    u32::try_from(n).unwrap_or(u32::MAX)
185}