Obake is a procedural macro for declaring and maintaining versioned data-structures.

Overview

Build Issues crates.io License


お化け

Obake

Versioned data-structures for Rust.
View on docs.rs »

About

Obake is a procedural macro for declaring and maintaining versioned data-structures. The name 'obake' is taken from the Japanese 'お化け (おばけ)', a class of supernatural beings in Japanese folklore that shapeshift.

When developing an application, configuration formats and internal data-structures typically evolve between versions. However, maintaining backwards compatibility between these versions requires declaring and maintaining data-structures for legacy formats and code for migrating between them. Obake aims to make this process effortless.

Getting Started

To get started, add the following to your Cargo.toml file:

[dependencies]
obake = "1.0"

Example

=0.2, <=0.3.0"))] // any semantic version constraint can appear in bar: u32, // a `cfg` attribute #[obake(cfg("0.1.0"))] // multiple `cfg` attributes are treated as a #[obake(cfg(">=0.3"))] // disjunction over version constraints baz: char, } // describe migrations between versions using the `From` trait // and an automatically generated type-level macro for referring to // specific versions of `Foo` impl From for Foo!["0.2.0"] { fn from(foo: Foo!["0.1.0"]) -> Self { Self { bar: 0 } } } // an enumeration of all versions of `Foo` is accessed using the `obake::AnyVersion` type // alias let versioned_example: obake::AnyVersion = (Foo { bar: 42 }).into(); // this enumeration implements `Into`, where `Foo` is the latest declared // version of `Foo` (in this case, `Foo!["0.2.0"]`) let example: Foo = versioned_example.into(); assert_eq!(example, Foo { bar: 42 }); ">
#[obake::versioned]                 // create a versioned data-structure
#[obake(version("0.1.0"))]          // declare some versions
#[obake(version("0.2.0"))]
#[derive(Debug, PartialEq, Eq)]     // additional attributes are applied to all versions
struct Foo {
    #[obake(cfg("0.1.0"))]          // enable fields for specific versions with
    foo: String,                    // semantic version constraints
   
    #[obake(cfg(">=0.2, <=0.3.0"))] // any semantic version constraint can appear in
    bar: u32,                       // a `cfg` attribute 
   
    #[obake(cfg("0.1.0"))]          // multiple `cfg` attributes are treated as a
    #[obake(cfg(">=0.3"))]          // disjunction over version constraints
    baz: char,
}

// describe migrations between versions using the `From` trait
// and an automatically generated type-level macro for referring to
// specific versions of `Foo`
impl From for Foo!["0.2.0"] {
    fn from(foo: Foo!["0.1.0"]) -> Self {
        Self { bar: 0 }
    }
}

// an enumeration of all versions of `Foo` is accessed using the `obake::AnyVersion` type
// alias
let versioned_example: obake::AnyVersion<Foo> = (Foo { bar: 42 }).into();

// this enumeration implements `Into`, where `Foo` is the latest declared
// version of `Foo` (in this case, `Foo!["0.2.0"]`)
let example: Foo = versioned_example.into();

assert_eq!(example, Foo { bar: 42 });

Other Features

  • #[obake(inherit)]: allows nesting of versioned data-structures.
  • #[obake(derive(...))]: allows derive attributes to be applied to generated enums.
  • #[obake(serde(...))]: allows serde attributes to be applied to generated enums.
    • Note: requires the feature serde.

Limitations

  • Cannot be applied to tuple structs (or enum variants with unnamed fields).
  • Cannot be applied to items with generic parameters.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Obake by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
You might also like...
Garbage Collector(Hyaline- Safe Memory Reclaimation) for lock free data structures

Hyaline-SMR This crate provides garbage collection using hyaline algorithm for building concurrent data structures. When a thread removes an object fr

Library containing various Data Structures implemented using Rust.

rust-data-structures Library containing various Data Structures implemented using Rust. Running You can test the library by running cargo test, an exa

A library for comparing data structures in Rust, oriented toward testing

Delta: Structural differencing in Rust The delta crate defines the trait Delta, along with a derive macro for auto-generating instances of this trait

A library for comparing data structures in Rust, oriented toward testing

The comparable crate defines the trait [Comparable], along with a derive macro for auto-generating instances of this trait for most data types. Primar

Collection of Data Structures in Rust

cds - Collection of Data Structures !!! UNDER CONSTRUCTION !!! The version v0.0.1 is a crates.io placeholder. License Licensed under either of Apache

Succinct data structures in Rust

sucds: Succinct data structures in Rust sucds contains some succinct data structures written in Rust. Data structures So far, the following data struc

Library containing implementations of various sequential data-structures.

Library containing implementations of various sequential data-structures.

Dade is data definition for Rust structures.

dade dade is data definition for Rust structures. For the easy handle of data, the following will support it. Data validation. Data schema conforms Js

NixEl is a Rust library that turns Nix code into a variety of correct, typed, memory-safe data-structures

🐉 NixEL Lexer, Parser, Abstract Syntax Tree and Concrete Syntax Tree for the Nix Expressions Language. NixEl is a Rust library that turns Nix code in

Comments
  • Add serde versioned derive

    Add serde versioned derive

    The main use I'd have for this crate is to version config files (as described in the readme) but there doesn't currently appear to be a mechanism for attempting to attempt to parse as any version (in order to then upgrade it).

    Currently it requires something along the lines of:

    let x: Foo  = toml::from_str(content)
        .map(<Foo as obake::Versioned>::Versioned::Foo_v0_2_0)
        .or_else(|_| {
            toml::from_str(content)
                .map(<Foo as obake::Versioned>::Versioned::Foo_v0_1_0)
        })?
        .into();
    

    This ofc gets a bit repetitive (the above is only 2 versions!) and while it would require a bit of special-casing, it'd be preferable for the above to be possible to derive serde traits for the Versioned enum.

    Alternatively you could provide a mechanism for running a function polymorphic over its return type over each version in sequence until one returns Ok.

    This could look something like:

    let x = Foo::for_all_versions(toml::from_str)(content)?;
    

    Also worth noting (as I'm sure you're away) that if you derive serde traits for the enum using #[obake(derive(Serialize, Deserialize))] it will ofc expect you to describe an version such as:

    [Foo_v0_1_0]
    foo = 'foo'
    baz = 'x'
    
    opened by jam1garner 4
  • silence clippy lint for generated enum

    silence clippy lint for generated enum

    hi, this is a pretty nice crate :)

    clippy is warning about the generated enum variant names once a struct has three or more versions. this pr is just silencing clippy at the respective place, it would be awesome to get a new release with this addition!

    opened by janpetschexain 1
  • Add type alias for accessing Versioned enum

    Add type alias for accessing Versioned enum

    I believe it would be desirable to provide an alias such as:

    pub type AnyVersion<T> = <T as obake::Versioned>::Versioned;
    

    reasoning:

    • less eye-sore
    • "reads" better, making it easier for new users to understand
    • removes redundancy of typing out "Versioned" twice, which honestly took me all of 5 minutes to get sick of :P

    Willing to PR such a change but didn't want to so without first seeing if it's desirable. Name is just the first thing I thought of and ultimately not important if you have one in mind.

    opened by jam1garner 1
  • Deserialzing `untagged` versions should default to latest version (not earliest)

    Deserialzing `untagged` versions should default to latest version (not earliest)

    Hi! This library is so useful, but I'm failing to use it with serde_json. I've tried the following way:

    
    use serde_json::json;
    use obake;
    
    #[obake::versioned]
    #[obake(version("0.1.0"))]
    #[obake(version("0.2.0"))]
    #[obake(version("0.3.0"))]
    #[obake(derive(Debug, PartialEq, serde::Serialize, serde::Deserialize))]
    #[obake(serde(untagged))]
    #[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
    struct Character {
        name: String,
        age: u32,
        #[obake(cfg(">=0.2.0"))]
        height: f32,
        #[obake(cfg(">=0.3.0"))]
        weight: f32,
    }
    
    impl From<Character!["0.1.0"]> for Character!["0.2.0"] {
        fn from(old: Character!["0.1.0"]) -> Self {
            Self {
                name: old.name,
                age: old.age,
                ..Default::default()
            }
        }
    }
    
    impl From<Character!["0.2.0"]> for Character!["0.3.0"] {
        fn from(old: Character!["0.2.0"]) -> Self {
            Self {
                name: old.name,
                age: old.age,
                height: old.height,
                ..Default::default()
            }
        }
    }
    
    fn main() {}
    
    #[cfg(test)]
    mod tests {
        use serde_json::json;
        use crate::*;
    
        #[test]
        fn from_2_to_3() {
            let freeza_ser = serde_json::to_string_pretty(&json!({
                "name": "Freeza",
                "age": 32,
                "height": 1.53,
            })).unwrap();
            dbg!(&freeza_ser);
    
            let freeza: VersionedCharacter = serde_json::from_str(&freeza_ser).unwrap();
            dbg!(&freeza);
            let freeza: Character = freeza.into();
            dbg!(&freeza);
    
            let freeza_expected = Character {
                name: "Freeza".into(),
                age: 32,
                height: 1.53,
                weight: 0.,
            };
            assert_eq!(&freeza_expected, &freeza);
        }
    }
    
    

    The output is:

    failures:
    
    ---- tests::from_2_to_3 stdout ----
    [src/main.rs:56] &freeza_ser = "{\n  \"age\": 32,\n  \"height\": 1.53,\n  \"name\": \"Freeza\"\n}"
    [src/main.rs:59] &freeza = Character_v0_1_0(
        Character_v0_1_0 {
            name: "Freeza",
            age: 32,
        },
    )
    [src/main.rs:61] &freeza = Character_v0_3_0 {
        name: "Freeza",
        age: 32,
        height: 0.0,
        weight: 0.0,
    }
    thread 'tests::from_2_to_3' panicked at 'assertion failed: `(left == right)`
      left: `Character_v0_3_0 { name: "Freeza", age: 32, height: 1.53, weight: 0.0 }`,
     right: `Character_v0_3_0 { name: "Freeza", age: 32, height: 0.0, weight: 0.0 }`', src/main.rs:69:9
    

    So, the current behavior is that serde_json::from_str::<VersionedCharacter>(&freeza_ser) is deserializing into the first version, and I want it to deserialize like the following:

    fn character_parser(serialized_data: &String) -> Character {
        if let Ok(data) = serde_json::from_str::<Character!["0.3.0"]>(&serialized_data) {
            return data;
        }
    
        if let Ok(data) = serde_json::from_str::<Character!["0.2.0"]>(&serialized_data) {
            let data: Character!["0.3.0"] = data.into();
            return data;
        }
    
        if let Ok(data) = serde_json::from_str::<Character!["0.1.0"]>(&serialized_data) {
            let data: Character!["0.2.0"] = data.into();
            let data: Character!["0.3.0"] = data.into();
            return data;
        }
    
        return Character::default();
    }
    

    Am I missing something crucial here? Is there any way for this lib to implement that parsing automatically?

    Thank you!

    bug 
    opened by joaoantoniocardoso 1
Releases(1.0.4)
Owner
Nathan Corbyn
DPhil Computer Science candidate at the University of Oxford
Nathan Corbyn
Coding-challenge - Algorithms and Data-structures, problems and solutions in Rust language using cargo-workspaces

Coding Challenge LeetCode/Hackerrank e.t.c Using this as an opportunity to improve my knowledge of rust lang If you found this repo useful to you, add

Tolumide Shopein 17 Apr 24, 2022
Algorithms and Data Structures of all kinds written in Rust.

Classic Algorithms in Rust This repo contains the implementation of various classic algorithms for educational purposes in Rust. Right now, it is in i

Alexander González 49 Dec 14, 2022
Rust-algorithm-club - Learn algorithms and data structures with Rust

Rust Algorithm Club ?? ?? This repo is under construction. Most materials are written in Chinese. Check it out here if you are able to read Chinese. W

Weihang Lo 360 Dec 28, 2022
Common data structures and algorithms in Rust

Contest Algorithms in Rust A collection of classic data structures and algorithms, emphasizing usability, beauty and clarity over full generality. As

Aram Ebtekar 3.3k Dec 27, 2022
Rust data structures and client for the PubChem REST API

pubchem.rs Rust data structures and client for the PubChem REST API. ?? Usage ?? Compound Create a Compound to query the PubChem API for a single comp

Martin Larralde 2 Jan 18, 2022
Fast, efficient, and robust memory reclamation for concurrent data structures

Seize Fast, efficient, and robust memory reclamation for concurrent data structures. Introduction Concurrent data structures are faced with the proble

Ibraheem Ahmed 240 Dec 23, 2022
rust_aads - Rust Algorithms And Data Structures

rust_aads - Rust Algorithms And Data Structures rust_aads is an open repository with algorithms and data structures, used in computer science and comp

stepa 2 Dec 15, 2022
Rust Persistent Data Structures

Rust Persistent Data Structures Rust Persistent Data Structures provides fully persistent data structures with structural sharing. Setup To use rpds a

Diogo Sousa 883 Dec 31, 2022
A proof of concept implementation of cyclic data structures in stable, safe, Rust.

A proof of concept implementation of cyclic data structures in stable, safe, Rust. This demonstrates the combined power of the static-rc crate and the

null 157 Dec 28, 2022
Rust library for string parsing of basic data structures.

afmt Simple rust library for parsing basic data structures from strings. Usage You can specify string formats to any strucute, via the use of the fmt

Eduard 4 May 8, 2021