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日

#2380 in Rust 模式

Download history 17279/week @ 2024-04-16 19547/week @ 2024-04-23 17609/week @ 2024-04-30 20651/week @ 2024-05-07 57988/week @ 2024-05-14 82611/week @ 2024-05-21 90707/week @ 2024-05-28 96436/week @ 2024-06-04 95856/week @ 2024-06-11 83247/week @ 2024-06-18 90653/week @ 2024-06-25 89761/week @ 2024-07-02 91640/week @ 2024-07-09 98513/week @ 2024-07-16 95327/week @ 2024-07-23 95150/week @ 2024-07-30

402,022 每月下载量
216 个crate中使用 (直接使用2个)

Apache-2.0

490KB
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 根据模式的基本属性以几种不同的方式转换 JSON Schema 类型

内置类型

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

包含已知格式的字符串模式以适当的Rust类型表示。例如,{ "type": "string", "format": "uuid" }表示为uuid::Uuid(这需要将uuid crate作为依赖项包含)。

数组

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

对象

通常,对象转换为Rust结构体。但是,如果模式未定义属性,当additionalProperties模式指定时,Typify会发出HashMap<String, T>,否则为HashMap<String, serde_json::Value>

生成的struct属性不在required集合中时,通常表示为带有#[serde(default)]属性的Option<T>。具有已具有默认值(如Vec<T>)的类型的不必要属性仅获得#[serde(default)]属性。

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版本,则不使用该类型。

crate作者可以选择遵守比semver提供更大的稳定性。如果扩展版本是>=0.1.0, <1.00,则crate作者承诺在所有版本直到1.0.0之前,给定类型的模式兼容性。重要的是crate作者要填充version字段,以维护类型的可用性。例如,虽然*是一个有效的值,但它仅在有关类型的crate的第一个版本中可用,并且在任何后续版本中都不会不兼容地更改时才是有效的。

类型参数

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 crate,将使用(非生成的)类型 #/$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

最适合 build.rs 脚本,因为传递依赖项可能未安装 rustfmt,因此应该是自包含的。

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

无格式化

如果没有人会看到代码(这几乎从不可能是这种情况)。

typespace.to_stream().to_string()

正在进行中

Typify 是一个正在进行中的项目。影响输出的更改将通过将 crate 版本号更新为破坏性更改来表示。

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

有一些我们知道我们想改进的领域

复杂的 JSON Schema 类型

JSON模式可以表示多种类型。其中一些在Rust中易于建模,而另一些则不是。处理某些特殊类型的工作量很大。用户的示例在这方面非常有帮助。

有界数字

有界数字的处理并不太好。例如,考虑以下模式

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

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

可配置的依赖项

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

依赖项

~3.5–5MB
~101K SLoC