├── .github └── workflows │ ├── release-plz.yml │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md └── src └── lib.rs /.github/workflows/release-plz.yml: -------------------------------------------------------------------------------- 1 | name: Release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | release-plz: 14 | name: Release-plz 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - name: Install Rust toolchain 22 | uses: dtolnay/rust-toolchain@stable 23 | - name: Run release-plz 24 | uses: MarcoIeni/release-plz-action@v0.5 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | RUSTFLAGS: -D warnings 12 | RUSTDOCFLAGS: -D warnings 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: taiki-e/install-action@cargo-hack 20 | - name: build 21 | run: cargo hack build --feature-powerset 22 | test: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: test 27 | run: cargo test --all-features 28 | fmt: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v3 32 | - name: fmt 33 | run: cargo fmt --check 34 | doc: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v3 38 | - name: doc 39 | run: cargo doc --all-features 40 | minimal: 41 | runs-on: ubuntu-latest 42 | name: ubuntu / stable / minimal-versions 43 | steps: 44 | - uses: actions/checkout@v3 45 | - name: Install stable 46 | uses: dtolnay/rust-toolchain@stable 47 | - name: Install nightly for -Zminimal-versions 48 | uses: dtolnay/rust-toolchain@nightly 49 | - name: rustup default stable 50 | run: rustup default stable 51 | - name: cargo update -Zminimal-versions 52 | run: cargo +nightly update -Zminimal-versions 53 | - name: cargo check 54 | run: cargo check --locked --all-features 55 | msrv: 56 | runs-on: ubuntu-latest 57 | # we use a matrix here just because env can't be used in job names 58 | # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability 59 | strategy: 60 | matrix: 61 | msrv: [1.56.0] 62 | name: ubuntu / ${{ matrix.msrv }} 63 | steps: 64 | - uses: actions/checkout@v3 65 | - name: Install ${{ matrix.msrv }} 66 | uses: dtolnay/rust-toolchain@master 67 | with: 68 | toolchain: ${{ matrix.msrv }} 69 | - name: cargo +${{ matrix.msrv }} check 70 | run: cargo check --all-features 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.1.4](https://github.com/eopb/serde_json_path_to_error/compare/v0.1.3...v0.1.4) - 2023-10-19 10 | 11 | ### Other 12 | - First release with a CHANGELOG (thanks to [github.com/MarcoIeni/release-plz](https://github.com/MarcoIeni/release-plz)) 13 | 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_json_path_to_error" 3 | version = "0.1.4" 4 | authors = ["Ethan Brierley "] 5 | license = "MIT/Apache-2.0" 6 | readme = "README.md" 7 | description = """ 8 | A drop in replacement for serde_json where detailed errors are the default 9 | """ 10 | keywords = ["serde", "json", "serialization"] 11 | documentation = "https://docs.rs/serde_json_path_to_error/" 12 | repository = "https://github.com/eopb/serde_json_path_to_error" 13 | edition = "2021" 14 | rust-version = "1.56" 15 | exclude = [".github"] 16 | 17 | 18 | [features] 19 | 20 | # We currently don't forward any features to serde_json or serde_path_to_error. 21 | # I'm happy to accept PRs that add support for that. 22 | 23 | [dependencies] 24 | serde = "1.0.188" 25 | serde_json = "1.0.107" 26 | serde_path_to_error = "0.1.14" 27 | 28 | 29 | [dev-dependencies] 30 | serde = { version = "1.0.188", features = ["derive"] } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serde_json_path_to_error 2 | 3 | [![License](https://img.shields.io/crates/l/serde_json_path_to_error.svg)](https://crates.io/crates/serde_json_path_to_error) 4 | [![Latest version](https://img.shields.io/crates/v/serde_json_path_to_error.svg)](https://crates.io/crates/serde_json_path_to_error) 5 | [![Latest Docs](https://docs.rs/serde_json_path_to_error/badge.svg)](https://docs.rs/serde_json_path_to_error/) 6 | [![downloads-badge](https://img.shields.io/crates/d/serde_json_path_to_error.svg)](https://crates.io/crates/serde_json_path_to_error) 7 | 8 | [API docs](https://docs.rs/serde_path_to_error/) 9 | 10 | A drop in replacement for [serde_json] with errors enriched by [serde_path_to_error]. 11 | 12 | This is usually a better default since it makes it easier to debug when serialization or deserialization fails. 13 | Paths are particularly helpful when your schema is large or when it's difficult to see the raw data that causes an error. 14 | 15 | This crate exposes the same items as [serde_json], just with different error types. 16 | For more detailed documentation see [serde_json]. 17 | 18 | ## Migrating from [serde_json] 19 | 20 | To enrich your errors simply replace your dependency on [serde_json] with one on serde_json_path_to_error. 21 | 22 | ```diff 23 | - serde_json = "1.0" 24 | + serde_json = { package = "serde_json_path_to_error", version = "0.1" } 25 | ``` 26 | 27 | Alternatively, you can add serde_json_path_to_error as a regular dependancy... 28 | 29 | ```text 30 | # cargo add serde_json_path_to_error 31 | ``` 32 | 33 | ..and rename the crate in your crate root to get the same API as [serde_json]. 34 | 35 | ```rust 36 | extern crate serde_json_path_to_error as serde_json; 37 | ``` 38 | 39 | In most cases, your project should continue to compile after migrating. 40 | Your errors will now be enriched with additional context showing the path to serialization and deserialization failures. 41 | 42 | ```rust 43 | // the rename trick shown above 44 | extern crate serde_json_path_to_error as serde_json; 45 | 46 | # use std::collections::BTreeMap as Map; 47 | # use serde::Deserialize; 48 | #[derive(Deserialize)] 49 | struct Package { 50 | name: String, 51 | dependencies: Map, 52 | } 53 | 54 | #[derive(Deserialize)] 55 | struct Dependency { 56 | version: String, 57 | } 58 | 59 | fn main() { 60 | let j = r#"{ 61 | "name": "demo", 62 | "dependencies": { 63 | "serde": { 64 | "version": 1 65 | } 66 | } 67 | }"#; 68 | 69 | 70 | // Uses the enriched version from [serde_json_path_to_error] but with the exact same API 71 | // you've come to expect from [serde_json] 72 | let result: Result = serde_json::from_str(j); 73 | 74 | match result { 75 | Ok(_) => panic!("expected a type error"), 76 | Err(err) => { 77 | // You get the error including the path as a default 78 | assert_eq!( 79 | err.to_string(), 80 | "dependencies.serde.version: invalid type: integer `1`, expected a string at line 5 column 28", 81 | ); 82 | // You can get just the path 83 | assert_eq!( 84 | err.path().to_string(), 85 | "dependencies.serde.version", 86 | ); 87 | // Or just the original serde_json error 88 | assert_eq!( 89 | err.into_inner().to_string(), 90 | "invalid type: integer `1`, expected a string at line 5 column 28", 91 | ); 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | ## Caveats 98 | 99 | There are still a small number of items that don't return enriched errors. 100 | I'd be interested in accepting PRs that wrap these items. 101 | 102 | - [serde_json::de::Deserializer] [#6](https://github.com/eopb/serde_json_path_to_error/issues/6) 103 | - [serde_json::de::StreamDeserializer] [#5](https://github.com/eopb/serde_json_path_to_error/issues/5) 104 | - [serde_json::ser::Serializer] [#4](https://github.com/eopb/serde_json_path_to_error/issues/4) 105 | - [serde_json::value::Serializer] [#3](https://github.com/eopb/serde_json_path_to_error/issues/3) 106 | 107 | 108 | [serde_json]: https://docs.rs/serde_json/latest/serde_json/ 109 | [serde_path_to_error]: https://docs.rs/serde_json/latest/serde_path_to_error/ -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | pub use serde_json::json; 4 | 5 | pub use de::{Deserializer, StreamDeserializer}; 6 | 7 | #[doc(inline)] 8 | pub use de::{from_reader, from_slice, from_str}; 9 | /// Deserialize JSON data to a Rust data structure. 10 | pub mod de { 11 | // See caveats in README.md 12 | pub use serde_json::de::{Deserializer, StreamDeserializer}; 13 | 14 | use crate::Result; 15 | 16 | use std::io; 17 | 18 | pub use serde_json::de::{IoRead, Read, SliceRead, StrRead}; 19 | 20 | use serde::{de::DeserializeOwned, Deserialize}; 21 | 22 | /// Deserialize an instance of type `T` from a string of JSON text. 23 | /// 24 | /// Equivalent to [serde_json::from_str] but with errors extended with [serde_path_to_error]. 25 | /// 26 | /// See [serde_json::from_str] for more documentation. 27 | pub fn from_str<'a, T>(s: &'a str) -> Result 28 | where 29 | T: Deserialize<'a>, 30 | { 31 | let jd = &mut serde_json::Deserializer::from_str(s); 32 | 33 | serde_path_to_error::deserialize(jd) 34 | } 35 | 36 | /// Deserialize an instance of type `T` from an I/O stream of JSON. 37 | /// 38 | /// Equivalent to [serde_json::from_reader] but with errors extended with [serde_path_to_error]. 39 | /// 40 | /// See [serde_json::from_reader] for more documentation. 41 | pub fn from_reader(rdr: R) -> Result 42 | where 43 | R: io::Read, 44 | T: DeserializeOwned, 45 | { 46 | let jd = &mut serde_json::Deserializer::from_reader(rdr); 47 | 48 | serde_path_to_error::deserialize(jd) 49 | } 50 | 51 | /// Deserialize an instance of type `T` from bytes of JSON text. 52 | /// 53 | /// Equivalent to [serde_json::from_slice] but with errors extended with [serde_path_to_error]. 54 | /// 55 | /// See [serde_json::from_slice] for more documentation. 56 | pub fn from_slice<'a, T>(v: &'a [u8]) -> Result 57 | where 58 | T: Deserialize<'a>, 59 | { 60 | let jd = &mut serde_json::Deserializer::from_slice(v); 61 | 62 | serde_path_to_error::deserialize(jd) 63 | } 64 | } 65 | 66 | #[doc(inline)] 67 | pub use error::{Error, Result}; 68 | /// When serializing or deserializing JSON goes wrong. 69 | pub mod error { 70 | pub use serde_json::error::Category; 71 | /// This type represents all possible errors that can occur when serializing or 72 | /// deserializing JSON data. 73 | pub type Error = serde_path_to_error::Error; 74 | /// Alias for a `Result` with the error type `serde_json_path_to_error::Error`. 75 | pub type Result = std::result::Result; 76 | } 77 | 78 | #[doc(inline)] 79 | pub use ser::{ 80 | to_string, to_string_pretty, to_vec, to_vec_pretty, to_writer, to_writer_pretty, Formatter, 81 | Serializer, 82 | }; 83 | /// Serialize a Rust data structure into JSON data. 84 | pub mod ser { 85 | use crate::Result; 86 | use serde::Serialize; 87 | 88 | static UTF8_ERROR: &str = 89 | "`serde_json` internally guarantees UTF8 and uses `String::from_utf8_unchecked`. \ 90 | If this error throws, `serde_json` must have broken this guarantee"; 91 | 92 | pub use serde_json::ser::{CharEscape, CompactFormatter, Formatter, PrettyFormatter}; 93 | 94 | // See caveats in README.md 95 | pub use serde_json::ser::Serializer; 96 | 97 | /// Serialize the given data structure as JSON into the I/O stream. 98 | /// 99 | /// Equivalent to [serde_json::to_writer] but with errors extended with [serde_path_to_error]. 100 | /// 101 | /// See [serde_json::to_writer] for more documentation. 102 | pub fn to_writer(writer: W, value: &T) -> Result<()> 103 | where 104 | W: std::io::Write, 105 | T: ?Sized + Serialize, 106 | { 107 | let mut ser = Serializer::new(writer); 108 | serde_path_to_error::serialize(&value, &mut ser) 109 | } 110 | 111 | /// Serialize the given data structure as a JSON byte vector. 112 | /// 113 | /// Equivalent to [serde_json::to_vec] but with errors extended with [serde_path_to_error]. 114 | /// 115 | /// See [serde_json::to_vec] for more documentation. 116 | pub fn to_vec(value: &T) -> Result> 117 | where 118 | T: ?Sized + Serialize, 119 | { 120 | let mut bytes = Vec::new(); 121 | 122 | to_writer(&mut bytes, value)?; 123 | 124 | Ok(bytes) 125 | } 126 | 127 | /// Serialize the given data structure as a String of JSON. 128 | /// 129 | /// Equivalent to [serde_json::to_string] but with errors extended with [serde_path_to_error]. 130 | /// 131 | /// See [serde_json::to_string] for more documentation. 132 | pub fn to_string(value: &T) -> Result 133 | where 134 | T: ?Sized + Serialize, 135 | { 136 | let vec = to_vec(value)?; 137 | 138 | Ok(String::from_utf8(vec).expect(UTF8_ERROR)) 139 | } 140 | 141 | /// Serialize the given data structure as pretty-printed JSON into the I/O 142 | /// stream. 143 | /// 144 | /// Equivalent to [serde_json::to_writer_pretty] but with errors extended with [serde_path_to_error]. 145 | /// 146 | /// See [serde_json::to_writer_pretty] for more documentation. 147 | pub fn to_writer_pretty(writer: W, value: &T) -> Result<()> 148 | where 149 | W: std::io::Write, 150 | T: ?Sized + Serialize, 151 | { 152 | let mut ser = Serializer::pretty(writer); 153 | serde_path_to_error::serialize(&value, &mut ser) 154 | } 155 | 156 | /// Serialize the given data structure as a pretty-printed JSON byte vector. 157 | /// 158 | /// Equivalent to [serde_json::to_vec_pretty] but with errors extended with [serde_path_to_error]. 159 | /// 160 | /// See [serde_json::to_vec_pretty] for more documentation. 161 | pub fn to_vec_pretty(value: &T) -> Result> 162 | where 163 | T: ?Sized + Serialize, 164 | { 165 | let mut bytes = Vec::new(); 166 | 167 | to_writer_pretty(&mut bytes, value)?; 168 | 169 | Ok(bytes) 170 | } 171 | 172 | /// Serialize the given data structure as a pretty-printed String of JSON. 173 | /// 174 | /// Equivalent to [serde_json::to_string_pretty] but with errors extended with [serde_path_to_error]. 175 | /// 176 | /// See [serde_json::to_string_pretty] for more documentation. 177 | pub fn to_string_pretty(value: &T) -> Result 178 | where 179 | T: ?Sized + Serialize, 180 | { 181 | let vec = to_vec_pretty(value)?; 182 | 183 | Ok(String::from_utf8(vec).expect(UTF8_ERROR)) 184 | } 185 | } 186 | 187 | #[doc(inline)] 188 | pub use map::Map; 189 | /// A map of String to serde_json::Value. 190 | /// 191 | /// See [serde_json::map] for more documentation. 192 | pub mod map { 193 | pub use serde_json::map::{ 194 | Entry, IntoIter, Iter, IterMut, Keys, Map, OccupiedEntry, VacantEntry, Values, ValuesMut, 195 | }; 196 | } 197 | 198 | #[doc(inline)] 199 | pub use value::{from_value, to_value, Number, Value}; 200 | /// The Value enum, a loosely typed way of representing any valid JSON value. 201 | /// 202 | /// See [serde_json::value] for more documentation. 203 | pub mod value { 204 | use serde::{de::DeserializeOwned, Serialize}; 205 | 206 | pub use serde_json::value::{Index, Map, Number, Value}; 207 | 208 | // See caveats in README.md 209 | pub use serde_json::value::Serializer; 210 | 211 | use crate::{Error, Result}; 212 | 213 | /// Convert a `T` into `serde_json::Value` which is an enum that can represent 214 | /// 215 | /// Equivalent to [serde_json::to_value] but with errors extended with [serde_path_to_error]. 216 | /// 217 | /// See [serde_json::to_value] for more documentation. 218 | pub fn to_value(value: T) -> Result 219 | where 220 | T: Serialize, 221 | { 222 | let mut track = serde_path_to_error::Track::new(); 223 | let ps = serde_path_to_error::Serializer::new(Serializer, &mut track); 224 | 225 | value.serialize(ps).map_err(|e| Error::new(track.path(), e)) 226 | } 227 | 228 | /// Interpret a `serde_json::Value` as an instance of type `T`. 229 | /// 230 | /// Equivalent to [serde_json::from_value] but with errors extended with [serde_path_to_error]. 231 | /// 232 | /// See [serde_json::from_value] for more documentation. 233 | pub fn from_value(value: Value) -> Result 234 | where 235 | T: DeserializeOwned, 236 | { 237 | serde_path_to_error::deserialize(value) 238 | } 239 | } 240 | --------------------------------------------------------------------------------