marked-yaml-0.8.0/.cargo_vcs_info.json0000644000000001510000000000100132410ustar { "git": { "sha1": "ace1cc4f8af7c38b7c07ec5ed35678955fbb2759" }, "path_in_vcs": "marked-yaml" }marked-yaml-0.8.0/CHANGELOG.md000064400000000000000000000002101046102023000136360ustar 00000000000000# marked-yaml changelog ## [Unreleased] ### Bug fixes * serde: Empty scalars can now be deserialized as a mapping, sequence or unit. marked-yaml-0.8.0/Cargo.lock0000644000000073000000000000100112170ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "arraydeque" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "hashbrown" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "foldhash", ] [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ "hashbrown", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "marked-yaml" version = "0.8.0" dependencies = [ "doc-comment", "hashlink", "serde", "serde_path_to_error", "yaml-rust2", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_path_to_error" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ "itoa", "serde", ] [[package]] name = "syn" version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "yaml-rust2" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18b783b2c2789414f8bb84ca3318fc9c2d7e7be1c22907d37839a58dedb369d3" dependencies = [ "arraydeque", "encoding_rs", "hashlink", ] marked-yaml-0.8.0/Cargo.toml0000644000000032400000000000100112410ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "marked-yaml" version = "0.8.0" authors = ["Daniel Silverstone "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A simplified YAML structure with provenance spans" homepage = "https://github.com/kinnison/marked-yaml/" readme = "README.md" keywords = [ "yaml", "provenance", ] categories = [ "data-structures", "encoding", "parsing", ] license = "MIT" repository = "https://github.com/kinnison/marked-yaml.git" [package.metadata.docs.rs] all-features = true [badges.maintenance] status = "experimental" [features] default = [] serde = ["dep:serde"] serde-path = [ "serde", "dep:serde_path_to_error", ] [lib] name = "marked_yaml" path = "src/lib.rs" [[test]] name = "character" path = "tests/character.rs" [[test]] name = "serde" path = "tests/serde.rs" [dependencies.doc-comment] version = "0.3" [dependencies.hashlink] version = "0.10.0" [dependencies.serde] version = "1.0.194" features = ["derive"] optional = true [dependencies.serde_path_to_error] version = "0.1.16" optional = true [dependencies.yaml-rust] version = "0.10.2" package = "yaml-rust2" marked-yaml-0.8.0/Cargo.toml.orig000064400000000000000000000015601046102023000147250ustar 00000000000000[package] name = "marked-yaml" version = "0.8.0" authors = ["Daniel Silverstone "] edition = "2021" description = "A simplified YAML structure with provenance spans" homepage = "https://github.com/kinnison/marked-yaml/" repository = "https://github.com/kinnison/marked-yaml.git" readme = "README.md" keywords = ["yaml", "provenance"] categories = ["data-structures", "encoding", "parsing"] license = "MIT" [badges] maintenance = { status = "experimental" } [features] default = [] serde = ["dep:serde"] serde-path = ["serde", "dep:serde_path_to_error"] [dependencies] doc-comment = "0.3" yaml-rust = { version = "0.10.2", package = "yaml-rust2" } hashlink = "0.10.0" serde = { version = "1.0.194", optional = true, features = ["derive"] } serde_path_to_error = { version = "0.1.16", optional = true } [package.metadata.docs.rs] all-features = true marked-yaml-0.8.0/README.md000064400000000000000000000043531046102023000133200ustar 00000000000000# Marked YAML This library builds atop [`yaml-rust2`][yaml-rust2] to provide a YAML AST which includes the marks for where the YAML data comes from. It explicitly operates at a low level, providing only the _base_ **safe** YAML types (i.e. the vanilla tags `tag:yaml.org,2002:seq`, `tag:yaml.org,2002:map`, and `tag:yaml.org,2002:str`) [yaml-rust2]: https://crates.io/crates/yaml-rust2 The subset of YAML which is supported is quite deliberately limited in order that users of this crate will implicitly discourage complex use of YAML which is harder to manage user expectations with. As an example, the mapping type in this crate explicitly only permits scalars as keys, and since all scalars are treated as strings, mappings always have string keys. The primary value of this kind of representation of YAML data is to allow applications which want to be very explicit about where input came from an opportunity to do this in a way which normal YAML parsers do not allow. # Using Marked YAML Currently this library only supports loading YAML from strings, but this is sufficient for most users' purposes. We would not recommend an un-streamed processing engine for massive data anyway. To load some YAML you simply need to: ```rust let node = marked_yaml::parse_yaml(0, r#" toplevel: must be a mapping but: - it - may - contain lists and: mappings: are permitted as: sub-mappings "#); assert!(node.is_ok()); ``` Parsing a valid YAML file may fail because `marked_yaml` adds some additional constraints: - The top level of the YAML **MUST** be one of a mapping or a sequence. This is controlled by the loader options. - Mapping keys **MUST** be scalars (strings). - Aliases and anchors **MAY NOT** be used (though this limit may be lifted in the future). In addition, you can convert between `marked_yaml::Node` and `yaml_rust::Yaml` though doing so will not give you any useful markers. # Using `serde` with `marked_yaml` If you want to use `marked-yaml` with your existing `serde` applications, you can enable the `serde-path` feature. If you do not need the errors produced by the `marked-yaml` deserializer to include nice paths to any problem, along with ensuring the marker for the problem area is populated in any errors, use the simpler `serde` feature. marked-yaml-0.8.0/examples/everything.yaml000064400000000000000000000010041046102023000167150ustar 00000000000000# This is an example document which exercises all of the properties of # the marked YAML parser. It covers scalars, mappings, and sequences, and # verifies that everything which *ought* to be supported is. simple: scalar boolean1: "true" boolean2: false integer: "1234" float: 12.34 nullvalue: null mapping: nesting: is quite: quite: possible plainsequence: - simple - values - are - supported heterogenous: - multiple - types: of - [values, are supported] - { such: as, these: values } marked-yaml-0.8.0/src/lib.rs000064400000000000000000000050551046102023000137440ustar 00000000000000//! Marked YAML //! =========== //! //! Currently this library only supports parsing YAML from strings, //! but this is sufficient for most users' purposes. We would not //! recommend an un-streamed processing engine for massive data anyway. //! //! To parse some YAML you simply need to: //! //! ``` //! let node = marked_yaml::parse_yaml(0, r#" //! toplevel: must be a mapping //! but: //! - it //! - may //! - contain lists //! and: //! mappings: are permitted //! as: sub-mappings //! "#); //! assert!(node.is_ok()); //! ``` //! //! Parsing a valid YAML string may fail because `marked_yaml` adds some //! additional constraints: //! //! * The top level of the YAML **MUST** be a mapping or a sequence. //! * Mapping keys **MUST** be scalars (strings). //! * Aliases and anchors **MAY NOT** be used (though this limit may be lifted in the future). //! //! In addition, you can convert between `marked_yaml::Node` and `yaml_rust::Yaml` //! though doing so will not give you any useful markers. #![cfg_attr( feature = "serde", doc = r#" ## Serde Should you so choose, you may use serde to deserialise YAML strings directly into structures, any amount of which could be annotated with the [`Spanned`] type to capture information about where the value came from in the input. ``` # use marked_yaml::{from_yaml, Marker, Spanned}; # use std::collections::HashMap; let YAML = "Daniel: Author\nUser: Not Author\n"; let roles: HashMap, Spanned> = from_yaml(0, YAML).unwrap(); assert_eq!(roles["Daniel"], "Author"); assert_eq!(roles["User"].span().start().copied(), Some(Marker::new(0, 21, 2, 7))); ``` You do not have to have all values [`Spanned`], and you can deserialize from an already parsed set of nodes with [`from_node`] instead. Empty scalars can be deserialized to empty sequences, maps, the unit type `()` and structs with a `#[serde(default)]` attribute. ### Error spans and paths If you want to have the error spans populated, along with string-form paths to errors in your YAML, then you can use the `serde-path` feature. "# )] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_cfg))] pub mod loader; pub mod types; #[doc(inline)] pub use loader::{parse_yaml, parse_yaml_with_options, LoadError, LoaderOptions}; #[doc(inline)] pub use types::{Marker, Node, Span}; #[cfg(feature = "serde")] #[doc(hidden)] pub mod spanned_serde; #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] #[doc(inline)] pub use spanned_serde::{ from_node, from_yaml, from_yaml_with_options, Error, FromNodeError, FromYamlError, Spanned, }; marked-yaml-0.8.0/src/loader.rs000064400000000000000000000576561046102023000144620ustar 00000000000000//! Loading YAML //! use crate::types::*; use hashlink::linked_hash_map::Entry; use yaml_rust::parser::{Event, MarkedEventReceiver, Parser}; use yaml_rust::scanner::ScanError; use yaml_rust::scanner::{Marker as YamlMarker, TScalarStyle}; use std::error::Error; use std::fmt::{self, Display}; /// An error indicating that a duplicate key was detected in a mapping #[derive(Debug, PartialEq, Eq)] pub struct DuplicateKeyInner { /// The first key pub prev_key: MarkedScalarNode, /// The second key pub key: MarkedScalarNode, } /// Errors which can occur during loading of YAML #[derive(Debug, PartialEq, Eq)] pub enum LoadError { /// Something other than a mapping detected at the top level TopLevelMustBeMapping(Marker), /// Something other than a sequence detected at the top level TopLevelMustBeSequence(Marker), /// Unexpected definition of anchor UnexpectedAnchor(Marker), /// Mapping keys must be scalars MappingKeyMustBeScalar(Marker), /// An explicit tag was detected UnexpectedTag(Marker), /// A YAML scanner error occured ScanError(Marker, ScanError), /// A duplicate key was detected in a mapping DuplicateKey(Box), } /// Options for loading YAML /// /// Default options ([`LoaderOptions::default()`]) are: /// /// - Permit duplicate keys /// #[derive(Debug)] pub struct LoaderOptions { error_on_duplicate_keys: bool, prevent_coercion: bool, toplevel_is_mapping: bool, lowercase_keys: bool, } impl Default for LoaderOptions { fn default() -> Self { Self { error_on_duplicate_keys: false, prevent_coercion: false, toplevel_is_mapping: true, lowercase_keys: false, } } } impl LoaderOptions { /// Enable errors on duplicate keys /// /// If enabled, duplicate keys in mappings will cause an error. /// If disabled, the last key/value pair will be used. pub fn error_on_duplicate_keys(self, enable: bool) -> Self { Self { error_on_duplicate_keys: enable, ..self } } /// Prevent coercion of scalar nodes /// /// If you want to disable things like [`.as_bool()`](crate::types::MarkedScalarNode::as_bool()) /// then you can call this and set coercion to be prevented. pub fn prevent_coercion(self, prevent: bool) -> Self { Self { prevent_coercion: prevent, ..self } } /// Require that the top level is a mapping node /// /// This is the default, but you can call this to be explicit. pub fn toplevel_mapping(self) -> Self { Self { toplevel_is_mapping: true, ..self } } /// Require that the top level is a sequence node /// /// Without calling this, the top level of the YAML is must be a mapping node pub fn toplevel_sequence(self) -> Self { Self { toplevel_is_mapping: false, ..self } } /// Whether or not to force-lowercase mapping keys when loading /// /// By default, the loader will leave key names alone, but in some /// cases it can be preferable to normalise them to lowercase pub fn lowercase_keys(self, force_lowercase: bool) -> Self { Self { lowercase_keys: force_lowercase, ..self } } } impl Display for LoadError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use LoadError::*; #[allow(deprecated)] match self { TopLevelMustBeMapping(m) => write!(f, "{}: Top level must be a mapping", m), TopLevelMustBeSequence(m) => write!(f, "{}: Top level must be a sequence", m), UnexpectedAnchor(m) => write!(f, "{}: Unexpected definition of anchor", m), MappingKeyMustBeScalar(m) => write!(f, "{}: Keys in mappings must be scalar", m), UnexpectedTag(m) => write!(f, "{}: Unexpected use of YAML tag", m), DuplicateKey(inner) => { let DuplicateKeyInner { prev_key, key } = inner.as_ref(); write!( f, "Duplicate key \"{}\" in mapping at {} and {}", prev_key.as_str(), prev_key .span() .start() .map(ToString::to_string) .unwrap_or_else(|| "?".to_string()), key.span() .start() .map(ToString::to_string) .unwrap_or_else(|| "?".to_string()), ) } ScanError(m, e) => { // e.description() is deprecated but it's the only way to get // the exact info we want out of yaml-rust write!(f, "{}: {}", m, e.description()) } } } } impl Error for LoadError {} #[derive(Debug, PartialEq, Eq)] enum LoaderState { Initial, StartStream, StartDocument, MappingWaitingOnKey(Marker, MappingHash), MappingWaitingOnValue(Marker, MappingHash, MarkedScalarNode), SequenceWaitingOnValue(Marker, Vec), Finished(Node), Error(LoadError), } use LoaderState::*; impl LoaderState { fn is_error(&self) -> bool { matches!(self, Error(_)) } } struct MarkedLoader { source: usize, state_stack: Vec, options: LoaderOptions, } impl MarkedEventReceiver for MarkedLoader { fn on_event(&mut self, ev: Event, mark: YamlMarker) { // Short-circuit if the state stack is in error if self.state_stack[self.state_stack.len() - 1].is_error() { return; } let mark = self.marker(mark); let curstate = self .state_stack .pop() .expect("State stack became unbalanced"); let newstate = match ev { Event::Alias(_) => unreachable!(), Event::StreamStart => { assert_eq!(curstate, Initial); StartStream } Event::DocumentStart => { assert_eq!(curstate, StartStream); StartDocument } Event::MappingStart(aid, tag) => { if tag.is_some() { Error(LoadError::UnexpectedTag(mark)) } else if aid == 0 { match curstate { StartDocument => { if self.options.toplevel_is_mapping { MappingWaitingOnKey(mark, MappingHash::new()) } else { Error(LoadError::TopLevelMustBeSequence(mark)) } } MappingWaitingOnKey(_, _) => Error(LoadError::MappingKeyMustBeScalar(mark)), MappingWaitingOnValue(_, _, _) => { self.state_stack.push(curstate); MappingWaitingOnKey(mark, MappingHash::new()) } SequenceWaitingOnValue(_, _) => { self.state_stack.push(curstate); MappingWaitingOnKey(mark, MappingHash::new()) } _ => unreachable!(), } } else { Error(LoadError::UnexpectedAnchor(mark)) } } Event::MappingEnd => match curstate { MappingWaitingOnKey(startmark, map) => { let span = Span::new_with_marks(startmark, mark); let node = Node::from(MarkedMappingNode::new(span, map)); if let Some(topstate) = self.state_stack.pop() { match topstate { MappingWaitingOnValue(mark, mut map, key) => { match map.entry(key.clone()) { Entry::Occupied(entry) if self.options.error_on_duplicate_keys => { Error(LoadError::DuplicateKey(Box::new( DuplicateKeyInner { prev_key: entry.key().clone(), key, }, ))) } _ => { map.insert(key, node); MappingWaitingOnKey(mark, map) } } } SequenceWaitingOnValue(mark, mut list) => { list.push(node); SequenceWaitingOnValue(mark, list) } _ => unreachable!(), } } else { Finished(node) } } _ => unreachable!(), }, Event::SequenceStart(aid, tag) => { if tag.is_some() { Error(LoadError::UnexpectedTag(mark)) } else if aid == 0 { match curstate { StartDocument => { if self.options.toplevel_is_mapping { Error(LoadError::TopLevelMustBeMapping(mark)) } else { SequenceWaitingOnValue(mark, Vec::new()) } } MappingWaitingOnKey(_, _) => Error(LoadError::MappingKeyMustBeScalar(mark)), mv @ MappingWaitingOnValue(_, _, _) => { self.state_stack.push(mv); SequenceWaitingOnValue(mark, Vec::new()) } sv @ SequenceWaitingOnValue(_, _) => { self.state_stack.push(sv); SequenceWaitingOnValue(mark, Vec::new()) } _ => unreachable!(), } } else { Error(LoadError::UnexpectedAnchor(mark)) } } Event::SequenceEnd => match curstate { SequenceWaitingOnValue(startmark, list) => { let span = Span::new_with_marks(startmark, mark); let node = Node::from(MarkedSequenceNode::new(span, list)); if let Some(topstate) = self.state_stack.pop() { match topstate { MappingWaitingOnValue(mark, mut map, key) => { match map.entry(key.clone()) { Entry::Occupied(entry) if self.options.error_on_duplicate_keys => { Error(LoadError::DuplicateKey(Box::new( DuplicateKeyInner { prev_key: entry.key().clone(), key, }, ))) } _ => { map.insert(key, node); MappingWaitingOnKey(mark, map) } } } SequenceWaitingOnValue(mark, mut list) => { list.push(node); SequenceWaitingOnValue(mark, list) } _ => unreachable!(), } } else { Finished(node) } } _ => unreachable!(), }, Event::DocumentEnd => match curstate { Finished(_) => curstate, _ => unreachable!(), }, Event::StreamEnd => match curstate { StartStream => Finished(Node::from(MarkedMappingNode::new_empty( Span::new_with_marks(mark, mark), ))), Finished(_) => curstate, _ => unreachable!(), }, Event::Scalar(val, kind, aid, tag) => { if aid == 0 { if tag.is_some() { Error(LoadError::UnexpectedTag(mark)) } else { let span = Span::new_start(mark); let val = if matches!(curstate, MappingWaitingOnKey(_, _)) && self.options.lowercase_keys { val.to_lowercase() } else { val }; let mut node = MarkedScalarNode::new(span, val); if self.options.prevent_coercion { node.set_coerce(matches!(kind, TScalarStyle::Plain)); } match curstate { MappingWaitingOnKey(mark, map) => { MappingWaitingOnValue(mark, map, node) } MappingWaitingOnValue(mark, mut map, key) => { match map.entry(key.clone()) { Entry::Occupied(entry) if self.options.error_on_duplicate_keys => { Error(LoadError::DuplicateKey(Box::new( DuplicateKeyInner { prev_key: entry.key().clone(), key, }, ))) } _ => { map.insert(key, Node::from(node)); MappingWaitingOnKey(mark, map) } } } SequenceWaitingOnValue(mark, mut list) => { list.push(Node::from(node)); SequenceWaitingOnValue(mark, list) } StartDocument => Error(LoadError::TopLevelMustBeMapping(mark)), _ => unreachable!(), } } } else { Error(LoadError::UnexpectedAnchor(mark)) } } Event::Nothing => unreachable!(), }; self.state_stack.push(newstate); } } impl MarkedLoader { fn new(source: usize, options: LoaderOptions) -> Self { Self { source, state_stack: vec![Initial], options, } } fn marker(&self, mark: YamlMarker) -> Marker { Marker::new(self.source, mark.index(), mark.line(), mark.col() + 1) } fn finish(mut self) -> Result { let top = self.state_stack.pop(); match top.expect("YAML parser state stack unexpectedly empty") { Finished(n) => Ok(n), Error(e) => Err(e), _ => unreachable!(), } } } /// Parse YAML from a string and return a Node representing /// the content. /// /// When parsing YAML, the source is stored into all markers which are /// in the node spans. This means that later if you only have a node, /// you can determine which source it came from without needing complex /// lifetimes to bind strings or other non-copy data to nodes. /// /// This function requires that the top level be a mapping, but the returned /// type here is the generic Node enumeration to make it potentially easier /// for callers to use. Regardless, it's always possible to treat the /// returned node as a mapping node without risk of panic. /// /// If you wish to load a sequence instead of a mapping, then you will /// need to use [`parse_yaml_with_options`] to request that. /// /// ``` /// # use marked_yaml::*; /// let node = parse_yaml(0, include_str!("../examples/everything.yaml")) /// .unwrap() /// .as_mapping() /// .unwrap(); /// ``` pub fn parse_yaml(source: usize, yaml: S) -> Result where S: AsRef, { let options = LoaderOptions::default(); parse_yaml_with_options(source, yaml, options) } /// Parse YAML from a string and return a Node representing /// the content. /// /// Takes an additional LoaderOptions struct to control the behavior of the loader. /// /// This is the way to parse a file with a top-level sequence instead of a mapping /// node. /// /// See `parse_yaml` for more information. pub fn parse_yaml_with_options( source: usize, yaml: S, options: LoaderOptions, ) -> Result where S: AsRef, { let mut loader = MarkedLoader::new(source, options); let mut parser = Parser::new(yaml.as_ref().chars()); parser.load(&mut loader, false).map_err(|se| { let mark = loader.marker(*se.marker()); LoadError::ScanError(mark, se) })?; loader.finish() } #[cfg(test)] mod test { use super::*; #[test] fn smoke_basics() { let node = parse_yaml(0, "{}").unwrap(); assert!(node.as_mapping().is_some()); } #[test] fn load_everything() { let node = parse_yaml(0, include_str!("../examples/everything.yaml")).unwrap(); let map = node.as_mapping().unwrap(); assert_eq!(map.get_scalar("simple").unwrap().as_str(), "scalar"); assert_eq!(map.get_scalar("boolean1").unwrap().as_bool(), Some(true)); assert_eq!(map.get_scalar("boolean2").unwrap().as_bool(), Some(false)); } #[test] fn prevent_coercion() { let node = parse_yaml_with_options( 0, include_str!("../examples/everything.yaml"), LoaderOptions::default().prevent_coercion(true), ) .unwrap(); let map = node.as_mapping().unwrap(); assert_eq!(map.get_scalar("simple").unwrap().as_str(), "scalar"); assert_eq!(map.get_scalar("boolean1").unwrap().as_str(), "true"); assert_eq!(map.get_scalar("boolean1").unwrap().as_bool(), None); assert_eq!(map.get_scalar("boolean2").unwrap().as_str(), "false"); assert_eq!(map.get_scalar("boolean2").unwrap().as_bool(), Some(false)); assert_eq!(map.get_scalar("integer").unwrap().as_str(), "1234"); assert_eq!(map.get_scalar("integer").unwrap().as_i32(), None); assert_eq!(map.get_scalar("float").unwrap().as_str(), "12.34"); assert_eq!(map.get_scalar("float").unwrap().as_f32(), Some(12.34)); } #[test] fn toplevel_is_empty() { let node = parse_yaml(0, "").unwrap(); let map = node.as_mapping().unwrap(); assert!(map.is_empty()); } #[test] fn toplevel_is_empty_inline() { let node = parse_yaml(0, "{}").unwrap(); let map = node.as_mapping().unwrap(); assert!(map.is_empty()); } #[test] fn toplevel_is_scalar() { let err = parse_yaml(0, "foo"); assert_eq!( err, Err(LoadError::TopLevelMustBeMapping(Marker::new(0, 0, 1, 1))) ); assert!(format!("{}", err.err().unwrap()).contains("1:1: ")); } #[test] fn toplevel_is_sequence() { assert_eq!( parse_yaml(0, "[]"), Err(LoadError::TopLevelMustBeMapping(Marker::new(0, 0, 1, 1))) ); } #[test] fn duplicate_key() { let err = parse_yaml_with_options( 0, "{foo: bar, foo: baz}", LoaderOptions::default().error_on_duplicate_keys(true), ); assert_eq!( err, Err(LoadError::DuplicateKey(Box::new(DuplicateKeyInner { prev_key: MarkedScalarNode::new(Span::new_start(Marker::new(0, 0, 1, 1)), "foo"), key: MarkedScalarNode::new(Span::new_start(Marker::new(0, 10, 1, 11)), "foo") }))) ); assert_eq!( format!("{}", err.err().unwrap()), "Duplicate key \"foo\" in mapping at 1:2 and 1:12" ); // Without error_on_duplicate_keys, the last key wins let node = parse_yaml(0, "{foo: bar, foo: baz}").unwrap(); let map = node.as_mapping().unwrap(); assert_eq!(map.get_scalar("foo").unwrap().as_str(), "baz"); } #[test] fn unexpected_anchor() { let err = parse_yaml(0, "&foo {}"); assert_eq!( err, Err(LoadError::UnexpectedAnchor(Marker::new(0, 5, 1, 6))) ); assert!(format!("{}", err.err().unwrap()).starts_with("1:6: ")); } #[test] fn unexpected_anchor2() { assert_eq!( parse_yaml(0, "{bar: &foo []}"), Err(LoadError::UnexpectedAnchor(Marker::new(0, 11, 1, 12))) ); } #[test] fn unexpected_anchor3() { assert_eq!( parse_yaml(0, "{bar: &foo susan}"), Err(LoadError::UnexpectedAnchor(Marker::new(0, 11, 1, 12))) ); } #[test] fn mapping_key_mapping() { let err = parse_yaml(0, "{? {} : {}}"); assert_eq!( err, Err(LoadError::MappingKeyMustBeScalar(Marker::new(0, 3, 1, 4))) ); assert!(format!("{}", err.err().unwrap()).starts_with("1:4: ")); } #[test] fn mapping_key_sequence() { assert_eq!( parse_yaml(0, "{? [] : {}}"), Err(LoadError::MappingKeyMustBeScalar(Marker::new(0, 3, 1, 4))) ); } #[test] fn unexpected_tag() { let err = parse_yaml(0, "{foo: !!str bar}"); assert_eq!( err, Err(LoadError::UnexpectedTag(Marker::new(0, 12, 1, 13))) ); assert!(format!("{}", err.err().unwrap()).starts_with("1:13: ")); } #[test] fn nested_mapping_key_mapping() { assert_eq!( parse_yaml(0, "{foo: {? [] : {}}}"), Err(LoadError::MappingKeyMustBeScalar(Marker::new(0, 9, 1, 10))) ); } #[test] fn malformed_yaml_for_scanerror() { let err = parse_yaml(0, "{"); assert!(err.is_err()); assert!(format!("{}", err.err().unwrap()).starts_with("2:1: ")); } #[test] fn toplevel_sequence_wanted() { let node = parse_yaml_with_options(0, "[yaml]", LoaderOptions::default().toplevel_sequence()) .unwrap(); assert!(node.as_sequence().is_some()); } #[test] fn toplevel_sequence_wanted_got_mapping() { assert_eq!( parse_yaml_with_options(0, "{}", LoaderOptions::default().toplevel_sequence()), Err(LoadError::TopLevelMustBeSequence(Marker::new(0, 0, 1, 1))) ); } #[test] fn lowercase_keys() { let node = parse_yaml_with_options( 0, "KEY: VALUE", LoaderOptions::default().lowercase_keys(false), ) .unwrap(); assert!(node.as_mapping().unwrap().contains_key("KEY")); assert!(!node.as_mapping().unwrap().contains_key("key")); let node = parse_yaml_with_options( 0, "KEY: VALUE", LoaderOptions::default().lowercase_keys(true), ) .unwrap(); assert!(!node.as_mapping().unwrap().contains_key("KEY")); assert!(node.as_mapping().unwrap().contains_key("key")); } } marked-yaml-0.8.0/src/spanned_serde.rs000064400000000000000000001406461046102023000160160ustar 00000000000000//! Serde support for marked data deserialisation use std::{ borrow::Borrow, fmt, hash::Hash, iter::Peekable, marker::PhantomData, num::{ParseFloatError, ParseIntError}, ops::Deref, }; use serde::{ de::{ value::BorrowedStrDeserializer, DeserializeOwned, EnumAccess, IntoDeserializer, MapAccess, SeqAccess, Unexpected, VariantAccess, Visitor, }, forward_to_deserialize_any, Deserialize, Deserializer, Serialize, }; use crate::{ types::{MarkedMappingNode, MarkedScalarNode, MarkedSequenceNode}, LoaderOptions, Marker, Node, Span, }; /// Wrapper which can be used when deserialising data from [`Node`] /// /// You must use a compatible deserializer if you want to deserialize these values. /// /// ``` /// use marked_yaml::{from_yaml, Spanned}; /// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct MyStuff { /// num: Spanned, /// } /// let stuff: MyStuff = from_yaml(0, "num: 12").unwrap(); /// ``` /// /// You can also serialize these values, /// however when serializing you will lose the span information so do not expect /// to round-trip these values. #[derive(Clone, Debug)] pub struct Spanned { span: Span, inner: T, } impl Spanned { /// Wrap an instance of something with the given span /// /// ``` /// # use marked_yaml::{Spanned, Span}; /// let spanned = Spanned::new(Span::new_blank(), "Hello World"); /// ``` pub fn new(span: Span, inner: T) -> Self { Self { span, inner } } /// The span associated with this value /// /// ``` /// # use marked_yaml::{Spanned, Span, Marker}; /// # let span = Span::new_start(Marker::new(0, 1, 1, 2)); /// let spanned = Spanned::new(span, "Hello World"); /// assert_eq!(spanned.span(), &span); /// ``` pub fn span(&self) -> &Span { &self.span } } impl Deref for Spanned { type Target = T; fn deref(&self) -> &Self::Target { &self.inner } } impl PartialEq for Spanned where T: PartialEq, { fn eq(&self, other: &Self) -> bool { self.inner == other.inner } } impl PartialEq for Spanned where T: PartialEq, { fn eq(&self, other: &T) -> bool { (&self.inner as &dyn PartialEq).eq(other) } } impl PartialEq<&str> for Spanned { fn eq(&self, other: &&str) -> bool { self.inner == *other } } impl Eq for Spanned where T: Eq {} impl Hash for Spanned where T: Hash, { fn hash(&self, state: &mut H) { self.inner.hash(state); } } impl Borrow for Spanned { fn borrow(&self) -> &str { self.inner.borrow() } } impl Borrow for Spanned<&'_ str> { fn borrow(&self) -> &str { self.inner } } // ------------------------------------------------------------------------------- // Convention for these markers comes from the toml crates const SPANNED_TYPE: &str = "$___::marked_data::serde::Spanned"; const SPANNED_SPAN_START_SOURCE: &str = "$___::marked_data::serde::Spanned::span_start_source"; const SPANNED_SPAN_START_CHARACTER: &str = "$___::marked_data::serde::Spanned::span_start_char"; const SPANNED_SPAN_START_LINE: &str = "$___::marked_data::serde::Spanned::span_start_line"; const SPANNED_SPAN_START_COLUMN: &str = "$___::marked_data::serde::Spanned::span_start_column"; const SPANNED_SPAN_END_SOURCE: &str = "$___::marked_data::serde::Spanned::span_end_source"; const SPANNED_SPAN_END_CHARACTER: &str = "$___::marked_data::serde::Spanned::span_end_char"; const SPANNED_SPAN_END_LINE: &str = "$___::marked_data::serde::Spanned::span_end_line"; const SPANNED_SPAN_END_COLUMN: &str = "$___::marked_data::serde::Spanned::span_end_column"; const SPANNED_INNER: &str = "$___::marked_data::serde::Spanned::inner"; const SPANNED_FIELDS: [&str; 9] = [ SPANNED_SPAN_START_SOURCE, SPANNED_SPAN_START_CHARACTER, SPANNED_SPAN_START_LINE, SPANNED_SPAN_START_COLUMN, SPANNED_SPAN_END_SOURCE, SPANNED_SPAN_END_CHARACTER, SPANNED_SPAN_END_LINE, SPANNED_SPAN_END_COLUMN, SPANNED_INNER, ]; impl<'de, T> Deserialize<'de> for Spanned where T: Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct MarkedNodeVisitor(PhantomData); impl<'de, T> Visitor<'de> for MarkedNodeVisitor where T: Deserialize<'de>, { type Value = Spanned; fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "a verbatim marked_yaml::Node") } fn visit_map(self, mut visitor: V) -> Result where V: MapAccess<'de>, { let mut key: Option<&str> = visitor.next_key()?; let span_start = if key == Some(SPANNED_SPAN_START_SOURCE) { let source: usize = visitor.next_value()?; if visitor.next_key()? != Some(SPANNED_SPAN_START_CHARACTER) { return Err(serde::de::Error::custom( "marked node span start character missing", )); } let character: usize = visitor.next_value()?; if visitor.next_key()? != Some(SPANNED_SPAN_START_LINE) { return Err(serde::de::Error::custom( "marked node span start line missing", )); } let line: usize = visitor.next_value()?; if visitor.next_key()? != Some(SPANNED_SPAN_START_COLUMN) { return Err(serde::de::Error::custom( "marked node span start column missing", )); } let column: usize = visitor.next_value()?; key = visitor.next_key()?; Some(Marker::new(source, character, line, column)) } else { None }; let span_end = if key == Some(SPANNED_SPAN_END_SOURCE) { let source: usize = visitor.next_value()?; if visitor.next_key()? != Some(SPANNED_SPAN_END_CHARACTER) { return Err(serde::de::Error::custom( "marked node span end character missing", )); } let character: usize = visitor.next_value()?; if visitor.next_key()? != Some(SPANNED_SPAN_END_LINE) { return Err(serde::de::Error::custom( "marked node span end line missing", )); } let line: usize = visitor.next_value()?; if visitor.next_key()? != Some(SPANNED_SPAN_END_COLUMN) { return Err(serde::de::Error::custom( "marked node span end column missing", )); } let column: usize = visitor.next_value()?; key = visitor.next_key()?; Some(Marker::new(source, character, line, column)) } else { None }; if key != Some(SPANNED_INNER) { return Err(serde::de::Error::custom( "marked node inner value not found", )); } let inner: T = visitor.next_value()?; let mut span = Span::new_blank(); span.set_start(span_start); span.set_end(span_end); Ok(Spanned::new(span, inner)) } } let visitor = MarkedNodeVisitor(PhantomData); deserializer.deserialize_struct(SPANNED_TYPE, &SPANNED_FIELDS, visitor) } } impl Serialize for Spanned where T: Serialize, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.inner.serialize(serializer) } } #[cfg(test)] mod spanned_tests { use super::Spanned; use serde::{forward_to_deserialize_any, Deserialize, Deserializer}; #[test] fn spanned_always_map() { struct NotSpanned; impl<'de> Deserializer<'de> for NotSpanned { type Error = super::Error; fn deserialize_any(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { visitor.visit_bool(false) } forward_to_deserialize_any! [ bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf unit unit_struct newtype_struct seq tuple tuple_struct map identifier ignored_any option struct enum ]; } type T = Spanned; assert!(T::deserialize(NotSpanned).is_err()); } } // ------------------------------------------------------------------------------- /// Errors which can come from deserialisation #[derive(Debug)] pub enum Error { /// The value was not a valid boolean NotBoolean(Span), /// Failed to parse integer IntegerParseFailure(ParseIntError, Span), /// Failed to parse float FloatParseFailure(ParseFloatError, Span), /// An unknown field was encountered UnknownFieldError(String, &'static [&'static str], Span), /// Some other error occurred Other(String, Span), } impl Error { fn set_span(&mut self, span: Span) { let spanloc = match self { Error::NotBoolean(s) => s, Error::IntegerParseFailure(_, s) => s, Error::FloatParseFailure(_, s) => s, Error::UnknownFieldError(_, _, s) => s, Error::Other(_, s) => s, }; *spanloc = span; } /// Retrieve the start marker if there is one /// /// Most spans which are generated by the loader only have start /// marks (containers have end marks as well, but these failures) /// are unlikely to exist here. /// /// ``` /// # use marked_yaml::*; /// # use serde::Deserialize; /// const YAML: &str = r#" /// bad: float /// "#; /// /// #[derive(Deserialize)] /// struct Example { /// bad: Spanned, /// } /// /// let nodes = parse_yaml(0, YAML).unwrap(); /// let err = from_node::(&nodes).err().unwrap(); /// /// assert!(matches!(&*err, Error::FloatParseFailure(_,_))); /// /// let mark = err.start_mark().unwrap(); /// /// assert_eq!(mark.source(), 0); /// assert_eq!(mark.line(), 2); /// assert_eq!(mark.column(), 6); /// ``` pub fn start_mark(&self) -> Option { let spanloc = match self { Error::NotBoolean(s) => s, Error::IntegerParseFailure(_, s) => s, Error::FloatParseFailure(_, s) => s, Error::UnknownFieldError(_, _, s) => s, Error::Other(_, s) => s, }; spanloc.start().copied() } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::NotBoolean(_) => f.write_str("Value was not a boolean"), Error::IntegerParseFailure(e, _) => e.fmt(f), Error::FloatParseFailure(e, _) => e.fmt(f), Error::UnknownFieldError(field, expected, _) => match expected.len() { 0 => write!(f, "Unknown field `{field}`, there are no fields"), 1 => write!(f, "Unknown field `{field}`, expected `{}`", expected[0]), 2 => write!( f, "Unknown field `{field}`, expected `{}` or `{}`", expected[0], expected[1] ), _ => { write!(f, "Unknown field `{field}`, expected one of ")?; let last = expected[expected.len() - 1]; for v in expected[..=expected.len() - 2].iter() { write!(f, "`{v}`, ")?; } write!(f, "or `{last}`") } }, Error::Other(e, _) => e.fmt(f), } } } impl std::error::Error for Error {} impl serde::de::Error for Error { fn custom(msg: T) -> Self where T: fmt::Display, { Error::Other(msg.to_string(), Span::new_blank()) } fn unknown_field(field: &str, expected: &'static [&'static str]) -> Self { Self::UnknownFieldError(field.to_string(), expected, Span::new_blank()) } } impl From for Error { fn from(value: ParseIntError) -> Self { Error::IntegerParseFailure(value, Span::new_blank()) } } impl From for Error { fn from(value: ParseFloatError) -> Self { Error::FloatParseFailure(value, Span::new_blank()) } } trait AddSpans { fn addspans(self, span: Span) -> Result; } impl AddSpans for Result where E: Into, { fn addspans(self, span: Span) -> Result { self.map_err(|e| { let mut e: Error = e.into(); e.set_span(span); e }) } } // ------------------------------------------------------------------------------- impl<'de> IntoDeserializer<'de, Error> for &'de Node { type Deserializer = NodeDeserializer<'de>; fn into_deserializer(self) -> Self::Deserializer { NodeDeserializer { node: self } } } /// Deserializer for nodes pub struct NodeDeserializer<'node> { node: &'node Node, } impl<'node> NodeDeserializer<'node> { /// Create a new deserializer over a borrowed node pub fn new(node: &'node Node) -> Self { Self { node } } } // ------------------------------------------------------------------------------- /// The error returned by [`from_node`] /// /// From here you can get the logical path to the error if /// one is available, and then via the error: the marker /// indicating where the error occurred if it's available. /// Finally you may extract the error itself. #[derive(Debug)] pub struct FromNodeError { error: Box, path: Option, } impl FromNodeError { #[cfg_attr(docsrs, doc(cfg(feature = "serde-path")))] /// The logical path representing where the error occurred. /// /// Note: this likely will only ever be `Some(value)` if /// you enable the `serde-path` feature, though the function /// itself will be available with the `serde` feature. pub fn path(&self) -> Option<&str> { self.path.as_deref() } /// Extract the inner error pub fn into_inner(self) -> Error { *self.error } } impl Deref for FromNodeError { type Target = Error; fn deref(&self) -> &Self::Target { &self.error } } impl fmt::Display for FromNodeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(path) = self.path() { write!(f, "{}: {}", path, self.error) } else { (&self.error as &dyn fmt::Display).fmt(f) } } } // ------------------------------------------------------------------------------- /// Errors which can occur when deserialising from YAML #[derive(Debug)] pub enum FromYamlError { /// A problem was encountered when parsing YAML ParseYaml(crate::LoadError), /// A problem was encountered when deserializing from nodes FromNode(FromNodeError), } impl fmt::Display for FromYamlError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FromYamlError::ParseYaml(e) => write!(f, "{e}"), FromYamlError::FromNode(e) => write!(f, "{e}"), } } } impl std::error::Error for FromYamlError {} impl From for FromYamlError { fn from(value: crate::LoadError) -> Self { Self::ParseYaml(value) } } impl From for FromYamlError { fn from(value: FromNodeError) -> Self { Self::FromNode(value) } } // ------------------------------------------------------------------------------- /// Deserialize some YAML into the requisite type /// /// This permits deserialisation of a YAML string into /// any structure which [`serde`] can deserialize. In /// addition, if any part of the type tree is [`Spanned`] /// then the spans are provided from the requisite marked /// node. /// /// ``` /// # use serde::Deserialize; /// # use marked_yaml::Spanned; /// const YAML: &str = "hello: world\n"; /// #[derive(Deserialize)] /// struct Greeting { /// hello: Spanned, /// } /// let greets: Greeting = marked_yaml::from_yaml(0, YAML).unwrap(); /// let start = greets.hello.span().start().unwrap(); /// assert_eq!(start.line(), 1); /// assert_eq!(start.column(), 8); /// ``` #[allow(clippy::result_large_err)] pub fn from_yaml(source: usize, yaml: &str) -> Result where T: DeserializeOwned, { from_yaml_with_options(source, yaml, LoaderOptions::default()) } /// Deserialize some YAML into the requisite type /// /// This permits deserialisation of a YAML string into /// any structure which [`serde`] can deserialize. In /// addition, if any part of the type tree is [`Spanned`] /// then the spans are provided from the requisite marked /// node. /// /// ``` /// # use serde::Deserialize; /// # use marked_yaml::{Spanned, LoaderOptions}; /// const YAML: &str = "hello: world\n"; /// #[derive(Deserialize)] /// struct Greeting { /// hello: Spanned, /// } /// let greets: Greeting = marked_yaml::from_yaml_with_options(0, YAML, LoaderOptions::default()).unwrap(); /// let start = greets.hello.span().start().unwrap(); /// assert_eq!(start.line(), 1); /// assert_eq!(start.column(), 8); /// ``` #[allow(clippy::result_large_err)] pub fn from_yaml_with_options( source: usize, yaml: &str, options: LoaderOptions, ) -> Result where T: DeserializeOwned, { let node = crate::parse_yaml_with_options(source, yaml, options)?; Ok(from_node(&node)?) } // ------------------------------------------------------------------------------- /// Deserialize some [`Node`] into the requisite type /// /// This permits deserialisation of [`Node`]s into any structure /// which [`serde`] can deserialize. In addition, if any part of /// the type tree is [`Spanned`] then the spans are provided /// from the requisite marked node. /// /// ``` /// # use serde::Deserialize; /// # use marked_yaml::Spanned; /// const YAML: &str = "hello: world\n"; /// let node = marked_yaml::parse_yaml(0, YAML).unwrap(); /// #[derive(Deserialize)] /// struct Greeting { /// hello: Spanned, /// } /// let greets: Greeting = marked_yaml::from_node(&node).unwrap(); /// let start = greets.hello.span().start().unwrap(); /// assert_eq!(start.line(), 1); /// assert_eq!(start.column(), 8); /// ``` #[allow(clippy::result_large_err)] pub fn from_node<'de, T>(node: &'de Node) -> Result where T: Deserialize<'de>, { #[cfg(not(feature = "serde-path"))] { T::deserialize(NodeDeserializer::new(node)).map_err(|e| FromNodeError { error: Box::new(e), path: None, }) } #[cfg(feature = "serde-path")] { use serde_path_to_error::Segment; let p2e: Result = serde_path_to_error::deserialize(NodeDeserializer::new(node)); p2e.map_err(|e| { if e.inner().start_mark().is_none() { let p = e.path().clone(); let path = render_path(&p); let mut e = e.into_inner(); let mut prev_best_node = node; let mut best_node = node; for seg in p.iter() { match seg { Segment::Seq { index } => { if let Some(seq) = best_node.as_sequence() { if let Some(node) = seq.get(*index) { prev_best_node = best_node; best_node = node; } else { // We can't traverse this? break; } } else { // We can't traverse this? break; } } Segment::Map { key } => { if let Some(map) = best_node.as_mapping() { // What we want here is the entry which matches the key // if there is one if let Some(node) = map.get(key.as_str()) { prev_best_node = best_node; best_node = node; } else { // We can't traverse this? break; } } else { // We can't traverse this? break; } } Segment::Enum { .. } => break, Segment::Unknown => break, } } let mut best_span = *best_node.span(); if let Error::UnknownFieldError(field, _, _) = &e { // We actually would prefer to point at the key not the value, if let Some(map) = prev_best_node.as_mapping() { for (k, _) in map.iter() { if k.as_str() == field.as_str() { best_span = *k.span(); break; } } } } e.set_span(best_span); FromNodeError { error: Box::new(e), path, } } else { let path = render_path(e.path()); FromNodeError { error: Box::new(e.into_inner()), path, } } }) } } #[cfg(feature = "serde-path")] fn render_path(path: &serde_path_to_error::Path) -> Option { use serde_path_to_error::Segment::*; use std::fmt::Write; let mut ret = String::new(); let mut separator = ""; for segment in path.iter() { if let Map { key } = segment { if key == SPANNED_INNER { continue; } } if !matches!(segment, Seq { .. }) { write!(ret, "{separator}{segment}").expect("Cannot format"); } else { write!(ret, "{segment}").expect("Cannot format"); } separator = "."; } if ret.is_empty() { None } else { Some(ret) } } // ------------------------------------------------------------------------------- macro_rules! forward_to_nodes { () => { forward_to_nodes! [ deserialize_any() deserialize_bool() deserialize_i8() deserialize_i16() deserialize_i32() deserialize_i64() deserialize_i128() deserialize_u8() deserialize_u16() deserialize_u32() deserialize_u64() deserialize_u128() deserialize_f32() deserialize_f64() deserialize_char() deserialize_str() deserialize_string() deserialize_bytes() deserialize_byte_buf() deserialize_option() deserialize_unit() deserialize_unit_struct(name: &'static str) deserialize_newtype_struct(name: &'static str) deserialize_seq() deserialize_tuple(len: usize) deserialize_tuple_struct(name: &'static str, len: usize) deserialize_map() deserialize_struct(name: &'static str, fields: &'static [&'static str]) deserialize_enum(name: &'static str, variants: &'static [&'static str]) deserialize_identifier() deserialize_ignored_any() ]; }; ($($meth:ident($($arg:ident: $ty:ty),*))*) => { $( fn $meth(self, $($arg: $ty,)* visitor: V) -> Result where V: Visitor<'de>, { match self.node { Node::Scalar(s) => s .into_deserializer() .$meth($($arg,)* visitor), Node::Mapping(m) => m .into_deserializer() .$meth($($arg,)* visitor), Node::Sequence(s) => s .into_deserializer() .$meth($($arg,)* visitor), } } )* }; } impl<'de> Deserializer<'de> for NodeDeserializer<'de> { type Error = Error; forward_to_nodes!(); } // ------------------------------------------------------------------------------- trait MarkedValue { fn mark_span(&self) -> &Span; } impl MarkedValue for MarkedScalarNode { fn mark_span(&self) -> &Span { self.span() } } impl MarkedValue for MarkedMappingNode { fn mark_span(&self) -> &Span { self.span() } } impl MarkedValue for MarkedSequenceNode { fn mark_span(&self) -> &Span { self.span() } } impl MarkedValue for Node { fn mark_span(&self) -> &Span { self.span() } } // ------------------------------------------------------------------------------- struct SpannedDeserializer<'de, T> { node: &'de T, state: SpannedDeserializerState, } enum SpannedDeserializerState { SendStartSource, SendStartCharacter, SendStartLine, SendStartColumn, SendEndSource, SendEndCharacter, SendEndLine, SendEndColumn, SendValue, Done, } impl<'de, T> SpannedDeserializer<'de, T> where T: MarkedValue, { fn new(node: &'de T) -> Self { let state = if node.mark_span().start().is_some() { SpannedDeserializerState::SendStartSource } else if node.mark_span().end().is_some() { SpannedDeserializerState::SendEndSource } else { SpannedDeserializerState::SendValue }; Self { node, state } } } impl<'de, T> MapAccess<'de> for SpannedDeserializer<'de, T> where T: MarkedValue, &'de T: IntoDeserializer<'de, Error>, { type Error = Error; fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> where K: serde::de::DeserializeSeed<'de>, { let key = match self.state { SpannedDeserializerState::SendStartSource => SPANNED_SPAN_START_SOURCE, SpannedDeserializerState::SendStartCharacter => SPANNED_SPAN_START_CHARACTER, SpannedDeserializerState::SendStartLine => SPANNED_SPAN_START_LINE, SpannedDeserializerState::SendStartColumn => SPANNED_SPAN_START_COLUMN, SpannedDeserializerState::SendEndSource => SPANNED_SPAN_END_SOURCE, SpannedDeserializerState::SendEndCharacter => SPANNED_SPAN_END_CHARACTER, SpannedDeserializerState::SendEndLine => SPANNED_SPAN_END_LINE, SpannedDeserializerState::SendEndColumn => SPANNED_SPAN_END_COLUMN, SpannedDeserializerState::SendValue => SPANNED_INNER, SpannedDeserializerState::Done => return Ok(None), }; seed.deserialize(BorrowedStrDeserializer::new(key)) .map(Some) } fn next_value_seed(&mut self, seed: V) -> Result where V: serde::de::DeserializeSeed<'de>, { match self.state { SpannedDeserializerState::SendStartSource => { let v = self .node .mark_span() .start() .expect("Span missing start") .source(); self.state = SpannedDeserializerState::SendStartCharacter; seed.deserialize(v.into_deserializer()) } SpannedDeserializerState::SendStartCharacter => { let v = self .node .mark_span() .start() .expect("Span missing start") .character(); self.state = SpannedDeserializerState::SendStartLine; seed.deserialize(v.into_deserializer()) } SpannedDeserializerState::SendStartLine => { let v = self .node .mark_span() .start() .expect("Span missing start") .line(); self.state = SpannedDeserializerState::SendStartColumn; seed.deserialize(v.into_deserializer()) } SpannedDeserializerState::SendStartColumn => { let v = self .node .mark_span() .start() .expect("Span missing start") .column(); self.state = if self.node.mark_span().end().is_some() { SpannedDeserializerState::SendEndSource } else { SpannedDeserializerState::SendValue }; seed.deserialize(v.into_deserializer()) } SpannedDeserializerState::SendEndSource => { let v = self .node .mark_span() .end() .expect("Span missing end") .source(); self.state = SpannedDeserializerState::SendEndCharacter; seed.deserialize(v.into_deserializer()) } SpannedDeserializerState::SendEndCharacter => { let v = self .node .mark_span() .end() .expect("Span missing end") .character(); self.state = SpannedDeserializerState::SendEndLine; seed.deserialize(v.into_deserializer()) } SpannedDeserializerState::SendEndLine => { let v = self .node .mark_span() .end() .expect("Span missing end") .line(); self.state = SpannedDeserializerState::SendEndColumn; seed.deserialize(v.into_deserializer()) } SpannedDeserializerState::SendEndColumn => { let v = self .node .mark_span() .end() .expect("Span missing end") .column(); self.state = SpannedDeserializerState::SendValue; seed.deserialize(v.into_deserializer()) } SpannedDeserializerState::SendValue => { self.state = SpannedDeserializerState::Done; seed.deserialize(self.node.into_deserializer()) } SpannedDeserializerState::Done => panic!("next_value_seed called before next_key_seed"), } } } // ------------------------------------------------------------------------------- impl<'de> IntoDeserializer<'de, Error> for &'de MarkedScalarNode { type Deserializer = MarkedScalarNodeDeserializer<'de>; fn into_deserializer(self) -> MarkedScalarNodeDeserializer<'de> { MarkedScalarNodeDeserializer { node: self } } } /// Deserializer for scalar nodes pub struct MarkedScalarNodeDeserializer<'node> { node: &'node MarkedScalarNode, } macro_rules! scalar_fromstr { () => { scalar_fromstr!(deserialize_u8 visit_u8 u8); scalar_fromstr!(deserialize_u16 visit_u16 u16); scalar_fromstr!(deserialize_u32 visit_u32 u32); scalar_fromstr!(deserialize_u64 visit_u64 u64); scalar_fromstr!(deserialize_u128 visit_u128 u128); scalar_fromstr!(deserialize_i8 visit_i8 i8); scalar_fromstr!(deserialize_i16 visit_i16 i16); scalar_fromstr!(deserialize_i32 visit_i32 i32); scalar_fromstr!(deserialize_i64 visit_i64 i64); scalar_fromstr!(deserialize_i128 visit_i128 i128); scalar_fromstr!(deserialize_f32 visit_f32 f32); scalar_fromstr!(deserialize_f64 visit_f64 f64); }; ($meth:ident $visit:ident $ty:ty) => { fn $meth(self, visitor: V) -> Result where V: Visitor<'de>, { if self.node.may_coerce() { let value: $ty = self.node.as_str().parse().addspans(*self.node.span())?; visitor.$visit(value) } else { visitor.visit_str(self.node.deref()) } } }; } impl<'de> Deserializer<'de> for MarkedScalarNodeDeserializer<'de> { type Error = Error; fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de>, { self.node .deref() .into_deserializer() .deserialize_any(visitor) } fn deserialize_bool(self, visitor: V) -> Result where V: Visitor<'de>, { visitor.visit_bool( self.node .as_bool() .ok_or(Error::NotBoolean(*self.node.span()))?, ) } scalar_fromstr!(); fn deserialize_struct( self, name: &'static str, fields: &'static [&'static str], visitor: V, ) -> Result where V: Visitor<'de>, { if name == SPANNED_TYPE && fields == SPANNED_FIELDS { return visitor.visit_map(SpannedDeserializer::new(self.node)); } if self.node.is_empty_scalar() { return visitor.visit_map(EmptyMap); } self.deserialize_any(visitor) } fn deserialize_option(self, visitor: V) -> Result where V: Visitor<'de>, { // Since we're here, there is no none, so visit as a some visitor.visit_some(self) } fn deserialize_enum( self, _name: &'static str, _variants: &'static [&'static str], visitor: V, ) -> Result where V: Visitor<'de>, { visitor.visit_enum(self.node.as_str().into_deserializer()) } fn deserialize_map(self, visitor: V) -> Result where V: Visitor<'de>, { if self.node.is_empty_scalar() { return visitor.visit_map(EmptyMap); } self.deserialize_any(visitor) } fn deserialize_seq(self, visitor: V) -> Result where V: Visitor<'de>, { if self.node.is_empty_scalar() { return visitor.visit_seq(EmptySeq); } self.deserialize_any(visitor) } fn deserialize_unit(self, visitor: V) -> Result where V: Visitor<'de>, { if self.node.is_empty_scalar() { return visitor.visit_unit(); } self.deserialize_any(visitor) } forward_to_deserialize_any! [ char str string bytes byte_buf tuple unit_struct newtype_struct tuple_struct identifier ignored_any ]; } struct EmptyMap; impl<'de> MapAccess<'de> for EmptyMap { type Error = Error; // FUTURE: change to never type once stable fn next_key_seed(&mut self, _seed: K) -> Result, Self::Error> where K: serde::de::DeserializeSeed<'de>, { Ok(None) } fn next_value_seed(&mut self, _seed: V) -> Result where V: serde::de::DeserializeSeed<'de>, { unreachable!() } } struct EmptySeq; impl<'de> SeqAccess<'de> for EmptySeq { type Error = Error; // FUTURE: change to never type once stable fn next_element_seed(&mut self, _seed: T) -> Result, Self::Error> where T: serde::de::DeserializeSeed<'de>, { Ok(None) } } // ------------------------------------------------------------------------------- type MappingValueSeq<'de> = hashlink::linked_hash_map::Iter<'de, MarkedScalarNode, Node>; struct MappingAccess<'de> { items: Peekable>, } impl<'de> MappingAccess<'de> { fn new(items: MappingValueSeq<'de>) -> Self { Self { items: items.peekable(), } } } impl<'de> MapAccess<'de> for MappingAccess<'de> { type Error = Error; fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> where K: serde::de::DeserializeSeed<'de>, { if let Some(next_key) = self.items.peek().map(|(k, _v)| k) { seed.deserialize(next_key.into_deserializer()).map(Some) } else { Ok(None) } } fn next_value_seed(&mut self, seed: V) -> Result where V: serde::de::DeserializeSeed<'de>, { seed.deserialize( self.items .next() .expect("next_value_seed called before next_key_seed") .1 .into_deserializer(), ) } } // ------------------------------------------------------------------------------- struct MarkedMappingNodeEnumAccess<'de> { node: &'de MarkedMappingNode, } impl<'de> MarkedMappingNodeEnumAccess<'de> { fn first(&self) -> &'de Node { self.node .values() .next() .expect("variant accessed before variant seed") } } impl<'de> EnumAccess<'de> for MarkedMappingNodeEnumAccess<'de> { type Error = Error; type Variant = Self; fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> where V: serde::de::DeserializeSeed<'de>, { if let Some(first) = self.node.keys().next() { seed.deserialize(first.into_deserializer()) .map(|v| (v, self)) } else { Err(serde::de::Error::custom( "Unexpected empty map when looking for enum variant", )) } } } impl<'de> VariantAccess<'de> for MarkedMappingNodeEnumAccess<'de> { type Error = Error; fn unit_variant(self) -> Result<(), Self::Error> { Err(serde::de::Error::invalid_type(Unexpected::Map, &"String")) } fn newtype_variant_seed(self, seed: T) -> Result where T: serde::de::DeserializeSeed<'de>, { seed.deserialize(self.first().into_deserializer()) } fn tuple_variant(self, _len: usize, visitor: V) -> Result where V: Visitor<'de>, { self.first().into_deserializer().deserialize_seq(visitor) } fn struct_variant( self, _fields: &'static [&'static str], visitor: V, ) -> Result where V: Visitor<'de>, { self.first().into_deserializer().deserialize_map(visitor) } } // ------------------------------------------------------------------------------- impl<'de> IntoDeserializer<'de, Error> for &'de MarkedMappingNode { type Deserializer = MarkedMappingNodeDeserializer<'de>; fn into_deserializer(self) -> Self::Deserializer { MarkedMappingNodeDeserializer { node: self } } } /// Deserializer for mapping nodes pub struct MarkedMappingNodeDeserializer<'de> { node: &'de MarkedMappingNode, } impl<'de> Deserializer<'de> for MarkedMappingNodeDeserializer<'de> { type Error = Error; fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de>, { visitor.visit_map(MappingAccess::new(self.node.iter())) } fn deserialize_struct( self, name: &'static str, fields: &'static [&'static str], visitor: V, ) -> Result where V: Visitor<'de>, { if name == SPANNED_TYPE && fields == SPANNED_FIELDS { return visitor.visit_map(SpannedDeserializer::new(self.node)); } self.deserialize_any(visitor) } fn deserialize_option(self, visitor: V) -> Result where V: Visitor<'de>, { // Since we're here, there is no none, so visit as a some visitor.visit_some(self) } fn deserialize_enum( self, _name: &'static str, _variants: &'static [&'static str], visitor: V, ) -> Result where V: Visitor<'de>, { match self.node.len() { 0 => Err(serde::de::Error::custom( "Expected map with one value, got empty map", )), 1 => visitor.visit_enum(MarkedMappingNodeEnumAccess { node: self.node }), n => Err(serde::de::Error::custom(format!( "Expected map with one value, got {n} values" ))), } } forward_to_deserialize_any! [ bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf unit unit_struct newtype_struct seq tuple tuple_struct map identifier ignored_any ]; } // ------------------------------------------------------------------------------- struct SequenceAccess<'de> { items: &'de [Node], pos: usize, } impl<'de> SequenceAccess<'de> { fn new(items: &'de [Node]) -> Self { Self { items, pos: 0 } } } impl<'de> SeqAccess<'de> for SequenceAccess<'de> { type Error = Error; fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> where T: serde::de::DeserializeSeed<'de>, { if self.pos == self.items.len() { return Ok(None); } let pos = self.pos; self.pos += 1; seed.deserialize(self.items[pos].into_deserializer()) .map(Some) } } // ------------------------------------------------------------------------------- impl<'de> IntoDeserializer<'de, Error> for &'de MarkedSequenceNode { type Deserializer = MarkedSequenceNodeDeserializer<'de>; fn into_deserializer(self) -> Self::Deserializer { MarkedSequenceNodeDeserializer { node: self } } } /// Deserializer for sequence nodes pub struct MarkedSequenceNodeDeserializer<'de> { node: &'de MarkedSequenceNode, } impl<'de> Deserializer<'de> for MarkedSequenceNodeDeserializer<'de> { type Error = Error; fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de>, { visitor.visit_seq(SequenceAccess::new(self.node.as_slice())) } fn deserialize_struct( self, name: &'static str, fields: &'static [&'static str], visitor: V, ) -> Result where V: Visitor<'de>, { if name == SPANNED_TYPE && fields == SPANNED_FIELDS { return visitor.visit_map(SpannedDeserializer::new(self.node)); } self.deserialize_any(visitor) } fn deserialize_option(self, visitor: V) -> Result where V: Visitor<'de>, { // Since we're here, there is no none, so visit as a some visitor.visit_some(self) } forward_to_deserialize_any! [ bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf unit unit_struct newtype_struct seq tuple tuple_struct map enum identifier ignored_any ]; } // ------------------------------------------------------------------------------- #[cfg(test)] mod test { use std::collections::HashMap; use super::*; const TEST_DOC: &str = r#"hello: world some: [ value, or, other ] says: { grow: nothing, or: die } numbers: [ 1, 2, 3, 500 ] success: true failure: False shouting: TRUE "#; #[test] #[allow(dead_code)] fn basic_deserialize() { #[derive(Deserialize, Debug)] struct TestDoc { hello: String, some: Vec, says: HashMap, } let node = crate::parse_yaml(0, TEST_DOC).unwrap(); let doc: TestDoc = from_node(&node).unwrap(); println!("{doc:#?}"); } #[test] #[allow(dead_code)] fn basic_deserialize_spanned_scalars() { #[derive(Deserialize, Debug)] struct TestDoc { hello: Spanned, some: Vec>, says: HashMap, Spanned>, } let node = crate::parse_yaml(0, TEST_DOC).unwrap(); let doc: TestDoc = from_node(&node).unwrap(); println!("{doc:#?}"); } #[test] #[allow(dead_code)] fn basic_deserialize_spanned_everything() { #[derive(Deserialize, Debug)] struct TestDoc { hello: Spanned, some: Spanned>>, says: Spanned, Spanned>>, } let node = crate::parse_yaml(0, TEST_DOC).unwrap(); let doc: Spanned = from_node(&node).unwrap(); println!("{doc:#?}"); } #[test] #[allow(dead_code)] fn basic_deserialize_numbers() { #[derive(Deserialize, Debug)] struct TestDoc { numbers: Vec, } let node = crate::parse_yaml(0, TEST_DOC).unwrap(); let doc: Spanned = from_node(&node).unwrap(); println!("{doc:#?}"); } #[test] #[allow(dead_code)] #[cfg(not(feature = "serde-path"))] fn basic_deserialize_bad_numbers() { #[derive(Deserialize, Debug)] struct TestDoc { numbers: Vec, } let node = crate::parse_yaml(0, TEST_DOC).unwrap(); let err = from_node::(&node).err().unwrap().into_inner(); match err { Error::IntegerParseFailure(_e, s) => { let start = s.start().unwrap(); assert_eq!(start.source(), 0); assert_eq!(start.character(), 93); assert_eq!(start.line(), 4); assert_eq!(start.column(), 21); } _ => panic!("Unexpected error"), } } #[test] #[allow(dead_code)] #[cfg(feature = "serde-path")] fn basic_deserialize_bad_numbers() { #[derive(Deserialize, Debug)] struct TestDoc { numbers: Vec, } let node = crate::parse_yaml(0, TEST_DOC).unwrap(); let err = from_node::(&node).err().unwrap(); assert_eq!(err.path(), Some("numbers[3]")); let err = err.into_inner(); match err { Error::IntegerParseFailure(_e, s) => { let start = s.start().unwrap(); assert_eq!(start.source(), 0); assert_eq!(start.line(), 4); assert_eq!(start.column(), 21); } _ => panic!("Unexpected error"), } } #[test] #[allow(dead_code)] fn basic_deserialize_spanned_numbers() { #[derive(Deserialize, Debug)] struct TestDoc { numbers: Vec>, } let node = crate::parse_yaml(0, TEST_DOC).unwrap(); let doc: Spanned = from_node(&node).unwrap(); println!("{doc:#?}"); } #[test] #[allow(dead_code)] fn basic_deserialize_bools() { #[derive(Deserialize, Debug)] struct TestDoc { success: bool, failure: Spanned, shouting: Spanned, } let node = crate::parse_yaml(0, TEST_DOC).unwrap(); let doc: Spanned = from_node(&node).unwrap(); println!("{doc:#?}"); } #[test] #[allow(dead_code)] fn disallowed_keys() { #[derive(Deserialize)] #[serde(deny_unknown_fields)] struct TestDoc { success: bool, } let node = crate::parse_yaml(0, TEST_DOC).unwrap(); let err = from_node::(&node).err().unwrap(); #[cfg(feature = "serde-path")] { assert_eq!(err.path(), Some("hello")); let mark = err.start_mark().unwrap(); assert_eq!(mark.source(), 0); assert_eq!(mark.line(), 1); assert_eq!(mark.column(), 1); }; assert!(matches!(&*err, Error::UnknownFieldError(_, _, _))); } } marked-yaml-0.8.0/src/types.rs000064400000000000000000001372651046102023000143530ustar 00000000000000//! Various basic types for YAML handling //! use doc_comment::doc_comment; use hashlink::LinkedHashMap; use std::borrow::{Borrow, Cow}; use std::fmt::{self, Display}; use std::hash::{Hash, Hasher}; use std::ops::{Deref, DerefMut}; use yaml_rust::Yaml as YamlNode; /// A displayable marker for a YAML node /// /// While `Marker` can be `Display`'d it doesn't understand what its source /// means. This struct is the result of asking a Marker to render itself. /// /// ``` /// # use marked_yaml::*; /// let filenames = vec!["examples/everything.yaml"]; /// let nodes: Vec<_> = filenames /// .iter() /// .enumerate() /// .map(|(i, name)| parse_yaml(i, std::fs::read_to_string(name).unwrap()).unwrap()) /// .collect(); /// for (n, node) in nodes.iter().enumerate() { /// let marker = node.span().start().unwrap(); /// let rendered = format!("{}", marker.render(|i| filenames[i])); /// assert!(rendered.starts_with(filenames[n])); /// } /// ``` pub struct RenderedMarker { source: D, line: usize, column: usize, } impl Display for RenderedMarker where D: Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.source.fmt(f)?; write!(f, ":{}:{}", self.line, self.column) } } /// A marker for a YAML node /// /// This indicates where a node started or ended. /// /// ``` /// use marked_yaml::{parse_yaml, Marker}; /// let node = parse_yaml(100, "{foo: bar}").unwrap(); /// let map = node.as_mapping().unwrap(); /// let bar = map.get("foo").unwrap(); /// // the "bar" string started on line 1, column 7 of source ID 100. /// assert_eq!(bar.span().start(), Some(&Marker::new(100, 6, 1, 7))); /// ``` #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Marker { source: usize, character: usize, line: usize, column: usize, } impl Marker { /// Create a new Marker /// /// This will typically not be used because markers will come from /// parsing YAML, however it is provided for completeness and in case /// you need it for your own tests. /// /// ``` /// # use marked_yaml::Marker; /// let marker = Marker::new(0, 3, 1, 2); /// # assert_eq!(marker.source(), 0); /// # assert_eq!(marker.character(), 3); /// # assert_eq!(marker.line(), 1); /// # assert_eq!(marker.column(), 2); /// ``` pub fn new(source: usize, character: usize, line: usize, column: usize) -> Self { Self { source, character, line, column, } } /// The source index given for this marker. /// /// When parsing YAML, we record where nodes start (and often finish). /// This is the source index provided when parsing the YAML. Likely this /// refers to a vector of PathBuf recording where input came from, but that /// is entirely up to the user of this library crate. /// /// This is likely most useful to computers, not humans. /// /// ``` /// # use marked_yaml::Marker; /// # let marker = Marker::new(0, 3, 1, 2); /// assert_eq!(marker.source(), 0); /// ``` pub fn source(&self) -> usize { self.source } /// The character index at which this marker resides /// /// When parsing YAML, we record where nodes start (and often finish). /// This is the character index into the source text of where this /// marker resides. Character indices start with zero since they're /// meant for software rather than humans. /// /// ``` /// # use marked_yaml::Marker; /// # let marker = Marker::new(0, 3, 1, 2); /// assert_eq!(marker.character(), 3); /// ``` pub fn character(&self) -> usize { self.character } /// The line number on which this marker resides, 1-indexed /// /// When parsing YAML, we record where nodes start (and often finish). /// This is the line number of where this marker resides. Line numbers /// start with 1 to make them more useful to humans. /// /// ``` /// # use marked_yaml::Marker; /// # let marker = Marker::new(0, 3, 1, 2); /// assert_eq!(marker.line(), 1); /// ``` pub fn line(&self) -> usize { self.line } /// The column number at which this marker resides, 1-indexed /// /// When parsing YAML, we record where nodes start (and often finish). /// This is the column number of where this marker resides. Column numbers /// start with 1 to make them more useful to humans. /// /// ``` /// # use marked_yaml::Marker; /// # let marker = Marker::new(0, 3, 1, 2); /// assert_eq!(marker.column(), 2); /// ``` pub fn column(&self) -> usize { self.column } /// Render this marker /// /// Markers have a source identifier, typically as passed to `parse_yaml()` /// but have no way in and of themselves to turn that into a useful name. /// This function allows you to create a rendered marker which knows how /// to show the source name. /// /// ``` /// # use marked_yaml::Marker; /// # let marker = Marker::new(0, 3, 1, 2); /// let rendered = marker.render(|_| "name"); /// assert_eq!(format!("{}", rendered), "name:1:2") /// ``` pub fn render(self, renderfn: F) -> RenderedMarker where D: Display, F: FnOnce(usize) -> D, { RenderedMarker { source: renderfn(self.source), line: self.line, column: self.column, } } /// Set the source index for this marker /// /// ``` /// # use marked_yaml::Marker; /// # let mut marker = Marker::new(0, 0, 0, 0); /// assert_ne!(marker.source(), 1); /// marker.set_source(1); /// assert_eq!(marker.source(), 1); /// ``` pub fn set_source(&mut self, source: usize) { self.source = source; } /// Set the character index for this marker /// /// /// ``` /// # use marked_yaml::Marker; /// # let mut marker = Marker::new(0, 0, 0, 0); /// assert_ne!(marker.character(), 1); /// marker.set_character(1); /// assert_eq!(marker.character(), 1); /// ``` pub fn set_character(&mut self, character: usize) { self.character = character; } /// Set the line number for this marker /// /// ``` /// # use marked_yaml::Marker; /// # let mut marker = Marker::new(0, 0, 0, 0); /// assert_ne!(marker.line(), 1); /// marker.set_line(1); /// assert_eq!(marker.line(), 1); /// ``` pub fn set_line(&mut self, line: usize) { self.line = line; } /// Set the column number for this marker /// /// ``` /// # use marked_yaml::Marker; /// # let mut marker = Marker::new(0, 0, 0, 0); /// assert_ne!(marker.column(), 1); /// marker.set_column(1); /// assert_eq!(marker.column(), 1); /// ``` pub fn set_column(&mut self, column: usize) { self.column = column; } } impl Display for Marker { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}:{}", self.line, self.column) } } /// The span for a YAML marked node /// /// ``` /// use marked_yaml::{parse_yaml, Marker, Span}; /// let node = parse_yaml(100, "{foo: bar}").unwrap(); /// let map = node.as_mapping().unwrap(); /// assert_eq!(map.span(), &Span::new_with_marks(Marker::new(100, 0, 1, 1), Marker::new(100, 9, 1, 10))); /// ``` #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Span { start: Option, end: Option, } impl Span { /// Create a span with no marker information /// /// Sometimes we simply do not know where information came from (for example /// if it was created by software) and in that case we can create a blank /// span. /// /// ``` /// # use marked_yaml::Span; /// let blank = Span::new_blank(); /// # assert_eq!(blank.start(), None); /// # assert_eq!(blank.end(), None); /// ``` pub fn new_blank() -> Self { Self { start: None, end: None, } } /// Create a span with only start information /// /// Sometimes when creating a span we know where it started but not where /// it ends. This might be during parsing, or for some other reason. /// /// ``` /// # use marked_yaml::{Marker, Span}; /// let span = Span::new_start(Marker::new(0, 1, 1, 2)); /// # assert_eq!(span.start().unwrap(), &Marker::new(0, 1, 1, 2)); /// # assert_eq!(span.end(), None); /// ``` pub fn new_start(start: Marker) -> Self { Self { start: Some(start), end: None, } } /// Create a span with both start and end markers /// /// When we know both the start and end of a node, we can create a span /// which has all that knowledge. /// /// ``` /// # use marked_yaml::{Marker,Span}; /// let span = Span::new_with_marks(Marker::new(0, 0, 1, 1), Marker::new(10, 1, 2, 1)); /// # assert_eq!(span.start().unwrap(), &Marker::new(0, 0, 1, 1)); /// # assert_eq!(span.end().unwrap(), &Marker::new(10, 1, 2, 1)); /// ``` pub fn new_with_marks(start: Marker, end: Marker) -> Self { Self { start: Some(start), end: Some(end), } } /// The start of the span /// /// ``` /// # use marked_yaml::{Marker, Span}; /// # let span = Span::new_with_marks(Marker::new(0, 0, 1, 1), Marker::new(10, 1, 2, 1)); /// assert_eq!(span.start(), Some(&Marker::new(0, 0, 1, 1))); /// ``` pub fn start(&self) -> Option<&Marker> { self.start.as_ref() } /// The end of the span /// /// ``` /// # use marked_yaml::{Marker, Span}; /// # let span = Span::new_with_marks(Marker::new(0, 0, 1, 1), Marker::new(10, 1, 2, 1)); /// assert_eq!(span.end(), Some(&Marker::new(10, 1, 2, 1))); /// ``` pub fn end(&self) -> Option<&Marker> { self.end.as_ref() } /// The start of the span, mutably /// /// ``` /// # use marked_yaml::{Marker, Span}; /// # let mut span = Span::new_with_marks(Marker::new(0, 0, 1, 1), Marker::new(10, 1, 2, 1)); /// span.start_mut().unwrap().set_line(5); /// assert_eq!(span.start(), Some(&Marker::new(0, 0, 5, 1))); /// ``` pub fn start_mut(&mut self) -> Option<&mut Marker> { self.start.as_mut() } /// The end of the span, mutably /// /// ``` /// # use marked_yaml::{Marker, Span}; /// # let mut span = Span::new_with_marks(Marker::new(0, 0, 1, 1), Marker::new(10, 1, 2, 1)); /// span.end_mut().unwrap().set_line(5); /// assert_eq!(span.end(), Some(&Marker::new(10, 1, 5, 1))); /// ``` pub fn end_mut(&mut self) -> Option<&mut Marker> { self.end.as_mut() } /// Replace the start of the span /// /// ``` /// # use marked_yaml::{Marker, Span}; /// # let mut span = Span::new_blank(); /// assert_eq!(span.start(), None); /// span.set_start(Some(Marker::new(0, 1, 1, 2))); /// assert_eq!(span.start(), Some(&Marker::new(0, 1, 1, 2))); /// ``` pub fn set_start(&mut self, start: Option) { self.start = start; } /// Replace the end of the span /// /// ``` /// # use marked_yaml::{Marker, Span}; /// # let mut span = Span::new_blank(); /// assert_eq!(span.end(), None); /// span.set_end(Some(Marker::new(0, 1, 1, 2))); /// assert_eq!(span.end(), Some(&Marker::new(0, 1, 1, 2))); /// ``` pub fn set_end(&mut self, end: Option) { self.end = end; } } /// A marked YAML node /// /// **NOTE**: Nodes are considered equal even if they don't come from the /// same place. *i.e. their spans are ignored for equality and hashing* /// /// ``` /// use marked_yaml::parse_yaml; /// let node = parse_yaml(100, "{foo: bar}").unwrap(); /// assert!(node.as_mapping().is_some()); /// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Node { /// A YAML scalar /// /// You can test if a node is a scalar, and retrieve it as one if you /// so wish. Scalar(MarkedScalarNode), /// A YAML mapping /// /// You can test if a node is a mapping, and retrieve it as one if you /// so wish. Mapping(MarkedMappingNode), /// A YAML sequence /// /// You can test if a node is a sequence, and retrieve it as one if you /// so wish. Sequence(MarkedSequenceNode), } /// A marked scalar YAML node /// /// Scalar nodes are treated by this crate as strings, though a few special /// values are processed into the types which YAML would ascribe. In particular /// strings of the value `null`, `true`, `false`, etc. are able to present as /// their special values to make it a bit easier for users of the crate. /// /// **NOTE**: Nodes are considered equal even if they don't come from the /// same place. *i.e. their spans are ignored for equality and hashing* /// /// ``` /// use marked_yaml::{parse_yaml, Marker}; /// let node = parse_yaml(100, "{foo: bar}").unwrap(); /// let map = node.as_mapping().unwrap(); /// let bar = map.get("foo").unwrap(); /// // the "bar" string started on line 1, column 7 of source ID 100. /// assert_eq!(bar.span().start(), Some(&Marker::new(100, 6, 1, 7))); /// ``` #[derive(Clone, Debug)] pub struct MarkedScalarNode { span: Span, value: String, may_coerce: bool, } pub(crate) type MappingHash = LinkedHashMap; /// A marked YAML mapping node /// /// Mapping nodes in YAML are defined as a key/value mapping where the keys are /// unique and always scalars, whereas values may be YAML nodes of any kind. /// /// Because *some* users of this crate may need to care about insertion order /// we use [`hashlink::LinkedHashMap`] for this. /// /// **NOTE**: Nodes are considered equal even if they don't come from the /// same place. *i.e. their spans are ignored for equality and hashing* /// /// ``` /// use marked_yaml::{parse_yaml, Marker, Span}; /// let node = parse_yaml(100, "{foo: bar}").unwrap(); /// let map = node.as_mapping().unwrap(); /// assert_eq!(map.span(), &Span::new_with_marks(Marker::new(100, 0, 1, 1), Marker::new(100, 9, 1, 10))); /// ``` #[derive(Clone, Debug)] pub struct MarkedMappingNode { span: Span, value: MappingHash, } /// A marked YAML sequence node /// /// Sequence nodes in YAML are simply ordered lists of YAML nodes. /// /// **NOTE**: Nodes are considered equal even if they don't come from the /// same place. *i.e. their spans are ignored for equality and hashing* /// /// ``` /// use marked_yaml::{parse_yaml, Marker, Span}; /// let node = parse_yaml(100, "{foo: [bar]}").unwrap(); /// let map = node.as_mapping().unwrap(); /// let seq = map.get("foo").unwrap(); /// let seq = seq.as_sequence().unwrap(); /// assert_eq!(seq.span(), &Span::new_with_marks(Marker::new(100, 6, 1, 7), Marker::new(100, 10, 1, 11))); /// ``` #[derive(Clone, Debug)] pub struct MarkedSequenceNode { span: Span, value: Vec, } macro_rules! basic_traits { ($t:path) => { impl PartialEq for $t { fn eq(&self, other: &Self) -> bool { self.value == other.value } #[allow(clippy::partialeq_ne_impl)] fn ne(&self, other: &Self) -> bool { self.value != other.value } } impl Eq for $t {} impl Hash for $t { fn hash(&self, state: &mut H) { self.value.hash(state); } } }; } basic_traits!(MarkedScalarNode); basic_traits!(MarkedSequenceNode); basic_traits!(MarkedMappingNode); impl From for Node where T: Into, { fn from(value: T) -> Node { Node::Scalar(value.into()) } } impl From for Node { fn from(value: MarkedSequenceNode) -> Node { Node::Sequence(value) } } impl From> for Node where T: Into, { fn from(value: Vec) -> Node { Node::Sequence(value.into_iter().collect()) } } impl From for Node { fn from(value: MarkedMappingNode) -> Node { Node::Mapping(value) } } impl From for Node { fn from(value: MappingHash) -> Node { Node::Mapping(value.into()) } } macro_rules! node_span { ($t:path) => { impl $t { doc_comment!( concat!( r#"Retrieve the Span from this node. ``` # use marked_yaml::types::*; let node = "#, stringify!($t), r#"::new_empty(Span::new_blank()); assert_eq!(node.span(), &Span::new_blank()); ```"# ), pub fn span(&self) -> &Span { &self.span } ); doc_comment!( concat!( r#"Retrieve the Span from this node mutably. ``` # use marked_yaml::types::*; let mut node = "#, stringify!($t), r#"::new_empty(Span::new_blank()); node.span_mut().set_start(Some(Marker::new(0, 0, 1, 0))); assert_eq!(node.span().start(), Some(&Marker::new(0, 0, 1, 0))); ```"# ), pub fn span_mut(&mut self) -> &mut Span { &mut self.span } ); } }; } node_span!(MarkedScalarNode); node_span!(MarkedMappingNode); node_span!(MarkedSequenceNode); impl Node { /// Retrieve the Span from the contained Node /// /// ``` /// # use marked_yaml::types::*; /// let node: Node = "foobar".into(); /// let span = node.span(); /// assert_eq!(span.start(), None); /// ``` pub fn span(&self) -> &Span { match self { Node::Scalar(msn) => msn.span(), Node::Sequence(msn) => msn.span(), Node::Mapping(mmn) => mmn.span(), } } /// Retrieve the Span from the contained Node, mutably /// /// ``` /// # use marked_yaml::types::*; /// let mut node: Node = "foobar".into(); /// let mut span = node.span_mut(); /// assert_eq!(span.start(), None); /// span.set_start(Some(Marker::new(0, 0, 1, 0))); /// assert_eq!(span.start(), Some(&Marker::new(0, 0, 1, 0))); /// ``` pub fn span_mut(&mut self) -> &mut Span { match self { Node::Scalar(msn) => msn.span_mut(), Node::Sequence(msn) => msn.span_mut(), Node::Mapping(mmn) => mmn.span_mut(), } } /// Retrieve the scalar from this node if there is one /// /// ``` /// # use marked_yaml::types::*; /// let node: Node = "foobar".into(); /// let scalar = node.as_scalar(); /// assert!(scalar.is_some()); /// ``` pub fn as_scalar(&self) -> Option<&MarkedScalarNode> { match self { Node::Scalar(msn) => Some(msn), _ => None, } } /// Retrieve the sequence from this node if there is one /// /// ``` /// # use marked_yaml::types::*; /// let node: Node = vec!["foobar"].into(); /// let sequence = node.as_sequence(); /// assert!(sequence.is_some()); /// ``` pub fn as_sequence(&self) -> Option<&MarkedSequenceNode> { match self { Node::Sequence(msn) => Some(msn), _ => None, } } /// Retrieve the mapping from this node if there is one /// /// ``` /// # use marked_yaml::*; /// let node: Node = parse_yaml(0, "{foobar: baz}").unwrap(); /// let mapping = node.as_mapping(); /// assert!(mapping.is_some()); /// ``` pub fn as_mapping(&self) -> Option<&MarkedMappingNode> { match self { Node::Mapping(mmn) => Some(mmn), _ => None, } } /// Retrieve the scalar from this node if there is one, mutably /// /// ``` /// # use marked_yaml::types::*; /// let mut node: Node = "foobar".into(); /// let mut scalar = node.as_scalar_mut(); /// assert!(scalar.is_some()); /// ``` pub fn as_scalar_mut(&mut self) -> Option<&mut MarkedScalarNode> { match self { Node::Scalar(msn) => Some(msn), _ => None, } } /// Retrieve the sequence from this node if there is one, mutably /// /// ``` /// # use marked_yaml::types::*; /// let mut node: Node = vec!["foobar"].into(); /// let mut sequence = node.as_sequence_mut(); /// assert!(sequence.is_some()); /// ``` pub fn as_sequence_mut(&mut self) -> Option<&mut MarkedSequenceNode> { match self { Node::Sequence(msn) => Some(msn), _ => None, } } /// Retrieve the mapping from this node if there is one, mutably /// /// ``` /// # use marked_yaml::*; /// let mut node: Node = parse_yaml(0, "{foobar: baz}").unwrap(); /// let mut mapping = node.as_mapping_mut(); /// assert!(mapping.is_some()); /// ``` pub fn as_mapping_mut(&mut self) -> Option<&mut MarkedMappingNode> { match self { Node::Mapping(mmn) => Some(mmn), _ => None, } } } impl MarkedScalarNode { /// Create a new scalar node with no value /// /// ``` /// # use marked_yaml::types::*; /// let node = MarkedScalarNode::new_empty(Span::new_blank()); /// ``` pub fn new_empty(span: Span) -> Self { Self { span, value: String::new(), may_coerce: true, } } /// Create a new scalar node /// /// ``` /// # use marked_yaml::types::*; /// let node = MarkedScalarNode::new(Span::new_blank(), "foobar"); /// ``` pub fn new<'a, S: Into>>(span: Span, content: S) -> Self { Self { span, value: content.into().into_owned(), may_coerce: true, } } /// Treat the scalar node as a string /// /// Since scalars are always stringish, this is always safe. /// /// ``` /// # use marked_yaml::types::*; /// let node: MarkedScalarNode = "foobar".into(); /// assert_eq!(node.as_str(), "foobar"); /// ``` pub fn as_str(&self) -> &str { self.value.as_str() } /// Enable or disable permission to coerce values /// /// The various [`as_bool()`][Self::as_bool()] and other methods /// which can coerce a string can be forced to always deny /// coercion. /// #[cfg_attr( feature = "serde", doc = r#" Note: this also applies for deserializing nodes via serde. "# )] /// ``` /// # use marked_yaml::types::*; /// let mut node: MarkedScalarNode = "true".into(); /// assert_eq!(node.as_bool(), Some(true)); /// node.set_coerce(false); /// assert_eq!(node.as_bool(), None); /// ``` pub fn set_coerce(&mut self, may_coerce: bool) { self.may_coerce = may_coerce; } /// Retrieve whether or not this node is set to be coerceable /// /// The various [`as_bool()`][Self::as_bool()] and other methods /// which can coerce a string can be forced to deny coercion. /// #[cfg_attr( feature = "serde", doc = r#" Note: this also applies for deserializing nodes via serde. "# )] /// ``` /// # use marked_yaml::types::*; /// let mut node: MarkedScalarNode = "true".into(); /// assert_eq!(node.as_bool(), Some(true)); /// assert_eq!(node.may_coerce(), true); /// node.set_coerce(false); /// assert_eq!(node.as_bool(), None); /// assert_eq!(node.may_coerce(), false); /// ``` pub fn may_coerce(&self) -> bool { self.may_coerce } /// Treat the scalar node as a boolean /// /// If the scalar contains any of the following then it is true: /// /// * `true` /// * `True` /// * `TRUE` /// /// The following are considered false: /// /// * `false` /// * `False` /// * `FALSE` /// /// Everything else is not a boolean and so will return None /// /// Note: If you have done [`.set_coerce(false)`](MarkedScalarNode::set_coerce()) /// then no matter the string's value, this will return `None`. /// /// ``` /// # use marked_yaml::types::*; /// let node: MarkedScalarNode = "true".into(); /// assert_eq!(node.as_bool(), Some(true)); /// let node: MarkedScalarNode = "FALSE".into(); /// assert_eq!(node.as_bool(), Some(false)); /// let node: MarkedScalarNode = "NO".into(); // YAML boolean, but not for us /// assert_eq!(node.as_bool(), None); /// let mut node: MarkedScalarNode = "true".into(); /// node.set_coerce(false); /// assert_eq!(node.as_bool(), None); /// ``` pub fn as_bool(&self) -> Option { if self.may_coerce { match self.value.as_str() { "true" | "True" | "TRUE" => Some(true), "false" | "False" | "FALSE" => Some(false), _ => None, } } else { None } } #[cfg(feature = "serde")] pub(crate) fn is_empty_scalar(&self) -> bool { self.value.is_empty() && self.may_coerce } } impl<'a> From<&'a str> for MarkedScalarNode { /// Convert from any borrowed string into a node /// /// ``` /// # use marked_yaml::types::*; /// let node: MarkedScalarNode = "foobar".into(); /// ``` fn from(value: &'a str) -> Self { Self::new(Span::new_blank(), value) } } impl From for MarkedScalarNode { /// Convert from any owned string into a node /// /// ``` /// # use marked_yaml::types::*; /// let foobar = "foobar".to_string(); /// let node: MarkedScalarNode = foobar.into(); /// ``` fn from(value: String) -> Self { Self::new(Span::new_blank(), value) } } impl From for MarkedScalarNode { /// Convert from a boolean into a node /// /// ``` /// # use marked_yaml::types::*; /// let node = MarkedScalarNode::from(true); /// ``` fn from(value: bool) -> Self { if value { "true".into() } else { "false".into() } } } macro_rules! scalar_from_to_number { ($t:ident, $as:ident) => { scalar_from_to_number!($t, $as, 0); }; ($t:ident, $as:ident, $zero:expr) => { impl From<$t> for MarkedScalarNode { doc_comment!( concat!( "Convert from ", stringify!($t), r#" into a node ``` # use marked_yaml::types::*; let value: "#, stringify!($t), " = ", stringify!($zero), r#"; let node: MarkedScalarNode = value.into(); assert_eq!(&*node, "0"); ```"# ), fn from(value: $t) -> Self { format!("{}", value).into() } ); } impl MarkedScalarNode { doc_comment!( concat!( "Treat the scalar node as ", stringify!($t), r#". If this scalar node's value can be represented properly as a number of the right kind then return it. This is essentially a shortcut for using the `FromStr` trait on the return value of `.as_str()`. Note, this honours the setting of [`MarkedScalarNode::set_coerce()`] ``` # use marked_yaml::types::*; let mut node: MarkedScalarNode = "0".into(); assert_eq!(node.as_"#, stringify!($t), r#"(), Some(0"#, stringify!($t), r#")); node.set_coerce(false); assert_eq!(node.as_"#, stringify!($t), r#"(), None); ```"# ), pub fn $as(&self) -> Option<$t> { if self.may_coerce { use std::str::FromStr; $t::from_str(&self.value).ok() } else { None } } ); } }; } scalar_from_to_number!(i8, as_i8); scalar_from_to_number!(i16, as_i16); scalar_from_to_number!(i32, as_i32); scalar_from_to_number!(i64, as_i64); scalar_from_to_number!(i128, as_i128); scalar_from_to_number!(isize, as_isize); scalar_from_to_number!(u8, as_u8); scalar_from_to_number!(u16, as_u16); scalar_from_to_number!(u32, as_u32); scalar_from_to_number!(u64, as_u64); scalar_from_to_number!(u128, as_u128); scalar_from_to_number!(usize, as_usize); scalar_from_to_number!(f32, as_f32, 0.0); scalar_from_to_number!(f64, as_f64, 0.0); impl Deref for MarkedScalarNode { type Target = str; /// Borrow the string value inside this scalar node /// /// ``` /// # use marked_yaml::types::*; /// # use std::str::FromStr; /// let truth: MarkedScalarNode = "true".into(); /// assert!(bool::from_str(&truth).unwrap()) /// ``` fn deref(&self) -> &Self::Target { &self.value } } impl Borrow for MarkedScalarNode { fn borrow(&self) -> &str { &self.value } } impl MarkedSequenceNode { /// Create a new empty sequence node /// /// ``` /// # use marked_yaml::types::*; /// let node = MarkedSequenceNode::new_empty(Span::new_blank()); /// ``` pub fn new_empty(span: Span) -> Self { Self { span, value: Vec::new(), } } /// Create a new sequence node from a vector of nodes /// /// ``` /// # use marked_yaml::types::*; /// let node = MarkedSequenceNode::new(Span::new_blank(), Vec::new()); /// ``` pub fn new(span: Span, value: Vec) -> Self { Self { span, value } } /// Get the node at the given index /// /// If the index is invalid then None will be returned /// /// ``` /// # use marked_yaml::types::*; /// let seq: MarkedSequenceNode = vec!["foobar"].into_iter().collect(); /// assert_eq!(seq.get_node(0) /// .and_then(Node::as_scalar) /// .map(MarkedScalarNode::as_str) /// .unwrap(), /// "foobar"); pub fn get_node(&self, index: usize) -> Option<&Node> { self.value.get(index) } /// Get the scalar at the given index /// /// If the index is invalid, or the node at that index is not a scalar /// node, then None will be returned. /// /// ``` /// # use marked_yaml::types::*; /// let seq: MarkedSequenceNode = vec!["foobar"].into_iter().collect(); /// assert_eq!(seq.get_scalar(0) /// .map(MarkedScalarNode::as_str) /// .unwrap(), /// "foobar"); /// ``` pub fn get_scalar(&self, index: usize) -> Option<&MarkedScalarNode> { self.get_node(index).and_then(Node::as_scalar) } /// Get the sequence at the given index /// /// If the index is invalid, or the node at that index is not a sequence /// node, then None will be returned. /// /// ``` /// # use marked_yaml::types::*; /// let seq: MarkedSequenceNode = vec![vec!["foobar"]].into_iter().collect(); /// assert_eq!(seq.get_sequence(0) /// .and_then(|s| s.get_scalar(0)) /// .map(MarkedScalarNode::as_str) /// .unwrap(), /// "foobar"); /// ``` pub fn get_sequence(&self, index: usize) -> Option<&MarkedSequenceNode> { self.get_node(index).and_then(Node::as_sequence) } /// Get the mapping at the given index /// /// If the index is invalid, or the node at that index is not a mapping /// node, then None will be returned. /// /// ``` /// # use marked_yaml::types::*; /// # use hashlink::LinkedHashMap; /// # let map: LinkedHashMap = LinkedHashMap::new(); /// let seq: MarkedSequenceNode = vec![map].into_iter().collect(); /// assert!(seq.get_mapping(0).is_some()); /// ``` pub fn get_mapping(&self, index: usize) -> Option<&MarkedMappingNode> { self.get_node(index).and_then(Node::as_mapping) } } impl Deref for MarkedSequenceNode { type Target = Vec; fn deref(&self) -> &Self::Target { &self.value } } impl DerefMut for MarkedSequenceNode { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.value } } impl FromIterator for MarkedSequenceNode where T: Into, { /// Allow collecting things into a sequence node /// /// ``` /// # use marked_yaml::types::*; /// let node: MarkedSequenceNode = vec!["hello", "world"].into_iter().collect(); /// ``` fn from_iter>(iter: I) -> Self { let value: Vec = iter.into_iter().map(Into::into).collect(); let span = match value.len() { 0 => Span::new_blank(), 1 => *value[0].span(), _ => Span { start: value[0].span().start, end: value[value.len() - 1].span().end, }, }; Self { span, value } } } impl From> for MarkedSequenceNode where T: Into, { /// Allow converting from vectors of things to sequence nodes /// /// ``` /// # use marked_yaml::types::*; /// let node: MarkedSequenceNode = vec!["hello", "world"].into(); /// ``` fn from(value: Vec) -> Self { let value: Vec = value.into_iter().map(Into::into).collect(); let span = match value.len() { 0 => Span::new_blank(), 1 => *value[0].span(), _ => { let start = value[0].span().start; let end = value[value.len() - 1].span().end; Span { start, end } } }; Self { span, value } } } impl MarkedMappingNode { /// Create a new empty mapping node /// /// ``` /// # use marked_yaml::types::*; /// let node = MarkedMappingNode::new_empty(Span::new_blank()); /// ``` pub fn new_empty(span: Span) -> Self { Self { span, value: LinkedHashMap::new(), } } /// Create a new mapping node from the given hash table /// /// ``` /// # use marked_yaml::types::*; /// # use hashlink::LinkedHashMap; /// let node = MarkedMappingNode::new(Span::new_blank(), LinkedHashMap::new()); /// ``` pub fn new(span: Span, value: MappingHash) -> Self { Self { span, value } } /// Get the node for the given string key /// /// If the index is not found then None is returned. /// /// ``` /// # use marked_yaml::types::*; /// # use marked_yaml::parse_yaml; /// let node = parse_yaml(0, "{key: value}").unwrap(); /// let map = node.as_mapping().unwrap(); /// assert_eq!(map.get_node("key") /// .and_then(Node::as_scalar) /// .map(MarkedScalarNode::as_str) /// .unwrap(), /// "value"); /// ``` pub fn get_node(&self, index: &str) -> Option<&Node> { self.value.get(index) } /// Get the scalar for the given string key /// /// If the key is not found, or the node for that key is not a scalar /// node, then None will be returned. /// /// ``` /// # use marked_yaml::types::*; /// # use marked_yaml::parse_yaml; /// let node = parse_yaml(0, "{key: value}").unwrap(); /// let map = node.as_mapping().unwrap(); /// assert_eq!(map.get_scalar("key") /// .map(MarkedScalarNode::as_str) /// .unwrap(), /// "value"); /// ``` pub fn get_scalar(&self, index: &str) -> Option<&MarkedScalarNode> { self.get_node(index).and_then(Node::as_scalar) } /// Get the sequence at the given index /// /// If the key is not found, or the node for that key is not a sequence /// node, then None will be returned. /// /// ``` /// # use marked_yaml::types::*; /// # use marked_yaml::parse_yaml; /// let node = parse_yaml(0, "{key: [value]}").unwrap(); /// let map = node.as_mapping().unwrap(); /// assert_eq!(map.get_sequence("key") /// .and_then(|s| s.get_scalar(0)) /// .map(MarkedScalarNode::as_str) /// .unwrap(), /// "value"); /// ``` pub fn get_sequence(&self, index: &str) -> Option<&MarkedSequenceNode> { self.get_node(index).and_then(Node::as_sequence) } /// Get the mapping at the given index /// /// If the key is not found, or the node for that key is not a mapping /// node, then None will be returned. /// /// ``` /// # use marked_yaml::types::*; /// # use marked_yaml::parse_yaml; /// let node = parse_yaml(0, "{key: {inner: value}}").unwrap(); /// let map = node.as_mapping().unwrap(); /// assert_eq!(map.get_mapping("key") /// .and_then(|m| m.get_scalar("inner")) /// .map(MarkedScalarNode::as_str) /// .unwrap(), /// "value"); /// ``` pub fn get_mapping(&self, index: &str) -> Option<&MarkedMappingNode> { self.get_node(index).and_then(Node::as_mapping) } } impl Deref for MarkedMappingNode { type Target = MappingHash; fn deref(&self) -> &Self::Target { &self.value } } impl DerefMut for MarkedMappingNode { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.value } } impl From for MarkedMappingNode { fn from(value: MappingHash) -> Self { Self::new(Span::new_blank(), value) } } impl FromIterator<(T, U)> for MarkedMappingNode where T: Into, U: Into, { /// Allow collecting into a mapping node /// /// ``` /// # use marked_yaml::types::*; /// # use std::collections::HashMap; /// # let mut hashmap = HashMap::new(); /// hashmap.insert("hello", vec!["world".to_string()]); /// hashmap.insert("key", vec!["value".to_string()]); /// let node: MarkedMappingNode = hashmap.into_iter().collect(); /// ``` fn from_iter>(iter: I) -> Self { let value: MappingHash = iter .into_iter() .map(|(k, v)| (k.into(), v.into())) .collect(); let span = match value.len() { 0 => Span::new_blank(), // Unwrap is safe because there's at least one span here 1 => { let (k, v) = value.iter().next().unwrap(); Span { start: k.span().start, end: v.span().end, } } _ => { let mut iter = value.iter(); // Unwraps save because there's at least two spans here let start = iter.next().unwrap().0.span().start; let end = iter.next_back().unwrap().1.span().end; Span { start, end } } }; Self { span, value } } } /// Errors which could be encountered while converting from a `yaml_rust::Yaml` #[derive(Debug, PartialEq, Eq)] pub enum YamlConversionError { /// An alias was encountered while converting Alias, /// A BadValue was encountered while converting BadValue, /// A non-scalar value was encountered when a scalar was expected NonScalar, } impl TryFrom for MarkedScalarNode { type Error = YamlConversionError; fn try_from(value: YamlNode) -> Result { match value { YamlNode::Alias(_) => Err(YamlConversionError::Alias), YamlNode::Array(_) => Err(YamlConversionError::NonScalar), YamlNode::BadValue => Err(YamlConversionError::BadValue), YamlNode::Boolean(b) => Ok(b.into()), YamlNode::Hash(_) => Err(YamlConversionError::NonScalar), YamlNode::Integer(i) => Ok(i.into()), YamlNode::Null => Ok("null".into()), YamlNode::Real(s) => Ok(s.into()), YamlNode::String(s) => Ok(s.into()), } } } impl TryFrom for Node { type Error = YamlConversionError; /// Convert from any `yaml_rust::Yaml` to a Node /// /// ``` /// # use yaml_rust::YamlLoader; /// # use marked_yaml::types::*; /// # use std::convert::TryFrom; /// let docs = YamlLoader::load_from_str("[1, 2]").unwrap(); /// let yaml = docs.into_iter().next().unwrap(); /// let node = Node::try_from(yaml).unwrap(); /// ``` fn try_from(value: YamlNode) -> Result { match value { YamlNode::Array(arr) => Ok(Node::Sequence( arr.into_iter() .map(Node::try_from) .collect::>()?, )), YamlNode::Hash(h) => Ok(Node::Mapping( h.into_iter() .map(|(k, v)| Ok((MarkedScalarNode::try_from(k)?, Node::try_from(v)?))) .collect::>()?, )), scalar => Ok(Node::Scalar(MarkedScalarNode::try_from(scalar)?)), } } } impl From for YamlNode { fn from(value: MarkedScalarNode) -> Self { YamlNode::String(value.value) } } impl From for YamlNode { fn from(value: MarkedSequenceNode) -> Self { YamlNode::Array(value.value.into_iter().map(Into::into).collect()) } } impl From for YamlNode { fn from(value: MarkedMappingNode) -> Self { YamlNode::Hash( value .value .into_iter() .map(|(k, v)| (k.into(), v.into())) .collect(), ) } } impl From for YamlNode { fn from(value: Node) -> Self { match value { Node::Scalar(msn) => msn.into(), Node::Sequence(msn) => msn.into(), Node::Mapping(mmn) => mmn.into(), } } } #[cfg(test)] mod test { use super::super::*; use super::*; #[test] fn basic_marker_checks() { let marker = Marker::new(0, 3, 1, 2); assert_eq!(marker.source(), 0); assert_eq!(marker.character(), 3); assert_eq!(marker.line(), 1); assert_eq!(marker.column(), 2); assert_eq!(format!("{}", marker), "1:2"); let rendered = marker.render(|n| { assert_eq!(n, 0); "name" }); assert_eq!(format!("{}", rendered), "name:1:2"); } #[test] fn basic_span_checks() { let span = Span::new_blank(); assert_eq!(span.start(), None); assert_eq!(span.end(), None); let mark = Marker::new(0, 1, 1, 2); let mark2 = Marker::new(3, 9, 4, 5); let span = Span::new_start(mark); assert_eq!(span.start(), Some(&mark)); assert_eq!(span.end(), None); let span = Span::new_with_marks(mark, mark2); assert_eq!(span.start(), Some(&mark)); assert_eq!(span.end(), Some(&mark2)); } #[test] fn basic_explore_load_test() { let node = parse_yaml(0, include_str!("../examples/everything.yaml")).unwrap(); let map = node.as_mapping().unwrap(); assert_eq!(node.as_scalar(), None); assert_eq!(node.as_sequence(), None); assert_eq!(map.get_node("XXX NOT PRESENT XXX"), None); assert_eq!(map.get_scalar("mapping"), None); assert_eq!(map.get_sequence("mapping"), None); assert_eq!(map.get_mapping("simple"), None); // This actually uses .eq() assert_ne!(map.get_scalar("boolean1"), map.get_scalar("boolean2")); // Whereas this uses .ne() assert!(map.get_scalar("boolean1") != map.get_scalar("boolean2")); // Now check the spans assert_eq!(node.span(), map.span()); let seq = map.get_sequence("heterogenous").unwrap(); assert_eq!(seq.span().start(), Some(&Marker::new(0, 431, 24, 3))); assert_eq!(seq.span(), map.get_node("heterogenous").unwrap().span()); // Helpers for the sequence node assert_eq!(seq.get_node(0), seq.first()); assert_ne!(seq.get_node(0), None); assert_ne!(seq.get_scalar(0), None); assert_ne!(seq.get_mapping(1), None); assert_ne!(seq.get_sequence(2), None); } #[test] fn basic_scalar_features() { let scalar1 = MarkedScalarNode::new(Span::new_blank(), ""); let scalar2 = MarkedScalarNode::new_empty(Span::new_blank()); assert_eq!(scalar1, scalar2); assert_eq!(scalar1.as_str(), ""); assert_eq!(scalar1.as_bool(), None); assert_eq!(scalar1.as_usize(), None); let truth: MarkedScalarNode = "true".into(); assert_eq!(truth.as_bool(), Some(true)); let falsehood: MarkedScalarNode = "false".to_string().into(); assert_eq!(falsehood.as_bool(), Some(false)); assert_eq!(truth, true.into()); assert_eq!(falsehood, false.into()); let zero: MarkedScalarNode = "0".into(); assert_eq!(zero.as_usize(), Some(0)); assert_eq!(zero, 0usize.into()); assert_eq!(&*zero, "0"); } #[test] fn basic_sequence_features() { // For features not covered by other tests let mut seq = MarkedSequenceNode::new_empty(Span::new_blank()); let seq2: MarkedSequenceNode = vec!["foo"].into_iter().collect(); let scalar: MarkedScalarNode = "foo".into(); seq.push(Node::from(scalar)); assert_eq!(seq, seq2); assert_eq!(seq, vec!["foo"].into()); let seq3: MarkedSequenceNode = vec!["foo", "bar"].into_iter().collect(); seq.push(Node::from("bar")); assert_eq!(seq, seq3); assert_eq!(seq, vec!["foo", "bar"].into()); } #[test] fn basic_mapping_features() { // For features not covered by other tests let mut map = MarkedMappingNode::new_empty(Span::new_blank()); let mut hash = MappingHash::new(); hash.insert("foo".into(), "bar".into()); let map2 = MarkedMappingNode::from(hash); map.insert("foo".into(), "bar".into()); assert_eq!(map, map2); assert_eq!(map.get("foo").unwrap().as_scalar().unwrap().as_str(), "bar"); let map3: MarkedMappingNode = vec![("foo", "bar")].into_iter().collect(); assert_eq!(map, map3); map.insert("baz".into(), "meta".into()); let map4: MarkedMappingNode = vec![("foo", "bar"), ("baz", "meta")].into_iter().collect(); assert_eq!(map, map4); } #[test] fn extra_node_impls() { let node = Node::from(vec!["foo"]); assert_ne!(node.as_sequence(), None); let node = Node::from(MappingHash::new()); assert!(node.as_mapping().unwrap().is_empty()); } #[test] fn yaml_conversions() { use yaml_rust::YamlLoader; let mut everything = YamlLoader::load_from_str(include_str!("../examples/everything.yaml")).unwrap(); let everything = everything.pop().unwrap(); let node = Node::try_from(everything.clone()).unwrap(); assert!(node.as_mapping().is_some()); let badscalar = MarkedScalarNode::try_from(everything); assert_eq!(badscalar, Err(YamlConversionError::NonScalar)); let badscalar = MarkedScalarNode::try_from(YamlNode::BadValue); assert_eq!(badscalar, Err(YamlConversionError::BadValue)); let badscalar = MarkedScalarNode::try_from(YamlNode::Array(vec![])); assert_eq!(badscalar, Err(YamlConversionError::NonScalar)); } fn flatten(node: YamlNode) -> YamlNode { match node { YamlNode::Array(arr) => YamlNode::Array(arr.into_iter().map(flatten).collect()), YamlNode::Boolean(b) => { YamlNode::String((if b { "true" } else { "false" }).to_string()) } YamlNode::Hash(h) => YamlNode::Hash( h.into_iter() .map(|(k, v)| (flatten(k), flatten(v))) .collect(), ), YamlNode::Integer(i) => YamlNode::String(format!("{}", i)), YamlNode::Null => YamlNode::String("null".to_string()), YamlNode::Real(r) => YamlNode::String(r), other => other, } } #[test] fn back_yaml_conversion() { use yaml_rust::YamlLoader; let mut everything = YamlLoader::load_from_str(include_str!("../examples/everything.yaml")).unwrap(); let everything = everything.pop().unwrap(); let node = Node::try_from(everything.clone()).unwrap(); let other: YamlNode = node.into(); let flat = flatten(everything); assert_eq!(flat, other); } } marked-yaml-0.8.0/tests/character.rs000064400000000000000000000025731046102023000155070ustar 00000000000000//! All the tests in here relate to the ability to get character offsets for things. #[test] fn fix_24_character_offset_works() { let input = r#"# some comment key: value # another comment "# .to_string(); let document = marked_yaml::parse_yaml(0, &input).unwrap(); // With the above shape, we know the document is a mapping, so let's retrieve // the "key" value from it let node = document.as_mapping().unwrap().get_scalar("key").unwrap(); let span = node.span(); eprintln!("{span:?}"); eprintln!("{node:?}"); let (start, end) = ( span.start().unwrap().character(), // Because span.end() isn't reliable for scalar nodes right now, // if there's no end marker, try and synthesise it from the node's value. // This is unreliable because the node might have been folded, or had escaped // characters in it which we won't notice. span.end() .map(marked_yaml::Marker::character) .unwrap_or_else(|| span.start().unwrap().character() + node.as_str().chars().count()), ); let output = input .chars() .take(start) .chain("new_value".chars()) .chain(input.chars().skip(end)) .collect::(); let expected_output = r#"# some comment key: new_value # another comment "#; assert_eq!(output, expected_output); } marked-yaml-0.8.0/tests/serde.rs000064400000000000000000000107031046102023000146470ustar 00000000000000#![cfg(feature = "serde")] //! All these tests require serde //! use std::collections::HashMap; use marked_yaml::{ from_node, from_yaml, from_yaml_with_options, parse_yaml, FromYamlError, LoaderOptions, Span, Spanned, }; use serde::Deserialize; const TEST_DOC: &str = r#"# Line one is a comment top: - level - is always - two - strings u8s: [ 0, 1, 2, "255" ] i8s: [ -128, 0, 127 ] u32s: [ 65537 ] thingy: blue outcome: bad: stuff looksee: { ugly: [ first, second ] } known: { unknown: { name: Jeff, age: 14 } } kvs: first: one second: two third: banana falsy: false truthy: true yes: true "#; #[derive(Debug, Deserialize)] #[allow(dead_code)] struct FullTest { top: Vec>, u8s: Spanned>, i8s: Vec, u32s: Vec, missing: Option, thingy: Colour, outcome: EnumCheck, looksee: EnumCheck, known: EnumCheck, kvs: HashMap, Spanned>, falsy: Spanned, truthy: Spanned, yes: Spanned, } #[derive(Deserialize, Debug)] #[allow(dead_code)] enum EnumCheck { #[serde(alias = "good")] Good(String), #[serde(alias = "bad")] Bad(Spanned), #[serde(alias = "ugly")] Ugly(String, String), #[serde(alias = "unknown")] Unknown { name: Spanned, age: i64 }, } #[derive(Deserialize, Debug)] enum Colour { #[serde(alias = "blue")] Blue, #[serde(alias = "red")] Red, } #[test] fn read_everything() { let nodes = parse_yaml(0, TEST_DOC).unwrap(); let doc: FullTest = from_node(&nodes).unwrap(); println!("{doc:?}"); assert_eq!(doc.top[0].as_str(), "level"); assert!(doc.falsy == false); assert!(doc.truthy == true); assert_ne!(doc.truthy, doc.falsy); assert_eq!(doc.truthy, doc.yes); assert_eq!(doc.top[2], "two"); assert_ne!(doc.top[3], "two"); let s = String::from("s"); assert!(doc.top[0] != s); } #[test] fn ergonomics() { let doc: FullTest = from_yaml(0, TEST_DOC).unwrap(); assert_eq!(doc.kvs.get("first").map(|s| s.as_str()), Some("one")); let k1 = Spanned::new(Span::new_blank(), "k1"); let mut map = HashMap::new(); map.insert(k1, "v1"); let k2 = Spanned::new(Span::new_blank(), "k2"); assert!(!map.contains_key(&k2)); assert!(map.contains_key("k1")); } #[test] fn parse_fails() { let err = from_yaml::(0, "hello world").err().unwrap(); assert!(matches!(err, FromYamlError::ParseYaml(_))); let err = from_yaml::(0, "hello: world").err().unwrap(); assert!(matches!(err, FromYamlError::FromNode(_))); let s = format!("{err}"); assert!(s.starts_with("missing field")); #[derive(Deserialize)] #[allow(dead_code)] struct MiniDoc { colour: Colour, } let err = from_yaml::(0, "colour: {Red: optional}") .err() .unwrap(); let s = format! {"{err}"}; #[cfg(feature = "serde-path")] assert_eq!(s, "colour.Red: invalid type: map, expected String"); #[cfg(not(feature = "serde-path"))] assert_eq!(s, "invalid type: map, expected String"); } #[test] fn parse_fails_coerce() { let options = LoaderOptions::default().prevent_coercion(true); let err = from_yaml_with_options::(0, TEST_DOC, options) .err() .unwrap(); let s = format!("{err}"); #[cfg(feature = "serde-path")] assert!(s.starts_with("u8s[3]")); #[cfg(feature = "serde-path")] let s = s.strip_prefix("u8s[3]: ").unwrap(); assert!(s.starts_with("invalid type: string")); assert!(s.contains("expected u8")); } #[test] fn empty_scalar_map() { #[allow(dead_code)] #[derive(Debug, Deserialize)] struct Foo { foo: HashMap, } let _: Foo = from_yaml(0, "foo:").unwrap(); } #[test] fn empty_scalar_seq() { #[allow(dead_code)] #[derive(Debug, Deserialize)] struct Foo { foo: Vec, } let _: Foo = from_yaml(0, "foo:").unwrap(); } #[test] fn empty_scalar_unit() { #[allow(dead_code)] #[derive(Debug, Deserialize)] struct Foo { foo: (), } let _: Foo = from_yaml(0, "foo:").unwrap(); } #[test] fn empty_scalar_struct_with_default() { #[allow(dead_code)] #[derive(Default, Debug, Deserialize)] struct Bar { buz: Option, } #[allow(dead_code)] #[derive(Debug, Deserialize)] struct Foo { #[serde(default)] bar: Bar, } let _: Foo = from_yaml(0, "bar:").unwrap(); }