#dhall #serde #serialization #pascal-case

serde_dhall_typegen

从Dhall类型自动生成Rust结构体和枚举

1个不稳定版本

0.1.0 2022年4月15日

#1254 in 过程宏

MIT/Apache

86KB
1.5K SLoC

serde_dhall_typegen

这是一个宏,可以从Dhall类型自动生成Rust结构体和枚举。然后可以使用serde_dhall crate将这些类型的Dhall值反序列化到Rust中。
目前可定制性有限,生成的Rust代码可能会频繁更改,因此仅建议在Dhall模式频繁更改时使用此crate进行原型设计。

用法

在以下mod中创建与Dhall类型兼容的Rust类型,这些类型包含在指定的文件夹中。可以在此mod块中包含手写的impl。默认情况下,生成的类型名称与定义它们的Dhall文件的Pascal大小写相同(例如,my_type.dhall -> MyType),但可以通过Dhall元数据覆盖(见Dhall元数据)。默认情况下,这些文件中包含的任何子联合或记录将分配一个任意的名称(这可能会在编译之间更改)。

示例 my_dhall_stuff.rs

    #[serde_dhall_typegen::dhall_types("./dhall/schema/")]
    mod dhall { }

参数

  • 到.dhall文件目录的字符串字面路径
  • 形式为name = literal的可选参数
    • impl = bool - 是否为所有类型生成函数?相当于设置named_implanonymous_impl
    • named_impl = bool - 是否为命名类型生成函数?相当于设置named_enum_implnamed_struct_impl
    • anonymous_impl = bool - 是否为匿名类型生成函数?相当于设置anonymous_enum_implanonymous_struct_impl
    • named_enum_impl = bool - 是否为匿名枚举生成变体访问函数?默认为false
    • named_struct_impl = bool - 是否为匿名结构体生成成员访问函数?默认为 false
    • anonymous_enum_impl = bool - 是否为匿名枚举生成成员访问函数?默认为 true
    • anonymous_struct_impl = bool - 是否为匿名结构体生成成员访问函数?默认为 false

Dhall 输入

指定目录中的每个 .dhall 文件都将被评估,并必须返回一个记录、一个联合、一个 模式,或一个接受单个 Type 参数并返回前述类型之一的函数。

泛型类型

dhall_types 支持,当提供包含类型为 Type -> Type 的函数的 Dhall 文件时,生成具有单个类型参数的类型。类型参数将命名为 T。与非泛型类型一样,如果可能,成员类型将被解释为泛型类型的实例。例如:

my_generic.dhall

    \(T: Type) -> {
      field: T
    }

my_type.dhall

    let MyGeneric = ./my_generic.dhall
    {
      a: MyGeneric Natural,
      b: { field: Text },
    }

... 变为...

    pub struct MyGeneric<T> {
      pub field: T,
    }
    pub struct MyType {
      a: MyGeneric<u64>,
      b: MyGeneric<String>,
    }

生成的函数

默认情况下,匿名枚举为每个变体生成函数。如果变体 MyVariant 有关联类型 T,枚举将具有以下函数

fn my_variant(&self) -> Option<&T>
fn my_variant_mut(&mut self) -> Option<&mut T>
fn into_my_variant(self) -> Result<T, Self>

如果变体没有关联类型,它将具有以下函数

fn is_my_variant(&self) -> bool

如果启用了结构体函数,类型为 T 的字段 my_field 将具有以下函数

fn my_field(&self) -> &T
fn my_field_mut(&mut self) -> &mut T

Dhall 元数据

由于 Dhall 和 Rust 的类型系统存在根本性的差异,因此在 Dhall 中可能需要进行一些非功能性的更改,以生成您打算使用的 Rust 类型。Dhall 的类型是 结构化 的,因此两个文件中具有相同结构的两个类型在 Dhall 看来是相同的类型。然而,当生成 Rust 类型时,这被认为是错误,因为它在确定给定 Dhall 类型应使用哪个 Rust 类型时会产生歧义。
为了避免这个问题,您可以为您的相同 Dhall 类型分配 'name 元数据',以便在评估期间区分它们。这不会影响您在其他上下文中的 Dhall 类型 - 结果的 Dhall 类型仅在设置特定环境变量时才会修改。
第一种方法可用于记录或联合,并且在类型之前

file_a.dhall

    env:rust_type ? (\(T: Type)->\(T: Type)->T) <MyEnumA>
    < A | B >

file_b.dhall

    env:rust_type ? (\(T: Type)->\(T: Type)->T) <MyEnumB>
    < A | B >

... 生成...

    pub enum MyEnumA {
      A,
      B,
    }
    pub enum MyEnumB {
      A,
      B,
    }

第二种方法更简洁,但只能用于记录,并且在类型之后

    {
      name: Text
    } 
    //\\ (env:rust_type <MyStructA> ? {})

    {
      name: Text
    } 
    //\\ (env:rust_type <MyStructB> ? {})

... 生成...

    pub struct MyStructA { 
      pub name: String,
    }
    pub struct MyStructB {
      pub name: String,
    }

您可以使用 let 绑定来减少冗长

    let rust_type = env:rust_type ? (\(T: Type)->\(T: Type)->T)
    let rust_struct = \(T: Type) -> (env:rust_struct T ? {})
    let Abc = rust_type <Abc> < A | B | C >
    let Def = rust_type <Def> < D | E | F >
    let Ghi = { g: Bool, h: Bool, i: Bool } //\\ (rust_struct <Ghi>)
    let Jkl = { j: Bool, k: Bool, l: Bool } //\\ (rust_struct <Jkl>)

示例

之前

./dhall/schema/person.dhall

    {
        name: Text,
        age_range: <
            Baby        |
            Toddler     |
            Child       |
            Teenager    |
            Adult       |
            Senior
        >,
        occupation: Optional {
            title: Text,
            salary: Natural,
        }
    }

./src/dhall.rs

    #[serde_dhall_typegen::dhall_types("./dhall/schema/")]
    mod dhall { }

之后

./src/dhall.rs (相当于)

    mod dhall {
        #[derive(Debug, Clone, Eq, PartialEq, Hash, ::serde::Serialize, ::serde::Deserialize)]
        pub struct Person {
            pub age_range: PersonAnon0,
            pub name: String,
            pub occupation: Option<PersonAnon1>,
        }
   
        #[derive(Debug, Clone, Eq, PartialEq, Hash, ::serde::Serialize, ::serde::Deserialize)]
        pub enum PersonAnon0 {
            Teenager,
            Senior,
            Baby,
            Toddler,
            Child,
            Adult,
        }
   
        impl PersonAnon0 {
            pub fn is_teenager(&self) -> bool {
                match self {
                    Self::Teenager => true,
                    _ => false,
                }
            }
            pub fn is_senior(&self) -> bool {
                match self {
                    Self::Senior => true,
                    _ => false,
                }
            }
            pub fn is_baby(&self) -> bool {
                match self {
                    Self::Baby => true,
                    _ => false,
                }
            }
            pub fn is_toddler(&self) -> bool {
                match self {
                    Self::Toddler => true,
                    _ => false,
                }
            }
            pub fn is_child(&self) -> bool {
                match self {
                    Self::Child => true,
                    _ => false,
                }
            }
            pub fn is_adult(&self) -> bool {
                match self {
                    Self::Adult => true,
                    _ => false,
                }
            }
        }
 
        #[derive(Debug, Clone, Eq, PartialEq, Hash, ::serde::Serialize, ::serde::Deserialize)]
        pub struct PersonAnon1 {
            pub salary: u64,
            pub title: String,
        }
    }

当前限制

  • 提供的目录中的所有 .dhall 文件都必须有效并满足类型要求,否则编译将失败。
  • 所有生成的结构体都具有公共成员。
  • 生成的函数是全部或无,您不能排除可变访问函数,例如。
  • 无法将属性应用于生成的结构体和枚举(例如 #[non_exhaustive])。
  • Dhall 模式中的默认值被忽略。
  • 每个类型仅支持单个类型参数。

依赖关系

~6–15MB
~213K SLoC