pub struct Raw<'de>(/* private fields */);Expand description
One JSON value, kept as the text that spelled it.
The span is exactly one value with no whitespace on either side, which is
what lets a Raw stand anywhere a value stands: a member of an object, an
element of an array, or a whole document on its own.
Reading borrows out of the input under the default policy: the value is
stepped over rather than decoded, nothing inside it is converted, and the
field is a subslice of the document. Writing is then a copy of that run of
bytes. The owning form exists for the value that has to outlive its
document, through into_owned, and for the span that
had a comment taken out of it and so is no longer any run of the input; see
the Read impl for both, and for exactly which spans those are.
The type deliberately has no From<&str>, no From<String> and no
FromStr. Each would be a conversion that looks total and is not:
new and from_string can fail, and the
unchecked ways in are new_unchecked and
from_string_unchecked, spelled that way so
that a span nobody validated is visible at the call site rather than hidden
behind an into().
Implementations§
Source§impl<'de> Raw<'de>
impl<'de> Raw<'de>
Sourcepub fn new(s: &'de str) -> Result<Self>
pub fn new(s: &'de str) -> Result<Self>
Take the one JSON value s spells, checking that it really is one.
The check is everything from_str checks reading
s into a Value under Standard,
except the number grammar, and it refuses what that read refuses with
the same code at the same offset. The value has to be complete and well
formed, with no control character inside a string and no escape in one,
key or value, that the string reader would not decode: \q, a \u
that is not four hex digits, and a surrogate without its other half
all fail. And it has to be the only thing in s. Trailing content
fails rather than being stored with the value, because a Raw carrying
a tail would write that tail back out into the middle of whatever
document it lands in and break it.
use structio::{ErrorCode, json::Raw};
assert!(Raw::new(r#"{"a":[1,2]}"#).is_ok());
assert_eq!(Raw::new(r#"{"a":}"#).unwrap_err().code, ErrorCode::UnexpectedCharacter);
assert_eq!(Raw::new("1 2").unwrap_err().code, ErrorCode::TrailingContent);
assert_eq!(Raw::new(r#""\q""#).unwrap_err().code, ErrorCode::InvalidEscape);
assert_eq!(Raw::new(r#""\ud800""#).unwrap_err().code, ErrorCode::InvalidSurrogate);Whitespace on either side is dropped rather than refused, so " 1 "
and "1" are the same Raw. Keeping it would put the caller’s
indentation inside a document laid out by someone else.
Nothing is decoded to be checked: an escape is refused or let through
and the span keeps it as it was spelled. So this settles that s is
one value rather than that it is a value this crate would have
produced, and the number grammar is the one place the two differ. A
number is stepped over by its alphabet rather than held to the grammar,
exactly as prettify steps over one, so 01 is
accepted and stored as it was written. Holding it to the grammar would
cost every well-formed number in every forwarded body to move a
rejection ahead of the reader that will make it anyway, and that reader
is the one that knows what the value was supposed to be.
Sourcepub fn new_unchecked(s: &'de str) -> Self
pub fn new_unchecked(s: &'de str) -> Self
Take s as one JSON value without looking at it.
The validity of the output document is yours, in the same way it is
yours when you reach for Writer::raw:
whatever s holds becomes part of whatever document this Raw is
written into, verbatim and unexamined. Hand it something that is not
one complete JSON value and you get output that is not JSON, at the
position where the value should have been.
There is no unsafe here and nothing unsound to be caused: the type
holds a &str either way, and every path through it stays in safe
code. unchecked is about the document, not about memory. What the
call buys is the walk new makes, which is worth
something for a span that some earlier step already proved out: a value
copied from another Raw, or a body a schema-aware layer has already
parsed.
A span this accepted and new would not still behaves the
same under every write policy. See the Write impl.
Sourcepub fn as_str(&self) -> &str
pub fn as_str(&self) -> &str
The value’s text, as it will be written.
The span itself, not a decoded value: a Raw holding a JSON string has
the quotes and the escapes in here, because those are part of how the
value was spelled and this type is about the spelling. Reading the
value as a value means declaring a type for it and parsing this text
with from_str, which is a second pass and is meant
to look like one.
Sourcepub fn into_owned(self) -> Raw<'static>
pub fn into_owned(self) -> Raw<'static>
Cut the borrow, copying the span if it is still one.
The way out of 'de for a value that has to outlive the document it
came from: a request body parked on a queue, or a params held until
the worker that will forward it is free. A span that is already owned,
which is what reading a value carrying a comment under
ALLOW_COMMENTS produces, moves
without copying.
use structio::json::Raw;
let owned = {
let text = String::from(r#"[1,2,3]"#);
Raw::new(&text).unwrap().into_owned()
};
assert_eq!(owned.as_str(), "[1,2,3]");Source§impl Raw<'static>
impl Raw<'static>
Sourcepub fn from_string(s: String) -> Result<Self>
pub fn from_string(s: String) -> Result<Self>
Take the one JSON value s spells, checking that it really is one,
without copying it.
The owning counterpart to new, and the way in for text
this program produced rather than read: a body assembled from parts, or
a value some other layer already rendered. The String becomes the
span, so nothing is reallocated and the span is never copied out of the
buffer it arrived in. Reaching for new(&s) and then
into_owned instead copies a buffer the caller
already owns.
The check and the trimming are exactly new’s, being the
same walk: one complete value, nothing after it, and no whitespace on
either side of what is kept. Trimming shifts bytes down inside s.
What the Raw then holds is the caller’s whole allocation, not just
the span: a megabyte buffer that was built up and whittled down to one
short value keeps the megabyte for as long as the Raw lives. Where
that matters, new(&s)?.into_owned() is the call that pays a copy to
allocate the span exactly.
use structio::json::Raw;
let text = format!(r#"{{"id":{}}}"#, 7);
let raw = Raw::from_string(text).unwrap();
assert_eq!(raw.as_str(), r#"{"id":7}"#);s is consumed either way: a value that fails the check is dropped
rather than handed back, because Error carries a code and a
position and is not a place to park a buffer. Where the text has to
survive its own rejection, check it with new first.
Sourcepub fn from_string_unchecked(s: String) -> Self
pub fn from_string_unchecked(s: String) -> Self
Take s as one JSON value without looking at it, and without copying
it.
new_unchecked for text this program owns, on
the same terms: the validity of the output document is yours, and
whatever s holds goes verbatim into whatever document this Raw
lands in. Like that one and unlike from_string
it does not trim, so whitespace around the value is part of the span
and is written with it.
An empty String is the case to watch, being the one a builder that
never got filled hands over. It writes nothing at all, which truncates
the member it stands in to {"params":}; see default
for why that is the hole the defaulted value exists to close.
Trait Implementations§
Source§impl Default for Raw<'_>
impl Default for Raw<'_>
Source§fn default() -> Self
fn default() -> Self
The literal null, borrowed.
Not the empty string, which is what a newtype over Cow<str> defaults
to on its own and what would make Default quietly able to break a
document. A Raw writes its span verbatim, so an empty span writes
nothing at all and a struct whose payload was merely never filled
comes out as {"payload":}: not a document any reader will accept, and
not one this crate can otherwise produce. Glaze’s raw-JSON type has
exactly that hole, and reproducing it here would mean a field that is
safe to fill and dangerous to leave alone, which is the wrong way round
for the member a schema says is optional.
null closes it and costs nothing to close: it is one complete value
like any other, it is the JSON for the member that has no value, and it
is what is_null answers to, so a defaulted Raw is
also the member SKIP_NULL leaves out
entirely.
let envelope = Envelope { id: 1, ..Default::default() };
assert_eq!(structio::to_string(&envelope), r#"{"id":1,"payload":null}"#);Source§impl Display for Raw<'_>
impl Display for Raw<'_>
Source§fn fmt(&self, f: &mut Formatter<'_>) -> Result
fn fmt(&self, f: &mut Formatter<'_>) -> Result
The span, exactly as as_str gives it and exactly as a
compact write emits it. Under PRETTY a
write lays the span out at the depth it sits at, which Display has no
enclosing document to do.
{:#} is the same text rather than the laid-out one.
Value prettifies under the alternate flag, but it is
a tree and cannot be holding anything it could not lay out. A Raw can:
new_unchecked takes a span nobody walked, and
laying that one out fails. Display has nowhere to report a failure
and would have to swallow it, so laying a span out stays
prettify, which is asked for by name and returns
a Result.
use structio::json::Raw;
let raw = Raw::new(r#""a\nb""#).unwrap();
assert_eq!(raw.to_string(), r#""a\nb""#);impl<'de> Eq for Raw<'de>
Source§impl<'de> Read<'de> for Raw<'de>
Borrow the value’s bytes straight out of the document.
impl<'de> Read<'de> for Raw<'de>
Borrow the value’s bytes straight out of the document.
The span is found by stepping over the value rather than by reading it, so
a Raw member costs a parse roughly what an unknown key costs: one walk to
find where the value ends, and then a slice of the input between the two
offsets that walk left. Nothing inside the value is converted, nothing is
copied, and under the default policy nothing is allocated, the field being
a subslice of the input.
The walk checks what Raw::new checks, so a document is refused or not
whatever type its value lands in, the number grammar aside. The one thing
it does that skipping an unknown key does not is decode each escape it
meets and throw the character away, because a skipped value is discarded
and this one is kept and written out again. Only a string holding a
backslash pays for that.
Under ALLOW_COMMENTS the span may hold
// and /* */. The document was allowed to carry those; the output is
not, a comment being no part of what any writer here can emit, and a
forwarded body carrying one would be refused by the next plain JSON reader
that saw it. So that policy, and only that policy, searches the span for a
/, and runs it through the minifier, which
already strips comments under it, only when it finds one.
What that means for the bytes, exactly. A span with no / in it holds
no comment, so it is borrowed just as the default policy borrows it: the
same bytes, interior whitespace and all. A span with a / in it is
minified into an owned string, which moves the whitespace between its
tokens but never the tokens themselves, so key order, number spellings and
escapes still come through as the document spelled them. The search is
conservative in the direction that costs nothing: a / inside a string,
as in "http://x", buys a minify the span did not need, while a / that
is absent is proof there was nothing to strip.
The test is O::ALLOW_COMMENTS, a compile-time constant, so the default
policy compiles to the borrow with neither the search nor the stripping
present.
impl<'de> StructuralPartialEq for Raw<'de>
Source§impl Write for Raw<'_>
Write the span back out.
impl Write for Raw<'_>
Write the span back out.
Compact, which is the default, that is one copy of the bytes that arrived and nothing else. Byte-for-byte preservation is the whole point of the type, so the bytes are not looked at, let alone reformatted.
Under PRETTY they are laid out again at the
writer’s current depth, through the same walk
prettify uses. A forwarded value is usually the one
part of a document that did not come from a writer here, and emitting it
verbatim would wedge an unindented blob between indented neighbours, which
is what Glaze does and is jarring exactly where a human is reading. Reusing
that walk rather than writing a second one is also what keeps the promise
its module docs make: the output is byte-identical to what writing the same
data under the same policy produces, because there is one set of layout
rules rather than two. Tokens are still copied through as the input spelled
them, so only the whitespace between them is this crate’s and the
passthrough guarantee survives the layout.
A span that is not one complete JSON value can only come from
new_unchecked, and is written verbatim under either
policy. write returns () and has no way to report the problem, so the
choice is between laying out as much as parses and emitting what the caller
supplied; emitting it is what the compact path would have done, which makes
an invalid span behave the same way whichever policy is in force. The
layout walk therefore settles the span’s structure before it emits a byte,
rather than starting and discovering the problem halfway: a writer has no
way to take output back, so a fallback after a partial layout would write
the value twice.
Source§fn is_null(&self) -> bool
fn is_null(&self) -> bool
Whether the span is the literal null.
Write::is_null is about absence rather than about bytes, and a
Raw is the one type here where the two questions have the same
answer: it holds no value of its own, only the text of one, so what it
means under SKIP_NULL is whatever the
value it stands for would have meant. A forwarded null is a forwarded
absence.
The comparison is against the span exactly, which
new guarantees carries no surrounding whitespace. A Raw
built by new_unchecked out of " null " is not
absent, for the same reason it is not the value new would have
stored.