From 21023277c0225688206ab654af9ab9c1dd978221 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Thu, 22 Feb 2024 11:54:00 +0100 Subject: [PATCH 1/4] Ignore lints --- toml-span/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toml-span/README.md b/toml-span/README.md index 7ea0aab..e77d607 100644 --- a/toml-span/README.md +++ b/toml-span/README.md @@ -1,5 +1,5 @@ - + From 9c6f3a315f0f395a26dbd70ee77705416b64ac2b Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Thu, 22 Feb 2024 11:54:18 +0100 Subject: [PATCH 2/4] Add keys to expected list when .take() is used --- toml-span/src/de_helpers.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/toml-span/src/de_helpers.rs b/toml-span/src/de_helpers.rs index 0b2f2e0..8024e51 100644 --- a/toml-span/src/de_helpers.rs +++ b/toml-span/src/de_helpers.rs @@ -74,6 +74,7 @@ impl<'de> TableHelper<'de> { #[inline] pub fn take(&mut self, name: &'static str) -> Option<(value::Key<'de>, Value<'de>)> { + self.expected.push(name); self.table.remove_entry(&name.into()) } From 645e3ded5cb7230102c6f2c4afdac42d39430040 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Thu, 22 Feb 2024 17:21:28 +0100 Subject: [PATCH 3/4] Add docs --- .cargo/config.toml | 1 + integ-tests/src/lib.rs | 2 + toml-span/Cargo.toml | 4 + toml-span/README.md | 16 ++-- toml-span/src/de.rs | 3 + toml-span/src/de_helpers.rs | 109 ++++++++++-------------- toml-span/src/error.rs | 38 ++++++++- toml-span/src/{value => }/impl_serde.rs | 14 +-- toml-span/src/lib.rs | 14 ++- toml-span/src/span.rs | 16 +++- toml-span/src/tokens.rs | 3 + toml-span/src/value.rs | 78 ++++++++++++++++- 12 files changed, 210 insertions(+), 88 deletions(-) rename toml-span/src/{value => }/impl_serde.rs (81%) diff --git a/.cargo/config.toml b/.cargo/config.toml index 69564e3..b5e2f2d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -76,6 +76,7 @@ rustflags = [ "-Wnonstandard_style", "-Wrust_2018_idioms", # END - Embark standard lints v6 for Rust 1.55+ + "-Dmissing_docs", ] [target.'cfg(target_env = "musl")'] diff --git a/integ-tests/src/lib.rs b/integ-tests/src/lib.rs index 502bffa..2898770 100644 --- a/integ-tests/src/lib.rs +++ b/integ-tests/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + /// Loads a valid toml file and does a snapshot assertion against `toml` #[macro_export] macro_rules! valid { diff --git a/toml-span/Cargo.toml b/toml-span/Cargo.toml index b50b004..b89ccf5 100644 --- a/toml-span/Cargo.toml +++ b/toml-span/Cargo.toml @@ -17,3 +17,7 @@ reporting = ["dep:codespan-reporting"] codespan-reporting = { version = "0.11", optional = true } serde = { version = "1.0", optional = true } smallvec = "1.13" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/toml-span/README.md b/toml-span/README.md index e77d607..5c00bb2 100644 --- a/toml-span/README.md +++ b/toml-span/README.md @@ -32,7 +32,7 @@ First off I just want to be up front and clear about the differences/limitations This crate was specifically made to suit the needs of [cargo-deny], namely, that it can always retrieve the span of any toml item that it wants to. While the [toml](https://docs.rs/toml/latest/toml/) crate can also produce span information via [toml::Spanned](https://docs.rs/toml/latest/toml/struct.Spanned.html) there is one rather significant limitation, namely, that it must pass through [serde](https://docs.rs/serde/latest/serde/). While in simple cases the `Spanned` type works quite well, eg. -```rust +```rust,ignore #[derive(serde::Deserialize)] struct Simple { /// This works just fine @@ -42,7 +42,7 @@ struct Simple { As soon as you have a [more complicated scenario](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=aeb611bbe387538d2ebb6780055b3167), the mechanism that `toml` uses to get the span information breaks down. -```rust +```rust,ignore #[derive(serde::Deserialize)] #[serde(untagged)] enum Ohno { @@ -73,7 +73,7 @@ failed to deserialize toml: Error { inner: Error { inner: TomlError { message: " To understand why this fails we can look at what `#[derive(serde::Deserialize)]` expand to for `Ohno` in HIR. -```rust +```rust,ignore #[allow(unused_extern_crates, clippy :: useless_attribute)] extern crate serde as _serde; #[automatically_derived] @@ -103,8 +103,8 @@ impl <'de> _serde::Deserialize<'de> for Ohno { _serde::Deserialize>::deserialize(__deserializer), Ohno::SpannedString) { return _serde::__private::Ok(__ok); } _serde::__private::Err(_serde::de::Error::custom("data did not match any variant of untagged enum Ohno")) - } - } }; + } +} ``` What serde does in the untagged case is first deserialize into `_serde::__private::de::Content`, an internal API container that is easiest to think of as something like `serde_json::Value`. This is because serde speculatively parses each enum variant until one succeeds by passing a `ContentRefDeserializer` that just borrows the deserialized `Content` from earlier to satisfy the serde deserialize API consuming the `Deserializer`. The problem comes because of how [`toml::Spanned`](https://docs.rs/serde_spanned/0.6.5/src/serde_spanned/spanned.rs.html#161-212) works, namely that it uses a hack to workaround the limitations of the serde API in order to "deserialize" the item as well as its span information, by the `Spanned` object specifically requesting a set of keys from the `toml::Deserializer` impl so that it can [encode](https://github.com/toml-rs/toml/blob/c4b62fda23343037ebe5ea93db9393cb25fcf233/crates/toml_edit/src/de/spanned.rs#L27-L70) the span information as if it was a struct to satisfy serde. But serde doesn't know that when it deserializes the `Content` object, it just knows that the Deserializer reports it has a string, int or what have you, and deserializes that, "losing" the span information. This problem also affects things like `#[serde(flatten)]` for slightly different reasons, but they all basically come down to the serde API not truly supporting span information, nor [any plans](https://github.com/serde-rs/serde/issues/1811) to. @@ -127,7 +127,7 @@ The most simple use case for `toml-span` is just as slimmer version of `toml` th #### `toml` version -```rust +```rust,ignore fn is_crates_io_sparse(config: &toml::Value) -> Option { config .get("registries") @@ -160,7 +160,7 @@ Of course the most common case is deserializing toml into Rust containers. #### `toml` version -```rust +```rust,ignore #[derive(Deserialize, Clone)] #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[serde(rename_all = "kebab-case", deny_unknown_fields)] @@ -182,7 +182,7 @@ The following code is much more verbose (before proc macros run at least), but s Before `toml-span`, all cases where a user specifies a crate spec, (ie, name + optional version requirement) was done via two separate fields, `name` and `version`. This was quite verbose, as in many cases not only is `version` not specified, but also could be just a string if the user doesn't need/want to provide other fields. Normally one would use the [string or struct](https://serde.rs/string-or-struct.html) idiom but this was impossible due to how I wanted to reorganize the data to have the package spec as either a string or struct, _as well as_ optional data that is flattened to the same level as the package spec. But since `toml-span` changes how deserialization is done, this change was quite trivial after the initial work of getting the crate stood up was done. -```rust +```rust,ignore pub type CrateBan = PackageSpecOrExtended; #[cfg_attr(test, derive(Debug, PartialEq, Eq))] diff --git a/toml-span/src/de.rs b/toml-span/src/de.rs index 0911fd1..a6e35fb 100644 --- a/toml-span/src/de.rs +++ b/toml-span/src/de.rs @@ -1,3 +1,5 @@ +//! Core deserialization logic that deserializes toml content to [`Value`] + use crate::{ error::{Error, ErrorKind}, tokens::{Error as TokenError, Token, Tokenizer}, @@ -14,6 +16,7 @@ type DeStr<'de> = Cow<'de, str>; type TablePair<'de> = (Key<'de>, Val<'de>); type InlineVec = SmallVec<[T; 5]>; +/// Parses a toml string into a [`ValueInner::Table`] pub fn parse(s: &str) -> Result, Error> { let mut de = Deserializer::new(s); diff --git a/toml-span/src/de_helpers.rs b/toml-span/src/de_helpers.rs index 8024e51..bb0e4ff 100644 --- a/toml-span/src/de_helpers.rs +++ b/toml-span/src/de_helpers.rs @@ -1,3 +1,5 @@ +//! Provides helpers for deserializing [`Value`]/[`ValueInner`] into Rust types + use crate::{ span::Spanned, value::{self, Table, Value, ValueInner}, @@ -5,6 +7,7 @@ use crate::{ }; use std::{fmt::Display, str::FromStr}; +/// Helper for construction an [`ErrorKind::Wanted`] #[inline] pub fn expected(expected: &'static str, found: ValueInner<'_>, span: Span) -> Error { Error { @@ -17,6 +20,8 @@ pub fn expected(expected: &'static str, found: ValueInner<'_>, span: Span) -> Er } } +/// Attempts to acquire a [`ValueInner::String`] and parse it, returning an error +/// if the value is not a string, or the parse implementation fails #[inline] pub fn parse(value: &mut Value<'_>) -> Result where @@ -34,10 +39,17 @@ where } } +/// A helper for dealing with [`ValueInner::Table`] pub struct TableHelper<'de> { + /// The table the helper is operating upon pub table: Table<'de>, + /// The errors accumulated while deserializing pub errors: Vec, + /// The list of keys that have been requested by the user, this is used to + /// show a list of keys that _could_ be used in the case the finalize method + /// fails due to keys still being present in the map expected: Vec<&'static str>, + /// The span for the table location span: Span, } @@ -53,6 +65,7 @@ impl<'de> From<(Table<'de>, Span)> for TableHelper<'de> { } impl<'de> TableHelper<'de> { + /// Creates a helper for the value, failing if it is not a table pub fn new(value: &mut Value<'de>) -> Result { let table = match value.take() { ValueInner::Table(table) => table, @@ -67,22 +80,34 @@ impl<'de> TableHelper<'de> { }) } + /// Returns true if the table contains the specified key #[inline] - pub fn contains(&self, name: &'static str) -> bool { + pub fn contains(&self, name: &'de str) -> bool { self.table.contains_key(&name.into()) } + /// Takes the specified key and its value if it exists #[inline] pub fn take(&mut self, name: &'static str) -> Option<(value::Key<'de>, Value<'de>)> { self.expected.push(name); self.table.remove_entry(&name.into()) } + /// Attempts to deserialize the specified key + /// + /// Errors that occur when calling this method are automatically added to + /// the set of errors that are reported from [`Self::finalize`], so not early + /// returning if this method fails will still report the error by default + /// + /// # Errors + /// - The key does not exist + /// - The [`Deserialize`] implementation for the type returns an error #[inline] pub fn required>(&mut self, name: &'static str) -> Result { Ok(self.required_s(name)?.value) } + /// The same as [`Self::required`], except it returns a [`Spanned`] pub fn required_s>( &mut self, name: &'static str, @@ -106,30 +131,16 @@ impl<'de> TableHelper<'de> { }) } - pub fn with_default>( - &mut self, - name: &'static str, - def: impl FnOnce() -> T, - ) -> (T, Span) { - self.expected.push(name); - - let Some(mut val) = self.table.remove(&name.into()) else { - return (def(), Span::default()); - }; - - match T::deserialize(&mut val) { - Ok(v) => (v, val.span), - Err(mut err) => { - self.errors.append(&mut err.errors); - (def(), Span::default()) - } - } - } - + /// Attempts to deserialize the specified key, if it exists + /// + /// Note that if the key exists but deserialization fails, an error will be + /// appended and if [`Self::finalize`] is called it will return that error + /// along with any others that occurred pub fn optional>(&mut self, name: &'static str) -> Option { self.optional_s(name).map(|v| v.value) } + /// The same as [`Self::optional`], except it returns a [`Spanned`] pub fn optional_s>(&mut self, name: &'static str) -> Option> { self.expected.push(name); @@ -146,51 +157,17 @@ impl<'de> TableHelper<'de> { } } - pub fn parse(&mut self, name: &'static str) -> T - where - T: FromStr + Default, - E: Display, - { - self.expected.push(name); - - let Some(mut val) = self.table.remove(&name.into()) else { - self.errors.push(Error { - kind: ErrorKind::MissingField(name), - span: self.span, - line_info: None, - }); - return T::default(); - }; - - match parse(&mut val) { - Ok(v) => v, - Err(err) => { - self.errors.push(err); - T::default() - } - } - } - - pub fn parse_opt(&mut self, name: &'static str) -> Option - where - T: FromStr, - E: Display, - { - self.expected.push(name); - - let Some(mut val) = self.table.remove(&name.into()) else { - return None; - }; - - match parse(&mut val) { - Ok(v) => Some(v), - Err(err) => { - self.errors.push(err); - None - } - } - } - + /// Called when you are finished with this [`TableHelper`] + /// + /// If errors have been accumulated when using this [`TableHelper`], this will + /// return an error with all of those errors. + /// + /// Additionally, if [`Option::None`] is passed, any keys that still exist + /// in the table will be added to an [`ErrorKind::UnexpectedKeys`] error, + /// which can be considered equivalent to [`#[serde(deny_unknown_fields)]`](https://serde.rs/container-attrs.html#deny_unknown_fields) + /// + /// If you want simulate [`#[serde(flatten)]`](https://serde.rs/field-attrs.html#flatten) + /// you can instead put that table back in its original value during this step pub fn finalize(mut self, original: Option<&mut Value<'de>>) -> Result<(), DeserError> { if let Some(original) = original { original.set(ValueInner::Table(self.table)); diff --git a/toml-span/src/error.rs b/toml-span/src/error.rs index decd7f4..09ec2f9 100644 --- a/toml-span/src/error.rs +++ b/toml-span/src/error.rs @@ -4,8 +4,13 @@ use std::fmt::{self, Debug, Display}; /// Error that can occur when deserializing TOML. #[derive(Debug, Clone)] pub struct Error { + /// The error kind pub kind: ErrorKind, + /// The span where the error occurs. + /// + /// Note some [`ErrorKind`] contain additional span information pub span: Span, + /// Line and column information, only available for errors coming from the parser pub line_info: Option<(usize, usize)>, } @@ -65,7 +70,12 @@ pub enum ErrorKind { }, /// A duplicate table definition was found. - DuplicateTable { name: String, first: Span }, + DuplicateTable { + /// The name of the duplicate table + name: String, + /// The span where the table was first defined + first: Span, + }, /// Duplicate key in table. DuplicateKey { @@ -86,7 +96,10 @@ pub enum ErrorKind { Custom(std::borrow::Cow<'static, str>), /// Dotted key attempted to extend something that is not a table. - DottedKeyInvalidType { first: Span }, + DottedKeyInvalidType { + /// The span where the non-table value was defined + first: Span, + }, /// An unexpected key was encountered. /// @@ -94,6 +107,7 @@ pub enum ErrorKind { UnexpectedKeys { /// The unexpected keys. keys: Vec<(String, Span)>, + /// The list of keys that were expected for the table expected: Vec, }, @@ -105,12 +119,17 @@ pub enum ErrorKind { /// A field in the table is deprecated and the new key should be used instead Deprecated { + /// The deprecated key name old: &'static str, + /// The key name that should be used instead new: &'static str, }, /// An unexpected value was encountered - UnexpectedValue { expected: &'static [&'static str] }, + UnexpectedValue { + /// The list of values that could have been used, eg. typically enum variants + expected: &'static [&'static str], + }, } impl Display for ErrorKind { @@ -229,7 +248,9 @@ impl Display for Error { } #[cfg(feature = "reporting")] +#[cfg_attr(docsrs, doc(cfg(feature = "reporting")))] impl Error { + /// Converts this [`Error`] into a [`codespan_reporting::diagnostic::Diagnostic`] pub fn to_diagnostic( &self, fid: FileId, @@ -333,11 +354,22 @@ impl Error { } } +/// When deserializing, it's possible to collect multiple errors instead of earlying +/// out at the first error #[derive(Debug)] pub struct DeserError { + /// The set of errors that occurred during deserialization pub errors: Vec, } +impl DeserError { + /// Merges errors from another [`Self`] + #[inline] + pub fn merge(&mut self, mut other: Self) { + self.errors.append(&mut other.errors); + } +} + impl std::error::Error for DeserError {} impl From for DeserError { diff --git a/toml-span/src/value/impl_serde.rs b/toml-span/src/impl_serde.rs similarity index 81% rename from toml-span/src/value/impl_serde.rs rename to toml-span/src/impl_serde.rs index 31a4a60..40562a9 100644 --- a/toml-span/src/value/impl_serde.rs +++ b/toml-span/src/impl_serde.rs @@ -1,8 +1,12 @@ -use crate::value::{Value, ValueInner}; -use serde::{ - self, - ser::{SerializeMap, SerializeSeq}, +#![cfg_attr(docsrs, doc(cfg(feature = "serde")))] + +//! Provides [`serde::Serialize`] support for [`Value`] and [`Spanned`] + +use crate::{ + value::{Value, ValueInner}, + Spanned, }; +use serde::ser::{SerializeMap, SerializeSeq}; impl<'de> serde::Serialize for Value<'de> { fn serialize(&self, ser: S) -> Result @@ -32,7 +36,7 @@ impl<'de> serde::Serialize for Value<'de> { } } -impl serde::Serialize for crate::span::Spanned +impl serde::Serialize for Spanned where T: serde::Serialize, { diff --git a/toml-span/src/lib.rs b/toml-span/src/lib.rs index a229b66..f205978 100644 --- a/toml-span/src/lib.rs +++ b/toml-span/src/lib.rs @@ -1,4 +1,5 @@ -#![allow(missing_docs)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc = include_str!("../README.md")] pub mod de; pub mod de_helpers; @@ -9,12 +10,21 @@ pub mod value; pub use de::parse; pub use error::{DeserError, Error, ErrorKind}; -pub use span::Span; +pub use span::{Span, Spanned}; pub use value::Value; +#[cfg(feature = "serde")] +pub mod impl_serde; + +/// This crate's equivalent to [`serde::Deserialize`](https://docs.rs/serde/latest/serde/de/trait.Deserialize.html) pub trait Deserialize<'de>: Sized { + /// Given a mutable [`Value`], allows you to deserialize the type from it, + /// or accumulate 1 or more errors fn deserialize(value: &mut Value<'de>) -> Result; } +/// This crate's equivalent to [`serde::DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html) +/// +/// This is useful if you want to use trait bounds pub trait DeserializeOwned: for<'de> Deserialize<'de> {} impl DeserializeOwned for T where T: for<'de> Deserialize<'de> {} diff --git a/toml-span/src/span.rs b/toml-span/src/span.rs index 39eb5aa..925145a 100644 --- a/toml-span/src/span.rs +++ b/toml-span/src/span.rs @@ -1,15 +1,22 @@ +//! Provides span helpers + +/// A start and end location within a toml document #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] pub struct Span { + /// The start byte index pub start: usize, + /// The end (exclusive) byte index pub end: usize, } impl Span { + /// Creates a new [`Span`] #[inline] pub fn new(start: usize, end: usize) -> Self { Self { start, end } } + /// Checks if the start and end are the same, and thus the span is empty #[inline] pub fn is_empty(&self) -> bool { self.start == 0 && self.end == 0 @@ -40,12 +47,16 @@ impl From for std::ops::Range { } } +/// An arbitrary `T` with additional span information pub struct Spanned { + /// The value pub value: T, + /// The span information for the value pub span: Span, } impl Spanned { + /// Creates a [`Spanned`] with just the value and an empty [`Span`] #[inline] pub const fn new(value: T) -> Self { Self { @@ -54,17 +65,20 @@ impl Spanned { } } + /// Creates a [`Spanned`] from both a value and a [`Span`] #[inline] pub const fn with_span(value: T, span: Span) -> Self { Self { value, span } } - /// Converts Self into its inner value + /// Converts [`Self`] into its inner value #[inline] pub fn take(self) -> T { self.value } + /// Helper to convert the value inside the Spanned + #[inline] pub fn map(self) -> Spanned where V: From, diff --git a/toml-span/src/tokens.rs b/toml-span/src/tokens.rs index 48dc4e9..134785f 100644 --- a/toml-span/src/tokens.rs +++ b/toml-span/src/tokens.rs @@ -1,3 +1,6 @@ +#![allow(missing_docs)] +//! The tokenizer is publicly exposed if you wish to use it instead + use crate::{value::Key, Span}; use std::{borrow::Cow, char, str}; diff --git a/toml-span/src/value.rs b/toml-span/src/value.rs index 2ebd442..14b6aa6 100644 --- a/toml-span/src/value.rs +++ b/toml-span/src/value.rs @@ -1,20 +1,27 @@ +//! Contains the [`Value`] and [`ValueInner`] containers into which all toml +//! contents can be deserialized into and either used directly or fed into +//! [`crate::Deserialize`] or your own constructs to deserialize into your own +//! types + use crate::{Error, ErrorKind, Span}; use std::{borrow::Cow, fmt}; -#[cfg(feature = "serde")] -mod impl_serde; - +/// A deserialized [`ValueInner`] with accompanying [`Span`] information for where +/// it was located in the toml document pub struct Value<'de> { value: Option>, + /// The location of the value in the toml document pub span: Span, } impl<'de> Value<'de> { + /// Creates a new [`Value`] with an empty [`Span`] #[inline] pub fn new(value: ValueInner<'de>) -> Self { Self::with_span(value, Span::default()) } + /// Creates a new [`Value`] with the specified [`Span`] #[inline] pub fn with_span(value: ValueInner<'de>, span: Span) -> Self { Self { @@ -23,11 +30,20 @@ impl<'de> Value<'de> { } } + /// Takes the inner [`ValueInner`] + /// + /// This panics if the inner value has already been taken. + /// + /// Typically paired with [`Self::set`] #[inline] pub fn take(&mut self) -> ValueInner<'de> { self.value.take().expect("the value has already been taken") } + /// Sets the inner [`ValueInner`] + /// + /// This is typically done when the value is taken with [`Self::take`], + /// processed, and returned #[inline] pub fn set(&mut self, value: ValueInner<'de>) { self.value = Some(value); @@ -45,6 +61,7 @@ impl<'de> Value<'de> { }) } + /// Returns true if the value is a table and has the specified key #[inline] pub fn has_key(&self, key: &str) -> bool { self.value.as_ref().map_or(false, |val| { @@ -56,6 +73,8 @@ impl<'de> Value<'de> { }) } + /// Takes the value as a string, returning an error with either a default + /// or user supplied message #[inline] pub fn take_string(&mut self, msg: Option<&'static str>) -> Result, Error> { match self.take() { @@ -71,36 +90,60 @@ impl<'de> Value<'de> { } } + /// Returns a borrowed string if this is a [`ValueInner::String`] #[inline] pub fn as_str(&self) -> Option<&str> { self.value.as_ref().and_then(|v| v.as_str()) } + /// Returns a borrowed table if this is a [`ValueInner::Table`] #[inline] pub fn as_table(&self) -> Option<&Table<'de>> { self.value.as_ref().and_then(|v| v.as_table()) } + /// Returns a borrowed array if this is a [`ValueInner::Array`] #[inline] pub fn as_array(&self) -> Option<&Array<'de>> { self.value.as_ref().and_then(|v| v.as_array()) } + /// Returns an `i64` if this is a [`ValueInner::Integer`] #[inline] pub fn as_integer(&self) -> Option { self.value.as_ref().and_then(|v| v.as_integer()) } + /// Returns an `f64` if this is a [`ValueInner::Float`] #[inline] pub fn as_float(&self) -> Option { self.value.as_ref().and_then(|v| v.as_float()) } + /// Returns a `bool` if this is a [`ValueInner::Boolean`] #[inline] pub fn as_bool(&self) -> Option { self.value.as_ref().and_then(|v| v.as_bool()) } + /// Uses JSON pointer-like syntax to lookup a specific [`Value`] + /// + /// The basic format is: + /// + /// - The path starts with `/` + /// - Each segment is separated by a `/` + /// - Each segment is either a key name, or an integer array index + /// + /// ```rust + /// let data = "[x]\ny = ['z', 'zz']"; + /// let value = toml_span::parse(data).unwrap(); + /// assert_eq!(value.pointer("/x/y/1").unwrap().as_str().unwrap(), "zz"); + /// assert!(value.pointer("/a/b/c").is_none()); + /// ``` + /// + /// Note that this is JSON pointer**-like** because `/` is not supported in + /// key names because I don't see the point. If you want this it is easy to + /// implement. pub fn pointer(&self, pointer: &'de str) -> Option<&Self> { if pointer.is_empty() { return Some(self); @@ -124,6 +167,7 @@ impl<'de> Value<'de> { }) } + /// The `mut` version of [`Self::pointer`] pub fn pointer_mut(&mut self, pointer: &'de str) -> Option<&mut Self> { if pointer.is_empty() { return Some(self); @@ -171,9 +215,13 @@ impl<'de> fmt::Debug for Value<'de> { } } +/// A toml table key #[derive(Clone)] pub struct Key<'de> { + /// The key itself, in most cases it will be borrowed, but may be owned + /// if escape characters are present in the original source pub name: Cow<'de, str>, + /// The span for the key in the original document pub span: Span, } @@ -212,20 +260,38 @@ impl<'de> PartialEq for Key<'de> { impl<'de> Eq for Key<'de> {} +/// A toml table, always represented as a sorted map. +/// +/// The original key ordering can be obtained by ordering the keys by their span pub type Table<'de> = std::collections::BTreeMap, Value<'de>>; +/// A toml array pub type Array<'de> = Vec>; +/// The core value types that toml can deserialize to +/// +/// Note that this library does not support datetime values that are part of the +/// toml spec since I have no need of them, but could be added #[derive(Debug)] pub enum ValueInner<'de> { + /// A string. + /// + /// This will be borrowed from the original toml source unless it contains + /// escape characters String(Cow<'de, str>), + /// An integer Integer(i64), + /// A float Float(f64), + /// A boolean Boolean(bool), + /// An array Array(Array<'de>), + /// A table Table(Table<'de>), } impl<'de> ValueInner<'de> { + /// Gets the type of the value as a string pub fn type_str(&self) -> &'static str { match self { Self::String(..) => "string", @@ -237,6 +303,7 @@ impl<'de> ValueInner<'de> { } } + /// Returns a borrowed string if this is a [`Self::String`] #[inline] pub fn as_str(&self) -> Option<&str> { if let Self::String(s) = self { @@ -246,6 +313,7 @@ impl<'de> ValueInner<'de> { } } + /// Returns a borrowed table if this is a [`Self::Table`] #[inline] pub fn as_table(&self) -> Option<&Table<'de>> { if let ValueInner::Table(t) = self { @@ -255,6 +323,7 @@ impl<'de> ValueInner<'de> { } } + /// Returns a borrowed array if this is a [`Self::Array`] #[inline] pub fn as_array(&self) -> Option<&Array<'de>> { if let ValueInner::Array(a) = self { @@ -264,6 +333,7 @@ impl<'de> ValueInner<'de> { } } + /// Returns an `i64` if this is a [`Self::Integer`] #[inline] pub fn as_integer(&self) -> Option { if let ValueInner::Integer(i) = self { @@ -273,6 +343,7 @@ impl<'de> ValueInner<'de> { } } + /// Returns an `f64` if this is a [`Self::Float`] #[inline] pub fn as_float(&self) -> Option { if let ValueInner::Float(f) = self { @@ -282,6 +353,7 @@ impl<'de> ValueInner<'de> { } } + /// Returns a `bool` if this is a [`Self::Boolean`] #[inline] pub fn as_bool(&self) -> Option { if let ValueInner::Boolean(b) = self { From 643adc87e934e288b0a8de0a619138549afe6947 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Thu, 22 Feb 2024 17:25:31 +0100 Subject: [PATCH 4/4] Join on single job --- .github/workflows/rust-ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 383b2cd..dbac30e 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -44,7 +44,7 @@ jobs: - name: cargo test run: cargo test - deny-check: + deny: name: cargo-deny runs-on: ubuntu-22.04 steps: @@ -60,3 +60,9 @@ jobs: - run: cargo fetch - name: cargo publish check run: cargo publish --dry-run -p toml-span + + test_success: + runs-on: ubuntu-22.04 + needs: [lint,test,deny,publish-check] + steps: + - run: echo "All test jobs passed" \ No newline at end of file