#data-source #embed #build

build uneval

Serde serializer to embed data as Rust code

7 releases

0.2.4 Mar 30, 2022
0.2.3 Dec 2, 2021
0.2.2 Nov 27, 2021
0.2.1 May 1, 2020
0.1.1 Apr 30, 2020

#155 in Build Utils

Download history 293/week @ 2023-12-13 218/week @ 2023-12-20 146/week @ 2023-12-27 343/week @ 2024-01-03 366/week @ 2024-01-10 239/week @ 2024-01-17 201/week @ 2024-01-24 133/week @ 2024-01-31 192/week @ 2024-02-07 231/week @ 2024-02-14 151/week @ 2024-02-21 265/week @ 2024-02-28 297/week @ 2024-03-06 214/week @ 2024-03-13 282/week @ 2024-03-20 375/week @ 2024-03-27

1,221 downloads per month
Used in 9 crates (via pai)

MIT license

31KB
466 lines

uneval

Makes Serde serialize your data to the Rust source code.

Why?

This crate was inspired by the Stack Overflow question. In short, if you want to make some data in the human-readable format, such as JSON, to be deserialized at compile-time and build into the binary, as if it was written by hand, then this crate can possibly help you.

How to?

This crate is intended to be used from the build script. It will serialize anything you provide to it to any path you provide (or to the arbitrary io::Write implementation, or into String, if you want to). Then, you'll include! the generated file wherever you want to use it.

How does it work?

See the crate documentation for details. In short, we use information provided by Serde to emit the code, which, when assigned to the variable of correct type, will provide all necessary conversions by using Into and iterators.

Is it really that simple?

Well... not. There are several limitations.

  1. All the types used in the serialized struct must be in scope on the include site. Serde doesn't provide the qualified name (i.e. path) to the serializer, only the "last" name. The probably easiest way is to use the serialized data as following:
let value: MainType = {
    use ::path::to::Type1;
    // ...and other types
    include!("path/to/file.rs")
}

or the similar construction using lazy_static.

  1. As a consequence, all the types used by the serialized one must have distinct names (or they'll clash with each other).
  2. Deserializer isn't implemented. This is intentional, since this crate isn't really intended for runtime usage. Well, in fact, the deserializer is implemented - it's just the Rust compiler itself.
  3. This serializer is intended for use with derived implementation. It may return bogus results when used with customized Serialize.
  4. It is impossible to serialize the struct with private fields outside from the module it is defined in. In fact, to be able to serialize this type at all, you'll have to distribute two copies of your crate, one of which would only export the definition with derived Serialize to be used by this crate during the build-time of the second copy. (Isn't this a bit too complex?)

If you find any other case where this doesn't work, feel free to open an issue - we'll either fix the code or document the newly-found limitation.

Testing

This crate uses batch_run to run its tests.

The common structure of test cases is like following:

  • File named definition.rs contains the necessary types.
  • File named {test_name}-main.rs includes definition.rs as module. It contains the main function, which creates an instance of some type from definition.rs, generates the corresponding Rust code in generated.rs and launches {test_name}-user.rs through batch_run.
  • File named {test_name}-user.rs includes definition.rs as module and generated.rs through call to include!. It checks that the generated code indeed creates the data equal to what was created initially.

Testing data itself is defined in test_fixtures/data.toml, and is in the following format:

  • Section name in TOML corresponds to the name of test case. Note that this is not the Cargo test, but the item in the batch_run's batch.
  • Field main_type corresponds to the type which serialization is being tested.
  • If there are several types (for example, in the nested struct), all other types except for main one should be listed under support_types as a comma-separated list. These, together with the main_type, will be included in {test_name}-user.rs as imports.
  • Field definition is literally copied into the definition.rs. It's necessary to derive Debug, Serialize and PartialEq on all the types there, since these traits are used during test entry run.
  • Field value is literally copied in two places: first, the {test_name}-main.rs, where the code is generated; second, in {test_name}-user.rs, where test checks two values for equality.

License

MIT

Dependencies

~0.4–1MB
~23K SLoC