Crate serde_dhall

source ·
Expand description

Dhall is a programmable configuration language that provides a non-repetitive alternative to JSON and YAML.

You can think of Dhall as: JSON + types + imports + functions

For a description of the Dhall language, examples, tutorials, and more, see the language website.

This crate provides support for consuming Dhall files the same way you would consume JSON or YAML. It uses the Serde serialization library to provide drop-in support for Dhall for any datatype that supports serde (and that’s a lot of them !).

Basic usage

Deserialization (reading)

The entrypoint for deserialization is the from_str() function. It reads a string containing a Dhall expression and deserializes it into any serde-compatible type.

This could mean a common Rust type like HashMap:

use std::collections::HashMap;

// Some Dhall data
let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }";

// Deserialize it to a Rust type.
let deserialized_map: HashMap<String, u64> = serde_dhall::from_str(data).parse()?;

let mut expected_map = HashMap::new();
expected_map.insert("x".to_string(), 1);
expected_map.insert("y".to_string(), 2);

assert_eq!(deserialized_map, expected_map);

or a custom datatype, using serde’s derive mechanism:

use serde::Deserialize;

#[derive(Deserialize)]
struct Point {
    x: u64,
    y: u64,
}

// Some Dhall data
let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }";

// Convert the Dhall string to a Point.
let point: Point = serde_dhall::from_str(data).parse()?;
assert_eq!(point.x, 1);
assert_eq!(point.y, 2);

Serialization (writing)

The entrypoint for serialization is the serialize() function. It takes a serde-compatible type value and serializes it to a string containing a Dhall expression.

This could mean a common Rust type like HashMap:

use std::collections::HashMap;

let mut map = HashMap::new();
map.insert("x".to_string(), 1u64);
map.insert("y".to_string(), 2u64);

let string = serde_dhall::serialize(&map).to_string()?;
assert_eq!(
    string,
    "{ x = 1, y = 2 }".to_string(),
);

or a custom datatype, using serde’s derive mechanism:

use serde::Serialize;

#[derive(Serialize)]
struct Point {
    x: u64,
    y: u64,
}

let data = Point { x: 1, y: 2 };
let string = serde_dhall::serialize(&data).to_string()?;
assert_eq!(
    string,
    "{ x = 1, y = 2 }".to_string(),
);

Beware that in order to serialize empty options, empty lists or enums correctly, you will need to provide a type annotation!

Replacing serde_json or serde_yaml

If you used to consume JSON or YAML, you only need to replace serde_json::from_str or serde_yaml::from_str with serde_dhall::from_str(…).parse(). If you used to produce JSON or YAML, you only need to replace serde_json::to_string or serde_yaml::to_string with serde_dhall::serialize(…).to_string().

Additional type annotations

When deserializing, normal type checking is done to ensure that the returned value is a valid Dhall value. However types are first-class in Dhall, and this library allows you to additionally check that the input data matches a given Dhall type. That way, a type error will be caught on the Dhall side, and have pretty and explicit errors that point to the source file.

It is also possible to provide a type annotation when serializing. This is useful in particular for types like HashMap or SimpleValue that do not have a fixed type as Dhall values.

Moreover, some values (namely empty options, empty lists, and enums) require a type annotation in order to be converted to Dhall, because the resulting Dhall value will contain the type explicitly.

There are two ways to provide a type in this way: you can provide it manually or you can let Rust infer it for you. To let Rust infer the appropriate Dhall type, use the StaticType trait.

use serde::Deserialize;
use serde_dhall::StaticType;

#[derive(Deserialize, StaticType)]
struct Point {
    x: u64,
    y: u64,
}

// Some Dhall data
let data = "{ x = 1, y = 1 + 1 }";

// Convert the Dhall string to a Point.
let point = serde_dhall::from_str(data)
    .static_type_annotation()
    .parse::<Point>()?;
assert_eq!(point.x, 1);
assert_eq!(point.y, 2);

// Invalid data fails the type validation
let invalid_data = "{ x = 1, z = 0.3 }";
assert!(
    serde_dhall::from_str(invalid_data)
        .static_type_annotation()
        .parse::<Point>()
        .is_err()
);
use serde::Serialize;
use serde_dhall::{serialize, StaticType};

#[derive(Serialize, StaticType)]
enum MyOption {
    MyNone,
    MySome(u64),
}

let data = MyOption::MySome(0);
let string = serialize(&data)
    .static_type_annotation()
    .to_string()?;
// The resulting Dhall string depends on the type annotation; it could not have been
// printed without it.
assert_eq!(string, "< MyNone | MySome: Natural >.MySome 0".to_string());

To provide a type manually, you need a SimpleType value. You can parse it from some Dhall text like you would parse any other value.

use serde_dhall::SimpleType;
use std::collections::HashMap;

// Parse a Dhall type
let point_type_str = "{ x: Natural, y: Natural }";
let point_type = serde_dhall::from_str(point_type_str).parse::<SimpleType>()?;

// Some Dhall data
let point_data = "{ x = 1, y = 1 + 1 }";

// Deserialize the data to a Rust type. This checks that
// the data matches the provided type.
let deserialized_map = serde_dhall::from_str(point_data)
    .type_annotation(&point_type)
    .parse::<HashMap<String, u64>>()?;

let mut expected_map = HashMap::new();
expected_map.insert("x".to_string(), 1);
expected_map.insert("y".to_string(), 2);

assert_eq!(deserialized_map, expected_map);
use serde_dhall::{serialize, from_str, SimpleValue};

let ty = from_str("< A | B: Bool >").parse()?;
let data = SimpleValue::Union("A".to_string(), None);
let string = serialize(&data)
    .type_annotation(&ty)
    .to_string()?;
assert_eq!(string, "< A | B: Bool >.A".to_string());

Controlling deserialization

If you need more control over the process of reading Dhall values, e.g. disabling imports, see the Deserializer methods.

Structs

Controls how a Dhall value is read.
Errors that can occur when deserializing Dhall data.
Controls how a Dhall value is written.

Enums

Numeric literals
The type of a value that can be decoded by serde_dhall, e.g. { x: Bool, y: List Natural }.
A value of the kind that can be decoded by serde_dhall, e.g. { x = True, y = [1, 2, 3] }. This can be obtained with from_str() or from_file(). It can also be deserialized into Rust types with from_simple_value().

Traits

A data structure that can be deserialized from a Dhall expression.
A Rust type that can be represented as a Dhall type.
A data structure that can be serialized from a Dhall expression.

Functions

Deserialize a value from a slice of CBOR-encoded Dhall. The binary format is specified by the Dhall standard specification and is mostly used for caching expressions. Using the format is not recommended because errors won’t have a file to refer to and thus will be hard to fix.
Deserialize a value from a CBOR-encoded Dhall binary file. The binary format is specified by the Dhall standard specification and is mostly used for caching expressions. Using the format is not recommended because errors won’t have a file to refer to and thus will be hard to fix.
Deserialize a value from a Dhall file.
Deserialize a Rust value from a Dhall SimpleValue.
Deserialize a value from a string of Dhall text.
Serialize a value to a string of Dhall text.

Type Definitions

Alias for a Result with the error type serde_dhall::Error.