15个版本

0.1.0 2024年5月13日
0.0.16 2024年2月28日
0.0.15 2023年12月15日
0.0.14 2023年9月25日
0.0.5 2021年11月17日

#136 in 编码

Download history 17045/week @ 2024-05-03 39950/week @ 2024-05-10 79704/week @ 2024-05-17 87806/week @ 2024-05-24 94765/week @ 2024-05-31 94654/week @ 2024-06-07 84304/week @ 2024-06-14 89655/week @ 2024-06-21 89704/week @ 2024-06-28 94326/week @ 2024-07-05 95421/week @ 2024-07-12 93900/week @ 2024-07-19 96206/week @ 2024-07-26 92274/week @ 2024-08-02 114150/week @ 2024-08-09 106337/week @ 2024-08-16

429,507 每月下载量
219 个crate中(194个直接使用) 使用

Apache-2.0

515KB
11K SLoC

Typify

Typify可以将JSON Schema文档编译成Rust类型。它可以以多种方式使用

  • 使用cargo typify命令

  • 通过宏import_types!("types.json")直接在程序中生成Rust类型

  • 通过构建器接口在build.rsxtask中生成Rust类型

  • 通过构建器函数生成持久文件,例如在构建API绑定时

如果生成失败、无法编译或总体上表现不佳:请提交一个问题,并包括JSON Schema和Rust输出(如果有)。使用cargo typify命令从命令行生成代码。如果您能描述您理想中希望看到的输出,那就更有帮助了。

JSON Schema → Rust类型

Typify根据Schema的一些基本属性以几种不同的方式翻译JSON Schema类型

内置类型

整数、浮点数、字符串等。这些都在Rust中有直接表示。唯一需要注意的是如何根据类型属性选择合适的内置类型。例如,JSON Schema可能指定最大值和/或最小值,以指示应使用适当的整数类型。

包含已知格式的字符串模式由相应的Rust类型表示。例如,以下代码{ "type": "string", "format": "uuid" }被表示为uuid::Uuid(这需要将uuidcrate作为依赖项包含)。

数组

JSON模式数组可以转换为三种Rust类型之一Vec<T>HashSet<T>和元组,具体取决于模式属性。数组可以具有与固定项目类型列表匹配的固定长度;这可以用Rust元组很好地表示。Vec<T>HashSet<T>之间的区别仅在于模式中uniqueItems字段设置为falsetrue

对象

通常,对象转换为Rust结构体。但是,如果模式没有定义属性,并且additionalProperties模式指定了T,则Typify会发出一个HashMap<String, T>,否则发出一个HashMap<String, serde_json::Value>

不是required集合中生成的struct属性通常表示为一个带有#[serde(default)]属性的Option<T>。具有默认值的类型(如Vec<T>>)的属性将简单地获得#[serde(default)]属性(因此您不会看到例如Option<Vec<T>>)。

OneOf

oneOf构造映射到Rust枚举。Typify将其映射到各种serde枚举类型

AllOf

“allOf”结构通过合并模式来处理。虽然大部分时间,typify都会尝试保留和共享类型名称,但在合并模式时,它并不能总是做到这一点。您可能会得到在类型间重复的字段;优化这一生成过程是当前工作的一个活跃领域。

AnyOf

anyOf结构更复杂。它可以接近于一个enumoneOf),但没有任何特定变体可能是特定数据的规范或唯一的。尽管今天我们(不精确地)将这些作为具有可选、展平成员的结构体进行建模,但这仍是代码生成的一个较弱领域。

欢迎和欢迎描述示例模式和期望输出的问题。

Rust -> Schema -> Rust

从Rust类型派生的模式可能包括一个扩展,提供有关原始类型的信息

{
  "type": "object",
  "properties": { .. },
  "x-rust-type": {
    "crate": "crate-o-types",
    "version": "1.0.0",
    "path": "crate_o_types::some_mod::SomeType"
  }
}

该扩展包括crate的名称、Cargo风格的版本要求规范以及完整的路径(必须以crate的ident-converted名称开头)。

typify的每种使用模式都允许指定一个crate和版本的列表。在这种情况下,如果用户指定“[email protected]”等,那么typify将使用其SomeType类型,而不是根据模式生成一个。

使用其他crate的类型

typify的每种使用模式都有一个控制带有x-rust-type注解的类型使用的方法。默认情况下是忽略它们。推荐的方法是指定您打算使用的每个crate和版本。您还可以为crate提供*版本(这可能导致不兼容)或定义一个策略以允许使用所有“未知”crate(这可能需要为这些crate添加依赖项)。

对于CLI

$ cargo typify --unknown-crates allow --crate [email protected] ...

对于构建器

let mut settings = typify::TypeSpaceSettings::default();
settings.with_unknown_crates(typify::UnknownPolicy::Allow)
    .with_crate("oxnet", typify::CrateVers::Version("1.0.0".parse().unwrap()));

对于宏

typify::import_types!(
  schema = "schema.json",
  unknown_types = Allow,
  crates {
    "oxnet" = "1.0.0"
  }
)

版本要求

x-rust-type扩展中的version字段遵循Cargo版本要求规范。如果扩展指定了crate的0.1.0版本,并且用户声明他们正在使用0.1.1版本,那么将使用该类型;相反,如果扩展指定了0.2.2版本,而用户只使用0.2.0版本,则不使用该类型。

盒作者可以选择遵守比semver提供更稳定的规范。如果扩展版本是 >=0.1.0, <1.0.0,那么盒作者就承诺在所有发布版本上保持给定类型的模式兼容性,直到 1.0.0。盒作者在version字段中填写内容时,应确保类型可用性。例如,虽然*是一个有效值,但只有当相关类型在第一个发布的盒版本中可用,并且在后续版本中从未以不兼容的方式更改时,它才可能有效。

类型参数

x-rust-type扩展还可以指定类型参数

{
  "$defs": {
    "Sprocket": {
      "type": "object",
      "properties": { .. },
      "x-rust-type": {
        "crate": "util",
        "version": "0.1.0",
        "path": "util::Sprocket",
        "parameters": [
          {
            "$ref": "#/$defs/Gizmo"
          }
        ]
      }
    },
    "Gizmo": {
      "type": "object",
      "properties": { .. },
      "x-rust-type": {
        "crate": "util",
        "version": "0.1.0",
        "path": "util::Gizmo"
      }
    }
  }
}

在类型生成期间指定的util@0.1.0盒中,引用#/$defs/Sprocket的模式将使用(非生成的)类型util::Sprocket<util::Gizmo>

parameters字段是一个模式数组。它们可以是内联模式或引用模式。

x-rust-type包含在您的库中

预期值的模式如下

{
  "description": "schema for the x-rust-type extension",
  "type": "object",
  "properties": {
    "crate": {
      "type": "string",
      "pattern": "^[a-zA-Z0-9_-]+$"
    },
    "version": {
      "description": "semver requirements per a Cargo.toml dependencies entry",
      "type": "string"
    },
    "path": {
      "type": "string",
      "pattern": "^[a-zA-Z0-9_]+(::[a-zA-Z0-9+]+)*$"
    },
    "parameters": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/Schema"
      }
    }
  },
  "required": [
    "crate",
    "path",
    "version"
  ]
}

version字段表示您类型的稳定性。例如,如果0.1.0表示0.1.1用户可以使用该类型,而0.2.0用户将无法使用该类型(而是生成它),您可以通过使用Cargo版本要求语法来传达超出semver暗示的未来承诺。例如,>=0.1.0, <1.0.0表示类型将从版本0.1.0开始直到1.0.0保持结构兼容性。

格式化

您可以使用诸如rustfmt-wrapperprettyplease之类的crate来格式化生成的代码。这在进行代码提交或从build.rs生成代码时尤其有用。

以下示例展示了将 TypeSpace 转换为字符串的不同方法 (typespacetypify::TypeSpace)。

rustfmt

适用于需要与手动编写的代码(如 xtask 或独立代码生成器(如 cargo-typify))一起提交的代码生成。

rustfmt_wrapper::rustfmt(typespace.to_stream().to_string())?

prettyplease

适用于可能没有安装 rustfmtbuild.rs 脚本,因此应自包含。

prettyplease::unparse(&syn::parse2::<syn::File>(typespace.to_stream())?)

无格式化

如果没有人会看到代码(这种情况几乎从未发生)。

typespace.to_stream().to_string()

WIP

Typify 正在开发中。影响输出的更改将通过版本号的重大更改来指示。

通常,如果您有一个导致 Typify 失败的 JSON Schema 或生成的类型不是您期望的,请提交问题。

有一些已知领域我们希望改进

复杂的 JSON Schema 类型

JSON schema 可以表达各种类型。其中一些在 Rust 中很容易建模;而另一些则不然。处理这些特殊类型需要做大量工作。用户提供的示例在此方面非常有帮助。

有界数字

有界数字处理得不是很好。例如,考虑以下模式

{
  "type": "integer",
  "minimum": 1,
  "maximum": 6
}

生成的类型不会强制执行这些值约束。

可配置的依赖关系

format 设置为 uuid 的字符串模式将生成 uuid::Uuid 类型;类似地,将 format 设置为 date 转换为 chrono::naive::NaiveDate。对于不想依赖 uuidchrono 的用户,Typify 可选择将这些表示为 String(或作为其他消费者指定的类型)。

依赖关系

~3.5–5MB
~102K SLoC