apollo_saphyr/lib.rs
1// Copyright 2015, Yuheng Chen.
2// Copyright 2023, Ethiraric.
3// See the LICENSE file at the top-level directory of this distribution.
4
5//! YAML 1.2 implementation in pure Rust.
6//!
7//! # Usage
8//!
9//! This crate is [on github](https://github.com/saphyr-rs/saphyr) and can be used by adding
10//! `saphyr` to the dependencies in your project's `Cargo.toml`:
11//! ```sh
12//! cargo add saphyr
13//! ```
14//!
15//! # Examples
16//! Parse a string into `Vec<Yaml>` and then serialize it as a YAML string.
17//!
18//! ```
19//! # extern crate apollo_saphyr as saphyr;
20//! use saphyr::{LoadableYamlNode, Yaml, YamlEmitter};
21//!
22//! let docs = Yaml::load_from_str("[1, 2, 3]").unwrap();
23//! let doc = &docs[0]; // select the first YAML document
24//! assert_eq!(doc[0].as_integer().unwrap(), 1); // access elements by index
25//!
26//! let mut out_str = String::new();
27//! let mut emitter = YamlEmitter::new(&mut out_str);
28//! emitter.dump(doc).unwrap(); // dump the YAML object to a String
29//! ```
30//!
31//! # YAML object types
32//!
33//! There are multiple YAML objects in this library which share most features but differ in
34//! usecase:
35//! - [`Yaml`]: The go-to YAML object. It contains YAML data and borrows from the input.
36//! - [`YamlOwned`]: An owned version of [`Yaml`]. It does not borrow from the input and can be
37//! used when tieing the object to the input is undesireable or would introduce unnecessary
38//! lifetimes.
39//! - [`MarkedYaml`]: A YAML object with added [`Marker`]s for the beginning and end of the YAML
40//! object in the input.
41//! - [`MarkedYamlOwned`]: An owned version of [`MarkedYaml`]. It does not borrow from the input
42//! and can be used when tieing the object to the input is undesireable or would introduce
43//! unnecessary lifetimes.
44//!
45//! All of these share the same inspection methods (`is_boolean`, `as_str`, `into_vec`, ...) with
46//! some variants between owned and borrowing versions.
47//!
48//! They also contain the same variants. [`Yaml`] and [`YamlOwned`] are `enums`, while annotated
49//! objects ([`MarkedYaml`], [`MarkedYamlOwned`]) are structures with a `.data` enum
50//! (see [`YamlData`], [`YamlDataOwned`]).
51//!
52//! # YAML Tags
53//! ## YAML Core Schema tags (`!!str`, `!!int`, `!!float`, ...)
54//! `saphyr` is aware of the [YAML Core Schema tags](https://yaml.org/spec/1.2.2/#103-core-schema)
55//! and will parse scalars accordingly. This is handled in [`Scalar::parse_from_cow_and_metadata`].
56//! Should a scalar be explicitly tagged with a tag whose handle is that of the Core Schema,
57//! `saphyr` will attempt to parse it as the given type. If parsing fails (e.g.: `!!int foo`), a
58//! [`BadValue`] will be returned. If however the tag is unknown, the scalar will be parsed as a
59//! string (e.g.: `!!unk 12`).
60//!
61//! Core Schema tags on collections are ignored, since the syntax disallows any ambiguity in
62//! parsing.
63//!
64//! Upon parsing, the core schema tags are not preserved. If you need the tags on scalar preserved,
65//! you may disable [`early_parse`]. This will cause all scalars to use the [`Representation`]
66//! variant which preserves the tags. There is currently no way to preserve Core Schema tags on
67//! collections.
68//!
69//! ## User-defined tags
70//! The YAML specification does not explicitly specify how user-defined tags should be parsed
71//! ([10.4](https://yaml.org/spec/1.2.2/#104-other-schemas)). `saphyr` is very conservative on this
72//! and will leave tags as-is. They are wrapped in a [`Tagged`] variant where you can freely
73//! inspect the tag alongside the tagged node.
74//!
75//! **The tagged node will be parsed as an untagged node.** What this means is that `13` will be
76//! parsed as an integer, `foo` as a string, ... etc. See the related discussion [on
77//! Github](https://github.com/saphyr-rs/saphyr/issues/4#issuecomment-2899433908) for more context
78//! on the decision.
79//!
80//! Examples:
81//! ```
82//! # extern crate apollo_saphyr as saphyr;
83//! # use saphyr::{LoadableYamlNode, Tag, Yaml};
84//! # let parse = |s| Yaml::load_from_str(s).unwrap().into_iter().next().unwrap();
85//! #
86//! assert!(matches!(parse("!custom 3"), Yaml::Tagged(_tag, node) if node.is_integer()));
87//! assert!(matches!(parse("!custom 'foo'"), Yaml::Tagged(_tag, node) if node.is_string()));
88//! assert!(matches!(parse("!custom foo"), Yaml::Tagged(_tag, node) if node.is_string()));
89//! assert!(matches!(parse("!custom ~"), Yaml::Tagged(_tag, node) if node.is_null()));
90//! assert!(matches!(parse("!custom '3'"), Yaml::Tagged(_tag, node) if node.is_string()));
91//! ```
92//!
93//! User-defined tags can be applied to any node, whether a collection or a scalar. They do not
94//! change the resolution behavior of inner nodes.
95//! ```
96//! # extern crate apollo_saphyr as saphyr;
97//! # use saphyr::{LoadableYamlNode, Tag, Yaml};
98//! # let parse = |s| Yaml::load_from_str(s).unwrap().into_iter().next().unwrap();
99//! #
100//! assert!(matches!(parse("!custom [foo]"), Yaml::Tagged(_tag, node) if node.is_sequence()));
101//! assert!(matches!(parse("!custom {a:b}"), Yaml::Tagged(_tag, node) if node.is_mapping()));
102//!
103//! let node = parse("!custom [1, foo, !!str 3, !custom 3]");
104//! let Yaml::Tagged(_, seq) = node else { panic!() };
105//! assert!(seq.is_sequence());
106//! assert!(seq[0].is_integer());
107//! assert!(seq[1].is_string());
108//! assert!(seq[2].is_string());
109//! assert!(matches!(&seq[3], Yaml::Tagged(_tag, node) if node.is_integer()));
110//! ```
111//!
112//! # Features
113//! **Note:** With all features disabled, this crate's MSRV is `1.65.0`.
114//!
115//! #### `encoding` (_enabled by default_)
116//! Enables encoding-aware decoding of Yaml documents.
117//!
118//! The MSRV for this feature is `1.70.0`.
119//!
120//! This feature is _not_ `no_std` compatible.
121//!
122//! [`MarkedYaml`]: crate::MarkedYaml
123//! [`MarkedYamlOwned`]: crate::MarkedYamlOwned
124//! [`Marker`]: crate::Marker
125//! [`YamlData`]: crate::YamlData
126//! [`YamlDataOwned`]: crate::YamlDataOwned
127//! [`BadValue`]: Yaml::BadValue
128//! [`Representation`]: Yaml::Representation
129//! [`Tagged`]: Yaml::Tagged
130//! [`early_parse`]: crate::YamlLoader::early_parse
131
132#![warn(missing_docs, clippy::pedantic)]
133#![no_std]
134
135#[macro_use]
136extern crate alloc;
137extern crate apollo_saphyr_parser as saphyr_parser;
138#[cfg(any(feature = "encoding", test))]
139extern crate std;
140
141#[macro_use]
142mod macros;
143
144mod annotated;
145mod char_traits;
146mod emitter;
147mod index;
148mod loader;
149mod scalar;
150mod yaml;
151mod yaml_owned;
152
153// Re-export main components.
154pub use crate::annotated::{
155 marked_yaml::MarkedYaml, marked_yaml_owned::MarkedYamlOwned, AnnotatedMapping,
156 AnnotatedMappingOwned, AnnotatedNode, AnnotatedNodeOwned, AnnotatedSequence,
157 AnnotatedSequenceOwned, AnnotatedYamlIter, YamlData, YamlDataOwned,
158};
159pub use crate::emitter::{EmitError, YamlEmitter};
160pub use crate::index::{Accessor, SafelyIndex, SafelyIndexMut};
161pub use crate::loader::{LoadError, LoadableYamlNode, YamlLoader};
162pub use crate::scalar::{parse_core_schema_fp, Scalar, ScalarOwned};
163pub use crate::yaml::{Mapping, Sequence, Yaml, YamlIter};
164pub use crate::yaml_owned::{MappingOwned, SequenceOwned, YamlOwned, YamlOwnedIter};
165
166#[cfg(feature = "encoding")]
167mod encoding;
168#[cfg(feature = "encoding")]
169pub use crate::encoding::{YAMLDecodingTrap, YAMLDecodingTrapFn, YamlDecoder};
170
171// Re-export `ScanError` as it is used as part of our public API and we want consumers to be able
172// to inspect it (e.g. perform a `match`). They wouldn't be able without it.
173pub use saphyr_parser::ScanError;
174// Re-export `Marker` which is used for annotated YAMLs.
175pub use saphyr_parser::Marker;
176// Re-export `ScalarStyle` and `Tag` which are used for representations.
177pub use saphyr_parser::{ScalarStyle, Tag};