Skip to main content

typst_library/loading/
cbor.rs

1use ciborium::de::Error;
2use ecow::eco_format;
3use typst_syntax::Spanned;
4
5use crate::diag::{At, LoadError, LoadedWithin, SourceResult};
6use crate::engine::Engine;
7use crate::foundations::{Bytes, Value, func, scope};
8use crate::loading::{DataSource, Load};
9
10/// Reads structured data from a CBOR file.
11///
12/// The file must contain a valid CBOR serialization. The CBOR values will be
13/// converted into corresponding Typst values as listed in the
14/// @cbor:conversion[table below].
15///
16/// The function returns a dictionary, an array or, depending on the CBOR file,
17/// another CBOR data type.
18///
19/// = #short-or-long[Conversion][Conversion details] <conversion>
20/// #docs-table(
21///   table.header[CBOR value][Converted into Typst],
22///
23///   [integer],
24///   [@int (or @float)],
25///
26///   [bytes],
27///   [@bytes],
28///
29///   [float],
30///   [@float],
31///
32///   [text],
33///   [@str],
34///
35///   [bool],
36///   [@bool],
37///
38///   [null],
39///   [`{none}`],
40///
41///   [array],
42///   [@array],
43///
44///   [map],
45///   [@dictionary],
46/// )
47///
48/// #docs-table(
49///   table.header[Typst value][Converted into CBOR],
50///
51///   [types that can be converted from CBOR],
52///   [corresponding CBOR value],
53///
54///   [@symbol],
55///   [text],
56///
57///   [@content],
58///   [a map describing the content],
59///
60///   [other types (@length, etc.)],
61///   [text via @repr],
62/// )
63///
64/// == Notes <notes>
65/// - Be aware that CBOR integers larger than 2#super[63]-1 or smaller
66///   than -2#super[63] will be converted to floating point numbers, which may
67///   result in an approximative value.
68///
69/// - CBOR tags are not supported, and an error will be thrown.
70///
71/// - The `repr` function is @repr:debugging-only[for debugging purposes only],
72///   and its output is not guaranteed to be stable across Typst versions.
73#[func(scope, title = "CBOR")]
74pub fn cbor(
75    engine: &mut Engine,
76    /// A path to a CBOR file or raw CBOR bytes.
77    source: Spanned<DataSource>,
78) -> SourceResult<Value> {
79    let loaded = source.load(engine.world)?;
80    ciborium::from_reader(loaded.data.as_slice())
81        .map_err(format_cbor_error)
82        .within(&loaded)
83}
84
85/// Format a user-facing error encountered while parsing a CBOR file
86/// ([`ciborium::de::Error`]'s [`Display`](std::fmt::Display) implementation
87/// just forwards to [`Debug`]).
88fn format_cbor_error(error: Error<std::io::Error>) -> LoadError {
89    LoadError::binary(
90        "failed to parse CBOR",
91        typst_utils::display(|f| match &error {
92            Error::Io(e) => write!(f, "IO error: {e}"),
93            Error::Syntax(_) => f.write_str("syntax error"),
94            Error::Semantic(_, s) => f.write_str(s),
95            Error::RecursionLimitExceeded => f.write_str("recursion limit exceeded"),
96        }),
97    )
98}
99
100#[scope]
101impl cbor {
102    /// Encode structured data into CBOR bytes.
103    #[func(title = "Encode CBOR")]
104    pub fn encode(
105        /// Value to be encoded.
106        value: Spanned<Value>,
107    ) -> SourceResult<Bytes> {
108        let Spanned { v: value, span } = value;
109        let mut res = Vec::new();
110        ciborium::into_writer(&value, &mut res)
111            .map(|_| Bytes::new(res))
112            .map_err(|err| eco_format!("failed to encode value as CBOR ({err})"))
113            .at(span)
114    }
115}