Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
a5117b272d | |||
3a090a8036 | |||
1b3d16c479 | |||
5a2855f0ae | |||
8c5f0ad7d1 | |||
f0ee27779d | |||
609c711e3f | |||
5ec97dab43 | |||
73d2b23ce5 | |||
66e708c9e6 | |||
6e413b7bf5 | |||
6d052fdd66 | |||
702b2e4411 | |||
516ddd1b08 | |||
81213f7927 | |||
e94218d8f5 | |||
1ca19b4dda | |||
a6ef5a914e | |||
25b4083379 | |||
81896339a3 | |||
fc5d8b25fb | |||
5a367bc478 | |||
39486e8503 | |||
f76acf5407 | |||
ded56befb2 |
14 changed files with 724 additions and 227 deletions
|
@ -1,20 +0,0 @@
|
|||
= Changelog
|
||||
:toc:
|
||||
:toclevels: 1
|
||||
:idprefix:
|
||||
:idseparator: -
|
||||
|
||||
== [Unreleased]
|
||||
|
||||
== [v0.2.0] - 2022-11-25
|
||||
|
||||
=== Added
|
||||
|
||||
* parsing & deserialization
|
||||
|
||||
== [v0.1.0] - 2022-11-18
|
||||
|
||||
=== Added
|
||||
|
||||
* initial release
|
||||
* serialization
|
69
CHANGELOG.md
Normal file
69
CHANGELOG.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Changelog
|
||||
|
||||
<!-- next-header -->
|
||||
|
||||
## [Unreleased] - ReleaseDate
|
||||
|
||||
## [1.2.0] - 2024-03-21
|
||||
|
||||
### Added
|
||||
|
||||
- publishing to [crates.io](https://crates.io)
|
||||
|
||||
## [v1.1.0] - 2024-03-21
|
||||
|
||||
### Added
|
||||
|
||||
- implement serializing into generic `io::Write`
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix parsing CRLF
|
||||
|
||||
## [v1.0.0] - 2023-03-10
|
||||
|
||||
### Added
|
||||
|
||||
- implement literal strings
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix serializing strings containing `:`
|
||||
- fix serializing certain escaped characters
|
||||
|
||||
## [v0.2.4] - 2023-03-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix incorrect parsing of unquoted strings
|
||||
|
||||
## [v0.2.3] - 2023-02-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- support backslashes in delimited strings
|
||||
|
||||
## [v0.2.2] - 2023-02-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix deserialization failing on arrays and objects in some cases
|
||||
|
||||
## [v0.2.1] - 2022-12-28
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix serializing Unicode
|
||||
|
||||
## [v0.2.0] - 2022-11-25
|
||||
|
||||
### Added
|
||||
|
||||
* parsing & deserialization
|
||||
|
||||
## [v0.1.0] - 2022-11-18
|
||||
|
||||
### Added
|
||||
|
||||
* initial release
|
||||
* serialization
|
26
Cargo.toml
26
Cargo.toml
|
@ -1,15 +1,27 @@
|
|||
[package]
|
||||
name = "serde_sjson"
|
||||
version = "0.2.0"
|
||||
version = "1.2.0"
|
||||
authors = ["Lucas Schwiderski"]
|
||||
categories = ["encoding", "parser-implementations"]
|
||||
description = "An SJSON serialization file format"
|
||||
documentation = "https://docs.rs/serde_sjson"
|
||||
edition = "2021"
|
||||
keywords = ["serde", "serialization", "sjson"]
|
||||
description = "An SJSON serialization file format"
|
||||
categories = ["encoding", "parser-implementations"]
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/sclu1034/serde_sjson"
|
||||
exclude = [
|
||||
".github/",
|
||||
".ci/",
|
||||
"Justfile"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
nom = "7.1.1"
|
||||
nom_locate = "4.0.0"
|
||||
serde = { version = "1.0.147", default-features = false }
|
||||
nom = "7"
|
||||
nom_locate = "4.1"
|
||||
serde = { version = "1.0", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde = { version = "1.0.194", features = ["derive"] }
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "passively-maintained" }
|
||||
|
|
14
Justfile
14
Justfile
|
@ -1,12 +1,26 @@
|
|||
project := "serde_sjson"
|
||||
default := "run"
|
||||
|
||||
build *ARGS:
|
||||
cargo build {{ARGS}}
|
||||
cargo readme > README.md
|
||||
|
||||
run *ARGS:
|
||||
cargo run -- {{ARGS}}
|
||||
|
||||
test *ARGS:
|
||||
cargo test {{ARGS}}
|
||||
|
||||
doc:
|
||||
cargo doc --no-deps
|
||||
cargo readme > README.md
|
||||
|
||||
serve-doc port='8000': doc
|
||||
python3 -m http.server {{port}} --directory target/doc
|
||||
|
||||
release version execute='':
|
||||
cargo release --sign --allow-branch master {{ if execute != "" { '-x' } else { '' } }} {{version}}
|
||||
|
||||
coverage *ARGS:
|
||||
RUSTFLAGS="-C instrument-coverage" cargo test --tests {{ARGS}} || true
|
||||
cargo profdata -- merge -sparse default*.profraw -o {{project}}.profdata
|
||||
|
|
13
README.adoc
13
README.adoc
|
@ -1,13 +0,0 @@
|
|||
= Serde SJSON
|
||||
:idprefix:
|
||||
:idseparator:
|
||||
:toc: macro
|
||||
:toclevels: 1
|
||||
:!toc-title:
|
||||
:caution-caption: :fire:
|
||||
:important-caption: :exclamtion:
|
||||
:note-caption: :paperclip:
|
||||
:tip-caption: :bulb:
|
||||
:warning-caption: :warning:
|
||||
|
||||
A __ser__ialization/__de__serialization library for __Simplified JSON__, specifically, the Bitsquid/Stingray flavor.
|
69
README.md
Normal file
69
README.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# serde_sjson
|
||||
|
||||
A **ser**ialization/**de**serialization library for Simplified JSON,
|
||||
the Bitsquid/Stingray flavor of JSON.
|
||||
|
||||
## Usage
|
||||
|
||||
### Serializing
|
||||
|
||||
```rust
|
||||
use serde::Serialize;
|
||||
use serde_sjson::Result;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: u8,
|
||||
friends: Vec<String>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let data = Person {
|
||||
name: String::from("Marc"),
|
||||
age: 21,
|
||||
friends: vec![String::from("Jessica"), String::from("Paul")],
|
||||
};
|
||||
|
||||
let s = serde_sjson::to_string(&data)?;
|
||||
|
||||
println!("{}", s);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Deserializing
|
||||
|
||||
```rust
|
||||
use serde::Deserialize;
|
||||
use serde_sjson::Result;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Person {
|
||||
name: String,
|
||||
age: u8,
|
||||
friends: Vec<String>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let sjson = r#"
|
||||
name = Marc
|
||||
age = 21
|
||||
friends = [
|
||||
Jessica
|
||||
Paul
|
||||
]"#;
|
||||
|
||||
let data: Person = serde_sjson::from_str(sjson)?;
|
||||
|
||||
println!(
|
||||
"{} is {} years old and has {} friends.",
|
||||
data.name,
|
||||
data.age,
|
||||
data.friends.len()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
3
README.tpl
Normal file
3
README.tpl
Normal file
|
@ -0,0 +1,3 @@
|
|||
# {{crate}}
|
||||
|
||||
{{readme}}
|
5
release.toml
Normal file
5
release.toml
Normal file
|
@ -0,0 +1,5 @@
|
|||
pre-release-replacements = [
|
||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}"},
|
||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}"},
|
||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n\n## [Unreleased] - ReleaseDate", exactly=1},
|
||||
]
|
80
src/de.rs
80
src/de.rs
|
@ -5,6 +5,7 @@ use serde::Deserialize;
|
|||
use crate::error::{Error, ErrorCode, Result};
|
||||
use crate::parser::*;
|
||||
|
||||
/// A container for deserializing Rust values from SJSON.
|
||||
pub struct Deserializer<'de> {
|
||||
input: Span<'de>,
|
||||
is_top_level: bool,
|
||||
|
@ -12,7 +13,7 @@ pub struct Deserializer<'de> {
|
|||
|
||||
impl<'de> Deserializer<'de> {
|
||||
#![allow(clippy::should_implement_trait)]
|
||||
pub fn from_str(input: &'de str) -> Self {
|
||||
pub(crate) fn from_str(input: &'de str) -> Self {
|
||||
Self {
|
||||
input: Span::from(input),
|
||||
is_top_level: true,
|
||||
|
@ -53,8 +54,20 @@ impl<'de> Deserializer<'de> {
|
|||
Some(self.input.fragment().to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
fn error_with_token(&self, code: ErrorCode, token: Token) -> Error {
|
||||
Error::with_token(
|
||||
code,
|
||||
self.input.location_line(),
|
||||
self.input.get_utf8_column(),
|
||||
Some(self.input.fragment().to_string()),
|
||||
token,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes an SJSON string to a Rust value.
|
||||
#[inline]
|
||||
pub fn from_str<'a, T>(input: &'a str) -> Result<T>
|
||||
where
|
||||
T: Deserialize<'a>,
|
||||
|
@ -79,13 +92,15 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut Deserializer<'de> {
|
|||
return Err(self.error(ErrorCode::ExpectedTopLevelObject));
|
||||
}
|
||||
|
||||
match self.next_token()? {
|
||||
Token::Boolean(val) => visitor.visit_bool::<Self::Error>(val),
|
||||
Token::Float(val) => visitor.visit_f64(val),
|
||||
Token::Integer(val) => visitor.visit_i64(val),
|
||||
Token::Null => visitor.visit_unit(),
|
||||
Token::String(val) => visitor.visit_str(&val),
|
||||
_ => Err(self.error(ErrorCode::ExpectedValue)),
|
||||
match self.peek_token()? {
|
||||
Token::Boolean(_) => self.deserialize_bool(visitor),
|
||||
Token::Float(_) => self.deserialize_f64(visitor),
|
||||
Token::Integer(_) => self.deserialize_i64(visitor),
|
||||
Token::Null => self.deserialize_unit(visitor),
|
||||
Token::String(_) => self.deserialize_str(visitor),
|
||||
Token::ArrayStart => self.deserialize_seq(visitor),
|
||||
Token::ObjectStart => self.deserialize_map(visitor),
|
||||
token => Err(self.error_with_token(ErrorCode::ExpectedValue, token)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -731,4 +746,53 @@ render_config = "core/rendering/renderer"
|
|||
let actual = from_str::<()>(json);
|
||||
assert_eq!(actual, Err(err));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_array() {
|
||||
#[derive(Debug, Default, serde::Deserialize, PartialEq)]
|
||||
struct Data {
|
||||
array: Vec<String>,
|
||||
}
|
||||
|
||||
let expected = Data {
|
||||
array: vec![String::from("foo")],
|
||||
};
|
||||
|
||||
let sjson = r#"
|
||||
array = [
|
||||
"foo"
|
||||
]
|
||||
"#;
|
||||
assert_ok!(Data, expected, sjson);
|
||||
}
|
||||
|
||||
// Regression test for #1 (https://git.sclu1034.dev/lucas/serde_sjson/issues/1)
|
||||
#[test]
|
||||
fn deserialize_dtmt_config() {
|
||||
#[derive(Debug, Default, serde::Deserialize, PartialEq)]
|
||||
struct DtmtConfig {
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
description: String,
|
||||
version: Option<String>,
|
||||
}
|
||||
|
||||
let sjson = r#"
|
||||
name = "test-mod"
|
||||
description = "A dummy project to test things with"
|
||||
version = "0.1.0"
|
||||
|
||||
packages = [
|
||||
"packages/test-mod"
|
||||
]
|
||||
"#;
|
||||
|
||||
let expected = DtmtConfig {
|
||||
name: String::from("test-mod"),
|
||||
description: String::from("A dummy project to test things with"),
|
||||
version: Some(String::from("0.1.0")),
|
||||
};
|
||||
|
||||
assert_ok!(DtmtConfig, expected, sjson);
|
||||
}
|
||||
}
|
||||
|
|
39
src/error.rs
39
src/error.rs
|
@ -1,7 +1,12 @@
|
|||
use std::fmt;
|
||||
use std::{fmt, io};
|
||||
|
||||
use crate::parser::Token;
|
||||
|
||||
/// An alias for a `Result` with `serde_sjson::Error`.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// A type encapsulating the different errors that might occurr
|
||||
/// during serialization or deserialization.
|
||||
#[derive(PartialEq)]
|
||||
pub struct Error {
|
||||
inner: Box<ErrorImpl>,
|
||||
|
@ -13,6 +18,7 @@ struct ErrorImpl {
|
|||
line: u32,
|
||||
column: usize,
|
||||
fragment: Option<String>,
|
||||
token: Option<Token>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
|
@ -35,6 +41,7 @@ pub(crate) enum ErrorCode {
|
|||
ExpectedTopLevelObject,
|
||||
ExpectedValue,
|
||||
TrailingCharacters,
|
||||
NonFiniteFloat,
|
||||
}
|
||||
|
||||
impl fmt::Display for ErrorCode {
|
||||
|
@ -61,6 +68,7 @@ impl fmt::Display for ErrorCode {
|
|||
ErrorCode::ExpectedTopLevelObject => f.write_str("expected object at the top level"),
|
||||
ErrorCode::ExpectedValue => f.write_str("expected a value"),
|
||||
ErrorCode::TrailingCharacters => f.write_str("unexpected trailing characters"),
|
||||
ErrorCode::NonFiniteFloat => f.write_str("got infinite floating point number"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,11 +97,12 @@ impl fmt::Debug for Error {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Error({:?}, line: {}, column: {}, fragment: {:?})",
|
||||
"Error({:?}, line: {}, column: {}, fragment: {:?}, token: {:?})",
|
||||
self.inner.code.to_string(),
|
||||
self.inner.line,
|
||||
self.inner.column,
|
||||
self.inner.fragment,
|
||||
self.inner.token,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +117,7 @@ impl serde::de::Error for Error {
|
|||
line: 0,
|
||||
column: 0,
|
||||
fragment: None,
|
||||
token: None,
|
||||
});
|
||||
Self { inner }
|
||||
}
|
||||
|
@ -123,6 +133,7 @@ impl serde::ser::Error for Error {
|
|||
line: 0,
|
||||
column: 0,
|
||||
fragment: None,
|
||||
token: None,
|
||||
});
|
||||
Self { inner }
|
||||
}
|
||||
|
@ -138,7 +149,31 @@ impl Error {
|
|||
line,
|
||||
column,
|
||||
fragment,
|
||||
token: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
pub(crate) fn with_token(
|
||||
code: ErrorCode,
|
||||
line: u32,
|
||||
column: usize,
|
||||
fragment: Option<String>,
|
||||
token: Token,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Box::new(ErrorImpl {
|
||||
code,
|
||||
line,
|
||||
column,
|
||||
fragment,
|
||||
token: Some(token),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Self::new(ErrorCode::Message(format!("{}", err)), 0, 0, None)
|
||||
}
|
||||
}
|
||||
|
|
70
src/lib.rs
70
src/lib.rs
|
@ -1,3 +1,71 @@
|
|||
//! A **ser**ialization/**de**serialization library for Simplified JSON,
|
||||
//! the Bitsquid/Stingray flavor of JSON.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! ## Serializing
|
||||
//!
|
||||
//! ```
|
||||
//! use serde::Serialize;
|
||||
//! use serde_sjson::Result;
|
||||
//!
|
||||
//! #[derive(Serialize)]
|
||||
//! struct Person {
|
||||
//! name: String,
|
||||
//! age: u8,
|
||||
//! friends: Vec<String>,
|
||||
//! }
|
||||
//!
|
||||
//! fn main() -> Result<()> {
|
||||
//! let data = Person {
|
||||
//! name: String::from("Marc"),
|
||||
//! age: 21,
|
||||
//! friends: vec![String::from("Jessica"), String::from("Paul")],
|
||||
//! };
|
||||
//!
|
||||
//! let s = serde_sjson::to_string(&data)?;
|
||||
//!
|
||||
//! println!("{}", s);
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Deserializing
|
||||
//!
|
||||
//! ```
|
||||
//! use serde::Deserialize;
|
||||
//! use serde_sjson::Result;
|
||||
//!
|
||||
//! #[derive(Deserialize)]
|
||||
//! struct Person {
|
||||
//! name: String,
|
||||
//! age: u8,
|
||||
//! friends: Vec<String>,
|
||||
//! }
|
||||
//!
|
||||
//! fn main() -> Result<()> {
|
||||
//! let sjson = r#"
|
||||
//! name = Marc
|
||||
//! age = 21
|
||||
//! friends = [
|
||||
//! Jessica
|
||||
//! Paul
|
||||
//! ]"#;
|
||||
//!
|
||||
//! let data: Person = serde_sjson::from_str(sjson)?;
|
||||
//!
|
||||
//! println!(
|
||||
//! "{} is {} years old and has {} friends.",
|
||||
//! data.name,
|
||||
//! data.age,
|
||||
//! data.friends.len()
|
||||
//! );
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
mod de;
|
||||
mod error;
|
||||
mod parser;
|
||||
|
@ -5,4 +73,4 @@ mod ser;
|
|||
|
||||
pub use de::{from_str, Deserializer};
|
||||
pub use error::{Error, Result};
|
||||
pub use ser::{to_string, Serializer};
|
||||
pub use ser::{to_string, to_vec, to_writer, Serializer};
|
||||
|
|
201
src/parser.rs
201
src/parser.rs
|
@ -1,13 +1,11 @@
|
|||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{escaped, tag, take_until};
|
||||
use nom::character::complete::{
|
||||
alpha1, alphanumeric1, char, digit1, none_of, not_line_ending, one_of,
|
||||
};
|
||||
use nom::bytes::complete::{tag, take_until};
|
||||
use nom::character::complete::{char, digit1, none_of, not_line_ending, one_of};
|
||||
use nom::combinator::{cut, eof, map, map_res, opt, recognize, value};
|
||||
use nom::multi::{many0_count, many1_count};
|
||||
use nom::multi::many1_count;
|
||||
use nom::number::complete::double;
|
||||
use nom::sequence::{delimited, pair, preceded, terminated, tuple};
|
||||
use nom::IResult;
|
||||
use nom::sequence::{delimited, preceded, terminated, tuple};
|
||||
use nom::{IResult, Slice};
|
||||
use nom_locate::LocatedSpan;
|
||||
|
||||
pub(crate) type Span<'a> = LocatedSpan<&'a str>;
|
||||
|
@ -41,7 +39,9 @@ fn null(input: Span) -> IResult<Span, ()> {
|
|||
}
|
||||
|
||||
fn separator(input: Span) -> IResult<Span, &str> {
|
||||
map(alt((tag(","), tag("\n"))), |val: Span| *val.fragment())(input)
|
||||
map(alt((tag(","), tag("\n"), tag("\r\n"))), |val: Span| {
|
||||
*val.fragment()
|
||||
})(input)
|
||||
}
|
||||
|
||||
fn bool(input: Span) -> IResult<Span, bool> {
|
||||
|
@ -59,22 +59,48 @@ fn float(input: Span) -> IResult<Span, f64> {
|
|||
}
|
||||
|
||||
fn identifier(input: Span) -> IResult<Span, &str> {
|
||||
let leading = alt((alpha1, tag("_")));
|
||||
let trailing = many0_count(alt((alphanumeric1, tag("_"))));
|
||||
let ident = pair(leading, trailing);
|
||||
map(recognize(many1_count(none_of("\" \t\n=:"))), |val: Span| {
|
||||
*val.fragment()
|
||||
})(input)
|
||||
}
|
||||
|
||||
map(recognize(ident), |val: Span| *val.fragment())(input)
|
||||
fn literal_string(input: Span) -> IResult<Span, &str> {
|
||||
map(
|
||||
delimited(tag("\"\"\""), take_until("\"\"\""), tag("\"\"\"")),
|
||||
|val: Span| *val.fragment(),
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn string_content(input: Span) -> IResult<Span, &str> {
|
||||
// TODO: Handle Unicode escapes
|
||||
map(
|
||||
alt((
|
||||
escaped(none_of("\n\\\""), '\\', one_of(r#""rtn\"#)),
|
||||
tag(""),
|
||||
)),
|
||||
|val: Span| *val.fragment(),
|
||||
)(input)
|
||||
let buf = input.fragment();
|
||||
let mut escaped = false;
|
||||
let mut i = 0;
|
||||
|
||||
for (j, ch) in buf.char_indices() {
|
||||
i = j;
|
||||
match ch {
|
||||
'\\' if !escaped => {
|
||||
escaped = true;
|
||||
}
|
||||
'\n' if !escaped => {
|
||||
let err = nom::error::Error {
|
||||
input: input.slice(j..),
|
||||
code: nom::error::ErrorKind::Char,
|
||||
};
|
||||
return Err(nom::Err::Error(err));
|
||||
}
|
||||
'"' if !escaped => {
|
||||
return Ok((input.slice(j..), &buf[0..j]));
|
||||
}
|
||||
_ => escaped = false,
|
||||
}
|
||||
}
|
||||
|
||||
let err = nom::error::Error {
|
||||
input: input.slice((i + 1)..),
|
||||
code: nom::error::ErrorKind::Char,
|
||||
};
|
||||
Err(nom::Err::Failure(err))
|
||||
}
|
||||
|
||||
fn delimited_string(input: Span) -> IResult<Span, &str> {
|
||||
|
@ -82,7 +108,7 @@ fn delimited_string(input: Span) -> IResult<Span, &str> {
|
|||
}
|
||||
|
||||
fn string(input: Span) -> IResult<Span, &str> {
|
||||
alt((identifier, delimited_string))(input)
|
||||
alt((identifier, literal_string, delimited_string))(input)
|
||||
}
|
||||
|
||||
fn line_comment(input: Span) -> IResult<Span, &str> {
|
||||
|
@ -203,6 +229,32 @@ mod test {
|
|||
}};
|
||||
}
|
||||
|
||||
fn check_parse_result<S: AsRef<str>, T: AsRef<[Token]>>(input: S, tokens: T) {
|
||||
let tokens = tokens.as_ref();
|
||||
let mut remaining = Span::from(input.as_ref());
|
||||
let mut i = 0;
|
||||
|
||||
loop {
|
||||
if remaining.fragment().is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let (span, token) =
|
||||
super::parse_next_token(remaining).expect("failed to parse next token");
|
||||
|
||||
assert_eq!(Some(&token), tokens.get(i));
|
||||
|
||||
remaining = span;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
tokens.len(),
|
||||
i,
|
||||
"tokens to check against were not exhausted"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_optional() {
|
||||
assert_ok!("\n", whitespace, "", '\n');
|
||||
|
@ -250,10 +302,14 @@ mod test {
|
|||
assert_ok!("foo_bar", identifier, "", "foo_bar");
|
||||
assert_ok!("_foo", identifier, "", "_foo");
|
||||
assert_ok!("foo bar", identifier, " bar", "foo");
|
||||
assert_ok!("123", identifier, "", "123");
|
||||
assert_ok!("1foo", identifier, "", "1foo");
|
||||
assert_ok!("foo-bar", identifier, "", "foo-bar");
|
||||
assert_ok!("foo/bar", identifier, "", "foo/bar");
|
||||
assert_ok!("foo\"", identifier, "\"", "foo");
|
||||
|
||||
assert_err!("123", identifier, ErrorKind::Tag);
|
||||
assert_err!("1foo", identifier, ErrorKind::Tag);
|
||||
assert_err!("\"foo\"", identifier, ErrorKind::Tag);
|
||||
assert_err!("\"foo", identifier, ErrorKind::Many1Count);
|
||||
assert_err!("\"foo\"", identifier, ErrorKind::Many1Count);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -265,6 +321,8 @@ mod test {
|
|||
assert_ok!(r#""foo123""#, delimited_string, "", "foo123");
|
||||
assert_ok!(r#""123foo""#, delimited_string, "", "123foo");
|
||||
assert_ok!(r#""foo\"bar""#, delimited_string, "", "foo\\\"bar");
|
||||
assert_ok!(r#""foo\\bar""#, delimited_string, "", "foo\\\\bar");
|
||||
assert_ok!(r#""foo/bar""#, delimited_string, "", "foo/bar");
|
||||
|
||||
assert_err!("foo\"", delimited_string, ErrorKind::Char);
|
||||
|
||||
|
@ -291,6 +349,41 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_literal_string() {
|
||||
assert_ok!(r#""""""""#, literal_string, "", "");
|
||||
assert_ok!(r#""""foo""""#, literal_string, "", "foo");
|
||||
assert_ok!(r#""""foo"""""#, literal_string, "\"", "foo");
|
||||
assert_ok!(r#"""""foo""""#, literal_string, "", "\"foo");
|
||||
assert_ok!(r#""""\n""""#, literal_string, "", "\\n");
|
||||
|
||||
{
|
||||
let raw = r#"
|
||||
This is a lengthy description!
|
||||
|
||||
It contains line breaks.
|
||||
|
||||
Escape sequences, like \n and \t, are parsed literally.
|
||||
"Quoted strings are fine", so are two sucessive quotes: "".
|
||||
"#;
|
||||
|
||||
let input = format!(r#""""{}""""#, raw);
|
||||
|
||||
assert_ok!(input.as_str(), literal_string, "", raw);
|
||||
}
|
||||
|
||||
{
|
||||
let input = Span::from(r#"""""""#);
|
||||
assert_eq!(
|
||||
literal_string(input),
|
||||
Err(Err::Error(Error::new(
|
||||
unsafe { Span::new_from_raw_offset(3, 1, "\"\"", ()) },
|
||||
ErrorKind::TakeUntil
|
||||
)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_line_comment() {
|
||||
assert_ok!("// foo", line_comment, "", " foo");
|
||||
|
@ -302,4 +395,64 @@ mod test {
|
|||
assert_ok!("/* foo */", block_comment, "", " foo ");
|
||||
assert_ok!("/*\n\tfoo\nbar\n*/", block_comment, "", "\n\tfoo\nbar\n");
|
||||
}
|
||||
|
||||
// Regression test for #1 (https://git.sclu1034.dev/lucas/serde_sjson/issues/1)
|
||||
#[test]
|
||||
fn parse_dtmt_config() {
|
||||
let sjson = r#"
|
||||
name = "test-mod"
|
||||
description = "A dummy project to test things with"
|
||||
version = "0.1.0"
|
||||
|
||||
packages = [
|
||||
"packages/test-mod"
|
||||
]
|
||||
"#;
|
||||
|
||||
check_parse_result(
|
||||
sjson,
|
||||
[
|
||||
Token::String(String::from("name")),
|
||||
Token::Equals,
|
||||
Token::String(String::from("test-mod")),
|
||||
Token::String(String::from("description")),
|
||||
Token::Equals,
|
||||
Token::String(String::from("A dummy project to test things with")),
|
||||
Token::String(String::from("version")),
|
||||
Token::Equals,
|
||||
Token::String(String::from("0.1.0")),
|
||||
Token::String(String::from("packages")),
|
||||
Token::Equals,
|
||||
Token::ArrayStart,
|
||||
Token::String(String::from("packages/test-mod")),
|
||||
Token::ArrayEnd,
|
||||
Token::Eof,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Regression test for #2
|
||||
#[test]
|
||||
fn parse_windows_path() {
|
||||
let text = "C:\\Users\\public\\test.txt";
|
||||
let sjson = format!(r#""{}""#, text);
|
||||
check_parse_result(sjson, [Token::String(String::from(text))]);
|
||||
}
|
||||
|
||||
// Regression test for #10
|
||||
#[test]
|
||||
fn parse_crlf_separator() {
|
||||
let sjson = "foo = 1\r\nbar = 2";
|
||||
check_parse_result(
|
||||
sjson,
|
||||
[
|
||||
Token::String(String::from("foo")),
|
||||
Token::Equals,
|
||||
Token::Integer(1),
|
||||
Token::String(String::from("bar")),
|
||||
Token::Equals,
|
||||
Token::Integer(2),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
312
src/ser.rs
312
src/ser.rs
|
@ -1,36 +1,80 @@
|
|||
use std::io;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::error::{Error, ErrorCode, Result};
|
||||
|
||||
// TODO: Make configurable
|
||||
const INDENT: &str = " ";
|
||||
const INDENT: [u8; 2] = [0x20, 0x20];
|
||||
|
||||
pub struct Serializer {
|
||||
/// A container for serializing Rust values into SJSON.
|
||||
pub struct Serializer<W> {
|
||||
// The current indentation level
|
||||
level: usize,
|
||||
// The output string
|
||||
output: String,
|
||||
writer: W,
|
||||
}
|
||||
|
||||
/// Serializes a value into a generic `io::Write`.
|
||||
#[inline]
|
||||
pub fn to_writer<T, W>(writer: &mut W, value: &T) -> Result<()>
|
||||
where
|
||||
W: io::Write,
|
||||
T: Serialize,
|
||||
{
|
||||
let mut serializer = Serializer::new(writer);
|
||||
value.serialize(&mut serializer)
|
||||
}
|
||||
|
||||
/// Serializes a value into a byte vector.
|
||||
#[inline]
|
||||
pub fn to_vec<T>(value: &T) -> Result<Vec<u8>>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let mut vec = Vec::with_capacity(128);
|
||||
to_writer(&mut vec, value)?;
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
/// Serializes a value into a string.
|
||||
#[inline]
|
||||
pub fn to_string<T>(value: &T) -> Result<String>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let mut serializer = Serializer {
|
||||
level: 0,
|
||||
output: String::new(),
|
||||
let vec = to_vec(value)?;
|
||||
let string = if cfg!(debug_assertions) {
|
||||
String::from_utf8(vec).expect("We do not emit invalid UTF-8")
|
||||
} else {
|
||||
unsafe { String::from_utf8_unchecked(vec) }
|
||||
};
|
||||
value.serialize(&mut serializer)?;
|
||||
Ok(serializer.output)
|
||||
Ok(string)
|
||||
}
|
||||
|
||||
impl Serializer {
|
||||
fn add_indent(&mut self) {
|
||||
for _ in 0..self.level.saturating_sub(1) {
|
||||
self.output += INDENT;
|
||||
}
|
||||
impl<W> Serializer<W>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
/// Creates a new `Serializer`.
|
||||
pub fn new(writer: W) -> Self {
|
||||
Self { level: 0, writer }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write(&mut self, bytes: impl AsRef<[u8]>) -> Result<()> {
|
||||
self.writer.write_all(bytes.as_ref()).map_err(Error::from)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_indent(&mut self) -> Result<()> {
|
||||
for _ in 0..self.level.saturating_sub(1) {
|
||||
self.write(INDENT)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ensure_top_level_struct(&self) -> Result<()> {
|
||||
if self.level == 0 {
|
||||
return Err(Error::new(ErrorCode::ExpectedTopLevelObject, 0, 0, None));
|
||||
|
@ -40,7 +84,10 @@ impl Serializer {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::ser::Serializer for &'a mut Serializer {
|
||||
impl<'a, W> serde::ser::Serializer for &'a mut Serializer<W>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
|
@ -54,7 +101,7 @@ impl<'a> serde::ser::Serializer for &'a mut Serializer {
|
|||
|
||||
fn serialize_bool(self, v: bool) -> Result<Self::Ok> {
|
||||
self.ensure_top_level_struct()?;
|
||||
self.output += if v { "true" } else { "false" };
|
||||
self.write(if v { "true" } else { "false" })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -72,8 +119,7 @@ impl<'a> serde::ser::Serializer for &'a mut Serializer {
|
|||
|
||||
fn serialize_i64(self, v: i64) -> Result<Self::Ok> {
|
||||
self.ensure_top_level_struct()?;
|
||||
self.output += &v.to_string();
|
||||
Ok(())
|
||||
self.serialize_str(&format!("{}", v))
|
||||
}
|
||||
|
||||
fn serialize_u8(self, v: u8) -> Result<Self::Ok> {
|
||||
|
@ -90,90 +136,80 @@ impl<'a> serde::ser::Serializer for &'a mut Serializer {
|
|||
|
||||
fn serialize_u64(self, v: u64) -> Result<Self::Ok> {
|
||||
self.ensure_top_level_struct()?;
|
||||
self.output += &v.to_string();
|
||||
Ok(())
|
||||
self.serialize_str(&format!("{}", v))
|
||||
}
|
||||
|
||||
fn serialize_f32(self, v: f32) -> Result<Self::Ok> {
|
||||
if v.is_finite() {
|
||||
self.serialize_f64(v.into())
|
||||
} else {
|
||||
self.ensure_top_level_struct()?;
|
||||
self.output += "null";
|
||||
Ok(())
|
||||
}
|
||||
self.serialize_f64(v.into())
|
||||
}
|
||||
|
||||
fn serialize_f64(self, v: f64) -> Result<Self::Ok> {
|
||||
self.ensure_top_level_struct()?;
|
||||
if v.is_finite() {
|
||||
self.output += &v.to_string();
|
||||
} else {
|
||||
self.output += "null";
|
||||
if !v.is_finite() {
|
||||
return Err(Error::new(ErrorCode::NonFiniteFloat, 0, 0, None));
|
||||
}
|
||||
Ok(())
|
||||
|
||||
self.serialize_str(&format!("{}", v))
|
||||
}
|
||||
|
||||
fn serialize_char(self, v: char) -> Result<Self::Ok> {
|
||||
let mut buf = [0; 4];
|
||||
self.serialize_str(v.encode_utf8(&mut buf))
|
||||
self.serialize_bytes(v.encode_utf8(&mut buf).as_bytes())
|
||||
}
|
||||
|
||||
fn serialize_str(self, v: &str) -> Result<Self::Ok> {
|
||||
self.ensure_top_level_struct()?;
|
||||
let needs_escapes =
|
||||
v.is_empty() || v.contains([' ', '\n', '\r', '\t', '=', '\'', '"', '\\', '/']);
|
||||
if needs_escapes {
|
||||
self.output += "\"";
|
||||
|
||||
let len = v.len();
|
||||
let chars = v.chars();
|
||||
let mut start = 0;
|
||||
let needs_quotes =
|
||||
v.is_empty() || v.contains([' ', '\n', '\r', '\t', '=', '\'', '"', '\\', ':']);
|
||||
|
||||
for (i, c) in chars.enumerate() {
|
||||
if ('\x20'..='\x7e').contains(&c)
|
||||
&& !['\t', '\n', '\r', '\"', '\\', '/'].contains(&c)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
self.output += &v[start..i];
|
||||
self.output.push('\\');
|
||||
if needs_quotes {
|
||||
self.write(b"\"")?;
|
||||
|
||||
// Since we've added a layer of quotes, we now need to escape
|
||||
// certain characters.
|
||||
for c in v.chars() {
|
||||
match c {
|
||||
'\t' => {
|
||||
self.output.push('t');
|
||||
self.write(b"\\")?;
|
||||
self.write(b"t")?;
|
||||
}
|
||||
'\n' => {
|
||||
self.output.push('n');
|
||||
self.write(b"\\")?;
|
||||
self.write(b"n")?;
|
||||
}
|
||||
'\r' => {
|
||||
self.output.push('r');
|
||||
self.write(b"\\")?;
|
||||
self.write(b"r")?;
|
||||
}
|
||||
'\x7f'.. => {
|
||||
self.output += &format!("u{:4x}", c as u32);
|
||||
'"' => {
|
||||
self.write(b"\\")?;
|
||||
self.write(b"\"")?;
|
||||
}
|
||||
'\\' => {
|
||||
self.write(b"\\")?;
|
||||
self.write(b"\\")?;
|
||||
}
|
||||
c => {
|
||||
self.output.push(c);
|
||||
self.serialize_char(c)?;
|
||||
}
|
||||
};
|
||||
|
||||
start = i + 1;
|
||||
}
|
||||
|
||||
if start < len {
|
||||
self.output += &v[start..];
|
||||
}
|
||||
|
||||
self.output += "\"";
|
||||
self.write(b"\"")?;
|
||||
} else {
|
||||
self.output += v;
|
||||
self.write(v.as_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok> {
|
||||
todo!()
|
||||
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
|
||||
self.ensure_top_level_struct()?;
|
||||
// For now we assume that the byte array contains
|
||||
// valid SJSON.
|
||||
// TODO: Turn this into an actual array of encoded bytes.
|
||||
self.write(v)
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<Self::Ok> {
|
||||
|
@ -193,8 +229,7 @@ impl<'a> serde::ser::Serializer for &'a mut Serializer {
|
|||
|
||||
fn serialize_unit(self) -> Result<Self::Ok> {
|
||||
self.ensure_top_level_struct()?;
|
||||
self.output += "null";
|
||||
Ok(())
|
||||
self.write(b"null")
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok> {
|
||||
|
@ -232,19 +267,18 @@ impl<'a> serde::ser::Serializer for &'a mut Serializer {
|
|||
{
|
||||
self.ensure_top_level_struct()?;
|
||||
|
||||
self.output += "{ ";
|
||||
self.write(b"{ ")?;
|
||||
variant.serialize(&mut *self)?;
|
||||
self.output += " = ";
|
||||
self.write(b" = ")?;
|
||||
value.serialize(&mut *self)?;
|
||||
self.output += " }\n";
|
||||
Ok(())
|
||||
self.write(b" }")
|
||||
}
|
||||
|
||||
// Serialize the start of a sequence.
|
||||
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
|
||||
self.ensure_top_level_struct()?;
|
||||
|
||||
self.output += "[\n";
|
||||
self.write(b"[\n")?;
|
||||
self.level += 1;
|
||||
Ok(self)
|
||||
}
|
||||
|
@ -275,7 +309,7 @@ impl<'a> serde::ser::Serializer for &'a mut Serializer {
|
|||
|
||||
variant.serialize(&mut *self)?;
|
||||
|
||||
self.output += " = [\n";
|
||||
self.write(b" = [\n")?;
|
||||
self.level += 1;
|
||||
|
||||
Ok(self)
|
||||
|
@ -283,7 +317,7 @@ impl<'a> serde::ser::Serializer for &'a mut Serializer {
|
|||
|
||||
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
|
||||
if self.level > 0 {
|
||||
self.output += "{\n";
|
||||
self.write(b"{\n")?;
|
||||
}
|
||||
self.level += 1;
|
||||
Ok(self)
|
||||
|
@ -305,7 +339,7 @@ impl<'a> serde::ser::Serializer for &'a mut Serializer {
|
|||
|
||||
variant.serialize(&mut *self)?;
|
||||
|
||||
self.output += " = {\n";
|
||||
self.write(b" = {\n")?;
|
||||
self.level += 1;
|
||||
|
||||
Ok(self)
|
||||
|
@ -319,7 +353,10 @@ impl<'a> serde::ser::Serializer for &'a mut Serializer {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::ser::SerializeSeq for &'a mut Serializer {
|
||||
impl<'a, W> serde::ser::SerializeSeq for &'a mut Serializer<W>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
|
@ -327,23 +364,22 @@ impl<'a> serde::ser::SerializeSeq for &'a mut Serializer {
|
|||
where
|
||||
T: Serialize,
|
||||
{
|
||||
self.add_indent();
|
||||
self.add_indent()?;
|
||||
value.serialize(&mut **self)?;
|
||||
if !self.output.ends_with('\n') {
|
||||
self.output += "\n";
|
||||
}
|
||||
Ok(())
|
||||
self.write(b"\n")
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
self.level -= 1;
|
||||
self.add_indent();
|
||||
self.output += "]\n";
|
||||
Ok(())
|
||||
self.add_indent()?;
|
||||
self.write(b"]")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::ser::SerializeTuple for &'a mut Serializer {
|
||||
impl<'a, W> serde::ser::SerializeTuple for &'a mut Serializer<W>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
|
@ -351,23 +387,22 @@ impl<'a> serde::ser::SerializeTuple for &'a mut Serializer {
|
|||
where
|
||||
T: Serialize,
|
||||
{
|
||||
self.add_indent();
|
||||
self.add_indent()?;
|
||||
value.serialize(&mut **self)?;
|
||||
if !self.output.ends_with('\n') {
|
||||
self.output += "\n";
|
||||
}
|
||||
Ok(())
|
||||
self.write(b"\n")
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
self.level -= 1;
|
||||
self.add_indent();
|
||||
self.output += "]\n";
|
||||
Ok(())
|
||||
self.add_indent()?;
|
||||
self.write(b"]")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::ser::SerializeTupleStruct for &'a mut Serializer {
|
||||
impl<'a, W> serde::ser::SerializeTupleStruct for &'a mut Serializer<W>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
|
@ -375,23 +410,22 @@ impl<'a> serde::ser::SerializeTupleStruct for &'a mut Serializer {
|
|||
where
|
||||
T: Serialize,
|
||||
{
|
||||
self.add_indent();
|
||||
self.add_indent()?;
|
||||
value.serialize(&mut **self)?;
|
||||
if !self.output.ends_with('\n') {
|
||||
self.output += "\n";
|
||||
}
|
||||
Ok(())
|
||||
self.write(b"\n")
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
self.level -= 1;
|
||||
self.add_indent();
|
||||
self.output += "]\n";
|
||||
Ok(())
|
||||
self.add_indent()?;
|
||||
self.write(b"]")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::ser::SerializeTupleVariant for &'a mut Serializer {
|
||||
impl<'a, W> serde::ser::SerializeTupleVariant for &'a mut Serializer<W>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
|
@ -399,28 +433,31 @@ impl<'a> serde::ser::SerializeTupleVariant for &'a mut Serializer {
|
|||
where
|
||||
T: Serialize,
|
||||
{
|
||||
self.add_indent();
|
||||
self.add_indent()?;
|
||||
value.serialize(&mut **self)?;
|
||||
if !self.output.ends_with('\n') {
|
||||
self.output += "\n";
|
||||
}
|
||||
Ok(())
|
||||
self.write(b"\n")
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
self.level -= 1;
|
||||
self.add_indent();
|
||||
self.output += "]\n";
|
||||
self.add_indent()?;
|
||||
self.write(b"]\n")?;
|
||||
|
||||
self.level -= 1;
|
||||
|
||||
if self.level > 0 {
|
||||
self.add_indent();
|
||||
self.output += "}\n";
|
||||
self.add_indent()?;
|
||||
self.write(b"}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::ser::SerializeMap for &'a mut Serializer {
|
||||
impl<'a, W> serde::ser::SerializeMap for &'a mut Serializer<W>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
|
@ -428,7 +465,7 @@ impl<'a> serde::ser::SerializeMap for &'a mut Serializer {
|
|||
where
|
||||
T: Serialize,
|
||||
{
|
||||
self.add_indent();
|
||||
self.add_indent()?;
|
||||
key.serialize(&mut **self)
|
||||
}
|
||||
|
||||
|
@ -439,25 +476,25 @@ impl<'a> serde::ser::SerializeMap for &'a mut Serializer {
|
|||
// It doesn't make a difference where the `=` is added. But doing it here
|
||||
// means `serialize_key` is only a call to a different function, which should
|
||||
// have greater optimization potential for the compiler.
|
||||
self.output += " = ";
|
||||
self.write(b" = ")?;
|
||||
value.serialize(&mut **self)?;
|
||||
if !self.output.ends_with('\n') {
|
||||
self.output += "\n";
|
||||
}
|
||||
Ok(())
|
||||
self.write(b"\n")
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
if self.level > 1 {
|
||||
self.level -= 1;
|
||||
self.add_indent();
|
||||
self.output += "}\n";
|
||||
self.add_indent()?;
|
||||
self.write(b"}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::ser::SerializeStruct for &'a mut Serializer {
|
||||
impl<'a, W> serde::ser::SerializeStruct for &'a mut Serializer<W>
|
||||
where
|
||||
W: io::Write,
|
||||
{
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
|
@ -465,29 +502,29 @@ impl<'a> serde::ser::SerializeStruct for &'a mut Serializer {
|
|||
where
|
||||
T: Serialize,
|
||||
{
|
||||
self.add_indent();
|
||||
self.add_indent()?;
|
||||
key.serialize(&mut **self)?;
|
||||
|
||||
self.output += " = ";
|
||||
self.write(b" = ")?;
|
||||
|
||||
value.serialize(&mut **self)?;
|
||||
if !self.output.ends_with('\n') {
|
||||
self.output += "\n";
|
||||
}
|
||||
Ok(())
|
||||
self.write(b"\n")
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
if self.level > 1 {
|
||||
self.level -= 1;
|
||||
self.add_indent();
|
||||
self.output += "}\n";
|
||||
self.add_indent()?;
|
||||
self.write(b"}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> serde::ser::SerializeStructVariant for &'a mut Serializer {
|
||||
impl<'a, W> serde::ser::SerializeStructVariant for &'a mut Serializer<W>
|
||||
where
|
||||
W: std::io::Write,
|
||||
{
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
|
@ -495,21 +532,18 @@ impl<'a> serde::ser::SerializeStructVariant for &'a mut Serializer {
|
|||
where
|
||||
T: Serialize,
|
||||
{
|
||||
self.add_indent();
|
||||
self.add_indent()?;
|
||||
key.serialize(&mut **self)?;
|
||||
self.output += " = ";
|
||||
self.write(b" = ")?;
|
||||
value.serialize(&mut **self)?;
|
||||
if !self.output.ends_with('\n') {
|
||||
self.output += "\n";
|
||||
}
|
||||
Ok(())
|
||||
self.write(b"\n")
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
if self.level > 0 {
|
||||
self.level -= 1;
|
||||
self.add_indent();
|
||||
self.output += "}\n";
|
||||
self.add_indent()?;
|
||||
self.write(b"}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ fn serialize_u64() {
|
|||
|
||||
let tests = [u64::MIN, 4, u64::MAX];
|
||||
for value in tests {
|
||||
let expected = format!("value = {}\n", value);
|
||||
let expected = format!("value = {value}\n");
|
||||
let value = Value { value };
|
||||
let actual = to_string(&value).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -38,7 +38,7 @@ fn serialize_i64() {
|
|||
|
||||
let tests = [i64::MIN, -6, 0, 4, i64::MAX];
|
||||
for value in tests {
|
||||
let expected = format!("value = {}\n", value);
|
||||
let expected = format!("value = {value}\n");
|
||||
let value = Value { value };
|
||||
let actual = to_string(&value).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -61,7 +61,7 @@ fn serialize_f64() {
|
|||
std::f64::consts::PI,
|
||||
];
|
||||
for value in tests {
|
||||
let expected = format!("value = {}\n", value);
|
||||
let expected = format!("value = {value}\n");
|
||||
let value = Value { value };
|
||||
let actual = to_string(&value).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -81,17 +81,14 @@ fn serialize_non_representable_floats() {
|
|||
}
|
||||
|
||||
let tests = [std::f64::NAN, std::f64::INFINITY, std::f64::NEG_INFINITY];
|
||||
let expected = String::from("value = null\n");
|
||||
for value in tests {
|
||||
let value = Value64 { value };
|
||||
let actual = to_string(&value).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
assert!(to_string(&value).is_err());
|
||||
}
|
||||
let tests = [std::f32::NAN, std::f32::INFINITY, std::f32::NEG_INFINITY];
|
||||
for value in tests {
|
||||
let value = Value32 { value };
|
||||
let actual = to_string(&value).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
assert!(to_string(&value).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +101,7 @@ fn serialize_bool() {
|
|||
|
||||
let tests = [true, false];
|
||||
for value in tests {
|
||||
let expected = format!("value = {}\n", value);
|
||||
let expected = format!("value = {value}\n");
|
||||
let value = Value { value };
|
||||
let actual = to_string(&value).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
@ -125,11 +122,18 @@ fn serialize_string() {
|
|||
("foo\nbar", "\"foo\\nbar\""),
|
||||
("foo\r\nbar", "\"foo\\r\\nbar\""),
|
||||
("foo\tbar", "\"foo\\tbar\""),
|
||||
("foo/bar", "\"foo\\/bar\""),
|
||||
("foo/bar", "foo/bar"),
|
||||
("foo\\bar", "\"foo\\\\bar\""),
|
||||
// Regression test for #7.
|
||||
("scripts/mods/test\\new", "\"scripts/mods/test\\\\new\""),
|
||||
// Regression test for #8.
|
||||
(
|
||||
"+002023-03-03T16:42:33.944311860Z",
|
||||
"\"+002023-03-03T16:42:33.944311860Z\"",
|
||||
),
|
||||
];
|
||||
for (value, expected) in tests {
|
||||
let expected = format!("value = {}\n", expected);
|
||||
let expected = format!("value = {expected}\n");
|
||||
let value = Value {
|
||||
value: value.to_string(),
|
||||
};
|
||||
|
@ -152,12 +156,12 @@ fn serialize_char() {
|
|||
('\t', "\"\\t\""),
|
||||
('\r', "\"\\r\""),
|
||||
('\\', "\"\\\\\""),
|
||||
('/', "\"\\/\""),
|
||||
('/', "/"),
|
||||
('\"', "\"\\\"\""),
|
||||
('\'', "\"'\""),
|
||||
];
|
||||
for (value, expected) in tests {
|
||||
let expected = format!("value = {}\n", expected);
|
||||
let expected = format!("value = {expected}\n");
|
||||
let value = Value {
|
||||
value: value.to_string(),
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue