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}