Skip to content

Commit

Permalink
Merge pull request #7 from kinnison/ergonomics
Browse files Browse the repository at this point in the history
Ergonomics
  • Loading branch information
kinnison authored Mar 26, 2024
2 parents e534fc5 + 79c0834 commit ed46c99
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 5 deletions.
4 changes: 3 additions & 1 deletion marked-yaml/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,6 @@ pub mod spanned_serde;

#[cfg(feature = "serde")]
#[doc(inline)]
pub use spanned_serde::{from_node, Error, FromNodeError, Spanned};
pub use spanned_serde::{
from_node, from_yaml, from_yaml_with_options, Error, FromNodeError, Spanned,
};
1 change: 1 addition & 0 deletions marked-yaml/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub enum LoadError {
}

/// Options for loading YAML
#[derive(Debug, Default)]
pub struct LoaderOptions {
/// If true, duplicate keys in mappings will cause an error. If false,
/// the last key will be used.
Expand Down
137 changes: 134 additions & 3 deletions marked-yaml/src/spanned_serde.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Serde support for marked data deserialisation

use std::{
borrow::Borrow,
fmt,
hash::Hash,
iter::Peekable,
Expand All @@ -11,15 +12,15 @@ use std::{

use serde::{
de::{
value::BorrowedStrDeserializer, EnumAccess, IntoDeserializer, MapAccess, SeqAccess,
Unexpected, VariantAccess, Visitor,
value::BorrowedStrDeserializer, DeserializeOwned, EnumAccess, IntoDeserializer, MapAccess,
SeqAccess, Unexpected, VariantAccess, Visitor,
},
forward_to_deserialize_any, Deserialize, Deserializer, Serialize,
};

use crate::{
types::{MarkedMappingNode, MarkedScalarNode, MarkedSequenceNode},
Marker, Node, Span,
LoaderOptions, Marker, Node, Span,
};

/// Wrapper which can be used when deserialising data from [`Node`]
Expand Down Expand Up @@ -62,6 +63,21 @@ where
}
}

impl<T> PartialEq<T> for Spanned<T>
where
T: PartialEq,
{
fn eq(&self, other: &T) -> bool {
(&self.inner as &dyn PartialEq<T>).eq(other)
}
}

impl PartialEq<str> for Spanned<String> {
fn eq(&self, other: &str) -> bool {
self.inner == other
}
}

impl<T> Eq for Spanned<T> where T: Eq {}

impl<T> Hash for Spanned<T>
Expand All @@ -73,6 +89,18 @@ where
}
}

impl Borrow<str> for Spanned<String> {
fn borrow(&self) -> &str {
self.inner.borrow()
}
}

impl Borrow<str> for Spanned<&'_ str> {
fn borrow(&self) -> &str {
self.inner
}
}

// -------------------------------------------------------------------------------

// Convention for these markers comes from the toml crates
Expand Down Expand Up @@ -359,6 +387,8 @@ impl<'node> NodeDeserializer<'node> {
}
}

// -------------------------------------------------------------------------------

/// The error returned by [`from_node`]
///
/// From here you can get the logical path to the error if
Expand Down Expand Up @@ -401,6 +431,105 @@ impl fmt::Display for FromNodeError {
}
}

// -------------------------------------------------------------------------------

/// Errors which can occur when deserialising from YAML
#[derive(Debug)]
pub enum FromYamlError {
ParseYaml(crate::LoadError),
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<crate::LoadError> for FromYamlError {
fn from(value: crate::LoadError) -> Self {
Self::ParseYaml(value)
}
}

impl From<FromNodeError> 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<String>,
/// }
/// 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<T>(source: usize, yaml: &str) -> Result<T, FromYamlError>
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<String>,
/// }
/// 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<T>(
source: usize,
yaml: &str,
options: LoaderOptions,
) -> Result<T, FromYamlError>
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
Expand Down Expand Up @@ -542,6 +671,8 @@ fn render_path(path: &serde_path_to_error::Path) -> String {
ret
}

// -------------------------------------------------------------------------------

macro_rules! forward_to_nodes {
() => {
forward_to_nodes! [
Expand Down
15 changes: 14 additions & 1 deletion marked-yaml/tests/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
//! All these tests require serde
//!

use marked_yaml::{from_node, parse_yaml, Spanned};
use std::collections::HashMap;

use marked_yaml::{from_node, from_yaml, parse_yaml, Spanned};
use serde::Deserialize;

const TEST_DOC: &str = r#"# Line one is a comment
Expand All @@ -18,6 +20,10 @@ outcome:
bad: stuff
looksee: { ugly: [ first, second ] }
known: { unknown: { name: Jeff, age: 14 } }
kvs:
first: one
second: two
third: banana
"#;

#[derive(Debug, Deserialize)]
Expand All @@ -32,6 +38,7 @@ struct FullTest {
outcome: EnumCheck,
looksee: EnumCheck,
known: EnumCheck,
kvs: HashMap<Spanned<String>, Spanned<String>>,
}

#[derive(Deserialize, Debug)]
Expand Down Expand Up @@ -61,3 +68,9 @@ fn read_everything() {
let doc: FullTest = from_node(&nodes).unwrap();
println!("{doc:?}");
}

#[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"));
}

0 comments on commit ed46c99

Please sign in to comment.