[−][src]Crate sval
A small, no-std, object-safe, serialization-only framework.
The sval
API is built around two key traits:
Value
is a trait for data with a streamable structure. It's likeserde::Serialize
.Stream
is a trait for receiving the structure of a value. It's likeserde::Serializer
.
Getting started
Add sval
to your Cargo.toml
:
[dependencies.sval]
version = "0.3.0"
Streaming values
The structure of a Value
can be streamed to a Stream
.
in a single call
For simple use-cases, use the stream
function to stream the structure of a value:
sval::stream(MyStream, 42)?;
where 42
is a Value
and MyStream
is a Stream
.
over multiple calls
More involved use-cases may want to build up structure over time. Use a stream::OwnedStream
to hang on to a stream and pass it values over time:
use sval::{ Value, stream::{self, OwnedStream}, }; struct StreamPairs { // Using `OwnedStream<MyStream>` instead of just `MyStream` // gives us better ergonomics and validation stream: OwnedStream<MyStream>, } impl StreamPairs { fn new() -> Result<Self, stream::Error> { let mut stream = OwnedStream::new(MyStream); stream.map_begin(None)?; Ok(StreamPairs { stream, }) } fn pair(&mut self, k: impl Value, v: impl Value) -> Result<(), stream::Error> { self.stream.map_key(k)?; self.stream.map_value(v)?; Ok(()) } fn end(mut self) -> Result<MyStream, stream::Error> { self.stream.map_end()?; Ok(self.stream.into_inner()) } } // We begin the wrapper over `MyStream` let mut stream = StreamPairs::new()?; // Pairs can be streamed independently stream.pair("a", 42)?; stream.pair("b", 17)?; // Eventually we end the wrapper and return the underlying `MyStream` let my_stream = stream.end()?;
The above example captures an OwnedStream<MyStream>
and then allows multiple key-value pairs to be
streamed through it before finishing.
Implementing the Value
trait
Use the derive
Cargo feature to allow Value
to be derived:
[dependencies.sval]
features = ["derive"]
Then derive the Value
trait for simple datastructures:
use sval::Value; #[derive(Value)] pub struct Data { id: u32, title: String, }
The trait can also be implemented manually:
use sval::value::{self, Value}; pub struct Id(u64); impl Value for Id { fn stream(&self, stream: &mut value::Stream) -> value::Result { stream.u64(self.0) } }
for a sequence
A sequence can be visited by iterating over its elements:
use sval::value::{self, Value}; pub struct Seq(Vec<u64>); impl Value for Seq { fn stream(&self, stream: &mut value::Stream) -> value::Result { stream.seq_begin(Some(self.0.len()))?; for v in &self.0 { stream.seq_elem(v)?; } stream.seq_end() } }
for a map
A map can be visited by iterating over its key-value pairs:
use std::collections::BTreeMap; use sval::value::{self, Value}; pub struct Map(BTreeMap<String, u64>); impl Value for Map { fn stream(&self, stream: &mut value::Stream) -> value::Result { stream.map_begin(Some(self.0.len()))?; for (k, v) in &self.0 { stream.map_key(k)?; stream.map_value(v)?; } stream.map_end() } }
for values that aren't known upfront
Types can stream a structure that's different than what they use internally.
In the following example, the Map
type doesn't have any keys or values,
but serializes a nested map like {"nested": {"key": 42}}
:
use sval::value::{self, Value}; pub struct Map; impl Value for Map { fn stream(&self, stream: &mut value::Stream) -> value::Result { stream.map_begin(Some(1))?; stream.map_key_begin()?.str("nested")?; stream.map_value_begin()?.map_begin(Some(1))?; stream.map_key_begin()?.str("key")?; stream.map_value_begin()?.u64(42)?; stream.map_end()?; stream.map_end() } }
Implementing the Stream
trait
without state
Implement the Stream
trait to visit the structure of a Value
:
use sval::stream::{self, Stream}; struct Fmt; impl Stream for Fmt { fn fmt(&mut self, v: stream::Arguments) -> stream::Result { println!("{}", v); Ok(()) } }
A Stream
might only care about a single kind of value.
The following example overrides the provided u64
method
to see whether a given value is a u64
:
use sval::{ Value, stream::{self, Stream, OwnedStream}, }; assert!(is_u64(42u64)); pub fn is_u64(v: impl Value) -> bool { OwnedStream::stream(IsU64(None), v) .map(|is_u64| is_u64.0.is_some()) .unwrap_or(false) } struct IsU64(Option<u64>); impl Stream for IsU64 { fn u64(&mut self, v: u64) -> stream::Result { self.0 = Some(v); Ok(()) } fn fmt(&mut self, _: stream::Arguments) -> stream::Result { Err(stream::Error::msg("not a u64")) } }
with state
There are more methods on Stream
that can be overriden for more complex
datastructures like sequences and maps. The following example uses a
stream::Stack
to track the state of any sequences and maps and ensure
they're valid:
use std::{fmt, mem}; use sval::stream::{self, stack, Stream, Stack}; struct Fmt { stack: stream::Stack, delim: &'static str, } impl Fmt { fn next_delim(pos: stack::Pos) -> &'static str { if pos.is_key() { return ": "; } if pos.is_value() || pos.is_elem() { return ", "; } return ""; } } impl Stream for Fmt { fn fmt(&mut self, v: stream::Arguments) -> stream::Result { let pos = self.stack.primitive()?; let delim = mem::replace(&mut self.delim, Self::next_delim(pos)); print!("{}{:?}", delim, v); Ok(()) } fn seq_begin(&mut self, _: Option<usize>) -> stream::Result { self.stack.seq_begin()?; let delim = mem::replace(&mut self.delim, ""); print!("{}[", delim); Ok(()) } fn seq_elem(&mut self) -> stream::Result { self.stack.seq_elem()?; Ok(()) } fn seq_end(&mut self) -> stream::Result { let pos = self.stack.seq_end()?; self.delim = Self::next_delim(pos); print!("]"); Ok(()) } fn map_begin(&mut self, _: Option<usize>) -> stream::Result { self.stack.map_begin()?; let delim = mem::replace(&mut self.delim, ""); print!("{}{{", delim); Ok(()) } fn map_key(&mut self) -> stream::Result { self.stack.map_key()?; Ok(()) } fn map_value(&mut self) -> stream::Result { self.stack.map_value()?; Ok(()) } fn map_end(&mut self) -> stream::Result { let pos = self.stack.map_end()?; self.delim = Self::next_delim(pos); print!("}}"); Ok(()) } }
By default, the Stack
type has a fixed depth. That means deeply nested
structures aren't supported. See the Stack
type for more details.
serde
integration
Use the serde
Cargo feature to enable integration with serde
:
[dependencies.sval]
features = ["serde"]
When serde
is available, the Value
trait can also be derived
based on an existing Serialize
implementation:
use sval::Value; #[derive(Serialize, Value)] #[sval(derive_from = "serde")] pub enum Data { Variant(i32, String), }
std::fmt
integration
Use the fmt
Cargo feature to enable extended integration with std::fmt
:
[dependencies.sval]
features = ["fmt"]
When fmt
is available, arbitrary Value
s can be treated like std::fmt::Debug
:
fn with_value(value: impl sval::Value) { dbg!(sval::fmt::to_debug(&value)); // Do something with the value }
Re-exports
pub use self::stream::Stream; |
pub use self::value::Value; |
Modules
fmt | Integration between |
serde | Integration between |
stream | A stream for datastructures. |
test | Helpers for testing value implementations. |
value | A streamable value. |
Structs
Error | An error encountered while visiting a value. |
Functions
stream |