Skip to content

Commit

Permalink
Merge pull request #165 from sval-rs/feat/json-str
Browse files Browse the repository at this point in the history
Add a JsonStr type for encoded JSON buffers
  • Loading branch information
KodrAus authored Sep 28, 2023
2 parents 1aa541f + fc8f80b commit a8aae05
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 7 deletions.
4 changes: 2 additions & 2 deletions fmt/src/to_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ impl<V: fmt::Debug + ?Sized> DebugToValue<V> {
Adapt a reference to a [`fmt::Debug`] into an [`sval::Value`].
*/
pub fn new_borrowed<'a>(value: &'a V) -> &'a DebugToValue<V> {
// SAFETY: `&'a V` and `&'a ToDebug<V>` have the same ABI
// SAFETY: `&'a V` and `&'a DebugToValue<V>` have the same ABI
unsafe { &*(value as *const _ as *const DebugToValue<V>) }
}
}
Expand All @@ -65,7 +65,7 @@ impl<V: fmt::Display + ?Sized> DisplayToValue<V> {
Adapt a reference to a [`fmt::Display`] into an [`sval::Value`].
*/
pub fn new_borrowed<'a>(value: &'a V) -> &'a DisplayToValue<V> {
// SAFETY: `&'a V` and `&'a ToDebug<V>` have the same ABI
// SAFETY: `&'a V` and `&'a DisplayToValue<V>` have the same ABI
unsafe { &*(value as *const _ as *const DisplayToValue<V>) }
}
}
Expand Down
3 changes: 3 additions & 0 deletions json/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ extern crate std;

mod error;

mod value;
pub use self::value::*;

mod to_fmt;
pub use self::{error::*, to_fmt::*};

Expand Down
9 changes: 9 additions & 0 deletions json/src/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
Tags for JSON-specific types.
*/

/**
A tag for strings that contain an embedded JSON value.
# Valid datatypes
- `text`
*/
pub const JSON_VALUE: sval::Tag = sval::Tag::new("JSON_VALUE");

/**
A tag for strings that either don't contain characters that need escaping or are already escaped.
Expand Down
8 changes: 8 additions & 0 deletions json/src/to_fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ where
Some(&tags::JSON_TEXT) => {
self.text_handler = Some(TextHandler::native());
}
Some(&tags::JSON_VALUE) => {
self.is_text_quoted = false;
self.text_handler = Some(TextHandler::native());
}
Some(&sval::tags::NUMBER) => {
self.is_text_quoted = false;

Expand Down Expand Up @@ -323,6 +327,10 @@ where
Some(&tags::JSON_TEXT) => {
self.text_handler = None;
}
Some(&tags::JSON_VALUE) => {
self.is_text_quoted = true;
self.text_handler = None;
}
Some(&sval::tags::NUMBER) => {
self.is_text_quoted = true;

Expand Down
13 changes: 11 additions & 2 deletions json/src/to_string.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::Error;
use crate::{Error, JsonStr};

use alloc::string::String;
use alloc::{boxed::Box, string::String};

/**
Stream a value as JSON into a string.
Expand All @@ -13,3 +13,12 @@ pub fn stream_to_string(v: impl sval::Value) -> Result<String, Error> {

Ok(out)
}

/**
Stream a value as JSON into a `JsonStr`.
This method will fail if the value contains complex values as keys.
*/
pub fn stream_to_json_str(v: impl sval::Value) -> Result<Box<JsonStr>, Error> {
Ok(JsonStr::boxed(stream_to_string(v)?))
}
Empty file added json/src/to_value.rs
Empty file.
73 changes: 73 additions & 0 deletions json/src/value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use core::fmt;

/**
A string containing encoded JSON.
Streaming a `JsonStr` will embed its contents directly rather
than treating them as a string.
*/
#[repr(transparent)]
#[derive(PartialEq, Eq, Hash)]
pub struct JsonStr(str);

impl JsonStr {
/**
Treat a string as native JSON.
*/
pub fn new<'a>(json: &'a str) -> &'a Self {
// SAFETY: `JsonStr` and `str` have the same ABI
unsafe { &*(json as *const _ as *const JsonStr) }
}

/**
Get a reference to the underlying string.
*/
pub fn as_str(&self) -> &str {
&self.0
}
}

impl fmt::Debug for JsonStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}

impl fmt::Display for JsonStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}

impl PartialEq<str> for JsonStr {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}

impl sval::Value for JsonStr {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
stream.tagged_begin(Some(&crate::tags::JSON_VALUE), None, None)?;
stream.value(&self.0)?;
stream.tagged_end(Some(&crate::tags::JSON_VALUE), None, None)
}
}

#[cfg(feature = "alloc")]
mod alloc_support {
use super::*;

use alloc::boxed::Box;

impl JsonStr {
/**
Treat a string as native JSON.
*/
pub fn boxed(json: impl Into<Box<str>>) -> Box<Self> {
let json = json.into();

// SAFETY: `JsonStr` and `str` have the same ABI
unsafe { Box::from_raw(Box::into_raw(json) as *mut str as *mut JsonStr) }
}
}
}
5 changes: 5 additions & 0 deletions json/test/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ fn assert_stream(expected: &str, v: impl sval::Value) {
let actual_string = sval_json::stream_to_string(&v).unwrap();
let actual_bytes = String::from_utf8(sval_json::stream_to_vec(&v).unwrap()).unwrap();

let actual_json_str = sval_json::stream_to_json_str(&v).unwrap();
let rountrip_json_str = sval_json::stream_to_string(&actual_json_str).unwrap();

assert_eq!(expected, actual_string);
assert_eq!(expected, actual_bytes);
assert_eq!(expected, actual_json_str.as_str());
assert_eq!(expected, rountrip_json_str);
}

fn assert_valid(v: impl sval::Value) {
Expand Down
51 changes: 48 additions & 3 deletions src/data/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,53 @@ use crate::{
Error, Result, Stream, Tag, Value,
};

/**
Adapt a [`fmt::Display`] into an [`sval::Value`].
*/
#[repr(transparent)]
pub struct Display<V: ?Sized>(V);

impl<V: fmt::Display> Display<V> {
/**
Adapt a [`fmt::Display`] into an [`sval::Value`].
*/
pub fn new(value: V) -> Display<V> {
Display(value)
}

/**
Get a reference to the inner value.
*/
pub fn inner(&self) -> &V {
&self.0
}

/**
Convert into the inner value.
*/
pub fn into_inner(self) -> V {
self.0
}
}

impl<V: fmt::Display + ?Sized> Display<V> {
/**
Adapt a reference to a [`fmt::Display`] into an [`sval::Value`].
*/
pub fn new_borrowed<'a>(value: &'a V) -> &'a Display<V> {
// SAFETY: `&'a V` and `&'a Display<V>` have the same ABI
unsafe { &*(value as *const _ as *const Display<V>) }
}
}

impl<V: fmt::Display> Value for Display<V> {
fn stream<'sval, S: Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> Result {
stream.text_begin(None)?;
stream_display_fragments(stream, &self.0)?;
stream.text_end()
}
}

/**
Stream a [`fmt::Display`] as text into a [`Stream`].
Expand All @@ -12,9 +59,7 @@ pub fn stream_display<'sval>(
stream: &mut (impl Stream<'sval> + ?Sized),
value: impl fmt::Display,
) -> Result {
stream.text_begin(None)?;
stream_display_fragments(stream, value)?;
stream.text_end()
stream.value_computed(&Display::new(value))
}

/**
Expand Down

0 comments on commit a8aae05

Please sign in to comment.