Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a JsonStr type for encoded JSON buffers #165

Merged
merged 1 commit into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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