1
Fork 0

Compare commits

..

No commits in common. "master" and "v0.2.1" have entirely different histories.

14 changed files with 258 additions and 766 deletions

26
CHANGELOG.adoc Normal file
View file

@ -0,0 +1,26 @@
= Changelog
:toc:
:toclevels: 1
:idprefix:
:idseparator: -
== [Unreleased]
== [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

View file

@ -1,69 +0,0 @@
# 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

View file

@ -1,27 +1,15 @@
[package] [package]
name = "serde_sjson" name = "serde_sjson"
version = "1.2.0" version = "0.2.1"
authors = ["Lucas Schwiderski"]
categories = ["encoding", "parser-implementations"]
description = "An SJSON serialization file format"
documentation = "https://docs.rs/serde_sjson"
edition = "2021" edition = "2021"
keywords = ["serde", "serialization", "sjson"] keywords = ["serde", "serialization", "sjson"]
license-file = "LICENSE" description = "An SJSON serialization file format"
repository = "https://github.com/sclu1034/serde_sjson" categories = ["encoding", "parser-implementations"]
exclude = [
".github/",
".ci/",
"Justfile"
]
[dependencies] [dependencies]
nom = "7" nom = "7.1.1"
nom_locate = "4.1" nom_locate = "4.0.0"
serde = { version = "1.0", default-features = false } serde = { version = "1.0.147", default-features = false }
[dev-dependencies] [dev-dependencies]
serde = { version = "1.0.194", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }
[badges]
maintenance = { status = "passively-maintained" }

View file

@ -1,26 +1,12 @@
project := "serde_sjson" project := "serde_sjson"
default := "run" default := "run"
build *ARGS:
cargo build {{ARGS}}
cargo readme > README.md
run *ARGS: run *ARGS:
cargo run -- {{ARGS}} cargo run -- {{ARGS}}
test *ARGS: test *ARGS:
cargo 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: coverage *ARGS:
RUSTFLAGS="-C instrument-coverage" cargo test --tests {{ARGS}} || true RUSTFLAGS="-C instrument-coverage" cargo test --tests {{ARGS}} || true
cargo profdata -- merge -sparse default*.profraw -o {{project}}.profdata cargo profdata -- merge -sparse default*.profraw -o {{project}}.profdata

13
README.adoc Normal file
View file

@ -0,0 +1,13 @@
= 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.

View file

@ -1,69 +0,0 @@
# 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(())
}
```

View file

@ -1,3 +0,0 @@
# {{crate}}
{{readme}}

View file

@ -1,5 +0,0 @@
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},
]

View file

@ -5,7 +5,6 @@ use serde::Deserialize;
use crate::error::{Error, ErrorCode, Result}; use crate::error::{Error, ErrorCode, Result};
use crate::parser::*; use crate::parser::*;
/// A container for deserializing Rust values from SJSON.
pub struct Deserializer<'de> { pub struct Deserializer<'de> {
input: Span<'de>, input: Span<'de>,
is_top_level: bool, is_top_level: bool,
@ -13,7 +12,7 @@ pub struct Deserializer<'de> {
impl<'de> Deserializer<'de> { impl<'de> Deserializer<'de> {
#![allow(clippy::should_implement_trait)] #![allow(clippy::should_implement_trait)]
pub(crate) fn from_str(input: &'de str) -> Self { pub fn from_str(input: &'de str) -> Self {
Self { Self {
input: Span::from(input), input: Span::from(input),
is_top_level: true, is_top_level: true,
@ -54,20 +53,8 @@ impl<'de> Deserializer<'de> {
Some(self.input.fragment().to_string()), 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> pub fn from_str<'a, T>(input: &'a str) -> Result<T>
where where
T: Deserialize<'a>, T: Deserialize<'a>,
@ -92,15 +79,13 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut Deserializer<'de> {
return Err(self.error(ErrorCode::ExpectedTopLevelObject)); return Err(self.error(ErrorCode::ExpectedTopLevelObject));
} }
match self.peek_token()? { match self.next_token()? {
Token::Boolean(_) => self.deserialize_bool(visitor), Token::Boolean(val) => visitor.visit_bool::<Self::Error>(val),
Token::Float(_) => self.deserialize_f64(visitor), Token::Float(val) => visitor.visit_f64(val),
Token::Integer(_) => self.deserialize_i64(visitor), Token::Integer(val) => visitor.visit_i64(val),
Token::Null => self.deserialize_unit(visitor), Token::Null => visitor.visit_unit(),
Token::String(_) => self.deserialize_str(visitor), Token::String(val) => visitor.visit_str(&val),
Token::ArrayStart => self.deserialize_seq(visitor), _ => Err(self.error(ErrorCode::ExpectedValue)),
Token::ObjectStart => self.deserialize_map(visitor),
token => Err(self.error_with_token(ErrorCode::ExpectedValue, token)),
} }
} }
@ -746,53 +731,4 @@ render_config = "core/rendering/renderer"
let actual = from_str::<()>(json); let actual = from_str::<()>(json);
assert_eq!(actual, Err(err)); 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);
}
} }

View file

@ -1,12 +1,7 @@
use std::{fmt, io}; use std::fmt;
use crate::parser::Token;
/// An alias for a `Result` with `serde_sjson::Error`.
pub type Result<T> = std::result::Result<T, 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)] #[derive(PartialEq)]
pub struct Error { pub struct Error {
inner: Box<ErrorImpl>, inner: Box<ErrorImpl>,
@ -18,7 +13,6 @@ struct ErrorImpl {
line: u32, line: u32,
column: usize, column: usize,
fragment: Option<String>, fragment: Option<String>,
token: Option<Token>,
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@ -41,7 +35,6 @@ pub(crate) enum ErrorCode {
ExpectedTopLevelObject, ExpectedTopLevelObject,
ExpectedValue, ExpectedValue,
TrailingCharacters, TrailingCharacters,
NonFiniteFloat,
} }
impl fmt::Display for ErrorCode { impl fmt::Display for ErrorCode {
@ -68,7 +61,6 @@ impl fmt::Display for ErrorCode {
ErrorCode::ExpectedTopLevelObject => f.write_str("expected object at the top level"), ErrorCode::ExpectedTopLevelObject => f.write_str("expected object at the top level"),
ErrorCode::ExpectedValue => f.write_str("expected a value"), ErrorCode::ExpectedValue => f.write_str("expected a value"),
ErrorCode::TrailingCharacters => f.write_str("unexpected trailing characters"), ErrorCode::TrailingCharacters => f.write_str("unexpected trailing characters"),
ErrorCode::NonFiniteFloat => f.write_str("got infinite floating point number"),
} }
} }
} }
@ -97,12 +89,11 @@ impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
"Error({:?}, line: {}, column: {}, fragment: {:?}, token: {:?})", "Error({:?}, line: {}, column: {}, fragment: {:?})",
self.inner.code.to_string(), self.inner.code.to_string(),
self.inner.line, self.inner.line,
self.inner.column, self.inner.column,
self.inner.fragment, self.inner.fragment,
self.inner.token,
) )
} }
} }
@ -117,7 +108,6 @@ impl serde::de::Error for Error {
line: 0, line: 0,
column: 0, column: 0,
fragment: None, fragment: None,
token: None,
}); });
Self { inner } Self { inner }
} }
@ -133,7 +123,6 @@ impl serde::ser::Error for Error {
line: 0, line: 0,
column: 0, column: 0,
fragment: None, fragment: None,
token: None,
}); });
Self { inner } Self { inner }
} }
@ -149,31 +138,7 @@ impl Error {
line, line,
column, column,
fragment, 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)
}
}

View file

@ -1,71 +1,3 @@
//! 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 de;
mod error; mod error;
mod parser; mod parser;
@ -73,4 +5,4 @@ mod ser;
pub use de::{from_str, Deserializer}; pub use de::{from_str, Deserializer};
pub use error::{Error, Result}; pub use error::{Error, Result};
pub use ser::{to_string, to_vec, to_writer, Serializer}; pub use ser::{to_string, Serializer};

View file

@ -1,11 +1,13 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::{tag, take_until}; use nom::bytes::complete::{escaped, tag, take_until};
use nom::character::complete::{char, digit1, none_of, not_line_ending, one_of}; use nom::character::complete::{
alpha1, alphanumeric1, char, digit1, none_of, not_line_ending, one_of,
};
use nom::combinator::{cut, eof, map, map_res, opt, recognize, value}; use nom::combinator::{cut, eof, map, map_res, opt, recognize, value};
use nom::multi::many1_count; use nom::multi::{many0_count, many1_count};
use nom::number::complete::double; use nom::number::complete::double;
use nom::sequence::{delimited, preceded, terminated, tuple}; use nom::sequence::{delimited, pair, preceded, terminated, tuple};
use nom::{IResult, Slice}; use nom::IResult;
use nom_locate::LocatedSpan; use nom_locate::LocatedSpan;
pub(crate) type Span<'a> = LocatedSpan<&'a str>; pub(crate) type Span<'a> = LocatedSpan<&'a str>;
@ -39,9 +41,7 @@ fn null(input: Span) -> IResult<Span, ()> {
} }
fn separator(input: Span) -> IResult<Span, &str> { fn separator(input: Span) -> IResult<Span, &str> {
map(alt((tag(","), tag("\n"), tag("\r\n"))), |val: Span| { map(alt((tag(","), tag("\n"))), |val: Span| *val.fragment())(input)
*val.fragment()
})(input)
} }
fn bool(input: Span) -> IResult<Span, bool> { fn bool(input: Span) -> IResult<Span, bool> {
@ -59,48 +59,22 @@ fn float(input: Span) -> IResult<Span, f64> {
} }
fn identifier(input: Span) -> IResult<Span, &str> { fn identifier(input: Span) -> IResult<Span, &str> {
map(recognize(many1_count(none_of("\" \t\n=:"))), |val: Span| { let leading = alt((alpha1, tag("_")));
*val.fragment() let trailing = many0_count(alt((alphanumeric1, tag("_"))));
})(input) let ident = pair(leading, trailing);
}
fn literal_string(input: Span) -> IResult<Span, &str> { map(recognize(ident), |val: Span| *val.fragment())(input)
map(
delimited(tag("\"\"\""), take_until("\"\"\""), tag("\"\"\"")),
|val: Span| *val.fragment(),
)(input)
} }
fn string_content(input: Span) -> IResult<Span, &str> { fn string_content(input: Span) -> IResult<Span, &str> {
let buf = input.fragment(); // TODO: Handle Unicode escapes
let mut escaped = false; map(
let mut i = 0; alt((
escaped(none_of("\n\\\""), '\\', one_of(r#""rtn\"#)),
for (j, ch) in buf.char_indices() { tag(""),
i = j; )),
match ch { |val: Span| *val.fragment(),
'\\' if !escaped => { )(input)
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> { fn delimited_string(input: Span) -> IResult<Span, &str> {
@ -108,7 +82,7 @@ fn delimited_string(input: Span) -> IResult<Span, &str> {
} }
fn string(input: Span) -> IResult<Span, &str> { fn string(input: Span) -> IResult<Span, &str> {
alt((identifier, literal_string, delimited_string))(input) alt((identifier, delimited_string))(input)
} }
fn line_comment(input: Span) -> IResult<Span, &str> { fn line_comment(input: Span) -> IResult<Span, &str> {
@ -229,32 +203,6 @@ 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] #[test]
fn parse_optional() { fn parse_optional() {
assert_ok!("\n", whitespace, "", '\n'); assert_ok!("\n", whitespace, "", '\n');
@ -302,14 +250,10 @@ mod test {
assert_ok!("foo_bar", identifier, "", "foo_bar"); assert_ok!("foo_bar", identifier, "", "foo_bar");
assert_ok!("_foo", identifier, "", "_foo"); assert_ok!("_foo", identifier, "", "_foo");
assert_ok!("foo bar", identifier, " bar", "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!("\"foo", identifier, ErrorKind::Many1Count); assert_err!("123", identifier, ErrorKind::Tag);
assert_err!("\"foo\"", identifier, ErrorKind::Many1Count); assert_err!("1foo", identifier, ErrorKind::Tag);
assert_err!("\"foo\"", identifier, ErrorKind::Tag);
} }
#[test] #[test]
@ -321,8 +265,6 @@ mod test {
assert_ok!(r#""foo123""#, delimited_string, "", "foo123"); assert_ok!(r#""foo123""#, delimited_string, "", "foo123");
assert_ok!(r#""123foo""#, delimited_string, "", "123foo"); 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_ok!(r#""foo/bar""#, delimited_string, "", "foo/bar");
assert_err!("foo\"", delimited_string, ErrorKind::Char); assert_err!("foo\"", delimited_string, ErrorKind::Char);
@ -349,41 +291,6 @@ 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] #[test]
fn parse_line_comment() { fn parse_line_comment() {
assert_ok!("// foo", line_comment, "", " foo"); assert_ok!("// foo", line_comment, "", " foo");
@ -395,64 +302,4 @@ Escape sequences, like \n and \t, are parsed literally.
assert_ok!("/* foo */", block_comment, "", " foo "); assert_ok!("/* foo */", block_comment, "", " foo ");
assert_ok!("/*\n\tfoo\nbar\n*/", block_comment, "", "\n\tfoo\nbar\n"); 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),
],
);
}
} }

View file

@ -1,80 +1,36 @@
use std::io;
use serde::Serialize; use serde::Serialize;
use crate::error::{Error, ErrorCode, Result}; use crate::error::{Error, ErrorCode, Result};
// TODO: Make configurable // TODO: Make configurable
const INDENT: [u8; 2] = [0x20, 0x20]; const INDENT: &str = " ";
/// A container for serializing Rust values into SJSON. pub struct Serializer {
pub struct Serializer<W> {
// The current indentation level // The current indentation level
level: usize, level: usize,
writer: W, // The output string
output: String,
} }
/// 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> pub fn to_string<T>(value: &T) -> Result<String>
where where
T: Serialize, T: Serialize,
{ {
let vec = to_vec(value)?; let mut serializer = Serializer {
let string = if cfg!(debug_assertions) { level: 0,
String::from_utf8(vec).expect("We do not emit invalid UTF-8") output: String::new(),
} else {
unsafe { String::from_utf8_unchecked(vec) }
}; };
Ok(string) value.serialize(&mut serializer)?;
Ok(serializer.output)
} }
impl<W> Serializer<W> impl Serializer {
where fn add_indent(&mut self) {
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) { for _ in 0..self.level.saturating_sub(1) {
self.write(INDENT)?; self.output += INDENT;
} }
Ok(())
} }
#[inline]
fn ensure_top_level_struct(&self) -> Result<()> { fn ensure_top_level_struct(&self) -> Result<()> {
if self.level == 0 { if self.level == 0 {
return Err(Error::new(ErrorCode::ExpectedTopLevelObject, 0, 0, None)); return Err(Error::new(ErrorCode::ExpectedTopLevelObject, 0, 0, None));
@ -84,10 +40,7 @@ where
} }
} }
impl<'a, W> serde::ser::Serializer for &'a mut Serializer<W> impl<'a> serde::ser::Serializer for &'a mut Serializer {
where
W: io::Write,
{
type Ok = (); type Ok = ();
type Error = Error; type Error = Error;
@ -101,7 +54,7 @@ where
fn serialize_bool(self, v: bool) -> Result<Self::Ok> { fn serialize_bool(self, v: bool) -> Result<Self::Ok> {
self.ensure_top_level_struct()?; self.ensure_top_level_struct()?;
self.write(if v { "true" } else { "false" })?; self.output += if v { "true" } else { "false" };
Ok(()) Ok(())
} }
@ -119,7 +72,8 @@ where
fn serialize_i64(self, v: i64) -> Result<Self::Ok> { fn serialize_i64(self, v: i64) -> Result<Self::Ok> {
self.ensure_top_level_struct()?; self.ensure_top_level_struct()?;
self.serialize_str(&format!("{}", v)) self.output += &v.to_string();
Ok(())
} }
fn serialize_u8(self, v: u8) -> Result<Self::Ok> { fn serialize_u8(self, v: u8) -> Result<Self::Ok> {
@ -136,80 +90,73 @@ where
fn serialize_u64(self, v: u64) -> Result<Self::Ok> { fn serialize_u64(self, v: u64) -> Result<Self::Ok> {
self.ensure_top_level_struct()?; self.ensure_top_level_struct()?;
self.serialize_str(&format!("{}", v)) self.output += &v.to_string();
Ok(())
} }
fn serialize_f32(self, v: f32) -> Result<Self::Ok> { fn serialize_f32(self, v: f32) -> Result<Self::Ok> {
self.serialize_f64(v.into()) if v.is_finite() {
self.serialize_f64(v.into())
} else {
self.ensure_top_level_struct()?;
self.output += "null";
Ok(())
}
} }
fn serialize_f64(self, v: f64) -> Result<Self::Ok> { fn serialize_f64(self, v: f64) -> Result<Self::Ok> {
self.ensure_top_level_struct()?; self.ensure_top_level_struct()?;
if !v.is_finite() { if v.is_finite() {
return Err(Error::new(ErrorCode::NonFiniteFloat, 0, 0, None)); self.output += &v.to_string();
} else {
self.output += "null";
} }
Ok(())
self.serialize_str(&format!("{}", v))
} }
fn serialize_char(self, v: char) -> Result<Self::Ok> { fn serialize_char(self, v: char) -> Result<Self::Ok> {
let mut buf = [0; 4]; let mut buf = [0; 4];
self.serialize_bytes(v.encode_utf8(&mut buf).as_bytes()) self.serialize_str(v.encode_utf8(&mut buf))
} }
fn serialize_str(self, v: &str) -> Result<Self::Ok> { fn serialize_str(self, v: &str) -> Result<Self::Ok> {
self.ensure_top_level_struct()?; self.ensure_top_level_struct()?;
let needs_quotes = let needs_escapes =
v.is_empty() || v.contains([' ', '\n', '\r', '\t', '=', '\'', '"', '\\', ':']); v.is_empty() || v.contains([' ', '\n', '\r', '\t', '=', '\'', '"', '\\', '/']);
if needs_quotes { if needs_escapes {
self.write(b"\"")?; self.output += "\"";
// Since we've added a layer of quotes, we now need to escape
// certain characters.
for c in v.chars() { for c in v.chars() {
match c { match c {
'\t' => { '\t' => {
self.write(b"\\")?; self.output.push('\\');
self.write(b"t")?; self.output.push('t');
} }
'\n' => { '\n' => {
self.write(b"\\")?; self.output.push('\\');
self.write(b"n")?; self.output.push('n');
} }
'\r' => { '\r' => {
self.write(b"\\")?; self.output.push('\\');
self.write(b"r")?; self.output.push('r');
}
'"' => {
self.write(b"\\")?;
self.write(b"\"")?;
}
'\\' => {
self.write(b"\\")?;
self.write(b"\\")?;
} }
c => { c => {
self.serialize_char(c)?; self.output.push(c);
} }
}; };
} }
self.write(b"\"")?; self.output += "\"";
} else { } else {
self.write(v.as_bytes())?; self.output += v;
} }
Ok(()) Ok(())
} }
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> { fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok> {
self.ensure_top_level_struct()?; todo!()
// 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> { fn serialize_none(self) -> Result<Self::Ok> {
@ -229,7 +176,8 @@ where
fn serialize_unit(self) -> Result<Self::Ok> { fn serialize_unit(self) -> Result<Self::Ok> {
self.ensure_top_level_struct()?; self.ensure_top_level_struct()?;
self.write(b"null") self.output += "null";
Ok(())
} }
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok> { fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok> {
@ -267,18 +215,19 @@ where
{ {
self.ensure_top_level_struct()?; self.ensure_top_level_struct()?;
self.write(b"{ ")?; self.output += "{ ";
variant.serialize(&mut *self)?; variant.serialize(&mut *self)?;
self.write(b" = ")?; self.output += " = ";
value.serialize(&mut *self)?; value.serialize(&mut *self)?;
self.write(b" }") self.output += " }\n";
Ok(())
} }
// Serialize the start of a sequence. // Serialize the start of a sequence.
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> { fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
self.ensure_top_level_struct()?; self.ensure_top_level_struct()?;
self.write(b"[\n")?; self.output += "[\n";
self.level += 1; self.level += 1;
Ok(self) Ok(self)
} }
@ -309,7 +258,7 @@ where
variant.serialize(&mut *self)?; variant.serialize(&mut *self)?;
self.write(b" = [\n")?; self.output += " = [\n";
self.level += 1; self.level += 1;
Ok(self) Ok(self)
@ -317,7 +266,7 @@ where
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> { fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
if self.level > 0 { if self.level > 0 {
self.write(b"{\n")?; self.output += "{\n";
} }
self.level += 1; self.level += 1;
Ok(self) Ok(self)
@ -339,7 +288,7 @@ where
variant.serialize(&mut *self)?; variant.serialize(&mut *self)?;
self.write(b" = {\n")?; self.output += " = {\n";
self.level += 1; self.level += 1;
Ok(self) Ok(self)
@ -353,10 +302,7 @@ where
} }
} }
impl<'a, W> serde::ser::SerializeSeq for &'a mut Serializer<W> impl<'a> serde::ser::SerializeSeq for &'a mut Serializer {
where
W: io::Write,
{
type Ok = (); type Ok = ();
type Error = Error; type Error = Error;
@ -364,100 +310,100 @@ where
where where
T: Serialize, T: Serialize,
{ {
self.add_indent()?; self.add_indent();
value.serialize(&mut **self)?; value.serialize(&mut **self)?;
self.write(b"\n") if !self.output.ends_with('\n') {
} self.output += "\n";
fn end(self) -> Result<Self::Ok> {
self.level -= 1;
self.add_indent()?;
self.write(b"]")
}
}
impl<'a, W> serde::ser::SerializeTuple for &'a mut Serializer<W>
where
W: io::Write,
{
type Ok = ();
type Error = Error;
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<()>
where
T: Serialize,
{
self.add_indent()?;
value.serialize(&mut **self)?;
self.write(b"\n")
}
fn end(self) -> Result<Self::Ok> {
self.level -= 1;
self.add_indent()?;
self.write(b"]")
}
}
impl<'a, W> serde::ser::SerializeTupleStruct for &'a mut Serializer<W>
where
W: io::Write,
{
type Ok = ();
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()>
where
T: Serialize,
{
self.add_indent()?;
value.serialize(&mut **self)?;
self.write(b"\n")
}
fn end(self) -> Result<Self::Ok> {
self.level -= 1;
self.add_indent()?;
self.write(b"]")
}
}
impl<'a, W> serde::ser::SerializeTupleVariant for &'a mut Serializer<W>
where
W: io::Write,
{
type Ok = ();
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()>
where
T: Serialize,
{
self.add_indent()?;
value.serialize(&mut **self)?;
self.write(b"\n")
}
fn end(self) -> Result<Self::Ok> {
self.level -= 1;
self.add_indent()?;
self.write(b"]\n")?;
self.level -= 1;
if self.level > 0 {
self.add_indent()?;
self.write(b"}")?;
} }
Ok(())
}
fn end(self) -> Result<Self::Ok> {
self.level -= 1;
self.add_indent();
self.output += "]\n";
Ok(()) Ok(())
} }
} }
impl<'a, W> serde::ser::SerializeMap for &'a mut Serializer<W> impl<'a> serde::ser::SerializeTuple for &'a mut Serializer {
where type Ok = ();
W: io::Write, type Error = Error;
{
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<()>
where
T: Serialize,
{
self.add_indent();
value.serialize(&mut **self)?;
if !self.output.ends_with('\n') {
self.output += "\n";
}
Ok(())
}
fn end(self) -> Result<Self::Ok> {
self.level -= 1;
self.add_indent();
self.output += "]\n";
Ok(())
}
}
impl<'a> serde::ser::SerializeTupleStruct for &'a mut Serializer {
type Ok = ();
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()>
where
T: Serialize,
{
self.add_indent();
value.serialize(&mut **self)?;
if !self.output.ends_with('\n') {
self.output += "\n";
}
Ok(())
}
fn end(self) -> Result<Self::Ok> {
self.level -= 1;
self.add_indent();
self.output += "]\n";
Ok(())
}
}
impl<'a> serde::ser::SerializeTupleVariant for &'a mut Serializer {
type Ok = ();
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()>
where
T: Serialize,
{
self.add_indent();
value.serialize(&mut **self)?;
if !self.output.ends_with('\n') {
self.output += "\n";
}
Ok(())
}
fn end(self) -> Result<Self::Ok> {
self.level -= 1;
self.add_indent();
self.output += "]\n";
self.level -= 1;
if self.level > 0 {
self.add_indent();
self.output += "}\n";
}
Ok(())
}
}
impl<'a> serde::ser::SerializeMap for &'a mut Serializer {
type Ok = (); type Ok = ();
type Error = Error; type Error = Error;
@ -465,7 +411,7 @@ where
where where
T: Serialize, T: Serialize,
{ {
self.add_indent()?; self.add_indent();
key.serialize(&mut **self) key.serialize(&mut **self)
} }
@ -476,25 +422,25 @@ where
// It doesn't make a difference where the `=` is added. But doing it here // 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 // means `serialize_key` is only a call to a different function, which should
// have greater optimization potential for the compiler. // have greater optimization potential for the compiler.
self.write(b" = ")?; self.output += " = ";
value.serialize(&mut **self)?; value.serialize(&mut **self)?;
self.write(b"\n") if !self.output.ends_with('\n') {
self.output += "\n";
}
Ok(())
} }
fn end(self) -> Result<Self::Ok> { fn end(self) -> Result<Self::Ok> {
if self.level > 1 { if self.level > 1 {
self.level -= 1; self.level -= 1;
self.add_indent()?; self.add_indent();
self.write(b"}")?; self.output += "}\n";
} }
Ok(()) Ok(())
} }
} }
impl<'a, W> serde::ser::SerializeStruct for &'a mut Serializer<W> impl<'a> serde::ser::SerializeStruct for &'a mut Serializer {
where
W: io::Write,
{
type Ok = (); type Ok = ();
type Error = Error; type Error = Error;
@ -502,29 +448,29 @@ where
where where
T: Serialize, T: Serialize,
{ {
self.add_indent()?; self.add_indent();
key.serialize(&mut **self)?; key.serialize(&mut **self)?;
self.write(b" = ")?; self.output += " = ";
value.serialize(&mut **self)?; value.serialize(&mut **self)?;
self.write(b"\n") if !self.output.ends_with('\n') {
self.output += "\n";
}
Ok(())
} }
fn end(self) -> Result<Self::Ok> { fn end(self) -> Result<Self::Ok> {
if self.level > 1 { if self.level > 1 {
self.level -= 1; self.level -= 1;
self.add_indent()?; self.add_indent();
self.write(b"}")?; self.output += "}\n";
} }
Ok(()) Ok(())
} }
} }
impl<'a, W> serde::ser::SerializeStructVariant for &'a mut Serializer<W> impl<'a> serde::ser::SerializeStructVariant for &'a mut Serializer {
where
W: std::io::Write,
{
type Ok = (); type Ok = ();
type Error = Error; type Error = Error;
@ -532,18 +478,21 @@ where
where where
T: Serialize, T: Serialize,
{ {
self.add_indent()?; self.add_indent();
key.serialize(&mut **self)?; key.serialize(&mut **self)?;
self.write(b" = ")?; self.output += " = ";
value.serialize(&mut **self)?; value.serialize(&mut **self)?;
self.write(b"\n") if !self.output.ends_with('\n') {
self.output += "\n";
}
Ok(())
} }
fn end(self) -> Result<Self::Ok> { fn end(self) -> Result<Self::Ok> {
if self.level > 0 { if self.level > 0 {
self.level -= 1; self.level -= 1;
self.add_indent()?; self.add_indent();
self.write(b"}")?; self.output += "}\n";
} }
Ok(()) Ok(())
} }

View file

@ -81,14 +81,17 @@ fn serialize_non_representable_floats() {
} }
let tests = [std::f64::NAN, std::f64::INFINITY, std::f64::NEG_INFINITY]; let tests = [std::f64::NAN, std::f64::INFINITY, std::f64::NEG_INFINITY];
let expected = String::from("value = null\n");
for value in tests { for value in tests {
let value = Value64 { value }; let value = Value64 { value };
assert!(to_string(&value).is_err()); let actual = to_string(&value).unwrap();
assert_eq!(actual, expected);
} }
let tests = [std::f32::NAN, std::f32::INFINITY, std::f32::NEG_INFINITY]; let tests = [std::f32::NAN, std::f32::INFINITY, std::f32::NEG_INFINITY];
for value in tests { for value in tests {
let value = Value32 { value }; let value = Value32 { value };
assert!(to_string(&value).is_err()); let actual = to_string(&value).unwrap();
assert_eq!(actual, expected);
} }
} }
@ -122,15 +125,8 @@ fn serialize_string() {
("foo\nbar", "\"foo\\nbar\""), ("foo\nbar", "\"foo\\nbar\""),
("foo\r\nbar", "\"foo\\r\\nbar\""), ("foo\r\nbar", "\"foo\\r\\nbar\""),
("foo\tbar", "\"foo\\tbar\""), ("foo\tbar", "\"foo\\tbar\""),
("foo/bar", "foo/bar"), ("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 { for (value, expected) in tests {
let expected = format!("value = {expected}\n"); let expected = format!("value = {expected}\n");
@ -156,7 +152,7 @@ fn serialize_char() {
('\t', "\"\\t\""), ('\t', "\"\\t\""),
('\r', "\"\\r\""), ('\r', "\"\\r\""),
('\\', "\"\\\\\""), ('\\', "\"\\\\\""),
('/', "/"), ('/', "\"\\/\""),
('\"', "\"\\\"\""), ('\"', "\"\\\"\""),
('\'', "\"'\""), ('\'', "\"'\""),
]; ];