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::RawStream;
9use crate::{Decoder, GeometryType, MltResult};
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    /// Values that name something: each `names` entry is what the `values` entry stands for.
39    Enum {
40        values: Vec<u32>,
41        names: Vec<String>,
42        truncated_from: Option<u32>,
43    },
44    /// A byte payload that is valid UTF-8.
45    Text { value: String },
46    /// A byte payload that is not.
47    Binary { len: u32 },
48    /// The payload did not decode.
49    Error { message: String },
50}
51
52/// Decode `data` the way `info`'s hint says to, keeping at most `max_values` values.
53///
54/// `max_values` of `0` keeps all of them.
55/// A bytes payload is one value rather than a list, so it crosses whole.
56/// Never panics: a decode failure comes back as [`DecodedBlob::Error`].
57pub fn decode_blob(
58    info: BlobInfo,
59    data: &[u8],
60    max_values: usize,
61    dec: &mut Decoder,
62) -> DecodedBlob {
63    // Bound memory/time per blob, as `render` does.
64    dec.reset_budget();
65    let meta = info.meta;
66    match info.hint {
67        DecodeHint::Presence => match RawStream::new(meta, data).decode_bitvec(dec) {
68            Ok(bits) => bools(bits.iter().by_vals().collect(), max_values),
69            Err(e) => error(&e),
70        },
71        #[cfg(feature = "unstable-v2")]
72        DecodeHint::PresenceCoded(coding) => {
73            use crate::codecs::presence_coding::{Unmetered, read};
74            match read(data, meta.num_values, coding, &mut Unmetered) {
75                Ok((_, bits)) => bools(bits.iter().by_vals().collect(), max_values),
76                Err(e) => DecodedBlob::Error {
77                    message: e.to_string(),
78                },
79            }
80        }
81        #[cfg(feature = "unstable-v2")]
82        DecodeHint::PackedBits => {
83            let n = meta.num_values.into_usize();
84            let available = data.len() * 8;
85            if available < n {
86                return DecodedBlob::Error {
87                    message: format!("needs {n} bits, got {available}"),
88                };
89            }
90            bools(
91                (0..n).map(|i| data[i / 8] >> (i % 8) & 1 == 1).collect(),
92                max_values,
93            )
94        }
95        DecodeHint::Bool => match RawStream::new(meta, data).decode_bools(dec) {
96            Ok(v) => bools(v, max_values),
97            Err(e) => error(&e),
98        },
99        DecodeHint::GeometryType => geometry_types(
100            RawStream::new(meta, data).decode_ints::<u32>(dec),
101            max_values,
102        ),
103        DecodeHint::I32 => numbers(
104            RawStream::new(meta, data).decode_ints::<i32>(dec),
105            max_values,
106        ),
107        DecodeHint::U32 => numbers(
108            RawStream::new(meta, data).decode_ints::<u32>(dec),
109            max_values,
110        ),
111        DecodeHint::F32 => numbers(
112            RawStream::new(meta, data).decode_floats::<f32>(dec),
113            max_values,
114        ),
115        DecodeHint::F64 => numbers(
116            RawStream::new(meta, data).decode_floats::<f64>(dec),
117            max_values,
118        ),
119        DecodeHint::I64 => bigints(
120            RawStream::new(meta, data).decode_ints::<i64>(dec),
121            max_values,
122        ),
123        DecodeHint::U64 => bigints(
124            RawStream::new(meta, data).decode_ints::<u64>(dec),
125            max_values,
126        ),
127        #[cfg(feature = "unstable-v2")]
128        DecodeHint::Alp(params) => bigints(
129            RawStream::new(meta, data).decode_alp_codes(params, dec),
130            max_values,
131        ),
132        DecodeHint::Bytes => match std::str::from_utf8(data) {
133            Ok(s) => DecodedBlob::Text {
134                value: s.to_string(),
135            },
136            Err(_) => DecodedBlob::Binary {
137                len: count(data.len()),
138            },
139        },
140    }
141}
142
143fn numbers<T: Into<f64>>(res: MltResult<Vec<T>>, max_values: usize) -> DecodedBlob {
144    match res {
145        Ok(v) => {
146            let (v, truncated_from) = cap(v, max_values);
147            DecodedBlob::Numbers {
148                values: v.into_iter().map(Into::into).collect(),
149                truncated_from,
150            }
151        }
152        Err(e) => error(&e),
153    }
154}
155
156fn geometry_types(res: MltResult<Vec<u32>>, max_values: usize) -> DecodedBlob {
157    match res {
158        Ok(v) => {
159            let (values, truncated_from) = cap(v, max_values);
160            let names = values
161                .iter()
162                .map(|&v| {
163                    u8::try_from(v)
164                        .ok()
165                        .and_then(|b| GeometryType::try_from(b).ok())
166                        .map_or_else(|| format!("unknown({v})"), |t| t.to_string())
167                })
168                .collect();
169            DecodedBlob::Enum {
170                values,
171                names,
172                truncated_from,
173            }
174        }
175        Err(e) => error(&e),
176    }
177}
178
179fn bigints<T: Into<i128>>(res: MltResult<Vec<T>>, max_values: usize) -> DecodedBlob {
180    match res {
181        Ok(v) => {
182            let (v, truncated_from) = cap(v, max_values);
183            DecodedBlob::BigInts {
184                values: v.into_iter().map(Into::into).collect(),
185                truncated_from,
186            }
187        }
188        Err(e) => error(&e),
189    }
190}
191
192fn bools(v: Vec<bool>, max_values: usize) -> DecodedBlob {
193    let (values, truncated_from) = cap(v, max_values);
194    DecodedBlob::Bools {
195        values,
196        truncated_from,
197    }
198}
199
200fn error(e: &crate::MltError) -> DecodedBlob {
201    DecodedBlob::Error {
202        message: e.to_string(),
203    }
204}
205
206/// Keep the first `max_values`, reporting the original count when that drops any.
207fn cap<T>(mut values: Vec<T>, max_values: usize) -> (Vec<T>, Option<u32>) {
208    let total = values.len();
209    if max_values == 0 || total <= max_values {
210        return (values, None);
211    }
212    values.truncate(max_values);
213    (values, Some(count(total)))
214}
215
216fn count(n: usize) -> u32 {
217    u32::try_from(n).unwrap_or(u32::MAX)
218}