├── .github ├── FUNDING.yml └── workflows │ ├── audit.yml │ └── ci.yml ├── .envrc ├── .gitignore ├── fuzz ├── .gitignore ├── fuzz_targets │ └── fuzz_from_slice.rs └── Cargo.toml ├── deny.toml ├── src ├── libyaml │ ├── mod.rs │ ├── tag.rs │ ├── util.rs │ ├── cstr.rs │ ├── error.rs │ ├── parser.rs │ └── emitter.rs ├── path.rs ├── value │ ├── debug.rs │ ├── partial_eq.rs │ ├── from.rs │ ├── index.rs │ └── tagged.rs ├── loader.rs ├── lib.rs ├── error.rs ├── number.rs └── ser.rs ├── Cargo.toml ├── LICENSE-MIT ├── cliff.toml ├── flake.lock ├── Cargo.lock ├── README.md ├── tests ├── test_value.rs ├── test_error.rs ├── test_serde.rs └── test_de.rs ├── flake.nix └── LICENSE-APACHE /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: cafkafk 2 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | if has nix; then 2 | use flake . 3 | fi 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | result 3 | 4 | .direnv/* 5 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | artifacts/ 2 | corpus/ 3 | coverage/ 4 | target/ 5 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [licenses] 2 | allow = [ 3 | "MIT", 4 | "Apache-2.0" 5 | ] 6 | -------------------------------------------------------------------------------- /src/libyaml/mod.rs: -------------------------------------------------------------------------------- 1 | mod cstr; 2 | pub mod emitter; 3 | pub mod error; 4 | pub mod parser; 5 | pub mod tag; 6 | mod util; 7 | 8 | use self::error::Error; 9 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_from_slice.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | if data.len() <= 10240 { 7 | _ = serde_norway::from_slice::(data); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | push: 6 | paths: 7 | - '**/Cargo.toml' 8 | - '**/Cargo.lock' 9 | jobs: 10 | security_audit: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: taiki-e/install-action@cargo-deny 15 | - name: Scan for vulnerabilities 16 | run: cargo deny check advisories 17 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_norway-fuzz" 3 | version = "0.0.0" 4 | authors = ["David Tolnay "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | libfuzzer-sys = "0.4" 13 | serde_norway = { path = ".." } 14 | 15 | [[bin]] 16 | name = "fuzz_from_slice" 17 | path = "fuzz_targets/fuzz_from_slice.rs" 18 | test = false 19 | doc = false 20 | 21 | [workspace] 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_norway" 3 | version = "0.9.42" 4 | authors = ["Christina Sørensen ", "David Tolnay "] 5 | categories = ["encoding", "parser-implementations"] 6 | description = "YAML data format for Serde" 7 | edition = "2021" 8 | keywords = ["yaml", "serde", "serialization"] 9 | license = "MIT OR Apache-2.0" 10 | repository = "https://github.com/cafkafk/serde-yaml" 11 | rust-version = "1.71.1" 12 | 13 | [dependencies] 14 | indexmap = "2.2.1" 15 | itoa = "1.0" 16 | ryu = "1.0" 17 | serde = "1.0.203" 18 | unsafe-libyaml-norway = "0.2.13" 19 | 20 | [dev-dependencies] 21 | anyhow = "1.0.93" 22 | indoc = "2.0" 23 | serde_derive = "1.0.203" 24 | 25 | [lib] 26 | doc-scrape-examples = false 27 | 28 | [package.metadata.docs.rs] 29 | targets = ["x86_64-unknown-linux-gnu"] 30 | rustdoc-args = ["--generate-link-to-definition"] 31 | -------------------------------------------------------------------------------- /src/libyaml/tag.rs: -------------------------------------------------------------------------------- 1 | use crate::libyaml::cstr; 2 | use std::fmt::{self, Debug}; 3 | use std::ops::Deref; 4 | 5 | #[derive(Ord, PartialOrd, Eq, PartialEq)] 6 | pub(crate) struct Tag(pub(in crate::libyaml) Box<[u8]>); 7 | 8 | impl Tag { 9 | pub const NULL: &'static str = "tag:yaml.org,2002:null"; 10 | pub const BOOL: &'static str = "tag:yaml.org,2002:bool"; 11 | pub const INT: &'static str = "tag:yaml.org,2002:int"; 12 | pub const FLOAT: &'static str = "tag:yaml.org,2002:float"; 13 | } 14 | 15 | impl Tag { 16 | pub fn starts_with(&self, prefix: &str) -> bool { 17 | self.0.starts_with(prefix.as_bytes()) 18 | } 19 | } 20 | 21 | impl PartialEq for Tag { 22 | fn eq(&self, other: &str) -> bool { 23 | *self.0 == *other.as_bytes() 24 | } 25 | } 26 | 27 | impl Deref for Tag { 28 | type Target = [u8]; 29 | fn deref(&self) -> &Self::Target { 30 | &self.0 31 | } 32 | } 33 | 34 | impl Debug for Tag { 35 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 36 | cstr::debug_lossy(&self.0, formatter) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /src/path.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | 3 | /// Path to the current value in the input, like `dependencies.serde.typo1`. 4 | #[derive(Copy, Clone)] 5 | pub enum Path<'a> { 6 | Root, 7 | Seq { parent: &'a Path<'a>, index: usize }, 8 | Map { parent: &'a Path<'a>, key: &'a str }, 9 | Alias { parent: &'a Path<'a> }, 10 | Unknown { parent: &'a Path<'a> }, 11 | } 12 | 13 | impl<'a> Display for Path<'a> { 14 | fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { 15 | struct Parent<'a>(&'a Path<'a>); 16 | 17 | impl<'a> Display for Parent<'a> { 18 | fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { 19 | match self.0 { 20 | Path::Root => Ok(()), 21 | path => write!(formatter, "{}.", path), 22 | } 23 | } 24 | } 25 | 26 | match self { 27 | Path::Root => formatter.write_str("."), 28 | Path::Seq { parent, index } => write!(formatter, "{}[{}]", parent, index), 29 | Path::Map { parent, key } => write!(formatter, "{}{}", Parent(parent), key), 30 | Path::Alias { parent } => write!(formatter, "{}", parent), 31 | Path::Unknown { parent } => write!(formatter, "{}?", Parent(parent)), 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/libyaml/util.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::mem::{self, MaybeUninit}; 3 | use std::ops::Deref; 4 | use std::ptr::{addr_of, NonNull}; 5 | 6 | pub(crate) struct Owned { 7 | ptr: NonNull, 8 | marker: PhantomData>, 9 | } 10 | 11 | impl Owned { 12 | pub fn new_uninit() -> Owned, T> { 13 | // FIXME: use Box::new_uninit when stable 14 | let boxed = Box::new(MaybeUninit::::uninit()); 15 | Owned { 16 | ptr: unsafe { NonNull::new_unchecked(Box::into_raw(boxed)) }, 17 | marker: PhantomData, 18 | } 19 | } 20 | 21 | pub unsafe fn assume_init(definitely_init: Owned, T>) -> Owned { 22 | let ptr = definitely_init.ptr; 23 | mem::forget(definitely_init); 24 | Owned { 25 | ptr: ptr.cast(), 26 | marker: PhantomData, 27 | } 28 | } 29 | } 30 | 31 | #[repr(transparent)] 32 | pub(crate) struct InitPtr { 33 | pub ptr: *mut T, 34 | } 35 | 36 | impl Deref for Owned { 37 | type Target = InitPtr; 38 | 39 | fn deref(&self) -> &Self::Target { 40 | unsafe { &*addr_of!(self.ptr).cast::>() } 41 | } 42 | } 43 | 44 | impl Drop for Owned { 45 | fn drop(&mut self) { 46 | let _ = unsafe { Box::from_raw(self.ptr.as_ptr()) }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/value/debug.rs: -------------------------------------------------------------------------------- 1 | use crate::mapping::Mapping; 2 | use crate::value::{Number, Value}; 3 | use std::fmt::{self, Debug, Display}; 4 | 5 | impl Debug for Value { 6 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 7 | match self { 8 | Value::Null => formatter.write_str("Null"), 9 | Value::Bool(boolean) => write!(formatter, "Bool({})", boolean), 10 | Value::Number(number) => write!(formatter, "Number({})", number), 11 | Value::String(string) => write!(formatter, "String({:?})", string), 12 | Value::Sequence(sequence) => { 13 | formatter.write_str("Sequence ")?; 14 | formatter.debug_list().entries(sequence).finish() 15 | } 16 | Value::Mapping(mapping) => Debug::fmt(mapping, formatter), 17 | Value::Tagged(tagged) => Debug::fmt(tagged, formatter), 18 | } 19 | } 20 | } 21 | 22 | struct DisplayNumber<'a>(&'a Number); 23 | 24 | impl<'a> Debug for DisplayNumber<'a> { 25 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 26 | Display::fmt(self.0, formatter) 27 | } 28 | } 29 | 30 | impl Debug for Number { 31 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 32 | write!(formatter, "Number({})", self) 33 | } 34 | } 35 | 36 | impl Debug for Mapping { 37 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 38 | formatter.write_str("Mapping ")?; 39 | let mut debug = formatter.debug_map(); 40 | for (k, v) in self { 41 | let tmp; 42 | debug.entry( 43 | match k { 44 | Value::Bool(boolean) => boolean, 45 | Value::Number(number) => { 46 | tmp = DisplayNumber(number); 47 | &tmp 48 | } 49 | Value::String(string) => string, 50 | _ => k, 51 | }, 52 | v, 53 | ); 54 | } 55 | debug.finish() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/value/partial_eq.rs: -------------------------------------------------------------------------------- 1 | use crate::Value; 2 | 3 | impl PartialEq for Value { 4 | /// Compare `str` with YAML value 5 | /// 6 | /// # Examples 7 | /// 8 | /// ``` 9 | /// # use serde_norway::Value; 10 | /// assert!(Value::String("lorem".into()) == *"lorem"); 11 | /// ``` 12 | fn eq(&self, other: &str) -> bool { 13 | self.as_str().map_or(false, |s| s == other) 14 | } 15 | } 16 | 17 | impl<'a> PartialEq<&'a str> for Value { 18 | /// Compare `&str` with YAML value 19 | /// 20 | /// # Examples 21 | /// 22 | /// ``` 23 | /// # use serde_norway::Value; 24 | /// assert!(Value::String("lorem".into()) == "lorem"); 25 | /// ``` 26 | fn eq(&self, other: &&str) -> bool { 27 | self.as_str().map_or(false, |s| s == *other) 28 | } 29 | } 30 | 31 | impl PartialEq for Value { 32 | /// Compare YAML value with String 33 | /// 34 | /// # Examples 35 | /// 36 | /// ``` 37 | /// # use serde_norway::Value; 38 | /// assert!(Value::String("lorem".into()) == "lorem".to_string()); 39 | /// ``` 40 | fn eq(&self, other: &String) -> bool { 41 | self.as_str().map_or(false, |s| s == other) 42 | } 43 | } 44 | 45 | impl PartialEq for Value { 46 | /// Compare YAML value with bool 47 | /// 48 | /// # Examples 49 | /// 50 | /// ``` 51 | /// # use serde_norway::Value; 52 | /// assert!(Value::Bool(true) == true); 53 | /// ``` 54 | fn eq(&self, other: &bool) -> bool { 55 | self.as_bool().map_or(false, |b| b == *other) 56 | } 57 | } 58 | 59 | macro_rules! partialeq_numeric { 60 | ($([$($ty:ty)*], $conversion:ident, $base:ty)*) => { 61 | $($( 62 | impl PartialEq<$ty> for Value { 63 | fn eq(&self, other: &$ty) -> bool { 64 | self.$conversion().map_or(false, |i| i == (*other as $base)) 65 | } 66 | } 67 | 68 | impl<'a> PartialEq<$ty> for &'a Value { 69 | fn eq(&self, other: &$ty) -> bool { 70 | self.$conversion().map_or(false, |i| i == (*other as $base)) 71 | } 72 | } 73 | 74 | impl<'a> PartialEq<$ty> for &'a mut Value { 75 | fn eq(&self, other: &$ty) -> bool { 76 | self.$conversion().map_or(false, |i| i == (*other as $base)) 77 | } 78 | } 79 | )*)* 80 | } 81 | } 82 | 83 | partialeq_numeric! { 84 | [i8 i16 i32 i64 isize], as_i64, i64 85 | [u8 u16 u32 u64 usize], as_u64, u64 86 | [f32 f64], as_f64, f64 87 | } 88 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # git-cliff ~ default configuration file 2 | # https://git-cliff.org/docs/configuration 3 | # 4 | # Lines starting with "#" are comments. 5 | # Configuration options are organized into tables and keys. 6 | # See documentation for more information on available options. 7 | 8 | [changelog] 9 | # changelog header 10 | header = """ 11 | # Changelog\n 12 | """ 13 | # template for the changelog body 14 | # https://tera.netlify.app/docs 15 | body = """ 16 | {% if version %}\ 17 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 18 | {% else %}\ 19 | ## [unreleased] 20 | {% endif %}\ 21 | {% for group, commits in commits | group_by(attribute="group") %} 22 | ### {{ group | upper_first }} 23 | {% for commit in commits %} 24 | - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ 25 | {% endfor %} 26 | {% endfor %}\n 27 | """ 28 | # remove the leading and trailing whitespace from the template 29 | trim = true 30 | # changelog footer 31 | footer = """ 32 | 33 | """ 34 | 35 | [git] 36 | # parse the commits based on https://www.conventionalcommits.org 37 | conventional_commits = true 38 | # filter out the commits that are not conventional 39 | filter_unconventional = true 40 | # process each line of a commit as an individual commit 41 | split_commits = false 42 | # regex for preprocessing the commit messages 43 | commit_preprocessors = [ 44 | # { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, # replace issue numbers 45 | ] 46 | # regex for parsing and grouping commits 47 | commit_parsers = [ 48 | { message = "^feat", group = "Features" }, 49 | { message = "^fix", group = "Bug Fixes" }, 50 | { message = "^doc", group = "Documentation" }, 51 | { message = "^perf", group = "Performance" }, 52 | { message = "^refactor", group = "Refactor" }, 53 | { message = "^style", group = "Styling" }, 54 | { message = "^test", group = "Testing" }, 55 | { message = "^chore\\(release\\): prepare for", skip = true }, 56 | { message = "^chore", group = "Miscellaneous Tasks" }, 57 | { body = ".*security", group = "Security" }, 58 | ] 59 | # protect breaking changes from being skipped due to matching a skipping commit_parser 60 | protect_breaking_commits = false 61 | # filter out the commits that are not matched by commit parsers 62 | filter_commits = false 63 | # glob pattern for matching git tags 64 | tag_pattern = "v[0-9]*" 65 | # regex for skipping tags 66 | skip_tags = "v0.1.0-beta.1" 67 | # regex for ignoring tags 68 | ignore_tags = "" 69 | # sort the tags topologically 70 | topo_order = false 71 | # sort the commits inside sections by oldest/newest order 72 | sort_commits = "oldest" 73 | # limit the number of commits included in the changelog. 74 | # limit_commits = 42 75 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "advisory-db": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1733749954, 7 | "narHash": "sha256-2Ug80Uf/oUujxgh02Iy5vTG0V+Ab9+YUHuRLRY0ayiY=", 8 | "owner": "rustsec", 9 | "repo": "advisory-db", 10 | "rev": "ec9ce28714bb38d77a2223e7266df705500a7f11", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "rustsec", 15 | "repo": "advisory-db", 16 | "type": "github" 17 | } 18 | }, 19 | "crane": { 20 | "locked": { 21 | "lastModified": 1734744351, 22 | "narHash": "sha256-fN9npuZHHHzDVr1wuKoh/TheHkerDaLB9l4vj/48Exg=", 23 | "owner": "ipetkov", 24 | "repo": "crane", 25 | "rev": "a83a48a62640517588c3d137c948ed034706363c", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "ipetkov", 30 | "repo": "crane", 31 | "type": "github" 32 | } 33 | }, 34 | "fenix": { 35 | "inputs": { 36 | "nixpkgs": [ 37 | "nixpkgs" 38 | ], 39 | "rust-analyzer-src": [] 40 | }, 41 | "locked": { 42 | "lastModified": 1734676450, 43 | "narHash": "sha256-iwcxhTVe4h5TqW0HsNiOQP27eMBmbBshF+q2UjEy5aU=", 44 | "owner": "nix-community", 45 | "repo": "fenix", 46 | "rev": "46e19fa0eb3260b2c3ee5b2cf89e73343c1296ab", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "owner": "nix-community", 51 | "repo": "fenix", 52 | "type": "github" 53 | } 54 | }, 55 | "flake-utils": { 56 | "inputs": { 57 | "systems": "systems" 58 | }, 59 | "locked": { 60 | "lastModified": 1731533236, 61 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 62 | "owner": "numtide", 63 | "repo": "flake-utils", 64 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 65 | "type": "github" 66 | }, 67 | "original": { 68 | "owner": "numtide", 69 | "repo": "flake-utils", 70 | "type": "github" 71 | } 72 | }, 73 | "nixpkgs": { 74 | "locked": { 75 | "lastModified": 1734435836, 76 | "narHash": "sha256-kMBQ5PRiFLagltK0sH+08aiNt3zGERC2297iB6vrvlU=", 77 | "owner": "NixOS", 78 | "repo": "nixpkgs", 79 | "rev": "4989a246d7a390a859852baddb1013f825435cee", 80 | "type": "github" 81 | }, 82 | "original": { 83 | "owner": "NixOS", 84 | "ref": "nixpkgs-unstable", 85 | "repo": "nixpkgs", 86 | "type": "github" 87 | } 88 | }, 89 | "root": { 90 | "inputs": { 91 | "advisory-db": "advisory-db", 92 | "crane": "crane", 93 | "fenix": "fenix", 94 | "flake-utils": "flake-utils", 95 | "nixpkgs": "nixpkgs" 96 | } 97 | }, 98 | "systems": { 99 | "locked": { 100 | "lastModified": 1681028828, 101 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 102 | "owner": "nix-systems", 103 | "repo": "default", 104 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 105 | "type": "github" 106 | }, 107 | "original": { 108 | "owner": "nix-systems", 109 | "repo": "default", 110 | "type": "github" 111 | } 112 | } 113 | }, 114 | "root": "root", 115 | "version": 7 116 | } 117 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.94" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" 10 | 11 | [[package]] 12 | name = "equivalent" 13 | version = "1.0.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 16 | 17 | [[package]] 18 | name = "hashbrown" 19 | version = "0.15.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 22 | 23 | [[package]] 24 | name = "indexmap" 25 | version = "2.7.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 28 | dependencies = [ 29 | "equivalent", 30 | "hashbrown", 31 | ] 32 | 33 | [[package]] 34 | name = "indoc" 35 | version = "2.0.5" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 38 | 39 | [[package]] 40 | name = "itoa" 41 | version = "1.0.14" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 44 | 45 | [[package]] 46 | name = "proc-macro2" 47 | version = "1.0.92" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 50 | dependencies = [ 51 | "unicode-ident", 52 | ] 53 | 54 | [[package]] 55 | name = "quote" 56 | version = "1.0.37" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 59 | dependencies = [ 60 | "proc-macro2", 61 | ] 62 | 63 | [[package]] 64 | name = "ryu" 65 | version = "1.0.18" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 68 | 69 | [[package]] 70 | name = "serde" 71 | version = "1.0.216" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" 74 | dependencies = [ 75 | "serde_derive", 76 | ] 77 | 78 | [[package]] 79 | name = "serde_derive" 80 | version = "1.0.216" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" 83 | dependencies = [ 84 | "proc-macro2", 85 | "quote", 86 | "syn", 87 | ] 88 | 89 | [[package]] 90 | name = "serde_norway" 91 | version = "0.9.42" 92 | dependencies = [ 93 | "anyhow", 94 | "indexmap", 95 | "indoc", 96 | "itoa", 97 | "ryu", 98 | "serde", 99 | "serde_derive", 100 | "unsafe-libyaml-norway", 101 | ] 102 | 103 | [[package]] 104 | name = "syn" 105 | version = "2.0.90" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 108 | dependencies = [ 109 | "proc-macro2", 110 | "quote", 111 | "unicode-ident", 112 | ] 113 | 114 | [[package]] 115 | name = "unicode-ident" 116 | version = "1.0.14" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 119 | 120 | [[package]] 121 | name = "unsafe-libyaml-norway" 122 | version = "0.2.15" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "b39abd59bf32521c7f2301b52d05a6a2c975b6003521cbd0c6dc1582f0a22104" 125 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: [cron: "40 1 * * *"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | pre_ci: 17 | needs: conventional 18 | uses: dtolnay/.github/.github/workflows/pre_ci.yml@master 19 | 20 | conventional: 21 | name: Conventional Commits 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - uses: webiny/action-conventional-commits@v1.3.0 27 | 28 | security_audit: 29 | needs: pre_ci 30 | if: needs.pre_ci.outputs.continue 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: taiki-e/install-action@cargo-deny 35 | - name: Scan for vulnerabilities 36 | run: cargo deny check advisories 37 | 38 | test: 39 | name: Rust ${{matrix.rust}} 40 | needs: pre_ci 41 | if: needs.pre_ci.outputs.continue 42 | runs-on: ubuntu-latest 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | rust: [nightly, beta, stable, 1.71.1] 47 | timeout-minutes: 45 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: dtolnay/rust-toolchain@master 51 | with: 52 | toolchain: ${{matrix.rust}} 53 | - name: Enable type layout randomization 54 | run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV 55 | if: matrix.rust == 'nightly' 56 | - run: cargo build 57 | - run: cargo test 58 | 59 | doc: 60 | name: Documentation 61 | needs: pre_ci 62 | if: needs.pre_ci.outputs.continue 63 | runs-on: ubuntu-latest 64 | timeout-minutes: 45 65 | env: 66 | RUSTDOCFLAGS: -Dwarnings 67 | steps: 68 | - uses: actions/checkout@v4 69 | - uses: dtolnay/rust-toolchain@nightly 70 | - uses: dtolnay/install@cargo-docs-rs 71 | - run: cargo docs-rs 72 | 73 | clippy: 74 | name: Clippy 75 | runs-on: ubuntu-latest 76 | if: github.event_name != 'pull_request' 77 | needs: conventional 78 | timeout-minutes: 45 79 | steps: 80 | - name: Checkout repository 81 | uses: actions/checkout@v4 82 | - name: Install Nix 83 | uses: DeterminateSystems/nix-installer-action@main 84 | - name: Setup Nix cache 85 | uses: DeterminateSystems/magic-nix-cache-action@main 86 | - run: nix build ./#checks.x86_64-linux.serde-yaml-clippy -L 87 | 88 | miri: 89 | name: Miri 90 | needs: pre_ci 91 | if: needs.pre_ci.outputs.continue 92 | runs-on: ubuntu-latest 93 | timeout-minutes: 45 94 | steps: 95 | - uses: actions/checkout@v4 96 | - uses: dtolnay/rust-toolchain@miri 97 | - run: cargo miri setup 98 | - run: cargo miri test 99 | env: 100 | MIRIFLAGS: -Zmiri-strict-provenance 101 | 102 | minimal: 103 | name: Minimal versions 104 | needs: pre_ci 105 | if: needs.pre_ci.outputs.continue 106 | runs-on: ubuntu-latest 107 | timeout-minutes: 45 108 | steps: 109 | - uses: actions/checkout@v4 110 | - uses: dtolnay/rust-toolchain@nightly 111 | - run: cargo generate-lockfile -Z minimal-versions 112 | - run: cargo check --locked 113 | 114 | fuzz: 115 | name: Fuzz 116 | needs: pre_ci 117 | if: needs.pre_ci.outputs.continue 118 | runs-on: ubuntu-latest 119 | timeout-minutes: 45 120 | steps: 121 | - uses: actions/checkout@v4 122 | - uses: dtolnay/rust-toolchain@nightly 123 | - uses: dtolnay/install@cargo-fuzz 124 | - run: cargo fuzz check 125 | 126 | outdated: 127 | name: Outdated 128 | runs-on: ubuntu-latest 129 | if: github.event_name != 'pull_request' 130 | needs: conventional 131 | timeout-minutes: 45 132 | steps: 133 | - uses: actions/checkout@v4 134 | - uses: dtolnay/install@cargo-outdated 135 | - run: cargo outdated --workspace --exit-code 1 136 | - run: cargo outdated --manifest-path fuzz/Cargo.toml --exit-code 1 137 | -------------------------------------------------------------------------------- /src/libyaml/cstr.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Debug, Display, Write as _}; 2 | use std::marker::PhantomData; 3 | use std::ptr::NonNull; 4 | use std::slice; 5 | use std::str; 6 | 7 | #[derive(Copy, Clone)] 8 | pub(crate) struct CStr<'a> { 9 | ptr: NonNull, 10 | marker: PhantomData<&'a [u8]>, 11 | } 12 | 13 | unsafe impl<'a> Send for CStr<'a> {} 14 | unsafe impl<'a> Sync for CStr<'a> {} 15 | 16 | impl<'a> CStr<'a> { 17 | pub fn from_bytes_with_nul(bytes: &'static [u8]) -> Self { 18 | assert_eq!(bytes.last(), Some(&b'\0')); 19 | let ptr = NonNull::from(bytes).cast(); 20 | unsafe { Self::from_ptr(ptr) } 21 | } 22 | 23 | pub unsafe fn from_ptr(ptr: NonNull) -> Self { 24 | CStr { 25 | ptr: ptr.cast(), 26 | marker: PhantomData, 27 | } 28 | } 29 | 30 | pub fn len(self) -> usize { 31 | let start = self.ptr.as_ptr(); 32 | let mut end = start; 33 | unsafe { 34 | while *end != 0 { 35 | end = end.add(1); 36 | } 37 | end.offset_from(start) as usize 38 | } 39 | } 40 | 41 | pub fn to_bytes(self) -> &'a [u8] { 42 | let len = self.len(); 43 | unsafe { slice::from_raw_parts(self.ptr.as_ptr(), len) } 44 | } 45 | } 46 | 47 | impl<'a> Display for CStr<'a> { 48 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 49 | let ptr = self.ptr.as_ptr(); 50 | let len = self.len(); 51 | let bytes = unsafe { slice::from_raw_parts(ptr, len) }; 52 | display_lossy(bytes, formatter) 53 | } 54 | } 55 | 56 | impl<'a> Debug for CStr<'a> { 57 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 58 | let ptr = self.ptr.as_ptr(); 59 | let len = self.len(); 60 | let bytes = unsafe { slice::from_raw_parts(ptr, len) }; 61 | debug_lossy(bytes, formatter) 62 | } 63 | } 64 | 65 | fn display_lossy(mut bytes: &[u8], formatter: &mut fmt::Formatter) -> fmt::Result { 66 | loop { 67 | match str::from_utf8(bytes) { 68 | Ok(valid) => return formatter.write_str(valid), 69 | Err(utf8_error) => { 70 | let valid_up_to = utf8_error.valid_up_to(); 71 | let valid = unsafe { str::from_utf8_unchecked(&bytes[..valid_up_to]) }; 72 | formatter.write_str(valid)?; 73 | formatter.write_char(char::REPLACEMENT_CHARACTER)?; 74 | if let Some(error_len) = utf8_error.error_len() { 75 | bytes = &bytes[valid_up_to + error_len..]; 76 | } else { 77 | return Ok(()); 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | pub(crate) fn debug_lossy(mut bytes: &[u8], formatter: &mut fmt::Formatter) -> fmt::Result { 85 | formatter.write_char('"')?; 86 | 87 | while !bytes.is_empty() { 88 | let from_utf8_result = str::from_utf8(bytes); 89 | let valid = match from_utf8_result { 90 | Ok(valid) => valid, 91 | Err(utf8_error) => { 92 | let valid_up_to = utf8_error.valid_up_to(); 93 | unsafe { str::from_utf8_unchecked(&bytes[..valid_up_to]) } 94 | } 95 | }; 96 | 97 | let mut written = 0; 98 | for (i, ch) in valid.char_indices() { 99 | let esc = ch.escape_debug(); 100 | if esc.len() != 1 && ch != '\'' { 101 | formatter.write_str(&valid[written..i])?; 102 | for ch in esc { 103 | formatter.write_char(ch)?; 104 | } 105 | written = i + ch.len_utf8(); 106 | } 107 | } 108 | formatter.write_str(&valid[written..])?; 109 | 110 | match from_utf8_result { 111 | Ok(_valid) => break, 112 | Err(utf8_error) => { 113 | let end_of_broken = if let Some(error_len) = utf8_error.error_len() { 114 | valid.len() + error_len 115 | } else { 116 | bytes.len() 117 | }; 118 | for b in &bytes[valid.len()..end_of_broken] { 119 | write!(formatter, "\\x{:02x}", b)?; 120 | } 121 | bytes = &bytes[end_of_broken..]; 122 | } 123 | } 124 | } 125 | 126 | formatter.write_char('"') 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | Serde Norway, a Serde YAML fork :) 4 | ========== 5 | 6 | ![Flag_of_Norway svg](https://github.com/cafkafk/serde-norway/assets/89321978/ff00f477-7ae4-4665-ad66-cb0c3da88e47) 7 | 8 | 9 | A hard-fork of Serde YAML. 10 | 11 | Rust library for using the [Serde] serialization framework with data in [YAML] 12 | file format. 13 | 14 | [Serde]: https://github.com/serde-rs/serde 15 | [YAML]: https://yaml.org/ 16 | 17 |
18 | 19 | ## Dependency 20 | 21 | ```toml 22 | [dependencies] 23 | serde = "1.0" 24 | serde_norway = "0.9" 25 | ``` 26 | 27 | Release notes are available under [GitHub releases]. 28 | 29 | [GitHub releases]: https://github.com/dtolnay/serde-yaml/releases 30 | 31 | ## Using Serde YAML 32 | 33 | [API documentation is available in rustdoc form][docs.rs] but the general idea 34 | is: 35 | 36 | [docs.rs]: https://docs.rs/serde_norway 37 | 38 | ```rust 39 | use std::collections::BTreeMap; 40 | 41 | fn main() -> Result<(), serde_norway::Error> { 42 | // You have some type. 43 | let mut map = BTreeMap::new(); 44 | map.insert("x".to_string(), 1.0); 45 | map.insert("y".to_string(), 2.0); 46 | 47 | // Serialize it to a YAML string. 48 | let yaml = serde_norway::to_string(&map)?; 49 | assert_eq!(yaml, "x: 1.0\ny: 2.0\n"); 50 | 51 | // Deserialize it back to a Rust type. 52 | let deserialized_map: BTreeMap = serde_norway::from_str(&yaml)?; 53 | assert_eq!(map, deserialized_map); 54 | Ok(()) 55 | } 56 | ``` 57 | 58 | It can also be used with Serde's derive macros to handle structs and enums 59 | defined in your program. 60 | 61 | ```toml 62 | [dependencies] 63 | serde = { version = "1.0", features = ["derive"] } 64 | serde_norway = "0.9" 65 | ``` 66 | 67 | Structs serialize in the obvious way: 68 | 69 | ```rust 70 | use serde::{Serialize, Deserialize}; 71 | 72 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 73 | struct Point { 74 | x: f64, 75 | y: f64, 76 | } 77 | 78 | fn main() -> Result<(), serde_norway::Error> { 79 | let point = Point { x: 1.0, y: 2.0 }; 80 | 81 | let yaml = serde_norway::to_string(&point)?; 82 | assert_eq!(yaml, "x: 1.0\ny: 2.0\n"); 83 | 84 | let deserialized_point: Point = serde_norway::from_str(&yaml)?; 85 | assert_eq!(point, deserialized_point); 86 | Ok(()) 87 | } 88 | ``` 89 | 90 | Enums serialize using YAML's `!tag` syntax to identify the variant name. 91 | 92 | ```rust 93 | use serde::{Serialize, Deserialize}; 94 | 95 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 96 | enum Enum { 97 | Unit, 98 | Newtype(usize), 99 | Tuple(usize, usize, usize), 100 | Struct { x: f64, y: f64 }, 101 | } 102 | 103 | fn main() -> Result<(), serde_norway::Error> { 104 | let yaml = " 105 | - !Newtype 1 106 | - !Tuple [0, 0, 0] 107 | - !Struct {x: 1.0, y: 2.0} 108 | "; 109 | let values: Vec = serde_norway::from_str(yaml).unwrap(); 110 | assert_eq!(values[0], Enum::Newtype(1)); 111 | assert_eq!(values[1], Enum::Tuple(0, 0, 0)); 112 | assert_eq!(values[2], Enum::Struct { x: 1.0, y: 2.0 }); 113 | 114 | // The last two in YAML's block style instead: 115 | let yaml = " 116 | - !Tuple 117 | - 0 118 | - 0 119 | - 0 120 | - !Struct 121 | x: 1.0 122 | y: 2.0 123 | "; 124 | let values: Vec = serde_norway::from_str(yaml).unwrap(); 125 | assert_eq!(values[0], Enum::Tuple(0, 0, 0)); 126 | assert_eq!(values[1], Enum::Struct { x: 1.0, y: 2.0 }); 127 | 128 | // Variants with no data can be written using !Tag or just the string name. 129 | let yaml = " 130 | - Unit # serialization produces this one 131 | - !Unit 132 | "; 133 | let values: Vec = serde_norway::from_str(yaml).unwrap(); 134 | assert_eq!(values[0], Enum::Unit); 135 | assert_eq!(values[1], Enum::Unit); 136 | 137 | Ok(()) 138 | } 139 | ``` 140 | 141 |
142 | 143 | #### License 144 | 145 | 146 | Licensed under either of Apache License, Version 147 | 2.0 or MIT license at your option. 148 | 149 | 150 |
151 | 152 | 153 | Unless you explicitly state otherwise, any contribution intentionally submitted 154 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 155 | be dual licensed as above, without any additional terms or conditions. 156 | 157 | -------------------------------------------------------------------------------- /tests/test_value.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::derive_partial_eq_without_eq, 3 | clippy::eq_op, 4 | clippy::uninlined_format_args 5 | )] 6 | 7 | use indoc::indoc; 8 | use serde::de::IntoDeserializer; 9 | use serde::Deserialize; 10 | use serde_derive::{Deserialize, Serialize}; 11 | use serde_norway::{Number, Value}; 12 | 13 | #[test] 14 | fn test_nan() { 15 | let pos_nan = serde_norway::from_str::(".nan").unwrap(); 16 | assert!(pos_nan.is_f64()); 17 | assert_eq!(pos_nan, pos_nan); 18 | 19 | let neg_fake_nan = serde_norway::from_str::("-.nan").unwrap(); 20 | assert!(neg_fake_nan.is_string()); 21 | 22 | let significand_mask = 0xF_FFFF_FFFF_FFFF; 23 | let bits = (f64::NAN.copysign(1.0).to_bits() ^ significand_mask) | 1; 24 | let different_pos_nan = Value::Number(Number::from(f64::from_bits(bits))); 25 | assert_eq!(pos_nan, different_pos_nan); 26 | } 27 | 28 | #[test] 29 | fn test_digits() { 30 | let num_string = serde_norway::from_str::("01").unwrap(); 31 | assert!(num_string.is_string()); 32 | } 33 | 34 | #[test] 35 | fn test_into_deserializer() { 36 | #[derive(Debug, Deserialize, PartialEq)] 37 | struct Test { 38 | first: String, 39 | second: u32, 40 | } 41 | 42 | let value = serde_norway::from_str::("xyz").unwrap(); 43 | let s = String::deserialize(value.into_deserializer()).unwrap(); 44 | assert_eq!(s, "xyz"); 45 | 46 | let value = serde_norway::from_str::("- first\n- second\n- third").unwrap(); 47 | let arr = Vec::::deserialize(value.into_deserializer()).unwrap(); 48 | assert_eq!(arr, &["first", "second", "third"]); 49 | 50 | let value = serde_norway::from_str::("first: abc\nsecond: 99").unwrap(); 51 | let test = Test::deserialize(value.into_deserializer()).unwrap(); 52 | assert_eq!( 53 | test, 54 | Test { 55 | first: "abc".to_string(), 56 | second: 99 57 | } 58 | ); 59 | } 60 | 61 | #[test] 62 | fn test_merge() { 63 | // From https://yaml.org/type/merge.html. 64 | let yaml = indoc! {" 65 | --- 66 | - &CENTER { x: 1, y: 2 } 67 | - &LEFT { x: 0, y: 2 } 68 | - &BIG { r: 10 } 69 | - &SMALL { r: 1 } 70 | 71 | # All the following maps are equal: 72 | 73 | - # Explicit keys 74 | x: 1 75 | y: 2 76 | r: 10 77 | label: center/big 78 | 79 | - # Merge one map 80 | << : *CENTER 81 | r: 10 82 | label: center/big 83 | 84 | - # Merge multiple maps 85 | << : [ *CENTER, *BIG ] 86 | label: center/big 87 | 88 | - # Override 89 | << : [ *BIG, *LEFT, *SMALL ] 90 | x: 1 91 | label: center/big 92 | "}; 93 | 94 | let mut value: Value = serde_norway::from_str(yaml).unwrap(); 95 | value.apply_merge().unwrap(); 96 | for i in 5..=7 { 97 | assert_eq!(value[4], value[i]); 98 | } 99 | } 100 | 101 | #[test] 102 | fn test_debug() { 103 | let yaml = indoc! {" 104 | 'Null': ~ 105 | Bool: true 106 | Number: 1 107 | String: ... 108 | Sequence: 109 | - true 110 | EmptySequence: [] 111 | EmptyMapping: {} 112 | Tagged: !tag true 113 | "}; 114 | 115 | let value: Value = serde_norway::from_str(yaml).unwrap(); 116 | let debug = format!("{:#?}", value); 117 | 118 | let expected = indoc! {r#" 119 | Mapping { 120 | "Null": Null, 121 | "Bool": Bool(true), 122 | "Number": Number(1), 123 | "String": String("..."), 124 | "Sequence": Sequence [ 125 | Bool(true), 126 | ], 127 | "EmptySequence": Sequence [], 128 | "EmptyMapping": Mapping {}, 129 | "Tagged": TaggedValue { 130 | tag: !tag, 131 | value: Bool(true), 132 | }, 133 | }"# 134 | }; 135 | 136 | assert_eq!(debug, expected); 137 | } 138 | 139 | #[test] 140 | fn test_tagged() { 141 | #[derive(Serialize)] 142 | enum Enum { 143 | Variant(usize), 144 | } 145 | 146 | let value = serde_norway::to_value(Enum::Variant(0)).unwrap(); 147 | 148 | let deserialized: serde_norway::Value = serde_norway::from_value(value.clone()).unwrap(); 149 | assert_eq!(value, deserialized); 150 | 151 | let serialized = serde_norway::to_value(&value).unwrap(); 152 | assert_eq!(value, serialized); 153 | } 154 | -------------------------------------------------------------------------------- /src/value/from.rs: -------------------------------------------------------------------------------- 1 | use crate::{Mapping, Value}; 2 | 3 | // Implement a bunch of conversion to make it easier to create YAML values 4 | // on the fly. 5 | 6 | macro_rules! from_number { 7 | ($($ty:ident)*) => { 8 | $( 9 | impl From<$ty> for Value { 10 | fn from(n: $ty) -> Self { 11 | Value::Number(n.into()) 12 | } 13 | } 14 | )* 15 | }; 16 | } 17 | 18 | from_number! { 19 | i8 i16 i32 i64 isize 20 | u8 u16 u32 u64 usize 21 | f32 f64 22 | } 23 | 24 | impl From for Value { 25 | /// Convert boolean to `Value` 26 | /// 27 | /// # Examples 28 | /// 29 | /// ``` 30 | /// use serde_norway::Value; 31 | /// 32 | /// let b = false; 33 | /// let x: Value = b.into(); 34 | /// ``` 35 | fn from(f: bool) -> Self { 36 | Value::Bool(f) 37 | } 38 | } 39 | 40 | impl From for Value { 41 | /// Convert `String` to `Value` 42 | /// 43 | /// # Examples 44 | /// 45 | /// ``` 46 | /// use serde_norway::Value; 47 | /// 48 | /// let s: String = "lorem".to_string(); 49 | /// let x: Value = s.into(); 50 | /// ``` 51 | fn from(f: String) -> Self { 52 | Value::String(f) 53 | } 54 | } 55 | 56 | impl<'a> From<&'a str> for Value { 57 | /// Convert string slice to `Value` 58 | /// 59 | /// # Examples 60 | /// 61 | /// ``` 62 | /// use serde_norway::Value; 63 | /// 64 | /// let s: &str = "lorem"; 65 | /// let x: Value = s.into(); 66 | /// ``` 67 | fn from(f: &str) -> Self { 68 | Value::String(f.to_string()) 69 | } 70 | } 71 | 72 | use std::borrow::Cow; 73 | 74 | impl<'a> From> for Value { 75 | /// Convert copy-on-write string to `Value` 76 | /// 77 | /// # Examples 78 | /// 79 | /// ``` 80 | /// use serde_norway::Value; 81 | /// use std::borrow::Cow; 82 | /// 83 | /// let s: Cow = Cow::Borrowed("lorem"); 84 | /// let x: Value = s.into(); 85 | /// ``` 86 | /// 87 | /// ``` 88 | /// use serde_norway::Value; 89 | /// use std::borrow::Cow; 90 | /// 91 | /// let s: Cow = Cow::Owned("lorem".to_string()); 92 | /// let x: Value = s.into(); 93 | /// ``` 94 | fn from(f: Cow<'a, str>) -> Self { 95 | Value::String(f.to_string()) 96 | } 97 | } 98 | 99 | impl From for Value { 100 | /// Convert map (with string keys) to `Value` 101 | /// 102 | /// # Examples 103 | /// 104 | /// ``` 105 | /// use serde_norway::{Mapping, Value}; 106 | /// 107 | /// let mut m = Mapping::new(); 108 | /// m.insert("Lorem".into(), "ipsum".into()); 109 | /// let x: Value = m.into(); 110 | /// ``` 111 | fn from(f: Mapping) -> Self { 112 | Value::Mapping(f) 113 | } 114 | } 115 | 116 | impl> From> for Value { 117 | /// Convert a `Vec` to `Value` 118 | /// 119 | /// # Examples 120 | /// 121 | /// ``` 122 | /// use serde_norway::Value; 123 | /// 124 | /// let v = vec!["lorem", "ipsum", "dolor"]; 125 | /// let x: Value = v.into(); 126 | /// ``` 127 | fn from(f: Vec) -> Self { 128 | Value::Sequence(f.into_iter().map(Into::into).collect()) 129 | } 130 | } 131 | 132 | impl<'a, T: Clone + Into> From<&'a [T]> for Value { 133 | /// Convert a slice to `Value` 134 | /// 135 | /// # Examples 136 | /// 137 | /// ``` 138 | /// use serde_norway::Value; 139 | /// 140 | /// let v: &[&str] = &["lorem", "ipsum", "dolor"]; 141 | /// let x: Value = v.into(); 142 | /// ``` 143 | fn from(f: &'a [T]) -> Self { 144 | Value::Sequence(f.iter().cloned().map(Into::into).collect()) 145 | } 146 | } 147 | 148 | impl> FromIterator for Value { 149 | /// Convert an iteratable type to a YAML sequence 150 | /// 151 | /// # Examples 152 | /// 153 | /// ``` 154 | /// use serde_norway::Value; 155 | /// 156 | /// let v = std::iter::repeat(42).take(5); 157 | /// let x: Value = v.collect(); 158 | /// ``` 159 | /// 160 | /// ``` 161 | /// use serde_norway::Value; 162 | /// 163 | /// let v: Vec<_> = vec!["lorem", "ipsum", "dolor"]; 164 | /// let x: Value = v.into_iter().collect(); 165 | /// ``` 166 | /// 167 | /// ``` 168 | /// use std::iter::FromIterator; 169 | /// use serde_norway::Value; 170 | /// 171 | /// let x: Value = Value::from_iter(vec!["lorem", "ipsum", "dolor"]); 172 | /// ``` 173 | fn from_iter>(iter: I) -> Self { 174 | let vec = iter.into_iter().map(T::into).collect(); 175 | 176 | Value::Sequence(vec) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/loader.rs: -------------------------------------------------------------------------------- 1 | use crate::de::{Event, Progress}; 2 | use crate::error::{self, Error, ErrorImpl, Result}; 3 | use crate::libyaml::error::Mark; 4 | use crate::libyaml::parser::{Event as YamlEvent, Parser}; 5 | use std::borrow::Cow; 6 | use std::collections::BTreeMap; 7 | use std::sync::Arc; 8 | 9 | pub(crate) struct Loader<'input> { 10 | parser: Option>, 11 | document_count: usize, 12 | } 13 | 14 | pub(crate) struct Document<'input> { 15 | pub events: Vec<(Event<'input>, Mark)>, 16 | pub error: Option>, 17 | /// Map from alias id to index in events. 18 | pub aliases: BTreeMap, 19 | } 20 | 21 | impl<'input> Loader<'input> { 22 | pub fn new(progress: Progress<'input>) -> Result { 23 | let input = match progress { 24 | Progress::Str(s) => Cow::Borrowed(s.as_bytes()), 25 | Progress::Slice(bytes) => Cow::Borrowed(bytes), 26 | Progress::Read(mut rdr) => { 27 | let mut buffer = Vec::new(); 28 | if let Err(io_error) = rdr.read_to_end(&mut buffer) { 29 | return Err(error::new(ErrorImpl::Io(io_error))); 30 | } 31 | Cow::Owned(buffer) 32 | } 33 | Progress::Iterable(_) | Progress::Document(_) => unreachable!(), 34 | Progress::Fail(err) => return Err(error::shared(err)), 35 | }; 36 | 37 | Ok(Loader { 38 | parser: Some(Parser::new(input)), 39 | document_count: 0, 40 | }) 41 | } 42 | 43 | pub fn next_document(&mut self) -> Option> { 44 | let Some(parser) = &mut self.parser else { 45 | return None; 46 | }; 47 | 48 | let first = self.document_count == 0; 49 | self.document_count += 1; 50 | 51 | let mut anchors = BTreeMap::new(); 52 | let mut document = Document { 53 | events: Vec::new(), 54 | error: None, 55 | aliases: BTreeMap::new(), 56 | }; 57 | 58 | loop { 59 | let (event, mark) = match parser.next() { 60 | Ok((event, mark)) => (event, mark), 61 | Err(err) => { 62 | document.error = Some(Error::from(err).shared()); 63 | return Some(document); 64 | } 65 | }; 66 | let event = match event { 67 | YamlEvent::StreamStart => continue, 68 | YamlEvent::StreamEnd => { 69 | self.parser = None; 70 | return if first { 71 | if document.events.is_empty() { 72 | document.events.push((Event::Void, mark)); 73 | } 74 | Some(document) 75 | } else { 76 | None 77 | }; 78 | } 79 | YamlEvent::DocumentStart => continue, 80 | YamlEvent::DocumentEnd => return Some(document), 81 | YamlEvent::Alias(alias) => match anchors.get(&alias) { 82 | Some(id) => Event::Alias(*id), 83 | None => { 84 | document.error = Some(error::new(ErrorImpl::UnknownAnchor(mark)).shared()); 85 | return Some(document); 86 | } 87 | }, 88 | YamlEvent::Scalar(mut scalar) => { 89 | if let Some(anchor) = scalar.anchor.take() { 90 | let id = anchors.len(); 91 | anchors.insert(anchor, id); 92 | document.aliases.insert(id, document.events.len()); 93 | } 94 | Event::Scalar(scalar) 95 | } 96 | YamlEvent::SequenceStart(mut sequence_start) => { 97 | if let Some(anchor) = sequence_start.anchor.take() { 98 | let id = anchors.len(); 99 | anchors.insert(anchor, id); 100 | document.aliases.insert(id, document.events.len()); 101 | } 102 | Event::SequenceStart(sequence_start) 103 | } 104 | YamlEvent::SequenceEnd => Event::SequenceEnd, 105 | YamlEvent::MappingStart(mut mapping_start) => { 106 | if let Some(anchor) = mapping_start.anchor.take() { 107 | let id = anchors.len(); 108 | anchors.insert(anchor, id); 109 | document.aliases.insert(id, document.events.len()); 110 | } 111 | Event::MappingStart(mapping_start) 112 | } 113 | YamlEvent::MappingEnd => Event::MappingEnd, 114 | }; 115 | document.events.push((event, mark)); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | 5 | crane = { 6 | url = "github:ipetkov/crane"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | 10 | fenix = { 11 | url = "github:nix-community/fenix"; 12 | inputs.nixpkgs.follows = "nixpkgs"; 13 | inputs.rust-analyzer-src.follows = ""; 14 | }; 15 | 16 | flake-utils.url = "github:numtide/flake-utils"; 17 | 18 | advisory-db = { 19 | url = "github:rustsec/advisory-db"; 20 | flake = false; 21 | }; 22 | }; 23 | 24 | outputs = 25 | { 26 | self, 27 | nixpkgs, 28 | crane, 29 | fenix, 30 | flake-utils, 31 | advisory-db, 32 | ... 33 | }: 34 | flake-utils.lib.eachDefaultSystem ( 35 | system: 36 | let 37 | pkgs = nixpkgs.legacyPackages.${system}; 38 | 39 | inherit (pkgs) lib; 40 | 41 | craneLib = crane.mkLib pkgs; 42 | src = craneLib.cleanCargoSource (craneLib.path ./.); 43 | 44 | # Common arguments can be set here to avoid repeating them later 45 | commonArgs = { 46 | inherit src; 47 | strictDeps = true; 48 | 49 | buildInputs = 50 | [ 51 | # Add additional build inputs here 52 | ] 53 | ++ lib.optionals pkgs.stdenv.isDarwin [ 54 | # Additional darwin specific inputs can be set here 55 | pkgs.libiconv 56 | ]; 57 | 58 | # Additional environment variables can be set directly 59 | # MY_CUSTOM_VAR = "some value"; 60 | }; 61 | 62 | craneLibLLvmTools = craneLib.overrideToolchain ( 63 | fenix.packages.${system}.complete.withComponents [ 64 | "cargo" 65 | "llvm-tools" 66 | "rustc" 67 | ] 68 | ); 69 | 70 | # Build *just* the cargo dependencies, so we can reuse 71 | # all of that work (e.g. via cachix) when running in CI 72 | cargoArtifacts = craneLib.buildDepsOnly commonArgs; 73 | 74 | # Build the actual crate itself, reusing the dependency 75 | # artifacts from above. 76 | serde-yaml = craneLib.buildPackage ( 77 | commonArgs 78 | // { 79 | inherit cargoArtifacts; 80 | } 81 | ); 82 | in 83 | { 84 | checks = { 85 | # Build the crate as part of `nix flake check` for convenience 86 | inherit serde-yaml; 87 | 88 | # Run clippy (and deny all warnings) on the crate source, 89 | # again, reusing the dependency artifacts from above. 90 | # 91 | # Note that this is done as a separate derivation so that 92 | # we can block the CI if there are issues here, but not 93 | # prevent downstream consumers from building our crate by itself. 94 | serde-yaml-clippy = craneLib.cargoClippy ( 95 | commonArgs 96 | // { 97 | inherit cargoArtifacts; 98 | cargoClippyExtraArgs = "--all-targets -- --deny warnings -Dclippy::all -Dclippy::pedantic"; 99 | } 100 | ); 101 | 102 | serde-yaml-doc = craneLib.cargoDoc ( 103 | commonArgs 104 | // { 105 | inherit cargoArtifacts; 106 | } 107 | ); 108 | 109 | # Check formatting 110 | serde-yaml-fmt = craneLib.cargoFmt { 111 | inherit src; 112 | }; 113 | 114 | # Audit dependencies 115 | serde-yaml-audit = craneLib.cargoAudit { 116 | inherit src advisory-db; 117 | }; 118 | 119 | # Audit licenses 120 | serde-yaml-deny = craneLib.cargoDeny { 121 | inherit src; 122 | }; 123 | 124 | # Run tests with cargo-nextest 125 | # Consider setting `doCheck = false` on `serde-yaml` if you do not want 126 | # the tests to run twice 127 | serde-yaml-nextest = craneLib.cargoNextest ( 128 | commonArgs 129 | // { 130 | inherit cargoArtifacts; 131 | partitions = 1; 132 | partitionType = "count"; 133 | } 134 | ); 135 | }; 136 | 137 | packages = 138 | { 139 | default = serde-yaml; 140 | } 141 | // lib.optionalAttrs (!pkgs.stdenv.isDarwin) { 142 | serde-yaml-llvm-coverage = craneLibLLvmTools.cargoLlvmCov ( 143 | commonArgs 144 | // { 145 | inherit cargoArtifacts; 146 | } 147 | ); 148 | }; 149 | 150 | apps.default = flake-utils.lib.mkApp { 151 | drv = serde-yaml; 152 | }; 153 | 154 | devShells.default = craneLib.devShell { 155 | # Inherit inputs from checks. 156 | checks = self.checks.${system}; 157 | 158 | # Additional dev-shell environment variables can be set directly 159 | # MY_CUSTOM_DEVELOPMENT_VAR = "something else"; 160 | 161 | # Extra inputs can be added here; cargo and rustc are provided by default. 162 | packages = [ 163 | pkgs.cargo-outdated 164 | ]; 165 | }; 166 | } 167 | ); 168 | } 169 | -------------------------------------------------------------------------------- /src/libyaml/error.rs: -------------------------------------------------------------------------------- 1 | use crate::libyaml::cstr::CStr; 2 | use std::fmt::{self, Debug, Display}; 3 | use std::mem::MaybeUninit; 4 | use std::ptr::NonNull; 5 | use unsafe_libyaml_norway as sys; 6 | 7 | pub(crate) type Result = std::result::Result; 8 | 9 | pub(crate) struct Error { 10 | kind: sys::yaml_error_type_t, 11 | problem: CStr<'static>, 12 | problem_offset: u64, 13 | problem_mark: Mark, 14 | context: Option>, 15 | context_mark: Mark, 16 | } 17 | 18 | impl Error { 19 | pub unsafe fn parse_error(parser: *const sys::yaml_parser_t) -> Self { 20 | Error { 21 | kind: unsafe { (*parser).error }, 22 | problem: match NonNull::new(unsafe { (*parser).problem.cast_mut() }) { 23 | Some(problem) => unsafe { CStr::from_ptr(problem) }, 24 | None => CStr::from_bytes_with_nul(b"libyaml parser failed but there is no error\0"), 25 | }, 26 | problem_offset: unsafe { (*parser).problem_offset }, 27 | problem_mark: Mark { 28 | sys: unsafe { (*parser).problem_mark }, 29 | }, 30 | context: match NonNull::new(unsafe { (*parser).context.cast_mut() }) { 31 | Some(context) => Some(unsafe { CStr::from_ptr(context) }), 32 | None => None, 33 | }, 34 | context_mark: Mark { 35 | sys: unsafe { (*parser).context_mark }, 36 | }, 37 | } 38 | } 39 | 40 | pub unsafe fn emit_error(emitter: *const sys::yaml_emitter_t) -> Self { 41 | Error { 42 | kind: unsafe { (*emitter).error }, 43 | problem: match NonNull::new(unsafe { (*emitter).problem.cast_mut() }) { 44 | Some(problem) => unsafe { CStr::from_ptr(problem) }, 45 | None => { 46 | CStr::from_bytes_with_nul(b"libyaml emitter failed but there is no error\0") 47 | } 48 | }, 49 | problem_offset: 0, 50 | problem_mark: Mark { 51 | sys: unsafe { MaybeUninit::::zeroed().assume_init() }, 52 | }, 53 | context: None, 54 | context_mark: Mark { 55 | sys: unsafe { MaybeUninit::::zeroed().assume_init() }, 56 | }, 57 | } 58 | } 59 | 60 | pub fn mark(&self) -> Mark { 61 | self.problem_mark 62 | } 63 | } 64 | 65 | impl Display for Error { 66 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 67 | write!(formatter, "{}", self.problem)?; 68 | if self.problem_mark.sys.line != 0 || self.problem_mark.sys.column != 0 { 69 | write!(formatter, " at {}", self.problem_mark)?; 70 | } else if self.problem_offset != 0 { 71 | write!(formatter, " at position {}", self.problem_offset)?; 72 | } 73 | if let Some(context) = &self.context { 74 | write!(formatter, ", {}", context)?; 75 | if (self.context_mark.sys.line != 0 || self.context_mark.sys.column != 0) 76 | && (self.context_mark.sys.line != self.problem_mark.sys.line 77 | || self.context_mark.sys.column != self.problem_mark.sys.column) 78 | { 79 | write!(formatter, " at {}", self.context_mark)?; 80 | } 81 | } 82 | Ok(()) 83 | } 84 | } 85 | 86 | impl Debug for Error { 87 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 88 | let mut formatter = formatter.debug_struct("Error"); 89 | if let Some(kind) = match self.kind { 90 | sys::YAML_MEMORY_ERROR => Some("MEMORY"), 91 | sys::YAML_READER_ERROR => Some("READER"), 92 | sys::YAML_SCANNER_ERROR => Some("SCANNER"), 93 | sys::YAML_PARSER_ERROR => Some("PARSER"), 94 | sys::YAML_COMPOSER_ERROR => Some("COMPOSER"), 95 | sys::YAML_WRITER_ERROR => Some("WRITER"), 96 | sys::YAML_EMITTER_ERROR => Some("EMITTER"), 97 | _ => None, 98 | } { 99 | formatter.field("kind", &format_args!("{}", kind)); 100 | } 101 | formatter.field("problem", &self.problem); 102 | if self.problem_mark.sys.line != 0 || self.problem_mark.sys.column != 0 { 103 | formatter.field("problem_mark", &self.problem_mark); 104 | } else if self.problem_offset != 0 { 105 | formatter.field("problem_offset", &self.problem_offset); 106 | } 107 | if let Some(context) = &self.context { 108 | formatter.field("context", context); 109 | if self.context_mark.sys.line != 0 || self.context_mark.sys.column != 0 { 110 | formatter.field("context_mark", &self.context_mark); 111 | } 112 | } 113 | formatter.finish() 114 | } 115 | } 116 | 117 | #[derive(Copy, Clone)] 118 | pub(crate) struct Mark { 119 | pub(super) sys: sys::yaml_mark_t, 120 | } 121 | 122 | impl Mark { 123 | pub fn index(&self) -> u64 { 124 | self.sys.index 125 | } 126 | 127 | pub fn line(&self) -> u64 { 128 | self.sys.line 129 | } 130 | 131 | pub fn column(&self) -> u64 { 132 | self.sys.column 133 | } 134 | } 135 | 136 | impl Display for Mark { 137 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 138 | if self.sys.line != 0 || self.sys.column != 0 { 139 | write!( 140 | formatter, 141 | "line {} column {}", 142 | self.sys.line + 1, 143 | self.sys.column + 1, 144 | ) 145 | } else { 146 | write!(formatter, "position {}", self.sys.index) 147 | } 148 | } 149 | } 150 | 151 | impl Debug for Mark { 152 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 153 | let mut formatter = formatter.debug_struct("Mark"); 154 | if self.sys.line != 0 || self.sys.column != 0 { 155 | formatter.field("line", &(self.sys.line + 1)); 156 | formatter.field("column", &(self.sys.column + 1)); 157 | } else { 158 | formatter.field("index", &self.sys.index); 159 | } 160 | formatter.finish() 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc(html_logo_url = "https://upload.wikimedia.org/wikipedia/commons/d/d9/Flag_of_Norway.svg")] 2 | //! [![github]](https://github.com/dtolnay/serde-yaml) [![crates-io]](https://crates.io/crates/serde-yaml) [![docs-rs]](https://docs.rs/serde-yaml) 3 | //! 4 | //! Rust library for using the [Serde] serialization framework with data in 5 | //! [YAML] file format. 6 | //! 7 | //! [Serde]: https://github.com/serde-rs/serde 8 | //! [YAML]: https://yaml.org/ 9 | //! 10 | //! # Examples 11 | //! 12 | //! ``` 13 | //! use std::collections::BTreeMap; 14 | //! 15 | //! fn main() -> Result<(), serde_norway::Error> { 16 | //! // You have some type. 17 | //! let mut map = BTreeMap::new(); 18 | //! map.insert("x".to_string(), 1.0); 19 | //! map.insert("y".to_string(), 2.0); 20 | //! 21 | //! // Serialize it to a YAML string. 22 | //! let yaml = serde_norway::to_string(&map)?; 23 | //! assert_eq!(yaml, "x: 1.0\ny: 2.0\n"); 24 | //! 25 | //! // Deserialize it back to a Rust type. 26 | //! let deserialized_map: BTreeMap = serde_norway::from_str(&yaml)?; 27 | //! assert_eq!(map, deserialized_map); 28 | //! Ok(()) 29 | //! } 30 | //! ``` 31 | //! 32 | //! ## Using Serde derive 33 | //! 34 | //! It can also be used with Serde's derive macros to handle structs and enums 35 | //! defined in your program. 36 | //! 37 | //! Structs serialize in the obvious way: 38 | //! 39 | //! ``` 40 | //! # use serde_derive::{Serialize, Deserialize}; 41 | //! use serde::{Serialize, Deserialize}; 42 | //! 43 | //! #[derive(Serialize, Deserialize, PartialEq, Debug)] 44 | //! struct Point { 45 | //! x: f64, 46 | //! y: f64, 47 | //! } 48 | //! 49 | //! fn main() -> Result<(), serde_norway::Error> { 50 | //! let point = Point { x: 1.0, y: 2.0 }; 51 | //! 52 | //! let yaml = serde_norway::to_string(&point)?; 53 | //! assert_eq!(yaml, "x: 1.0\ny: 2.0\n"); 54 | //! 55 | //! let deserialized_point: Point = serde_norway::from_str(&yaml)?; 56 | //! assert_eq!(point, deserialized_point); 57 | //! Ok(()) 58 | //! } 59 | //! ``` 60 | //! 61 | //! Enums serialize using YAML's `!tag` syntax to identify the variant name. 62 | //! 63 | //! ``` 64 | //! # use serde_derive::{Serialize, Deserialize}; 65 | //! use serde::{Serialize, Deserialize}; 66 | //! 67 | //! #[derive(Serialize, Deserialize, PartialEq, Debug)] 68 | //! enum Enum { 69 | //! Unit, 70 | //! Newtype(usize), 71 | //! Tuple(usize, usize, usize), 72 | //! Struct { x: f64, y: f64 }, 73 | //! } 74 | //! 75 | //! fn main() -> Result<(), serde_norway::Error> { 76 | //! let yaml = " 77 | //! - !Newtype 1 78 | //! - !Tuple [0, 0, 0] 79 | //! - !Struct {x: 1.0, y: 2.0} 80 | //! "; 81 | //! let values: Vec = serde_norway::from_str(yaml).unwrap(); 82 | //! assert_eq!(values[0], Enum::Newtype(1)); 83 | //! assert_eq!(values[1], Enum::Tuple(0, 0, 0)); 84 | //! assert_eq!(values[2], Enum::Struct { x: 1.0, y: 2.0 }); 85 | //! 86 | //! // The last two in YAML's block style instead: 87 | //! let yaml = " 88 | //! - !Tuple 89 | //! - 0 90 | //! - 0 91 | //! - 0 92 | //! - !Struct 93 | //! x: 1.0 94 | //! y: 2.0 95 | //! "; 96 | //! let values: Vec = serde_norway::from_str(yaml).unwrap(); 97 | //! assert_eq!(values[0], Enum::Tuple(0, 0, 0)); 98 | //! assert_eq!(values[1], Enum::Struct { x: 1.0, y: 2.0 }); 99 | //! 100 | //! // Variants with no data can be written using !Tag or just the string name. 101 | //! let yaml = " 102 | //! - Unit # serialization produces this one 103 | //! - !Unit 104 | //! "; 105 | //! let values: Vec = serde_norway::from_str(yaml).unwrap(); 106 | //! assert_eq!(values[0], Enum::Unit); 107 | //! assert_eq!(values[1], Enum::Unit); 108 | //! 109 | //! Ok(()) 110 | //! } 111 | //! ``` 112 | 113 | #![deny(missing_docs, unsafe_op_in_unsafe_fn)] 114 | // Suppressed clippy_pedantic lints 115 | #![allow( 116 | // buggy 117 | clippy::iter_not_returning_iterator, // https://github.com/rust-lang/rust-clippy/issues/8285 118 | clippy::ptr_arg, // https://github.com/rust-lang/rust-clippy/issues/9218 119 | clippy::question_mark, // https://github.com/rust-lang/rust-clippy/issues/7859 120 | // private Deserializer::next 121 | clippy::should_implement_trait, 122 | // things are often more readable this way 123 | clippy::cast_lossless, 124 | clippy::checked_conversions, 125 | clippy::if_not_else, 126 | clippy::manual_assert, 127 | clippy::match_like_matches_macro, 128 | clippy::match_same_arms, 129 | clippy::module_name_repetitions, 130 | clippy::needless_pass_by_value, 131 | clippy::redundant_else, 132 | clippy::single_match_else, 133 | // code is acceptable 134 | clippy::blocks_in_conditions, 135 | clippy::cast_possible_truncation, 136 | clippy::cast_possible_wrap, 137 | clippy::cast_precision_loss, 138 | clippy::cast_sign_loss, 139 | clippy::derive_partial_eq_without_eq, 140 | clippy::derived_hash_with_manual_eq, 141 | clippy::doc_markdown, 142 | clippy::items_after_statements, 143 | clippy::let_underscore_untyped, 144 | clippy::manual_map, 145 | clippy::missing_panics_doc, 146 | clippy::never_loop, 147 | clippy::return_self_not_must_use, 148 | clippy::too_many_lines, 149 | clippy::uninlined_format_args, 150 | clippy::unsafe_removed_from_name, 151 | clippy::wildcard_in_or_patterns, 152 | // noisy 153 | clippy::missing_errors_doc, 154 | clippy::must_use_candidate, 155 | )] 156 | 157 | pub use crate::de::{from_reader, from_slice, from_str, Deserializer}; 158 | pub use crate::error::{Error, Location, Result}; 159 | pub use crate::ser::{to_string, to_writer, Serializer}; 160 | #[doc(inline)] 161 | pub use crate::value::{from_value, to_value, Index, Number, Sequence, Value}; 162 | 163 | #[doc(inline)] 164 | pub use crate::mapping::Mapping; 165 | 166 | mod de; 167 | mod error; 168 | mod libyaml; 169 | mod loader; 170 | pub mod mapping; 171 | mod number; 172 | mod path; 173 | mod ser; 174 | pub mod value; 175 | pub mod with; 176 | 177 | // Prevent downstream code from implementing the Index trait. 178 | mod private { 179 | pub trait Sealed {} 180 | impl Sealed for usize {} 181 | impl Sealed for str {} 182 | impl Sealed for String {} 183 | impl Sealed for crate::Value {} 184 | impl<'a, T> Sealed for &'a T where T: ?Sized + Sealed {} 185 | } 186 | -------------------------------------------------------------------------------- /src/libyaml/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::libyaml::cstr::{self, CStr}; 2 | use crate::libyaml::error::{Error, Mark, Result}; 3 | use crate::libyaml::tag::Tag; 4 | use crate::libyaml::util::Owned; 5 | use std::borrow::Cow; 6 | use std::fmt::{self, Debug}; 7 | use std::mem::MaybeUninit; 8 | use std::ptr::{addr_of_mut, NonNull}; 9 | use std::slice; 10 | use unsafe_libyaml_norway as sys; 11 | 12 | pub(crate) struct Parser<'input> { 13 | pin: Owned>, 14 | } 15 | 16 | struct ParserPinned<'input> { 17 | sys: sys::yaml_parser_t, 18 | input: Cow<'input, [u8]>, 19 | } 20 | 21 | #[derive(Debug)] 22 | pub(crate) enum Event<'input> { 23 | StreamStart, 24 | StreamEnd, 25 | DocumentStart, 26 | DocumentEnd, 27 | Alias(Anchor), 28 | Scalar(Scalar<'input>), 29 | SequenceStart(SequenceStart), 30 | SequenceEnd, 31 | MappingStart(MappingStart), 32 | MappingEnd, 33 | } 34 | 35 | pub(crate) struct Scalar<'input> { 36 | pub anchor: Option, 37 | pub tag: Option, 38 | pub value: Box<[u8]>, 39 | pub style: ScalarStyle, 40 | pub repr: Option<&'input [u8]>, 41 | } 42 | 43 | #[derive(Debug)] 44 | pub(crate) struct SequenceStart { 45 | pub anchor: Option, 46 | pub tag: Option, 47 | } 48 | 49 | #[derive(Debug)] 50 | pub(crate) struct MappingStart { 51 | pub anchor: Option, 52 | pub tag: Option, 53 | } 54 | 55 | #[derive(Ord, PartialOrd, Eq, PartialEq)] 56 | pub(crate) struct Anchor(Box<[u8]>); 57 | 58 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 59 | pub(crate) enum ScalarStyle { 60 | Plain, 61 | SingleQuoted, 62 | DoubleQuoted, 63 | Literal, 64 | Folded, 65 | } 66 | 67 | impl<'input> Parser<'input> { 68 | pub fn new(input: Cow<'input, [u8]>) -> Parser<'input> { 69 | let owned = Owned::::new_uninit(); 70 | let pin = unsafe { 71 | let parser = addr_of_mut!((*owned.ptr).sys); 72 | if sys::yaml_parser_initialize(parser).fail { 73 | panic!("malloc error: {}", Error::parse_error(parser)); 74 | } 75 | sys::yaml_parser_set_encoding(parser, sys::YAML_UTF8_ENCODING); 76 | sys::yaml_parser_set_input_string(parser, input.as_ptr(), input.len() as u64); 77 | addr_of_mut!((*owned.ptr).input).write(input); 78 | Owned::assume_init(owned) 79 | }; 80 | Parser { pin } 81 | } 82 | 83 | pub fn next(&mut self) -> Result<(Event<'input>, Mark)> { 84 | let mut event = MaybeUninit::::uninit(); 85 | unsafe { 86 | let parser = addr_of_mut!((*self.pin.ptr).sys); 87 | if (*parser).error != sys::YAML_NO_ERROR { 88 | return Err(Error::parse_error(parser)); 89 | } 90 | let event = event.as_mut_ptr(); 91 | if sys::yaml_parser_parse(parser, event).fail { 92 | return Err(Error::parse_error(parser)); 93 | } 94 | let ret = convert_event(&*event, &(*self.pin.ptr).input); 95 | let mark = Mark { 96 | sys: (*event).start_mark, 97 | }; 98 | sys::yaml_event_delete(event); 99 | Ok((ret, mark)) 100 | } 101 | } 102 | } 103 | 104 | unsafe fn convert_event<'input>( 105 | sys: &sys::yaml_event_t, 106 | input: &Cow<'input, [u8]>, 107 | ) -> Event<'input> { 108 | match sys.type_ { 109 | sys::YAML_STREAM_START_EVENT => Event::StreamStart, 110 | sys::YAML_STREAM_END_EVENT => Event::StreamEnd, 111 | sys::YAML_DOCUMENT_START_EVENT => Event::DocumentStart, 112 | sys::YAML_DOCUMENT_END_EVENT => Event::DocumentEnd, 113 | sys::YAML_ALIAS_EVENT => { 114 | Event::Alias(unsafe { optional_anchor(sys.data.alias.anchor) }.unwrap()) 115 | } 116 | sys::YAML_SCALAR_EVENT => Event::Scalar(Scalar { 117 | anchor: unsafe { optional_anchor(sys.data.scalar.anchor) }, 118 | tag: unsafe { optional_tag(sys.data.scalar.tag) }, 119 | value: Box::from(unsafe { 120 | slice::from_raw_parts(sys.data.scalar.value, sys.data.scalar.length as usize) 121 | }), 122 | style: match unsafe { sys.data.scalar.style } { 123 | sys::YAML_PLAIN_SCALAR_STYLE => ScalarStyle::Plain, 124 | sys::YAML_SINGLE_QUOTED_SCALAR_STYLE => ScalarStyle::SingleQuoted, 125 | sys::YAML_DOUBLE_QUOTED_SCALAR_STYLE => ScalarStyle::DoubleQuoted, 126 | sys::YAML_LITERAL_SCALAR_STYLE => ScalarStyle::Literal, 127 | sys::YAML_FOLDED_SCALAR_STYLE => ScalarStyle::Folded, 128 | sys::YAML_ANY_SCALAR_STYLE | _ => unreachable!(), 129 | }, 130 | repr: if let Cow::Borrowed(input) = input { 131 | Some(&input[sys.start_mark.index as usize..sys.end_mark.index as usize]) 132 | } else { 133 | None 134 | }, 135 | }), 136 | sys::YAML_SEQUENCE_START_EVENT => Event::SequenceStart(SequenceStart { 137 | anchor: unsafe { optional_anchor(sys.data.sequence_start.anchor) }, 138 | tag: unsafe { optional_tag(sys.data.sequence_start.tag) }, 139 | }), 140 | sys::YAML_SEQUENCE_END_EVENT => Event::SequenceEnd, 141 | sys::YAML_MAPPING_START_EVENT => Event::MappingStart(MappingStart { 142 | anchor: unsafe { optional_anchor(sys.data.mapping_start.anchor) }, 143 | tag: unsafe { optional_tag(sys.data.mapping_start.tag) }, 144 | }), 145 | sys::YAML_MAPPING_END_EVENT => Event::MappingEnd, 146 | sys::YAML_NO_EVENT => unreachable!(), 147 | _ => unimplemented!(), 148 | } 149 | } 150 | 151 | unsafe fn optional_anchor(anchor: *const u8) -> Option { 152 | let ptr = NonNull::new(anchor as *mut i8)?; 153 | let cstr = unsafe { CStr::from_ptr(ptr) }; 154 | Some(Anchor(Box::from(cstr.to_bytes()))) 155 | } 156 | 157 | unsafe fn optional_tag(tag: *const u8) -> Option { 158 | let ptr = NonNull::new(tag as *mut i8)?; 159 | let cstr = unsafe { CStr::from_ptr(ptr) }; 160 | Some(Tag(Box::from(cstr.to_bytes()))) 161 | } 162 | 163 | impl<'input> Debug for Scalar<'input> { 164 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 165 | let Scalar { 166 | anchor, 167 | tag, 168 | value, 169 | style, 170 | repr: _, 171 | } = self; 172 | 173 | struct LossySlice<'a>(&'a [u8]); 174 | 175 | impl<'a> Debug for LossySlice<'a> { 176 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 177 | cstr::debug_lossy(self.0, formatter) 178 | } 179 | } 180 | 181 | formatter 182 | .debug_struct("Scalar") 183 | .field("anchor", anchor) 184 | .field("tag", tag) 185 | .field("value", &LossySlice(value)) 186 | .field("style", style) 187 | .finish() 188 | } 189 | } 190 | 191 | impl Debug for Anchor { 192 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 193 | cstr::debug_lossy(&self.0, formatter) 194 | } 195 | } 196 | 197 | impl<'input> Drop for ParserPinned<'input> { 198 | fn drop(&mut self) { 199 | unsafe { sys::yaml_parser_delete(&mut self.sys) } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/libyaml/emitter.rs: -------------------------------------------------------------------------------- 1 | use crate::libyaml; 2 | use crate::libyaml::util::Owned; 3 | use std::ffi::c_void; 4 | use std::io; 5 | use std::mem::{self, MaybeUninit}; 6 | use std::ptr::{self, addr_of_mut}; 7 | use std::slice; 8 | use unsafe_libyaml_norway as sys; 9 | 10 | #[derive(Debug)] 11 | pub(crate) enum Error { 12 | Libyaml(libyaml::error::Error), 13 | Io(io::Error), 14 | } 15 | 16 | pub(crate) struct Emitter<'a> { 17 | pin: Owned>, 18 | } 19 | 20 | struct EmitterPinned<'a> { 21 | sys: sys::yaml_emitter_t, 22 | write: Box, 23 | write_error: Option, 24 | } 25 | 26 | #[derive(Debug)] 27 | pub(crate) enum Event<'a> { 28 | StreamStart, 29 | StreamEnd, 30 | DocumentStart, 31 | DocumentEnd, 32 | Scalar(Scalar<'a>), 33 | SequenceStart(Sequence), 34 | SequenceEnd, 35 | MappingStart(Mapping), 36 | MappingEnd, 37 | } 38 | 39 | #[derive(Debug)] 40 | pub(crate) struct Scalar<'a> { 41 | pub tag: Option, 42 | pub value: &'a str, 43 | pub style: ScalarStyle, 44 | } 45 | 46 | #[derive(Debug)] 47 | pub(crate) enum ScalarStyle { 48 | Any, 49 | Plain, 50 | SingleQuoted, 51 | Literal, 52 | } 53 | 54 | #[derive(Debug)] 55 | pub(crate) struct Sequence { 56 | pub tag: Option, 57 | } 58 | 59 | #[derive(Debug)] 60 | pub(crate) struct Mapping { 61 | pub tag: Option, 62 | } 63 | 64 | impl<'a> Emitter<'a> { 65 | pub fn new(write: Box) -> Emitter<'a> { 66 | let owned = Owned::::new_uninit(); 67 | let pin = unsafe { 68 | let emitter = addr_of_mut!((*owned.ptr).sys); 69 | if sys::yaml_emitter_initialize(emitter).fail { 70 | panic!("malloc error: {}", libyaml::Error::emit_error(emitter)); 71 | } 72 | sys::yaml_emitter_set_unicode(emitter, true); 73 | sys::yaml_emitter_set_width(emitter, -1); 74 | addr_of_mut!((*owned.ptr).write).write(write); 75 | addr_of_mut!((*owned.ptr).write_error).write(None); 76 | sys::yaml_emitter_set_output(emitter, write_handler, owned.ptr.cast()); 77 | Owned::assume_init(owned) 78 | }; 79 | Emitter { pin } 80 | } 81 | 82 | pub fn emit(&mut self, event: Event) -> Result<(), Error> { 83 | let mut sys_event = MaybeUninit::::uninit(); 84 | let sys_event = sys_event.as_mut_ptr(); 85 | unsafe { 86 | let emitter = addr_of_mut!((*self.pin.ptr).sys); 87 | let initialize_status = match event { 88 | Event::StreamStart => { 89 | sys::yaml_stream_start_event_initialize(sys_event, sys::YAML_UTF8_ENCODING) 90 | } 91 | Event::StreamEnd => sys::yaml_stream_end_event_initialize(sys_event), 92 | Event::DocumentStart => { 93 | let version_directive = ptr::null_mut(); 94 | let tag_directives_start = ptr::null_mut(); 95 | let tag_directives_end = ptr::null_mut(); 96 | let implicit = true; 97 | sys::yaml_document_start_event_initialize( 98 | sys_event, 99 | version_directive, 100 | tag_directives_start, 101 | tag_directives_end, 102 | implicit, 103 | ) 104 | } 105 | Event::DocumentEnd => { 106 | let implicit = true; 107 | sys::yaml_document_end_event_initialize(sys_event, implicit) 108 | } 109 | Event::Scalar(mut scalar) => { 110 | let anchor = ptr::null(); 111 | let tag = scalar.tag.as_mut().map_or_else(ptr::null, |tag| { 112 | tag.push('\0'); 113 | tag.as_ptr() 114 | }); 115 | let value = scalar.value.as_ptr(); 116 | let length = scalar.value.len() as i32; 117 | let plain_implicit = tag.is_null(); 118 | let quoted_implicit = tag.is_null(); 119 | let style = match scalar.style { 120 | ScalarStyle::Any => sys::YAML_ANY_SCALAR_STYLE, 121 | ScalarStyle::Plain => sys::YAML_PLAIN_SCALAR_STYLE, 122 | ScalarStyle::SingleQuoted => sys::YAML_SINGLE_QUOTED_SCALAR_STYLE, 123 | ScalarStyle::Literal => sys::YAML_LITERAL_SCALAR_STYLE, 124 | }; 125 | sys::yaml_scalar_event_initialize( 126 | sys_event, 127 | anchor, 128 | tag, 129 | value, 130 | length, 131 | plain_implicit, 132 | quoted_implicit, 133 | style, 134 | ) 135 | } 136 | Event::SequenceStart(mut sequence) => { 137 | let anchor = ptr::null(); 138 | let tag = sequence.tag.as_mut().map_or_else(ptr::null, |tag| { 139 | tag.push('\0'); 140 | tag.as_ptr() 141 | }); 142 | let implicit = tag.is_null(); 143 | let style = sys::YAML_ANY_SEQUENCE_STYLE; 144 | sys::yaml_sequence_start_event_initialize( 145 | sys_event, anchor, tag, implicit, style, 146 | ) 147 | } 148 | Event::SequenceEnd => sys::yaml_sequence_end_event_initialize(sys_event), 149 | Event::MappingStart(mut mapping) => { 150 | let anchor = ptr::null(); 151 | let tag = mapping.tag.as_mut().map_or_else(ptr::null, |tag| { 152 | tag.push('\0'); 153 | tag.as_ptr() 154 | }); 155 | let implicit = tag.is_null(); 156 | let style = sys::YAML_ANY_MAPPING_STYLE; 157 | sys::yaml_mapping_start_event_initialize( 158 | sys_event, anchor, tag, implicit, style, 159 | ) 160 | } 161 | Event::MappingEnd => sys::yaml_mapping_end_event_initialize(sys_event), 162 | }; 163 | if initialize_status.fail { 164 | return Err(Error::Libyaml(libyaml::Error::emit_error(emitter))); 165 | } 166 | if sys::yaml_emitter_emit(emitter, sys_event).fail { 167 | return Err(self.error()); 168 | } 169 | } 170 | Ok(()) 171 | } 172 | 173 | pub fn flush(&mut self) -> Result<(), Error> { 174 | unsafe { 175 | let emitter = addr_of_mut!((*self.pin.ptr).sys); 176 | if sys::yaml_emitter_flush(emitter).fail { 177 | return Err(self.error()); 178 | } 179 | } 180 | Ok(()) 181 | } 182 | 183 | pub fn into_inner(self) -> Box { 184 | let sink = Box::new(io::sink()); 185 | unsafe { mem::replace(&mut (*self.pin.ptr).write, sink) } 186 | } 187 | 188 | fn error(&mut self) -> Error { 189 | let emitter = unsafe { &mut *self.pin.ptr }; 190 | if let Some(write_error) = emitter.write_error.take() { 191 | Error::Io(write_error) 192 | } else { 193 | Error::Libyaml(unsafe { libyaml::Error::emit_error(&emitter.sys) }) 194 | } 195 | } 196 | } 197 | 198 | unsafe fn write_handler(data: *mut c_void, buffer: *mut u8, size: u64) -> i32 { 199 | let data = data.cast::(); 200 | match io::Write::write_all(unsafe { &mut *(*data).write }, unsafe { 201 | slice::from_raw_parts(buffer, size as usize) 202 | }) { 203 | Ok(()) => 1, 204 | Err(err) => { 205 | unsafe { 206 | (*data).write_error = Some(err); 207 | } 208 | 0 209 | } 210 | } 211 | } 212 | 213 | impl<'a> Drop for EmitterPinned<'a> { 214 | fn drop(&mut self) { 215 | unsafe { sys::yaml_emitter_delete(&mut self.sys) } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::libyaml::{emitter, error as libyaml}; 2 | use crate::path::Path; 3 | use serde::{de, ser}; 4 | use std::error::Error as StdError; 5 | use std::fmt::{self, Debug, Display}; 6 | use std::io; 7 | use std::result; 8 | use std::string; 9 | use std::sync::Arc; 10 | 11 | /// An error that happened serializing or deserializing YAML data. 12 | pub struct Error(Box); 13 | 14 | /// Alias for a `Result` with the error type `serde_norway::Error`. 15 | pub type Result = result::Result; 16 | 17 | #[derive(Debug)] 18 | pub(crate) enum ErrorImpl { 19 | Message(String, Option), 20 | 21 | Libyaml(libyaml::Error), 22 | Io(io::Error), 23 | FromUtf8(string::FromUtf8Error), 24 | 25 | EndOfStream, 26 | MoreThanOneDocument, 27 | RecursionLimitExceeded(libyaml::Mark), 28 | RepetitionLimitExceeded, 29 | BytesUnsupported, 30 | UnknownAnchor(libyaml::Mark), 31 | SerializeNestedEnum, 32 | ScalarInMerge, 33 | TaggedInMerge, 34 | ScalarInMergeElement, 35 | SequenceInMergeElement, 36 | EmptyTag, 37 | FailedToParseNumber, 38 | 39 | Shared(Arc), 40 | } 41 | 42 | #[derive(Debug)] 43 | pub(crate) struct Pos { 44 | mark: libyaml::Mark, 45 | path: String, 46 | } 47 | 48 | /// The input location that an error occured. 49 | #[derive(Debug)] 50 | pub struct Location { 51 | index: usize, 52 | line: usize, 53 | column: usize, 54 | } 55 | 56 | impl Location { 57 | /// The byte index of the error 58 | pub fn index(&self) -> usize { 59 | self.index 60 | } 61 | 62 | /// The line of the error 63 | pub fn line(&self) -> usize { 64 | self.line 65 | } 66 | 67 | /// The column of the error 68 | pub fn column(&self) -> usize { 69 | self.column 70 | } 71 | 72 | // This is to keep decoupled with the yaml crate 73 | #[doc(hidden)] 74 | fn from_mark(mark: libyaml::Mark) -> Self { 75 | Location { 76 | index: mark.index() as usize, 77 | // `line` and `column` returned from libyaml are 0-indexed but all error messages add +1 to this value 78 | line: mark.line() as usize + 1, 79 | column: mark.column() as usize + 1, 80 | } 81 | } 82 | } 83 | 84 | impl Error { 85 | /// Returns the Location from the error if one exists. 86 | /// 87 | /// Not all types of errors have a location so this can return `None`. 88 | /// 89 | /// # Examples 90 | /// 91 | /// ``` 92 | /// # use serde_norway::{Value, Error}; 93 | /// # 94 | /// // The `@` character as the first character makes this invalid yaml 95 | /// let invalid_yaml: Result = serde_norway::from_str("@invalid_yaml"); 96 | /// 97 | /// let location = invalid_yaml.unwrap_err().location().unwrap(); 98 | /// 99 | /// assert_eq!(location.line(), 1); 100 | /// assert_eq!(location.column(), 1); 101 | /// ``` 102 | pub fn location(&self) -> Option { 103 | self.0.location() 104 | } 105 | } 106 | 107 | pub(crate) fn new(inner: ErrorImpl) -> Error { 108 | Error(Box::new(inner)) 109 | } 110 | 111 | pub(crate) fn shared(shared: Arc) -> Error { 112 | Error(Box::new(ErrorImpl::Shared(shared))) 113 | } 114 | 115 | pub(crate) fn fix_mark(mut error: Error, mark: libyaml::Mark, path: Path) -> Error { 116 | if let ErrorImpl::Message(_, none @ None) = error.0.as_mut() { 117 | *none = Some(Pos { 118 | mark, 119 | path: path.to_string(), 120 | }); 121 | } 122 | error 123 | } 124 | 125 | impl Error { 126 | pub(crate) fn shared(self) -> Arc { 127 | if let ErrorImpl::Shared(err) = *self.0 { 128 | err 129 | } else { 130 | Arc::from(self.0) 131 | } 132 | } 133 | } 134 | 135 | impl From for Error { 136 | fn from(err: libyaml::Error) -> Self { 137 | Error(Box::new(ErrorImpl::Libyaml(err))) 138 | } 139 | } 140 | 141 | impl From for Error { 142 | fn from(err: emitter::Error) -> Self { 143 | match err { 144 | emitter::Error::Libyaml(err) => Self::from(err), 145 | emitter::Error::Io(err) => new(ErrorImpl::Io(err)), 146 | } 147 | } 148 | } 149 | 150 | impl StdError for Error { 151 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 152 | self.0.source() 153 | } 154 | } 155 | 156 | impl Display for Error { 157 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 158 | self.0.display(f) 159 | } 160 | } 161 | 162 | // Remove two layers of verbosity from the debug representation. Humans often 163 | // end up seeing this representation because it is what unwrap() shows. 164 | impl Debug for Error { 165 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 166 | self.0.debug(f) 167 | } 168 | } 169 | 170 | impl ser::Error for Error { 171 | fn custom(msg: T) -> Self { 172 | Error(Box::new(ErrorImpl::Message(msg.to_string(), None))) 173 | } 174 | } 175 | 176 | impl de::Error for Error { 177 | fn custom(msg: T) -> Self { 178 | Error(Box::new(ErrorImpl::Message(msg.to_string(), None))) 179 | } 180 | } 181 | 182 | impl ErrorImpl { 183 | fn location(&self) -> Option { 184 | self.mark().map(Location::from_mark) 185 | } 186 | 187 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 188 | match self { 189 | ErrorImpl::Io(err) => err.source(), 190 | ErrorImpl::FromUtf8(err) => err.source(), 191 | ErrorImpl::Shared(err) => err.source(), 192 | _ => None, 193 | } 194 | } 195 | 196 | fn mark(&self) -> Option { 197 | match self { 198 | ErrorImpl::Message(_, Some(Pos { mark, path: _ })) 199 | | ErrorImpl::RecursionLimitExceeded(mark) 200 | | ErrorImpl::UnknownAnchor(mark) => Some(*mark), 201 | ErrorImpl::Libyaml(err) => Some(err.mark()), 202 | ErrorImpl::Shared(err) => err.mark(), 203 | _ => None, 204 | } 205 | } 206 | 207 | fn message_no_mark(&self, f: &mut fmt::Formatter) -> fmt::Result { 208 | match self { 209 | ErrorImpl::Message(msg, None) => f.write_str(msg), 210 | ErrorImpl::Message(msg, Some(Pos { mark: _, path })) => { 211 | if path != "." { 212 | write!(f, "{}: ", path)?; 213 | } 214 | f.write_str(msg) 215 | } 216 | ErrorImpl::Libyaml(_) => unreachable!(), 217 | ErrorImpl::Io(err) => Display::fmt(err, f), 218 | ErrorImpl::FromUtf8(err) => Display::fmt(err, f), 219 | ErrorImpl::EndOfStream => f.write_str("EOF while parsing a value"), 220 | ErrorImpl::MoreThanOneDocument => f.write_str( 221 | "deserializing from YAML containing more than one document is not supported", 222 | ), 223 | ErrorImpl::RecursionLimitExceeded(_mark) => f.write_str("recursion limit exceeded"), 224 | ErrorImpl::RepetitionLimitExceeded => f.write_str("repetition limit exceeded"), 225 | ErrorImpl::BytesUnsupported => { 226 | f.write_str("serialization and deserialization of bytes in YAML is not implemented") 227 | } 228 | ErrorImpl::UnknownAnchor(_mark) => f.write_str("unknown anchor"), 229 | ErrorImpl::SerializeNestedEnum => { 230 | f.write_str("serializing nested enums in YAML is not supported yet") 231 | } 232 | ErrorImpl::ScalarInMerge => { 233 | f.write_str("expected a mapping or list of mappings for merging, but found scalar") 234 | } 235 | ErrorImpl::TaggedInMerge => f.write_str("unexpected tagged value in merge"), 236 | ErrorImpl::ScalarInMergeElement => { 237 | f.write_str("expected a mapping for merging, but found scalar") 238 | } 239 | ErrorImpl::SequenceInMergeElement => { 240 | f.write_str("expected a mapping for merging, but found sequence") 241 | } 242 | ErrorImpl::EmptyTag => f.write_str("empty YAML tag is not allowed"), 243 | ErrorImpl::FailedToParseNumber => f.write_str("failed to parse YAML number"), 244 | ErrorImpl::Shared(_) => unreachable!(), 245 | } 246 | } 247 | 248 | fn display(&self, f: &mut fmt::Formatter) -> fmt::Result { 249 | match self { 250 | ErrorImpl::Libyaml(err) => Display::fmt(err, f), 251 | ErrorImpl::Shared(err) => err.display(f), 252 | _ => { 253 | self.message_no_mark(f)?; 254 | if let Some(mark) = self.mark() { 255 | if mark.line() != 0 || mark.column() != 0 { 256 | write!(f, " at {}", mark)?; 257 | } 258 | } 259 | Ok(()) 260 | } 261 | } 262 | } 263 | 264 | fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result { 265 | match self { 266 | ErrorImpl::Libyaml(err) => Debug::fmt(err, f), 267 | ErrorImpl::Shared(err) => err.debug(f), 268 | _ => { 269 | f.write_str("Error(")?; 270 | struct MessageNoMark<'a>(&'a ErrorImpl); 271 | impl<'a> Display for MessageNoMark<'a> { 272 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 273 | self.0.message_no_mark(f) 274 | } 275 | } 276 | let msg = MessageNoMark(self).to_string(); 277 | Debug::fmt(&msg, f)?; 278 | if let Some(mark) = self.mark() { 279 | write!( 280 | f, 281 | ", line: {}, column: {}", 282 | mark.line() + 1, 283 | mark.column() + 1, 284 | )?; 285 | } 286 | f.write_str(")") 287 | } 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/value/index.rs: -------------------------------------------------------------------------------- 1 | use crate::mapping::Entry; 2 | use crate::{mapping, private, Mapping, Value}; 3 | use std::fmt::{self, Debug}; 4 | use std::ops; 5 | 6 | /// A type that can be used to index into a `serde_norway::Value`. See the `get` 7 | /// and `get_mut` methods of `Value`. 8 | /// 9 | /// This trait is sealed and cannot be implemented for types outside of 10 | /// `serde_norway`. 11 | pub trait Index: private::Sealed { 12 | /// Return None if the key is not already in the sequence or object. 13 | #[doc(hidden)] 14 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value>; 15 | 16 | /// Return None if the key is not already in the sequence or object. 17 | #[doc(hidden)] 18 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value>; 19 | 20 | /// Panic if sequence index out of bounds. If key is not already in the object, 21 | /// insert it with a value of null. Panic if Value is a type that cannot be 22 | /// indexed into, except if Value is null then it can be treated as an empty 23 | /// object. 24 | #[doc(hidden)] 25 | fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value; 26 | } 27 | 28 | impl Index for usize { 29 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 30 | match v.untag_ref() { 31 | Value::Sequence(vec) => vec.get(*self), 32 | Value::Mapping(vec) => vec.get(Value::Number((*self).into())), 33 | _ => None, 34 | } 35 | } 36 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 37 | match v.untag_mut() { 38 | Value::Sequence(vec) => vec.get_mut(*self), 39 | Value::Mapping(vec) => vec.get_mut(Value::Number((*self).into())), 40 | _ => None, 41 | } 42 | } 43 | fn index_or_insert<'v>(&self, mut v: &'v mut Value) -> &'v mut Value { 44 | loop { 45 | match v { 46 | Value::Sequence(vec) => { 47 | let len = vec.len(); 48 | return vec.get_mut(*self).unwrap_or_else(|| { 49 | panic!( 50 | "cannot access index {} of YAML sequence of length {}", 51 | self, len 52 | ) 53 | }); 54 | } 55 | Value::Mapping(map) => { 56 | let n = Value::Number((*self).into()); 57 | return map.entry(n).or_insert(Value::Null); 58 | } 59 | Value::Tagged(tagged) => v = &mut tagged.value, 60 | _ => panic!("cannot access index {} of YAML {}", self, Type(v)), 61 | } 62 | } 63 | } 64 | } 65 | 66 | fn index_into_mapping<'v, I>(index: &I, v: &'v Value) -> Option<&'v Value> 67 | where 68 | I: ?Sized + mapping::Index, 69 | { 70 | match v.untag_ref() { 71 | Value::Mapping(map) => map.get(index), 72 | _ => None, 73 | } 74 | } 75 | 76 | fn index_into_mut_mapping<'v, I>(index: &I, v: &'v mut Value) -> Option<&'v mut Value> 77 | where 78 | I: ?Sized + mapping::Index, 79 | { 80 | match v.untag_mut() { 81 | Value::Mapping(map) => map.get_mut(index), 82 | _ => None, 83 | } 84 | } 85 | 86 | fn index_or_insert_mapping<'v, I>(index: &I, mut v: &'v mut Value) -> &'v mut Value 87 | where 88 | I: ?Sized + mapping::Index + ToOwned + Debug, 89 | Value: From, 90 | { 91 | if let Value::Null = *v { 92 | *v = Value::Mapping(Mapping::new()); 93 | return match v { 94 | Value::Mapping(map) => match map.entry(index.to_owned().into()) { 95 | Entry::Vacant(entry) => entry.insert(Value::Null), 96 | Entry::Occupied(_) => unreachable!(), 97 | }, 98 | _ => unreachable!(), 99 | }; 100 | } 101 | loop { 102 | match v { 103 | Value::Mapping(map) => { 104 | return map.entry(index.to_owned().into()).or_insert(Value::Null); 105 | } 106 | Value::Tagged(tagged) => v = &mut tagged.value, 107 | _ => panic!("cannot access key {:?} in YAML {}", index, Type(v)), 108 | } 109 | } 110 | } 111 | 112 | impl Index for Value { 113 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 114 | index_into_mapping(self, v) 115 | } 116 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 117 | index_into_mut_mapping(self, v) 118 | } 119 | fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value { 120 | index_or_insert_mapping(self, v) 121 | } 122 | } 123 | 124 | impl Index for str { 125 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 126 | index_into_mapping(self, v) 127 | } 128 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 129 | index_into_mut_mapping(self, v) 130 | } 131 | fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value { 132 | index_or_insert_mapping(self, v) 133 | } 134 | } 135 | 136 | impl Index for String { 137 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 138 | self.as_str().index_into(v) 139 | } 140 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 141 | self.as_str().index_into_mut(v) 142 | } 143 | fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value { 144 | self.as_str().index_or_insert(v) 145 | } 146 | } 147 | 148 | impl<'a, T> Index for &'a T 149 | where 150 | T: ?Sized + Index, 151 | { 152 | fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> { 153 | (**self).index_into(v) 154 | } 155 | fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> { 156 | (**self).index_into_mut(v) 157 | } 158 | fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value { 159 | (**self).index_or_insert(v) 160 | } 161 | } 162 | 163 | /// Used in panic messages. 164 | struct Type<'a>(&'a Value); 165 | 166 | impl<'a> fmt::Display for Type<'a> { 167 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 168 | match self.0 { 169 | Value::Null => formatter.write_str("null"), 170 | Value::Bool(_) => formatter.write_str("boolean"), 171 | Value::Number(_) => formatter.write_str("number"), 172 | Value::String(_) => formatter.write_str("string"), 173 | Value::Sequence(_) => formatter.write_str("sequence"), 174 | Value::Mapping(_) => formatter.write_str("mapping"), 175 | Value::Tagged(_) => unreachable!(), 176 | } 177 | } 178 | } 179 | 180 | // The usual semantics of Index is to panic on invalid indexing. 181 | // 182 | // That said, the usual semantics are for things like `Vec` and `BTreeMap` which 183 | // have different use cases than Value. If you are working with a Vec, you know 184 | // that you are working with a Vec and you can get the len of the Vec and make 185 | // sure your indices are within bounds. The Value use cases are more 186 | // loosey-goosey. You got some YAML from an endpoint and you want to pull values 187 | // out of it. Outside of this Index impl, you already have the option of using 188 | // `value.as_sequence()` and working with the Vec directly, or matching on 189 | // `Value::Sequence` and getting the Vec directly. The Index impl means you can 190 | // skip that and index directly into the thing using a concise syntax. You don't 191 | // have to check the type, you don't have to check the len, it is all about what 192 | // you expect the Value to look like. 193 | // 194 | // Basically the use cases that would be well served by panicking here are 195 | // better served by using one of the other approaches: `get` and `get_mut`, 196 | // `as_sequence`, or match. The value of this impl is that it adds a way of 197 | // working with Value that is not well served by the existing approaches: 198 | // concise and careless and sometimes that is exactly what you want. 199 | impl ops::Index for Value 200 | where 201 | I: Index, 202 | { 203 | type Output = Value; 204 | 205 | /// Index into a `serde_norway::Value` using the syntax `value[0]` or 206 | /// `value["k"]`. 207 | /// 208 | /// Returns `Value::Null` if the type of `self` does not match the type of 209 | /// the index, for example if the index is a string and `self` is a sequence 210 | /// or a number. Also returns `Value::Null` if the given key does not exist 211 | /// in the map or the given index is not within the bounds of the sequence. 212 | /// 213 | /// For retrieving deeply nested values, you should have a look at the 214 | /// `Value::pointer` method. 215 | /// 216 | /// # Examples 217 | /// 218 | /// ``` 219 | /// # use serde_norway::Value; 220 | /// # 221 | /// # fn main() -> serde_norway::Result<()> { 222 | /// let data: serde_norway::Value = serde_norway::from_str(r#"{ x: { y: [z, zz] } }"#)?; 223 | /// 224 | /// assert_eq!(data["x"]["y"], serde_norway::from_str::(r#"["z", "zz"]"#).unwrap()); 225 | /// assert_eq!(data["x"]["y"][0], serde_norway::from_str::(r#""z""#).unwrap()); 226 | /// 227 | /// assert_eq!(data["a"], serde_norway::from_str::(r#"null"#).unwrap()); // returns null for undefined values 228 | /// assert_eq!(data["a"]["b"], serde_norway::from_str::(r#"null"#).unwrap()); // does not panic 229 | /// # Ok(()) 230 | /// # } 231 | /// ``` 232 | fn index(&self, index: I) -> &Value { 233 | static NULL: Value = Value::Null; 234 | index.index_into(self).unwrap_or(&NULL) 235 | } 236 | } 237 | 238 | impl ops::IndexMut for Value 239 | where 240 | I: Index, 241 | { 242 | /// Write into a `serde_norway::Value` using the syntax `value[0] = ...` or 243 | /// `value["k"] = ...`. 244 | /// 245 | /// If the index is a number, the value must be a sequence of length bigger 246 | /// than the index. Indexing into a value that is not a sequence or a 247 | /// sequence that is too small will panic. 248 | /// 249 | /// If the index is a string, the value must be an object or null which is 250 | /// treated like an empty object. If the key is not already present in the 251 | /// object, it will be inserted with a value of null. Indexing into a value 252 | /// that is neither an object nor null will panic. 253 | /// 254 | /// # Examples 255 | /// 256 | /// ``` 257 | /// # fn main() -> serde_norway::Result<()> { 258 | /// let mut data: serde_norway::Value = serde_norway::from_str(r#"{x: 0}"#)?; 259 | /// 260 | /// // replace an existing key 261 | /// data["x"] = serde_norway::from_str(r#"1"#)?; 262 | /// 263 | /// // insert a new key 264 | /// data["y"] = serde_norway::from_str(r#"[false, false, false]"#)?; 265 | /// 266 | /// // replace a value in a sequence 267 | /// data["y"][0] = serde_norway::from_str(r#"true"#)?; 268 | /// 269 | /// // inserted a deeply nested key 270 | /// data["a"]["b"]["c"]["d"] = serde_norway::from_str(r#"true"#)?; 271 | /// 272 | /// println!("{:?}", data); 273 | /// # Ok(()) 274 | /// # } 275 | /// ``` 276 | fn index_mut(&mut self, index: I) -> &mut Value { 277 | index.index_or_insert(self) 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /tests/test_error.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::zero_sized_map_values)] 2 | 3 | use indoc::indoc; 4 | use serde::de::Deserialize; 5 | #[cfg(not(miri))] 6 | use serde::de::{SeqAccess, Visitor}; 7 | use serde_derive::{Deserialize, Serialize}; 8 | use serde_norway::value::{Tag, TaggedValue}; 9 | use serde_norway::{Deserializer, Value}; 10 | #[cfg(not(miri))] 11 | use std::collections::BTreeMap; 12 | #[cfg(not(miri))] 13 | use std::fmt; 14 | use std::fmt::Debug; 15 | 16 | fn test_error<'de, T>(yaml: &'de str, expected: &str) 17 | where 18 | T: Deserialize<'de> + Debug, 19 | { 20 | let result = serde_norway::from_str::(yaml); 21 | assert_eq!(expected, result.unwrap_err().to_string()); 22 | 23 | let mut deserializer = Deserializer::from_str(yaml); 24 | if let Some(first_document) = deserializer.next() { 25 | if deserializer.next().is_none() { 26 | let result = T::deserialize(first_document); 27 | assert_eq!(expected, result.unwrap_err().to_string()); 28 | } 29 | } 30 | } 31 | 32 | #[test] 33 | fn test_scan_error() { 34 | let yaml = ">\n@"; 35 | let expected = "found character that cannot start any token at line 2 column 1, while scanning for the next token"; 36 | test_error::(yaml, expected); 37 | } 38 | 39 | #[test] 40 | fn test_incorrect_type() { 41 | let yaml = indoc! {" 42 | --- 43 | str 44 | "}; 45 | let expected = "invalid type: string \"str\", expected i16 at line 2 column 1"; 46 | test_error::(yaml, expected); 47 | } 48 | 49 | #[test] 50 | fn test_incorrect_nested_type() { 51 | #[derive(Deserialize, Debug)] 52 | pub struct A { 53 | #[allow(dead_code)] 54 | pub b: Vec, 55 | } 56 | #[derive(Deserialize, Debug)] 57 | pub enum B { 58 | C(#[allow(dead_code)] C), 59 | } 60 | #[derive(Deserialize, Debug)] 61 | pub struct C { 62 | #[allow(dead_code)] 63 | pub d: bool, 64 | } 65 | let yaml = indoc! {" 66 | b: 67 | - !C 68 | d: fase 69 | "}; 70 | let expected = "b[0].d: invalid type: string \"fase\", expected a boolean at line 3 column 8"; 71 | test_error::(yaml, expected); 72 | } 73 | 74 | #[test] 75 | fn test_empty() { 76 | let expected = "EOF while parsing a value"; 77 | test_error::("", expected); 78 | } 79 | 80 | #[test] 81 | fn test_missing_field() { 82 | #[derive(Deserialize, Debug)] 83 | pub struct Basic { 84 | #[allow(dead_code)] 85 | pub v: bool, 86 | #[allow(dead_code)] 87 | pub w: bool, 88 | } 89 | let yaml = indoc! {" 90 | --- 91 | v: true 92 | "}; 93 | let expected = "missing field `w` at line 2 column 1"; 94 | test_error::(yaml, expected); 95 | } 96 | 97 | #[test] 98 | fn test_unknown_anchor() { 99 | let yaml = indoc! {" 100 | --- 101 | *some 102 | "}; 103 | let expected = "unknown anchor at line 2 column 1"; 104 | test_error::(yaml, expected); 105 | } 106 | 107 | #[test] 108 | fn test_ignored_unknown_anchor() { 109 | #[derive(Deserialize, Debug)] 110 | pub struct Wrapper { 111 | #[allow(dead_code)] 112 | pub c: (), 113 | } 114 | let yaml = indoc! {" 115 | b: [*a] 116 | c: ~ 117 | "}; 118 | let expected = "unknown anchor at line 1 column 5"; 119 | test_error::(yaml, expected); 120 | } 121 | 122 | #[test] 123 | fn test_bytes() { 124 | let expected = "serialization and deserialization of bytes in YAML is not implemented"; 125 | test_error::<&[u8]>("...", expected); 126 | } 127 | 128 | #[test] 129 | fn test_two_documents() { 130 | let yaml = indoc! {" 131 | --- 132 | 0 133 | --- 134 | 1 135 | "}; 136 | let expected = "deserializing from YAML containing more than one document is not supported"; 137 | test_error::(yaml, expected); 138 | } 139 | 140 | #[test] 141 | fn test_second_document_syntax_error() { 142 | let yaml = indoc! {" 143 | --- 144 | 0 145 | --- 146 | ] 147 | "}; 148 | 149 | let mut de = Deserializer::from_str(yaml); 150 | let first_doc = de.next().unwrap(); 151 | let result = ::deserialize(first_doc); 152 | assert_eq!(0, result.unwrap()); 153 | 154 | let second_doc = de.next().unwrap(); 155 | let result = ::deserialize(second_doc); 156 | let expected = 157 | "did not find expected node content at line 4 column 1, while parsing a block node"; 158 | assert_eq!(expected, result.unwrap_err().to_string()); 159 | } 160 | 161 | #[test] 162 | fn test_missing_enum_tag() { 163 | #[derive(Deserialize, Debug)] 164 | pub enum E { 165 | V(#[allow(dead_code)] usize), 166 | } 167 | let yaml = indoc! {r#" 168 | "V": 16 169 | "other": 32 170 | "#}; 171 | let expected = "invalid type: map, expected a YAML tag starting with '!'"; 172 | test_error::(yaml, expected); 173 | } 174 | 175 | #[test] 176 | fn test_serialize_nested_enum() { 177 | #[derive(Serialize, Debug)] 178 | pub enum Outer { 179 | Inner(Inner), 180 | } 181 | #[derive(Serialize, Debug)] 182 | pub enum Inner { 183 | Newtype(usize), 184 | Tuple(usize, usize), 185 | Struct { x: usize }, 186 | } 187 | 188 | let expected = "serializing nested enums in YAML is not supported yet"; 189 | 190 | let e = Outer::Inner(Inner::Newtype(0)); 191 | let error = serde_norway::to_string(&e).unwrap_err(); 192 | assert_eq!(error.to_string(), expected); 193 | 194 | let e = Outer::Inner(Inner::Tuple(0, 0)); 195 | let error = serde_norway::to_string(&e).unwrap_err(); 196 | assert_eq!(error.to_string(), expected); 197 | 198 | let e = Outer::Inner(Inner::Struct { x: 0 }); 199 | let error = serde_norway::to_string(&e).unwrap_err(); 200 | assert_eq!(error.to_string(), expected); 201 | 202 | let e = Value::Tagged(Box::new(TaggedValue { 203 | tag: Tag::new("Outer"), 204 | value: Value::Tagged(Box::new(TaggedValue { 205 | tag: Tag::new("Inner"), 206 | value: Value::Null, 207 | })), 208 | })); 209 | let error = serde_norway::to_string(&e).unwrap_err(); 210 | assert_eq!(error.to_string(), expected); 211 | } 212 | 213 | #[test] 214 | fn test_deserialize_nested_enum() { 215 | #[derive(Deserialize, Debug)] 216 | pub enum Outer { 217 | Inner(#[allow(dead_code)] Inner), 218 | } 219 | #[derive(Deserialize, Debug)] 220 | pub enum Inner { 221 | Variant(#[allow(dead_code)] Vec), 222 | } 223 | 224 | let yaml = indoc! {" 225 | --- 226 | !Inner [] 227 | "}; 228 | let expected = "deserializing nested enum in Outer::Inner from YAML is not supported yet at line 2 column 1"; 229 | test_error::(yaml, expected); 230 | 231 | let yaml = indoc! {" 232 | --- 233 | !Variant [] 234 | "}; 235 | let expected = "unknown variant `Variant`, expected `Inner`"; 236 | test_error::(yaml, expected); 237 | 238 | let yaml = indoc! {" 239 | --- 240 | !Inner !Variant [] 241 | "}; 242 | let expected = "deserializing nested enum in Outer::Inner from YAML is not supported yet at line 2 column 1"; 243 | test_error::(yaml, expected); 244 | } 245 | 246 | #[test] 247 | fn test_variant_not_a_seq() { 248 | #[derive(Deserialize, Debug)] 249 | pub enum E { 250 | V(#[allow(dead_code)] usize), 251 | } 252 | let yaml = indoc! {" 253 | --- 254 | !V 255 | value: 0 256 | "}; 257 | let expected = "invalid type: map, expected usize at line 2 column 1"; 258 | test_error::(yaml, expected); 259 | } 260 | 261 | #[test] 262 | fn test_struct_from_sequence() { 263 | #[derive(Deserialize, Debug)] 264 | pub struct Struct { 265 | #[allow(dead_code)] 266 | pub x: usize, 267 | #[allow(dead_code)] 268 | pub y: usize, 269 | } 270 | let yaml = indoc! {" 271 | [0, 0] 272 | "}; 273 | let expected = "invalid type: sequence, expected struct Struct"; 274 | test_error::(yaml, expected); 275 | } 276 | 277 | #[test] 278 | fn test_bad_bool() { 279 | let yaml = indoc! {" 280 | --- 281 | !!bool str 282 | "}; 283 | let expected = "invalid value: string \"str\", expected a boolean at line 2 column 1"; 284 | test_error::(yaml, expected); 285 | } 286 | 287 | #[test] 288 | fn test_bad_int() { 289 | let yaml = indoc! {" 290 | --- 291 | !!int str 292 | "}; 293 | let expected = "invalid value: string \"str\", expected an integer at line 2 column 1"; 294 | test_error::(yaml, expected); 295 | } 296 | 297 | #[test] 298 | fn test_bad_float() { 299 | let yaml = indoc! {" 300 | --- 301 | !!float str 302 | "}; 303 | let expected = "invalid value: string \"str\", expected a float at line 2 column 1"; 304 | test_error::(yaml, expected); 305 | } 306 | 307 | #[test] 308 | fn test_bad_null() { 309 | let yaml = indoc! {" 310 | --- 311 | !!null str 312 | "}; 313 | let expected = "invalid value: string \"str\", expected null at line 2 column 1"; 314 | test_error::<()>(yaml, expected); 315 | } 316 | 317 | #[test] 318 | fn test_short_tuple() { 319 | let yaml = indoc! {" 320 | --- 321 | [0, 0] 322 | "}; 323 | let expected = "invalid length 2, expected a tuple of size 3 at line 2 column 1"; 324 | test_error::<(u8, u8, u8)>(yaml, expected); 325 | } 326 | 327 | #[test] 328 | fn test_long_tuple() { 329 | let yaml = indoc! {" 330 | --- 331 | [0, 0, 0] 332 | "}; 333 | let expected = "invalid length 3, expected sequence of 2 elements at line 2 column 1"; 334 | test_error::<(u8, u8)>(yaml, expected); 335 | } 336 | 337 | #[test] 338 | fn test_invalid_scalar_type() { 339 | #[derive(Deserialize, Debug)] 340 | pub struct S { 341 | #[allow(dead_code)] 342 | pub x: [i32; 1], 343 | } 344 | 345 | let yaml = "x: ''\n"; 346 | let expected = "x: invalid type: string \"\", expected an array of length 1 at line 1 column 4"; 347 | test_error::(yaml, expected); 348 | } 349 | 350 | #[cfg(not(miri))] 351 | #[test] 352 | fn test_infinite_recursion_objects() { 353 | #[derive(Deserialize, Debug)] 354 | pub struct S { 355 | #[allow(dead_code)] 356 | pub x: Option>, 357 | } 358 | 359 | let yaml = "&a {'x': *a}"; 360 | let expected = "recursion limit exceeded"; 361 | test_error::(yaml, expected); 362 | } 363 | 364 | #[cfg(not(miri))] 365 | #[test] 366 | fn test_infinite_recursion_arrays() { 367 | #[derive(Deserialize, Debug)] 368 | pub struct S( 369 | #[allow(dead_code)] pub usize, 370 | #[allow(dead_code)] pub Option>, 371 | ); 372 | 373 | let yaml = "&a [0, *a]"; 374 | let expected = "recursion limit exceeded"; 375 | test_error::(yaml, expected); 376 | } 377 | 378 | #[cfg(not(miri))] 379 | #[test] 380 | fn test_infinite_recursion_newtype() { 381 | #[derive(Deserialize, Debug)] 382 | pub struct S(#[allow(dead_code)] pub Option>); 383 | 384 | let yaml = "&a [*a]"; 385 | let expected = "recursion limit exceeded"; 386 | test_error::(yaml, expected); 387 | } 388 | 389 | #[cfg(not(miri))] 390 | #[test] 391 | fn test_finite_recursion_objects() { 392 | #[derive(Deserialize, Debug)] 393 | pub struct S { 394 | #[allow(dead_code)] 395 | pub x: Option>, 396 | } 397 | 398 | let yaml = "{'x':".repeat(1_000) + &"}".repeat(1_000); 399 | let expected = "recursion limit exceeded at line 1 column 641"; 400 | test_error::(&yaml, expected); 401 | } 402 | 403 | #[cfg(not(miri))] 404 | #[test] 405 | fn test_finite_recursion_arrays() { 406 | #[derive(Deserialize, Debug)] 407 | pub struct S( 408 | #[allow(dead_code)] pub usize, 409 | #[allow(dead_code)] pub Option>, 410 | ); 411 | 412 | let yaml = "[0, ".repeat(1_000) + &"]".repeat(1_000); 413 | let expected = "recursion limit exceeded at line 1 column 513"; 414 | test_error::(&yaml, expected); 415 | } 416 | 417 | #[cfg(not(miri))] 418 | #[test] 419 | fn test_billion_laughs() { 420 | #[derive(Debug)] 421 | struct X; 422 | 423 | impl<'de> Visitor<'de> for X { 424 | type Value = X; 425 | 426 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 427 | formatter.write_str("exponential blowup") 428 | } 429 | 430 | fn visit_unit(self) -> Result { 431 | Ok(X) 432 | } 433 | 434 | fn visit_seq(self, mut seq: S) -> Result 435 | where 436 | S: SeqAccess<'de>, 437 | { 438 | while let Some(X) = seq.next_element()? {} 439 | Ok(X) 440 | } 441 | } 442 | 443 | impl<'de> Deserialize<'de> for X { 444 | fn deserialize(deserializer: D) -> Result 445 | where 446 | D: serde::Deserializer<'de>, 447 | { 448 | deserializer.deserialize_any(X) 449 | } 450 | } 451 | 452 | let yaml = indoc! {" 453 | a: &a ~ 454 | b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a] 455 | c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b] 456 | d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c] 457 | e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d] 458 | f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e] 459 | g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f] 460 | h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g] 461 | i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h] 462 | "}; 463 | let expected = "repetition limit exceeded"; 464 | test_error::>(yaml, expected); 465 | } 466 | 467 | #[test] 468 | fn test_duplicate_keys() { 469 | let yaml = indoc! {" 470 | --- 471 | thing: true 472 | thing: false 473 | "}; 474 | let expected = "duplicate entry with key \"thing\" at line 2 column 1"; 475 | test_error::(yaml, expected); 476 | 477 | let yaml = indoc! {" 478 | --- 479 | null: true 480 | ~: false 481 | "}; 482 | let expected = "duplicate entry with null key at line 2 column 1"; 483 | test_error::(yaml, expected); 484 | 485 | let yaml = indoc! {" 486 | --- 487 | 99: true 488 | 99: false 489 | "}; 490 | let expected = "duplicate entry with key 99 at line 2 column 1"; 491 | test_error::(yaml, expected); 492 | 493 | let yaml = indoc! {" 494 | --- 495 | {}: true 496 | {}: false 497 | "}; 498 | let expected = "duplicate entry in YAML map at line 2 column 1"; 499 | test_error::(yaml, expected); 500 | } 501 | -------------------------------------------------------------------------------- /tests/test_serde.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::decimal_literal_representation, 3 | clippy::derive_partial_eq_without_eq, 4 | clippy::unreadable_literal, 5 | clippy::shadow_unrelated 6 | )] 7 | 8 | use indoc::indoc; 9 | use serde::ser::SerializeMap; 10 | use serde_derive::{Deserialize, Serialize}; 11 | use serde_norway::{Mapping, Number, Value}; 12 | use std::collections::BTreeMap; 13 | use std::fmt::Debug; 14 | use std::iter; 15 | 16 | fn test_serde(thing: &T, yaml: &str) 17 | where 18 | T: serde::Serialize + serde::de::DeserializeOwned + PartialEq + Debug, 19 | { 20 | let serialized = serde_norway::to_string(&thing).unwrap(); 21 | assert_eq!(yaml, serialized); 22 | 23 | let value = serde_norway::to_value(thing).unwrap(); 24 | let serialized = serde_norway::to_string(&value).unwrap(); 25 | assert_eq!(yaml, serialized); 26 | 27 | let deserialized: T = serde_norway::from_str(yaml).unwrap(); 28 | assert_eq!(*thing, deserialized); 29 | 30 | let value: Value = serde_norway::from_str(yaml).unwrap(); 31 | let deserialized = T::deserialize(&value).unwrap(); 32 | assert_eq!(*thing, deserialized); 33 | 34 | let deserialized: T = serde_norway::from_value(value).unwrap(); 35 | assert_eq!(*thing, deserialized); 36 | 37 | serde_norway::from_str::(yaml).unwrap(); 38 | } 39 | 40 | #[test] 41 | fn test_default() { 42 | assert_eq!(Value::default(), Value::Null); 43 | } 44 | 45 | #[test] 46 | fn test_int() { 47 | let thing = 256; 48 | let yaml = indoc! {" 49 | 256 50 | "}; 51 | test_serde(&thing, yaml); 52 | } 53 | 54 | #[test] 55 | fn test_int_max_u64() { 56 | let thing = u64::MAX; 57 | let yaml = indoc! {" 58 | 18446744073709551615 59 | "}; 60 | test_serde(&thing, yaml); 61 | } 62 | 63 | #[test] 64 | fn test_int_min_i64() { 65 | let thing = i64::MIN; 66 | let yaml = indoc! {" 67 | -9223372036854775808 68 | "}; 69 | test_serde(&thing, yaml); 70 | } 71 | 72 | #[test] 73 | fn test_int_max_i64() { 74 | let thing = i64::MAX; 75 | let yaml = indoc! {" 76 | 9223372036854775807 77 | "}; 78 | test_serde(&thing, yaml); 79 | } 80 | 81 | #[test] 82 | fn test_i128_small() { 83 | let thing: i128 = -256; 84 | let yaml = indoc! {" 85 | -256 86 | "}; 87 | test_serde(&thing, yaml); 88 | } 89 | 90 | #[test] 91 | fn test_u128_small() { 92 | let thing: u128 = 256; 93 | let yaml = indoc! {" 94 | 256 95 | "}; 96 | test_serde(&thing, yaml); 97 | } 98 | 99 | #[test] 100 | fn test_float() { 101 | let thing = 25.6; 102 | let yaml = indoc! {" 103 | 25.6 104 | "}; 105 | test_serde(&thing, yaml); 106 | 107 | let thing = 25.; 108 | let yaml = indoc! {" 109 | 25.0 110 | "}; 111 | test_serde(&thing, yaml); 112 | 113 | let thing = f64::INFINITY; 114 | let yaml = indoc! {" 115 | .inf 116 | "}; 117 | test_serde(&thing, yaml); 118 | 119 | let thing = f64::NEG_INFINITY; 120 | let yaml = indoc! {" 121 | -.inf 122 | "}; 123 | test_serde(&thing, yaml); 124 | 125 | let float: f64 = serde_norway::from_str(indoc! {" 126 | .nan 127 | "}) 128 | .unwrap(); 129 | assert!(float.is_nan()); 130 | } 131 | 132 | #[test] 133 | fn test_float32() { 134 | let thing: f32 = 25.5; 135 | let yaml = indoc! {" 136 | 25.5 137 | "}; 138 | test_serde(&thing, yaml); 139 | 140 | let thing = f32::INFINITY; 141 | let yaml = indoc! {" 142 | .inf 143 | "}; 144 | test_serde(&thing, yaml); 145 | 146 | let thing = f32::NEG_INFINITY; 147 | let yaml = indoc! {" 148 | -.inf 149 | "}; 150 | test_serde(&thing, yaml); 151 | 152 | let single_float: f32 = serde_norway::from_str(indoc! {" 153 | .nan 154 | "}) 155 | .unwrap(); 156 | assert!(single_float.is_nan()); 157 | } 158 | 159 | #[test] 160 | fn test_char() { 161 | let ch = '.'; 162 | let yaml = indoc! {" 163 | '.' 164 | "}; 165 | assert_eq!(yaml, serde_norway::to_string(&ch).unwrap()); 166 | 167 | let ch = '#'; 168 | let yaml = indoc! {" 169 | '#' 170 | "}; 171 | assert_eq!(yaml, serde_norway::to_string(&ch).unwrap()); 172 | 173 | let ch = '-'; 174 | let yaml = indoc! {" 175 | '-' 176 | "}; 177 | assert_eq!(yaml, serde_norway::to_string(&ch).unwrap()); 178 | } 179 | 180 | #[test] 181 | fn test_vec() { 182 | let thing = vec![1, 2, 3]; 183 | let yaml = indoc! {" 184 | - 1 185 | - 2 186 | - 3 187 | "}; 188 | test_serde(&thing, yaml); 189 | } 190 | 191 | #[test] 192 | fn test_map() { 193 | let mut thing = BTreeMap::new(); 194 | thing.insert("x".to_owned(), 1); 195 | thing.insert("y".to_owned(), 2); 196 | let yaml = indoc! {" 197 | x: 1 198 | y: 2 199 | "}; 200 | test_serde(&thing, yaml); 201 | } 202 | 203 | #[test] 204 | fn test_map_key_value() { 205 | struct Map; 206 | 207 | impl serde::Serialize for Map { 208 | fn serialize(&self, serializer: S) -> Result 209 | where 210 | S: serde::Serializer, 211 | { 212 | // Test maps which do not serialize using serialize_entry. 213 | let mut map = serializer.serialize_map(Some(1))?; 214 | map.serialize_key("k")?; 215 | map.serialize_value("v")?; 216 | map.end() 217 | } 218 | } 219 | 220 | let yaml = indoc! {" 221 | k: v 222 | "}; 223 | assert_eq!(yaml, serde_norway::to_string(&Map).unwrap()); 224 | } 225 | 226 | #[test] 227 | fn test_basic_struct() { 228 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 229 | struct Basic { 230 | x: isize, 231 | y: String, 232 | z: bool, 233 | } 234 | let thing = Basic { 235 | x: -4, 236 | y: "hi\tquoted".to_owned(), 237 | z: true, 238 | }; 239 | let yaml = indoc! {r#" 240 | x: -4 241 | y: "hi\tquoted" 242 | z: true 243 | "#}; 244 | test_serde(&thing, yaml); 245 | } 246 | 247 | #[test] 248 | fn test_string_escapes() { 249 | let yaml = indoc! {" 250 | ascii 251 | "}; 252 | test_serde(&"ascii".to_owned(), yaml); 253 | 254 | let yaml = indoc! {r#" 255 | "\0\a\b\t\n\v\f\r\e\"\\\N\L\P" 256 | "#}; 257 | test_serde( 258 | &"\0\u{7}\u{8}\t\n\u{b}\u{c}\r\u{1b}\"\\\u{85}\u{2028}\u{2029}".to_owned(), 259 | yaml, 260 | ); 261 | 262 | let yaml = indoc! {r#" 263 | "\x1F\uFEFF" 264 | "#}; 265 | test_serde(&"\u{1f}\u{feff}".to_owned(), yaml); 266 | 267 | let yaml = indoc! {" 268 | 🎉 269 | "}; 270 | test_serde(&"\u{1f389}".to_owned(), yaml); 271 | } 272 | 273 | #[test] 274 | fn test_multiline_string() { 275 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 276 | struct Struct { 277 | trailing_newline: String, 278 | no_trailing_newline: String, 279 | } 280 | let thing = Struct { 281 | trailing_newline: "aaa\nbbb\n".to_owned(), 282 | no_trailing_newline: "aaa\nbbb".to_owned(), 283 | }; 284 | let yaml = indoc! {" 285 | trailing_newline: | 286 | aaa 287 | bbb 288 | no_trailing_newline: |- 289 | aaa 290 | bbb 291 | "}; 292 | test_serde(&thing, yaml); 293 | } 294 | 295 | #[test] 296 | fn test_strings_needing_quote() { 297 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 298 | struct Struct { 299 | boolean: String, 300 | integer: String, 301 | void: String, 302 | leading_zeros: String, 303 | } 304 | let thing = Struct { 305 | boolean: "true".to_owned(), 306 | integer: "1".to_owned(), 307 | void: "null".to_owned(), 308 | leading_zeros: "007".to_owned(), 309 | }; 310 | let yaml = indoc! {" 311 | boolean: 'true' 312 | integer: '1' 313 | void: 'null' 314 | leading_zeros: '007' 315 | "}; 316 | test_serde(&thing, yaml); 317 | } 318 | 319 | #[test] 320 | fn test_nested_vec() { 321 | let thing = vec![vec![1, 2, 3], vec![4, 5, 6]]; 322 | let yaml = indoc! {" 323 | - - 1 324 | - 2 325 | - 3 326 | - - 4 327 | - 5 328 | - 6 329 | "}; 330 | test_serde(&thing, yaml); 331 | } 332 | 333 | #[test] 334 | fn test_nested_struct() { 335 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 336 | struct Outer { 337 | inner: Inner, 338 | } 339 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 340 | struct Inner { 341 | v: u16, 342 | } 343 | let thing = Outer { 344 | inner: Inner { v: 512 }, 345 | }; 346 | let yaml = indoc! {" 347 | inner: 348 | v: 512 349 | "}; 350 | test_serde(&thing, yaml); 351 | } 352 | 353 | #[test] 354 | fn test_nested_enum() { 355 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 356 | enum Outer { 357 | Inner(Inner), 358 | } 359 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 360 | enum Inner { 361 | Unit, 362 | } 363 | let thing = Outer::Inner(Inner::Unit); 364 | let yaml = indoc! {" 365 | !Inner Unit 366 | "}; 367 | test_serde(&thing, yaml); 368 | } 369 | 370 | #[test] 371 | fn test_option() { 372 | let thing = vec![Some(1), None, Some(3)]; 373 | let yaml = indoc! {" 374 | - 1 375 | - null 376 | - 3 377 | "}; 378 | test_serde(&thing, yaml); 379 | } 380 | 381 | #[test] 382 | fn test_unit() { 383 | let thing = vec![(), ()]; 384 | let yaml = indoc! {" 385 | - null 386 | - null 387 | "}; 388 | test_serde(&thing, yaml); 389 | } 390 | 391 | #[test] 392 | fn test_unit_struct() { 393 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 394 | struct Foo; 395 | let thing = Foo; 396 | let yaml = indoc! {" 397 | null 398 | "}; 399 | test_serde(&thing, yaml); 400 | } 401 | 402 | #[test] 403 | fn test_unit_variant() { 404 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 405 | enum Variant { 406 | First, 407 | Second, 408 | } 409 | let thing = Variant::First; 410 | let yaml = indoc! {" 411 | First 412 | "}; 413 | test_serde(&thing, yaml); 414 | } 415 | 416 | #[test] 417 | fn test_newtype_struct() { 418 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 419 | struct OriginalType { 420 | v: u16, 421 | } 422 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 423 | struct NewType(OriginalType); 424 | let thing = NewType(OriginalType { v: 1 }); 425 | let yaml = indoc! {" 426 | v: 1 427 | "}; 428 | test_serde(&thing, yaml); 429 | } 430 | 431 | #[test] 432 | fn test_newtype_variant() { 433 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 434 | enum Variant { 435 | Size(usize), 436 | } 437 | let thing = Variant::Size(127); 438 | let yaml = indoc! {" 439 | !Size 127 440 | "}; 441 | test_serde(&thing, yaml); 442 | } 443 | 444 | #[test] 445 | fn test_tuple_variant() { 446 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 447 | enum Variant { 448 | Rgb(u8, u8, u8), 449 | } 450 | let thing = Variant::Rgb(32, 64, 96); 451 | let yaml = indoc! {" 452 | !Rgb 453 | - 32 454 | - 64 455 | - 96 456 | "}; 457 | test_serde(&thing, yaml); 458 | } 459 | 460 | #[test] 461 | fn test_struct_variant() { 462 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 463 | enum Variant { 464 | Color { r: u8, g: u8, b: u8 }, 465 | } 466 | let thing = Variant::Color { 467 | r: 32, 468 | g: 64, 469 | b: 96, 470 | }; 471 | let yaml = indoc! {" 472 | !Color 473 | r: 32 474 | g: 64 475 | b: 96 476 | "}; 477 | test_serde(&thing, yaml); 478 | } 479 | 480 | #[test] 481 | fn test_tagged_map_value() { 482 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 483 | struct Bindings { 484 | profile: Profile, 485 | } 486 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 487 | enum Profile { 488 | ClassValidator { class_name: String }, 489 | } 490 | let thing = Bindings { 491 | profile: Profile::ClassValidator { 492 | class_name: "ApplicationConfig".to_owned(), 493 | }, 494 | }; 495 | let yaml = indoc! {" 496 | profile: !ClassValidator 497 | class_name: ApplicationConfig 498 | "}; 499 | test_serde(&thing, yaml); 500 | } 501 | 502 | #[test] 503 | fn test_value() { 504 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 505 | pub struct GenericInstructions { 506 | #[serde(rename = "type")] 507 | pub typ: String, 508 | pub config: Value, 509 | } 510 | let thing = GenericInstructions { 511 | typ: "primary".to_string(), 512 | config: Value::Sequence(vec![ 513 | Value::Null, 514 | Value::Bool(true), 515 | Value::Number(Number::from(65535)), 516 | Value::Number(Number::from(0.54321)), 517 | Value::String("s".into()), 518 | Value::Mapping(Mapping::new()), 519 | ]), 520 | }; 521 | let yaml = indoc! {" 522 | type: primary 523 | config: 524 | - null 525 | - true 526 | - 65535 527 | - 0.54321 528 | - s 529 | - {} 530 | "}; 531 | test_serde(&thing, yaml); 532 | } 533 | 534 | #[test] 535 | fn test_mapping() { 536 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 537 | struct Data { 538 | pub substructure: Mapping, 539 | } 540 | 541 | let mut thing = Data { 542 | substructure: Mapping::new(), 543 | }; 544 | thing.substructure.insert( 545 | Value::String("a".to_owned()), 546 | Value::String("foo".to_owned()), 547 | ); 548 | thing.substructure.insert( 549 | Value::String("b".to_owned()), 550 | Value::String("bar".to_owned()), 551 | ); 552 | 553 | let yaml = indoc! {" 554 | substructure: 555 | a: foo 556 | b: bar 557 | "}; 558 | 559 | test_serde(&thing, yaml); 560 | } 561 | 562 | #[test] 563 | fn test_long_string() { 564 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 565 | struct Data { 566 | pub string: String, 567 | } 568 | 569 | let thing = Data { 570 | string: iter::repeat(["word", " "]).flatten().take(69).collect(), 571 | }; 572 | 573 | let yaml = indoc! {" 574 | string: word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word 575 | "}; 576 | 577 | test_serde(&thing, yaml); 578 | } 579 | -------------------------------------------------------------------------------- /src/value/tagged.rs: -------------------------------------------------------------------------------- 1 | use crate::value::de::{MapDeserializer, MapRefDeserializer, SeqDeserializer, SeqRefDeserializer}; 2 | use crate::value::Value; 3 | use crate::Error; 4 | use serde::de::value::{BorrowedStrDeserializer, StrDeserializer}; 5 | use serde::de::{ 6 | Deserialize, DeserializeSeed, Deserializer, EnumAccess, Error as _, VariantAccess, Visitor, 7 | }; 8 | use serde::forward_to_deserialize_any; 9 | use serde::ser::{Serialize, SerializeMap, Serializer}; 10 | use std::cmp::Ordering; 11 | use std::fmt::{self, Debug, Display}; 12 | use std::hash::{Hash, Hasher}; 13 | use std::mem; 14 | 15 | /// A representation of YAML's `!Tag` syntax, used for enums. 16 | /// 17 | /// Refer to the example code on [`TaggedValue`] for an example of deserializing 18 | /// tagged values. 19 | #[derive(Clone)] 20 | pub struct Tag { 21 | pub(crate) string: String, 22 | } 23 | 24 | /// A `Tag` + `Value` representing a tagged YAML scalar, sequence, or mapping. 25 | /// 26 | /// ``` 27 | /// use serde_norway::value::TaggedValue; 28 | /// use std::collections::BTreeMap; 29 | /// 30 | /// let yaml = " 31 | /// scalar: !Thing x 32 | /// sequence_flow: !Thing [first] 33 | /// sequence_block: !Thing 34 | /// - first 35 | /// mapping_flow: !Thing {k: v} 36 | /// mapping_block: !Thing 37 | /// k: v 38 | /// "; 39 | /// 40 | /// let data: BTreeMap = serde_norway::from_str(yaml).unwrap(); 41 | /// assert!(data["scalar"].tag == "Thing"); 42 | /// assert!(data["sequence_flow"].tag == "Thing"); 43 | /// assert!(data["sequence_block"].tag == "Thing"); 44 | /// assert!(data["mapping_flow"].tag == "Thing"); 45 | /// assert!(data["mapping_block"].tag == "Thing"); 46 | /// 47 | /// // The leading '!' in tags are not significant. The following is also true. 48 | /// assert!(data["scalar"].tag == "!Thing"); 49 | /// ``` 50 | #[derive(Clone, PartialEq, PartialOrd, Hash, Debug)] 51 | pub struct TaggedValue { 52 | #[allow(missing_docs)] 53 | pub tag: Tag, 54 | #[allow(missing_docs)] 55 | pub value: Value, 56 | } 57 | 58 | impl Tag { 59 | /// Create tag. 60 | /// 61 | /// The leading '!' is not significant. It may be provided, but does not 62 | /// have to be. The following are equivalent: 63 | /// 64 | /// ``` 65 | /// use serde_norway::value::Tag; 66 | /// 67 | /// assert_eq!(Tag::new("!Thing"), Tag::new("Thing")); 68 | /// 69 | /// let tag = Tag::new("Thing"); 70 | /// assert!(tag == "Thing"); 71 | /// assert!(tag == "!Thing"); 72 | /// assert!(tag.to_string() == "!Thing"); 73 | /// 74 | /// let tag = Tag::new("!Thing"); 75 | /// assert!(tag == "Thing"); 76 | /// assert!(tag == "!Thing"); 77 | /// assert!(tag.to_string() == "!Thing"); 78 | /// ``` 79 | /// 80 | /// Such a tag would serialize to `!Thing` in YAML regardless of whether a 81 | /// '!' was included in the call to `Tag::new`. 82 | /// 83 | /// # Panics 84 | /// 85 | /// Panics if `string.is_empty()`. There is no syntax in YAML for an empty 86 | /// tag. 87 | pub fn new(string: impl Into) -> Self { 88 | let tag: String = string.into(); 89 | assert!(!tag.is_empty(), "empty YAML tag is not allowed"); 90 | Tag { string: tag } 91 | } 92 | } 93 | 94 | impl Value { 95 | pub(crate) fn untag(self) -> Self { 96 | let mut cur = self; 97 | while let Value::Tagged(tagged) = cur { 98 | cur = tagged.value; 99 | } 100 | cur 101 | } 102 | 103 | pub(crate) fn untag_ref(&self) -> &Self { 104 | let mut cur = self; 105 | while let Value::Tagged(tagged) = cur { 106 | cur = &tagged.value; 107 | } 108 | cur 109 | } 110 | 111 | pub(crate) fn untag_mut(&mut self) -> &mut Self { 112 | let mut cur = self; 113 | while let Value::Tagged(tagged) = cur { 114 | cur = &mut tagged.value; 115 | } 116 | cur 117 | } 118 | } 119 | 120 | pub(crate) fn nobang(maybe_banged: &str) -> &str { 121 | match maybe_banged.strip_prefix('!') { 122 | Some("") | None => maybe_banged, 123 | Some(unbanged) => unbanged, 124 | } 125 | } 126 | 127 | impl Eq for Tag {} 128 | 129 | impl PartialEq for Tag { 130 | fn eq(&self, other: &Tag) -> bool { 131 | PartialEq::eq(nobang(&self.string), nobang(&other.string)) 132 | } 133 | } 134 | 135 | impl PartialEq for Tag 136 | where 137 | T: ?Sized + AsRef, 138 | { 139 | fn eq(&self, other: &T) -> bool { 140 | PartialEq::eq(nobang(&self.string), nobang(other.as_ref())) 141 | } 142 | } 143 | 144 | impl Ord for Tag { 145 | fn cmp(&self, other: &Self) -> Ordering { 146 | Ord::cmp(nobang(&self.string), nobang(&other.string)) 147 | } 148 | } 149 | 150 | impl PartialOrd for Tag { 151 | fn partial_cmp(&self, other: &Self) -> Option { 152 | Some(self.cmp(other)) 153 | } 154 | } 155 | 156 | impl Hash for Tag { 157 | fn hash(&self, hasher: &mut H) { 158 | nobang(&self.string).hash(hasher); 159 | } 160 | } 161 | 162 | impl Display for Tag { 163 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 164 | write!(formatter, "!{}", nobang(&self.string)) 165 | } 166 | } 167 | 168 | impl Debug for Tag { 169 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 170 | Display::fmt(self, formatter) 171 | } 172 | } 173 | 174 | impl Serialize for TaggedValue { 175 | fn serialize(&self, serializer: S) -> Result 176 | where 177 | S: Serializer, 178 | { 179 | struct SerializeTag<'a>(&'a Tag); 180 | 181 | impl<'a> Serialize for SerializeTag<'a> { 182 | fn serialize(&self, serializer: S) -> Result 183 | where 184 | S: Serializer, 185 | { 186 | serializer.collect_str(self.0) 187 | } 188 | } 189 | 190 | let mut map = serializer.serialize_map(Some(1))?; 191 | map.serialize_entry(&SerializeTag(&self.tag), &self.value)?; 192 | map.end() 193 | } 194 | } 195 | 196 | impl<'de> Deserialize<'de> for TaggedValue { 197 | fn deserialize(deserializer: D) -> Result 198 | where 199 | D: Deserializer<'de>, 200 | { 201 | struct TaggedValueVisitor; 202 | 203 | impl<'de> Visitor<'de> for TaggedValueVisitor { 204 | type Value = TaggedValue; 205 | 206 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 207 | formatter.write_str("a YAML value with a !Tag") 208 | } 209 | 210 | fn visit_enum(self, data: A) -> Result 211 | where 212 | A: EnumAccess<'de>, 213 | { 214 | let (tag, contents) = data.variant_seed(TagStringVisitor)?; 215 | let value = contents.newtype_variant()?; 216 | Ok(TaggedValue { tag, value }) 217 | } 218 | } 219 | 220 | deserializer.deserialize_any(TaggedValueVisitor) 221 | } 222 | } 223 | 224 | impl<'de> Deserializer<'de> for TaggedValue { 225 | type Error = Error; 226 | 227 | fn deserialize_any(self, visitor: V) -> Result 228 | where 229 | V: Visitor<'de>, 230 | { 231 | visitor.visit_enum(self) 232 | } 233 | 234 | fn deserialize_ignored_any(self, visitor: V) -> Result 235 | where 236 | V: Visitor<'de>, 237 | { 238 | drop(self); 239 | visitor.visit_unit() 240 | } 241 | 242 | forward_to_deserialize_any! { 243 | bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes 244 | byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct 245 | map struct enum identifier 246 | } 247 | } 248 | 249 | impl<'de> EnumAccess<'de> for TaggedValue { 250 | type Error = Error; 251 | type Variant = Value; 252 | 253 | fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Error> 254 | where 255 | V: DeserializeSeed<'de>, 256 | { 257 | let tag = StrDeserializer::::new(nobang(&self.tag.string)); 258 | let value = seed.deserialize(tag)?; 259 | Ok((value, self.value)) 260 | } 261 | } 262 | 263 | impl<'de> VariantAccess<'de> for Value { 264 | type Error = Error; 265 | 266 | fn unit_variant(self) -> Result<(), Error> { 267 | Deserialize::deserialize(self) 268 | } 269 | 270 | fn newtype_variant_seed(self, seed: T) -> Result 271 | where 272 | T: DeserializeSeed<'de>, 273 | { 274 | seed.deserialize(self) 275 | } 276 | 277 | fn tuple_variant(self, _len: usize, visitor: V) -> Result 278 | where 279 | V: Visitor<'de>, 280 | { 281 | if let Value::Sequence(v) = self { 282 | Deserializer::deserialize_any(SeqDeserializer::new(v), visitor) 283 | } else { 284 | Err(Error::invalid_type(self.unexpected(), &"tuple variant")) 285 | } 286 | } 287 | 288 | fn struct_variant( 289 | self, 290 | _fields: &'static [&'static str], 291 | visitor: V, 292 | ) -> Result 293 | where 294 | V: Visitor<'de>, 295 | { 296 | if let Value::Mapping(v) = self { 297 | Deserializer::deserialize_any(MapDeserializer::new(v), visitor) 298 | } else { 299 | Err(Error::invalid_type(self.unexpected(), &"struct variant")) 300 | } 301 | } 302 | } 303 | 304 | impl<'de> Deserializer<'de> for &'de TaggedValue { 305 | type Error = Error; 306 | 307 | fn deserialize_any(self, visitor: V) -> Result 308 | where 309 | V: Visitor<'de>, 310 | { 311 | visitor.visit_enum(self) 312 | } 313 | 314 | fn deserialize_ignored_any(self, visitor: V) -> Result 315 | where 316 | V: Visitor<'de>, 317 | { 318 | visitor.visit_unit() 319 | } 320 | 321 | forward_to_deserialize_any! { 322 | bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes 323 | byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct 324 | map struct enum identifier 325 | } 326 | } 327 | 328 | impl<'de> EnumAccess<'de> for &'de TaggedValue { 329 | type Error = Error; 330 | type Variant = &'de Value; 331 | 332 | fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Error> 333 | where 334 | V: DeserializeSeed<'de>, 335 | { 336 | let tag = BorrowedStrDeserializer::::new(nobang(&self.tag.string)); 337 | let value = seed.deserialize(tag)?; 338 | Ok((value, &self.value)) 339 | } 340 | } 341 | 342 | impl<'de> VariantAccess<'de> for &'de Value { 343 | type Error = Error; 344 | 345 | fn unit_variant(self) -> Result<(), Error> { 346 | Deserialize::deserialize(self) 347 | } 348 | 349 | fn newtype_variant_seed(self, seed: T) -> Result 350 | where 351 | T: DeserializeSeed<'de>, 352 | { 353 | seed.deserialize(self) 354 | } 355 | 356 | fn tuple_variant(self, _len: usize, visitor: V) -> Result 357 | where 358 | V: Visitor<'de>, 359 | { 360 | if let Value::Sequence(v) = self { 361 | Deserializer::deserialize_any(SeqRefDeserializer::new(v), visitor) 362 | } else { 363 | Err(Error::invalid_type(self.unexpected(), &"tuple variant")) 364 | } 365 | } 366 | 367 | fn struct_variant( 368 | self, 369 | _fields: &'static [&'static str], 370 | visitor: V, 371 | ) -> Result 372 | where 373 | V: Visitor<'de>, 374 | { 375 | if let Value::Mapping(v) = self { 376 | Deserializer::deserialize_any(MapRefDeserializer::new(v), visitor) 377 | } else { 378 | Err(Error::invalid_type(self.unexpected(), &"struct variant")) 379 | } 380 | } 381 | } 382 | 383 | pub(crate) struct TagStringVisitor; 384 | 385 | impl<'de> Visitor<'de> for TagStringVisitor { 386 | type Value = Tag; 387 | 388 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 389 | formatter.write_str("a YAML tag string") 390 | } 391 | 392 | fn visit_str(self, string: &str) -> Result 393 | where 394 | E: serde::de::Error, 395 | { 396 | self.visit_string(string.to_owned()) 397 | } 398 | 399 | fn visit_string(self, string: String) -> Result 400 | where 401 | E: serde::de::Error, 402 | { 403 | if string.is_empty() { 404 | return Err(E::custom("empty YAML tag is not allowed")); 405 | } 406 | Ok(Tag::new(string)) 407 | } 408 | } 409 | 410 | impl<'de> DeserializeSeed<'de> for TagStringVisitor { 411 | type Value = Tag; 412 | 413 | fn deserialize(self, deserializer: D) -> Result 414 | where 415 | D: Deserializer<'de>, 416 | { 417 | deserializer.deserialize_string(self) 418 | } 419 | } 420 | 421 | pub(crate) enum MaybeTag { 422 | Tag(String), 423 | NotTag(T), 424 | } 425 | 426 | pub(crate) fn check_for_tag(value: &T) -> MaybeTag 427 | where 428 | T: ?Sized + Display, 429 | { 430 | enum CheckForTag { 431 | Empty, 432 | Bang, 433 | Tag(String), 434 | NotTag(String), 435 | } 436 | 437 | impl fmt::Write for CheckForTag { 438 | fn write_str(&mut self, s: &str) -> fmt::Result { 439 | if s.is_empty() { 440 | return Ok(()); 441 | } 442 | match self { 443 | CheckForTag::Empty => { 444 | if s == "!" { 445 | *self = CheckForTag::Bang; 446 | } else { 447 | *self = CheckForTag::NotTag(s.to_owned()); 448 | } 449 | } 450 | CheckForTag::Bang => { 451 | *self = CheckForTag::Tag(s.to_owned()); 452 | } 453 | CheckForTag::Tag(string) => { 454 | let mut string = mem::take(string); 455 | string.push_str(s); 456 | *self = CheckForTag::NotTag(string); 457 | } 458 | CheckForTag::NotTag(string) => { 459 | string.push_str(s); 460 | } 461 | } 462 | Ok(()) 463 | } 464 | } 465 | 466 | let mut check_for_tag = CheckForTag::Empty; 467 | fmt::write(&mut check_for_tag, format_args!("{}", value)).unwrap(); 468 | match check_for_tag { 469 | CheckForTag::Empty => MaybeTag::NotTag(String::new()), 470 | CheckForTag::Bang => MaybeTag::NotTag("!".to_owned()), 471 | CheckForTag::Tag(string) => MaybeTag::Tag(string), 472 | CheckForTag::NotTag(string) => MaybeTag::NotTag(string), 473 | } 474 | } 475 | -------------------------------------------------------------------------------- /src/number.rs: -------------------------------------------------------------------------------- 1 | use crate::de; 2 | use crate::error::{self, Error, ErrorImpl}; 3 | use serde::de::{Unexpected, Visitor}; 4 | use serde::{forward_to_deserialize_any, Deserialize, Deserializer, Serialize, Serializer}; 5 | use std::cmp::Ordering; 6 | use std::fmt::{self, Display}; 7 | use std::hash::{Hash, Hasher}; 8 | use std::str::FromStr; 9 | 10 | /// Represents a YAML number, whether integer or floating point. 11 | #[derive(Clone, PartialEq, PartialOrd)] 12 | pub struct Number { 13 | n: N, 14 | } 15 | 16 | // "N" is a prefix of "NegInt"... this is a false positive. 17 | // https://github.com/Manishearth/rust-clippy/issues/1241 18 | #[allow(clippy::enum_variant_names)] 19 | #[derive(Copy, Clone)] 20 | enum N { 21 | PosInt(u64), 22 | /// Always less than zero. 23 | NegInt(i64), 24 | /// May be infinite or NaN. 25 | Float(f64), 26 | } 27 | 28 | impl Number { 29 | /// Returns true if the `Number` is an integer between `i64::MIN` and 30 | /// `i64::MAX`. 31 | /// 32 | /// For any Number on which `is_i64` returns true, `as_i64` is guaranteed to 33 | /// return the integer value. 34 | /// 35 | /// ``` 36 | /// # fn main() -> serde_norway::Result<()> { 37 | /// let big = i64::MAX as u64 + 10; 38 | /// let v: serde_norway::Value = serde_norway::from_str(r#" 39 | /// a: 64 40 | /// b: 9223372036854775817 41 | /// c: 256.0 42 | /// "#)?; 43 | /// 44 | /// assert!(v["a"].is_i64()); 45 | /// 46 | /// // Greater than i64::MAX. 47 | /// assert!(!v["b"].is_i64()); 48 | /// 49 | /// // Numbers with a decimal point are not considered integers. 50 | /// assert!(!v["c"].is_i64()); 51 | /// # Ok(()) 52 | /// # } 53 | /// ``` 54 | #[inline] 55 | #[allow(clippy::cast_sign_loss)] 56 | pub fn is_i64(&self) -> bool { 57 | match self.n { 58 | N::PosInt(v) => v <= i64::MAX as u64, 59 | N::NegInt(_) => true, 60 | N::Float(_) => false, 61 | } 62 | } 63 | 64 | /// Returns true if the `Number` is an integer between zero and `u64::MAX`. 65 | /// 66 | /// For any Number on which `is_u64` returns true, `as_u64` is guaranteed to 67 | /// return the integer value. 68 | /// 69 | /// ``` 70 | /// # fn main() -> serde_norway::Result<()> { 71 | /// let v: serde_norway::Value = serde_norway::from_str(r#" 72 | /// a: 64 73 | /// b: -64 74 | /// c: 256.0 75 | /// "#)?; 76 | /// 77 | /// assert!(v["a"].is_u64()); 78 | /// 79 | /// // Negative integer. 80 | /// assert!(!v["b"].is_u64()); 81 | /// 82 | /// // Numbers with a decimal point are not considered integers. 83 | /// assert!(!v["c"].is_u64()); 84 | /// # Ok(()) 85 | /// # } 86 | /// ``` 87 | #[inline] 88 | pub fn is_u64(&self) -> bool { 89 | match self.n { 90 | N::PosInt(_) => true, 91 | N::NegInt(_) | N::Float(_) => false, 92 | } 93 | } 94 | 95 | /// Returns true if the `Number` can be represented by f64. 96 | /// 97 | /// For any Number on which `is_f64` returns true, `as_f64` is guaranteed to 98 | /// return the floating point value. 99 | /// 100 | /// Currently this function returns true if and only if both `is_i64` and 101 | /// `is_u64` return false but this is not a guarantee in the future. 102 | /// 103 | /// ``` 104 | /// # fn main() -> serde_norway::Result<()> { 105 | /// let v: serde_norway::Value = serde_norway::from_str(r#" 106 | /// a: 256.0 107 | /// b: 64 108 | /// c: -64 109 | /// "#)?; 110 | /// 111 | /// assert!(v["a"].is_f64()); 112 | /// 113 | /// // Integers. 114 | /// assert!(!v["b"].is_f64()); 115 | /// assert!(!v["c"].is_f64()); 116 | /// # Ok(()) 117 | /// # } 118 | /// ``` 119 | #[inline] 120 | pub fn is_f64(&self) -> bool { 121 | match self.n { 122 | N::Float(_) => true, 123 | N::PosInt(_) | N::NegInt(_) => false, 124 | } 125 | } 126 | 127 | /// If the `Number` is an integer, represent it as i64 if possible. Returns 128 | /// None otherwise. 129 | /// 130 | /// ``` 131 | /// # fn main() -> serde_norway::Result<()> { 132 | /// let big = i64::MAX as u64 + 10; 133 | /// let v: serde_norway::Value = serde_norway::from_str(r#" 134 | /// a: 64 135 | /// b: 9223372036854775817 136 | /// c: 256.0 137 | /// "#)?; 138 | /// 139 | /// assert_eq!(v["a"].as_i64(), Some(64)); 140 | /// assert_eq!(v["b"].as_i64(), None); 141 | /// assert_eq!(v["c"].as_i64(), None); 142 | /// # Ok(()) 143 | /// # } 144 | /// ``` 145 | #[inline] 146 | pub fn as_i64(&self) -> Option { 147 | match self.n { 148 | N::PosInt(n) => { 149 | if n <= i64::MAX as u64 { 150 | Some(n as i64) 151 | } else { 152 | None 153 | } 154 | } 155 | N::NegInt(n) => Some(n), 156 | N::Float(_) => None, 157 | } 158 | } 159 | 160 | /// If the `Number` is an integer, represent it as u64 if possible. Returns 161 | /// None otherwise. 162 | /// 163 | /// ``` 164 | /// # fn main() -> serde_norway::Result<()> { 165 | /// let v: serde_norway::Value = serde_norway::from_str(r#" 166 | /// a: 64 167 | /// b: -64 168 | /// c: 256.0 169 | /// "#)?; 170 | /// 171 | /// assert_eq!(v["a"].as_u64(), Some(64)); 172 | /// assert_eq!(v["b"].as_u64(), None); 173 | /// assert_eq!(v["c"].as_u64(), None); 174 | /// # Ok(()) 175 | /// # } 176 | /// ``` 177 | #[inline] 178 | pub fn as_u64(&self) -> Option { 179 | match self.n { 180 | N::PosInt(n) => Some(n), 181 | N::NegInt(_) | N::Float(_) => None, 182 | } 183 | } 184 | 185 | /// Represents the number as f64 if possible. Returns None otherwise. 186 | /// 187 | /// ``` 188 | /// # fn main() -> serde_norway::Result<()> { 189 | /// let v: serde_norway::Value = serde_norway::from_str(r#" 190 | /// a: 256.0 191 | /// b: 64 192 | /// c: -64 193 | /// "#)?; 194 | /// 195 | /// assert_eq!(v["a"].as_f64(), Some(256.0)); 196 | /// assert_eq!(v["b"].as_f64(), Some(64.0)); 197 | /// assert_eq!(v["c"].as_f64(), Some(-64.0)); 198 | /// # Ok(()) 199 | /// # } 200 | /// ``` 201 | /// 202 | /// ``` 203 | /// # fn main() -> serde_norway::Result<()> { 204 | /// let v: serde_norway::Value = serde_norway::from_str(".inf")?; 205 | /// assert_eq!(v.as_f64(), Some(f64::INFINITY)); 206 | /// 207 | /// let v: serde_norway::Value = serde_norway::from_str("-.inf")?; 208 | /// assert_eq!(v.as_f64(), Some(f64::NEG_INFINITY)); 209 | /// 210 | /// let v: serde_norway::Value = serde_norway::from_str(".nan")?; 211 | /// assert!(v.as_f64().unwrap().is_nan()); 212 | /// # Ok(()) 213 | /// # } 214 | /// ``` 215 | #[inline] 216 | pub fn as_f64(&self) -> Option { 217 | match self.n { 218 | N::PosInt(n) => Some(n as f64), 219 | N::NegInt(n) => Some(n as f64), 220 | N::Float(n) => Some(n), 221 | } 222 | } 223 | 224 | /// Returns true if this value is NaN and false otherwise. 225 | /// 226 | /// ``` 227 | /// # use serde_norway::Number; 228 | /// # 229 | /// assert!(!Number::from(256.0).is_nan()); 230 | /// 231 | /// assert!(Number::from(f64::NAN).is_nan()); 232 | /// 233 | /// assert!(!Number::from(f64::INFINITY).is_nan()); 234 | /// 235 | /// assert!(!Number::from(f64::NEG_INFINITY).is_nan()); 236 | /// 237 | /// assert!(!Number::from(1).is_nan()); 238 | /// ``` 239 | #[inline] 240 | pub fn is_nan(&self) -> bool { 241 | match self.n { 242 | N::PosInt(_) | N::NegInt(_) => false, 243 | N::Float(f) => f.is_nan(), 244 | } 245 | } 246 | 247 | /// Returns true if this value is positive infinity or negative infinity and 248 | /// false otherwise. 249 | /// 250 | /// ``` 251 | /// # use serde_norway::Number; 252 | /// # 253 | /// assert!(!Number::from(256.0).is_infinite()); 254 | /// 255 | /// assert!(!Number::from(f64::NAN).is_infinite()); 256 | /// 257 | /// assert!(Number::from(f64::INFINITY).is_infinite()); 258 | /// 259 | /// assert!(Number::from(f64::NEG_INFINITY).is_infinite()); 260 | /// 261 | /// assert!(!Number::from(1).is_infinite()); 262 | /// ``` 263 | #[inline] 264 | pub fn is_infinite(&self) -> bool { 265 | match self.n { 266 | N::PosInt(_) | N::NegInt(_) => false, 267 | N::Float(f) => f.is_infinite(), 268 | } 269 | } 270 | 271 | /// Returns true if this number is neither infinite nor NaN. 272 | /// 273 | /// ``` 274 | /// # use serde_norway::Number; 275 | /// # 276 | /// assert!(Number::from(256.0).is_finite()); 277 | /// 278 | /// assert!(!Number::from(f64::NAN).is_finite()); 279 | /// 280 | /// assert!(!Number::from(f64::INFINITY).is_finite()); 281 | /// 282 | /// assert!(!Number::from(f64::NEG_INFINITY).is_finite()); 283 | /// 284 | /// assert!(Number::from(1).is_finite()); 285 | /// ``` 286 | #[inline] 287 | pub fn is_finite(&self) -> bool { 288 | match self.n { 289 | N::PosInt(_) | N::NegInt(_) => true, 290 | N::Float(f) => f.is_finite(), 291 | } 292 | } 293 | } 294 | 295 | impl Display for Number { 296 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 297 | match self.n { 298 | N::PosInt(i) => formatter.write_str(itoa::Buffer::new().format(i)), 299 | N::NegInt(i) => formatter.write_str(itoa::Buffer::new().format(i)), 300 | N::Float(f) if f.is_nan() => formatter.write_str(".nan"), 301 | N::Float(f) if f.is_infinite() => { 302 | if f.is_sign_negative() { 303 | formatter.write_str("-.inf") 304 | } else { 305 | formatter.write_str(".inf") 306 | } 307 | } 308 | N::Float(f) => formatter.write_str(ryu::Buffer::new().format_finite(f)), 309 | } 310 | } 311 | } 312 | 313 | impl FromStr for Number { 314 | type Err = Error; 315 | 316 | fn from_str(repr: &str) -> Result { 317 | if let Ok(result) = de::visit_int(NumberVisitor, repr) { 318 | return result; 319 | } 320 | if !de::digits_but_not_number(repr) { 321 | if let Some(float) = de::parse_f64(repr) { 322 | return Ok(float.into()); 323 | } 324 | } 325 | Err(error::new(ErrorImpl::FailedToParseNumber)) 326 | } 327 | } 328 | 329 | impl PartialEq for N { 330 | fn eq(&self, other: &N) -> bool { 331 | match (*self, *other) { 332 | (N::PosInt(a), N::PosInt(b)) => a == b, 333 | (N::NegInt(a), N::NegInt(b)) => a == b, 334 | (N::Float(a), N::Float(b)) => { 335 | if a.is_nan() && b.is_nan() { 336 | // YAML only has one NaN; 337 | // the bit representation isn't preserved 338 | true 339 | } else { 340 | a == b 341 | } 342 | } 343 | _ => false, 344 | } 345 | } 346 | } 347 | 348 | impl PartialOrd for N { 349 | fn partial_cmp(&self, other: &Self) -> Option { 350 | match (*self, *other) { 351 | (N::Float(a), N::Float(b)) => { 352 | if a.is_nan() && b.is_nan() { 353 | // YAML only has one NaN 354 | Some(Ordering::Equal) 355 | } else { 356 | a.partial_cmp(&b) 357 | } 358 | } 359 | _ => Some(self.total_cmp(other)), 360 | } 361 | } 362 | } 363 | 364 | impl N { 365 | fn total_cmp(&self, other: &Self) -> Ordering { 366 | match (*self, *other) { 367 | (N::PosInt(a), N::PosInt(b)) => a.cmp(&b), 368 | (N::NegInt(a), N::NegInt(b)) => a.cmp(&b), 369 | // negint is always less than zero 370 | (N::NegInt(_), N::PosInt(_)) => Ordering::Less, 371 | (N::PosInt(_), N::NegInt(_)) => Ordering::Greater, 372 | (N::Float(a), N::Float(b)) => a.partial_cmp(&b).unwrap_or_else(|| { 373 | // arbitrarily sort the NaN last 374 | if !a.is_nan() { 375 | Ordering::Less 376 | } else if !b.is_nan() { 377 | Ordering::Greater 378 | } else { 379 | Ordering::Equal 380 | } 381 | }), 382 | // arbitrarily sort integers below floats 383 | // FIXME: maybe something more sensible? 384 | (_, N::Float(_)) => Ordering::Less, 385 | (N::Float(_), _) => Ordering::Greater, 386 | } 387 | } 388 | } 389 | 390 | impl Number { 391 | pub(crate) fn total_cmp(&self, other: &Self) -> Ordering { 392 | self.n.total_cmp(&other.n) 393 | } 394 | } 395 | 396 | impl Serialize for Number { 397 | #[inline] 398 | fn serialize(&self, serializer: S) -> Result 399 | where 400 | S: Serializer, 401 | { 402 | match self.n { 403 | N::PosInt(i) => serializer.serialize_u64(i), 404 | N::NegInt(i) => serializer.serialize_i64(i), 405 | N::Float(f) => serializer.serialize_f64(f), 406 | } 407 | } 408 | } 409 | 410 | struct NumberVisitor; 411 | 412 | impl<'de> Visitor<'de> for NumberVisitor { 413 | type Value = Number; 414 | 415 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 416 | formatter.write_str("a number") 417 | } 418 | 419 | #[inline] 420 | fn visit_i64(self, value: i64) -> Result { 421 | Ok(value.into()) 422 | } 423 | 424 | #[inline] 425 | fn visit_u64(self, value: u64) -> Result { 426 | Ok(value.into()) 427 | } 428 | 429 | #[inline] 430 | fn visit_f64(self, value: f64) -> Result { 431 | Ok(value.into()) 432 | } 433 | } 434 | 435 | impl<'de> Deserialize<'de> for Number { 436 | #[inline] 437 | fn deserialize(deserializer: D) -> Result 438 | where 439 | D: Deserializer<'de>, 440 | { 441 | deserializer.deserialize_any(NumberVisitor) 442 | } 443 | } 444 | 445 | impl<'de> Deserializer<'de> for Number { 446 | type Error = Error; 447 | 448 | #[inline] 449 | fn deserialize_any(self, visitor: V) -> Result 450 | where 451 | V: Visitor<'de>, 452 | { 453 | match self.n { 454 | N::PosInt(i) => visitor.visit_u64(i), 455 | N::NegInt(i) => visitor.visit_i64(i), 456 | N::Float(f) => visitor.visit_f64(f), 457 | } 458 | } 459 | 460 | forward_to_deserialize_any! { 461 | bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string 462 | bytes byte_buf option unit unit_struct newtype_struct seq tuple 463 | tuple_struct map struct enum identifier ignored_any 464 | } 465 | } 466 | 467 | impl<'de, 'a> Deserializer<'de> for &'a Number { 468 | type Error = Error; 469 | 470 | #[inline] 471 | fn deserialize_any(self, visitor: V) -> Result 472 | where 473 | V: Visitor<'de>, 474 | { 475 | match self.n { 476 | N::PosInt(i) => visitor.visit_u64(i), 477 | N::NegInt(i) => visitor.visit_i64(i), 478 | N::Float(f) => visitor.visit_f64(f), 479 | } 480 | } 481 | 482 | forward_to_deserialize_any! { 483 | bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string 484 | bytes byte_buf option unit unit_struct newtype_struct seq tuple 485 | tuple_struct map struct enum identifier ignored_any 486 | } 487 | } 488 | 489 | macro_rules! from_signed { 490 | ($($signed_ty:ident)*) => { 491 | $( 492 | impl From<$signed_ty> for Number { 493 | #[inline] 494 | #[allow(clippy::cast_sign_loss)] 495 | fn from(i: $signed_ty) -> Self { 496 | if i < 0 { 497 | Number { n: N::NegInt(i as i64) } 498 | } else { 499 | Number { n: N::PosInt(i as u64) } 500 | } 501 | } 502 | } 503 | )* 504 | }; 505 | } 506 | 507 | macro_rules! from_unsigned { 508 | ($($unsigned_ty:ident)*) => { 509 | $( 510 | impl From<$unsigned_ty> for Number { 511 | #[inline] 512 | fn from(u: $unsigned_ty) -> Self { 513 | Number { n: N::PosInt(u as u64) } 514 | } 515 | } 516 | )* 517 | }; 518 | } 519 | 520 | from_signed!(i8 i16 i32 i64 isize); 521 | from_unsigned!(u8 u16 u32 u64 usize); 522 | 523 | impl From for Number { 524 | fn from(f: f32) -> Self { 525 | Number::from(f as f64) 526 | } 527 | } 528 | 529 | impl From for Number { 530 | fn from(mut f: f64) -> Self { 531 | if f.is_nan() { 532 | // Destroy NaN sign, signaling, and payload. YAML only has one NaN. 533 | f = f64::NAN.copysign(1.0); 534 | } 535 | Number { n: N::Float(f) } 536 | } 537 | } 538 | 539 | // This is fine, because we don't _really_ implement hash for floats 540 | // all other hash functions should work as expected 541 | #[allow(clippy::derived_hash_with_manual_eq)] 542 | impl Hash for Number { 543 | fn hash(&self, state: &mut H) { 544 | match self.n { 545 | N::Float(_) => { 546 | // you should feel bad for using f64 as a map key 547 | 3.hash(state); 548 | } 549 | N::PosInt(u) => u.hash(state), 550 | N::NegInt(i) => i.hash(state), 551 | } 552 | } 553 | } 554 | 555 | pub(crate) fn unexpected(number: &Number) -> Unexpected { 556 | match number.n { 557 | N::PosInt(u) => Unexpected::Unsigned(u), 558 | N::NegInt(i) => Unexpected::Signed(i), 559 | N::Float(f) => Unexpected::Float(f), 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /tests/test_de.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::cast_lossless, 3 | clippy::cast_possible_wrap, 4 | clippy::derive_partial_eq_without_eq, 5 | clippy::similar_names, 6 | clippy::uninlined_format_args 7 | )] 8 | 9 | use indoc::indoc; 10 | use serde_derive::Deserialize; 11 | use serde_norway::{Deserializer, Number, Value}; 12 | use std::collections::BTreeMap; 13 | use std::fmt::Debug; 14 | 15 | fn test_de(yaml: &str, expected: &T) 16 | where 17 | T: serde::de::DeserializeOwned + PartialEq + Debug, 18 | { 19 | let deserialized: T = serde_norway::from_str(yaml).unwrap(); 20 | assert_eq!(*expected, deserialized); 21 | 22 | let value: Value = serde_norway::from_str(yaml).unwrap(); 23 | let deserialized = T::deserialize(&value).unwrap(); 24 | assert_eq!(*expected, deserialized); 25 | 26 | let deserialized: T = serde_norway::from_value(value).unwrap(); 27 | assert_eq!(*expected, deserialized); 28 | 29 | serde_norway::from_str::(yaml).unwrap(); 30 | 31 | let mut deserializer = Deserializer::from_str(yaml); 32 | let document = deserializer.next().unwrap(); 33 | let deserialized = T::deserialize(document).unwrap(); 34 | assert_eq!(*expected, deserialized); 35 | assert!(deserializer.next().is_none()); 36 | } 37 | 38 | fn test_de_no_value<'de, T>(yaml: &'de str, expected: &T) 39 | where 40 | T: serde::de::Deserialize<'de> + PartialEq + Debug, 41 | { 42 | let deserialized: T = serde_norway::from_str(yaml).unwrap(); 43 | assert_eq!(*expected, deserialized); 44 | 45 | serde_norway::from_str::(yaml).unwrap(); 46 | serde_norway::from_str::(yaml).unwrap(); 47 | } 48 | 49 | fn test_de_seed<'de, T, S>(yaml: &'de str, seed: S, expected: &T) 50 | where 51 | T: PartialEq + Debug, 52 | S: serde::de::DeserializeSeed<'de, Value = T>, 53 | { 54 | let deserialized: T = seed.deserialize(Deserializer::from_str(yaml)).unwrap(); 55 | assert_eq!(*expected, deserialized); 56 | 57 | serde_norway::from_str::(yaml).unwrap(); 58 | serde_norway::from_str::(yaml).unwrap(); 59 | } 60 | 61 | #[test] 62 | fn test_borrowed() { 63 | let yaml = indoc! {" 64 | - plain nonàscii 65 | - 'single quoted' 66 | - \"double quoted\" 67 | "}; 68 | let expected = vec!["plain nonàscii", "single quoted", "double quoted"]; 69 | test_de_no_value(yaml, &expected); 70 | } 71 | 72 | #[test] 73 | fn test_alias() { 74 | let yaml = indoc! {" 75 | first: 76 | &alias 77 | 1 78 | second: 79 | *alias 80 | third: 3 81 | "}; 82 | let mut expected = BTreeMap::new(); 83 | expected.insert("first".to_owned(), 1); 84 | expected.insert("second".to_owned(), 1); 85 | expected.insert("third".to_owned(), 3); 86 | test_de(yaml, &expected); 87 | } 88 | 89 | #[test] 90 | fn test_option() { 91 | #[derive(Deserialize, PartialEq, Debug)] 92 | struct Data { 93 | a: Option, 94 | b: Option, 95 | c: Option, 96 | } 97 | let yaml = indoc! {" 98 | b: 99 | c: true 100 | "}; 101 | let expected = Data { 102 | a: None, 103 | b: None, 104 | c: Some(true), 105 | }; 106 | test_de(yaml, &expected); 107 | } 108 | 109 | #[test] 110 | fn test_option_alias() { 111 | #[derive(Deserialize, PartialEq, Debug)] 112 | struct Data { 113 | a: Option, 114 | b: Option, 115 | c: Option, 116 | d: Option, 117 | e: Option, 118 | f: Option, 119 | } 120 | let yaml = indoc! {" 121 | none_f: 122 | &none_f 123 | ~ 124 | none_s: 125 | &none_s 126 | ~ 127 | none_b: 128 | &none_b 129 | ~ 130 | 131 | some_f: 132 | &some_f 133 | 1.0 134 | some_s: 135 | &some_s 136 | x 137 | some_b: 138 | &some_b 139 | true 140 | 141 | a: *none_f 142 | b: *none_s 143 | c: *none_b 144 | d: *some_f 145 | e: *some_s 146 | f: *some_b 147 | "}; 148 | let expected = Data { 149 | a: None, 150 | b: None, 151 | c: None, 152 | d: Some(1.0), 153 | e: Some("x".to_owned()), 154 | f: Some(true), 155 | }; 156 | test_de(yaml, &expected); 157 | } 158 | 159 | #[test] 160 | fn test_enum_alias() { 161 | #[derive(Deserialize, PartialEq, Debug)] 162 | enum E { 163 | A, 164 | B(u8, u8), 165 | } 166 | #[derive(Deserialize, PartialEq, Debug)] 167 | struct Data { 168 | a: E, 169 | b: E, 170 | } 171 | let yaml = indoc! {" 172 | aref: 173 | &aref 174 | A 175 | bref: 176 | &bref 177 | !B 178 | - 1 179 | - 2 180 | 181 | a: *aref 182 | b: *bref 183 | "}; 184 | let expected = Data { 185 | a: E::A, 186 | b: E::B(1, 2), 187 | }; 188 | test_de(yaml, &expected); 189 | } 190 | 191 | #[test] 192 | fn test_enum_representations() { 193 | #[derive(Deserialize, PartialEq, Debug)] 194 | enum Enum { 195 | Unit, 196 | Tuple(i32, i32), 197 | Struct { x: i32, y: i32 }, 198 | String(String), 199 | Number(f64), 200 | } 201 | 202 | let yaml = indoc! {" 203 | - Unit 204 | - 'Unit' 205 | - !Unit 206 | - !Unit ~ 207 | - !Unit null 208 | - !Tuple [0, 0] 209 | - !Tuple 210 | - 0 211 | - 0 212 | - !Struct {x: 0, y: 0} 213 | - !Struct 214 | x: 0 215 | y: 0 216 | - !String '...' 217 | - !String ... 218 | - !Number 0 219 | "}; 220 | 221 | let expected = vec![ 222 | Enum::Unit, 223 | Enum::Unit, 224 | Enum::Unit, 225 | Enum::Unit, 226 | Enum::Unit, 227 | Enum::Tuple(0, 0), 228 | Enum::Tuple(0, 0), 229 | Enum::Struct { x: 0, y: 0 }, 230 | Enum::Struct { x: 0, y: 0 }, 231 | Enum::String("...".to_owned()), 232 | Enum::String("...".to_owned()), 233 | Enum::Number(0.0), 234 | ]; 235 | 236 | test_de(yaml, &expected); 237 | 238 | let yaml = indoc! {" 239 | - !String 240 | "}; 241 | let expected = vec![Enum::String(String::new())]; 242 | test_de_no_value(yaml, &expected); 243 | } 244 | 245 | #[test] 246 | fn test_number_as_string() { 247 | #[derive(Deserialize, PartialEq, Debug)] 248 | struct Num { 249 | value: String, 250 | } 251 | let yaml = indoc! {" 252 | # Cannot be represented as u128 253 | value: 340282366920938463463374607431768211457 254 | "}; 255 | let expected = Num { 256 | value: "340282366920938463463374607431768211457".to_owned(), 257 | }; 258 | test_de_no_value(yaml, &expected); 259 | } 260 | 261 | #[test] 262 | fn test_empty_string() { 263 | #[derive(Deserialize, PartialEq, Debug)] 264 | struct Struct { 265 | empty: String, 266 | tilde: String, 267 | } 268 | let yaml = indoc! {" 269 | empty: 270 | tilde: ~ 271 | "}; 272 | let expected = Struct { 273 | empty: String::new(), 274 | tilde: "~".to_owned(), 275 | }; 276 | test_de_no_value(yaml, &expected); 277 | } 278 | 279 | #[test] 280 | fn test_i128_big() { 281 | let expected: i128 = i64::MIN as i128 - 1; 282 | let yaml = indoc! {" 283 | -9223372036854775809 284 | "}; 285 | assert_eq!(expected, serde_norway::from_str::(yaml).unwrap()); 286 | 287 | let octal = indoc! {" 288 | -0o1000000000000000000001 289 | "}; 290 | assert_eq!(expected, serde_norway::from_str::(octal).unwrap()); 291 | } 292 | 293 | #[test] 294 | fn test_u128_big() { 295 | let expected: u128 = u64::MAX as u128 + 1; 296 | let yaml = indoc! {" 297 | 18446744073709551616 298 | "}; 299 | assert_eq!(expected, serde_norway::from_str::(yaml).unwrap()); 300 | 301 | let octal = indoc! {" 302 | 0o2000000000000000000000 303 | "}; 304 | assert_eq!(expected, serde_norway::from_str::(octal).unwrap()); 305 | } 306 | 307 | #[test] 308 | fn test_number_alias_as_string() { 309 | #[derive(Deserialize, PartialEq, Debug)] 310 | struct Num { 311 | version: String, 312 | value: String, 313 | } 314 | let yaml = indoc! {" 315 | version: &a 1.10 316 | value: *a 317 | "}; 318 | let expected = Num { 319 | version: "1.10".to_owned(), 320 | value: "1.10".to_owned(), 321 | }; 322 | test_de_no_value(yaml, &expected); 323 | } 324 | 325 | #[test] 326 | fn test_de_mapping() { 327 | #[derive(Debug, Deserialize, PartialEq)] 328 | struct Data { 329 | pub substructure: serde_norway::Mapping, 330 | } 331 | let yaml = indoc! {" 332 | substructure: 333 | a: 'foo' 334 | b: 'bar' 335 | "}; 336 | 337 | let mut expected = Data { 338 | substructure: serde_norway::Mapping::new(), 339 | }; 340 | expected.substructure.insert( 341 | serde_norway::Value::String("a".to_owned()), 342 | serde_norway::Value::String("foo".to_owned()), 343 | ); 344 | expected.substructure.insert( 345 | serde_norway::Value::String("b".to_owned()), 346 | serde_norway::Value::String("bar".to_owned()), 347 | ); 348 | 349 | test_de(yaml, &expected); 350 | } 351 | 352 | #[test] 353 | fn test_byte_order_mark() { 354 | let yaml = "\u{feff}- 0\n"; 355 | let expected = vec![0]; 356 | test_de(yaml, &expected); 357 | } 358 | 359 | #[test] 360 | fn test_bomb() { 361 | #[derive(Debug, Deserialize, PartialEq)] 362 | struct Data { 363 | expected: String, 364 | } 365 | 366 | // This would deserialize an astronomical number of elements if we were 367 | // vulnerable. 368 | let yaml = indoc! {" 369 | a: &a ~ 370 | b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a] 371 | c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b] 372 | d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c] 373 | e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d] 374 | f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e] 375 | g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f] 376 | h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g] 377 | i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h] 378 | j: &j [*i,*i,*i,*i,*i,*i,*i,*i,*i] 379 | k: &k [*j,*j,*j,*j,*j,*j,*j,*j,*j] 380 | l: &l [*k,*k,*k,*k,*k,*k,*k,*k,*k] 381 | m: &m [*l,*l,*l,*l,*l,*l,*l,*l,*l] 382 | n: &n [*m,*m,*m,*m,*m,*m,*m,*m,*m] 383 | o: &o [*n,*n,*n,*n,*n,*n,*n,*n,*n] 384 | p: &p [*o,*o,*o,*o,*o,*o,*o,*o,*o] 385 | q: &q [*p,*p,*p,*p,*p,*p,*p,*p,*p] 386 | r: &r [*q,*q,*q,*q,*q,*q,*q,*q,*q] 387 | s: &s [*r,*r,*r,*r,*r,*r,*r,*r,*r] 388 | t: &t [*s,*s,*s,*s,*s,*s,*s,*s,*s] 389 | u: &u [*t,*t,*t,*t,*t,*t,*t,*t,*t] 390 | v: &v [*u,*u,*u,*u,*u,*u,*u,*u,*u] 391 | w: &w [*v,*v,*v,*v,*v,*v,*v,*v,*v] 392 | x: &x [*w,*w,*w,*w,*w,*w,*w,*w,*w] 393 | y: &y [*x,*x,*x,*x,*x,*x,*x,*x,*x] 394 | z: &z [*y,*y,*y,*y,*y,*y,*y,*y,*y] 395 | expected: string 396 | "}; 397 | 398 | let expected = Data { 399 | expected: "string".to_owned(), 400 | }; 401 | 402 | assert_eq!(expected, serde_norway::from_str::(yaml).unwrap()); 403 | } 404 | 405 | #[test] 406 | fn test_numbers() { 407 | let cases = [ 408 | ("0xF0", "240"), 409 | ("+0xF0", "240"), 410 | ("-0xF0", "-240"), 411 | ("0o70", "56"), 412 | ("+0o70", "56"), 413 | ("-0o70", "-56"), 414 | ("0b10", "2"), 415 | ("+0b10", "2"), 416 | ("-0b10", "-2"), 417 | ("127", "127"), 418 | ("+127", "127"), 419 | ("-127", "-127"), 420 | (".inf", ".inf"), 421 | (".Inf", ".inf"), 422 | (".INF", ".inf"), 423 | ("-.inf", "-.inf"), 424 | ("-.Inf", "-.inf"), 425 | ("-.INF", "-.inf"), 426 | (".nan", ".nan"), 427 | (".NaN", ".nan"), 428 | (".NAN", ".nan"), 429 | ("0.1", "0.1"), 430 | ]; 431 | for &(yaml, expected) in &cases { 432 | let value = serde_norway::from_str::(yaml).unwrap(); 433 | match value { 434 | Value::Number(number) => assert_eq!(number.to_string(), expected), 435 | _ => panic!("expected number. input={:?}, result={:?}", yaml, value), 436 | } 437 | } 438 | 439 | // NOT numbers. 440 | let cases = [ 441 | "0127", "+0127", "-0127", "++.inf", "+-.inf", "++1", "+-1", "-+1", "--1", "0x+1", "0x-1", 442 | "-0x+1", "-0x-1", "++0x1", "+-0x1", "-+0x1", "--0x1", 443 | ]; 444 | for yaml in &cases { 445 | let value = serde_norway::from_str::(yaml).unwrap(); 446 | match value { 447 | Value::String(string) => assert_eq!(string, *yaml), 448 | _ => panic!("expected string. input={:?}, result={:?}", yaml, value), 449 | } 450 | } 451 | } 452 | 453 | #[test] 454 | fn test_nan() { 455 | // There is no negative NaN in YAML. 456 | assert!(serde_norway::from_str::(".nan") 457 | .unwrap() 458 | .is_sign_positive()); 459 | assert!(serde_norway::from_str::(".nan") 460 | .unwrap() 461 | .is_sign_positive()); 462 | } 463 | 464 | #[test] 465 | fn test_stateful() { 466 | struct Seed(i64); 467 | 468 | impl<'de> serde::de::DeserializeSeed<'de> for Seed { 469 | type Value = i64; 470 | fn deserialize(self, deserializer: D) -> Result 471 | where 472 | D: serde::de::Deserializer<'de>, 473 | { 474 | struct Visitor(i64); 475 | impl<'de> serde::de::Visitor<'de> for Visitor { 476 | type Value = i64; 477 | 478 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 479 | write!(formatter, "an integer") 480 | } 481 | 482 | fn visit_i64(self, v: i64) -> Result { 483 | Ok(v * self.0) 484 | } 485 | 486 | fn visit_u64(self, v: u64) -> Result { 487 | Ok(v as i64 * self.0) 488 | } 489 | } 490 | 491 | deserializer.deserialize_any(Visitor(self.0)) 492 | } 493 | } 494 | 495 | let cases = [("3", 5, 15), ("6", 7, 42), ("-5", 9, -45)]; 496 | for &(yaml, seed, expected) in &cases { 497 | test_de_seed(yaml, Seed(seed), &expected); 498 | } 499 | } 500 | 501 | #[test] 502 | fn test_ignore_tag() { 503 | #[derive(Deserialize, Debug, PartialEq)] 504 | struct Data { 505 | struc: Struc, 506 | tuple: Tuple, 507 | newtype: Newtype, 508 | map: BTreeMap, 509 | vec: Vec, 510 | } 511 | 512 | #[derive(Deserialize, Debug, PartialEq)] 513 | struct Struc { 514 | x: usize, 515 | } 516 | 517 | #[derive(Deserialize, Debug, PartialEq)] 518 | struct Tuple(usize, usize); 519 | 520 | #[derive(Deserialize, Debug, PartialEq)] 521 | struct Newtype(usize); 522 | 523 | let yaml = indoc! {" 524 | struc: !wat 525 | x: 0 526 | tuple: !wat 527 | - 0 528 | - 0 529 | newtype: !wat 0 530 | map: !wat 531 | x: 0 532 | vec: !wat 533 | - 0 534 | "}; 535 | 536 | let expected = Data { 537 | struc: Struc { x: 0 }, 538 | tuple: Tuple(0, 0), 539 | newtype: Newtype(0), 540 | map: { 541 | let mut map = BTreeMap::new(); 542 | map.insert('x', 0); 543 | map 544 | }, 545 | vec: vec![0], 546 | }; 547 | 548 | test_de(yaml, &expected); 549 | } 550 | 551 | #[test] 552 | fn test_no_required_fields() { 553 | #[derive(Deserialize, PartialEq, Debug)] 554 | pub struct NoRequiredFields { 555 | optional: Option, 556 | } 557 | 558 | for document in ["", "# comment\n"] { 559 | let expected = NoRequiredFields { optional: None }; 560 | let deserialized: NoRequiredFields = serde_norway::from_str(document).unwrap(); 561 | assert_eq!(expected, deserialized); 562 | 563 | let expected = Vec::::new(); 564 | let deserialized: Vec = serde_norway::from_str(document).unwrap(); 565 | assert_eq!(expected, deserialized); 566 | 567 | let expected = BTreeMap::new(); 568 | let deserialized: BTreeMap = serde_norway::from_str(document).unwrap(); 569 | assert_eq!(expected, deserialized); 570 | 571 | let expected = None; 572 | let deserialized: Option = serde_norway::from_str(document).unwrap(); 573 | assert_eq!(expected, deserialized); 574 | 575 | let expected = Value::Null; 576 | let deserialized: Value = serde_norway::from_str(document).unwrap(); 577 | assert_eq!(expected, deserialized); 578 | } 579 | } 580 | 581 | #[test] 582 | fn test_empty_scalar() { 583 | #[derive(Deserialize, PartialEq, Debug)] 584 | struct Struct { 585 | thing: T, 586 | } 587 | 588 | let yaml = "thing:\n"; 589 | let expected = Struct { 590 | thing: serde_norway::Sequence::new(), 591 | }; 592 | test_de(yaml, &expected); 593 | 594 | let expected = Struct { 595 | thing: serde_norway::Mapping::new(), 596 | }; 597 | test_de(yaml, &expected); 598 | } 599 | 600 | #[test] 601 | fn test_python_safe_dump() { 602 | #[derive(Deserialize, PartialEq, Debug)] 603 | struct Frob { 604 | foo: u32, 605 | } 606 | 607 | // This matches output produced by PyYAML's `yaml.safe_dump` when using the 608 | // default_style parameter. 609 | // 610 | // >>> import yaml 611 | // >>> d = {"foo": 7200} 612 | // >>> print(yaml.safe_dump(d, default_style="|")) 613 | // "foo": !!int |- 614 | // 7200 615 | // 616 | let yaml = indoc! {r#" 617 | "foo": !!int |- 618 | 7200 619 | "#}; 620 | 621 | let expected = Frob { foo: 7200 }; 622 | test_de(yaml, &expected); 623 | } 624 | 625 | #[test] 626 | fn test_tag_resolution() { 627 | // https://yaml.org/spec/1.2.2/#1032-tag-resolution 628 | let yaml = indoc! {" 629 | - null 630 | - Null 631 | - NULL 632 | - ~ 633 | - 634 | - true 635 | - True 636 | - TRUE 637 | - false 638 | - False 639 | - FALSE 640 | - y 641 | - Y 642 | - yes 643 | - Yes 644 | - YES 645 | - n 646 | - N 647 | - no 648 | - No 649 | - NO 650 | - on 651 | - On 652 | - ON 653 | - off 654 | - Off 655 | - OFF 656 | "}; 657 | 658 | let expected = vec![ 659 | Value::Null, 660 | Value::Null, 661 | Value::Null, 662 | Value::Null, 663 | Value::Null, 664 | Value::Bool(true), 665 | Value::Bool(true), 666 | Value::Bool(true), 667 | Value::Bool(false), 668 | Value::Bool(false), 669 | Value::Bool(false), 670 | Value::String("y".to_owned()), 671 | Value::String("Y".to_owned()), 672 | Value::String("yes".to_owned()), 673 | Value::String("Yes".to_owned()), 674 | Value::String("YES".to_owned()), 675 | Value::String("n".to_owned()), 676 | Value::String("N".to_owned()), 677 | Value::String("no".to_owned()), 678 | Value::String("No".to_owned()), 679 | Value::String("NO".to_owned()), 680 | Value::String("on".to_owned()), 681 | Value::String("On".to_owned()), 682 | Value::String("ON".to_owned()), 683 | Value::String("off".to_owned()), 684 | Value::String("Off".to_owned()), 685 | Value::String("OFF".to_owned()), 686 | ]; 687 | 688 | test_de(yaml, &expected); 689 | } 690 | 691 | #[test] 692 | fn test_parse_number() { 693 | let n = "111".parse::().unwrap(); 694 | assert_eq!(n, Number::from(111)); 695 | 696 | let n = "-111".parse::().unwrap(); 697 | assert_eq!(n, Number::from(-111)); 698 | 699 | let n = "-1.1".parse::().unwrap(); 700 | assert_eq!(n, Number::from(-1.1)); 701 | 702 | let n = ".nan".parse::().unwrap(); 703 | assert_eq!(n, Number::from(f64::NAN)); 704 | assert!(n.as_f64().unwrap().is_sign_positive()); 705 | 706 | let n = ".inf".parse::().unwrap(); 707 | assert_eq!(n, Number::from(f64::INFINITY)); 708 | 709 | let n = "-.inf".parse::().unwrap(); 710 | assert_eq!(n, Number::from(f64::NEG_INFINITY)); 711 | 712 | let err = "null".parse::().unwrap_err(); 713 | assert_eq!(err.to_string(), "failed to parse YAML number"); 714 | 715 | let err = " 1 ".parse::().unwrap_err(); 716 | assert_eq!(err.to_string(), "failed to parse YAML number"); 717 | } 718 | -------------------------------------------------------------------------------- /src/ser.rs: -------------------------------------------------------------------------------- 1 | //! YAML Serialization 2 | //! 3 | //! This module provides YAML serialization with the type `Serializer`. 4 | 5 | use crate::error::{self, Error, ErrorImpl}; 6 | use crate::libyaml; 7 | use crate::libyaml::emitter::{Emitter, Event, Mapping, Scalar, ScalarStyle, Sequence}; 8 | use crate::value::tagged::{self, MaybeTag}; 9 | use serde::de::Visitor; 10 | use serde::ser::{self, Serializer as _}; 11 | use std::fmt::{self, Display}; 12 | use std::io; 13 | use std::marker::PhantomData; 14 | use std::mem; 15 | use std::num; 16 | use std::str; 17 | 18 | type Result = std::result::Result; 19 | 20 | /// A structure for serializing Rust values into YAML. 21 | /// 22 | /// # Example 23 | /// 24 | /// ``` 25 | /// use anyhow::Result; 26 | /// use serde::Serialize; 27 | /// use std::collections::BTreeMap; 28 | /// 29 | /// fn main() -> Result<()> { 30 | /// let mut buffer = Vec::new(); 31 | /// let mut ser = serde_norway::Serializer::new(&mut buffer); 32 | /// 33 | /// let mut object = BTreeMap::new(); 34 | /// object.insert("k", 107); 35 | /// object.serialize(&mut ser)?; 36 | /// 37 | /// object.insert("J", 74); 38 | /// object.serialize(&mut ser)?; 39 | /// 40 | /// assert_eq!(buffer, b"k: 107\n---\nJ: 74\nk: 107\n"); 41 | /// Ok(()) 42 | /// } 43 | /// ``` 44 | pub struct Serializer { 45 | depth: usize, 46 | state: State, 47 | emitter: Emitter<'static>, 48 | writer: PhantomData, 49 | } 50 | 51 | enum State { 52 | NothingInParticular, 53 | CheckForTag, 54 | CheckForDuplicateTag, 55 | FoundTag(String), 56 | AlreadyTagged, 57 | } 58 | 59 | impl Serializer 60 | where 61 | W: io::Write, 62 | { 63 | /// Creates a new YAML serializer. 64 | pub fn new(writer: W) -> Self { 65 | let mut emitter = Emitter::new({ 66 | let writer = Box::new(writer); 67 | unsafe { mem::transmute::, Box>(writer) } 68 | }); 69 | emitter.emit(Event::StreamStart).unwrap(); 70 | Serializer { 71 | depth: 0, 72 | state: State::NothingInParticular, 73 | emitter, 74 | writer: PhantomData, 75 | } 76 | } 77 | 78 | /// Calls [`.flush()`](io::Write::flush) on the underlying `io::Write` 79 | /// object. 80 | pub fn flush(&mut self) -> Result<()> { 81 | self.emitter.flush()?; 82 | Ok(()) 83 | } 84 | 85 | /// Unwrap the underlying `io::Write` object from the `Serializer`. 86 | pub fn into_inner(mut self) -> Result { 87 | self.emitter.emit(Event::StreamEnd)?; 88 | self.emitter.flush()?; 89 | let writer = self.emitter.into_inner(); 90 | Ok(*unsafe { Box::from_raw(Box::into_raw(writer).cast::()) }) 91 | } 92 | 93 | fn emit_scalar(&mut self, mut scalar: Scalar) -> Result<()> { 94 | self.flush_mapping_start()?; 95 | if let Some(tag) = self.take_tag() { 96 | scalar.tag = Some(tag); 97 | } 98 | self.value_start()?; 99 | self.emitter.emit(Event::Scalar(scalar))?; 100 | self.value_end() 101 | } 102 | 103 | fn emit_sequence_start(&mut self) -> Result<()> { 104 | self.flush_mapping_start()?; 105 | self.value_start()?; 106 | let tag = self.take_tag(); 107 | self.emitter.emit(Event::SequenceStart(Sequence { tag }))?; 108 | Ok(()) 109 | } 110 | 111 | fn emit_sequence_end(&mut self) -> Result<()> { 112 | self.emitter.emit(Event::SequenceEnd)?; 113 | self.value_end() 114 | } 115 | 116 | fn emit_mapping_start(&mut self) -> Result<()> { 117 | self.flush_mapping_start()?; 118 | self.value_start()?; 119 | let tag = self.take_tag(); 120 | self.emitter.emit(Event::MappingStart(Mapping { tag }))?; 121 | Ok(()) 122 | } 123 | 124 | fn emit_mapping_end(&mut self) -> Result<()> { 125 | self.emitter.emit(Event::MappingEnd)?; 126 | self.value_end() 127 | } 128 | 129 | fn value_start(&mut self) -> Result<()> { 130 | if self.depth == 0 { 131 | self.emitter.emit(Event::DocumentStart)?; 132 | } 133 | self.depth += 1; 134 | Ok(()) 135 | } 136 | 137 | fn value_end(&mut self) -> Result<()> { 138 | self.depth -= 1; 139 | if self.depth == 0 { 140 | self.emitter.emit(Event::DocumentEnd)?; 141 | } 142 | Ok(()) 143 | } 144 | 145 | fn take_tag(&mut self) -> Option { 146 | let state = mem::replace(&mut self.state, State::NothingInParticular); 147 | if let State::FoundTag(mut tag) = state { 148 | if !tag.starts_with('!') { 149 | tag.insert(0, '!'); 150 | } 151 | Some(tag) 152 | } else { 153 | self.state = state; 154 | None 155 | } 156 | } 157 | 158 | fn flush_mapping_start(&mut self) -> Result<()> { 159 | if let State::CheckForTag = self.state { 160 | self.state = State::NothingInParticular; 161 | self.emit_mapping_start()?; 162 | } else if let State::CheckForDuplicateTag = self.state { 163 | self.state = State::NothingInParticular; 164 | } 165 | Ok(()) 166 | } 167 | } 168 | 169 | impl<'a, W> ser::Serializer for &'a mut Serializer 170 | where 171 | W: io::Write, 172 | { 173 | type Ok = (); 174 | type Error = Error; 175 | 176 | type SerializeSeq = Self; 177 | type SerializeTuple = Self; 178 | type SerializeTupleStruct = Self; 179 | type SerializeTupleVariant = Self; 180 | type SerializeMap = Self; 181 | type SerializeStruct = Self; 182 | type SerializeStructVariant = Self; 183 | 184 | fn serialize_bool(self, v: bool) -> Result<()> { 185 | self.emit_scalar(Scalar { 186 | tag: None, 187 | value: if v { "true" } else { "false" }, 188 | style: ScalarStyle::Plain, 189 | }) 190 | } 191 | 192 | fn serialize_i8(self, v: i8) -> Result<()> { 193 | self.emit_scalar(Scalar { 194 | tag: None, 195 | value: itoa::Buffer::new().format(v), 196 | style: ScalarStyle::Plain, 197 | }) 198 | } 199 | 200 | fn serialize_i16(self, v: i16) -> Result<()> { 201 | self.emit_scalar(Scalar { 202 | tag: None, 203 | value: itoa::Buffer::new().format(v), 204 | style: ScalarStyle::Plain, 205 | }) 206 | } 207 | 208 | fn serialize_i32(self, v: i32) -> Result<()> { 209 | self.emit_scalar(Scalar { 210 | tag: None, 211 | value: itoa::Buffer::new().format(v), 212 | style: ScalarStyle::Plain, 213 | }) 214 | } 215 | 216 | fn serialize_i64(self, v: i64) -> Result<()> { 217 | self.emit_scalar(Scalar { 218 | tag: None, 219 | value: itoa::Buffer::new().format(v), 220 | style: ScalarStyle::Plain, 221 | }) 222 | } 223 | 224 | fn serialize_i128(self, v: i128) -> Result<()> { 225 | self.emit_scalar(Scalar { 226 | tag: None, 227 | value: itoa::Buffer::new().format(v), 228 | style: ScalarStyle::Plain, 229 | }) 230 | } 231 | 232 | fn serialize_u8(self, v: u8) -> Result<()> { 233 | self.emit_scalar(Scalar { 234 | tag: None, 235 | value: itoa::Buffer::new().format(v), 236 | style: ScalarStyle::Plain, 237 | }) 238 | } 239 | 240 | fn serialize_u16(self, v: u16) -> Result<()> { 241 | self.emit_scalar(Scalar { 242 | tag: None, 243 | value: itoa::Buffer::new().format(v), 244 | style: ScalarStyle::Plain, 245 | }) 246 | } 247 | 248 | fn serialize_u32(self, v: u32) -> Result<()> { 249 | self.emit_scalar(Scalar { 250 | tag: None, 251 | value: itoa::Buffer::new().format(v), 252 | style: ScalarStyle::Plain, 253 | }) 254 | } 255 | 256 | fn serialize_u64(self, v: u64) -> Result<()> { 257 | self.emit_scalar(Scalar { 258 | tag: None, 259 | value: itoa::Buffer::new().format(v), 260 | style: ScalarStyle::Plain, 261 | }) 262 | } 263 | 264 | fn serialize_u128(self, v: u128) -> Result<()> { 265 | self.emit_scalar(Scalar { 266 | tag: None, 267 | value: itoa::Buffer::new().format(v), 268 | style: ScalarStyle::Plain, 269 | }) 270 | } 271 | 272 | fn serialize_f32(self, v: f32) -> Result<()> { 273 | let mut buffer = ryu::Buffer::new(); 274 | self.emit_scalar(Scalar { 275 | tag: None, 276 | value: match v.classify() { 277 | num::FpCategory::Infinite if v.is_sign_positive() => ".inf", 278 | num::FpCategory::Infinite => "-.inf", 279 | num::FpCategory::Nan => ".nan", 280 | _ => buffer.format_finite(v), 281 | }, 282 | style: ScalarStyle::Plain, 283 | }) 284 | } 285 | 286 | fn serialize_f64(self, v: f64) -> Result<()> { 287 | let mut buffer = ryu::Buffer::new(); 288 | self.emit_scalar(Scalar { 289 | tag: None, 290 | value: match v.classify() { 291 | num::FpCategory::Infinite if v.is_sign_positive() => ".inf", 292 | num::FpCategory::Infinite => "-.inf", 293 | num::FpCategory::Nan => ".nan", 294 | _ => buffer.format_finite(v), 295 | }, 296 | style: ScalarStyle::Plain, 297 | }) 298 | } 299 | 300 | fn serialize_char(self, value: char) -> Result<()> { 301 | self.emit_scalar(Scalar { 302 | tag: None, 303 | value: value.encode_utf8(&mut [0u8; 4]), 304 | style: ScalarStyle::SingleQuoted, 305 | }) 306 | } 307 | 308 | fn serialize_str(self, value: &str) -> Result<()> { 309 | struct InferScalarStyle; 310 | 311 | impl<'de> Visitor<'de> for InferScalarStyle { 312 | type Value = ScalarStyle; 313 | 314 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 315 | formatter.write_str("I wonder") 316 | } 317 | 318 | fn visit_bool(self, _v: bool) -> Result { 319 | Ok(ScalarStyle::SingleQuoted) 320 | } 321 | 322 | fn visit_i64(self, _v: i64) -> Result { 323 | Ok(ScalarStyle::SingleQuoted) 324 | } 325 | 326 | fn visit_i128(self, _v: i128) -> Result { 327 | Ok(ScalarStyle::SingleQuoted) 328 | } 329 | 330 | fn visit_u64(self, _v: u64) -> Result { 331 | Ok(ScalarStyle::SingleQuoted) 332 | } 333 | 334 | fn visit_u128(self, _v: u128) -> Result { 335 | Ok(ScalarStyle::SingleQuoted) 336 | } 337 | 338 | fn visit_f64(self, _v: f64) -> Result { 339 | Ok(ScalarStyle::SingleQuoted) 340 | } 341 | 342 | fn visit_str(self, v: &str) -> Result { 343 | Ok(if crate::de::digits_but_not_number(v) { 344 | ScalarStyle::SingleQuoted 345 | } else { 346 | ScalarStyle::Any 347 | }) 348 | } 349 | 350 | fn visit_unit(self) -> Result { 351 | Ok(ScalarStyle::SingleQuoted) 352 | } 353 | } 354 | 355 | let style = if value.contains('\n') { 356 | ScalarStyle::Literal 357 | } else { 358 | let result = crate::de::visit_untagged_scalar( 359 | InferScalarStyle, 360 | value, 361 | None, 362 | libyaml::parser::ScalarStyle::Plain, 363 | ); 364 | result.unwrap_or(ScalarStyle::Any) 365 | }; 366 | 367 | self.emit_scalar(Scalar { 368 | tag: None, 369 | value, 370 | style, 371 | }) 372 | } 373 | 374 | fn serialize_bytes(self, _value: &[u8]) -> Result<()> { 375 | Err(error::new(ErrorImpl::BytesUnsupported)) 376 | } 377 | 378 | fn serialize_unit(self) -> Result<()> { 379 | self.emit_scalar(Scalar { 380 | tag: None, 381 | value: "null", 382 | style: ScalarStyle::Plain, 383 | }) 384 | } 385 | 386 | fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { 387 | self.serialize_unit() 388 | } 389 | 390 | fn serialize_unit_variant( 391 | self, 392 | _name: &'static str, 393 | _variant_index: u32, 394 | variant: &'static str, 395 | ) -> Result<()> { 396 | self.serialize_str(variant) 397 | } 398 | 399 | fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result<()> 400 | where 401 | T: ?Sized + ser::Serialize, 402 | { 403 | value.serialize(self) 404 | } 405 | 406 | fn serialize_newtype_variant( 407 | self, 408 | _name: &'static str, 409 | _variant_index: u32, 410 | variant: &'static str, 411 | value: &T, 412 | ) -> Result<()> 413 | where 414 | T: ?Sized + ser::Serialize, 415 | { 416 | if let State::FoundTag(_) = self.state { 417 | return Err(error::new(ErrorImpl::SerializeNestedEnum)); 418 | } 419 | self.state = State::FoundTag(variant.to_owned()); 420 | value.serialize(&mut *self) 421 | } 422 | 423 | fn serialize_none(self) -> Result<()> { 424 | self.serialize_unit() 425 | } 426 | 427 | fn serialize_some(self, value: &V) -> Result<()> 428 | where 429 | V: ?Sized + ser::Serialize, 430 | { 431 | value.serialize(self) 432 | } 433 | 434 | fn serialize_seq(self, _len: Option) -> Result { 435 | self.emit_sequence_start()?; 436 | Ok(self) 437 | } 438 | 439 | fn serialize_tuple(self, _len: usize) -> Result { 440 | self.emit_sequence_start()?; 441 | Ok(self) 442 | } 443 | 444 | fn serialize_tuple_struct( 445 | self, 446 | _name: &'static str, 447 | _len: usize, 448 | ) -> Result { 449 | self.emit_sequence_start()?; 450 | Ok(self) 451 | } 452 | 453 | fn serialize_tuple_variant( 454 | self, 455 | _enm: &'static str, 456 | _idx: u32, 457 | variant: &'static str, 458 | _len: usize, 459 | ) -> Result { 460 | if let State::FoundTag(_) = self.state { 461 | return Err(error::new(ErrorImpl::SerializeNestedEnum)); 462 | } 463 | self.state = State::FoundTag(variant.to_owned()); 464 | self.emit_sequence_start()?; 465 | Ok(self) 466 | } 467 | 468 | fn serialize_map(self, len: Option) -> Result { 469 | if len == Some(1) { 470 | self.state = if let State::FoundTag(_) = self.state { 471 | self.emit_mapping_start()?; 472 | State::CheckForDuplicateTag 473 | } else { 474 | State::CheckForTag 475 | }; 476 | } else { 477 | self.emit_mapping_start()?; 478 | } 479 | Ok(self) 480 | } 481 | 482 | fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { 483 | self.emit_mapping_start()?; 484 | Ok(self) 485 | } 486 | 487 | fn serialize_struct_variant( 488 | self, 489 | _enm: &'static str, 490 | _idx: u32, 491 | variant: &'static str, 492 | _len: usize, 493 | ) -> Result { 494 | if let State::FoundTag(_) = self.state { 495 | return Err(error::new(ErrorImpl::SerializeNestedEnum)); 496 | } 497 | self.state = State::FoundTag(variant.to_owned()); 498 | self.emit_mapping_start()?; 499 | Ok(self) 500 | } 501 | 502 | fn collect_str(self, value: &T) -> Result 503 | where 504 | T: ?Sized + Display, 505 | { 506 | let string = if let State::CheckForTag | State::CheckForDuplicateTag = self.state { 507 | match tagged::check_for_tag(value) { 508 | MaybeTag::NotTag(string) => string, 509 | MaybeTag::Tag(string) => { 510 | return if let State::CheckForDuplicateTag = self.state { 511 | Err(error::new(ErrorImpl::SerializeNestedEnum)) 512 | } else { 513 | self.state = State::FoundTag(string); 514 | Ok(()) 515 | }; 516 | } 517 | } 518 | } else { 519 | value.to_string() 520 | }; 521 | 522 | self.serialize_str(&string) 523 | } 524 | } 525 | 526 | impl<'a, W> ser::SerializeSeq for &'a mut Serializer 527 | where 528 | W: io::Write, 529 | { 530 | type Ok = (); 531 | type Error = Error; 532 | 533 | fn serialize_element(&mut self, elem: &T) -> Result<()> 534 | where 535 | T: ?Sized + ser::Serialize, 536 | { 537 | elem.serialize(&mut **self) 538 | } 539 | 540 | fn end(self) -> Result<()> { 541 | self.emit_sequence_end() 542 | } 543 | } 544 | 545 | impl<'a, W> ser::SerializeTuple for &'a mut Serializer 546 | where 547 | W: io::Write, 548 | { 549 | type Ok = (); 550 | type Error = Error; 551 | 552 | fn serialize_element(&mut self, elem: &T) -> Result<()> 553 | where 554 | T: ?Sized + ser::Serialize, 555 | { 556 | elem.serialize(&mut **self) 557 | } 558 | 559 | fn end(self) -> Result<()> { 560 | self.emit_sequence_end() 561 | } 562 | } 563 | 564 | impl<'a, W> ser::SerializeTupleStruct for &'a mut Serializer 565 | where 566 | W: io::Write, 567 | { 568 | type Ok = (); 569 | type Error = Error; 570 | 571 | fn serialize_field(&mut self, value: &V) -> Result<()> 572 | where 573 | V: ?Sized + ser::Serialize, 574 | { 575 | value.serialize(&mut **self) 576 | } 577 | 578 | fn end(self) -> Result<()> { 579 | self.emit_sequence_end() 580 | } 581 | } 582 | 583 | impl<'a, W> ser::SerializeTupleVariant for &'a mut Serializer 584 | where 585 | W: io::Write, 586 | { 587 | type Ok = (); 588 | type Error = Error; 589 | 590 | fn serialize_field(&mut self, v: &V) -> Result<()> 591 | where 592 | V: ?Sized + ser::Serialize, 593 | { 594 | v.serialize(&mut **self) 595 | } 596 | 597 | fn end(self) -> Result<()> { 598 | self.emit_sequence_end() 599 | } 600 | } 601 | 602 | impl<'a, W> ser::SerializeMap for &'a mut Serializer 603 | where 604 | W: io::Write, 605 | { 606 | type Ok = (); 607 | type Error = Error; 608 | 609 | fn serialize_key(&mut self, key: &T) -> Result<()> 610 | where 611 | T: ?Sized + ser::Serialize, 612 | { 613 | self.flush_mapping_start()?; 614 | key.serialize(&mut **self) 615 | } 616 | 617 | fn serialize_value(&mut self, value: &T) -> Result<()> 618 | where 619 | T: ?Sized + ser::Serialize, 620 | { 621 | value.serialize(&mut **self) 622 | } 623 | 624 | fn serialize_entry(&mut self, key: &K, value: &V) -> Result<(), Self::Error> 625 | where 626 | K: ?Sized + ser::Serialize, 627 | V: ?Sized + ser::Serialize, 628 | { 629 | key.serialize(&mut **self)?; 630 | let tagged = matches!(self.state, State::FoundTag(_)); 631 | value.serialize(&mut **self)?; 632 | if tagged { 633 | self.state = State::AlreadyTagged; 634 | } 635 | Ok(()) 636 | } 637 | 638 | fn end(self) -> Result<()> { 639 | if let State::CheckForTag = self.state { 640 | self.emit_mapping_start()?; 641 | } 642 | if !matches!(self.state, State::AlreadyTagged) { 643 | self.emit_mapping_end()?; 644 | } 645 | self.state = State::NothingInParticular; 646 | Ok(()) 647 | } 648 | } 649 | 650 | impl<'a, W> ser::SerializeStruct for &'a mut Serializer 651 | where 652 | W: io::Write, 653 | { 654 | type Ok = (); 655 | type Error = Error; 656 | 657 | fn serialize_field(&mut self, key: &'static str, value: &V) -> Result<()> 658 | where 659 | V: ?Sized + ser::Serialize, 660 | { 661 | self.serialize_str(key)?; 662 | value.serialize(&mut **self) 663 | } 664 | 665 | fn end(self) -> Result<()> { 666 | self.emit_mapping_end() 667 | } 668 | } 669 | 670 | impl<'a, W> ser::SerializeStructVariant for &'a mut Serializer 671 | where 672 | W: io::Write, 673 | { 674 | type Ok = (); 675 | type Error = Error; 676 | 677 | fn serialize_field(&mut self, field: &'static str, v: &V) -> Result<()> 678 | where 679 | V: ?Sized + ser::Serialize, 680 | { 681 | self.serialize_str(field)?; 682 | v.serialize(&mut **self) 683 | } 684 | 685 | fn end(self) -> Result<()> { 686 | self.emit_mapping_end() 687 | } 688 | } 689 | 690 | /// Serialize the given data structure as YAML into the IO stream. 691 | /// 692 | /// Serialization can fail if `T`'s implementation of `Serialize` decides to 693 | /// return an error. 694 | pub fn to_writer(writer: W, value: &T) -> Result<()> 695 | where 696 | W: io::Write, 697 | T: ?Sized + ser::Serialize, 698 | { 699 | let mut serializer = Serializer::new(writer); 700 | value.serialize(&mut serializer) 701 | } 702 | 703 | /// Serialize the given data structure as a String of YAML. 704 | /// 705 | /// Serialization can fail if `T`'s implementation of `Serialize` decides to 706 | /// return an error. 707 | pub fn to_string(value: &T) -> Result 708 | where 709 | T: ?Sized + ser::Serialize, 710 | { 711 | let mut vec = Vec::with_capacity(128); 712 | to_writer(&mut vec, value)?; 713 | String::from_utf8(vec).map_err(|error| error::new(ErrorImpl::FromUtf8(error))) 714 | } 715 | --------------------------------------------------------------------------------