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日

过程宏中排名1282

Download history 17665/week @ 2024-04-15 18650/week @ 2024-04-22 18458/week @ 2024-04-29 19548/week @ 2024-05-06 49901/week @ 2024-05-13 83657/week @ 2024-05-20 85303/week @ 2024-05-27 95307/week @ 2024-06-03 96657/week @ 2024-06-10 86750/week @ 2024-06-17 85272/week @ 2024-06-24 87207/week @ 2024-07-01 95892/week @ 2024-07-08 96132/week @ 2024-07-15 98408/week @ 2024-07-22 92346/week @ 2024-07-29

每月下载量385,131
4个crate中使用了(通过typify

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类型

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

内置类型

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

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

数组

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>

生成的 struct 的属性不在 required 集合中,通常表示为带有 #[serde(default)] 属性的 Option<T>。具有默认值的类型(例如 Vec<T>)的非必需属性只需添加 #[serde(default)] 属性(因此您不会看到例如 Option<Vec<T>>)。

OneOf

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

AllOf

‘allOf’ 构造通过合并模式来处理。虽然 typify 尝试保留和共享类型名称,但在合并模式时可能无法总是做到这一点。您可能会遇到跨类型重复的字段;优化此生成是一个活跃的工作领域。

AnyOf

《anyOf》结构更复杂。它可以接近于一个枚举(oneOf),但没有任何特定的变体可能是特定数据的规范或唯一。虽然现在我们(不够精确地)将这些作为具有可选、扁平成员的结构体来建模,但这通常是代码生成的一个较弱的区域。

欢迎并提供描述示例模式和期望输出的说明。

Rust -> 模式 -> 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"
  }
)

版本要求

version字段在x-rust-type扩展中遵循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 包,引用 #/$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()

WIP

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
~103K SLoC