Add @dtolnay's serde-yaml
in subdirectory.
This commit is contained in:
commit
71cb7f162f
38 changed files with 13864 additions and 0 deletions
1
serde/.github/FUNDING.yml
vendored
Normal file
1
serde/.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
github: dtolnay
|
111
serde/.github/workflows/ci.yml
vendored
Normal file
111
serde/.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule: [cron: "40 1 * * *"]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: -Dwarnings
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pre_ci:
|
||||||
|
uses: dtolnay/.github/.github/workflows/pre_ci.yml@master
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Rust ${{matrix.rust}}
|
||||||
|
needs: pre_ci
|
||||||
|
if: needs.pre_ci.outputs.continue
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
rust: [nightly, beta, stable, 1.64.0]
|
||||||
|
timeout-minutes: 45
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@master
|
||||||
|
with:
|
||||||
|
toolchain: ${{matrix.rust}}
|
||||||
|
- name: Enable type layout randomization
|
||||||
|
run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV
|
||||||
|
if: matrix.rust == 'nightly'
|
||||||
|
- run: cargo build
|
||||||
|
- run: cargo test
|
||||||
|
|
||||||
|
doc:
|
||||||
|
name: Documentation
|
||||||
|
needs: pre_ci
|
||||||
|
if: needs.pre_ci.outputs.continue
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 45
|
||||||
|
env:
|
||||||
|
RUSTDOCFLAGS: -Dwarnings
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
|
- uses: dtolnay/install@cargo-docs-rs
|
||||||
|
- run: cargo docs-rs
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
name: Clippy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
timeout-minutes: 45
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@clippy
|
||||||
|
- run: cargo clippy --tests -- -Dclippy::all -Dclippy::pedantic
|
||||||
|
|
||||||
|
miri:
|
||||||
|
name: Miri
|
||||||
|
needs: pre_ci
|
||||||
|
if: needs.pre_ci.outputs.continue
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 45
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@miri
|
||||||
|
- run: cargo miri setup
|
||||||
|
- run: cargo miri test
|
||||||
|
env:
|
||||||
|
MIRIFLAGS: -Zmiri-strict-provenance
|
||||||
|
|
||||||
|
minimal:
|
||||||
|
name: Minimal versions
|
||||||
|
needs: pre_ci
|
||||||
|
if: needs.pre_ci.outputs.continue
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 45
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
|
- run: cargo generate-lockfile -Z minimal-versions
|
||||||
|
- run: cargo check --locked
|
||||||
|
|
||||||
|
fuzz:
|
||||||
|
name: Fuzz
|
||||||
|
needs: pre_ci
|
||||||
|
if: needs.pre_ci.outputs.continue
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 45
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
|
- uses: dtolnay/install@cargo-fuzz
|
||||||
|
- run: cargo fuzz check
|
||||||
|
|
||||||
|
outdated:
|
||||||
|
name: Outdated
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
timeout-minutes: 45
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/install@cargo-outdated
|
||||||
|
- run: cargo outdated --workspace --exit-code 1
|
||||||
|
- run: cargo outdated --manifest-path fuzz/Cargo.toml --exit-code 1
|
2
serde/.gitignore
vendored
Normal file
2
serde/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
target
|
||||||
|
Cargo.lock
|
31
serde/Cargo.toml
Normal file
31
serde/Cargo.toml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[package]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.9.34+deprecated"
|
||||||
|
authors = ["David Tolnay <dtolnay@gmail.com>"]
|
||||||
|
categories = ["encoding", "parser-implementations"]
|
||||||
|
description = "YAML data format for Serde"
|
||||||
|
documentation = "https://docs.rs/serde_yaml/"
|
||||||
|
edition = "2021"
|
||||||
|
keywords = ["yaml", "serde", "serialization"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
repository = "https://github.com/dtolnay/serde-yaml"
|
||||||
|
rust-version = "1.64"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
indexmap = "2.2.1"
|
||||||
|
itoa = "1.0"
|
||||||
|
ryu = "1.0"
|
||||||
|
serde = "1.0.195"
|
||||||
|
unsafe-libyaml = "0.2.11"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
anyhow = "1.0.79"
|
||||||
|
indoc = "2.0"
|
||||||
|
serde_derive = "1.0.195"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
doc-scrape-examples = false
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
targets = ["x86_64-unknown-linux-gnu"]
|
||||||
|
rustdoc-args = ["--generate-link-to-definition"]
|
176
serde/LICENSE-APACHE
Normal file
176
serde/LICENSE-APACHE
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
23
serde/LICENSE-MIT
Normal file
23
serde/LICENSE-MIT
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
152
serde/README.md
Normal file
152
serde/README.md
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
Serde YAML
|
||||||
|
==========
|
||||||
|
|
||||||
|
[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/serde--yaml-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/serde-yaml)
|
||||||
|
[<img alt="crates.io" src="https://img.shields.io/crates/v/serde_yaml.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/serde_yaml)
|
||||||
|
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-serde__yaml-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/serde_yaml)
|
||||||
|
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/serde-yaml/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/serde-yaml/actions?query=branch%3Amaster)
|
||||||
|
|
||||||
|
Rust library for using the [Serde] serialization framework with data in [YAML]
|
||||||
|
file format. _(This project is no longer maintained.)_
|
||||||
|
|
||||||
|
[Serde]: https://github.com/serde-rs/serde
|
||||||
|
[YAML]: https://yaml.org/
|
||||||
|
|
||||||
|
## Dependency
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
serde = "1.0"
|
||||||
|
serde_yaml = "0.9"
|
||||||
|
```
|
||||||
|
|
||||||
|
Release notes are available under [GitHub releases].
|
||||||
|
|
||||||
|
[GitHub releases]: https://github.com/dtolnay/serde-yaml/releases
|
||||||
|
|
||||||
|
## Using Serde YAML
|
||||||
|
|
||||||
|
[API documentation is available in rustdoc form][docs.rs] but the general idea
|
||||||
|
is:
|
||||||
|
|
||||||
|
[docs.rs]: https://docs.rs/serde_yaml
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
fn main() -> Result<(), serde_yaml::Error> {
|
||||||
|
// You have some type.
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
map.insert("x".to_string(), 1.0);
|
||||||
|
map.insert("y".to_string(), 2.0);
|
||||||
|
|
||||||
|
// Serialize it to a YAML string.
|
||||||
|
let yaml = serde_yaml::to_string(&map)?;
|
||||||
|
assert_eq!(yaml, "x: 1.0\ny: 2.0\n");
|
||||||
|
|
||||||
|
// Deserialize it back to a Rust type.
|
||||||
|
let deserialized_map: BTreeMap<String, f64> = serde_yaml::from_str(&yaml)?;
|
||||||
|
assert_eq!(map, deserialized_map);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It can also be used with Serde's derive macros to handle structs and enums
|
||||||
|
defined in your program.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_yaml = "0.9"
|
||||||
|
```
|
||||||
|
|
||||||
|
Structs serialize in the obvious way:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
struct Point {
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), serde_yaml::Error> {
|
||||||
|
let point = Point { x: 1.0, y: 2.0 };
|
||||||
|
|
||||||
|
let yaml = serde_yaml::to_string(&point)?;
|
||||||
|
assert_eq!(yaml, "x: 1.0\ny: 2.0\n");
|
||||||
|
|
||||||
|
let deserialized_point: Point = serde_yaml::from_str(&yaml)?;
|
||||||
|
assert_eq!(point, deserialized_point);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Enums serialize using YAML's `!tag` syntax to identify the variant name.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
enum Enum {
|
||||||
|
Unit,
|
||||||
|
Newtype(usize),
|
||||||
|
Tuple(usize, usize, usize),
|
||||||
|
Struct { x: f64, y: f64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), serde_yaml::Error> {
|
||||||
|
let yaml = "
|
||||||
|
- !Newtype 1
|
||||||
|
- !Tuple [0, 0, 0]
|
||||||
|
- !Struct {x: 1.0, y: 2.0}
|
||||||
|
";
|
||||||
|
let values: Vec<Enum> = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
assert_eq!(values[0], Enum::Newtype(1));
|
||||||
|
assert_eq!(values[1], Enum::Tuple(0, 0, 0));
|
||||||
|
assert_eq!(values[2], Enum::Struct { x: 1.0, y: 2.0 });
|
||||||
|
|
||||||
|
// The last two in YAML's block style instead:
|
||||||
|
let yaml = "
|
||||||
|
- !Tuple
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
- !Struct
|
||||||
|
x: 1.0
|
||||||
|
y: 2.0
|
||||||
|
";
|
||||||
|
let values: Vec<Enum> = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
assert_eq!(values[0], Enum::Tuple(0, 0, 0));
|
||||||
|
assert_eq!(values[1], Enum::Struct { x: 1.0, y: 2.0 });
|
||||||
|
|
||||||
|
// Variants with no data can be written using !Tag or just the string name.
|
||||||
|
let yaml = "
|
||||||
|
- Unit # serialization produces this one
|
||||||
|
- !Unit
|
||||||
|
";
|
||||||
|
let values: Vec<Enum> = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
assert_eq!(values[0], Enum::Unit);
|
||||||
|
assert_eq!(values[1], Enum::Unit);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
#### License
|
||||||
|
|
||||||
|
<sup>
|
||||||
|
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||||
|
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||||
|
</sup>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<sub>
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
|
||||||
|
be dual licensed as above, without any additional terms or conditions.
|
||||||
|
</sub>
|
4
serde/fuzz/.gitignore
vendored
Normal file
4
serde/fuzz/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
artifacts/
|
||||||
|
corpus/
|
||||||
|
coverage/
|
||||||
|
target/
|
21
serde/fuzz/Cargo.toml
Normal file
21
serde/fuzz/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "serde_yaml-fuzz"
|
||||||
|
version = "0.0.0"
|
||||||
|
authors = ["David Tolnay <dtolnay@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
cargo-fuzz = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libfuzzer-sys = "0.4"
|
||||||
|
serde_yaml = { path = ".." }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fuzz_from_slice"
|
||||||
|
path = "fuzz_targets/fuzz_from_slice.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
[workspace]
|
9
serde/fuzz/fuzz_targets/fuzz_from_slice.rs
Normal file
9
serde/fuzz/fuzz_targets/fuzz_from_slice.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
if data.len() <= 10240 {
|
||||||
|
_ = serde_yaml::from_slice::<serde_yaml::Value>(data);
|
||||||
|
}
|
||||||
|
});
|
1846
serde/src/de.rs
Normal file
1846
serde/src/de.rs
Normal file
File diff suppressed because it is too large
Load diff
290
serde/src/error.rs
Normal file
290
serde/src/error.rs
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
use crate::libyaml::{emitter, error as libyaml};
|
||||||
|
use crate::path::Path;
|
||||||
|
use serde::{de, ser};
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt::{self, Debug, Display};
|
||||||
|
use std::io;
|
||||||
|
use std::result;
|
||||||
|
use std::string;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// An error that happened serializing or deserializing YAML data.
|
||||||
|
pub struct Error(Box<ErrorImpl>);
|
||||||
|
|
||||||
|
/// Alias for a `Result` with the error type `serde_yaml::Error`.
|
||||||
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum ErrorImpl {
|
||||||
|
Message(String, Option<Pos>),
|
||||||
|
|
||||||
|
Libyaml(libyaml::Error),
|
||||||
|
Io(io::Error),
|
||||||
|
FromUtf8(string::FromUtf8Error),
|
||||||
|
|
||||||
|
EndOfStream,
|
||||||
|
MoreThanOneDocument,
|
||||||
|
RecursionLimitExceeded(libyaml::Mark),
|
||||||
|
RepetitionLimitExceeded,
|
||||||
|
BytesUnsupported,
|
||||||
|
UnknownAnchor(libyaml::Mark),
|
||||||
|
SerializeNestedEnum,
|
||||||
|
ScalarInMerge,
|
||||||
|
TaggedInMerge,
|
||||||
|
ScalarInMergeElement,
|
||||||
|
SequenceInMergeElement,
|
||||||
|
EmptyTag,
|
||||||
|
FailedToParseNumber,
|
||||||
|
|
||||||
|
Shared(Arc<ErrorImpl>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Pos {
|
||||||
|
mark: libyaml::Mark,
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The input location that an error occured.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Location {
|
||||||
|
index: usize,
|
||||||
|
line: usize,
|
||||||
|
column: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Location {
|
||||||
|
/// The byte index of the error
|
||||||
|
pub fn index(&self) -> usize {
|
||||||
|
self.index
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The line of the error
|
||||||
|
pub fn line(&self) -> usize {
|
||||||
|
self.line
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The column of the error
|
||||||
|
pub fn column(&self) -> usize {
|
||||||
|
self.column
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is to keep decoupled with the yaml crate
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn from_mark(mark: libyaml::Mark) -> Self {
|
||||||
|
Location {
|
||||||
|
index: mark.index() as usize,
|
||||||
|
// `line` and `column` returned from libyaml are 0-indexed but all error messages add +1 to this value
|
||||||
|
line: mark.line() as usize + 1,
|
||||||
|
column: mark.column() as usize + 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
/// Returns the Location from the error if one exists.
|
||||||
|
///
|
||||||
|
/// Not all types of errors have a location so this can return `None`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::{Value, Error};
|
||||||
|
/// #
|
||||||
|
/// // The `@` character as the first character makes this invalid yaml
|
||||||
|
/// let invalid_yaml: Result<Value, Error> = serde_yaml::from_str("@invalid_yaml");
|
||||||
|
///
|
||||||
|
/// let location = invalid_yaml.unwrap_err().location().unwrap();
|
||||||
|
///
|
||||||
|
/// assert_eq!(location.line(), 1);
|
||||||
|
/// assert_eq!(location.column(), 1);
|
||||||
|
/// ```
|
||||||
|
pub fn location(&self) -> Option<Location> {
|
||||||
|
self.0.location()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new(inner: ErrorImpl) -> Error {
|
||||||
|
Error(Box::new(inner))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn shared(shared: Arc<ErrorImpl>) -> Error {
|
||||||
|
Error(Box::new(ErrorImpl::Shared(shared)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fix_mark(mut error: Error, mark: libyaml::Mark, path: Path) -> Error {
|
||||||
|
if let ErrorImpl::Message(_, none @ None) = error.0.as_mut() {
|
||||||
|
*none = Some(Pos {
|
||||||
|
mark,
|
||||||
|
path: path.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub(crate) fn shared(self) -> Arc<ErrorImpl> {
|
||||||
|
if let ErrorImpl::Shared(err) = *self.0 {
|
||||||
|
err
|
||||||
|
} else {
|
||||||
|
Arc::from(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<libyaml::Error> for Error {
|
||||||
|
fn from(err: libyaml::Error) -> Self {
|
||||||
|
Error(Box::new(ErrorImpl::Libyaml(err)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<emitter::Error> for Error {
|
||||||
|
fn from(err: emitter::Error) -> Self {
|
||||||
|
match err {
|
||||||
|
emitter::Error::Libyaml(err) => Self::from(err),
|
||||||
|
emitter::Error::Io(err) => new(ErrorImpl::Io(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StdError for Error {
|
||||||
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
|
self.0.source()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.0.display(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove two layers of verbosity from the debug representation. Humans often
|
||||||
|
// end up seeing this representation because it is what unwrap() shows.
|
||||||
|
impl Debug for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.0.debug(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::Error for Error {
|
||||||
|
fn custom<T: Display>(msg: T) -> Self {
|
||||||
|
Error(Box::new(ErrorImpl::Message(msg.to_string(), None)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl de::Error for Error {
|
||||||
|
fn custom<T: Display>(msg: T) -> Self {
|
||||||
|
Error(Box::new(ErrorImpl::Message(msg.to_string(), None)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorImpl {
|
||||||
|
fn location(&self) -> Option<Location> {
|
||||||
|
self.mark().map(Location::from_mark)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
|
match self {
|
||||||
|
ErrorImpl::Io(err) => err.source(),
|
||||||
|
ErrorImpl::FromUtf8(err) => err.source(),
|
||||||
|
ErrorImpl::Shared(err) => err.source(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark(&self) -> Option<libyaml::Mark> {
|
||||||
|
match self {
|
||||||
|
ErrorImpl::Message(_, Some(Pos { mark, path: _ }))
|
||||||
|
| ErrorImpl::RecursionLimitExceeded(mark)
|
||||||
|
| ErrorImpl::UnknownAnchor(mark) => Some(*mark),
|
||||||
|
ErrorImpl::Libyaml(err) => Some(err.mark()),
|
||||||
|
ErrorImpl::Shared(err) => err.mark(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message_no_mark(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ErrorImpl::Message(msg, None) => f.write_str(msg),
|
||||||
|
ErrorImpl::Message(msg, Some(Pos { mark: _, path })) => {
|
||||||
|
if path != "." {
|
||||||
|
write!(f, "{}: ", path)?;
|
||||||
|
}
|
||||||
|
f.write_str(msg)
|
||||||
|
}
|
||||||
|
ErrorImpl::Libyaml(_) => unreachable!(),
|
||||||
|
ErrorImpl::Io(err) => Display::fmt(err, f),
|
||||||
|
ErrorImpl::FromUtf8(err) => Display::fmt(err, f),
|
||||||
|
ErrorImpl::EndOfStream => f.write_str("EOF while parsing a value"),
|
||||||
|
ErrorImpl::MoreThanOneDocument => f.write_str(
|
||||||
|
"deserializing from YAML containing more than one document is not supported",
|
||||||
|
),
|
||||||
|
ErrorImpl::RecursionLimitExceeded(_mark) => f.write_str("recursion limit exceeded"),
|
||||||
|
ErrorImpl::RepetitionLimitExceeded => f.write_str("repetition limit exceeded"),
|
||||||
|
ErrorImpl::BytesUnsupported => {
|
||||||
|
f.write_str("serialization and deserialization of bytes in YAML is not implemented")
|
||||||
|
}
|
||||||
|
ErrorImpl::UnknownAnchor(_mark) => f.write_str("unknown anchor"),
|
||||||
|
ErrorImpl::SerializeNestedEnum => {
|
||||||
|
f.write_str("serializing nested enums in YAML is not supported yet")
|
||||||
|
}
|
||||||
|
ErrorImpl::ScalarInMerge => {
|
||||||
|
f.write_str("expected a mapping or list of mappings for merging, but found scalar")
|
||||||
|
}
|
||||||
|
ErrorImpl::TaggedInMerge => f.write_str("unexpected tagged value in merge"),
|
||||||
|
ErrorImpl::ScalarInMergeElement => {
|
||||||
|
f.write_str("expected a mapping for merging, but found scalar")
|
||||||
|
}
|
||||||
|
ErrorImpl::SequenceInMergeElement => {
|
||||||
|
f.write_str("expected a mapping for merging, but found sequence")
|
||||||
|
}
|
||||||
|
ErrorImpl::EmptyTag => f.write_str("empty YAML tag is not allowed"),
|
||||||
|
ErrorImpl::FailedToParseNumber => f.write_str("failed to parse YAML number"),
|
||||||
|
ErrorImpl::Shared(_) => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ErrorImpl::Libyaml(err) => Display::fmt(err, f),
|
||||||
|
ErrorImpl::Shared(err) => err.display(f),
|
||||||
|
_ => {
|
||||||
|
self.message_no_mark(f)?;
|
||||||
|
if let Some(mark) = self.mark() {
|
||||||
|
if mark.line() != 0 || mark.column() != 0 {
|
||||||
|
write!(f, " at {}", mark)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ErrorImpl::Libyaml(err) => Debug::fmt(err, f),
|
||||||
|
ErrorImpl::Shared(err) => err.debug(f),
|
||||||
|
_ => {
|
||||||
|
f.write_str("Error(")?;
|
||||||
|
struct MessageNoMark<'a>(&'a ErrorImpl);
|
||||||
|
impl<'a> Display for MessageNoMark<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.0.message_no_mark(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let msg = MessageNoMark(self).to_string();
|
||||||
|
Debug::fmt(&msg, f)?;
|
||||||
|
if let Some(mark) = self.mark() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
", line: {}, column: {}",
|
||||||
|
mark.line() + 1,
|
||||||
|
mark.column() + 1,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
f.write_str(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
191
serde/src/lib.rs
Normal file
191
serde/src/lib.rs
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
//! [![github]](https://github.com/dtolnay/serde-yaml) [![crates-io]](https://crates.io/crates/serde-yaml) [![docs-rs]](https://docs.rs/serde-yaml)
|
||||||
|
//!
|
||||||
|
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
|
||||||
|
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
|
||||||
|
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
|
||||||
|
//!
|
||||||
|
//! <br>
|
||||||
|
//!
|
||||||
|
//! Rust library for using the [Serde] serialization framework with data in
|
||||||
|
//! [YAML] file format. _(This project is no longer maintained.)_
|
||||||
|
//!
|
||||||
|
//! [Serde]: https://github.com/serde-rs/serde
|
||||||
|
//! [YAML]: https://yaml.org/
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use std::collections::BTreeMap;
|
||||||
|
//!
|
||||||
|
//! fn main() -> Result<(), serde_yaml::Error> {
|
||||||
|
//! // You have some type.
|
||||||
|
//! let mut map = BTreeMap::new();
|
||||||
|
//! map.insert("x".to_string(), 1.0);
|
||||||
|
//! map.insert("y".to_string(), 2.0);
|
||||||
|
//!
|
||||||
|
//! // Serialize it to a YAML string.
|
||||||
|
//! let yaml = serde_yaml::to_string(&map)?;
|
||||||
|
//! assert_eq!(yaml, "x: 1.0\ny: 2.0\n");
|
||||||
|
//!
|
||||||
|
//! // Deserialize it back to a Rust type.
|
||||||
|
//! let deserialized_map: BTreeMap<String, f64> = serde_yaml::from_str(&yaml)?;
|
||||||
|
//! assert_eq!(map, deserialized_map);
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Using Serde derive
|
||||||
|
//!
|
||||||
|
//! It can also be used with Serde's derive macros to handle structs and enums
|
||||||
|
//! defined in your program.
|
||||||
|
//!
|
||||||
|
//! Structs serialize in the obvious way:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # use serde_derive::{Serialize, Deserialize};
|
||||||
|
//! use serde::{Serialize, Deserialize};
|
||||||
|
//!
|
||||||
|
//! #[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
//! struct Point {
|
||||||
|
//! x: f64,
|
||||||
|
//! y: f64,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main() -> Result<(), serde_yaml::Error> {
|
||||||
|
//! let point = Point { x: 1.0, y: 2.0 };
|
||||||
|
//!
|
||||||
|
//! let yaml = serde_yaml::to_string(&point)?;
|
||||||
|
//! assert_eq!(yaml, "x: 1.0\ny: 2.0\n");
|
||||||
|
//!
|
||||||
|
//! let deserialized_point: Point = serde_yaml::from_str(&yaml)?;
|
||||||
|
//! assert_eq!(point, deserialized_point);
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Enums serialize using YAML's `!tag` syntax to identify the variant name.
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # use serde_derive::{Serialize, Deserialize};
|
||||||
|
//! use serde::{Serialize, Deserialize};
|
||||||
|
//!
|
||||||
|
//! #[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
//! enum Enum {
|
||||||
|
//! Unit,
|
||||||
|
//! Newtype(usize),
|
||||||
|
//! Tuple(usize, usize, usize),
|
||||||
|
//! Struct { x: f64, y: f64 },
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main() -> Result<(), serde_yaml::Error> {
|
||||||
|
//! let yaml = "
|
||||||
|
//! - !Newtype 1
|
||||||
|
//! - !Tuple [0, 0, 0]
|
||||||
|
//! - !Struct {x: 1.0, y: 2.0}
|
||||||
|
//! ";
|
||||||
|
//! let values: Vec<Enum> = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
//! assert_eq!(values[0], Enum::Newtype(1));
|
||||||
|
//! assert_eq!(values[1], Enum::Tuple(0, 0, 0));
|
||||||
|
//! assert_eq!(values[2], Enum::Struct { x: 1.0, y: 2.0 });
|
||||||
|
//!
|
||||||
|
//! // The last two in YAML's block style instead:
|
||||||
|
//! let yaml = "
|
||||||
|
//! - !Tuple
|
||||||
|
//! - 0
|
||||||
|
//! - 0
|
||||||
|
//! - 0
|
||||||
|
//! - !Struct
|
||||||
|
//! x: 1.0
|
||||||
|
//! y: 2.0
|
||||||
|
//! ";
|
||||||
|
//! let values: Vec<Enum> = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
//! assert_eq!(values[0], Enum::Tuple(0, 0, 0));
|
||||||
|
//! assert_eq!(values[1], Enum::Struct { x: 1.0, y: 2.0 });
|
||||||
|
//!
|
||||||
|
//! // Variants with no data can be written using !Tag or just the string name.
|
||||||
|
//! let yaml = "
|
||||||
|
//! - Unit # serialization produces this one
|
||||||
|
//! - !Unit
|
||||||
|
//! ";
|
||||||
|
//! let values: Vec<Enum> = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
//! assert_eq!(values[0], Enum::Unit);
|
||||||
|
//! assert_eq!(values[1], Enum::Unit);
|
||||||
|
//!
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
#![doc(html_root_url = "https://docs.rs/serde_yaml/0.9.34+deprecated")]
|
||||||
|
#![deny(missing_docs, unsafe_op_in_unsafe_fn)]
|
||||||
|
// Suppressed clippy_pedantic lints
|
||||||
|
#![allow(
|
||||||
|
// buggy
|
||||||
|
clippy::iter_not_returning_iterator, // https://github.com/rust-lang/rust-clippy/issues/8285
|
||||||
|
clippy::ptr_arg, // https://github.com/rust-lang/rust-clippy/issues/9218
|
||||||
|
clippy::question_mark, // https://github.com/rust-lang/rust-clippy/issues/7859
|
||||||
|
// private Deserializer::next
|
||||||
|
clippy::should_implement_trait,
|
||||||
|
// things are often more readable this way
|
||||||
|
clippy::cast_lossless,
|
||||||
|
clippy::checked_conversions,
|
||||||
|
clippy::if_not_else,
|
||||||
|
clippy::manual_assert,
|
||||||
|
clippy::match_like_matches_macro,
|
||||||
|
clippy::match_same_arms,
|
||||||
|
clippy::module_name_repetitions,
|
||||||
|
clippy::needless_pass_by_value,
|
||||||
|
clippy::redundant_else,
|
||||||
|
clippy::single_match_else,
|
||||||
|
// code is acceptable
|
||||||
|
clippy::blocks_in_conditions,
|
||||||
|
clippy::cast_possible_truncation,
|
||||||
|
clippy::cast_possible_wrap,
|
||||||
|
clippy::cast_precision_loss,
|
||||||
|
clippy::cast_sign_loss,
|
||||||
|
clippy::derive_partial_eq_without_eq,
|
||||||
|
clippy::derived_hash_with_manual_eq,
|
||||||
|
clippy::doc_markdown,
|
||||||
|
clippy::items_after_statements,
|
||||||
|
clippy::let_underscore_untyped,
|
||||||
|
clippy::manual_map,
|
||||||
|
clippy::missing_panics_doc,
|
||||||
|
clippy::never_loop,
|
||||||
|
clippy::return_self_not_must_use,
|
||||||
|
clippy::too_many_lines,
|
||||||
|
clippy::uninlined_format_args,
|
||||||
|
clippy::unsafe_removed_from_name,
|
||||||
|
clippy::wildcard_in_or_patterns,
|
||||||
|
// noisy
|
||||||
|
clippy::missing_errors_doc,
|
||||||
|
clippy::must_use_candidate,
|
||||||
|
)]
|
||||||
|
|
||||||
|
pub use crate::de::{from_reader, from_slice, from_str, Deserializer};
|
||||||
|
pub use crate::error::{Error, Location, Result};
|
||||||
|
pub use crate::ser::{to_string, to_writer, Serializer};
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use crate::value::{from_value, to_value, Index, Number, Sequence, Value};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use crate::mapping::Mapping;
|
||||||
|
|
||||||
|
mod de;
|
||||||
|
mod error;
|
||||||
|
mod libyaml;
|
||||||
|
mod loader;
|
||||||
|
pub mod mapping;
|
||||||
|
mod number;
|
||||||
|
mod path;
|
||||||
|
mod ser;
|
||||||
|
pub mod value;
|
||||||
|
pub mod with;
|
||||||
|
|
||||||
|
// Prevent downstream code from implementing the Index trait.
|
||||||
|
mod private {
|
||||||
|
pub trait Sealed {}
|
||||||
|
impl Sealed for usize {}
|
||||||
|
impl Sealed for str {}
|
||||||
|
impl Sealed for String {}
|
||||||
|
impl Sealed for crate::Value {}
|
||||||
|
impl<'a, T> Sealed for &'a T where T: ?Sized + Sealed {}
|
||||||
|
}
|
127
serde/src/libyaml/cstr.rs
Normal file
127
serde/src/libyaml/cstr.rs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
use std::fmt::{self, Debug, Display, Write as _};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
use std::slice;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) struct CStr<'a> {
|
||||||
|
ptr: NonNull<u8>,
|
||||||
|
marker: PhantomData<&'a [u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<'a> Send for CStr<'a> {}
|
||||||
|
unsafe impl<'a> Sync for CStr<'a> {}
|
||||||
|
|
||||||
|
impl<'a> CStr<'a> {
|
||||||
|
pub fn from_bytes_with_nul(bytes: &'static [u8]) -> Self {
|
||||||
|
assert_eq!(bytes.last(), Some(&b'\0'));
|
||||||
|
let ptr = NonNull::from(bytes).cast();
|
||||||
|
unsafe { Self::from_ptr(ptr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn from_ptr(ptr: NonNull<i8>) -> Self {
|
||||||
|
CStr {
|
||||||
|
ptr: ptr.cast(),
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(self) -> usize {
|
||||||
|
let start = self.ptr.as_ptr();
|
||||||
|
let mut end = start;
|
||||||
|
unsafe {
|
||||||
|
while *end != 0 {
|
||||||
|
end = end.add(1);
|
||||||
|
}
|
||||||
|
end.offset_from(start) as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(self) -> &'a [u8] {
|
||||||
|
let len = self.len();
|
||||||
|
unsafe { slice::from_raw_parts(self.ptr.as_ptr(), len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for CStr<'a> {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let ptr = self.ptr.as_ptr();
|
||||||
|
let len = self.len();
|
||||||
|
let bytes = unsafe { slice::from_raw_parts(ptr, len) };
|
||||||
|
display_lossy(bytes, formatter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Debug for CStr<'a> {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let ptr = self.ptr.as_ptr();
|
||||||
|
let len = self.len();
|
||||||
|
let bytes = unsafe { slice::from_raw_parts(ptr, len) };
|
||||||
|
debug_lossy(bytes, formatter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_lossy(mut bytes: &[u8], formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
loop {
|
||||||
|
match str::from_utf8(bytes) {
|
||||||
|
Ok(valid) => return formatter.write_str(valid),
|
||||||
|
Err(utf8_error) => {
|
||||||
|
let valid_up_to = utf8_error.valid_up_to();
|
||||||
|
let valid = unsafe { str::from_utf8_unchecked(&bytes[..valid_up_to]) };
|
||||||
|
formatter.write_str(valid)?;
|
||||||
|
formatter.write_char(char::REPLACEMENT_CHARACTER)?;
|
||||||
|
if let Some(error_len) = utf8_error.error_len() {
|
||||||
|
bytes = &bytes[valid_up_to + error_len..];
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn debug_lossy(mut bytes: &[u8], formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_char('"')?;
|
||||||
|
|
||||||
|
while !bytes.is_empty() {
|
||||||
|
let from_utf8_result = str::from_utf8(bytes);
|
||||||
|
let valid = match from_utf8_result {
|
||||||
|
Ok(valid) => valid,
|
||||||
|
Err(utf8_error) => {
|
||||||
|
let valid_up_to = utf8_error.valid_up_to();
|
||||||
|
unsafe { str::from_utf8_unchecked(&bytes[..valid_up_to]) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut written = 0;
|
||||||
|
for (i, ch) in valid.char_indices() {
|
||||||
|
let esc = ch.escape_debug();
|
||||||
|
if esc.len() != 1 && ch != '\'' {
|
||||||
|
formatter.write_str(&valid[written..i])?;
|
||||||
|
for ch in esc {
|
||||||
|
formatter.write_char(ch)?;
|
||||||
|
}
|
||||||
|
written = i + ch.len_utf8();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formatter.write_str(&valid[written..])?;
|
||||||
|
|
||||||
|
match from_utf8_result {
|
||||||
|
Ok(_valid) => break,
|
||||||
|
Err(utf8_error) => {
|
||||||
|
let end_of_broken = if let Some(error_len) = utf8_error.error_len() {
|
||||||
|
valid.len() + error_len
|
||||||
|
} else {
|
||||||
|
bytes.len()
|
||||||
|
};
|
||||||
|
for b in &bytes[valid.len()..end_of_broken] {
|
||||||
|
write!(formatter, "\\x{:02x}", b)?;
|
||||||
|
}
|
||||||
|
bytes = &bytes[end_of_broken..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatter.write_char('"')
|
||||||
|
}
|
217
serde/src/libyaml/emitter.rs
Normal file
217
serde/src/libyaml/emitter.rs
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
use crate::libyaml;
|
||||||
|
use crate::libyaml::util::Owned;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::io;
|
||||||
|
use std::mem::{self, MaybeUninit};
|
||||||
|
use std::ptr::{self, addr_of_mut};
|
||||||
|
use std::slice;
|
||||||
|
use unsafe_libyaml as sys;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Error {
|
||||||
|
Libyaml(libyaml::error::Error),
|
||||||
|
Io(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Emitter<'a> {
|
||||||
|
pin: Owned<EmitterPinned<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EmitterPinned<'a> {
|
||||||
|
sys: sys::yaml_emitter_t,
|
||||||
|
write: Box<dyn io::Write + 'a>,
|
||||||
|
write_error: Option<io::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Event<'a> {
|
||||||
|
StreamStart,
|
||||||
|
StreamEnd,
|
||||||
|
DocumentStart,
|
||||||
|
DocumentEnd,
|
||||||
|
Scalar(Scalar<'a>),
|
||||||
|
SequenceStart(Sequence),
|
||||||
|
SequenceEnd,
|
||||||
|
MappingStart(Mapping),
|
||||||
|
MappingEnd,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Scalar<'a> {
|
||||||
|
pub tag: Option<String>,
|
||||||
|
pub value: &'a str,
|
||||||
|
pub style: ScalarStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum ScalarStyle {
|
||||||
|
Any,
|
||||||
|
Plain,
|
||||||
|
SingleQuoted,
|
||||||
|
Literal,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Sequence {
|
||||||
|
pub tag: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Mapping {
|
||||||
|
pub tag: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Emitter<'a> {
|
||||||
|
pub fn new(write: Box<dyn io::Write + 'a>) -> Emitter<'a> {
|
||||||
|
let owned = Owned::<EmitterPinned>::new_uninit();
|
||||||
|
let pin = unsafe {
|
||||||
|
let emitter = addr_of_mut!((*owned.ptr).sys);
|
||||||
|
if sys::yaml_emitter_initialize(emitter).fail {
|
||||||
|
panic!("malloc error: {}", libyaml::Error::emit_error(emitter));
|
||||||
|
}
|
||||||
|
sys::yaml_emitter_set_unicode(emitter, true);
|
||||||
|
sys::yaml_emitter_set_width(emitter, -1);
|
||||||
|
addr_of_mut!((*owned.ptr).write).write(write);
|
||||||
|
addr_of_mut!((*owned.ptr).write_error).write(None);
|
||||||
|
sys::yaml_emitter_set_output(emitter, write_handler, owned.ptr.cast());
|
||||||
|
Owned::assume_init(owned)
|
||||||
|
};
|
||||||
|
Emitter { pin }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit(&mut self, event: Event) -> Result<(), Error> {
|
||||||
|
let mut sys_event = MaybeUninit::<sys::yaml_event_t>::uninit();
|
||||||
|
let sys_event = sys_event.as_mut_ptr();
|
||||||
|
unsafe {
|
||||||
|
let emitter = addr_of_mut!((*self.pin.ptr).sys);
|
||||||
|
let initialize_status = match event {
|
||||||
|
Event::StreamStart => {
|
||||||
|
sys::yaml_stream_start_event_initialize(sys_event, sys::YAML_UTF8_ENCODING)
|
||||||
|
}
|
||||||
|
Event::StreamEnd => sys::yaml_stream_end_event_initialize(sys_event),
|
||||||
|
Event::DocumentStart => {
|
||||||
|
let version_directive = ptr::null_mut();
|
||||||
|
let tag_directives_start = ptr::null_mut();
|
||||||
|
let tag_directives_end = ptr::null_mut();
|
||||||
|
let implicit = true;
|
||||||
|
sys::yaml_document_start_event_initialize(
|
||||||
|
sys_event,
|
||||||
|
version_directive,
|
||||||
|
tag_directives_start,
|
||||||
|
tag_directives_end,
|
||||||
|
implicit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Event::DocumentEnd => {
|
||||||
|
let implicit = true;
|
||||||
|
sys::yaml_document_end_event_initialize(sys_event, implicit)
|
||||||
|
}
|
||||||
|
Event::Scalar(mut scalar) => {
|
||||||
|
let anchor = ptr::null();
|
||||||
|
let tag = scalar.tag.as_mut().map_or_else(ptr::null, |tag| {
|
||||||
|
tag.push('\0');
|
||||||
|
tag.as_ptr()
|
||||||
|
});
|
||||||
|
let value = scalar.value.as_ptr();
|
||||||
|
let length = scalar.value.len() as i32;
|
||||||
|
let plain_implicit = tag.is_null();
|
||||||
|
let quoted_implicit = tag.is_null();
|
||||||
|
let style = match scalar.style {
|
||||||
|
ScalarStyle::Any => sys::YAML_ANY_SCALAR_STYLE,
|
||||||
|
ScalarStyle::Plain => sys::YAML_PLAIN_SCALAR_STYLE,
|
||||||
|
ScalarStyle::SingleQuoted => sys::YAML_SINGLE_QUOTED_SCALAR_STYLE,
|
||||||
|
ScalarStyle::Literal => sys::YAML_LITERAL_SCALAR_STYLE,
|
||||||
|
};
|
||||||
|
sys::yaml_scalar_event_initialize(
|
||||||
|
sys_event,
|
||||||
|
anchor,
|
||||||
|
tag,
|
||||||
|
value,
|
||||||
|
length,
|
||||||
|
plain_implicit,
|
||||||
|
quoted_implicit,
|
||||||
|
style,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Event::SequenceStart(mut sequence) => {
|
||||||
|
let anchor = ptr::null();
|
||||||
|
let tag = sequence.tag.as_mut().map_or_else(ptr::null, |tag| {
|
||||||
|
tag.push('\0');
|
||||||
|
tag.as_ptr()
|
||||||
|
});
|
||||||
|
let implicit = tag.is_null();
|
||||||
|
let style = sys::YAML_ANY_SEQUENCE_STYLE;
|
||||||
|
sys::yaml_sequence_start_event_initialize(
|
||||||
|
sys_event, anchor, tag, implicit, style,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Event::SequenceEnd => sys::yaml_sequence_end_event_initialize(sys_event),
|
||||||
|
Event::MappingStart(mut mapping) => {
|
||||||
|
let anchor = ptr::null();
|
||||||
|
let tag = mapping.tag.as_mut().map_or_else(ptr::null, |tag| {
|
||||||
|
tag.push('\0');
|
||||||
|
tag.as_ptr()
|
||||||
|
});
|
||||||
|
let implicit = tag.is_null();
|
||||||
|
let style = sys::YAML_ANY_MAPPING_STYLE;
|
||||||
|
sys::yaml_mapping_start_event_initialize(
|
||||||
|
sys_event, anchor, tag, implicit, style,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Event::MappingEnd => sys::yaml_mapping_end_event_initialize(sys_event),
|
||||||
|
};
|
||||||
|
if initialize_status.fail {
|
||||||
|
return Err(Error::Libyaml(libyaml::Error::emit_error(emitter)));
|
||||||
|
}
|
||||||
|
if sys::yaml_emitter_emit(emitter, sys_event).fail {
|
||||||
|
return Err(self.error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flush(&mut self) -> Result<(), Error> {
|
||||||
|
unsafe {
|
||||||
|
let emitter = addr_of_mut!((*self.pin.ptr).sys);
|
||||||
|
if sys::yaml_emitter_flush(emitter).fail {
|
||||||
|
return Err(self.error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> Box<dyn io::Write + 'a> {
|
||||||
|
let sink = Box::new(io::sink());
|
||||||
|
unsafe { mem::replace(&mut (*self.pin.ptr).write, sink) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error(&mut self) -> Error {
|
||||||
|
let emitter = unsafe { &mut *self.pin.ptr };
|
||||||
|
if let Some(write_error) = emitter.write_error.take() {
|
||||||
|
Error::Io(write_error)
|
||||||
|
} else {
|
||||||
|
Error::Libyaml(unsafe { libyaml::Error::emit_error(&emitter.sys) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn write_handler(data: *mut c_void, buffer: *mut u8, size: u64) -> i32 {
|
||||||
|
let data = data.cast::<EmitterPinned>();
|
||||||
|
match io::Write::write_all(unsafe { &mut *(*data).write }, unsafe {
|
||||||
|
slice::from_raw_parts(buffer, size as usize)
|
||||||
|
}) {
|
||||||
|
Ok(()) => 1,
|
||||||
|
Err(err) => {
|
||||||
|
unsafe {
|
||||||
|
(*data).write_error = Some(err);
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Drop for EmitterPinned<'a> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { sys::yaml_emitter_delete(&mut self.sys) }
|
||||||
|
}
|
||||||
|
}
|
162
serde/src/libyaml/error.rs
Normal file
162
serde/src/libyaml/error.rs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
use crate::libyaml::cstr::CStr;
|
||||||
|
use std::fmt::{self, Debug, Display};
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
use unsafe_libyaml as sys;
|
||||||
|
|
||||||
|
pub(crate) type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
pub(crate) struct Error {
|
||||||
|
kind: sys::yaml_error_type_t,
|
||||||
|
problem: CStr<'static>,
|
||||||
|
problem_offset: u64,
|
||||||
|
problem_mark: Mark,
|
||||||
|
context: Option<CStr<'static>>,
|
||||||
|
context_mark: Mark,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub unsafe fn parse_error(parser: *const sys::yaml_parser_t) -> Self {
|
||||||
|
Error {
|
||||||
|
kind: unsafe { (*parser).error },
|
||||||
|
problem: match NonNull::new(unsafe { (*parser).problem as *mut _ }) {
|
||||||
|
Some(problem) => unsafe { CStr::from_ptr(problem) },
|
||||||
|
None => CStr::from_bytes_with_nul(b"libyaml parser failed but there is no error\0"),
|
||||||
|
},
|
||||||
|
problem_offset: unsafe { (*parser).problem_offset },
|
||||||
|
problem_mark: Mark {
|
||||||
|
sys: unsafe { (*parser).problem_mark },
|
||||||
|
},
|
||||||
|
context: match NonNull::new(unsafe { (*parser).context as *mut _ }) {
|
||||||
|
Some(context) => Some(unsafe { CStr::from_ptr(context) }),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
context_mark: Mark {
|
||||||
|
sys: unsafe { (*parser).context_mark },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn emit_error(emitter: *const sys::yaml_emitter_t) -> Self {
|
||||||
|
Error {
|
||||||
|
kind: unsafe { (*emitter).error },
|
||||||
|
problem: match NonNull::new(unsafe { (*emitter).problem as *mut _ }) {
|
||||||
|
Some(problem) => unsafe { CStr::from_ptr(problem) },
|
||||||
|
None => {
|
||||||
|
CStr::from_bytes_with_nul(b"libyaml emitter failed but there is no error\0")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
problem_offset: 0,
|
||||||
|
problem_mark: Mark {
|
||||||
|
sys: unsafe { MaybeUninit::<sys::yaml_mark_t>::zeroed().assume_init() },
|
||||||
|
},
|
||||||
|
context: None,
|
||||||
|
context_mark: Mark {
|
||||||
|
sys: unsafe { MaybeUninit::<sys::yaml_mark_t>::zeroed().assume_init() },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark(&self) -> Mark {
|
||||||
|
self.problem_mark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "{}", self.problem)?;
|
||||||
|
if self.problem_mark.sys.line != 0 || self.problem_mark.sys.column != 0 {
|
||||||
|
write!(formatter, " at {}", self.problem_mark)?;
|
||||||
|
} else if self.problem_offset != 0 {
|
||||||
|
write!(formatter, " at position {}", self.problem_offset)?;
|
||||||
|
}
|
||||||
|
if let Some(context) = &self.context {
|
||||||
|
write!(formatter, ", {}", context)?;
|
||||||
|
if (self.context_mark.sys.line != 0 || self.context_mark.sys.column != 0)
|
||||||
|
&& (self.context_mark.sys.line != self.problem_mark.sys.line
|
||||||
|
|| self.context_mark.sys.column != self.problem_mark.sys.column)
|
||||||
|
{
|
||||||
|
write!(formatter, " at {}", self.context_mark)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Error {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let mut formatter = formatter.debug_struct("Error");
|
||||||
|
if let Some(kind) = match self.kind {
|
||||||
|
sys::YAML_MEMORY_ERROR => Some("MEMORY"),
|
||||||
|
sys::YAML_READER_ERROR => Some("READER"),
|
||||||
|
sys::YAML_SCANNER_ERROR => Some("SCANNER"),
|
||||||
|
sys::YAML_PARSER_ERROR => Some("PARSER"),
|
||||||
|
sys::YAML_COMPOSER_ERROR => Some("COMPOSER"),
|
||||||
|
sys::YAML_WRITER_ERROR => Some("WRITER"),
|
||||||
|
sys::YAML_EMITTER_ERROR => Some("EMITTER"),
|
||||||
|
_ => None,
|
||||||
|
} {
|
||||||
|
formatter.field("kind", &format_args!("{}", kind));
|
||||||
|
}
|
||||||
|
formatter.field("problem", &self.problem);
|
||||||
|
if self.problem_mark.sys.line != 0 || self.problem_mark.sys.column != 0 {
|
||||||
|
formatter.field("problem_mark", &self.problem_mark);
|
||||||
|
} else if self.problem_offset != 0 {
|
||||||
|
formatter.field("problem_offset", &self.problem_offset);
|
||||||
|
}
|
||||||
|
if let Some(context) = &self.context {
|
||||||
|
formatter.field("context", context);
|
||||||
|
if self.context_mark.sys.line != 0 || self.context_mark.sys.column != 0 {
|
||||||
|
formatter.field("context_mark", &self.context_mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formatter.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) struct Mark {
|
||||||
|
pub(super) sys: sys::yaml_mark_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mark {
|
||||||
|
pub fn index(&self) -> u64 {
|
||||||
|
self.sys.index
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line(&self) -> u64 {
|
||||||
|
self.sys.line
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn column(&self) -> u64 {
|
||||||
|
self.sys.column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Mark {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
if self.sys.line != 0 || self.sys.column != 0 {
|
||||||
|
write!(
|
||||||
|
formatter,
|
||||||
|
"line {} column {}",
|
||||||
|
self.sys.line + 1,
|
||||||
|
self.sys.column + 1,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
write!(formatter, "position {}", self.sys.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Mark {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let mut formatter = formatter.debug_struct("Mark");
|
||||||
|
if self.sys.line != 0 || self.sys.column != 0 {
|
||||||
|
formatter.field("line", &(self.sys.line + 1));
|
||||||
|
formatter.field("column", &(self.sys.column + 1));
|
||||||
|
} else {
|
||||||
|
formatter.field("index", &self.sys.index);
|
||||||
|
}
|
||||||
|
formatter.finish()
|
||||||
|
}
|
||||||
|
}
|
8
serde/src/libyaml/mod.rs
Normal file
8
serde/src/libyaml/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
mod cstr;
|
||||||
|
pub mod emitter;
|
||||||
|
pub mod error;
|
||||||
|
pub mod parser;
|
||||||
|
pub mod tag;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
use self::error::Error;
|
201
serde/src/libyaml/parser.rs
Normal file
201
serde/src/libyaml/parser.rs
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
use crate::libyaml::cstr::{self, CStr};
|
||||||
|
use crate::libyaml::error::{Error, Mark, Result};
|
||||||
|
use crate::libyaml::tag::Tag;
|
||||||
|
use crate::libyaml::util::Owned;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::{self, Debug};
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
use std::ptr::{addr_of_mut, NonNull};
|
||||||
|
use std::slice;
|
||||||
|
use unsafe_libyaml as sys;
|
||||||
|
|
||||||
|
pub(crate) struct Parser<'input> {
|
||||||
|
pin: Owned<ParserPinned<'input>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ParserPinned<'input> {
|
||||||
|
sys: sys::yaml_parser_t,
|
||||||
|
input: Cow<'input, [u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Event<'input> {
|
||||||
|
StreamStart,
|
||||||
|
StreamEnd,
|
||||||
|
DocumentStart,
|
||||||
|
DocumentEnd,
|
||||||
|
Alias(Anchor),
|
||||||
|
Scalar(Scalar<'input>),
|
||||||
|
SequenceStart(SequenceStart),
|
||||||
|
SequenceEnd,
|
||||||
|
MappingStart(MappingStart),
|
||||||
|
MappingEnd,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Scalar<'input> {
|
||||||
|
pub anchor: Option<Anchor>,
|
||||||
|
pub tag: Option<Tag>,
|
||||||
|
pub value: Box<[u8]>,
|
||||||
|
pub style: ScalarStyle,
|
||||||
|
pub repr: Option<&'input [u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct SequenceStart {
|
||||||
|
pub anchor: Option<Anchor>,
|
||||||
|
pub tag: Option<Tag>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct MappingStart {
|
||||||
|
pub anchor: Option<Anchor>,
|
||||||
|
pub tag: Option<Tag>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
pub(crate) struct Anchor(Box<[u8]>);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub(crate) enum ScalarStyle {
|
||||||
|
Plain,
|
||||||
|
SingleQuoted,
|
||||||
|
DoubleQuoted,
|
||||||
|
Literal,
|
||||||
|
Folded,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'input> Parser<'input> {
|
||||||
|
pub fn new(input: Cow<'input, [u8]>) -> Parser<'input> {
|
||||||
|
let owned = Owned::<ParserPinned>::new_uninit();
|
||||||
|
let pin = unsafe {
|
||||||
|
let parser = addr_of_mut!((*owned.ptr).sys);
|
||||||
|
if sys::yaml_parser_initialize(parser).fail {
|
||||||
|
panic!("malloc error: {}", Error::parse_error(parser));
|
||||||
|
}
|
||||||
|
sys::yaml_parser_set_encoding(parser, sys::YAML_UTF8_ENCODING);
|
||||||
|
sys::yaml_parser_set_input_string(parser, input.as_ptr(), input.len() as u64);
|
||||||
|
addr_of_mut!((*owned.ptr).input).write(input);
|
||||||
|
Owned::assume_init(owned)
|
||||||
|
};
|
||||||
|
Parser { pin }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) -> Result<(Event<'input>, Mark)> {
|
||||||
|
let mut event = MaybeUninit::<sys::yaml_event_t>::uninit();
|
||||||
|
unsafe {
|
||||||
|
let parser = addr_of_mut!((*self.pin.ptr).sys);
|
||||||
|
if (*parser).error != sys::YAML_NO_ERROR {
|
||||||
|
return Err(Error::parse_error(parser));
|
||||||
|
}
|
||||||
|
let event = event.as_mut_ptr();
|
||||||
|
if sys::yaml_parser_parse(parser, event).fail {
|
||||||
|
return Err(Error::parse_error(parser));
|
||||||
|
}
|
||||||
|
let ret = convert_event(&*event, &(*self.pin.ptr).input);
|
||||||
|
let mark = Mark {
|
||||||
|
sys: (*event).start_mark,
|
||||||
|
};
|
||||||
|
sys::yaml_event_delete(event);
|
||||||
|
Ok((ret, mark))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn convert_event<'input>(
|
||||||
|
sys: &sys::yaml_event_t,
|
||||||
|
input: &Cow<'input, [u8]>,
|
||||||
|
) -> Event<'input> {
|
||||||
|
match sys.type_ {
|
||||||
|
sys::YAML_STREAM_START_EVENT => Event::StreamStart,
|
||||||
|
sys::YAML_STREAM_END_EVENT => Event::StreamEnd,
|
||||||
|
sys::YAML_DOCUMENT_START_EVENT => Event::DocumentStart,
|
||||||
|
sys::YAML_DOCUMENT_END_EVENT => Event::DocumentEnd,
|
||||||
|
sys::YAML_ALIAS_EVENT => {
|
||||||
|
Event::Alias(unsafe { optional_anchor(sys.data.alias.anchor) }.unwrap())
|
||||||
|
}
|
||||||
|
sys::YAML_SCALAR_EVENT => Event::Scalar(Scalar {
|
||||||
|
anchor: unsafe { optional_anchor(sys.data.scalar.anchor) },
|
||||||
|
tag: unsafe { optional_tag(sys.data.scalar.tag) },
|
||||||
|
value: Box::from(unsafe {
|
||||||
|
slice::from_raw_parts(sys.data.scalar.value, sys.data.scalar.length as usize)
|
||||||
|
}),
|
||||||
|
style: match unsafe { sys.data.scalar.style } {
|
||||||
|
sys::YAML_PLAIN_SCALAR_STYLE => ScalarStyle::Plain,
|
||||||
|
sys::YAML_SINGLE_QUOTED_SCALAR_STYLE => ScalarStyle::SingleQuoted,
|
||||||
|
sys::YAML_DOUBLE_QUOTED_SCALAR_STYLE => ScalarStyle::DoubleQuoted,
|
||||||
|
sys::YAML_LITERAL_SCALAR_STYLE => ScalarStyle::Literal,
|
||||||
|
sys::YAML_FOLDED_SCALAR_STYLE => ScalarStyle::Folded,
|
||||||
|
sys::YAML_ANY_SCALAR_STYLE | _ => unreachable!(),
|
||||||
|
},
|
||||||
|
repr: if let Cow::Borrowed(input) = input {
|
||||||
|
Some(&input[sys.start_mark.index as usize..sys.end_mark.index as usize])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
sys::YAML_SEQUENCE_START_EVENT => Event::SequenceStart(SequenceStart {
|
||||||
|
anchor: unsafe { optional_anchor(sys.data.sequence_start.anchor) },
|
||||||
|
tag: unsafe { optional_tag(sys.data.sequence_start.tag) },
|
||||||
|
}),
|
||||||
|
sys::YAML_SEQUENCE_END_EVENT => Event::SequenceEnd,
|
||||||
|
sys::YAML_MAPPING_START_EVENT => Event::MappingStart(MappingStart {
|
||||||
|
anchor: unsafe { optional_anchor(sys.data.mapping_start.anchor) },
|
||||||
|
tag: unsafe { optional_tag(sys.data.mapping_start.tag) },
|
||||||
|
}),
|
||||||
|
sys::YAML_MAPPING_END_EVENT => Event::MappingEnd,
|
||||||
|
sys::YAML_NO_EVENT => unreachable!(),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn optional_anchor(anchor: *const u8) -> Option<Anchor> {
|
||||||
|
let ptr = NonNull::new(anchor as *mut i8)?;
|
||||||
|
let cstr = unsafe { CStr::from_ptr(ptr) };
|
||||||
|
Some(Anchor(Box::from(cstr.to_bytes())))
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn optional_tag(tag: *const u8) -> Option<Tag> {
|
||||||
|
let ptr = NonNull::new(tag as *mut i8)?;
|
||||||
|
let cstr = unsafe { CStr::from_ptr(ptr) };
|
||||||
|
Some(Tag(Box::from(cstr.to_bytes())))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'input> Debug for Scalar<'input> {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let Scalar {
|
||||||
|
anchor,
|
||||||
|
tag,
|
||||||
|
value,
|
||||||
|
style,
|
||||||
|
repr: _,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
struct LossySlice<'a>(&'a [u8]);
|
||||||
|
|
||||||
|
impl<'a> Debug for LossySlice<'a> {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
cstr::debug_lossy(self.0, formatter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatter
|
||||||
|
.debug_struct("Scalar")
|
||||||
|
.field("anchor", anchor)
|
||||||
|
.field("tag", tag)
|
||||||
|
.field("value", &LossySlice(value))
|
||||||
|
.field("style", style)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Anchor {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
cstr::debug_lossy(&self.0, formatter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'input> Drop for ParserPinned<'input> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { sys::yaml_parser_delete(&mut self.sys) }
|
||||||
|
}
|
||||||
|
}
|
38
serde/src/libyaml/tag.rs
Normal file
38
serde/src/libyaml/tag.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use crate::libyaml::cstr;
|
||||||
|
use std::fmt::{self, Debug};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
#[derive(Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
pub(crate) struct Tag(pub(in crate::libyaml) Box<[u8]>);
|
||||||
|
|
||||||
|
impl Tag {
|
||||||
|
pub const NULL: &'static str = "tag:yaml.org,2002:null";
|
||||||
|
pub const BOOL: &'static str = "tag:yaml.org,2002:bool";
|
||||||
|
pub const INT: &'static str = "tag:yaml.org,2002:int";
|
||||||
|
pub const FLOAT: &'static str = "tag:yaml.org,2002:float";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tag {
|
||||||
|
pub fn starts_with(&self, prefix: &str) -> bool {
|
||||||
|
self.0.starts_with(prefix.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<str> for Tag {
|
||||||
|
fn eq(&self, other: &str) -> bool {
|
||||||
|
*self.0 == *other.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Tag {
|
||||||
|
type Target = [u8];
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Tag {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
cstr::debug_lossy(&self.0, formatter)
|
||||||
|
}
|
||||||
|
}
|
48
serde/src/libyaml/util.rs
Normal file
48
serde/src/libyaml/util.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::mem::{self, MaybeUninit};
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::ptr::{addr_of, NonNull};
|
||||||
|
|
||||||
|
pub(crate) struct Owned<T, Init = T> {
|
||||||
|
ptr: NonNull<T>,
|
||||||
|
marker: PhantomData<NonNull<Init>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Owned<T> {
|
||||||
|
pub fn new_uninit() -> Owned<MaybeUninit<T>, T> {
|
||||||
|
// FIXME: use Box::new_uninit when stable
|
||||||
|
let boxed = Box::new(MaybeUninit::<T>::uninit());
|
||||||
|
Owned {
|
||||||
|
ptr: unsafe { NonNull::new_unchecked(Box::into_raw(boxed)) },
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn assume_init(definitely_init: Owned<MaybeUninit<T>, T>) -> Owned<T> {
|
||||||
|
let ptr = definitely_init.ptr;
|
||||||
|
mem::forget(definitely_init);
|
||||||
|
Owned {
|
||||||
|
ptr: ptr.cast(),
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub(crate) struct InitPtr<T> {
|
||||||
|
pub ptr: *mut T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Init> Deref for Owned<T, Init> {
|
||||||
|
type Target = InitPtr<Init>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*addr_of!(self.ptr).cast::<InitPtr<Init>>() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Init> Drop for Owned<T, Init> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = unsafe { Box::from_raw(self.ptr.as_ptr()) };
|
||||||
|
}
|
||||||
|
}
|
119
serde/src/loader.rs
Normal file
119
serde/src/loader.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
use crate::de::{Event, Progress};
|
||||||
|
use crate::error::{self, Error, ErrorImpl, Result};
|
||||||
|
use crate::libyaml::error::Mark;
|
||||||
|
use crate::libyaml::parser::{Event as YamlEvent, Parser};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub(crate) struct Loader<'input> {
|
||||||
|
parser: Option<Parser<'input>>,
|
||||||
|
document_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Document<'input> {
|
||||||
|
pub events: Vec<(Event<'input>, Mark)>,
|
||||||
|
pub error: Option<Arc<ErrorImpl>>,
|
||||||
|
/// Map from alias id to index in events.
|
||||||
|
pub aliases: BTreeMap<usize, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'input> Loader<'input> {
|
||||||
|
pub fn new(progress: Progress<'input>) -> Result<Self> {
|
||||||
|
let input = match progress {
|
||||||
|
Progress::Str(s) => Cow::Borrowed(s.as_bytes()),
|
||||||
|
Progress::Slice(bytes) => Cow::Borrowed(bytes),
|
||||||
|
Progress::Read(mut rdr) => {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
if let Err(io_error) = rdr.read_to_end(&mut buffer) {
|
||||||
|
return Err(error::new(ErrorImpl::Io(io_error)));
|
||||||
|
}
|
||||||
|
Cow::Owned(buffer)
|
||||||
|
}
|
||||||
|
Progress::Iterable(_) | Progress::Document(_) => unreachable!(),
|
||||||
|
Progress::Fail(err) => return Err(error::shared(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Loader {
|
||||||
|
parser: Some(Parser::new(input)),
|
||||||
|
document_count: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_document(&mut self) -> Option<Document<'input>> {
|
||||||
|
let parser = match &mut self.parser {
|
||||||
|
Some(parser) => parser,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let first = self.document_count == 0;
|
||||||
|
self.document_count += 1;
|
||||||
|
|
||||||
|
let mut anchors = BTreeMap::new();
|
||||||
|
let mut document = Document {
|
||||||
|
events: Vec::new(),
|
||||||
|
error: None,
|
||||||
|
aliases: BTreeMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (event, mark) = match parser.next() {
|
||||||
|
Ok((event, mark)) => (event, mark),
|
||||||
|
Err(err) => {
|
||||||
|
document.error = Some(Error::from(err).shared());
|
||||||
|
return Some(document);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let event = match event {
|
||||||
|
YamlEvent::StreamStart => continue,
|
||||||
|
YamlEvent::StreamEnd => {
|
||||||
|
self.parser = None;
|
||||||
|
return if first {
|
||||||
|
if document.events.is_empty() {
|
||||||
|
document.events.push((Event::Void, mark));
|
||||||
|
}
|
||||||
|
Some(document)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
YamlEvent::DocumentStart => continue,
|
||||||
|
YamlEvent::DocumentEnd => return Some(document),
|
||||||
|
YamlEvent::Alias(alias) => match anchors.get(&alias) {
|
||||||
|
Some(id) => Event::Alias(*id),
|
||||||
|
None => {
|
||||||
|
document.error = Some(error::new(ErrorImpl::UnknownAnchor(mark)).shared());
|
||||||
|
return Some(document);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
YamlEvent::Scalar(mut scalar) => {
|
||||||
|
if let Some(anchor) = scalar.anchor.take() {
|
||||||
|
let id = anchors.len();
|
||||||
|
anchors.insert(anchor, id);
|
||||||
|
document.aliases.insert(id, document.events.len());
|
||||||
|
}
|
||||||
|
Event::Scalar(scalar)
|
||||||
|
}
|
||||||
|
YamlEvent::SequenceStart(mut sequence_start) => {
|
||||||
|
if let Some(anchor) = sequence_start.anchor.take() {
|
||||||
|
let id = anchors.len();
|
||||||
|
anchors.insert(anchor, id);
|
||||||
|
document.aliases.insert(id, document.events.len());
|
||||||
|
}
|
||||||
|
Event::SequenceStart(sequence_start)
|
||||||
|
}
|
||||||
|
YamlEvent::SequenceEnd => Event::SequenceEnd,
|
||||||
|
YamlEvent::MappingStart(mut mapping_start) => {
|
||||||
|
if let Some(anchor) = mapping_start.anchor.take() {
|
||||||
|
let id = anchors.len();
|
||||||
|
anchors.insert(anchor, id);
|
||||||
|
document.aliases.insert(id, document.events.len());
|
||||||
|
}
|
||||||
|
Event::MappingStart(mapping_start)
|
||||||
|
}
|
||||||
|
YamlEvent::MappingEnd => Event::MappingEnd,
|
||||||
|
};
|
||||||
|
document.events.push((event, mark));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
851
serde/src/mapping.rs
Normal file
851
serde/src/mapping.rs
Normal file
|
@ -0,0 +1,851 @@
|
||||||
|
//! A YAML mapping and its iterator types.
|
||||||
|
|
||||||
|
use crate::{private, Value};
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
/// A YAML mapping in which the keys and values are both `serde_yaml::Value`.
|
||||||
|
#[derive(Clone, Default, Eq, PartialEq)]
|
||||||
|
pub struct Mapping {
|
||||||
|
map: IndexMap<Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mapping {
|
||||||
|
/// Creates an empty YAML map.
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an empty YAML map with the given initial capacity.
|
||||||
|
#[inline]
|
||||||
|
pub fn with_capacity(capacity: usize) -> Self {
|
||||||
|
Mapping {
|
||||||
|
map: IndexMap::with_capacity(capacity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reserves capacity for at least `additional` more elements to be inserted
|
||||||
|
/// into the map. The map may reserve more space to avoid frequent
|
||||||
|
/// allocations.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the new allocation size overflows `usize`.
|
||||||
|
#[inline]
|
||||||
|
pub fn reserve(&mut self, additional: usize) {
|
||||||
|
self.map.reserve(additional);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shrinks the capacity of the map as much as possible. It will drop down
|
||||||
|
/// as much as possible while maintaining the internal rules and possibly
|
||||||
|
/// leaving some space in accordance with the resize policy.
|
||||||
|
#[inline]
|
||||||
|
pub fn shrink_to_fit(&mut self) {
|
||||||
|
self.map.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts a key-value pair into the map. If the key already existed, the
|
||||||
|
/// old value is returned.
|
||||||
|
#[inline]
|
||||||
|
pub fn insert(&mut self, k: Value, v: Value) -> Option<Value> {
|
||||||
|
self.map.insert(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the map contains the given key.
|
||||||
|
#[inline]
|
||||||
|
pub fn contains_key<I: Index>(&self, index: I) -> bool {
|
||||||
|
index.is_key_into(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the value corresponding to the key in the map.
|
||||||
|
#[inline]
|
||||||
|
pub fn get<I: Index>(&self, index: I) -> Option<&Value> {
|
||||||
|
index.index_into(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the mutable reference corresponding to the key in the map.
|
||||||
|
#[inline]
|
||||||
|
pub fn get_mut<I: Index>(&mut self, index: I) -> Option<&mut Value> {
|
||||||
|
index.index_into_mut(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the given key's corresponding entry in the map for insertion and/or
|
||||||
|
/// in-place manipulation.
|
||||||
|
#[inline]
|
||||||
|
pub fn entry(&mut self, k: Value) -> Entry {
|
||||||
|
match self.map.entry(k) {
|
||||||
|
indexmap::map::Entry::Occupied(occupied) => Entry::Occupied(OccupiedEntry { occupied }),
|
||||||
|
indexmap::map::Entry::Vacant(vacant) => Entry::Vacant(VacantEntry { vacant }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes and returns the value corresponding to the key from the map.
|
||||||
|
///
|
||||||
|
/// This is equivalent to [`.swap_remove(index)`][Self::swap_remove],
|
||||||
|
/// replacing this entry's position with the last element. If you need to
|
||||||
|
/// preserve the relative order of the keys in the map, use
|
||||||
|
/// [`.shift_remove(key)`][Self::shift_remove] instead.
|
||||||
|
#[inline]
|
||||||
|
pub fn remove<I: Index>(&mut self, index: I) -> Option<Value> {
|
||||||
|
self.swap_remove(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove and return the key-value pair.
|
||||||
|
///
|
||||||
|
/// This is equivalent to [`.swap_remove_entry(index)`][Self::swap_remove_entry],
|
||||||
|
/// replacing this entry's position with the last element. If you need to
|
||||||
|
/// preserve the relative order of the keys in the map, use
|
||||||
|
/// [`.shift_remove_entry(key)`][Self::shift_remove_entry] instead.
|
||||||
|
#[inline]
|
||||||
|
pub fn remove_entry<I: Index>(&mut self, index: I) -> Option<(Value, Value)> {
|
||||||
|
self.swap_remove_entry(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes and returns the value corresponding to the key from the map.
|
||||||
|
///
|
||||||
|
/// Like [`Vec::swap_remove`], the entry is removed by swapping it with the
|
||||||
|
/// last element of the map and popping it off. This perturbs the position
|
||||||
|
/// of what used to be the last element!
|
||||||
|
#[inline]
|
||||||
|
pub fn swap_remove<I: Index>(&mut self, index: I) -> Option<Value> {
|
||||||
|
index.swap_remove_from(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove and return the key-value pair.
|
||||||
|
///
|
||||||
|
/// Like [`Vec::swap_remove`], the entry is removed by swapping it with the
|
||||||
|
/// last element of the map and popping it off. This perturbs the position
|
||||||
|
/// of what used to be the last element!
|
||||||
|
#[inline]
|
||||||
|
pub fn swap_remove_entry<I: Index>(&mut self, index: I) -> Option<(Value, Value)> {
|
||||||
|
index.swap_remove_entry_from(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes and returns the value corresponding to the key from the map.
|
||||||
|
///
|
||||||
|
/// Like [`Vec::remove`], the entry is removed by shifting all of the
|
||||||
|
/// elements that follow it, preserving their relative order. This perturbs
|
||||||
|
/// the index of all of those elements!
|
||||||
|
#[inline]
|
||||||
|
pub fn shift_remove<I: Index>(&mut self, index: I) -> Option<Value> {
|
||||||
|
index.shift_remove_from(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove and return the key-value pair.
|
||||||
|
///
|
||||||
|
/// Like [`Vec::remove`], the entry is removed by shifting all of the
|
||||||
|
/// elements that follow it, preserving their relative order. This perturbs
|
||||||
|
/// the index of all of those elements!
|
||||||
|
#[inline]
|
||||||
|
pub fn shift_remove_entry<I: Index>(&mut self, index: I) -> Option<(Value, Value)> {
|
||||||
|
index.shift_remove_entry_from(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scan through each key-value pair in the map and keep those where the
|
||||||
|
/// closure `keep` returns true.
|
||||||
|
#[inline]
|
||||||
|
pub fn retain<F>(&mut self, keep: F)
|
||||||
|
where
|
||||||
|
F: FnMut(&Value, &mut Value) -> bool,
|
||||||
|
{
|
||||||
|
self.map.retain(keep);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the maximum number of key-value pairs the map can hold without
|
||||||
|
/// reallocating.
|
||||||
|
#[inline]
|
||||||
|
pub fn capacity(&self) -> usize {
|
||||||
|
self.map.capacity()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of key-value pairs in the map.
|
||||||
|
#[inline]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.map.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the map is currently empty.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.map.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the map of all key-value pairs.
|
||||||
|
#[inline]
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a double-ended iterator visiting all key-value pairs in order of
|
||||||
|
/// insertion. Iterator element type is `(&'a Value, &'a Value)`.
|
||||||
|
#[inline]
|
||||||
|
pub fn iter(&self) -> Iter {
|
||||||
|
Iter {
|
||||||
|
iter: self.map.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a double-ended iterator visiting all key-value pairs in order of
|
||||||
|
/// insertion. Iterator element type is `(&'a Value, &'a mut ValuE)`.
|
||||||
|
#[inline]
|
||||||
|
pub fn iter_mut(&mut self) -> IterMut {
|
||||||
|
IterMut {
|
||||||
|
iter: self.map.iter_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an iterator over the keys of the map.
|
||||||
|
pub fn keys(&self) -> Keys {
|
||||||
|
Keys {
|
||||||
|
iter: self.map.keys(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an owning iterator over the keys of the map.
|
||||||
|
pub fn into_keys(self) -> IntoKeys {
|
||||||
|
IntoKeys {
|
||||||
|
iter: self.map.into_keys(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an iterator over the values of the map.
|
||||||
|
pub fn values(&self) -> Values {
|
||||||
|
Values {
|
||||||
|
iter: self.map.values(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an iterator over mutable references to the values of the map.
|
||||||
|
pub fn values_mut(&mut self) -> ValuesMut {
|
||||||
|
ValuesMut {
|
||||||
|
iter: self.map.values_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an owning iterator over the values of the map.
|
||||||
|
pub fn into_values(self) -> IntoValues {
|
||||||
|
IntoValues {
|
||||||
|
iter: self.map.into_values(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type that can be used to index into a `serde_yaml::Mapping`. See the
|
||||||
|
/// methods `get`, `get_mut`, `contains_key`, and `remove` of `Value`.
|
||||||
|
///
|
||||||
|
/// This trait is sealed and cannot be implemented for types outside of
|
||||||
|
/// `serde_yaml`.
|
||||||
|
pub trait Index: private::Sealed {
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn is_key_into(&self, v: &Mapping) -> bool;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn index_into<'a>(&self, v: &'a Mapping) -> Option<&'a Value>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn index_into_mut<'a>(&self, v: &'a mut Mapping) -> Option<&'a mut Value>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn swap_remove_from(&self, v: &mut Mapping) -> Option<Value>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn swap_remove_entry_from(&self, v: &mut Mapping) -> Option<(Value, Value)>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn shift_remove_from(&self, v: &mut Mapping) -> Option<Value>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn shift_remove_entry_from(&self, v: &mut Mapping) -> Option<(Value, Value)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HashLikeValue<'a>(&'a str);
|
||||||
|
|
||||||
|
impl<'a> indexmap::Equivalent<Value> for HashLikeValue<'a> {
|
||||||
|
fn equivalent(&self, key: &Value) -> bool {
|
||||||
|
match key {
|
||||||
|
Value::String(string) => self.0 == string,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This impl must be consistent with Value's Hash impl.
|
||||||
|
impl<'a> Hash for HashLikeValue<'a> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
const STRING: Value = Value::String(String::new());
|
||||||
|
mem::discriminant(&STRING).hash(state);
|
||||||
|
self.0.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index for Value {
|
||||||
|
fn is_key_into(&self, v: &Mapping) -> bool {
|
||||||
|
v.map.contains_key(self)
|
||||||
|
}
|
||||||
|
fn index_into<'a>(&self, v: &'a Mapping) -> Option<&'a Value> {
|
||||||
|
v.map.get(self)
|
||||||
|
}
|
||||||
|
fn index_into_mut<'a>(&self, v: &'a mut Mapping) -> Option<&'a mut Value> {
|
||||||
|
v.map.get_mut(self)
|
||||||
|
}
|
||||||
|
fn swap_remove_from(&self, v: &mut Mapping) -> Option<Value> {
|
||||||
|
v.map.swap_remove(self)
|
||||||
|
}
|
||||||
|
fn swap_remove_entry_from(&self, v: &mut Mapping) -> Option<(Value, Value)> {
|
||||||
|
v.map.swap_remove_entry(self)
|
||||||
|
}
|
||||||
|
fn shift_remove_from(&self, v: &mut Mapping) -> Option<Value> {
|
||||||
|
v.map.shift_remove(self)
|
||||||
|
}
|
||||||
|
fn shift_remove_entry_from(&self, v: &mut Mapping) -> Option<(Value, Value)> {
|
||||||
|
v.map.shift_remove_entry(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index for str {
|
||||||
|
fn is_key_into(&self, v: &Mapping) -> bool {
|
||||||
|
v.map.contains_key(&HashLikeValue(self))
|
||||||
|
}
|
||||||
|
fn index_into<'a>(&self, v: &'a Mapping) -> Option<&'a Value> {
|
||||||
|
v.map.get(&HashLikeValue(self))
|
||||||
|
}
|
||||||
|
fn index_into_mut<'a>(&self, v: &'a mut Mapping) -> Option<&'a mut Value> {
|
||||||
|
v.map.get_mut(&HashLikeValue(self))
|
||||||
|
}
|
||||||
|
fn swap_remove_from(&self, v: &mut Mapping) -> Option<Value> {
|
||||||
|
v.map.swap_remove(&HashLikeValue(self))
|
||||||
|
}
|
||||||
|
fn swap_remove_entry_from(&self, v: &mut Mapping) -> Option<(Value, Value)> {
|
||||||
|
v.map.swap_remove_entry(&HashLikeValue(self))
|
||||||
|
}
|
||||||
|
fn shift_remove_from(&self, v: &mut Mapping) -> Option<Value> {
|
||||||
|
v.map.shift_remove(&HashLikeValue(self))
|
||||||
|
}
|
||||||
|
fn shift_remove_entry_from(&self, v: &mut Mapping) -> Option<(Value, Value)> {
|
||||||
|
v.map.shift_remove_entry(&HashLikeValue(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index for String {
|
||||||
|
fn is_key_into(&self, v: &Mapping) -> bool {
|
||||||
|
self.as_str().is_key_into(v)
|
||||||
|
}
|
||||||
|
fn index_into<'a>(&self, v: &'a Mapping) -> Option<&'a Value> {
|
||||||
|
self.as_str().index_into(v)
|
||||||
|
}
|
||||||
|
fn index_into_mut<'a>(&self, v: &'a mut Mapping) -> Option<&'a mut Value> {
|
||||||
|
self.as_str().index_into_mut(v)
|
||||||
|
}
|
||||||
|
fn swap_remove_from(&self, v: &mut Mapping) -> Option<Value> {
|
||||||
|
self.as_str().swap_remove_from(v)
|
||||||
|
}
|
||||||
|
fn swap_remove_entry_from(&self, v: &mut Mapping) -> Option<(Value, Value)> {
|
||||||
|
self.as_str().swap_remove_entry_from(v)
|
||||||
|
}
|
||||||
|
fn shift_remove_from(&self, v: &mut Mapping) -> Option<Value> {
|
||||||
|
self.as_str().shift_remove_from(v)
|
||||||
|
}
|
||||||
|
fn shift_remove_entry_from(&self, v: &mut Mapping) -> Option<(Value, Value)> {
|
||||||
|
self.as_str().shift_remove_entry_from(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Index for &T
|
||||||
|
where
|
||||||
|
T: ?Sized + Index,
|
||||||
|
{
|
||||||
|
fn is_key_into(&self, v: &Mapping) -> bool {
|
||||||
|
(**self).is_key_into(v)
|
||||||
|
}
|
||||||
|
fn index_into<'a>(&self, v: &'a Mapping) -> Option<&'a Value> {
|
||||||
|
(**self).index_into(v)
|
||||||
|
}
|
||||||
|
fn index_into_mut<'a>(&self, v: &'a mut Mapping) -> Option<&'a mut Value> {
|
||||||
|
(**self).index_into_mut(v)
|
||||||
|
}
|
||||||
|
fn swap_remove_from(&self, v: &mut Mapping) -> Option<Value> {
|
||||||
|
(**self).swap_remove_from(v)
|
||||||
|
}
|
||||||
|
fn swap_remove_entry_from(&self, v: &mut Mapping) -> Option<(Value, Value)> {
|
||||||
|
(**self).swap_remove_entry_from(v)
|
||||||
|
}
|
||||||
|
fn shift_remove_from(&self, v: &mut Mapping) -> Option<Value> {
|
||||||
|
(**self).shift_remove_from(v)
|
||||||
|
}
|
||||||
|
fn shift_remove_entry_from(&self, v: &mut Mapping) -> Option<(Value, Value)> {
|
||||||
|
(**self).shift_remove_entry_from(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||||
|
impl Hash for Mapping {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
// Hash the kv pairs in a way that is not sensitive to their order.
|
||||||
|
let mut xor = 0;
|
||||||
|
for (k, v) in self {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
k.hash(&mut hasher);
|
||||||
|
v.hash(&mut hasher);
|
||||||
|
xor ^= hasher.finish();
|
||||||
|
}
|
||||||
|
xor.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Mapping {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
let mut self_entries = Vec::from_iter(self);
|
||||||
|
let mut other_entries = Vec::from_iter(other);
|
||||||
|
|
||||||
|
// Sort in an arbitrary order that is consistent with Value's PartialOrd
|
||||||
|
// impl.
|
||||||
|
fn total_cmp(a: &Value, b: &Value) -> Ordering {
|
||||||
|
match (a, b) {
|
||||||
|
(Value::Null, Value::Null) => Ordering::Equal,
|
||||||
|
(Value::Null, _) => Ordering::Less,
|
||||||
|
(_, Value::Null) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Value::Bool(a), Value::Bool(b)) => a.cmp(b),
|
||||||
|
(Value::Bool(_), _) => Ordering::Less,
|
||||||
|
(_, Value::Bool(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Value::Number(a), Value::Number(b)) => a.total_cmp(b),
|
||||||
|
(Value::Number(_), _) => Ordering::Less,
|
||||||
|
(_, Value::Number(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Value::String(a), Value::String(b)) => a.cmp(b),
|
||||||
|
(Value::String(_), _) => Ordering::Less,
|
||||||
|
(_, Value::String(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Value::Sequence(a), Value::Sequence(b)) => iter_cmp_by(a, b, total_cmp),
|
||||||
|
(Value::Sequence(_), _) => Ordering::Less,
|
||||||
|
(_, Value::Sequence(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Value::Mapping(a), Value::Mapping(b)) => {
|
||||||
|
iter_cmp_by(a, b, |(ak, av), (bk, bv)| {
|
||||||
|
total_cmp(ak, bk).then_with(|| total_cmp(av, bv))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
(Value::Mapping(_), _) => Ordering::Less,
|
||||||
|
(_, Value::Mapping(_)) => Ordering::Greater,
|
||||||
|
|
||||||
|
(Value::Tagged(a), Value::Tagged(b)) => a
|
||||||
|
.tag
|
||||||
|
.cmp(&b.tag)
|
||||||
|
.then_with(|| total_cmp(&a.value, &b.value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_cmp_by<I, F>(this: I, other: I, mut cmp: F) -> Ordering
|
||||||
|
where
|
||||||
|
I: IntoIterator,
|
||||||
|
F: FnMut(I::Item, I::Item) -> Ordering,
|
||||||
|
{
|
||||||
|
let mut this = this.into_iter();
|
||||||
|
let mut other = other.into_iter();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let x = match this.next() {
|
||||||
|
None => {
|
||||||
|
if other.next().is_none() {
|
||||||
|
return Ordering::Equal;
|
||||||
|
} else {
|
||||||
|
return Ordering::Less;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(val) => val,
|
||||||
|
};
|
||||||
|
|
||||||
|
let y = match other.next() {
|
||||||
|
None => return Ordering::Greater,
|
||||||
|
Some(val) => val,
|
||||||
|
};
|
||||||
|
|
||||||
|
match cmp(x, y) {
|
||||||
|
Ordering::Equal => {}
|
||||||
|
non_eq => return non_eq,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// While sorting by map key, we get to assume that no two keys are
|
||||||
|
// equal, otherwise they wouldn't both be in the map. This is not a safe
|
||||||
|
// assumption outside of this situation.
|
||||||
|
let total_cmp = |&(a, _): &_, &(b, _): &_| total_cmp(a, b);
|
||||||
|
self_entries.sort_by(total_cmp);
|
||||||
|
other_entries.sort_by(total_cmp);
|
||||||
|
self_entries.partial_cmp(&other_entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> std::ops::Index<I> for Mapping
|
||||||
|
where
|
||||||
|
I: Index,
|
||||||
|
{
|
||||||
|
type Output = Value;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
|
fn index(&self, index: I) -> &Value {
|
||||||
|
index.index_into(self).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> std::ops::IndexMut<I> for Mapping
|
||||||
|
where
|
||||||
|
I: Index,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
|
fn index_mut(&mut self, index: I) -> &mut Value {
|
||||||
|
index.index_into_mut(self).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Extend<(Value, Value)> for Mapping {
|
||||||
|
#[inline]
|
||||||
|
fn extend<I: IntoIterator<Item = (Value, Value)>>(&mut self, iter: I) {
|
||||||
|
self.map.extend(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<(Value, Value)> for Mapping {
|
||||||
|
#[inline]
|
||||||
|
fn from_iter<I: IntoIterator<Item = (Value, Value)>>(iter: I) -> Self {
|
||||||
|
Mapping {
|
||||||
|
map: IndexMap::from_iter(iter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! delegate_iterator {
|
||||||
|
(($name:ident $($generics:tt)*) => $item:ty) => {
|
||||||
|
impl $($generics)* Iterator for $name $($generics)* {
|
||||||
|
type Item = $item;
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.iter.next()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.iter.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $($generics)* ExactSizeIterator for $name $($generics)* {
|
||||||
|
#[inline]
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.iter.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over `&serde_yaml::Mapping`.
|
||||||
|
pub struct Iter<'a> {
|
||||||
|
iter: indexmap::map::Iter<'a, Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate_iterator!((Iter<'a>) => (&'a Value, &'a Value));
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a Mapping {
|
||||||
|
type Item = (&'a Value, &'a Value);
|
||||||
|
type IntoIter = Iter<'a>;
|
||||||
|
#[inline]
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
Iter {
|
||||||
|
iter: self.map.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over `&mut serde_yaml::Mapping`.
|
||||||
|
pub struct IterMut<'a> {
|
||||||
|
iter: indexmap::map::IterMut<'a, Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate_iterator!((IterMut<'a>) => (&'a Value, &'a mut Value));
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a mut Mapping {
|
||||||
|
type Item = (&'a Value, &'a mut Value);
|
||||||
|
type IntoIter = IterMut<'a>;
|
||||||
|
#[inline]
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
IterMut {
|
||||||
|
iter: self.map.iter_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over `serde_yaml::Mapping` by value.
|
||||||
|
pub struct IntoIter {
|
||||||
|
iter: indexmap::map::IntoIter<Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate_iterator!((IntoIter) => (Value, Value));
|
||||||
|
|
||||||
|
impl IntoIterator for Mapping {
|
||||||
|
type Item = (Value, Value);
|
||||||
|
type IntoIter = IntoIter;
|
||||||
|
#[inline]
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
IntoIter {
|
||||||
|
iter: self.map.into_iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator of the keys of a `&serde_yaml::Mapping`.
|
||||||
|
pub struct Keys<'a> {
|
||||||
|
iter: indexmap::map::Keys<'a, Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate_iterator!((Keys<'a>) => &'a Value);
|
||||||
|
|
||||||
|
/// Iterator of the keys of a `serde_yaml::Mapping`.
|
||||||
|
pub struct IntoKeys {
|
||||||
|
iter: indexmap::map::IntoKeys<Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate_iterator!((IntoKeys) => Value);
|
||||||
|
|
||||||
|
/// Iterator of the values of a `&serde_yaml::Mapping`.
|
||||||
|
pub struct Values<'a> {
|
||||||
|
iter: indexmap::map::Values<'a, Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate_iterator!((Values<'a>) => &'a Value);
|
||||||
|
|
||||||
|
/// Iterator of the values of a `&mut serde_yaml::Mapping`.
|
||||||
|
pub struct ValuesMut<'a> {
|
||||||
|
iter: indexmap::map::ValuesMut<'a, Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate_iterator!((ValuesMut<'a>) => &'a mut Value);
|
||||||
|
|
||||||
|
/// Iterator of the values of a `serde_yaml::Mapping`.
|
||||||
|
pub struct IntoValues {
|
||||||
|
iter: indexmap::map::IntoValues<Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate_iterator!((IntoValues) => Value);
|
||||||
|
|
||||||
|
/// Entry for an existing key-value pair or a vacant location to insert one.
|
||||||
|
pub enum Entry<'a> {
|
||||||
|
/// Existing slot with equivalent key.
|
||||||
|
Occupied(OccupiedEntry<'a>),
|
||||||
|
/// Vacant slot (no equivalent key in the map).
|
||||||
|
Vacant(VacantEntry<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A view into an occupied entry in a [`Mapping`]. It is part of the [`Entry`]
|
||||||
|
/// enum.
|
||||||
|
pub struct OccupiedEntry<'a> {
|
||||||
|
occupied: indexmap::map::OccupiedEntry<'a, Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A view into a vacant entry in a [`Mapping`]. It is part of the [`Entry`]
|
||||||
|
/// enum.
|
||||||
|
pub struct VacantEntry<'a> {
|
||||||
|
vacant: indexmap::map::VacantEntry<'a, Value, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Entry<'a> {
|
||||||
|
/// Returns a reference to this entry's key.
|
||||||
|
pub fn key(&self) -> &Value {
|
||||||
|
match self {
|
||||||
|
Entry::Vacant(e) => e.key(),
|
||||||
|
Entry::Occupied(e) => e.key(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensures a value is in the entry by inserting the default if empty, and
|
||||||
|
/// returns a mutable reference to the value in the entry.
|
||||||
|
pub fn or_insert(self, default: Value) -> &'a mut Value {
|
||||||
|
match self {
|
||||||
|
Entry::Vacant(entry) => entry.insert(default),
|
||||||
|
Entry::Occupied(entry) => entry.into_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensures a value is in the entry by inserting the result of the default
|
||||||
|
/// function if empty, and returns a mutable reference to the value in the
|
||||||
|
/// entry.
|
||||||
|
pub fn or_insert_with<F>(self, default: F) -> &'a mut Value
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Value,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Entry::Vacant(entry) => entry.insert(default()),
|
||||||
|
Entry::Occupied(entry) => entry.into_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides in-place mutable access to an occupied entry before any
|
||||||
|
/// potential inserts into the map.
|
||||||
|
pub fn and_modify<F>(self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Value),
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
f(entry.get_mut());
|
||||||
|
Entry::Occupied(entry)
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => Entry::Vacant(entry),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> OccupiedEntry<'a> {
|
||||||
|
/// Gets a reference to the key in the entry.
|
||||||
|
#[inline]
|
||||||
|
pub fn key(&self) -> &Value {
|
||||||
|
self.occupied.key()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a reference to the value in the entry.
|
||||||
|
#[inline]
|
||||||
|
pub fn get(&self) -> &Value {
|
||||||
|
self.occupied.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the value in the entry.
|
||||||
|
#[inline]
|
||||||
|
pub fn get_mut(&mut self) -> &mut Value {
|
||||||
|
self.occupied.get_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the entry into a mutable reference to its value.
|
||||||
|
#[inline]
|
||||||
|
pub fn into_mut(self) -> &'a mut Value {
|
||||||
|
self.occupied.into_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the value of the entry with the `OccupiedEntry`'s key, and returns
|
||||||
|
/// the entry's old value.
|
||||||
|
#[inline]
|
||||||
|
pub fn insert(&mut self, value: Value) -> Value {
|
||||||
|
self.occupied.insert(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes the value of the entry out of the map, and returns it.
|
||||||
|
#[inline]
|
||||||
|
pub fn remove(self) -> Value {
|
||||||
|
self.occupied.swap_remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove and return the key, value pair stored in the map for this entry.
|
||||||
|
#[inline]
|
||||||
|
pub fn remove_entry(self) -> (Value, Value) {
|
||||||
|
self.occupied.swap_remove_entry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> VacantEntry<'a> {
|
||||||
|
/// Gets a reference to the key that would be used when inserting a value
|
||||||
|
/// through the VacantEntry.
|
||||||
|
#[inline]
|
||||||
|
pub fn key(&self) -> &Value {
|
||||||
|
self.vacant.key()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes ownership of the key, leaving the entry vacant.
|
||||||
|
#[inline]
|
||||||
|
pub fn into_key(self) -> Value {
|
||||||
|
self.vacant.into_key()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the value of the entry with the VacantEntry's key, and returns a
|
||||||
|
/// mutable reference to it.
|
||||||
|
#[inline]
|
||||||
|
pub fn insert(self, value: Value) -> &'a mut Value {
|
||||||
|
self.vacant.insert(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Mapping {
|
||||||
|
#[inline]
|
||||||
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
use serde::ser::SerializeMap;
|
||||||
|
let mut map_serializer = serializer.serialize_map(Some(self.len()))?;
|
||||||
|
for (k, v) in self {
|
||||||
|
map_serializer.serialize_entry(k, v)?;
|
||||||
|
}
|
||||||
|
map_serializer.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Mapping {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct Visitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||||
|
type Value = Mapping;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a YAML mapping")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
Ok(Mapping::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn visit_map<A>(self, mut data: A) -> Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: serde::de::MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut mapping = Mapping::new();
|
||||||
|
|
||||||
|
while let Some(key) = data.next_key()? {
|
||||||
|
match mapping.entry(key) {
|
||||||
|
Entry::Occupied(entry) => {
|
||||||
|
return Err(serde::de::Error::custom(DuplicateKeyError { entry }));
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
let value = data.next_value()?;
|
||||||
|
entry.insert(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(mapping)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_map(Visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DuplicateKeyError<'a> {
|
||||||
|
entry: OccupiedEntry<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for DuplicateKeyError<'a> {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("duplicate entry ")?;
|
||||||
|
match self.entry.key() {
|
||||||
|
Value::Null => formatter.write_str("with null key"),
|
||||||
|
Value::Bool(boolean) => write!(formatter, "with key `{}`", boolean),
|
||||||
|
Value::Number(number) => write!(formatter, "with key {}", number),
|
||||||
|
Value::String(string) => write!(formatter, "with key {:?}", string),
|
||||||
|
Value::Sequence(_) | Value::Mapping(_) | Value::Tagged(_) => {
|
||||||
|
formatter.write_str("in YAML map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
561
serde/src/number.rs
Normal file
561
serde/src/number.rs
Normal file
|
@ -0,0 +1,561 @@
|
||||||
|
use crate::de;
|
||||||
|
use crate::error::{self, Error, ErrorImpl};
|
||||||
|
use serde::de::{Unexpected, Visitor};
|
||||||
|
use serde::{forward_to_deserialize_any, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// Represents a YAML number, whether integer or floating point.
|
||||||
|
#[derive(Clone, PartialEq, PartialOrd)]
|
||||||
|
pub struct Number {
|
||||||
|
n: N,
|
||||||
|
}
|
||||||
|
|
||||||
|
// "N" is a prefix of "NegInt"... this is a false positive.
|
||||||
|
// https://github.com/Manishearth/rust-clippy/issues/1241
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
enum N {
|
||||||
|
PosInt(u64),
|
||||||
|
/// Always less than zero.
|
||||||
|
NegInt(i64),
|
||||||
|
/// May be infinite or NaN.
|
||||||
|
Float(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Number {
|
||||||
|
/// Returns true if the `Number` is an integer between `i64::MIN` and
|
||||||
|
/// `i64::MAX`.
|
||||||
|
///
|
||||||
|
/// For any Number on which `is_i64` returns true, `as_i64` is guaranteed to
|
||||||
|
/// return the integer value.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> serde_yaml::Result<()> {
|
||||||
|
/// let big = i64::MAX as u64 + 10;
|
||||||
|
/// let v: serde_yaml::Value = serde_yaml::from_str(r#"
|
||||||
|
/// a: 64
|
||||||
|
/// b: 9223372036854775817
|
||||||
|
/// c: 256.0
|
||||||
|
/// "#)?;
|
||||||
|
///
|
||||||
|
/// assert!(v["a"].is_i64());
|
||||||
|
///
|
||||||
|
/// // Greater than i64::MAX.
|
||||||
|
/// assert!(!v["b"].is_i64());
|
||||||
|
///
|
||||||
|
/// // Numbers with a decimal point are not considered integers.
|
||||||
|
/// assert!(!v["c"].is_i64());
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
#[allow(clippy::cast_sign_loss)]
|
||||||
|
pub fn is_i64(&self) -> bool {
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(v) => v <= i64::max_value() as u64,
|
||||||
|
N::NegInt(_) => true,
|
||||||
|
N::Float(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `Number` is an integer between zero and `u64::MAX`.
|
||||||
|
///
|
||||||
|
/// For any Number on which `is_u64` returns true, `as_u64` is guaranteed to
|
||||||
|
/// return the integer value.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> serde_yaml::Result<()> {
|
||||||
|
/// let v: serde_yaml::Value = serde_yaml::from_str(r#"
|
||||||
|
/// a: 64
|
||||||
|
/// b: -64
|
||||||
|
/// c: 256.0
|
||||||
|
/// "#)?;
|
||||||
|
///
|
||||||
|
/// assert!(v["a"].is_u64());
|
||||||
|
///
|
||||||
|
/// // Negative integer.
|
||||||
|
/// assert!(!v["b"].is_u64());
|
||||||
|
///
|
||||||
|
/// // Numbers with a decimal point are not considered integers.
|
||||||
|
/// assert!(!v["c"].is_u64());
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn is_u64(&self) -> bool {
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(_) => true,
|
||||||
|
N::NegInt(_) | N::Float(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `Number` can be represented by f64.
|
||||||
|
///
|
||||||
|
/// For any Number on which `is_f64` returns true, `as_f64` is guaranteed to
|
||||||
|
/// return the floating point value.
|
||||||
|
///
|
||||||
|
/// Currently this function returns true if and only if both `is_i64` and
|
||||||
|
/// `is_u64` return false but this is not a guarantee in the future.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> serde_yaml::Result<()> {
|
||||||
|
/// let v: serde_yaml::Value = serde_yaml::from_str(r#"
|
||||||
|
/// a: 256.0
|
||||||
|
/// b: 64
|
||||||
|
/// c: -64
|
||||||
|
/// "#)?;
|
||||||
|
///
|
||||||
|
/// assert!(v["a"].is_f64());
|
||||||
|
///
|
||||||
|
/// // Integers.
|
||||||
|
/// assert!(!v["b"].is_f64());
|
||||||
|
/// assert!(!v["c"].is_f64());
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn is_f64(&self) -> bool {
|
||||||
|
match self.n {
|
||||||
|
N::Float(_) => true,
|
||||||
|
N::PosInt(_) | N::NegInt(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Number` is an integer, represent it as i64 if possible. Returns
|
||||||
|
/// None otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> serde_yaml::Result<()> {
|
||||||
|
/// let big = i64::MAX as u64 + 10;
|
||||||
|
/// let v: serde_yaml::Value = serde_yaml::from_str(r#"
|
||||||
|
/// a: 64
|
||||||
|
/// b: 9223372036854775817
|
||||||
|
/// c: 256.0
|
||||||
|
/// "#)?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(v["a"].as_i64(), Some(64));
|
||||||
|
/// assert_eq!(v["b"].as_i64(), None);
|
||||||
|
/// assert_eq!(v["c"].as_i64(), None);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn as_i64(&self) -> Option<i64> {
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(n) => {
|
||||||
|
if n <= i64::max_value() as u64 {
|
||||||
|
Some(n as i64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
N::NegInt(n) => Some(n),
|
||||||
|
N::Float(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Number` is an integer, represent it as u64 if possible. Returns
|
||||||
|
/// None otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> serde_yaml::Result<()> {
|
||||||
|
/// let v: serde_yaml::Value = serde_yaml::from_str(r#"
|
||||||
|
/// a: 64
|
||||||
|
/// b: -64
|
||||||
|
/// c: 256.0
|
||||||
|
/// "#)?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(v["a"].as_u64(), Some(64));
|
||||||
|
/// assert_eq!(v["b"].as_u64(), None);
|
||||||
|
/// assert_eq!(v["c"].as_u64(), None);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn as_u64(&self) -> Option<u64> {
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(n) => Some(n),
|
||||||
|
N::NegInt(_) | N::Float(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the number as f64 if possible. Returns None otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> serde_yaml::Result<()> {
|
||||||
|
/// let v: serde_yaml::Value = serde_yaml::from_str(r#"
|
||||||
|
/// a: 256.0
|
||||||
|
/// b: 64
|
||||||
|
/// c: -64
|
||||||
|
/// "#)?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(v["a"].as_f64(), Some(256.0));
|
||||||
|
/// assert_eq!(v["b"].as_f64(), Some(64.0));
|
||||||
|
/// assert_eq!(v["c"].as_f64(), Some(-64.0));
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> serde_yaml::Result<()> {
|
||||||
|
/// let v: serde_yaml::Value = serde_yaml::from_str(".inf")?;
|
||||||
|
/// assert_eq!(v.as_f64(), Some(f64::INFINITY));
|
||||||
|
///
|
||||||
|
/// let v: serde_yaml::Value = serde_yaml::from_str("-.inf")?;
|
||||||
|
/// assert_eq!(v.as_f64(), Some(f64::NEG_INFINITY));
|
||||||
|
///
|
||||||
|
/// let v: serde_yaml::Value = serde_yaml::from_str(".nan")?;
|
||||||
|
/// assert!(v.as_f64().unwrap().is_nan());
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn as_f64(&self) -> Option<f64> {
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(n) => Some(n as f64),
|
||||||
|
N::NegInt(n) => Some(n as f64),
|
||||||
|
N::Float(n) => Some(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this value is NaN and false otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Number;
|
||||||
|
/// #
|
||||||
|
/// assert!(!Number::from(256.0).is_nan());
|
||||||
|
///
|
||||||
|
/// assert!(Number::from(f64::NAN).is_nan());
|
||||||
|
///
|
||||||
|
/// assert!(!Number::from(f64::INFINITY).is_nan());
|
||||||
|
///
|
||||||
|
/// assert!(!Number::from(f64::NEG_INFINITY).is_nan());
|
||||||
|
///
|
||||||
|
/// assert!(!Number::from(1).is_nan());
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn is_nan(&self) -> bool {
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(_) | N::NegInt(_) => false,
|
||||||
|
N::Float(f) => f.is_nan(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this value is positive infinity or negative infinity and
|
||||||
|
/// false otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Number;
|
||||||
|
/// #
|
||||||
|
/// assert!(!Number::from(256.0).is_infinite());
|
||||||
|
///
|
||||||
|
/// assert!(!Number::from(f64::NAN).is_infinite());
|
||||||
|
///
|
||||||
|
/// assert!(Number::from(f64::INFINITY).is_infinite());
|
||||||
|
///
|
||||||
|
/// assert!(Number::from(f64::NEG_INFINITY).is_infinite());
|
||||||
|
///
|
||||||
|
/// assert!(!Number::from(1).is_infinite());
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn is_infinite(&self) -> bool {
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(_) | N::NegInt(_) => false,
|
||||||
|
N::Float(f) => f.is_infinite(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this number is neither infinite nor NaN.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Number;
|
||||||
|
/// #
|
||||||
|
/// assert!(Number::from(256.0).is_finite());
|
||||||
|
///
|
||||||
|
/// assert!(!Number::from(f64::NAN).is_finite());
|
||||||
|
///
|
||||||
|
/// assert!(!Number::from(f64::INFINITY).is_finite());
|
||||||
|
///
|
||||||
|
/// assert!(!Number::from(f64::NEG_INFINITY).is_finite());
|
||||||
|
///
|
||||||
|
/// assert!(Number::from(1).is_finite());
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn is_finite(&self) -> bool {
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(_) | N::NegInt(_) => true,
|
||||||
|
N::Float(f) => f.is_finite(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Number {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(i) => formatter.write_str(itoa::Buffer::new().format(i)),
|
||||||
|
N::NegInt(i) => formatter.write_str(itoa::Buffer::new().format(i)),
|
||||||
|
N::Float(f) if f.is_nan() => formatter.write_str(".nan"),
|
||||||
|
N::Float(f) if f.is_infinite() => {
|
||||||
|
if f.is_sign_negative() {
|
||||||
|
formatter.write_str("-.inf")
|
||||||
|
} else {
|
||||||
|
formatter.write_str(".inf")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
N::Float(f) => formatter.write_str(ryu::Buffer::new().format_finite(f)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Number {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(repr: &str) -> Result<Self, Self::Err> {
|
||||||
|
if let Ok(result) = de::visit_int(NumberVisitor, repr) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if !de::digits_but_not_number(repr) {
|
||||||
|
if let Some(float) = de::parse_f64(repr) {
|
||||||
|
return Ok(float.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error::new(ErrorImpl::FailedToParseNumber))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for N {
|
||||||
|
fn eq(&self, other: &N) -> bool {
|
||||||
|
match (*self, *other) {
|
||||||
|
(N::PosInt(a), N::PosInt(b)) => a == b,
|
||||||
|
(N::NegInt(a), N::NegInt(b)) => a == b,
|
||||||
|
(N::Float(a), N::Float(b)) => {
|
||||||
|
if a.is_nan() && b.is_nan() {
|
||||||
|
// YAML only has one NaN;
|
||||||
|
// the bit representation isn't preserved
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
a == b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for N {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
match (*self, *other) {
|
||||||
|
(N::Float(a), N::Float(b)) => {
|
||||||
|
if a.is_nan() && b.is_nan() {
|
||||||
|
// YAML only has one NaN
|
||||||
|
Some(Ordering::Equal)
|
||||||
|
} else {
|
||||||
|
a.partial_cmp(&b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Some(self.total_cmp(other)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl N {
|
||||||
|
fn total_cmp(&self, other: &Self) -> Ordering {
|
||||||
|
match (*self, *other) {
|
||||||
|
(N::PosInt(a), N::PosInt(b)) => a.cmp(&b),
|
||||||
|
(N::NegInt(a), N::NegInt(b)) => a.cmp(&b),
|
||||||
|
// negint is always less than zero
|
||||||
|
(N::NegInt(_), N::PosInt(_)) => Ordering::Less,
|
||||||
|
(N::PosInt(_), N::NegInt(_)) => Ordering::Greater,
|
||||||
|
(N::Float(a), N::Float(b)) => a.partial_cmp(&b).unwrap_or_else(|| {
|
||||||
|
// arbitrarily sort the NaN last
|
||||||
|
if !a.is_nan() {
|
||||||
|
Ordering::Less
|
||||||
|
} else if !b.is_nan() {
|
||||||
|
Ordering::Greater
|
||||||
|
} else {
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
// arbitrarily sort integers below floats
|
||||||
|
// FIXME: maybe something more sensible?
|
||||||
|
(_, N::Float(_)) => Ordering::Less,
|
||||||
|
(N::Float(_), _) => Ordering::Greater,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Number {
|
||||||
|
pub(crate) fn total_cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.n.total_cmp(&other.n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Number {
|
||||||
|
#[inline]
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(i) => serializer.serialize_u64(i),
|
||||||
|
N::NegInt(i) => serializer.serialize_i64(i),
|
||||||
|
N::Float(f) => serializer.serialize_f64(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NumberVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for NumberVisitor {
|
||||||
|
type Value = Number;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn visit_i64<E>(self, value: i64) -> Result<Number, E> {
|
||||||
|
Ok(value.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn visit_u64<E>(self, value: u64) -> Result<Number, E> {
|
||||||
|
Ok(value.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn visit_f64<E>(self, value: f64) -> Result<Number, E> {
|
||||||
|
Ok(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Number {
|
||||||
|
#[inline]
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Number, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(NumberVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserializer<'de> for Number {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(i) => visitor.visit_u64(i),
|
||||||
|
N::NegInt(i) => visitor.visit_i64(i),
|
||||||
|
N::Float(f) => visitor.visit_f64(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_to_deserialize_any! {
|
||||||
|
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
|
||||||
|
bytes byte_buf option unit unit_struct newtype_struct seq tuple
|
||||||
|
tuple_struct map struct enum identifier ignored_any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, 'a> Deserializer<'de> for &'a Number {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
match self.n {
|
||||||
|
N::PosInt(i) => visitor.visit_u64(i),
|
||||||
|
N::NegInt(i) => visitor.visit_i64(i),
|
||||||
|
N::Float(f) => visitor.visit_f64(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_to_deserialize_any! {
|
||||||
|
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
|
||||||
|
bytes byte_buf option unit unit_struct newtype_struct seq tuple
|
||||||
|
tuple_struct map struct enum identifier ignored_any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! from_signed {
|
||||||
|
($($signed_ty:ident)*) => {
|
||||||
|
$(
|
||||||
|
impl From<$signed_ty> for Number {
|
||||||
|
#[inline]
|
||||||
|
#[allow(clippy::cast_sign_loss)]
|
||||||
|
fn from(i: $signed_ty) -> Self {
|
||||||
|
if i < 0 {
|
||||||
|
Number { n: N::NegInt(i as i64) }
|
||||||
|
} else {
|
||||||
|
Number { n: N::PosInt(i as u64) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! from_unsigned {
|
||||||
|
($($unsigned_ty:ident)*) => {
|
||||||
|
$(
|
||||||
|
impl From<$unsigned_ty> for Number {
|
||||||
|
#[inline]
|
||||||
|
fn from(u: $unsigned_ty) -> Self {
|
||||||
|
Number { n: N::PosInt(u as u64) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
from_signed!(i8 i16 i32 i64 isize);
|
||||||
|
from_unsigned!(u8 u16 u32 u64 usize);
|
||||||
|
|
||||||
|
impl From<f32> for Number {
|
||||||
|
fn from(f: f32) -> Self {
|
||||||
|
Number::from(f as f64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f64> for Number {
|
||||||
|
fn from(mut f: f64) -> Self {
|
||||||
|
if f.is_nan() {
|
||||||
|
// Destroy NaN sign, signaling, and payload. YAML only has one NaN.
|
||||||
|
f = f64::NAN.copysign(1.0);
|
||||||
|
}
|
||||||
|
Number { n: N::Float(f) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is fine, because we don't _really_ implement hash for floats
|
||||||
|
// all other hash functions should work as expected
|
||||||
|
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||||
|
impl Hash for Number {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
match self.n {
|
||||||
|
N::Float(_) => {
|
||||||
|
// you should feel bad for using f64 as a map key
|
||||||
|
3.hash(state);
|
||||||
|
}
|
||||||
|
N::PosInt(u) => u.hash(state),
|
||||||
|
N::NegInt(i) => i.hash(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unexpected(number: &Number) -> Unexpected {
|
||||||
|
match number.n {
|
||||||
|
N::PosInt(u) => Unexpected::Unsigned(u),
|
||||||
|
N::NegInt(i) => Unexpected::Signed(i),
|
||||||
|
N::Float(f) => Unexpected::Float(f),
|
||||||
|
}
|
||||||
|
}
|
34
serde/src/path.rs
Normal file
34
serde/src/path.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
|
/// Path to the current value in the input, like `dependencies.serde.typo1`.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum Path<'a> {
|
||||||
|
Root,
|
||||||
|
Seq { parent: &'a Path<'a>, index: usize },
|
||||||
|
Map { parent: &'a Path<'a>, key: &'a str },
|
||||||
|
Alias { parent: &'a Path<'a> },
|
||||||
|
Unknown { parent: &'a Path<'a> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Path<'a> {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
struct Parent<'a>(&'a Path<'a>);
|
||||||
|
|
||||||
|
impl<'a> Display for Parent<'a> {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
match self.0 {
|
||||||
|
Path::Root => Ok(()),
|
||||||
|
path => write!(formatter, "{}.", path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Path::Root => formatter.write_str("."),
|
||||||
|
Path::Seq { parent, index } => write!(formatter, "{}[{}]", parent, index),
|
||||||
|
Path::Map { parent, key } => write!(formatter, "{}{}", Parent(parent), key),
|
||||||
|
Path::Alias { parent } => write!(formatter, "{}", parent),
|
||||||
|
Path::Unknown { parent } => write!(formatter, "{}?", Parent(parent)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
714
serde/src/ser.rs
Normal file
714
serde/src/ser.rs
Normal file
|
@ -0,0 +1,714 @@
|
||||||
|
//! YAML Serialization
|
||||||
|
//!
|
||||||
|
//! This module provides YAML serialization with the type `Serializer`.
|
||||||
|
|
||||||
|
use crate::error::{self, Error, ErrorImpl};
|
||||||
|
use crate::libyaml;
|
||||||
|
use crate::libyaml::emitter::{Emitter, Event, Mapping, Scalar, ScalarStyle, Sequence};
|
||||||
|
use crate::value::tagged::{self, MaybeTag};
|
||||||
|
use serde::de::Visitor;
|
||||||
|
use serde::ser::{self, Serializer as _};
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::io;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::mem;
|
||||||
|
use std::num;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
/// A structure for serializing Rust values into YAML.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use anyhow::Result;
|
||||||
|
/// use serde::Serialize;
|
||||||
|
/// use std::collections::BTreeMap;
|
||||||
|
///
|
||||||
|
/// fn main() -> Result<()> {
|
||||||
|
/// let mut buffer = Vec::new();
|
||||||
|
/// let mut ser = serde_yaml::Serializer::new(&mut buffer);
|
||||||
|
///
|
||||||
|
/// let mut object = BTreeMap::new();
|
||||||
|
/// object.insert("k", 107);
|
||||||
|
/// object.serialize(&mut ser)?;
|
||||||
|
///
|
||||||
|
/// object.insert("J", 74);
|
||||||
|
/// object.serialize(&mut ser)?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(buffer, b"k: 107\n---\nJ: 74\nk: 107\n");
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct Serializer<W> {
|
||||||
|
depth: usize,
|
||||||
|
state: State,
|
||||||
|
emitter: Emitter<'static>,
|
||||||
|
writer: PhantomData<W>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
NothingInParticular,
|
||||||
|
CheckForTag,
|
||||||
|
CheckForDuplicateTag,
|
||||||
|
FoundTag(String),
|
||||||
|
AlreadyTagged,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W> Serializer<W>
|
||||||
|
where
|
||||||
|
W: io::Write,
|
||||||
|
{
|
||||||
|
/// Creates a new YAML serializer.
|
||||||
|
pub fn new(writer: W) -> Self {
|
||||||
|
let mut emitter = Emitter::new({
|
||||||
|
let writer = Box::new(writer);
|
||||||
|
unsafe { mem::transmute::<Box<dyn io::Write>, Box<dyn io::Write>>(writer) }
|
||||||
|
});
|
||||||
|
emitter.emit(Event::StreamStart).unwrap();
|
||||||
|
Serializer {
|
||||||
|
depth: 0,
|
||||||
|
state: State::NothingInParticular,
|
||||||
|
emitter,
|
||||||
|
writer: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls [`.flush()`](io::Write::flush) on the underlying `io::Write`
|
||||||
|
/// object.
|
||||||
|
pub fn flush(&mut self) -> Result<()> {
|
||||||
|
self.emitter.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unwrap the underlying `io::Write` object from the `Serializer`.
|
||||||
|
pub fn into_inner(mut self) -> Result<W> {
|
||||||
|
self.emitter.emit(Event::StreamEnd)?;
|
||||||
|
self.emitter.flush()?;
|
||||||
|
let writer = self.emitter.into_inner();
|
||||||
|
Ok(*unsafe { Box::from_raw(Box::into_raw(writer).cast::<W>()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_scalar(&mut self, mut scalar: Scalar) -> Result<()> {
|
||||||
|
self.flush_mapping_start()?;
|
||||||
|
if let Some(tag) = self.take_tag() {
|
||||||
|
scalar.tag = Some(tag);
|
||||||
|
}
|
||||||
|
self.value_start()?;
|
||||||
|
self.emitter.emit(Event::Scalar(scalar))?;
|
||||||
|
self.value_end()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_sequence_start(&mut self) -> Result<()> {
|
||||||
|
self.flush_mapping_start()?;
|
||||||
|
self.value_start()?;
|
||||||
|
let tag = self.take_tag();
|
||||||
|
self.emitter.emit(Event::SequenceStart(Sequence { tag }))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_sequence_end(&mut self) -> Result<()> {
|
||||||
|
self.emitter.emit(Event::SequenceEnd)?;
|
||||||
|
self.value_end()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_mapping_start(&mut self) -> Result<()> {
|
||||||
|
self.flush_mapping_start()?;
|
||||||
|
self.value_start()?;
|
||||||
|
let tag = self.take_tag();
|
||||||
|
self.emitter.emit(Event::MappingStart(Mapping { tag }))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_mapping_end(&mut self) -> Result<()> {
|
||||||
|
self.emitter.emit(Event::MappingEnd)?;
|
||||||
|
self.value_end()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_start(&mut self) -> Result<()> {
|
||||||
|
if self.depth == 0 {
|
||||||
|
self.emitter.emit(Event::DocumentStart)?;
|
||||||
|
}
|
||||||
|
self.depth += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_end(&mut self) -> Result<()> {
|
||||||
|
self.depth -= 1;
|
||||||
|
if self.depth == 0 {
|
||||||
|
self.emitter.emit(Event::DocumentEnd)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take_tag(&mut self) -> Option<String> {
|
||||||
|
let state = mem::replace(&mut self.state, State::NothingInParticular);
|
||||||
|
if let State::FoundTag(mut tag) = state {
|
||||||
|
if !tag.starts_with('!') {
|
||||||
|
tag.insert(0, '!');
|
||||||
|
}
|
||||||
|
Some(tag)
|
||||||
|
} else {
|
||||||
|
self.state = state;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_mapping_start(&mut self) -> Result<()> {
|
||||||
|
if let State::CheckForTag = self.state {
|
||||||
|
self.state = State::NothingInParticular;
|
||||||
|
self.emit_mapping_start()?;
|
||||||
|
} else if let State::CheckForDuplicateTag = self.state {
|
||||||
|
self.state = State::NothingInParticular;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, W> ser::Serializer for &'a mut Serializer<W>
|
||||||
|
where
|
||||||
|
W: io::Write,
|
||||||
|
{
|
||||||
|
type Ok = ();
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Self;
|
||||||
|
type SerializeTuple = Self;
|
||||||
|
type SerializeTupleStruct = Self;
|
||||||
|
type SerializeTupleVariant = Self;
|
||||||
|
type SerializeMap = Self;
|
||||||
|
type SerializeStruct = Self;
|
||||||
|
type SerializeStructVariant = Self;
|
||||||
|
|
||||||
|
fn serialize_bool(self, v: bool) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: if v { "true" } else { "false" },
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i8(self, v: i8) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: itoa::Buffer::new().format(v),
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i16(self, v: i16) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: itoa::Buffer::new().format(v),
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i32(self, v: i32) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: itoa::Buffer::new().format(v),
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i64(self, v: i64) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: itoa::Buffer::new().format(v),
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i128(self, v: i128) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: itoa::Buffer::new().format(v),
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u8(self, v: u8) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: itoa::Buffer::new().format(v),
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u16(self, v: u16) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: itoa::Buffer::new().format(v),
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u32(self, v: u32) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: itoa::Buffer::new().format(v),
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u64(self, v: u64) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: itoa::Buffer::new().format(v),
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u128(self, v: u128) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: itoa::Buffer::new().format(v),
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_f32(self, v: f32) -> Result<()> {
|
||||||
|
let mut buffer = ryu::Buffer::new();
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: match v.classify() {
|
||||||
|
num::FpCategory::Infinite if v.is_sign_positive() => ".inf",
|
||||||
|
num::FpCategory::Infinite => "-.inf",
|
||||||
|
num::FpCategory::Nan => ".nan",
|
||||||
|
_ => buffer.format_finite(v),
|
||||||
|
},
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_f64(self, v: f64) -> Result<()> {
|
||||||
|
let mut buffer = ryu::Buffer::new();
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: match v.classify() {
|
||||||
|
num::FpCategory::Infinite if v.is_sign_positive() => ".inf",
|
||||||
|
num::FpCategory::Infinite => "-.inf",
|
||||||
|
num::FpCategory::Nan => ".nan",
|
||||||
|
_ => buffer.format_finite(v),
|
||||||
|
},
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_char(self, value: char) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: value.encode_utf8(&mut [0u8; 4]),
|
||||||
|
style: ScalarStyle::SingleQuoted,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_str(self, value: &str) -> Result<()> {
|
||||||
|
struct InferScalarStyle;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for InferScalarStyle {
|
||||||
|
type Value = ScalarStyle;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("I wonder")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_bool<E>(self, _v: bool) -> Result<Self::Value, E> {
|
||||||
|
Ok(ScalarStyle::SingleQuoted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_i64<E>(self, _v: i64) -> Result<Self::Value, E> {
|
||||||
|
Ok(ScalarStyle::SingleQuoted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_i128<E>(self, _v: i128) -> Result<Self::Value, E> {
|
||||||
|
Ok(ScalarStyle::SingleQuoted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u64<E>(self, _v: u64) -> Result<Self::Value, E> {
|
||||||
|
Ok(ScalarStyle::SingleQuoted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u128<E>(self, _v: u128) -> Result<Self::Value, E> {
|
||||||
|
Ok(ScalarStyle::SingleQuoted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_f64<E>(self, _v: f64) -> Result<Self::Value, E> {
|
||||||
|
Ok(ScalarStyle::SingleQuoted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
|
||||||
|
Ok(if crate::de::digits_but_not_number(v) {
|
||||||
|
ScalarStyle::SingleQuoted
|
||||||
|
} else {
|
||||||
|
ScalarStyle::Any
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_unit<E>(self) -> Result<Self::Value, E> {
|
||||||
|
Ok(ScalarStyle::SingleQuoted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let style = if value.contains('\n') {
|
||||||
|
ScalarStyle::Literal
|
||||||
|
} else {
|
||||||
|
let result = crate::de::visit_untagged_scalar(
|
||||||
|
InferScalarStyle,
|
||||||
|
value,
|
||||||
|
None,
|
||||||
|
libyaml::parser::ScalarStyle::Plain,
|
||||||
|
);
|
||||||
|
result.unwrap_or(ScalarStyle::Any)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value,
|
||||||
|
style,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_bytes(self, _value: &[u8]) -> Result<()> {
|
||||||
|
Err(error::new(ErrorImpl::BytesUnsupported))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit(self) -> Result<()> {
|
||||||
|
self.emit_scalar(Scalar {
|
||||||
|
tag: None,
|
||||||
|
value: "null",
|
||||||
|
style: ScalarStyle::Plain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
|
||||||
|
self.serialize_unit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit_variant(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_variant_index: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.serialize_str(variant)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
value.serialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_newtype_variant<T>(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_variant_index: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
if let State::FoundTag(_) = self.state {
|
||||||
|
return Err(error::new(ErrorImpl::SerializeNestedEnum));
|
||||||
|
}
|
||||||
|
self.state = State::FoundTag(variant.to_owned());
|
||||||
|
value.serialize(&mut *self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_none(self) -> Result<()> {
|
||||||
|
self.serialize_unit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_some<V>(self, value: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
value.serialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
|
||||||
|
self.emit_sequence_start()?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
|
||||||
|
self.emit_sequence_start()?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple_struct(
|
||||||
|
self,
|
||||||
|
_name: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeTupleStruct> {
|
||||||
|
self.emit_sequence_start()?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple_variant(
|
||||||
|
self,
|
||||||
|
_enm: &'static str,
|
||||||
|
_idx: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeTupleVariant> {
|
||||||
|
if let State::FoundTag(_) = self.state {
|
||||||
|
return Err(error::new(ErrorImpl::SerializeNestedEnum));
|
||||||
|
}
|
||||||
|
self.state = State::FoundTag(variant.to_owned());
|
||||||
|
self.emit_sequence_start()?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
|
||||||
|
if len == Some(1) {
|
||||||
|
self.state = if let State::FoundTag(_) = self.state {
|
||||||
|
self.emit_mapping_start()?;
|
||||||
|
State::CheckForDuplicateTag
|
||||||
|
} else {
|
||||||
|
State::CheckForTag
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
self.emit_mapping_start()?;
|
||||||
|
}
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
|
||||||
|
self.emit_mapping_start()?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_struct_variant(
|
||||||
|
self,
|
||||||
|
_enm: &'static str,
|
||||||
|
_idx: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<Self::SerializeStructVariant> {
|
||||||
|
if let State::FoundTag(_) = self.state {
|
||||||
|
return Err(error::new(ErrorImpl::SerializeNestedEnum));
|
||||||
|
}
|
||||||
|
self.state = State::FoundTag(variant.to_owned());
|
||||||
|
self.emit_mapping_start()?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_str<T>(self, value: &T) -> Result<Self::Ok>
|
||||||
|
where
|
||||||
|
T: ?Sized + Display,
|
||||||
|
{
|
||||||
|
let string = if let State::CheckForTag | State::CheckForDuplicateTag = self.state {
|
||||||
|
match tagged::check_for_tag(value) {
|
||||||
|
MaybeTag::NotTag(string) => string,
|
||||||
|
MaybeTag::Tag(string) => {
|
||||||
|
return if let State::CheckForDuplicateTag = self.state {
|
||||||
|
Err(error::new(ErrorImpl::SerializeNestedEnum))
|
||||||
|
} else {
|
||||||
|
self.state = State::FoundTag(string);
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.serialize_str(&string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, W> ser::SerializeSeq for &'a mut Serializer<W>
|
||||||
|
where
|
||||||
|
W: io::Write,
|
||||||
|
{
|
||||||
|
type Ok = ();
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_element<T>(&mut self, elem: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
elem.serialize(&mut **self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<()> {
|
||||||
|
self.emit_sequence_end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, W> ser::SerializeTuple for &'a mut Serializer<W>
|
||||||
|
where
|
||||||
|
W: io::Write,
|
||||||
|
{
|
||||||
|
type Ok = ();
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_element<T>(&mut self, elem: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
elem.serialize(&mut **self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<()> {
|
||||||
|
self.emit_sequence_end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, W> ser::SerializeTupleStruct for &'a mut Serializer<W>
|
||||||
|
where
|
||||||
|
W: io::Write,
|
||||||
|
{
|
||||||
|
type Ok = ();
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, value: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
value.serialize(&mut **self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<()> {
|
||||||
|
self.emit_sequence_end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, W> ser::SerializeTupleVariant for &'a mut Serializer<W>
|
||||||
|
where
|
||||||
|
W: io::Write,
|
||||||
|
{
|
||||||
|
type Ok = ();
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, v: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
v.serialize(&mut **self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<()> {
|
||||||
|
self.emit_sequence_end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, W> ser::SerializeMap for &'a mut Serializer<W>
|
||||||
|
where
|
||||||
|
W: io::Write,
|
||||||
|
{
|
||||||
|
type Ok = ();
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.flush_mapping_start()?;
|
||||||
|
key.serialize(&mut **self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
value.serialize(&mut **self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_entry<K, V>(&mut self, key: &K, value: &V) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
K: ?Sized + ser::Serialize,
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
key.serialize(&mut **self)?;
|
||||||
|
let tagged = matches!(self.state, State::FoundTag(_));
|
||||||
|
value.serialize(&mut **self)?;
|
||||||
|
if tagged {
|
||||||
|
self.state = State::AlreadyTagged;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<()> {
|
||||||
|
if let State::CheckForTag = self.state {
|
||||||
|
self.emit_mapping_start()?;
|
||||||
|
}
|
||||||
|
if !matches!(self.state, State::AlreadyTagged) {
|
||||||
|
self.emit_mapping_end()?;
|
||||||
|
}
|
||||||
|
self.state = State::NothingInParticular;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, W> ser::SerializeStruct for &'a mut Serializer<W>
|
||||||
|
where
|
||||||
|
W: io::Write,
|
||||||
|
{
|
||||||
|
type Ok = ();
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, key: &'static str, value: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.serialize_str(key)?;
|
||||||
|
value.serialize(&mut **self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<()> {
|
||||||
|
self.emit_mapping_end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, W> ser::SerializeStructVariant for &'a mut Serializer<W>
|
||||||
|
where
|
||||||
|
W: io::Write,
|
||||||
|
{
|
||||||
|
type Ok = ();
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, field: &'static str, v: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.serialize_str(field)?;
|
||||||
|
v.serialize(&mut **self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<()> {
|
||||||
|
self.emit_mapping_end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize the given data structure as YAML into the IO stream.
|
||||||
|
///
|
||||||
|
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
|
||||||
|
/// return an error.
|
||||||
|
pub fn to_writer<W, T>(writer: W, value: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
W: io::Write,
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
let mut serializer = Serializer::new(writer);
|
||||||
|
value.serialize(&mut serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize the given data structure as a String of YAML.
|
||||||
|
///
|
||||||
|
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
|
||||||
|
/// return an error.
|
||||||
|
pub fn to_string<T>(value: &T) -> Result<String>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
let mut vec = Vec::with_capacity(128);
|
||||||
|
to_writer(&mut vec, value)?;
|
||||||
|
String::from_utf8(vec).map_err(|error| error::new(ErrorImpl::FromUtf8(error)))
|
||||||
|
}
|
1242
serde/src/value/de.rs
Normal file
1242
serde/src/value/de.rs
Normal file
File diff suppressed because it is too large
Load diff
57
serde/src/value/debug.rs
Normal file
57
serde/src/value/debug.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use crate::mapping::Mapping;
|
||||||
|
use crate::value::{Number, Value};
|
||||||
|
use std::fmt::{self, Debug, Display};
|
||||||
|
|
||||||
|
impl Debug for Value {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Value::Null => formatter.write_str("Null"),
|
||||||
|
Value::Bool(boolean) => write!(formatter, "Bool({})", boolean),
|
||||||
|
Value::Number(number) => write!(formatter, "Number({})", number),
|
||||||
|
Value::String(string) => write!(formatter, "String({:?})", string),
|
||||||
|
Value::Sequence(sequence) => {
|
||||||
|
formatter.write_str("Sequence ")?;
|
||||||
|
formatter.debug_list().entries(sequence).finish()
|
||||||
|
}
|
||||||
|
Value::Mapping(mapping) => Debug::fmt(mapping, formatter),
|
||||||
|
Value::Tagged(tagged) => Debug::fmt(tagged, formatter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DisplayNumber<'a>(&'a Number);
|
||||||
|
|
||||||
|
impl<'a> Debug for DisplayNumber<'a> {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(self.0, formatter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Number {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "Number({})", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Mapping {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("Mapping ")?;
|
||||||
|
let mut debug = formatter.debug_map();
|
||||||
|
for (k, v) in self {
|
||||||
|
let tmp;
|
||||||
|
debug.entry(
|
||||||
|
match k {
|
||||||
|
Value::Bool(boolean) => boolean,
|
||||||
|
Value::Number(number) => {
|
||||||
|
tmp = DisplayNumber(number);
|
||||||
|
&tmp
|
||||||
|
}
|
||||||
|
Value::String(string) => string,
|
||||||
|
_ => k,
|
||||||
|
},
|
||||||
|
v,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
debug.finish()
|
||||||
|
}
|
||||||
|
}
|
178
serde/src/value/from.rs
Normal file
178
serde/src/value/from.rs
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
use crate::{Mapping, Value};
|
||||||
|
|
||||||
|
// Implement a bunch of conversion to make it easier to create YAML values
|
||||||
|
// on the fly.
|
||||||
|
|
||||||
|
macro_rules! from_number {
|
||||||
|
($($ty:ident)*) => {
|
||||||
|
$(
|
||||||
|
impl From<$ty> for Value {
|
||||||
|
fn from(n: $ty) -> Self {
|
||||||
|
Value::Number(n.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
from_number! {
|
||||||
|
i8 i16 i32 i64 isize
|
||||||
|
u8 u16 u32 u64 usize
|
||||||
|
f32 f64
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for Value {
|
||||||
|
/// Convert boolean to `Value`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
///
|
||||||
|
/// let b = false;
|
||||||
|
/// let x: Value = b.into();
|
||||||
|
/// ```
|
||||||
|
fn from(f: bool) -> Self {
|
||||||
|
Value::Bool(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Value {
|
||||||
|
/// Convert `String` to `Value`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
///
|
||||||
|
/// let s: String = "lorem".to_string();
|
||||||
|
/// let x: Value = s.into();
|
||||||
|
/// ```
|
||||||
|
fn from(f: String) -> Self {
|
||||||
|
Value::String(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Value {
|
||||||
|
/// Convert string slice to `Value`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
///
|
||||||
|
/// let s: &str = "lorem";
|
||||||
|
/// let x: Value = s.into();
|
||||||
|
/// ```
|
||||||
|
fn from(f: &str) -> Self {
|
||||||
|
Value::String(f.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
impl<'a> From<Cow<'a, str>> for Value {
|
||||||
|
/// Convert copy-on-write string to `Value`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
/// use std::borrow::Cow;
|
||||||
|
///
|
||||||
|
/// let s: Cow<str> = Cow::Borrowed("lorem");
|
||||||
|
/// let x: Value = s.into();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
/// use std::borrow::Cow;
|
||||||
|
///
|
||||||
|
/// let s: Cow<str> = Cow::Owned("lorem".to_string());
|
||||||
|
/// let x: Value = s.into();
|
||||||
|
/// ```
|
||||||
|
fn from(f: Cow<'a, str>) -> Self {
|
||||||
|
Value::String(f.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Mapping> for Value {
|
||||||
|
/// Convert map (with string keys) to `Value`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::{Mapping, Value};
|
||||||
|
///
|
||||||
|
/// let mut m = Mapping::new();
|
||||||
|
/// m.insert("Lorem".into(), "ipsum".into());
|
||||||
|
/// let x: Value = m.into();
|
||||||
|
/// ```
|
||||||
|
fn from(f: Mapping) -> Self {
|
||||||
|
Value::Mapping(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Value>> From<Vec<T>> for Value {
|
||||||
|
/// Convert a `Vec` to `Value`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
///
|
||||||
|
/// let v = vec!["lorem", "ipsum", "dolor"];
|
||||||
|
/// let x: Value = v.into();
|
||||||
|
/// ```
|
||||||
|
fn from(f: Vec<T>) -> Self {
|
||||||
|
Value::Sequence(f.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Clone + Into<Value>> From<&'a [T]> for Value {
|
||||||
|
/// Convert a slice to `Value`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
///
|
||||||
|
/// let v: &[&str] = &["lorem", "ipsum", "dolor"];
|
||||||
|
/// let x: Value = v.into();
|
||||||
|
/// ```
|
||||||
|
fn from(f: &'a [T]) -> Self {
|
||||||
|
Value::Sequence(f.iter().cloned().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Value>> FromIterator<T> for Value {
|
||||||
|
/// Convert an iteratable type to a YAML sequence
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
///
|
||||||
|
/// let v = std::iter::repeat(42).take(5);
|
||||||
|
/// let x: Value = v.collect();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
///
|
||||||
|
/// let v: Vec<_> = vec!["lorem", "ipsum", "dolor"];
|
||||||
|
/// let x: Value = v.into_iter().collect();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::iter::FromIterator;
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
///
|
||||||
|
/// let x: Value = Value::from_iter(vec!["lorem", "ipsum", "dolor"]);
|
||||||
|
/// ```
|
||||||
|
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
||||||
|
let vec = iter.into_iter().map(T::into).collect();
|
||||||
|
|
||||||
|
Value::Sequence(vec)
|
||||||
|
}
|
||||||
|
}
|
279
serde/src/value/index.rs
Normal file
279
serde/src/value/index.rs
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
use crate::mapping::Entry;
|
||||||
|
use crate::{mapping, private, Mapping, Value};
|
||||||
|
use std::fmt::{self, Debug};
|
||||||
|
use std::ops;
|
||||||
|
|
||||||
|
/// A type that can be used to index into a `serde_yaml::Value`. See the `get`
|
||||||
|
/// and `get_mut` methods of `Value`.
|
||||||
|
///
|
||||||
|
/// This trait is sealed and cannot be implemented for types outside of
|
||||||
|
/// `serde_yaml`.
|
||||||
|
pub trait Index: private::Sealed {
|
||||||
|
/// Return None if the key is not already in the sequence or object.
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value>;
|
||||||
|
|
||||||
|
/// Return None if the key is not already in the sequence or object.
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value>;
|
||||||
|
|
||||||
|
/// Panic if sequence index out of bounds. If key is not already in the object,
|
||||||
|
/// insert it with a value of null. Panic if Value is a type that cannot be
|
||||||
|
/// indexed into, except if Value is null then it can be treated as an empty
|
||||||
|
/// object.
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index for usize {
|
||||||
|
fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
|
||||||
|
match v.untag_ref() {
|
||||||
|
Value::Sequence(vec) => vec.get(*self),
|
||||||
|
Value::Mapping(vec) => vec.get(&Value::Number((*self).into())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> {
|
||||||
|
match v.untag_mut() {
|
||||||
|
Value::Sequence(vec) => vec.get_mut(*self),
|
||||||
|
Value::Mapping(vec) => vec.get_mut(&Value::Number((*self).into())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn index_or_insert<'v>(&self, mut v: &'v mut Value) -> &'v mut Value {
|
||||||
|
loop {
|
||||||
|
match v {
|
||||||
|
Value::Sequence(vec) => {
|
||||||
|
let len = vec.len();
|
||||||
|
return vec.get_mut(*self).unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"cannot access index {} of YAML sequence of length {}",
|
||||||
|
self, len
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Value::Mapping(map) => {
|
||||||
|
let n = Value::Number((*self).into());
|
||||||
|
return map.entry(n).or_insert(Value::Null);
|
||||||
|
}
|
||||||
|
Value::Tagged(tagged) => v = &mut tagged.value,
|
||||||
|
_ => panic!("cannot access index {} of YAML {}", self, Type(v)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_into_mapping<'v, I>(index: &I, v: &'v Value) -> Option<&'v Value>
|
||||||
|
where
|
||||||
|
I: ?Sized + mapping::Index,
|
||||||
|
{
|
||||||
|
match v.untag_ref() {
|
||||||
|
Value::Mapping(map) => map.get(index),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_into_mut_mapping<'v, I>(index: &I, v: &'v mut Value) -> Option<&'v mut Value>
|
||||||
|
where
|
||||||
|
I: ?Sized + mapping::Index,
|
||||||
|
{
|
||||||
|
match v.untag_mut() {
|
||||||
|
Value::Mapping(map) => map.get_mut(index),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_or_insert_mapping<'v, I>(index: &I, mut v: &'v mut Value) -> &'v mut Value
|
||||||
|
where
|
||||||
|
I: ?Sized + mapping::Index + ToOwned + Debug,
|
||||||
|
Value: From<I::Owned>,
|
||||||
|
{
|
||||||
|
if let Value::Null = *v {
|
||||||
|
*v = Value::Mapping(Mapping::new());
|
||||||
|
return match v {
|
||||||
|
Value::Mapping(map) => match map.entry(index.to_owned().into()) {
|
||||||
|
Entry::Vacant(entry) => entry.insert(Value::Null),
|
||||||
|
Entry::Occupied(_) => unreachable!(),
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
match v {
|
||||||
|
Value::Mapping(map) => {
|
||||||
|
return map.entry(index.to_owned().into()).or_insert(Value::Null);
|
||||||
|
}
|
||||||
|
Value::Tagged(tagged) => v = &mut tagged.value,
|
||||||
|
_ => panic!("cannot access key {:?} in YAML {}", index, Type(v)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index for Value {
|
||||||
|
fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
|
||||||
|
index_into_mapping(self, v)
|
||||||
|
}
|
||||||
|
fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> {
|
||||||
|
index_into_mut_mapping(self, v)
|
||||||
|
}
|
||||||
|
fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value {
|
||||||
|
index_or_insert_mapping(self, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index for str {
|
||||||
|
fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
|
||||||
|
index_into_mapping(self, v)
|
||||||
|
}
|
||||||
|
fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> {
|
||||||
|
index_into_mut_mapping(self, v)
|
||||||
|
}
|
||||||
|
fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value {
|
||||||
|
index_or_insert_mapping(self, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index for String {
|
||||||
|
fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
|
||||||
|
self.as_str().index_into(v)
|
||||||
|
}
|
||||||
|
fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> {
|
||||||
|
self.as_str().index_into_mut(v)
|
||||||
|
}
|
||||||
|
fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value {
|
||||||
|
self.as_str().index_or_insert(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Index for &'a T
|
||||||
|
where
|
||||||
|
T: ?Sized + Index,
|
||||||
|
{
|
||||||
|
fn index_into<'v>(&self, v: &'v Value) -> Option<&'v Value> {
|
||||||
|
(**self).index_into(v)
|
||||||
|
}
|
||||||
|
fn index_into_mut<'v>(&self, v: &'v mut Value) -> Option<&'v mut Value> {
|
||||||
|
(**self).index_into_mut(v)
|
||||||
|
}
|
||||||
|
fn index_or_insert<'v>(&self, v: &'v mut Value) -> &'v mut Value {
|
||||||
|
(**self).index_or_insert(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used in panic messages.
|
||||||
|
struct Type<'a>(&'a Value);
|
||||||
|
|
||||||
|
impl<'a> fmt::Display for Type<'a> {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self.0 {
|
||||||
|
Value::Null => formatter.write_str("null"),
|
||||||
|
Value::Bool(_) => formatter.write_str("boolean"),
|
||||||
|
Value::Number(_) => formatter.write_str("number"),
|
||||||
|
Value::String(_) => formatter.write_str("string"),
|
||||||
|
Value::Sequence(_) => formatter.write_str("sequence"),
|
||||||
|
Value::Mapping(_) => formatter.write_str("mapping"),
|
||||||
|
Value::Tagged(_) => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The usual semantics of Index is to panic on invalid indexing.
|
||||||
|
//
|
||||||
|
// That said, the usual semantics are for things like `Vec` and `BTreeMap` which
|
||||||
|
// have different use cases than Value. If you are working with a Vec, you know
|
||||||
|
// that you are working with a Vec and you can get the len of the Vec and make
|
||||||
|
// sure your indices are within bounds. The Value use cases are more
|
||||||
|
// loosey-goosey. You got some YAML from an endpoint and you want to pull values
|
||||||
|
// out of it. Outside of this Index impl, you already have the option of using
|
||||||
|
// `value.as_sequence()` and working with the Vec directly, or matching on
|
||||||
|
// `Value::Sequence` and getting the Vec directly. The Index impl means you can
|
||||||
|
// skip that and index directly into the thing using a concise syntax. You don't
|
||||||
|
// have to check the type, you don't have to check the len, it is all about what
|
||||||
|
// you expect the Value to look like.
|
||||||
|
//
|
||||||
|
// Basically the use cases that would be well served by panicking here are
|
||||||
|
// better served by using one of the other approaches: `get` and `get_mut`,
|
||||||
|
// `as_sequence`, or match. The value of this impl is that it adds a way of
|
||||||
|
// working with Value that is not well served by the existing approaches:
|
||||||
|
// concise and careless and sometimes that is exactly what you want.
|
||||||
|
impl<I> ops::Index<I> for Value
|
||||||
|
where
|
||||||
|
I: Index,
|
||||||
|
{
|
||||||
|
type Output = Value;
|
||||||
|
|
||||||
|
/// Index into a `serde_yaml::Value` using the syntax `value[0]` or
|
||||||
|
/// `value["k"]`.
|
||||||
|
///
|
||||||
|
/// Returns `Value::Null` if the type of `self` does not match the type of
|
||||||
|
/// the index, for example if the index is a string and `self` is a sequence
|
||||||
|
/// or a number. Also returns `Value::Null` if the given key does not exist
|
||||||
|
/// in the map or the given index is not within the bounds of the sequence.
|
||||||
|
///
|
||||||
|
/// For retrieving deeply nested values, you should have a look at the
|
||||||
|
/// `Value::pointer` method.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// #
|
||||||
|
/// # fn main() -> serde_yaml::Result<()> {
|
||||||
|
/// let data: serde_yaml::Value = serde_yaml::from_str(r#"{ x: { y: [z, zz] } }"#)?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(data["x"]["y"], serde_yaml::from_str::<Value>(r#"["z", "zz"]"#).unwrap());
|
||||||
|
/// assert_eq!(data["x"]["y"][0], serde_yaml::from_str::<Value>(r#""z""#).unwrap());
|
||||||
|
///
|
||||||
|
/// assert_eq!(data["a"], serde_yaml::from_str::<Value>(r#"null"#).unwrap()); // returns null for undefined values
|
||||||
|
/// assert_eq!(data["a"]["b"], serde_yaml::from_str::<Value>(r#"null"#).unwrap()); // does not panic
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
fn index(&self, index: I) -> &Value {
|
||||||
|
static NULL: Value = Value::Null;
|
||||||
|
index.index_into(self).unwrap_or(&NULL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> ops::IndexMut<I> for Value
|
||||||
|
where
|
||||||
|
I: Index,
|
||||||
|
{
|
||||||
|
/// Write into a `serde_yaml::Value` using the syntax `value[0] = ...` or
|
||||||
|
/// `value["k"] = ...`.
|
||||||
|
///
|
||||||
|
/// If the index is a number, the value must be a sequence of length bigger
|
||||||
|
/// than the index. Indexing into a value that is not a sequence or a
|
||||||
|
/// sequence that is too small will panic.
|
||||||
|
///
|
||||||
|
/// If the index is a string, the value must be an object or null which is
|
||||||
|
/// treated like an empty object. If the key is not already present in the
|
||||||
|
/// object, it will be inserted with a value of null. Indexing into a value
|
||||||
|
/// that is neither an object nor null will panic.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> serde_yaml::Result<()> {
|
||||||
|
/// let mut data: serde_yaml::Value = serde_yaml::from_str(r#"{x: 0}"#)?;
|
||||||
|
///
|
||||||
|
/// // replace an existing key
|
||||||
|
/// data["x"] = serde_yaml::from_str(r#"1"#)?;
|
||||||
|
///
|
||||||
|
/// // insert a new key
|
||||||
|
/// data["y"] = serde_yaml::from_str(r#"[false, false, false]"#)?;
|
||||||
|
///
|
||||||
|
/// // replace a value in a sequence
|
||||||
|
/// data["y"][0] = serde_yaml::from_str(r#"true"#)?;
|
||||||
|
///
|
||||||
|
/// // inserted a deeply nested key
|
||||||
|
/// data["a"]["b"]["c"]["d"] = serde_yaml::from_str(r#"true"#)?;
|
||||||
|
///
|
||||||
|
/// println!("{:?}", data);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
fn index_mut(&mut self, index: I) -> &mut Value {
|
||||||
|
index.index_or_insert(self)
|
||||||
|
}
|
||||||
|
}
|
698
serde/src/value/mod.rs
Normal file
698
serde/src/value/mod.rs
Normal file
|
@ -0,0 +1,698 @@
|
||||||
|
//! The Value enum, a loosely typed way of representing any valid YAML value.
|
||||||
|
|
||||||
|
mod de;
|
||||||
|
mod debug;
|
||||||
|
mod from;
|
||||||
|
mod index;
|
||||||
|
mod partial_eq;
|
||||||
|
mod ser;
|
||||||
|
pub(crate) mod tagged;
|
||||||
|
|
||||||
|
use crate::error::{self, Error, ErrorImpl};
|
||||||
|
use serde::de::{Deserialize, DeserializeOwned, IntoDeserializer};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
pub use self::index::Index;
|
||||||
|
pub use self::ser::Serializer;
|
||||||
|
pub use self::tagged::{Tag, TaggedValue};
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use crate::mapping::Mapping;
|
||||||
|
pub use crate::number::Number;
|
||||||
|
|
||||||
|
/// Represents any valid YAML value.
|
||||||
|
#[derive(Clone, PartialEq, PartialOrd)]
|
||||||
|
pub enum Value {
|
||||||
|
/// Represents a YAML null value.
|
||||||
|
Null,
|
||||||
|
/// Represents a YAML boolean.
|
||||||
|
Bool(bool),
|
||||||
|
/// Represents a YAML numerical value, whether integer or floating point.
|
||||||
|
Number(Number),
|
||||||
|
/// Represents a YAML string.
|
||||||
|
String(String),
|
||||||
|
/// Represents a YAML sequence in which the elements are
|
||||||
|
/// `serde_yaml::Value`.
|
||||||
|
Sequence(Sequence),
|
||||||
|
/// Represents a YAML mapping in which the keys and values are both
|
||||||
|
/// `serde_yaml::Value`.
|
||||||
|
Mapping(Mapping),
|
||||||
|
/// A representation of YAML's `!Tag` syntax, used for enums.
|
||||||
|
Tagged(Box<TaggedValue>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The default value is `Value::Null`.
|
||||||
|
///
|
||||||
|
/// This is useful for handling omitted `Value` fields when deserializing.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_derive::Deserialize;
|
||||||
|
/// use serde::Deserialize;
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct Settings {
|
||||||
|
/// level: i32,
|
||||||
|
/// #[serde(default)]
|
||||||
|
/// extras: Value,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # fn try_main() -> Result<(), serde_yaml::Error> {
|
||||||
|
/// let data = r#" { "level": 42 } "#;
|
||||||
|
/// let s: Settings = serde_yaml::from_str(data)?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(s.level, 42);
|
||||||
|
/// assert_eq!(s.extras, Value::Null);
|
||||||
|
/// #
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # try_main().unwrap()
|
||||||
|
/// ```
|
||||||
|
impl Default for Value {
|
||||||
|
fn default() -> Value {
|
||||||
|
Value::Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A YAML sequence in which the elements are `serde_yaml::Value`.
|
||||||
|
pub type Sequence = Vec<Value>;
|
||||||
|
|
||||||
|
/// Convert a `T` into `serde_yaml::Value` which is an enum that can represent
|
||||||
|
/// any valid YAML data.
|
||||||
|
///
|
||||||
|
/// This conversion can fail if `T`'s implementation of `Serialize` decides to
|
||||||
|
/// return an error.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let val = serde_yaml::to_value("s").unwrap();
|
||||||
|
/// assert_eq!(val, Value::String("s".to_owned()));
|
||||||
|
/// ```
|
||||||
|
pub fn to_value<T>(value: T) -> Result<Value, Error>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
value.serialize(Serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interpret a `serde_yaml::Value` as an instance of type `T`.
|
||||||
|
///
|
||||||
|
/// This conversion can fail if the structure of the Value does not match the
|
||||||
|
/// structure expected by `T`, for example if `T` is a struct type but the Value
|
||||||
|
/// contains something other than a YAML map. It can also fail if the structure
|
||||||
|
/// is correct but `T`'s implementation of `Deserialize` decides that something
|
||||||
|
/// is wrong with the data, for example required struct fields are missing from
|
||||||
|
/// the YAML map or some number is too big to fit in the expected primitive
|
||||||
|
/// type.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let val = Value::String("foo".to_owned());
|
||||||
|
/// let s: String = serde_yaml::from_value(val).unwrap();
|
||||||
|
/// assert_eq!("foo", s);
|
||||||
|
/// ```
|
||||||
|
pub fn from_value<T>(value: Value) -> Result<T, Error>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
Deserialize::deserialize(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
/// Index into a YAML sequence or map. A string index can be used to access
|
||||||
|
/// a value in a map, and a usize index can be used to access an element of
|
||||||
|
/// an sequence.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the type of `self` does not match the type of the
|
||||||
|
/// index, for example if the index is a string and `self` is a sequence or
|
||||||
|
/// a number. Also returns `None` if the given key does not exist in the map
|
||||||
|
/// or the given index is not within the bounds of the sequence.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> serde_yaml::Result<()> {
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
///
|
||||||
|
/// let object: Value = serde_yaml::from_str(r#"{ A: 65, B: 66, C: 67 }"#)?;
|
||||||
|
/// let x = object.get("A").unwrap();
|
||||||
|
/// assert_eq!(x, 65);
|
||||||
|
///
|
||||||
|
/// let sequence: Value = serde_yaml::from_str(r#"[ "A", "B", "C" ]"#)?;
|
||||||
|
/// let x = sequence.get(2).unwrap();
|
||||||
|
/// assert_eq!(x, &Value::String("C".into()));
|
||||||
|
///
|
||||||
|
/// assert_eq!(sequence.get("A"), None);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Square brackets can also be used to index into a value in a more concise
|
||||||
|
/// way. This returns `Value::Null` in cases where `get` would have returned
|
||||||
|
/// `None`.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// #
|
||||||
|
/// # fn main() -> serde_yaml::Result<()> {
|
||||||
|
/// let object: Value = serde_yaml::from_str(r#"
|
||||||
|
/// A: [a, á, à]
|
||||||
|
/// B: [b, b́]
|
||||||
|
/// C: [c, ć, ć̣, ḉ]
|
||||||
|
/// 42: true
|
||||||
|
/// "#)?;
|
||||||
|
/// assert_eq!(object["B"][0], Value::String("b".into()));
|
||||||
|
///
|
||||||
|
/// assert_eq!(object[Value::String("D".into())], Value::Null);
|
||||||
|
/// assert_eq!(object["D"], Value::Null);
|
||||||
|
/// assert_eq!(object[0]["x"]["y"]["z"], Value::Null);
|
||||||
|
///
|
||||||
|
/// assert_eq!(object[42], Value::Bool(true));
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn get<I: Index>(&self, index: I) -> Option<&Value> {
|
||||||
|
index.index_into(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Index into a YAML sequence or map. A string index can be used to access
|
||||||
|
/// a value in a map, and a usize index can be used to access an element of
|
||||||
|
/// an sequence.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the type of `self` does not match the type of the
|
||||||
|
/// index, for example if the index is a string and `self` is a sequence or
|
||||||
|
/// a number. Also returns `None` if the given key does not exist in the map
|
||||||
|
/// or the given index is not within the bounds of the sequence.
|
||||||
|
pub fn get_mut<I: Index>(&mut self, index: I) -> Option<&mut Value> {
|
||||||
|
index.index_into_mut(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `Value` is a Null. Returns false otherwise.
|
||||||
|
///
|
||||||
|
/// For any Value on which `is_null` returns true, `as_null` is guaranteed
|
||||||
|
/// to return `Some(())`.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("null").unwrap();
|
||||||
|
/// assert!(v.is_null());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("false").unwrap();
|
||||||
|
/// assert!(!v.is_null());
|
||||||
|
/// ```
|
||||||
|
pub fn is_null(&self) -> bool {
|
||||||
|
if let Value::Null = self.untag_ref() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Value` is a Null, returns (). Returns None otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("null").unwrap();
|
||||||
|
/// assert_eq!(v.as_null(), Some(()));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("false").unwrap();
|
||||||
|
/// assert_eq!(v.as_null(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn as_null(&self) -> Option<()> {
|
||||||
|
match self.untag_ref() {
|
||||||
|
Value::Null => Some(()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `Value` is a Boolean. Returns false otherwise.
|
||||||
|
///
|
||||||
|
/// For any Value on which `is_boolean` returns true, `as_bool` is
|
||||||
|
/// guaranteed to return the boolean value.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("true").unwrap();
|
||||||
|
/// assert!(v.is_bool());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("42").unwrap();
|
||||||
|
/// assert!(!v.is_bool());
|
||||||
|
/// ```
|
||||||
|
pub fn is_bool(&self) -> bool {
|
||||||
|
self.as_bool().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Value` is a Boolean, returns the associated bool. Returns None
|
||||||
|
/// otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("true").unwrap();
|
||||||
|
/// assert_eq!(v.as_bool(), Some(true));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("42").unwrap();
|
||||||
|
/// assert_eq!(v.as_bool(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn as_bool(&self) -> Option<bool> {
|
||||||
|
match self.untag_ref() {
|
||||||
|
Value::Bool(b) => Some(*b),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `Value` is a Number. Returns false otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("5").unwrap();
|
||||||
|
/// assert!(v.is_number());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("true").unwrap();
|
||||||
|
/// assert!(!v.is_number());
|
||||||
|
/// ```
|
||||||
|
pub fn is_number(&self) -> bool {
|
||||||
|
match self.untag_ref() {
|
||||||
|
Value::Number(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `Value` is an integer between `i64::MIN` and
|
||||||
|
/// `i64::MAX`.
|
||||||
|
///
|
||||||
|
/// For any Value on which `is_i64` returns true, `as_i64` is guaranteed to
|
||||||
|
/// return the integer value.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("1337").unwrap();
|
||||||
|
/// assert!(v.is_i64());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("null").unwrap();
|
||||||
|
/// assert!(!v.is_i64());
|
||||||
|
/// ```
|
||||||
|
pub fn is_i64(&self) -> bool {
|
||||||
|
self.as_i64().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Value` is an integer, represent it as i64 if possible. Returns
|
||||||
|
/// None otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("1337").unwrap();
|
||||||
|
/// assert_eq!(v.as_i64(), Some(1337));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("false").unwrap();
|
||||||
|
/// assert_eq!(v.as_i64(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn as_i64(&self) -> Option<i64> {
|
||||||
|
match self.untag_ref() {
|
||||||
|
Value::Number(n) => n.as_i64(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `Value` is an integer between `u64::MIN` and
|
||||||
|
/// `u64::MAX`.
|
||||||
|
///
|
||||||
|
/// For any Value on which `is_u64` returns true, `as_u64` is guaranteed to
|
||||||
|
/// return the integer value.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("1337").unwrap();
|
||||||
|
/// assert!(v.is_u64());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("null").unwrap();
|
||||||
|
/// assert!(!v.is_u64());
|
||||||
|
/// ```
|
||||||
|
pub fn is_u64(&self) -> bool {
|
||||||
|
self.as_u64().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Value` is an integer, represent it as u64 if possible. Returns
|
||||||
|
/// None otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("1337").unwrap();
|
||||||
|
/// assert_eq!(v.as_u64(), Some(1337));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("false").unwrap();
|
||||||
|
/// assert_eq!(v.as_u64(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn as_u64(&self) -> Option<u64> {
|
||||||
|
match self.untag_ref() {
|
||||||
|
Value::Number(n) => n.as_u64(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `Value` is a number that can be represented by f64.
|
||||||
|
///
|
||||||
|
/// For any Value on which `is_f64` returns true, `as_f64` is guaranteed to
|
||||||
|
/// return the floating point value.
|
||||||
|
///
|
||||||
|
/// Currently this function returns true if and only if both `is_i64` and
|
||||||
|
/// `is_u64` return false but this is not a guarantee in the future.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("256.01").unwrap();
|
||||||
|
/// assert!(v.is_f64());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("true").unwrap();
|
||||||
|
/// assert!(!v.is_f64());
|
||||||
|
/// ```
|
||||||
|
pub fn is_f64(&self) -> bool {
|
||||||
|
match self.untag_ref() {
|
||||||
|
Value::Number(n) => n.is_f64(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Value` is a number, represent it as f64 if possible. Returns
|
||||||
|
/// None otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("13.37").unwrap();
|
||||||
|
/// assert_eq!(v.as_f64(), Some(13.37));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("false").unwrap();
|
||||||
|
/// assert_eq!(v.as_f64(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn as_f64(&self) -> Option<f64> {
|
||||||
|
match self.untag_ref() {
|
||||||
|
Value::Number(i) => i.as_f64(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `Value` is a String. Returns false otherwise.
|
||||||
|
///
|
||||||
|
/// For any Value on which `is_string` returns true, `as_str` is guaranteed
|
||||||
|
/// to return the string slice.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("'lorem ipsum'").unwrap();
|
||||||
|
/// assert!(v.is_string());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("42").unwrap();
|
||||||
|
/// assert!(!v.is_string());
|
||||||
|
/// ```
|
||||||
|
pub fn is_string(&self) -> bool {
|
||||||
|
self.as_str().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Value` is a String, returns the associated str. Returns None
|
||||||
|
/// otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("'lorem ipsum'").unwrap();
|
||||||
|
/// assert_eq!(v.as_str(), Some("lorem ipsum"));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("false").unwrap();
|
||||||
|
/// assert_eq!(v.as_str(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn as_str(&self) -> Option<&str> {
|
||||||
|
match self.untag_ref() {
|
||||||
|
Value::String(s) => Some(s),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `Value` is a sequence. Returns false otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("[1, 2, 3]").unwrap();
|
||||||
|
/// assert!(v.is_sequence());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("true").unwrap();
|
||||||
|
/// assert!(!v.is_sequence());
|
||||||
|
/// ```
|
||||||
|
pub fn is_sequence(&self) -> bool {
|
||||||
|
self.as_sequence().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Value` is a sequence, return a reference to it if possible.
|
||||||
|
/// Returns None otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::{Value, Number};
|
||||||
|
/// let v: Value = serde_yaml::from_str("[1, 2]").unwrap();
|
||||||
|
/// assert_eq!(v.as_sequence(), Some(&vec![Value::Number(Number::from(1)), Value::Number(Number::from(2))]));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("false").unwrap();
|
||||||
|
/// assert_eq!(v.as_sequence(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn as_sequence(&self) -> Option<&Sequence> {
|
||||||
|
match self.untag_ref() {
|
||||||
|
Value::Sequence(seq) => Some(seq),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Value` is a sequence, return a mutable reference to it if
|
||||||
|
/// possible. Returns None otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::{Value, Number};
|
||||||
|
/// let mut v: Value = serde_yaml::from_str("[1]").unwrap();
|
||||||
|
/// let s = v.as_sequence_mut().unwrap();
|
||||||
|
/// s.push(Value::Number(Number::from(2)));
|
||||||
|
/// assert_eq!(s, &vec![Value::Number(Number::from(1)), Value::Number(Number::from(2))]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let mut v: Value = serde_yaml::from_str("false").unwrap();
|
||||||
|
/// assert_eq!(v.as_sequence_mut(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn as_sequence_mut(&mut self) -> Option<&mut Sequence> {
|
||||||
|
match self.untag_mut() {
|
||||||
|
Value::Sequence(seq) => Some(seq),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `Value` is a mapping. Returns false otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("a: 42").unwrap();
|
||||||
|
/// assert!(v.is_mapping());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("true").unwrap();
|
||||||
|
/// assert!(!v.is_mapping());
|
||||||
|
/// ```
|
||||||
|
pub fn is_mapping(&self) -> bool {
|
||||||
|
self.as_mapping().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Value` is a mapping, return a reference to it if possible.
|
||||||
|
/// Returns None otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::{Value, Mapping, Number};
|
||||||
|
/// let v: Value = serde_yaml::from_str("a: 42").unwrap();
|
||||||
|
///
|
||||||
|
/// let mut expected = Mapping::new();
|
||||||
|
/// expected.insert(Value::String("a".into()),Value::Number(Number::from(42)));
|
||||||
|
///
|
||||||
|
/// assert_eq!(v.as_mapping(), Some(&expected));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// let v: Value = serde_yaml::from_str("false").unwrap();
|
||||||
|
/// assert_eq!(v.as_mapping(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn as_mapping(&self) -> Option<&Mapping> {
|
||||||
|
match self.untag_ref() {
|
||||||
|
Value::Mapping(map) => Some(map),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the `Value` is a mapping, return a reference to it if possible.
|
||||||
|
/// Returns None otherwise.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::{Value, Mapping, Number};
|
||||||
|
/// let mut v: Value = serde_yaml::from_str("a: 42").unwrap();
|
||||||
|
/// let m = v.as_mapping_mut().unwrap();
|
||||||
|
/// m.insert(Value::String("b".into()), Value::Number(Number::from(21)));
|
||||||
|
///
|
||||||
|
/// let mut expected = Mapping::new();
|
||||||
|
/// expected.insert(Value::String("a".into()), Value::Number(Number::from(42)));
|
||||||
|
/// expected.insert(Value::String("b".into()), Value::Number(Number::from(21)));
|
||||||
|
///
|
||||||
|
/// assert_eq!(m, &expected);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::{Value, Mapping};
|
||||||
|
/// let mut v: Value = serde_yaml::from_str("false").unwrap();
|
||||||
|
/// assert_eq!(v.as_mapping_mut(), None);
|
||||||
|
/// ```
|
||||||
|
pub fn as_mapping_mut(&mut self) -> Option<&mut Mapping> {
|
||||||
|
match self.untag_mut() {
|
||||||
|
Value::Mapping(map) => Some(map),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs merging of `<<` keys into the surrounding mapping.
|
||||||
|
///
|
||||||
|
/// The intended use of this in YAML is described in
|
||||||
|
/// <https://yaml.org/type/merge.html>.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::Value;
|
||||||
|
///
|
||||||
|
/// let config = "\
|
||||||
|
/// tasks:
|
||||||
|
/// build: &webpack_shared
|
||||||
|
/// command: webpack
|
||||||
|
/// args: build
|
||||||
|
/// inputs:
|
||||||
|
/// - 'src/**/*'
|
||||||
|
/// start:
|
||||||
|
/// <<: *webpack_shared
|
||||||
|
/// args: start
|
||||||
|
/// ";
|
||||||
|
///
|
||||||
|
/// let mut value: Value = serde_yaml::from_str(config).unwrap();
|
||||||
|
/// value.apply_merge().unwrap();
|
||||||
|
///
|
||||||
|
/// assert_eq!(value["tasks"]["start"]["command"], "webpack");
|
||||||
|
/// assert_eq!(value["tasks"]["start"]["args"], "start");
|
||||||
|
/// ```
|
||||||
|
pub fn apply_merge(&mut self) -> Result<(), Error> {
|
||||||
|
let mut stack = Vec::new();
|
||||||
|
stack.push(self);
|
||||||
|
while let Some(node) = stack.pop() {
|
||||||
|
match node {
|
||||||
|
Value::Mapping(mapping) => {
|
||||||
|
match mapping.remove("<<") {
|
||||||
|
Some(Value::Mapping(merge)) => {
|
||||||
|
for (k, v) in merge {
|
||||||
|
mapping.entry(k).or_insert(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Value::Sequence(sequence)) => {
|
||||||
|
for value in sequence {
|
||||||
|
match value {
|
||||||
|
Value::Mapping(merge) => {
|
||||||
|
for (k, v) in merge {
|
||||||
|
mapping.entry(k).or_insert(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Sequence(_) => {
|
||||||
|
return Err(error::new(ErrorImpl::SequenceInMergeElement));
|
||||||
|
}
|
||||||
|
Value::Tagged(_) => {
|
||||||
|
return Err(error::new(ErrorImpl::TaggedInMerge));
|
||||||
|
}
|
||||||
|
_unexpected => {
|
||||||
|
return Err(error::new(ErrorImpl::ScalarInMergeElement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
Some(Value::Tagged(_)) => return Err(error::new(ErrorImpl::TaggedInMerge)),
|
||||||
|
Some(_unexpected) => return Err(error::new(ErrorImpl::ScalarInMerge)),
|
||||||
|
}
|
||||||
|
stack.extend(mapping.values_mut());
|
||||||
|
}
|
||||||
|
Value::Sequence(sequence) => stack.extend(sequence),
|
||||||
|
Value::Tagged(tagged) => stack.push(&mut tagged.value),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Value {}
|
||||||
|
|
||||||
|
// NOTE: This impl must be kept consistent with HashLikeValue's Hash impl in
|
||||||
|
// mapping.rs in order for value[str] indexing to work.
|
||||||
|
impl Hash for Value {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
mem::discriminant(self).hash(state);
|
||||||
|
match self {
|
||||||
|
Value::Null => {}
|
||||||
|
Value::Bool(v) => v.hash(state),
|
||||||
|
Value::Number(v) => v.hash(state),
|
||||||
|
Value::String(v) => v.hash(state),
|
||||||
|
Value::Sequence(v) => v.hash(state),
|
||||||
|
Value::Mapping(v) => v.hash(state),
|
||||||
|
Value::Tagged(v) => v.hash(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> IntoDeserializer<'de, Error> for Value {
|
||||||
|
type Deserializer = Self;
|
||||||
|
|
||||||
|
fn into_deserializer(self) -> Self::Deserializer {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
87
serde/src/value/partial_eq.rs
Normal file
87
serde/src/value/partial_eq.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
use crate::Value;
|
||||||
|
|
||||||
|
impl PartialEq<str> for Value {
|
||||||
|
/// Compare `str` with YAML value
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// assert!(Value::String("lorem".into()) == *"lorem");
|
||||||
|
/// ```
|
||||||
|
fn eq(&self, other: &str) -> bool {
|
||||||
|
self.as_str().map_or(false, |s| s == other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PartialEq<&'a str> for Value {
|
||||||
|
/// Compare `&str` with YAML value
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// assert!(Value::String("lorem".into()) == "lorem");
|
||||||
|
/// ```
|
||||||
|
fn eq(&self, other: &&str) -> bool {
|
||||||
|
self.as_str().map_or(false, |s| s == *other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<String> for Value {
|
||||||
|
/// Compare YAML value with String
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// assert!(Value::String("lorem".into()) == "lorem".to_string());
|
||||||
|
/// ```
|
||||||
|
fn eq(&self, other: &String) -> bool {
|
||||||
|
self.as_str().map_or(false, |s| s == other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<bool> for Value {
|
||||||
|
/// Compare YAML value with bool
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use serde_yaml::Value;
|
||||||
|
/// assert!(Value::Bool(true) == true);
|
||||||
|
/// ```
|
||||||
|
fn eq(&self, other: &bool) -> bool {
|
||||||
|
self.as_bool().map_or(false, |b| b == *other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! partialeq_numeric {
|
||||||
|
($([$($ty:ty)*], $conversion:ident, $base:ty)*) => {
|
||||||
|
$($(
|
||||||
|
impl PartialEq<$ty> for Value {
|
||||||
|
fn eq(&self, other: &$ty) -> bool {
|
||||||
|
self.$conversion().map_or(false, |i| i == (*other as $base))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PartialEq<$ty> for &'a Value {
|
||||||
|
fn eq(&self, other: &$ty) -> bool {
|
||||||
|
self.$conversion().map_or(false, |i| i == (*other as $base))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PartialEq<$ty> for &'a mut Value {
|
||||||
|
fn eq(&self, other: &$ty) -> bool {
|
||||||
|
self.$conversion().map_or(false, |i| i == (*other as $base))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partialeq_numeric! {
|
||||||
|
[i8 i16 i32 i64 isize], as_i64, i64
|
||||||
|
[u8 u16 u32 u64 usize], as_u64, u64
|
||||||
|
[f32 f64], as_f64, f64
|
||||||
|
}
|
840
serde/src/value/ser.rs
Normal file
840
serde/src/value/ser.rs
Normal file
|
@ -0,0 +1,840 @@
|
||||||
|
use crate::error::{self, Error, ErrorImpl};
|
||||||
|
use crate::value::tagged::{self, MaybeTag};
|
||||||
|
use crate::value::{to_value, Mapping, Number, Sequence, Tag, TaggedValue, Value};
|
||||||
|
use serde::ser::{self, Serialize};
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
impl Serialize for Value {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Value::Null => serializer.serialize_unit(),
|
||||||
|
Value::Bool(b) => serializer.serialize_bool(*b),
|
||||||
|
Value::Number(n) => n.serialize(serializer),
|
||||||
|
Value::String(s) => serializer.serialize_str(s),
|
||||||
|
Value::Sequence(seq) => seq.serialize(serializer),
|
||||||
|
Value::Mapping(mapping) => {
|
||||||
|
use serde::ser::SerializeMap;
|
||||||
|
let mut map = serializer.serialize_map(Some(mapping.len()))?;
|
||||||
|
for (k, v) in mapping {
|
||||||
|
map.serialize_entry(k, v)?;
|
||||||
|
}
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
Value::Tagged(tagged) => tagged.serialize(serializer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializer whose output is a `Value`.
|
||||||
|
///
|
||||||
|
/// This is the serializer that backs [`serde_yaml::to_value`][crate::to_value].
|
||||||
|
/// Unlike the main serde_yaml serializer which goes from some serializable
|
||||||
|
/// value of type `T` to YAML text, this one goes from `T` to
|
||||||
|
/// `serde_yaml::Value`.
|
||||||
|
///
|
||||||
|
/// The `to_value` function is implementable as:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde::Serialize;
|
||||||
|
/// use serde_yaml::{Error, Value};
|
||||||
|
///
|
||||||
|
/// pub fn to_value<T>(input: T) -> Result<Value, Error>
|
||||||
|
/// where
|
||||||
|
/// T: Serialize,
|
||||||
|
/// {
|
||||||
|
/// input.serialize(serde_yaml::value::Serializer)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct Serializer;
|
||||||
|
|
||||||
|
impl ser::Serializer for Serializer {
|
||||||
|
type Ok = Value;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = SerializeArray;
|
||||||
|
type SerializeTuple = SerializeArray;
|
||||||
|
type SerializeTupleStruct = SerializeArray;
|
||||||
|
type SerializeTupleVariant = SerializeTupleVariant;
|
||||||
|
type SerializeMap = SerializeMap;
|
||||||
|
type SerializeStruct = SerializeStruct;
|
||||||
|
type SerializeStructVariant = SerializeStructVariant;
|
||||||
|
|
||||||
|
fn serialize_bool(self, v: bool) -> Result<Value> {
|
||||||
|
Ok(Value::Bool(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i8(self, v: i8) -> Result<Value> {
|
||||||
|
Ok(Value::Number(Number::from(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i16(self, v: i16) -> Result<Value> {
|
||||||
|
Ok(Value::Number(Number::from(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i32(self, v: i32) -> Result<Value> {
|
||||||
|
Ok(Value::Number(Number::from(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i64(self, v: i64) -> Result<Value> {
|
||||||
|
Ok(Value::Number(Number::from(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i128(self, v: i128) -> Result<Value> {
|
||||||
|
if let Ok(v) = u64::try_from(v) {
|
||||||
|
self.serialize_u64(v)
|
||||||
|
} else if let Ok(v) = i64::try_from(v) {
|
||||||
|
self.serialize_i64(v)
|
||||||
|
} else {
|
||||||
|
Ok(Value::String(v.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u8(self, v: u8) -> Result<Value> {
|
||||||
|
Ok(Value::Number(Number::from(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u16(self, v: u16) -> Result<Value> {
|
||||||
|
Ok(Value::Number(Number::from(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u32(self, v: u32) -> Result<Value> {
|
||||||
|
Ok(Value::Number(Number::from(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u64(self, v: u64) -> Result<Value> {
|
||||||
|
Ok(Value::Number(Number::from(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u128(self, v: u128) -> Result<Value> {
|
||||||
|
if let Ok(v) = u64::try_from(v) {
|
||||||
|
self.serialize_u64(v)
|
||||||
|
} else {
|
||||||
|
Ok(Value::String(v.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_f32(self, v: f32) -> Result<Value> {
|
||||||
|
Ok(Value::Number(Number::from(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_f64(self, v: f64) -> Result<Value> {
|
||||||
|
Ok(Value::Number(Number::from(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_char(self, value: char) -> Result<Value> {
|
||||||
|
Ok(Value::String(value.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_str(self, value: &str) -> Result<Value> {
|
||||||
|
Ok(Value::String(value.to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_bytes(self, value: &[u8]) -> Result<Value> {
|
||||||
|
let vec = value
|
||||||
|
.iter()
|
||||||
|
.map(|&b| Value::Number(Number::from(b)))
|
||||||
|
.collect();
|
||||||
|
Ok(Value::Sequence(vec))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit(self) -> Result<Value> {
|
||||||
|
Ok(Value::Null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit_struct(self, _name: &'static str) -> Result<Value> {
|
||||||
|
self.serialize_unit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit_variant(
|
||||||
|
self,
|
||||||
|
_name: &str,
|
||||||
|
_variant_index: u32,
|
||||||
|
variant: &str,
|
||||||
|
) -> Result<Value> {
|
||||||
|
Ok(Value::String(variant.to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<Value>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
value.serialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_newtype_variant<T>(
|
||||||
|
self,
|
||||||
|
_name: &str,
|
||||||
|
_variant_index: u32,
|
||||||
|
variant: &str,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<Value>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
if variant.is_empty() {
|
||||||
|
return Err(error::new(ErrorImpl::EmptyTag));
|
||||||
|
}
|
||||||
|
Ok(Value::Tagged(Box::new(TaggedValue {
|
||||||
|
tag: Tag::new(variant),
|
||||||
|
value: to_value(value)?,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_none(self) -> Result<Value> {
|
||||||
|
self.serialize_unit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_some<V>(self, value: &V) -> Result<Value>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
value.serialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_seq(self, len: Option<usize>) -> Result<SerializeArray> {
|
||||||
|
let sequence = match len {
|
||||||
|
None => Sequence::new(),
|
||||||
|
Some(len) => Sequence::with_capacity(len),
|
||||||
|
};
|
||||||
|
Ok(SerializeArray { sequence })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple(self, len: usize) -> Result<SerializeArray> {
|
||||||
|
self.serialize_seq(Some(len))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple_struct(self, _name: &'static str, len: usize) -> Result<SerializeArray> {
|
||||||
|
self.serialize_seq(Some(len))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple_variant(
|
||||||
|
self,
|
||||||
|
_enum: &'static str,
|
||||||
|
_idx: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<SerializeTupleVariant> {
|
||||||
|
if variant.is_empty() {
|
||||||
|
return Err(error::new(ErrorImpl::EmptyTag));
|
||||||
|
}
|
||||||
|
Ok(SerializeTupleVariant {
|
||||||
|
tag: variant,
|
||||||
|
sequence: Sequence::with_capacity(len),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_map(self, len: Option<usize>) -> Result<SerializeMap> {
|
||||||
|
if len == Some(1) {
|
||||||
|
Ok(SerializeMap::CheckForTag)
|
||||||
|
} else {
|
||||||
|
Ok(SerializeMap::Untagged {
|
||||||
|
mapping: Mapping::new(),
|
||||||
|
next_key: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<SerializeStruct> {
|
||||||
|
Ok(SerializeStruct {
|
||||||
|
mapping: Mapping::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_struct_variant(
|
||||||
|
self,
|
||||||
|
_enum: &'static str,
|
||||||
|
_idx: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
_len: usize,
|
||||||
|
) -> Result<SerializeStructVariant> {
|
||||||
|
if variant.is_empty() {
|
||||||
|
return Err(error::new(ErrorImpl::EmptyTag));
|
||||||
|
}
|
||||||
|
Ok(SerializeStructVariant {
|
||||||
|
tag: variant,
|
||||||
|
mapping: Mapping::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SerializeArray {
|
||||||
|
sequence: Sequence,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeSeq for SerializeArray {
|
||||||
|
type Ok = Value;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_element<T>(&mut self, elem: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.sequence.push(to_value(elem)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Value> {
|
||||||
|
Ok(Value::Sequence(self.sequence))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeTuple for SerializeArray {
|
||||||
|
type Ok = Value;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_element<T>(&mut self, elem: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
ser::SerializeSeq::serialize_element(self, elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Value> {
|
||||||
|
ser::SerializeSeq::end(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeTupleStruct for SerializeArray {
|
||||||
|
type Ok = Value;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, value: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
ser::SerializeSeq::serialize_element(self, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Value> {
|
||||||
|
ser::SerializeSeq::end(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SerializeTupleVariant {
|
||||||
|
tag: &'static str,
|
||||||
|
sequence: Sequence,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeTupleVariant for SerializeTupleVariant {
|
||||||
|
type Ok = Value;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, v: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.sequence.push(to_value(v)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Value> {
|
||||||
|
Ok(Value::Tagged(Box::new(TaggedValue {
|
||||||
|
tag: Tag::new(self.tag),
|
||||||
|
value: Value::Sequence(self.sequence),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SerializeMap {
|
||||||
|
CheckForTag,
|
||||||
|
Tagged(TaggedValue),
|
||||||
|
Untagged {
|
||||||
|
mapping: Mapping,
|
||||||
|
next_key: Option<Value>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeMap for SerializeMap {
|
||||||
|
type Ok = Value;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
let key = Some(to_value(key)?);
|
||||||
|
match self {
|
||||||
|
SerializeMap::CheckForTag => {
|
||||||
|
*self = SerializeMap::Untagged {
|
||||||
|
mapping: Mapping::new(),
|
||||||
|
next_key: key,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
SerializeMap::Tagged(tagged) => {
|
||||||
|
let mut mapping = Mapping::new();
|
||||||
|
mapping.insert(
|
||||||
|
Value::String(tagged.tag.to_string()),
|
||||||
|
mem::take(&mut tagged.value),
|
||||||
|
);
|
||||||
|
*self = SerializeMap::Untagged {
|
||||||
|
mapping,
|
||||||
|
next_key: key,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
SerializeMap::Untagged { next_key, .. } => *next_key = key,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
let (mapping, key) = match self {
|
||||||
|
SerializeMap::CheckForTag | SerializeMap::Tagged(_) => unreachable!(),
|
||||||
|
SerializeMap::Untagged { mapping, next_key } => (mapping, next_key),
|
||||||
|
};
|
||||||
|
match key.take() {
|
||||||
|
Some(key) => mapping.insert(key, to_value(value)?),
|
||||||
|
None => panic!("serialize_value called before serialize_key"),
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_entry<K, V>(&mut self, key: &K, value: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
K: ?Sized + ser::Serialize,
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
struct CheckForTag;
|
||||||
|
struct NotTag<T> {
|
||||||
|
delegate: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::Serializer for CheckForTag {
|
||||||
|
type Ok = MaybeTag<Value>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = NotTag<SerializeArray>;
|
||||||
|
type SerializeTuple = NotTag<SerializeArray>;
|
||||||
|
type SerializeTupleStruct = NotTag<SerializeArray>;
|
||||||
|
type SerializeTupleVariant = NotTag<SerializeTupleVariant>;
|
||||||
|
type SerializeMap = NotTag<SerializeMap>;
|
||||||
|
type SerializeStruct = NotTag<SerializeStruct>;
|
||||||
|
type SerializeStructVariant = NotTag<SerializeStructVariant>;
|
||||||
|
|
||||||
|
fn serialize_bool(self, v: bool) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_bool(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i8(self, v: i8) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_i8(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i16(self, v: i16) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_i16(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i32(self, v: i32) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_i32(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i64(self, v: i64) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_i64(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_i128(self, v: i128) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_i128(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u8(self, v: u8) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_u8(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u16(self, v: u16) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_u16(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u32(self, v: u32) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_u32(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u64(self, v: u64) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_u64(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_u128(self, v: u128) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_u128(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_f32(self, v: f32) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_f32(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_f64(self, v: f64) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_f64(v).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_char(self, value: char) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_char(value).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_str(self, value: &str) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_str(value).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_bytes(self, value: &[u8]) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_bytes(value).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit(self) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_unit().map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_unit_struct(name).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_unit_variant(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
variant_index: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
) -> Result<Self::Ok> {
|
||||||
|
Serializer
|
||||||
|
.serialize_unit_variant(name, variant_index, variant)
|
||||||
|
.map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_newtype_struct<T>(self, name: &'static str, value: &T) -> Result<Self::Ok>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
Serializer
|
||||||
|
.serialize_newtype_struct(name, value)
|
||||||
|
.map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_newtype_variant<T>(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
variant_index: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<Self::Ok>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
Serializer
|
||||||
|
.serialize_newtype_variant(name, variant_index, variant, value)
|
||||||
|
.map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_none(self) -> Result<Self::Ok> {
|
||||||
|
Serializer.serialize_none().map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_some<V>(self, value: &V) -> Result<Self::Ok>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
Serializer.serialize_some(value).map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
|
||||||
|
Ok(NotTag {
|
||||||
|
delegate: Serializer.serialize_seq(len)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
|
||||||
|
Ok(NotTag {
|
||||||
|
delegate: Serializer.serialize_tuple(len)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple_struct(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<Self::SerializeTupleStruct> {
|
||||||
|
Ok(NotTag {
|
||||||
|
delegate: Serializer.serialize_tuple_struct(name, len)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_tuple_variant(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
variant_index: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<Self::SerializeTupleVariant> {
|
||||||
|
Ok(NotTag {
|
||||||
|
delegate: Serializer.serialize_tuple_variant(
|
||||||
|
name,
|
||||||
|
variant_index,
|
||||||
|
variant,
|
||||||
|
len,
|
||||||
|
)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
|
||||||
|
Ok(NotTag {
|
||||||
|
delegate: Serializer.serialize_map(len)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_struct(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<Self::SerializeStruct> {
|
||||||
|
Ok(NotTag {
|
||||||
|
delegate: Serializer.serialize_struct(name, len)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_struct_variant(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
variant_index: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<Self::SerializeStructVariant> {
|
||||||
|
Ok(NotTag {
|
||||||
|
delegate: Serializer.serialize_struct_variant(
|
||||||
|
name,
|
||||||
|
variant_index,
|
||||||
|
variant,
|
||||||
|
len,
|
||||||
|
)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_str<T>(self, value: &T) -> Result<Self::Ok>
|
||||||
|
where
|
||||||
|
T: ?Sized + Display,
|
||||||
|
{
|
||||||
|
Ok(match tagged::check_for_tag(value) {
|
||||||
|
MaybeTag::Tag(tag) => MaybeTag::Tag(tag),
|
||||||
|
MaybeTag::NotTag(string) => MaybeTag::NotTag(Value::String(string)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeSeq for NotTag<SerializeArray> {
|
||||||
|
type Ok = MaybeTag<Value>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_element<T>(&mut self, elem: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.delegate.serialize_element(elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok> {
|
||||||
|
self.delegate.end().map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeTuple for NotTag<SerializeArray> {
|
||||||
|
type Ok = MaybeTag<Value>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_element<T>(&mut self, elem: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.delegate.serialize_element(elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok> {
|
||||||
|
self.delegate.end().map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeTupleStruct for NotTag<SerializeArray> {
|
||||||
|
type Ok = MaybeTag<Value>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, value: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.delegate.serialize_field(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok> {
|
||||||
|
self.delegate.end().map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeTupleVariant for NotTag<SerializeTupleVariant> {
|
||||||
|
type Ok = MaybeTag<Value>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, v: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.delegate.serialize_field(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok> {
|
||||||
|
self.delegate.end().map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeMap for NotTag<SerializeMap> {
|
||||||
|
type Ok = MaybeTag<Value>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.delegate.serialize_key(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.delegate.serialize_value(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_entry<K, V>(&mut self, key: &K, value: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
K: ?Sized + ser::Serialize,
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.delegate.serialize_entry(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok> {
|
||||||
|
self.delegate.end().map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeStruct for NotTag<SerializeStruct> {
|
||||||
|
type Ok = MaybeTag<Value>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, key: &'static str, value: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.delegate.serialize_field(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok> {
|
||||||
|
self.delegate.end().map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeStructVariant for NotTag<SerializeStructVariant> {
|
||||||
|
type Ok = MaybeTag<Value>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, field: &'static str, v: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.delegate.serialize_field(field, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Self::Ok> {
|
||||||
|
self.delegate.end().map(MaybeTag::NotTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match self {
|
||||||
|
SerializeMap::CheckForTag => {
|
||||||
|
let key = key.serialize(CheckForTag)?;
|
||||||
|
let mut mapping = Mapping::new();
|
||||||
|
*self = match key {
|
||||||
|
MaybeTag::Tag(string) => SerializeMap::Tagged(TaggedValue {
|
||||||
|
tag: Tag::new(string),
|
||||||
|
value: to_value(value)?,
|
||||||
|
}),
|
||||||
|
MaybeTag::NotTag(key) => {
|
||||||
|
mapping.insert(key, to_value(value)?);
|
||||||
|
SerializeMap::Untagged {
|
||||||
|
mapping,
|
||||||
|
next_key: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
SerializeMap::Tagged(tagged) => {
|
||||||
|
let mut mapping = Mapping::new();
|
||||||
|
mapping.insert(
|
||||||
|
Value::String(tagged.tag.to_string()),
|
||||||
|
mem::take(&mut tagged.value),
|
||||||
|
);
|
||||||
|
mapping.insert(to_value(key)?, to_value(value)?);
|
||||||
|
*self = SerializeMap::Untagged {
|
||||||
|
mapping,
|
||||||
|
next_key: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
SerializeMap::Untagged { mapping, .. } => {
|
||||||
|
mapping.insert(to_value(key)?, to_value(value)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Value> {
|
||||||
|
Ok(match self {
|
||||||
|
SerializeMap::CheckForTag => Value::Mapping(Mapping::new()),
|
||||||
|
SerializeMap::Tagged(tagged) => Value::Tagged(Box::new(tagged)),
|
||||||
|
SerializeMap::Untagged { mapping, .. } => Value::Mapping(mapping),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SerializeStruct {
|
||||||
|
mapping: Mapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeStruct for SerializeStruct {
|
||||||
|
type Ok = Value;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, key: &'static str, value: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.mapping.insert(to_value(key)?, to_value(value)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Value> {
|
||||||
|
Ok(Value::Mapping(self.mapping))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SerializeStructVariant {
|
||||||
|
tag: &'static str,
|
||||||
|
mapping: Mapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::SerializeStructVariant for SerializeStructVariant {
|
||||||
|
type Ok = Value;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn serialize_field<V>(&mut self, field: &'static str, v: &V) -> Result<()>
|
||||||
|
where
|
||||||
|
V: ?Sized + ser::Serialize,
|
||||||
|
{
|
||||||
|
self.mapping.insert(to_value(field)?, to_value(v)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(self) -> Result<Value> {
|
||||||
|
Ok(Value::Tagged(Box::new(TaggedValue {
|
||||||
|
tag: Tag::new(self.tag),
|
||||||
|
value: Value::Mapping(self.mapping),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
474
serde/src/value/tagged.rs
Normal file
474
serde/src/value/tagged.rs
Normal file
|
@ -0,0 +1,474 @@
|
||||||
|
use crate::value::de::{MapDeserializer, MapRefDeserializer, SeqDeserializer, SeqRefDeserializer};
|
||||||
|
use crate::value::Value;
|
||||||
|
use crate::Error;
|
||||||
|
use serde::de::value::{BorrowedStrDeserializer, StrDeserializer};
|
||||||
|
use serde::de::{
|
||||||
|
Deserialize, DeserializeSeed, Deserializer, EnumAccess, Error as _, VariantAccess, Visitor,
|
||||||
|
};
|
||||||
|
use serde::forward_to_deserialize_any;
|
||||||
|
use serde::ser::{Serialize, SerializeMap, Serializer};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::fmt::{self, Debug, Display};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
/// A representation of YAML's `!Tag` syntax, used for enums.
|
||||||
|
///
|
||||||
|
/// Refer to the example code on [`TaggedValue`] for an example of deserializing
|
||||||
|
/// tagged values.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Tag {
|
||||||
|
pub(crate) string: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `Tag` + `Value` representing a tagged YAML scalar, sequence, or mapping.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::value::TaggedValue;
|
||||||
|
/// use std::collections::BTreeMap;
|
||||||
|
///
|
||||||
|
/// let yaml = "
|
||||||
|
/// scalar: !Thing x
|
||||||
|
/// sequence_flow: !Thing [first]
|
||||||
|
/// sequence_block: !Thing
|
||||||
|
/// - first
|
||||||
|
/// mapping_flow: !Thing {k: v}
|
||||||
|
/// mapping_block: !Thing
|
||||||
|
/// k: v
|
||||||
|
/// ";
|
||||||
|
///
|
||||||
|
/// let data: BTreeMap<String, TaggedValue> = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
/// assert!(data["scalar"].tag == "Thing");
|
||||||
|
/// assert!(data["sequence_flow"].tag == "Thing");
|
||||||
|
/// assert!(data["sequence_block"].tag == "Thing");
|
||||||
|
/// assert!(data["mapping_flow"].tag == "Thing");
|
||||||
|
/// assert!(data["mapping_block"].tag == "Thing");
|
||||||
|
///
|
||||||
|
/// // The leading '!' in tags are not significant. The following is also true.
|
||||||
|
/// assert!(data["scalar"].tag == "!Thing");
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, PartialEq, PartialOrd, Hash, Debug)]
|
||||||
|
pub struct TaggedValue {
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub tag: Tag,
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub value: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tag {
|
||||||
|
/// Create tag.
|
||||||
|
///
|
||||||
|
/// The leading '!' is not significant. It may be provided, but does not
|
||||||
|
/// have to be. The following are equivalent:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use serde_yaml::value::Tag;
|
||||||
|
///
|
||||||
|
/// assert_eq!(Tag::new("!Thing"), Tag::new("Thing"));
|
||||||
|
///
|
||||||
|
/// let tag = Tag::new("Thing");
|
||||||
|
/// assert!(tag == "Thing");
|
||||||
|
/// assert!(tag == "!Thing");
|
||||||
|
/// assert!(tag.to_string() == "!Thing");
|
||||||
|
///
|
||||||
|
/// let tag = Tag::new("!Thing");
|
||||||
|
/// assert!(tag == "Thing");
|
||||||
|
/// assert!(tag == "!Thing");
|
||||||
|
/// assert!(tag.to_string() == "!Thing");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Such a tag would serialize to `!Thing` in YAML regardless of whether a
|
||||||
|
/// '!' was included in the call to `Tag::new`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `string.is_empty()`. There is no syntax in YAML for an empty
|
||||||
|
/// tag.
|
||||||
|
pub fn new(string: impl Into<String>) -> Self {
|
||||||
|
let tag: String = string.into();
|
||||||
|
assert!(!tag.is_empty(), "empty YAML tag is not allowed");
|
||||||
|
Tag { string: tag }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
pub(crate) fn untag(self) -> Self {
|
||||||
|
let mut cur = self;
|
||||||
|
while let Value::Tagged(tagged) = cur {
|
||||||
|
cur = tagged.value;
|
||||||
|
}
|
||||||
|
cur
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn untag_ref(&self) -> &Self {
|
||||||
|
let mut cur = self;
|
||||||
|
while let Value::Tagged(tagged) = cur {
|
||||||
|
cur = &tagged.value;
|
||||||
|
}
|
||||||
|
cur
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn untag_mut(&mut self) -> &mut Self {
|
||||||
|
let mut cur = self;
|
||||||
|
while let Value::Tagged(tagged) = cur {
|
||||||
|
cur = &mut tagged.value;
|
||||||
|
}
|
||||||
|
cur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn nobang(maybe_banged: &str) -> &str {
|
||||||
|
match maybe_banged.strip_prefix('!') {
|
||||||
|
Some("") | None => maybe_banged,
|
||||||
|
Some(unbanged) => unbanged,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Tag {}
|
||||||
|
|
||||||
|
impl PartialEq for Tag {
|
||||||
|
fn eq(&self, other: &Tag) -> bool {
|
||||||
|
PartialEq::eq(nobang(&self.string), nobang(&other.string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PartialEq<T> for Tag
|
||||||
|
where
|
||||||
|
T: ?Sized + AsRef<str>,
|
||||||
|
{
|
||||||
|
fn eq(&self, other: &T) -> bool {
|
||||||
|
PartialEq::eq(nobang(&self.string), nobang(other.as_ref()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Tag {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
Ord::cmp(nobang(&self.string), nobang(&other.string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Tag {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for Tag {
|
||||||
|
fn hash<H: Hasher>(&self, hasher: &mut H) {
|
||||||
|
nobang(&self.string).hash(hasher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Tag {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "!{}", nobang(&self.string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Tag {
|
||||||
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(self, formatter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for TaggedValue {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
struct SerializeTag<'a>(&'a Tag);
|
||||||
|
|
||||||
|
impl<'a> Serialize for SerializeTag<'a> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.collect_str(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut map = serializer.serialize_map(Some(1))?;
|
||||||
|
map.serialize_entry(&SerializeTag(&self.tag), &self.value)?;
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for TaggedValue {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct TaggedValueVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for TaggedValueVisitor {
|
||||||
|
type Value = TaggedValue;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a YAML value with a !Tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: EnumAccess<'de>,
|
||||||
|
{
|
||||||
|
let (tag, contents) = data.variant_seed(TagStringVisitor)?;
|
||||||
|
let value = contents.newtype_variant()?;
|
||||||
|
Ok(TaggedValue { tag, value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(TaggedValueVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserializer<'de> for TaggedValue {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
visitor.visit_enum(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
drop(self);
|
||||||
|
visitor.visit_unit()
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_to_deserialize_any! {
|
||||||
|
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
|
||||||
|
byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct
|
||||||
|
map struct enum identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> EnumAccess<'de> for TaggedValue {
|
||||||
|
type Error = Error;
|
||||||
|
type Variant = Value;
|
||||||
|
|
||||||
|
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Error>
|
||||||
|
where
|
||||||
|
V: DeserializeSeed<'de>,
|
||||||
|
{
|
||||||
|
let tag = StrDeserializer::<Error>::new(nobang(&self.tag.string));
|
||||||
|
let value = seed.deserialize(tag)?;
|
||||||
|
Ok((value, self.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> VariantAccess<'de> for Value {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn unit_variant(self) -> Result<(), Error> {
|
||||||
|
Deserialize::deserialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Error>
|
||||||
|
where
|
||||||
|
T: DeserializeSeed<'de>,
|
||||||
|
{
|
||||||
|
seed.deserialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value, Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
if let Value::Sequence(v) = self {
|
||||||
|
Deserializer::deserialize_any(SeqDeserializer::new(v), visitor)
|
||||||
|
} else {
|
||||||
|
Err(Error::invalid_type(self.unexpected(), &"tuple variant"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn struct_variant<V>(
|
||||||
|
self,
|
||||||
|
_fields: &'static [&'static str],
|
||||||
|
visitor: V,
|
||||||
|
) -> Result<V::Value, Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
if let Value::Mapping(v) = self {
|
||||||
|
Deserializer::deserialize_any(MapDeserializer::new(v), visitor)
|
||||||
|
} else {
|
||||||
|
Err(Error::invalid_type(self.unexpected(), &"struct variant"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserializer<'de> for &'de TaggedValue {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
visitor.visit_enum(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
visitor.visit_unit()
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_to_deserialize_any! {
|
||||||
|
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
|
||||||
|
byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct
|
||||||
|
map struct enum identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> EnumAccess<'de> for &'de TaggedValue {
|
||||||
|
type Error = Error;
|
||||||
|
type Variant = &'de Value;
|
||||||
|
|
||||||
|
fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Error>
|
||||||
|
where
|
||||||
|
V: DeserializeSeed<'de>,
|
||||||
|
{
|
||||||
|
let tag = BorrowedStrDeserializer::<Error>::new(nobang(&self.tag.string));
|
||||||
|
let value = seed.deserialize(tag)?;
|
||||||
|
Ok((value, &self.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> VariantAccess<'de> for &'de Value {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn unit_variant(self) -> Result<(), Error> {
|
||||||
|
Deserialize::deserialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Error>
|
||||||
|
where
|
||||||
|
T: DeserializeSeed<'de>,
|
||||||
|
{
|
||||||
|
seed.deserialize(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value, Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
if let Value::Sequence(v) = self {
|
||||||
|
Deserializer::deserialize_any(SeqRefDeserializer::new(v), visitor)
|
||||||
|
} else {
|
||||||
|
Err(Error::invalid_type(self.unexpected(), &"tuple variant"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn struct_variant<V>(
|
||||||
|
self,
|
||||||
|
_fields: &'static [&'static str],
|
||||||
|
visitor: V,
|
||||||
|
) -> Result<V::Value, Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
if let Value::Mapping(v) = self {
|
||||||
|
Deserializer::deserialize_any(MapRefDeserializer::new(v), visitor)
|
||||||
|
} else {
|
||||||
|
Err(Error::invalid_type(self.unexpected(), &"struct variant"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct TagStringVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for TagStringVisitor {
|
||||||
|
type Value = Tag;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a YAML tag string")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
self.visit_string(string.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_string<E>(self, string: String) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
if string.is_empty() {
|
||||||
|
return Err(E::custom("empty YAML tag is not allowed"));
|
||||||
|
}
|
||||||
|
Ok(Tag::new(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> DeserializeSeed<'de> for TagStringVisitor {
|
||||||
|
type Value = Tag;
|
||||||
|
|
||||||
|
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_string(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum MaybeTag<T> {
|
||||||
|
Tag(String),
|
||||||
|
NotTag(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn check_for_tag<T>(value: &T) -> MaybeTag<String>
|
||||||
|
where
|
||||||
|
T: ?Sized + Display,
|
||||||
|
{
|
||||||
|
enum CheckForTag {
|
||||||
|
Empty,
|
||||||
|
Bang,
|
||||||
|
Tag(String),
|
||||||
|
NotTag(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Write for CheckForTag {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
if s.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
CheckForTag::Empty => {
|
||||||
|
if s == "!" {
|
||||||
|
*self = CheckForTag::Bang;
|
||||||
|
} else {
|
||||||
|
*self = CheckForTag::NotTag(s.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CheckForTag::Bang => {
|
||||||
|
*self = CheckForTag::Tag(s.to_owned());
|
||||||
|
}
|
||||||
|
CheckForTag::Tag(string) => {
|
||||||
|
let mut string = mem::take(string);
|
||||||
|
string.push_str(s);
|
||||||
|
*self = CheckForTag::NotTag(string);
|
||||||
|
}
|
||||||
|
CheckForTag::NotTag(string) => {
|
||||||
|
string.push_str(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut check_for_tag = CheckForTag::Empty;
|
||||||
|
fmt::write(&mut check_for_tag, format_args!("{}", value)).unwrap();
|
||||||
|
match check_for_tag {
|
||||||
|
CheckForTag::Empty => MaybeTag::NotTag(String::new()),
|
||||||
|
CheckForTag::Bang => MaybeTag::NotTag("!".to_owned()),
|
||||||
|
CheckForTag::Tag(string) => MaybeTag::Tag(string),
|
||||||
|
CheckForTag::NotTag(string) => MaybeTag::NotTag(string),
|
||||||
|
}
|
||||||
|
}
|
2124
serde/src/with.rs
Normal file
2124
serde/src/with.rs
Normal file
File diff suppressed because it is too large
Load diff
717
serde/tests/test_de.rs
Normal file
717
serde/tests/test_de.rs
Normal file
|
@ -0,0 +1,717 @@
|
||||||
|
#![allow(
|
||||||
|
clippy::cast_lossless,
|
||||||
|
clippy::cast_possible_wrap,
|
||||||
|
clippy::derive_partial_eq_without_eq,
|
||||||
|
clippy::similar_names,
|
||||||
|
clippy::uninlined_format_args
|
||||||
|
)]
|
||||||
|
|
||||||
|
use indoc::indoc;
|
||||||
|
use serde_derive::Deserialize;
|
||||||
|
use serde_yaml::{Deserializer, Number, Value};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
fn test_de<T>(yaml: &str, expected: &T)
|
||||||
|
where
|
||||||
|
T: serde::de::DeserializeOwned + PartialEq + Debug,
|
||||||
|
{
|
||||||
|
let deserialized: T = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
assert_eq!(*expected, deserialized);
|
||||||
|
|
||||||
|
let value: Value = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
let deserialized = T::deserialize(&value).unwrap();
|
||||||
|
assert_eq!(*expected, deserialized);
|
||||||
|
|
||||||
|
let deserialized: T = serde_yaml::from_value(value).unwrap();
|
||||||
|
assert_eq!(*expected, deserialized);
|
||||||
|
|
||||||
|
serde_yaml::from_str::<serde::de::IgnoredAny>(yaml).unwrap();
|
||||||
|
|
||||||
|
let mut deserializer = Deserializer::from_str(yaml);
|
||||||
|
let document = deserializer.next().unwrap();
|
||||||
|
let deserialized = T::deserialize(document).unwrap();
|
||||||
|
assert_eq!(*expected, deserialized);
|
||||||
|
assert!(deserializer.next().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_de_no_value<'de, T>(yaml: &'de str, expected: &T)
|
||||||
|
where
|
||||||
|
T: serde::de::Deserialize<'de> + PartialEq + Debug,
|
||||||
|
{
|
||||||
|
let deserialized: T = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
assert_eq!(*expected, deserialized);
|
||||||
|
|
||||||
|
serde_yaml::from_str::<serde_yaml::Value>(yaml).unwrap();
|
||||||
|
serde_yaml::from_str::<serde::de::IgnoredAny>(yaml).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_de_seed<'de, T, S>(yaml: &'de str, seed: S, expected: &T)
|
||||||
|
where
|
||||||
|
T: PartialEq + Debug,
|
||||||
|
S: serde::de::DeserializeSeed<'de, Value = T>,
|
||||||
|
{
|
||||||
|
let deserialized: T = seed.deserialize(Deserializer::from_str(yaml)).unwrap();
|
||||||
|
assert_eq!(*expected, deserialized);
|
||||||
|
|
||||||
|
serde_yaml::from_str::<serde_yaml::Value>(yaml).unwrap();
|
||||||
|
serde_yaml::from_str::<serde::de::IgnoredAny>(yaml).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_borrowed() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
- plain nonàscii
|
||||||
|
- 'single quoted'
|
||||||
|
- \"double quoted\"
|
||||||
|
"};
|
||||||
|
let expected = vec!["plain nonàscii", "single quoted", "double quoted"];
|
||||||
|
test_de_no_value(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_alias() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
first:
|
||||||
|
&alias
|
||||||
|
1
|
||||||
|
second:
|
||||||
|
*alias
|
||||||
|
third: 3
|
||||||
|
"};
|
||||||
|
let mut expected = BTreeMap::new();
|
||||||
|
expected.insert("first".to_owned(), 1);
|
||||||
|
expected.insert("second".to_owned(), 1);
|
||||||
|
expected.insert("third".to_owned(), 3);
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_option() {
|
||||||
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
struct Data {
|
||||||
|
a: Option<f64>,
|
||||||
|
b: Option<String>,
|
||||||
|
c: Option<bool>,
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
b:
|
||||||
|
c: true
|
||||||
|
"};
|
||||||
|
let expected = Data {
|
||||||
|
a: None,
|
||||||
|
b: None,
|
||||||
|
c: Some(true),
|
||||||
|
};
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_option_alias() {
|
||||||
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
struct Data {
|
||||||
|
a: Option<f64>,
|
||||||
|
b: Option<String>,
|
||||||
|
c: Option<bool>,
|
||||||
|
d: Option<f64>,
|
||||||
|
e: Option<String>,
|
||||||
|
f: Option<bool>,
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
none_f:
|
||||||
|
&none_f
|
||||||
|
~
|
||||||
|
none_s:
|
||||||
|
&none_s
|
||||||
|
~
|
||||||
|
none_b:
|
||||||
|
&none_b
|
||||||
|
~
|
||||||
|
|
||||||
|
some_f:
|
||||||
|
&some_f
|
||||||
|
1.0
|
||||||
|
some_s:
|
||||||
|
&some_s
|
||||||
|
x
|
||||||
|
some_b:
|
||||||
|
&some_b
|
||||||
|
true
|
||||||
|
|
||||||
|
a: *none_f
|
||||||
|
b: *none_s
|
||||||
|
c: *none_b
|
||||||
|
d: *some_f
|
||||||
|
e: *some_s
|
||||||
|
f: *some_b
|
||||||
|
"};
|
||||||
|
let expected = Data {
|
||||||
|
a: None,
|
||||||
|
b: None,
|
||||||
|
c: None,
|
||||||
|
d: Some(1.0),
|
||||||
|
e: Some("x".to_owned()),
|
||||||
|
f: Some(true),
|
||||||
|
};
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enum_alias() {
|
||||||
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
enum E {
|
||||||
|
A,
|
||||||
|
B(u8, u8),
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
struct Data {
|
||||||
|
a: E,
|
||||||
|
b: E,
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
aref:
|
||||||
|
&aref
|
||||||
|
A
|
||||||
|
bref:
|
||||||
|
&bref
|
||||||
|
!B
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
|
||||||
|
a: *aref
|
||||||
|
b: *bref
|
||||||
|
"};
|
||||||
|
let expected = Data {
|
||||||
|
a: E::A,
|
||||||
|
b: E::B(1, 2),
|
||||||
|
};
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enum_representations() {
|
||||||
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
enum Enum {
|
||||||
|
Unit,
|
||||||
|
Tuple(i32, i32),
|
||||||
|
Struct { x: i32, y: i32 },
|
||||||
|
String(String),
|
||||||
|
Number(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
- Unit
|
||||||
|
- 'Unit'
|
||||||
|
- !Unit
|
||||||
|
- !Unit ~
|
||||||
|
- !Unit null
|
||||||
|
- !Tuple [0, 0]
|
||||||
|
- !Tuple
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
- !Struct {x: 0, y: 0}
|
||||||
|
- !Struct
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
- !String '...'
|
||||||
|
- !String ...
|
||||||
|
- !Number 0
|
||||||
|
"};
|
||||||
|
|
||||||
|
let expected = vec![
|
||||||
|
Enum::Unit,
|
||||||
|
Enum::Unit,
|
||||||
|
Enum::Unit,
|
||||||
|
Enum::Unit,
|
||||||
|
Enum::Unit,
|
||||||
|
Enum::Tuple(0, 0),
|
||||||
|
Enum::Tuple(0, 0),
|
||||||
|
Enum::Struct { x: 0, y: 0 },
|
||||||
|
Enum::Struct { x: 0, y: 0 },
|
||||||
|
Enum::String("...".to_owned()),
|
||||||
|
Enum::String("...".to_owned()),
|
||||||
|
Enum::Number(0.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
- !String
|
||||||
|
"};
|
||||||
|
let expected = vec![Enum::String(String::new())];
|
||||||
|
test_de_no_value(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_number_as_string() {
|
||||||
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
struct Num {
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
# Cannot be represented as u128
|
||||||
|
value: 340282366920938463463374607431768211457
|
||||||
|
"};
|
||||||
|
let expected = Num {
|
||||||
|
value: "340282366920938463463374607431768211457".to_owned(),
|
||||||
|
};
|
||||||
|
test_de_no_value(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_string() {
|
||||||
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
struct Struct {
|
||||||
|
empty: String,
|
||||||
|
tilde: String,
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
empty:
|
||||||
|
tilde: ~
|
||||||
|
"};
|
||||||
|
let expected = Struct {
|
||||||
|
empty: String::new(),
|
||||||
|
tilde: "~".to_owned(),
|
||||||
|
};
|
||||||
|
test_de_no_value(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_i128_big() {
|
||||||
|
let expected: i128 = i64::MIN as i128 - 1;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
-9223372036854775809
|
||||||
|
"};
|
||||||
|
assert_eq!(expected, serde_yaml::from_str::<i128>(yaml).unwrap());
|
||||||
|
|
||||||
|
let octal = indoc! {"
|
||||||
|
-0o1000000000000000000001
|
||||||
|
"};
|
||||||
|
assert_eq!(expected, serde_yaml::from_str::<i128>(octal).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u128_big() {
|
||||||
|
let expected: u128 = u64::MAX as u128 + 1;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
18446744073709551616
|
||||||
|
"};
|
||||||
|
assert_eq!(expected, serde_yaml::from_str::<u128>(yaml).unwrap());
|
||||||
|
|
||||||
|
let octal = indoc! {"
|
||||||
|
0o2000000000000000000000
|
||||||
|
"};
|
||||||
|
assert_eq!(expected, serde_yaml::from_str::<u128>(octal).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_number_alias_as_string() {
|
||||||
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
struct Num {
|
||||||
|
version: String,
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
version: &a 1.10
|
||||||
|
value: *a
|
||||||
|
"};
|
||||||
|
let expected = Num {
|
||||||
|
version: "1.10".to_owned(),
|
||||||
|
value: "1.10".to_owned(),
|
||||||
|
};
|
||||||
|
test_de_no_value(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_de_mapping() {
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
|
struct Data {
|
||||||
|
pub substructure: serde_yaml::Mapping,
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
substructure:
|
||||||
|
a: 'foo'
|
||||||
|
b: 'bar'
|
||||||
|
"};
|
||||||
|
|
||||||
|
let mut expected = Data {
|
||||||
|
substructure: serde_yaml::Mapping::new(),
|
||||||
|
};
|
||||||
|
expected.substructure.insert(
|
||||||
|
serde_yaml::Value::String("a".to_owned()),
|
||||||
|
serde_yaml::Value::String("foo".to_owned()),
|
||||||
|
);
|
||||||
|
expected.substructure.insert(
|
||||||
|
serde_yaml::Value::String("b".to_owned()),
|
||||||
|
serde_yaml::Value::String("bar".to_owned()),
|
||||||
|
);
|
||||||
|
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_byte_order_mark() {
|
||||||
|
let yaml = "\u{feff}- 0\n";
|
||||||
|
let expected = vec![0];
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bomb() {
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
|
struct Data {
|
||||||
|
expected: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This would deserialize an astronomical number of elements if we were
|
||||||
|
// vulnerable.
|
||||||
|
let yaml = indoc! {"
|
||||||
|
a: &a ~
|
||||||
|
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
||||||
|
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
||||||
|
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
|
||||||
|
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]
|
||||||
|
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]
|
||||||
|
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]
|
||||||
|
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]
|
||||||
|
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]
|
||||||
|
j: &j [*i,*i,*i,*i,*i,*i,*i,*i,*i]
|
||||||
|
k: &k [*j,*j,*j,*j,*j,*j,*j,*j,*j]
|
||||||
|
l: &l [*k,*k,*k,*k,*k,*k,*k,*k,*k]
|
||||||
|
m: &m [*l,*l,*l,*l,*l,*l,*l,*l,*l]
|
||||||
|
n: &n [*m,*m,*m,*m,*m,*m,*m,*m,*m]
|
||||||
|
o: &o [*n,*n,*n,*n,*n,*n,*n,*n,*n]
|
||||||
|
p: &p [*o,*o,*o,*o,*o,*o,*o,*o,*o]
|
||||||
|
q: &q [*p,*p,*p,*p,*p,*p,*p,*p,*p]
|
||||||
|
r: &r [*q,*q,*q,*q,*q,*q,*q,*q,*q]
|
||||||
|
s: &s [*r,*r,*r,*r,*r,*r,*r,*r,*r]
|
||||||
|
t: &t [*s,*s,*s,*s,*s,*s,*s,*s,*s]
|
||||||
|
u: &u [*t,*t,*t,*t,*t,*t,*t,*t,*t]
|
||||||
|
v: &v [*u,*u,*u,*u,*u,*u,*u,*u,*u]
|
||||||
|
w: &w [*v,*v,*v,*v,*v,*v,*v,*v,*v]
|
||||||
|
x: &x [*w,*w,*w,*w,*w,*w,*w,*w,*w]
|
||||||
|
y: &y [*x,*x,*x,*x,*x,*x,*x,*x,*x]
|
||||||
|
z: &z [*y,*y,*y,*y,*y,*y,*y,*y,*y]
|
||||||
|
expected: string
|
||||||
|
"};
|
||||||
|
|
||||||
|
let expected = Data {
|
||||||
|
expected: "string".to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(expected, serde_yaml::from_str::<Data>(yaml).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numbers() {
|
||||||
|
let cases = [
|
||||||
|
("0xF0", "240"),
|
||||||
|
("+0xF0", "240"),
|
||||||
|
("-0xF0", "-240"),
|
||||||
|
("0o70", "56"),
|
||||||
|
("+0o70", "56"),
|
||||||
|
("-0o70", "-56"),
|
||||||
|
("0b10", "2"),
|
||||||
|
("+0b10", "2"),
|
||||||
|
("-0b10", "-2"),
|
||||||
|
("127", "127"),
|
||||||
|
("+127", "127"),
|
||||||
|
("-127", "-127"),
|
||||||
|
(".inf", ".inf"),
|
||||||
|
(".Inf", ".inf"),
|
||||||
|
(".INF", ".inf"),
|
||||||
|
("-.inf", "-.inf"),
|
||||||
|
("-.Inf", "-.inf"),
|
||||||
|
("-.INF", "-.inf"),
|
||||||
|
(".nan", ".nan"),
|
||||||
|
(".NaN", ".nan"),
|
||||||
|
(".NAN", ".nan"),
|
||||||
|
("0.1", "0.1"),
|
||||||
|
];
|
||||||
|
for &(yaml, expected) in &cases {
|
||||||
|
let value = serde_yaml::from_str::<Value>(yaml).unwrap();
|
||||||
|
match value {
|
||||||
|
Value::Number(number) => assert_eq!(number.to_string(), expected),
|
||||||
|
_ => panic!("expected number. input={:?}, result={:?}", yaml, value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOT numbers.
|
||||||
|
let cases = [
|
||||||
|
"0127", "+0127", "-0127", "++.inf", "+-.inf", "++1", "+-1", "-+1", "--1", "0x+1", "0x-1",
|
||||||
|
"-0x+1", "-0x-1", "++0x1", "+-0x1", "-+0x1", "--0x1",
|
||||||
|
];
|
||||||
|
for yaml in &cases {
|
||||||
|
let value = serde_yaml::from_str::<Value>(yaml).unwrap();
|
||||||
|
match value {
|
||||||
|
Value::String(string) => assert_eq!(string, *yaml),
|
||||||
|
_ => panic!("expected string. input={:?}, result={:?}", yaml, value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nan() {
|
||||||
|
// There is no negative NaN in YAML.
|
||||||
|
assert!(serde_yaml::from_str::<f32>(".nan")
|
||||||
|
.unwrap()
|
||||||
|
.is_sign_positive());
|
||||||
|
assert!(serde_yaml::from_str::<f64>(".nan")
|
||||||
|
.unwrap()
|
||||||
|
.is_sign_positive());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stateful() {
|
||||||
|
struct Seed(i64);
|
||||||
|
|
||||||
|
impl<'de> serde::de::DeserializeSeed<'de> for Seed {
|
||||||
|
type Value = i64;
|
||||||
|
fn deserialize<D>(self, deserializer: D) -> Result<i64, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct Visitor(i64);
|
||||||
|
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||||
|
type Value = i64;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(formatter, "an integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<i64, E> {
|
||||||
|
Ok(v * self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<i64, E> {
|
||||||
|
Ok(v as i64 * self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(Visitor(self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cases = [("3", 5, 15), ("6", 7, 42), ("-5", 9, -45)];
|
||||||
|
for &(yaml, seed, expected) in &cases {
|
||||||
|
test_de_seed(yaml, Seed(seed), &expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ignore_tag() {
|
||||||
|
#[derive(Deserialize, Debug, PartialEq)]
|
||||||
|
struct Data {
|
||||||
|
struc: Struc,
|
||||||
|
tuple: Tuple,
|
||||||
|
newtype: Newtype,
|
||||||
|
map: BTreeMap<char, usize>,
|
||||||
|
vec: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, PartialEq)]
|
||||||
|
struct Struc {
|
||||||
|
x: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, PartialEq)]
|
||||||
|
struct Tuple(usize, usize);
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, PartialEq)]
|
||||||
|
struct Newtype(usize);
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
struc: !wat
|
||||||
|
x: 0
|
||||||
|
tuple: !wat
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
newtype: !wat 0
|
||||||
|
map: !wat
|
||||||
|
x: 0
|
||||||
|
vec: !wat
|
||||||
|
- 0
|
||||||
|
"};
|
||||||
|
|
||||||
|
let expected = Data {
|
||||||
|
struc: Struc { x: 0 },
|
||||||
|
tuple: Tuple(0, 0),
|
||||||
|
newtype: Newtype(0),
|
||||||
|
map: {
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
map.insert('x', 0);
|
||||||
|
map
|
||||||
|
},
|
||||||
|
vec: vec![0],
|
||||||
|
};
|
||||||
|
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_required_fields() {
|
||||||
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
pub struct NoRequiredFields {
|
||||||
|
optional: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
for document in ["", "# comment\n"] {
|
||||||
|
let expected = NoRequiredFields { optional: None };
|
||||||
|
let deserialized: NoRequiredFields = serde_yaml::from_str(document).unwrap();
|
||||||
|
assert_eq!(expected, deserialized);
|
||||||
|
|
||||||
|
let expected = Vec::<String>::new();
|
||||||
|
let deserialized: Vec<String> = serde_yaml::from_str(document).unwrap();
|
||||||
|
assert_eq!(expected, deserialized);
|
||||||
|
|
||||||
|
let expected = BTreeMap::new();
|
||||||
|
let deserialized: BTreeMap<char, usize> = serde_yaml::from_str(document).unwrap();
|
||||||
|
assert_eq!(expected, deserialized);
|
||||||
|
|
||||||
|
let expected = None;
|
||||||
|
let deserialized: Option<String> = serde_yaml::from_str(document).unwrap();
|
||||||
|
assert_eq!(expected, deserialized);
|
||||||
|
|
||||||
|
let expected = Value::Null;
|
||||||
|
let deserialized: Value = serde_yaml::from_str(document).unwrap();
|
||||||
|
assert_eq!(expected, deserialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_scalar() {
|
||||||
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
struct Struct<T> {
|
||||||
|
thing: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml = "thing:\n";
|
||||||
|
let expected = Struct {
|
||||||
|
thing: serde_yaml::Sequence::new(),
|
||||||
|
};
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
|
||||||
|
let expected = Struct {
|
||||||
|
thing: serde_yaml::Mapping::new(),
|
||||||
|
};
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_python_safe_dump() {
|
||||||
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
struct Frob {
|
||||||
|
foo: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This matches output produced by PyYAML's `yaml.safe_dump` when using the
|
||||||
|
// default_style parameter.
|
||||||
|
//
|
||||||
|
// >>> import yaml
|
||||||
|
// >>> d = {"foo": 7200}
|
||||||
|
// >>> print(yaml.safe_dump(d, default_style="|"))
|
||||||
|
// "foo": !!int |-
|
||||||
|
// 7200
|
||||||
|
//
|
||||||
|
let yaml = indoc! {r#"
|
||||||
|
"foo": !!int |-
|
||||||
|
7200
|
||||||
|
"#};
|
||||||
|
|
||||||
|
let expected = Frob { foo: 7200 };
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tag_resolution() {
|
||||||
|
// https://yaml.org/spec/1.2.2/#1032-tag-resolution
|
||||||
|
let yaml = indoc! {"
|
||||||
|
- null
|
||||||
|
- Null
|
||||||
|
- NULL
|
||||||
|
- ~
|
||||||
|
-
|
||||||
|
- true
|
||||||
|
- True
|
||||||
|
- TRUE
|
||||||
|
- false
|
||||||
|
- False
|
||||||
|
- FALSE
|
||||||
|
- y
|
||||||
|
- Y
|
||||||
|
- yes
|
||||||
|
- Yes
|
||||||
|
- YES
|
||||||
|
- n
|
||||||
|
- N
|
||||||
|
- no
|
||||||
|
- No
|
||||||
|
- NO
|
||||||
|
- on
|
||||||
|
- On
|
||||||
|
- ON
|
||||||
|
- off
|
||||||
|
- Off
|
||||||
|
- OFF
|
||||||
|
"};
|
||||||
|
|
||||||
|
let expected = vec![
|
||||||
|
Value::Null,
|
||||||
|
Value::Null,
|
||||||
|
Value::Null,
|
||||||
|
Value::Null,
|
||||||
|
Value::Null,
|
||||||
|
Value::Bool(true),
|
||||||
|
Value::Bool(true),
|
||||||
|
Value::Bool(true),
|
||||||
|
Value::Bool(false),
|
||||||
|
Value::Bool(false),
|
||||||
|
Value::Bool(false),
|
||||||
|
Value::String("y".to_owned()),
|
||||||
|
Value::String("Y".to_owned()),
|
||||||
|
Value::String("yes".to_owned()),
|
||||||
|
Value::String("Yes".to_owned()),
|
||||||
|
Value::String("YES".to_owned()),
|
||||||
|
Value::String("n".to_owned()),
|
||||||
|
Value::String("N".to_owned()),
|
||||||
|
Value::String("no".to_owned()),
|
||||||
|
Value::String("No".to_owned()),
|
||||||
|
Value::String("NO".to_owned()),
|
||||||
|
Value::String("on".to_owned()),
|
||||||
|
Value::String("On".to_owned()),
|
||||||
|
Value::String("ON".to_owned()),
|
||||||
|
Value::String("off".to_owned()),
|
||||||
|
Value::String("Off".to_owned()),
|
||||||
|
Value::String("OFF".to_owned()),
|
||||||
|
];
|
||||||
|
|
||||||
|
test_de(yaml, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_number() {
|
||||||
|
let n = "111".parse::<Number>().unwrap();
|
||||||
|
assert_eq!(n, Number::from(111));
|
||||||
|
|
||||||
|
let n = "-111".parse::<Number>().unwrap();
|
||||||
|
assert_eq!(n, Number::from(-111));
|
||||||
|
|
||||||
|
let n = "-1.1".parse::<Number>().unwrap();
|
||||||
|
assert_eq!(n, Number::from(-1.1));
|
||||||
|
|
||||||
|
let n = ".nan".parse::<Number>().unwrap();
|
||||||
|
assert_eq!(n, Number::from(f64::NAN));
|
||||||
|
assert!(n.as_f64().unwrap().is_sign_positive());
|
||||||
|
|
||||||
|
let n = ".inf".parse::<Number>().unwrap();
|
||||||
|
assert_eq!(n, Number::from(f64::INFINITY));
|
||||||
|
|
||||||
|
let n = "-.inf".parse::<Number>().unwrap();
|
||||||
|
assert_eq!(n, Number::from(f64::NEG_INFINITY));
|
||||||
|
|
||||||
|
let err = "null".parse::<Number>().unwrap_err();
|
||||||
|
assert_eq!(err.to_string(), "failed to parse YAML number");
|
||||||
|
|
||||||
|
let err = " 1 ".parse::<Number>().unwrap_err();
|
||||||
|
assert_eq!(err.to_string(), "failed to parse YAML number");
|
||||||
|
}
|
500
serde/tests/test_error.rs
Normal file
500
serde/tests/test_error.rs
Normal file
|
@ -0,0 +1,500 @@
|
||||||
|
#![allow(clippy::zero_sized_map_values)]
|
||||||
|
|
||||||
|
use indoc::indoc;
|
||||||
|
use serde::de::Deserialize;
|
||||||
|
#[cfg(not(miri))]
|
||||||
|
use serde::de::{SeqAccess, Visitor};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use serde_yaml::value::{Tag, TaggedValue};
|
||||||
|
use serde_yaml::{Deserializer, Value};
|
||||||
|
#[cfg(not(miri))]
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
#[cfg(not(miri))]
|
||||||
|
use std::fmt;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
fn test_error<'de, T>(yaml: &'de str, expected: &str)
|
||||||
|
where
|
||||||
|
T: Deserialize<'de> + Debug,
|
||||||
|
{
|
||||||
|
let result = serde_yaml::from_str::<T>(yaml);
|
||||||
|
assert_eq!(expected, result.unwrap_err().to_string());
|
||||||
|
|
||||||
|
let mut deserializer = Deserializer::from_str(yaml);
|
||||||
|
if let Some(first_document) = deserializer.next() {
|
||||||
|
if deserializer.next().is_none() {
|
||||||
|
let result = T::deserialize(first_document);
|
||||||
|
assert_eq!(expected, result.unwrap_err().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scan_error() {
|
||||||
|
let yaml = ">\n@";
|
||||||
|
let expected = "found character that cannot start any token at line 2 column 1, while scanning for the next token";
|
||||||
|
test_error::<Value>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_incorrect_type() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
str
|
||||||
|
"};
|
||||||
|
let expected = "invalid type: string \"str\", expected i16 at line 2 column 1";
|
||||||
|
test_error::<i16>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_incorrect_nested_type() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct A {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub b: Vec<B>,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub enum B {
|
||||||
|
C(#[allow(dead_code)] C),
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct C {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub d: bool,
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
b:
|
||||||
|
- !C
|
||||||
|
d: fase
|
||||||
|
"};
|
||||||
|
let expected = "b[0].d: invalid type: string \"fase\", expected a boolean at line 3 column 8";
|
||||||
|
test_error::<A>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty() {
|
||||||
|
let expected = "EOF while parsing a value";
|
||||||
|
test_error::<String>("", expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_field() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Basic {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub v: bool,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub w: bool,
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
v: true
|
||||||
|
"};
|
||||||
|
let expected = "missing field `w` at line 2 column 1";
|
||||||
|
test_error::<Basic>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unknown_anchor() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
*some
|
||||||
|
"};
|
||||||
|
let expected = "unknown anchor at line 2 column 1";
|
||||||
|
test_error::<String>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ignored_unknown_anchor() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Wrapper {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub c: (),
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
b: [*a]
|
||||||
|
c: ~
|
||||||
|
"};
|
||||||
|
let expected = "unknown anchor at line 1 column 5";
|
||||||
|
test_error::<Wrapper>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bytes() {
|
||||||
|
let expected = "serialization and deserialization of bytes in YAML is not implemented";
|
||||||
|
test_error::<&[u8]>("...", expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_two_documents() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
0
|
||||||
|
---
|
||||||
|
1
|
||||||
|
"};
|
||||||
|
let expected = "deserializing from YAML containing more than one document is not supported";
|
||||||
|
test_error::<usize>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_second_document_syntax_error() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
0
|
||||||
|
---
|
||||||
|
]
|
||||||
|
"};
|
||||||
|
|
||||||
|
let mut de = Deserializer::from_str(yaml);
|
||||||
|
let first_doc = de.next().unwrap();
|
||||||
|
let result = <usize as serde::Deserialize>::deserialize(first_doc);
|
||||||
|
assert_eq!(0, result.unwrap());
|
||||||
|
|
||||||
|
let second_doc = de.next().unwrap();
|
||||||
|
let result = <usize as serde::Deserialize>::deserialize(second_doc);
|
||||||
|
let expected =
|
||||||
|
"did not find expected node content at line 4 column 1, while parsing a block node";
|
||||||
|
assert_eq!(expected, result.unwrap_err().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_enum_tag() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub enum E {
|
||||||
|
V(#[allow(dead_code)] usize),
|
||||||
|
}
|
||||||
|
let yaml = indoc! {r#"
|
||||||
|
"V": 16
|
||||||
|
"other": 32
|
||||||
|
"#};
|
||||||
|
let expected = "invalid type: map, expected a YAML tag starting with '!'";
|
||||||
|
test_error::<E>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialize_nested_enum() {
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub enum Outer {
|
||||||
|
Inner(Inner),
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub enum Inner {
|
||||||
|
Newtype(usize),
|
||||||
|
Tuple(usize, usize),
|
||||||
|
Struct { x: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected = "serializing nested enums in YAML is not supported yet";
|
||||||
|
|
||||||
|
let e = Outer::Inner(Inner::Newtype(0));
|
||||||
|
let error = serde_yaml::to_string(&e).unwrap_err();
|
||||||
|
assert_eq!(error.to_string(), expected);
|
||||||
|
|
||||||
|
let e = Outer::Inner(Inner::Tuple(0, 0));
|
||||||
|
let error = serde_yaml::to_string(&e).unwrap_err();
|
||||||
|
assert_eq!(error.to_string(), expected);
|
||||||
|
|
||||||
|
let e = Outer::Inner(Inner::Struct { x: 0 });
|
||||||
|
let error = serde_yaml::to_string(&e).unwrap_err();
|
||||||
|
assert_eq!(error.to_string(), expected);
|
||||||
|
|
||||||
|
let e = Value::Tagged(Box::new(TaggedValue {
|
||||||
|
tag: Tag::new("Outer"),
|
||||||
|
value: Value::Tagged(Box::new(TaggedValue {
|
||||||
|
tag: Tag::new("Inner"),
|
||||||
|
value: Value::Null,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
let error = serde_yaml::to_string(&e).unwrap_err();
|
||||||
|
assert_eq!(error.to_string(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_nested_enum() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub enum Outer {
|
||||||
|
Inner(#[allow(dead_code)] Inner),
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub enum Inner {
|
||||||
|
Variant(#[allow(dead_code)] Vec<usize>),
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
!Inner []
|
||||||
|
"};
|
||||||
|
let expected = "deserializing nested enum in Outer::Inner from YAML is not supported yet at line 2 column 1";
|
||||||
|
test_error::<Outer>(yaml, expected);
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
!Variant []
|
||||||
|
"};
|
||||||
|
let expected = "unknown variant `Variant`, expected `Inner`";
|
||||||
|
test_error::<Outer>(yaml, expected);
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
!Inner !Variant []
|
||||||
|
"};
|
||||||
|
let expected = "deserializing nested enum in Outer::Inner from YAML is not supported yet at line 2 column 1";
|
||||||
|
test_error::<Outer>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_variant_not_a_seq() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub enum E {
|
||||||
|
V(#[allow(dead_code)] usize),
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
!V
|
||||||
|
value: 0
|
||||||
|
"};
|
||||||
|
let expected = "invalid type: map, expected usize at line 2 column 1";
|
||||||
|
test_error::<E>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_struct_from_sequence() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Struct {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub x: usize,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub y: usize,
|
||||||
|
}
|
||||||
|
let yaml = indoc! {"
|
||||||
|
[0, 0]
|
||||||
|
"};
|
||||||
|
let expected = "invalid type: sequence, expected struct Struct";
|
||||||
|
test_error::<Struct>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bad_bool() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
!!bool str
|
||||||
|
"};
|
||||||
|
let expected = "invalid value: string \"str\", expected a boolean at line 2 column 1";
|
||||||
|
test_error::<bool>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bad_int() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
!!int str
|
||||||
|
"};
|
||||||
|
let expected = "invalid value: string \"str\", expected an integer at line 2 column 1";
|
||||||
|
test_error::<i64>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bad_float() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
!!float str
|
||||||
|
"};
|
||||||
|
let expected = "invalid value: string \"str\", expected a float at line 2 column 1";
|
||||||
|
test_error::<f64>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bad_null() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
!!null str
|
||||||
|
"};
|
||||||
|
let expected = "invalid value: string \"str\", expected null at line 2 column 1";
|
||||||
|
test_error::<()>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_short_tuple() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
[0, 0]
|
||||||
|
"};
|
||||||
|
let expected = "invalid length 2, expected a tuple of size 3 at line 2 column 1";
|
||||||
|
test_error::<(u8, u8, u8)>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_long_tuple() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
[0, 0, 0]
|
||||||
|
"};
|
||||||
|
let expected = "invalid length 3, expected sequence of 2 elements at line 2 column 1";
|
||||||
|
test_error::<(u8, u8)>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_scalar_type() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct S {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub x: [i32; 1],
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml = "x: ''\n";
|
||||||
|
let expected = "x: invalid type: string \"\", expected an array of length 1 at line 1 column 4";
|
||||||
|
test_error::<S>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
|
#[test]
|
||||||
|
fn test_infinite_recursion_objects() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct S {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub x: Option<Box<S>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml = "&a {'x': *a}";
|
||||||
|
let expected = "recursion limit exceeded";
|
||||||
|
test_error::<S>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
|
#[test]
|
||||||
|
fn test_infinite_recursion_arrays() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct S(
|
||||||
|
#[allow(dead_code)] pub usize,
|
||||||
|
#[allow(dead_code)] pub Option<Box<S>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
let yaml = "&a [0, *a]";
|
||||||
|
let expected = "recursion limit exceeded";
|
||||||
|
test_error::<S>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
|
#[test]
|
||||||
|
fn test_infinite_recursion_newtype() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct S(#[allow(dead_code)] pub Option<Box<S>>);
|
||||||
|
|
||||||
|
let yaml = "&a [*a]";
|
||||||
|
let expected = "recursion limit exceeded";
|
||||||
|
test_error::<S>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
|
#[test]
|
||||||
|
fn test_finite_recursion_objects() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct S {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub x: Option<Box<S>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml = "{'x':".repeat(1_000) + &"}".repeat(1_000);
|
||||||
|
let expected = "recursion limit exceeded at line 1 column 641";
|
||||||
|
test_error::<S>(&yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
|
#[test]
|
||||||
|
fn test_finite_recursion_arrays() {
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct S(
|
||||||
|
#[allow(dead_code)] pub usize,
|
||||||
|
#[allow(dead_code)] pub Option<Box<S>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
let yaml = "[0, ".repeat(1_000) + &"]".repeat(1_000);
|
||||||
|
let expected = "recursion limit exceeded at line 1 column 513";
|
||||||
|
test_error::<S>(&yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
|
#[test]
|
||||||
|
fn test_billion_laughs() {
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct X;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for X {
|
||||||
|
type Value = X;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("exponential blowup")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_unit<E>(self) -> Result<X, E> {
|
||||||
|
Ok(X)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<S>(self, mut seq: S) -> Result<X, S::Error>
|
||||||
|
where
|
||||||
|
S: SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
while let Some(X) = seq.next_element()? {}
|
||||||
|
Ok(X)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for X {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<X, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(X)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
a: &a ~
|
||||||
|
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
|
||||||
|
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
|
||||||
|
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
|
||||||
|
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]
|
||||||
|
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]
|
||||||
|
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]
|
||||||
|
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]
|
||||||
|
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]
|
||||||
|
"};
|
||||||
|
let expected = "repetition limit exceeded";
|
||||||
|
test_error::<BTreeMap<String, X>>(yaml, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_duplicate_keys() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
thing: true
|
||||||
|
thing: false
|
||||||
|
"};
|
||||||
|
let expected = "duplicate entry with key \"thing\" at line 2 column 1";
|
||||||
|
test_error::<Value>(yaml, expected);
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
null: true
|
||||||
|
~: false
|
||||||
|
"};
|
||||||
|
let expected = "duplicate entry with null key at line 2 column 1";
|
||||||
|
test_error::<Value>(yaml, expected);
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
99: true
|
||||||
|
99: false
|
||||||
|
"};
|
||||||
|
let expected = "duplicate entry with key 99 at line 2 column 1";
|
||||||
|
test_error::<Value>(yaml, expected);
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
{}: true
|
||||||
|
{}: false
|
||||||
|
"};
|
||||||
|
let expected = "duplicate entry in YAML map at line 2 column 1";
|
||||||
|
test_error::<Value>(yaml, expected);
|
||||||
|
}
|
578
serde/tests/test_serde.rs
Normal file
578
serde/tests/test_serde.rs
Normal file
|
@ -0,0 +1,578 @@
|
||||||
|
#![allow(
|
||||||
|
clippy::decimal_literal_representation,
|
||||||
|
clippy::derive_partial_eq_without_eq,
|
||||||
|
clippy::unreadable_literal,
|
||||||
|
clippy::shadow_unrelated
|
||||||
|
)]
|
||||||
|
|
||||||
|
use indoc::indoc;
|
||||||
|
use serde::ser::SerializeMap;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use serde_yaml::{Mapping, Number, Value};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
fn test_serde<T>(thing: &T, yaml: &str)
|
||||||
|
where
|
||||||
|
T: serde::Serialize + serde::de::DeserializeOwned + PartialEq + Debug,
|
||||||
|
{
|
||||||
|
let serialized = serde_yaml::to_string(&thing).unwrap();
|
||||||
|
assert_eq!(yaml, serialized);
|
||||||
|
|
||||||
|
let value = serde_yaml::to_value(thing).unwrap();
|
||||||
|
let serialized = serde_yaml::to_string(&value).unwrap();
|
||||||
|
assert_eq!(yaml, serialized);
|
||||||
|
|
||||||
|
let deserialized: T = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
assert_eq!(*thing, deserialized);
|
||||||
|
|
||||||
|
let value: Value = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
let deserialized = T::deserialize(&value).unwrap();
|
||||||
|
assert_eq!(*thing, deserialized);
|
||||||
|
|
||||||
|
let deserialized: T = serde_yaml::from_value(value).unwrap();
|
||||||
|
assert_eq!(*thing, deserialized);
|
||||||
|
|
||||||
|
serde_yaml::from_str::<serde::de::IgnoredAny>(yaml).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default() {
|
||||||
|
assert_eq!(Value::default(), Value::Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_int() {
|
||||||
|
let thing = 256;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
256
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_int_max_u64() {
|
||||||
|
let thing = u64::MAX;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
18446744073709551615
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_int_min_i64() {
|
||||||
|
let thing = i64::MIN;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
-9223372036854775808
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_int_max_i64() {
|
||||||
|
let thing = i64::MAX;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
9223372036854775807
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_i128_small() {
|
||||||
|
let thing: i128 = -256;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
-256
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_u128_small() {
|
||||||
|
let thing: u128 = 256;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
256
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_float() {
|
||||||
|
let thing = 25.6;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
25.6
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
|
||||||
|
let thing = 25.;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
25.0
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
|
||||||
|
let thing = f64::INFINITY;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
.inf
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
|
||||||
|
let thing = f64::NEG_INFINITY;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
-.inf
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
|
||||||
|
let float: f64 = serde_yaml::from_str(indoc! {"
|
||||||
|
.nan
|
||||||
|
"})
|
||||||
|
.unwrap();
|
||||||
|
assert!(float.is_nan());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_float32() {
|
||||||
|
let thing: f32 = 25.5;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
25.5
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
|
||||||
|
let thing = f32::INFINITY;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
.inf
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
|
||||||
|
let thing = f32::NEG_INFINITY;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
-.inf
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
|
||||||
|
let single_float: f32 = serde_yaml::from_str(indoc! {"
|
||||||
|
.nan
|
||||||
|
"})
|
||||||
|
.unwrap();
|
||||||
|
assert!(single_float.is_nan());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_char() {
|
||||||
|
let ch = '.';
|
||||||
|
let yaml = indoc! {"
|
||||||
|
'.'
|
||||||
|
"};
|
||||||
|
assert_eq!(yaml, serde_yaml::to_string(&ch).unwrap());
|
||||||
|
|
||||||
|
let ch = '#';
|
||||||
|
let yaml = indoc! {"
|
||||||
|
'#'
|
||||||
|
"};
|
||||||
|
assert_eq!(yaml, serde_yaml::to_string(&ch).unwrap());
|
||||||
|
|
||||||
|
let ch = '-';
|
||||||
|
let yaml = indoc! {"
|
||||||
|
'-'
|
||||||
|
"};
|
||||||
|
assert_eq!(yaml, serde_yaml::to_string(&ch).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vec() {
|
||||||
|
let thing = vec![1, 2, 3];
|
||||||
|
let yaml = indoc! {"
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map() {
|
||||||
|
let mut thing = BTreeMap::new();
|
||||||
|
thing.insert("x".to_owned(), 1);
|
||||||
|
thing.insert("y".to_owned(), 2);
|
||||||
|
let yaml = indoc! {"
|
||||||
|
x: 1
|
||||||
|
y: 2
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_key_value() {
|
||||||
|
struct Map;
|
||||||
|
|
||||||
|
impl serde::Serialize for Map {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
// Test maps which do not serialize using serialize_entry.
|
||||||
|
let mut map = serializer.serialize_map(Some(1))?;
|
||||||
|
map.serialize_key("k")?;
|
||||||
|
map.serialize_value("v")?;
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
k: v
|
||||||
|
"};
|
||||||
|
assert_eq!(yaml, serde_yaml::to_string(&Map).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_struct() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct Basic {
|
||||||
|
x: isize,
|
||||||
|
y: String,
|
||||||
|
z: bool,
|
||||||
|
}
|
||||||
|
let thing = Basic {
|
||||||
|
x: -4,
|
||||||
|
y: "hi\tquoted".to_owned(),
|
||||||
|
z: true,
|
||||||
|
};
|
||||||
|
let yaml = indoc! {r#"
|
||||||
|
x: -4
|
||||||
|
y: "hi\tquoted"
|
||||||
|
z: true
|
||||||
|
"#};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_string_escapes() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
ascii
|
||||||
|
"};
|
||||||
|
test_serde(&"ascii".to_owned(), yaml);
|
||||||
|
|
||||||
|
let yaml = indoc! {r#"
|
||||||
|
"\0\a\b\t\n\v\f\r\e\"\\\N\L\P"
|
||||||
|
"#};
|
||||||
|
test_serde(
|
||||||
|
&"\0\u{7}\u{8}\t\n\u{b}\u{c}\r\u{1b}\"\\\u{85}\u{2028}\u{2029}".to_owned(),
|
||||||
|
yaml,
|
||||||
|
);
|
||||||
|
|
||||||
|
let yaml = indoc! {r#"
|
||||||
|
"\x1F\uFEFF"
|
||||||
|
"#};
|
||||||
|
test_serde(&"\u{1f}\u{feff}".to_owned(), yaml);
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
🎉
|
||||||
|
"};
|
||||||
|
test_serde(&"\u{1f389}".to_owned(), yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiline_string() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct Struct {
|
||||||
|
trailing_newline: String,
|
||||||
|
no_trailing_newline: String,
|
||||||
|
}
|
||||||
|
let thing = Struct {
|
||||||
|
trailing_newline: "aaa\nbbb\n".to_owned(),
|
||||||
|
no_trailing_newline: "aaa\nbbb".to_owned(),
|
||||||
|
};
|
||||||
|
let yaml = indoc! {"
|
||||||
|
trailing_newline: |
|
||||||
|
aaa
|
||||||
|
bbb
|
||||||
|
no_trailing_newline: |-
|
||||||
|
aaa
|
||||||
|
bbb
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strings_needing_quote() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct Struct {
|
||||||
|
boolean: String,
|
||||||
|
integer: String,
|
||||||
|
void: String,
|
||||||
|
leading_zeros: String,
|
||||||
|
}
|
||||||
|
let thing = Struct {
|
||||||
|
boolean: "true".to_owned(),
|
||||||
|
integer: "1".to_owned(),
|
||||||
|
void: "null".to_owned(),
|
||||||
|
leading_zeros: "007".to_owned(),
|
||||||
|
};
|
||||||
|
let yaml = indoc! {"
|
||||||
|
boolean: 'true'
|
||||||
|
integer: '1'
|
||||||
|
void: 'null'
|
||||||
|
leading_zeros: '007'
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_vec() {
|
||||||
|
let thing = vec![vec![1, 2, 3], vec![4, 5, 6]];
|
||||||
|
let yaml = indoc! {"
|
||||||
|
- - 1
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
- - 4
|
||||||
|
- 5
|
||||||
|
- 6
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_struct() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct Outer {
|
||||||
|
inner: Inner,
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct Inner {
|
||||||
|
v: u16,
|
||||||
|
}
|
||||||
|
let thing = Outer {
|
||||||
|
inner: Inner { v: 512 },
|
||||||
|
};
|
||||||
|
let yaml = indoc! {"
|
||||||
|
inner:
|
||||||
|
v: 512
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_enum() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
enum Outer {
|
||||||
|
Inner(Inner),
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
enum Inner {
|
||||||
|
Unit,
|
||||||
|
}
|
||||||
|
let thing = Outer::Inner(Inner::Unit);
|
||||||
|
let yaml = indoc! {"
|
||||||
|
!Inner Unit
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_option() {
|
||||||
|
let thing = vec![Some(1), None, Some(3)];
|
||||||
|
let yaml = indoc! {"
|
||||||
|
- 1
|
||||||
|
- null
|
||||||
|
- 3
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unit() {
|
||||||
|
let thing = vec![(), ()];
|
||||||
|
let yaml = indoc! {"
|
||||||
|
- null
|
||||||
|
- null
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unit_struct() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct Foo;
|
||||||
|
let thing = Foo;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
null
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unit_variant() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
enum Variant {
|
||||||
|
First,
|
||||||
|
Second,
|
||||||
|
}
|
||||||
|
let thing = Variant::First;
|
||||||
|
let yaml = indoc! {"
|
||||||
|
First
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_newtype_struct() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct OriginalType {
|
||||||
|
v: u16,
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct NewType(OriginalType);
|
||||||
|
let thing = NewType(OriginalType { v: 1 });
|
||||||
|
let yaml = indoc! {"
|
||||||
|
v: 1
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_newtype_variant() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
enum Variant {
|
||||||
|
Size(usize),
|
||||||
|
}
|
||||||
|
let thing = Variant::Size(127);
|
||||||
|
let yaml = indoc! {"
|
||||||
|
!Size 127
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tuple_variant() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
enum Variant {
|
||||||
|
Rgb(u8, u8, u8),
|
||||||
|
}
|
||||||
|
let thing = Variant::Rgb(32, 64, 96);
|
||||||
|
let yaml = indoc! {"
|
||||||
|
!Rgb
|
||||||
|
- 32
|
||||||
|
- 64
|
||||||
|
- 96
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_struct_variant() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
enum Variant {
|
||||||
|
Color { r: u8, g: u8, b: u8 },
|
||||||
|
}
|
||||||
|
let thing = Variant::Color {
|
||||||
|
r: 32,
|
||||||
|
g: 64,
|
||||||
|
b: 96,
|
||||||
|
};
|
||||||
|
let yaml = indoc! {"
|
||||||
|
!Color
|
||||||
|
r: 32
|
||||||
|
g: 64
|
||||||
|
b: 96
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tagged_map_value() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct Bindings {
|
||||||
|
profile: Profile,
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
enum Profile {
|
||||||
|
ClassValidator { class_name: String },
|
||||||
|
}
|
||||||
|
let thing = Bindings {
|
||||||
|
profile: Profile::ClassValidator {
|
||||||
|
class_name: "ApplicationConfig".to_owned(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let yaml = indoc! {"
|
||||||
|
profile: !ClassValidator
|
||||||
|
class_name: ApplicationConfig
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_value() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
pub struct GenericInstructions {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub typ: String,
|
||||||
|
pub config: Value,
|
||||||
|
}
|
||||||
|
let thing = GenericInstructions {
|
||||||
|
typ: "primary".to_string(),
|
||||||
|
config: Value::Sequence(vec![
|
||||||
|
Value::Null,
|
||||||
|
Value::Bool(true),
|
||||||
|
Value::Number(Number::from(65535)),
|
||||||
|
Value::Number(Number::from(0.54321)),
|
||||||
|
Value::String("s".into()),
|
||||||
|
Value::Mapping(Mapping::new()),
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
let yaml = indoc! {"
|
||||||
|
type: primary
|
||||||
|
config:
|
||||||
|
- null
|
||||||
|
- true
|
||||||
|
- 65535
|
||||||
|
- 0.54321
|
||||||
|
- s
|
||||||
|
- {}
|
||||||
|
"};
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mapping() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct Data {
|
||||||
|
pub substructure: Mapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut thing = Data {
|
||||||
|
substructure: Mapping::new(),
|
||||||
|
};
|
||||||
|
thing.substructure.insert(
|
||||||
|
Value::String("a".to_owned()),
|
||||||
|
Value::String("foo".to_owned()),
|
||||||
|
);
|
||||||
|
thing.substructure.insert(
|
||||||
|
Value::String("b".to_owned()),
|
||||||
|
Value::String("bar".to_owned()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
substructure:
|
||||||
|
a: foo
|
||||||
|
b: bar
|
||||||
|
"};
|
||||||
|
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_long_string() {
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct Data {
|
||||||
|
pub string: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let thing = Data {
|
||||||
|
string: iter::repeat(["word", " "]).flatten().take(69).collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let yaml = indoc! {"
|
||||||
|
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
|
||||||
|
"};
|
||||||
|
|
||||||
|
test_serde(&thing, yaml);
|
||||||
|
}
|
153
serde/tests/test_value.rs
Normal file
153
serde/tests/test_value.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
#![allow(
|
||||||
|
clippy::derive_partial_eq_without_eq,
|
||||||
|
clippy::eq_op,
|
||||||
|
clippy::uninlined_format_args
|
||||||
|
)]
|
||||||
|
|
||||||
|
use indoc::indoc;
|
||||||
|
use serde::de::IntoDeserializer;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use serde_yaml::{Number, Value};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nan() {
|
||||||
|
let pos_nan = serde_yaml::from_str::<Value>(".nan").unwrap();
|
||||||
|
assert!(pos_nan.is_f64());
|
||||||
|
assert_eq!(pos_nan, pos_nan);
|
||||||
|
|
||||||
|
let neg_fake_nan = serde_yaml::from_str::<Value>("-.nan").unwrap();
|
||||||
|
assert!(neg_fake_nan.is_string());
|
||||||
|
|
||||||
|
let significand_mask = 0xF_FFFF_FFFF_FFFF;
|
||||||
|
let bits = (f64::NAN.copysign(1.0).to_bits() ^ significand_mask) | 1;
|
||||||
|
let different_pos_nan = Value::Number(Number::from(f64::from_bits(bits)));
|
||||||
|
assert_eq!(pos_nan, different_pos_nan);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_digits() {
|
||||||
|
let num_string = serde_yaml::from_str::<Value>("01").unwrap();
|
||||||
|
assert!(num_string.is_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_into_deserializer() {
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
|
struct Test {
|
||||||
|
first: String,
|
||||||
|
second: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = serde_yaml::from_str::<Value>("xyz").unwrap();
|
||||||
|
let s = String::deserialize(value.into_deserializer()).unwrap();
|
||||||
|
assert_eq!(s, "xyz");
|
||||||
|
|
||||||
|
let value = serde_yaml::from_str::<Value>("- first\n- second\n- third").unwrap();
|
||||||
|
let arr = Vec::<String>::deserialize(value.into_deserializer()).unwrap();
|
||||||
|
assert_eq!(arr, &["first", "second", "third"]);
|
||||||
|
|
||||||
|
let value = serde_yaml::from_str::<Value>("first: abc\nsecond: 99").unwrap();
|
||||||
|
let test = Test::deserialize(value.into_deserializer()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
test,
|
||||||
|
Test {
|
||||||
|
first: "abc".to_string(),
|
||||||
|
second: 99
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merge() {
|
||||||
|
// From https://yaml.org/type/merge.html.
|
||||||
|
let yaml = indoc! {"
|
||||||
|
---
|
||||||
|
- &CENTER { x: 1, y: 2 }
|
||||||
|
- &LEFT { x: 0, y: 2 }
|
||||||
|
- &BIG { r: 10 }
|
||||||
|
- &SMALL { r: 1 }
|
||||||
|
|
||||||
|
# All the following maps are equal:
|
||||||
|
|
||||||
|
- # Explicit keys
|
||||||
|
x: 1
|
||||||
|
y: 2
|
||||||
|
r: 10
|
||||||
|
label: center/big
|
||||||
|
|
||||||
|
- # Merge one map
|
||||||
|
<< : *CENTER
|
||||||
|
r: 10
|
||||||
|
label: center/big
|
||||||
|
|
||||||
|
- # Merge multiple maps
|
||||||
|
<< : [ *CENTER, *BIG ]
|
||||||
|
label: center/big
|
||||||
|
|
||||||
|
- # Override
|
||||||
|
<< : [ *BIG, *LEFT, *SMALL ]
|
||||||
|
x: 1
|
||||||
|
label: center/big
|
||||||
|
"};
|
||||||
|
|
||||||
|
let mut value: Value = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
value.apply_merge().unwrap();
|
||||||
|
for i in 5..=7 {
|
||||||
|
assert_eq!(value[4], value[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_debug() {
|
||||||
|
let yaml = indoc! {"
|
||||||
|
'Null': ~
|
||||||
|
Bool: true
|
||||||
|
Number: 1
|
||||||
|
String: ...
|
||||||
|
Sequence:
|
||||||
|
- true
|
||||||
|
EmptySequence: []
|
||||||
|
EmptyMapping: {}
|
||||||
|
Tagged: !tag true
|
||||||
|
"};
|
||||||
|
|
||||||
|
let value: Value = serde_yaml::from_str(yaml).unwrap();
|
||||||
|
let debug = format!("{:#?}", value);
|
||||||
|
|
||||||
|
let expected = indoc! {r#"
|
||||||
|
Mapping {
|
||||||
|
"Null": Null,
|
||||||
|
"Bool": Bool(true),
|
||||||
|
"Number": Number(1),
|
||||||
|
"String": String("..."),
|
||||||
|
"Sequence": Sequence [
|
||||||
|
Bool(true),
|
||||||
|
],
|
||||||
|
"EmptySequence": Sequence [],
|
||||||
|
"EmptyMapping": Mapping {},
|
||||||
|
"Tagged": TaggedValue {
|
||||||
|
tag: !tag,
|
||||||
|
value: Bool(true),
|
||||||
|
},
|
||||||
|
}"#
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(debug, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tagged() {
|
||||||
|
#[derive(Serialize)]
|
||||||
|
enum Enum {
|
||||||
|
Variant(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = serde_yaml::to_value(&Enum::Variant(0)).unwrap();
|
||||||
|
|
||||||
|
let deserialized: serde_yaml::Value = serde_yaml::from_value(value.clone()).unwrap();
|
||||||
|
assert_eq!(value, deserialized);
|
||||||
|
|
||||||
|
let serialized = serde_yaml::to_value(&value).unwrap();
|
||||||
|
assert_eq!(value, serialized);
|
||||||
|
}
|
Loading…
Reference in a new issue