[][src]Crate sval

A small, no-std, object-safe, serialization-only framework.

The sval API is built around two key traits:

Getting started

Add sval to your Cargo.toml:

[dependencies.sval]
version = "0.4.2"

Supported formats

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:

This example is not tested
use sval::Value;

#[derive(Serialize, Value)]
#[sval(derive_from = "serde")]
pub enum Data {
    Variant(i32, String),
}

In no-std environments, serde support can be enabled using the serde_no_std feature instead:

[dependencies.sval]
features = ["serde_no_std"]

std::fmt integration

Use the fmt Cargo feature to enable extended integration with std::fmt:

[dependencies.sval]
features = ["fmt"]

When fmt is available, arbitrary Values 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 sval and std::fmt.

serde

Integration between sval and serde.

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

Stream the structure of a Value using the given Stream.