From bf7371391959df875883700ce1e9db0dbc79b0e8 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 9 Jul 2024 17:43:02 -0400 Subject: [PATCH] Small DX changes + more docs (#61) * push and replace token methods take `T: Into` rather than `Token` * impl `From` all primitive int types for `Token` * docs & links * renames `replace_token` to `replace`, `ReplaceTokenError` to `ReplaceError` * removes Serialize and Deserialize from `Token` * adds apache license file, renames mit license to LICENSE-MIT --- LICENSE-APACHE | 201 +++++++++++++++++++++++++++++++++++++++++ LICENSE => LICENSE-MIT | 2 +- README.md | 66 ++++++++++---- src/index.rs | 4 +- src/lib.rs | 61 +++++++------ src/pointer.rs | 100 ++++++++++---------- src/token.rs | 57 +++--------- 7 files changed, 352 insertions(+), 139 deletions(-) create mode 100644 LICENSE-APACHE rename LICENSE => LICENSE-MIT (96%) diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..3173c0e --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2024 Chance Dinkins + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE b/LICENSE-MIT similarity index 96% rename from LICENSE rename to LICENSE-MIT index 25f66dc..7b7af1b 100644 --- a/LICENSE +++ b/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Chance +Copyright (c) 2022 Chance Dinkins Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 7381f09..a1fd86e 100644 --- a/README.md +++ b/README.md @@ -36,28 +36,60 @@ flags](#feature-flags). ## Usage -To parse a [`Pointer`] from a string, use the [`Pointer::parse`] method. -[`PointerBuf`] can use either the [`PointerBuf::parse`] or constructed from an -iterator of [`Token`]s with the [`PointerBuf::from_tokens`]: +To parse a [`Pointer`] from a string, use either [`Pointer::parse`], for +potentially fallible parsing, or the `const fn` `from_static` to produce a +`&'static Pointer` from a string that is known to be valid. ```rust -use jsonptr::{Pointer, PointerBuf}; -use serde_json::json; - +# use jsonptr::Pointer; let ptr = Pointer::parse("/examples/0/name").unwrap(); -let buf = PointerBuf::from_tokens(["examples", "0", "name"]); -assert_eq!(ptr, &buf); +let static_ptr = Pointer::from_static("/examples/0/name"); +assert_eq!(ptr, static_ptr); let parent = ptr.parent().unwrap(); assert_eq!(parent, Pointer::parse("/examples/0").unwrap()); -let (front, remaining) = ptr.split_front().unwrap(); -assert_eq!(front.decoded(), "examples"); +let (token, remaining) = ptr.split_front().unwrap(); +assert_eq!(token.decoded(), "examples"); assert_eq!(remaining, Pointer::parse("/0/name").unwrap()); ``` -Get values at the location of a pointer using either the [`Resolve`] and +[`PointerBuf`]s can be parsed using [`PointerBuf::parse`] or constructed from an +iterator of [`Token`]s with the [`from_tokens`] method: + +```rust +# use jsonptr::PointerBuf; +let mut buf = PointerBuf::parse("/examples/0/name").unwrap(); + +let from_tokens = PointerBuf::from_tokens(["examples", "0", "name"]); +assert_eq!(&buf, &from_tokens); + +buf.push_front("pointer"); +buf.push_front("~"); +buf.push_back("/"); +assert_eq!(buf.as_str(), "/~0/pointer/examples/0/name/~1"); +``` + +Iterating over the tokens or components of a pointer: + +```rust +# use jsonptr::{Pointer, Component, Token}; +let ptr = Pointer::from_static("/path/to/value"); + +// Using the `tokens` method: +let tokens: Vec<_> = ptr.tokens().collect(); +assert_eq!(tokens, vec![Token::new("path"), Token::new("to"), Token::new("value")]); + +// Using the `components` method: +let mut components = ptr.components(); +assert_eq!(components.next(), Some(Component::Root)); +assert_eq!(components.next(), Some(Component::Token(Token::new("path")))); +assert_eq!(components.next(), Some(Component::Token(Token::new("to")))); +assert_eq!(components.next(), Some(Component::Token(Token::new("value")))); +``` + +To get a value at the location of a pointer, use either the [`Resolve`] and [`ResolveMut`] traits or [`Pointer::resolve`] and [`Pointer::resolve_mut`] methods. See the [`resolve`] mod for more information. @@ -141,19 +173,19 @@ dual licensed as above, without any additional terms or conditions. [`Pointer::components`]: https://docs.rs/jsonptr/latest/jsonptrstruct.Pointer.html#method.components [`Pointer::tokens`]: https://docs.rs/jsonptr/latest/jsonptrstruct.Pointer.html#method.tokens [`Pointer`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html +[`Pointer::parse`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.parse [`Pointer::resolve`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.resolve [`Pointer::resolve_mut`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.resolve_mut [`Pointer::assign`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.assign [`Pointer::delete`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.delete [`PointerBuf::parse`]: https://docs.rs/jsonptr/latest/jsonptr/struct.PointerBuf.html#method.parse -[`PointerBuf::from_tokens`]: https://docs.rs/jsonptr/latest/jsonptr/struct.PointerBuf.html#method.from_tokens +[`from_tokens`]: https://docs.rs/jsonptr/latest/jsonptr/struct.PointerBuf.html#method.from_tokens [`PointerBuf`]: https://docs.rs/jsonptr/latest/jsonptr/struct.PointerBuf.html [`Token`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Token.html [`Tokens`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Tokens.html [`Components`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Components.html [`Component`]: https://docs.rs/jsonptr/latest/jsonptr/enum.Component.html -[`Root`]: https://docs.rs/jsonptr/latest/jsonptr/enum.Component.html#variant.Root -[`index`]: https://doc.rust-lang.org/std/primitive.usize.html +[`index`]: https://docs.rs/jsonptr/latest/jsonptr/index/index.html [`tokens`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.tokens [`components`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.components [`resolve`]: https://docs.rs/jsonptr/latest/jsonptr/resolve/index.html @@ -163,8 +195,10 @@ dual licensed as above, without any additional terms or conditions. [`ResolveMut`]: https://docs.rs/jsonptr/latest/jsonptr/resolve/trait.ResolveMut.html [`Assign`]: https://docs.rs/jsonptr/latest/jsonptr/assign/trait.Assign.html [`Delete`]: https://docs.rs/jsonptr/latest/jsonptr/delete/trait.Delete.html -[`serde`]: https://docs.rs/serde/1.0.120/serde/index -[`serde_json`]: https://docs.rs/serde_json/1.0.120/serde_json/enum.Value.html +[`serde`]: https://docs.rs/serde/1.0/serde/index +[`serde_json`]: https://docs.rs/serde_json/1.0/serde_json/enum.Value.html +[`serde_json::Value`]: https://docs.rs/serde_json/1.0/serde_json/enum.Value.html [`toml`]: https://docs.rs/toml/0.8/toml/enum.Value.html +[`toml::Value`]: https://docs.rs/toml/0.8/toml/enum.Value.html [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html [`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html diff --git a/src/index.rs b/src/index.rs index e81acca..d2e4649 100644 --- a/src/index.rs +++ b/src/index.rs @@ -33,7 +33,7 @@ //! //! assert_eq!(Index::Num(42).for_len_unchecked(30), 42); //! assert_eq!(Index::Next.for_len_unchecked(30), 30); -//! ```` +//! ``` use crate::Token; use alloc::string::String; @@ -133,7 +133,7 @@ impl Index { /// // no bounds checks /// assert_eq!(Index::Num(34).for_len_unchecked(40), 34); /// assert_eq!(Index::Next.for_len_unchecked(34), 34); - /// ```` + /// ``` pub fn for_len_unchecked(&self, length: usize) -> usize { match *self { Self::Num(idx) => idx, diff --git a/src/lib.rs b/src/lib.rs index 6608445..850dd2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,32 +1,37 @@ // rustdoc + README hack: https://linebender.org/blog/doc-include -//! -//! -//! [`Pointer`]: `crate::Pointer` -//! [`PointerBuf`]: `crate::PointerBuf` -//! [`Token`]: `crate::Token` -//! [`Tokens`]: `crate::Tokens` -//! [`Component`]: `crate::Component` -//! [`Components`]: `crate::Components` -//! [`Resolve`]: `crate::resolve::Resolve` -//! [`ResolveMut`]: `crate::resolve::ResolveMut` -//! [`resolve`]: `crate::resolve` -//! [`assign`]: `crate::assign` -//! [`delete`]: `crate::delete` -//! [`index`]: `crate::index` -//! [`Root`]: `crate::Component::Root` -//! [`str`]: `str` -//! [`String`]: `String` -//! [`serde_json::Value`]: `serde_json::Value` -//! [`Pointer::resolve`]: `crate::Pointer::resolve` -//! [`Pointer::resolve_mut`]: `crate::Pointer::resolve_mut` -//! [`Pointer::assign`]: `crate::Pointer::assign` -//! [`Pointer::delete`]: `crate::Pointer::delete` -//! [`Pointer::parse`]: `crate::Pointer::parse` -//! [`PointerBuf::parse`]: `crate::PointerBuf::parse` -//! [`PointerBuf::from_tokens`]: `crate::Pointer::from_tokens` +//! +//! [`Pointer`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html +//! [`Pointer::tokens`]: crate::Pointer::tokens +//! [`Pointer::components`]: crate::Pointer::components +//! [`Pointer::parse`]: crate::Pointer::parse +//! [`Pointer::resolve`]: crate::Pointer::resolve +//! [`Pointer::resolve_mut`]: crate::Pointer::resolve_mut +//! [`Pointer::assign`]: crate::Pointer::assign +//! [`Pointer::delete`]: crate::Pointer::delete +//! [`PointerBuf::parse`]: crate::PointerBuf::parse +//! [`PointerBuf`]: crate::PointerBuf +//! [`from_tokens`]: crate::PointerBuf::from_tokens +//! [`Token`]: crate::Token +//! [`Tokens`]: crate::Tokens +//! [`Components`]: crate::Components +//! [`Component`]: crate::Component +//! [`index`]: crate::index +//! [`tokens`]: crate::Pointer::tokens +//! [`components`]: crate::Pointer::components +//! [`resolve`]: crate::resolve +//! [`assign`]: crate::asign +//! [`delete`]: crate::delete +//! [`Resolve`]: crate::resolve::Resolve +//! [`ResolveMut`]: crate::resolve::ResolveMut +//! [`Assign`]: crate::assign::Assign +//! [`Delete`]: crate::delete::Delete +//! [`serde`]: https://docs.rs/serde/1.0/serde/index +//! [`serde_json`]: https://docs.rs/serde_json/1.0/serde_json/enum.Value.html +//! [`serde_json::Value`]: https://docs.rs/serde_json/1.0/serde_json/enum.Value.html +//! [`toml`]: https://docs.rs/toml/0.8/toml/enum.Value.html //! [`toml::Value`]: https://docs.rs/toml/0.8/toml/enum.Value.html +//! [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html +//! [`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html #![doc = include_str!("../README.md")] #![warn(missing_docs)] @@ -60,7 +65,7 @@ pub mod resolve; pub use resolve::{Resolve, ResolveMut}; mod pointer; -pub use pointer::{ParseError, Pointer, PointerBuf, ReplaceTokenError}; +pub use pointer::{ParseError, Pointer, PointerBuf}; mod token; pub use token::{InvalidEncodingError, Token, Tokens}; diff --git a/src/pointer.rs b/src/pointer.rs index 0e56d88..7312b0d 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -91,7 +91,7 @@ impl Pointer { /// let data = json!({ "foo": { "bar": "baz" } }); /// let bar = data.resolve(POINTER).unwrap(); /// assert_eq!(bar, "baz"); - /// ```` + /// ``` pub const fn from_static(s: &'static str) -> &'static Self { assert!(validate(s).is_ok(), "invalid json pointer"); unsafe { &*(core::ptr::from_ref::(s) as *const Self) } @@ -204,11 +204,11 @@ impl Pointer { /// assert_eq!(tail, Pointer::from_static("/bar/baz")); /// assert_eq!(ptr.split_at(3), None); /// ``` - pub fn split_at(&self, idx: usize) -> Option<(&Self, &Self)> { - if self.0.as_bytes().get(idx).copied() != Some(b'/') { + pub fn split_at(&self, offset: usize) -> Option<(&Self, &Self)> { + if self.0.as_bytes().get(offset).copied() != Some(b'/') { return None; } - let (head, tail) = self.0.split_at(idx); + let (head, tail) = self.0.split_at(offset); Some((Self::new(head), Self::new(tail))) } @@ -367,6 +367,8 @@ impl Pointer { /// assert_eq!(data.delete(&ptr), Some(json!({ "foo": { "bar": "baz" } }))); /// assert!(data.is_null()); /// ``` + /// + /// [`Delete`]: crate::delete::Delete #[cfg(feature = "delete")] pub fn delete(&self, value: &mut D) -> Option { value.delete(self) @@ -393,6 +395,7 @@ impl Pointer { /// ## Errors /// Returns [`Assign::Error`] if the path is invalid or if the value cannot be assigned. /// + /// [`Assign::Error`]: crate::assign::Assign::Error #[cfg(feature = "assign")] pub fn assign(&self, dest: &mut D, src: V) -> Result, D::Error> where @@ -698,7 +701,7 @@ impl<'a> IntoIterator for &'a Pointer { /// An owned, mutable [`Pointer`] (akin to `String`). /// /// This type provides methods like [`PointerBuf::push_back`] and -/// [`PointerBuf::replace_token`] that mutate the pointer in place. It also +/// [`PointerBuf::replace`] that mutate the pointer in place. It also /// implements [`core::ops::Deref`] to [`Pointer`], meaning that all methods on /// [`Pointer`] slices are available on `PointerBuf` values as well. #[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -737,15 +740,21 @@ impl PointerBuf { } /// Pushes a `Token` onto the front of this `Pointer`. - pub fn push_front(&mut self, token: Token) { + pub fn push_front<'t, T>(&mut self, token: T) + where + T: Into>, + { self.0.insert(0, '/'); - self.0.insert_str(1, token.encoded()); + self.0.insert_str(1, token.into().encoded()); } /// Pushes a `Token` onto the back of this `Pointer`. - pub fn push_back(&mut self, token: Token) { + pub fn push_back<'t, T>(&mut self, token: T) + where + T: Into>, + { self.0.push('/'); - self.0.push_str(token.encoded()); + self.0.push_str(token.into().encoded()); } /// Removes and returns the last `Token` in the `Pointer` if it exists. @@ -789,27 +798,26 @@ impl PointerBuf { /// `Token` if it already exists. Returns `None` otherwise. /// /// ## Errors - /// A [`ReplaceTokenError`] is returned if the index is out of bounds. - pub fn replace_token( - &mut self, - index: usize, - token: Token, - ) -> Result, ReplaceTokenError> { + /// A [`ReplaceError`] is returned if the index is out of bounds. + pub fn replace<'t, T>(&mut self, index: usize, token: T) -> Result, ReplaceError> + where + T: Into>, + { if self.is_root() { - return Err(ReplaceTokenError { + return Err(ReplaceError { count: self.count(), index, }); } let mut tokens = self.tokens().collect::>(); if index >= tokens.len() { - return Err(ReplaceTokenError { + return Err(ReplaceError { count: tokens.len(), index, }); } let old = tokens.get(index).map(super::token::Token::to_owned); - tokens[index] = token; + tokens[index] = token.into(); let mut buf = String::new(); for token in tokens { @@ -1108,24 +1116,24 @@ impl std::error::Error for ParseError { ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ -/// Returned from `Pointer::replace_token` when the provided index is out of +/// Returned from [`PointerBuf::replace`] when the provided index is out of /// bounds. #[derive(Debug, PartialEq, Eq)] -pub struct ReplaceTokenError { +pub struct ReplaceError { /// The index of the token that was out of bounds. pub index: usize, /// The number of tokens in the `Pointer`. pub count: usize, } -impl fmt::Display for ReplaceTokenError { +impl fmt::Display for ReplaceError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "index {} is out of bounds ({})", self.index, self.count) } } #[cfg(feature = "std")] -impl std::error::Error for ReplaceTokenError {} +impl std::error::Error for ReplaceError {} /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ @@ -1272,12 +1280,12 @@ mod tests { assert_eq!(ptr, "", "default, root pointer should equal \"\""); assert_eq!(ptr.count(), 0, "default pointer should have 0 tokens"); - ptr.push_back("foo".into()); + ptr.push_back("foo"); assert_eq!(ptr, "/foo", "pointer should equal \"/foo\" after push_back"); - ptr.push_back("bar".into()); + ptr.push_back("bar"); assert_eq!(ptr, "/foo/bar"); - ptr.push_back("/baz".into()); + ptr.push_back("/baz"); assert_eq!(ptr, "/foo/bar/~1baz"); let mut ptr = PointerBuf::from_tokens(["foo", "bar"]); @@ -1291,11 +1299,11 @@ mod tests { fn replace_token() { let mut ptr = PointerBuf::try_from("/test/token").unwrap(); - let res = ptr.replace_token(0, "new".into()); + let res = ptr.replace(0, "new"); assert!(res.is_ok()); assert_eq!(ptr, "/new/token"); - let res = ptr.replace_token(3, "invalid".into()); + let res = ptr.replace(3, "invalid"); assert!(res.is_err()); } @@ -1305,15 +1313,15 @@ mod tests { let mut ptr = PointerBuf::default(); assert_eq!(ptr, ""); assert_eq!(ptr.count(), 0); - ptr.push_front("bar".into()); + ptr.push_front("bar"); assert_eq!(ptr, "/bar"); assert_eq!(ptr.count(), 1); - ptr.push_front("foo".into()); + ptr.push_front("foo"); assert_eq!(ptr, "/foo/bar"); assert_eq!(ptr.count(), 2); - ptr.push_front("too".into()); + ptr.push_front("too"); assert_eq!(ptr, "/too/foo/bar"); assert_eq!(ptr.count(), 3); @@ -1330,7 +1338,7 @@ mod tests { #[test] fn display_replace_token_error() { - let err = ReplaceTokenError { index: 3, count: 2 }; + let err = ReplaceError { index: 3, count: 2 }; assert_eq!(format!("{err}"), "index 3 is out of bounds (2)"); } @@ -1354,7 +1362,7 @@ mod tests { { let mut ptr = PointerBuf::new(); assert_eq!(ptr.tokens().count(), 0); - ptr.push_back("".into()); + ptr.push_back(""); assert_eq!(ptr.tokens().count(), 1); ptr.pop_back(); assert_eq!(ptr.tokens().count(), 0); @@ -1362,9 +1370,9 @@ mod tests { { let mut ptr = PointerBuf::new(); let input = ["", "", "", "foo", "", "bar", "baz", ""]; - for (idx, s) in input.iter().enumerate() { + for (idx, &s) in input.iter().enumerate() { assert_eq!(ptr.tokens().count(), idx); - ptr.push_back((*s).into()); + ptr.push_back(s); } assert_eq!(ptr.tokens().count(), input.len()); for (idx, s) in input.iter().enumerate() { @@ -1520,34 +1528,34 @@ mod tests { #[test] fn replace_token_success() { let mut ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]); - assert!(ptr.replace_token(1, "qux".into()).is_ok()); + assert!(ptr.replace(1, "qux").is_ok()); assert_eq!(ptr, PointerBuf::from_tokens(["foo", "qux", "baz"])); - assert!(ptr.replace_token(0, "corge".into()).is_ok()); + assert!(ptr.replace(0, "corge").is_ok()); assert_eq!(ptr, PointerBuf::from_tokens(["corge", "qux", "baz"])); - assert!(ptr.replace_token(2, "quux".into()).is_ok()); + assert!(ptr.replace(2, "quux").is_ok()); assert_eq!(ptr, PointerBuf::from_tokens(["corge", "qux", "quux"])); } #[test] fn replace_token_out_of_bounds() { let mut ptr = PointerBuf::from_tokens(["foo", "bar"]); - assert!(ptr.replace_token(2, "baz".into()).is_err()); + assert!(ptr.replace(2, "baz").is_err()); assert_eq!(ptr, PointerBuf::from_tokens(["foo", "bar"])); // Ensure original pointer is unchanged } #[test] fn replace_token_with_empty_string() { let mut ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]); - assert!(ptr.replace_token(1, "".into()).is_ok()); + assert!(ptr.replace(1, "").is_ok()); assert_eq!(ptr, PointerBuf::from_tokens(["foo", "", "baz"])); } #[test] fn replace_token_in_empty_pointer() { let mut ptr = PointerBuf::default(); - assert!(ptr.replace_token(0, "foo".into()).is_err()); + assert!(ptr.replace(0, "foo").is_err()); assert_eq!(ptr, PointerBuf::default()); // Ensure the pointer remains empty } @@ -1555,9 +1563,9 @@ mod tests { fn pop_back_works_with_empty_strings() { { let mut ptr = PointerBuf::new(); - ptr.push_back("".into()); - ptr.push_back("".into()); - ptr.push_back("bar".into()); + ptr.push_back(""); + ptr.push_back(""); + ptr.push_back("bar"); assert_eq!(ptr.tokens().count(), 3); ptr.pop_back(); @@ -1571,7 +1579,7 @@ mod tests { { let mut ptr = PointerBuf::new(); assert_eq!(ptr.tokens().count(), 0); - ptr.push_back("".into()); + ptr.push_back(""); assert_eq!(ptr.tokens().count(), 1); ptr.pop_back(); assert_eq!(ptr.tokens().count(), 0); @@ -1579,9 +1587,9 @@ mod tests { { let mut ptr = PointerBuf::new(); let input = ["", "", "", "foo", "", "bar", "baz", ""]; - for (idx, s) in input.iter().enumerate() { + for (idx, &s) in input.iter().enumerate() { assert_eq!(ptr.tokens().count(), idx); - ptr.push_back((*s).into()); + ptr.push_back(s); } assert_eq!(ptr.tokens().count(), input.len()); for (idx, s) in input.iter().enumerate().rev() { diff --git a/src/token.rs b/src/token.rs index 5f89f10..9772ea6 100644 --- a/src/token.rs +++ b/src/token.rs @@ -250,44 +250,18 @@ impl<'a> Token<'a> { } } -#[cfg(feature = "serde")] -impl serde::Serialize for Token<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(self.decoded().as_ref()) - } -} - -#[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for Token<'de> { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s = <&'de str>::deserialize(deserializer)?; - Ok(Token::new(s)) - } -} - -impl From for Token<'static> { - fn from(v: usize) -> Self { - Token::from_encoded_unchecked(v.to_string()) - } -} - -impl From for Token<'static> { - fn from(v: u32) -> Self { - Token::from_encoded_unchecked(v.to_string()) - } -} - -impl From for Token<'static> { - fn from(v: u64) -> Self { - Token::from_encoded_unchecked(v.to_string()) - } +macro_rules! impl_from_num { + ($($ty:ty),*) => { + $( + impl From<$ty> for Token<'static> { + fn from(v: $ty) -> Self { + Token::from_encoded_unchecked(v.to_string()) + } + } + )* + }; } +impl_from_num!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); impl<'a> From<&'a str> for Token<'a> { fn from(value: &'a str) -> Self { @@ -427,15 +401,6 @@ mod tests { assert_eq!(Token::new("a/b").encoded(), "a~1b"); } - #[test] - fn serde() { - let token = Token::from_encoded("foo~0").unwrap(); - let json = serde_json::to_string(&token).unwrap(); - assert_eq!(json, "\"foo~\""); - let deserialized: Token = serde_json::from_str(&json).unwrap(); - assert_eq!(deserialized, token); - } - #[test] fn assign_error_display() { let err = AssignError::FailedToParseIndex {