Expand description
A small, no-std, object-safe, serialization-only framework.
Getting started
Add sval
to your Cargo.toml
:
[dependencies.sval]
version = "0.0.4"
Streaming values
sval::stream(42, MyStream)?;
where 42
is a Value
and MyStream
is a Stream
.
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) -> Result<(), value::Error> {
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) -> Result<(), value::Error> {
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) -> Result<(), value::Error> {
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) -> Result<(), value::Error> {
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) -> Result<(), stream::Error> {
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}
};
assert!(is_u64(42u64));
pub fn is_u64(v: impl Value) -> bool {
let mut stream = IsU64(None);
sval::stream(v, &mut stream)
.map(|_| stream.0.is_some())
.unwrap_or(false)
}
struct IsU64(Option<u64>);
impl Stream for IsU64 {
fn u64(&mut self, v: u64) -> Result<(), stream::Error> {
self.0 = Some(v);
Ok(())
}
fn fmt(&mut self, _: stream::Arguments) -> Result<(), stream::Error> {
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) -> Result<(), stream::Error> {
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>) -> Result<(), stream::Error> {
self.stack.seq_begin()?;
let delim = mem::replace(&mut self.delim, "");
print!("{}[", delim);
Ok(())
}
fn seq_elem(&mut self) -> Result<(), stream::Error> {
self.stack.seq_elem()?;
Ok(())
}
fn seq_end(&mut self) -> Result<(), stream::Error> {
let pos = self.stack.seq_end()?;
self.delim = Self::next_delim(pos);
print!("]");
Ok(())
}
fn map_begin(&mut self, _: Option<usize>) -> Result<(), stream::Error> {
self.stack.map_begin()?;
let delim = mem::replace(&mut self.delim, "");
print!("{}{{", delim);
Ok(())
}
fn map_key(&mut self) -> Result<(), stream::Error> {
self.stack.map_key()?;
Ok(())
}
fn map_value(&mut self) -> Result<(), stream::Error> {
self.stack.map_value()?;
Ok(())
}
fn map_end(&mut self) -> Result<(), stream::Error> {
let pos = self.stack.map_end()?;
self.delim = Self::next_delim(pos);
print!("}}");
Ok(())
}
fn end(&mut self) -> Result<(), stream::Error> {
self.stack.end()?;
println!();
Ok(())
}
}
The Stack
type has a fixed depth, so deeply nested structures
aren’t supported.
serde
integration
Use the serde
Cargo feature to enable integration with serde
:
[dependencies.sval]
features = "serde"