Skip to main content

forge_ir/
types.rs

1//! Type-system pieces of the IR.
2//!
3//! Every named type lives in [`crate::Ir::types`] keyed by its sanitized [`NamedType::id`].
4//! [`TypeRef`] is just that string. This makes recursion trivial across the WIT
5//! boundary (no recursive records) and keeps the structure flat.
6
7use serde::{Deserialize, Serialize};
8
9use crate::diagnostic::SpecLocation;
10use crate::value::ValueRef;
11
12/// String id into [`crate::Ir::types`].
13pub type TypeRef = String;
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct NamedType {
17    pub id: String,
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub original_name: Option<String>,
20    /// JSON Schema `title` — short human label.
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub title: Option<String>,
23    /// JSON Schema / OAS `description` (CommonMark).
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub description: Option<String>,
26    /// JSON Schema 2020-12 `deprecated`.
27    #[serde(default, skip_serializing_if = "crate::is_false")]
28    pub deprecated: bool,
29    /// JSON Schema `readOnly` at the schema level. Generators that
30    /// distinguish request from response shapes can opt to drop
31    /// `read_only` types from their request surface.
32    #[serde(default, skip_serializing_if = "crate::is_false")]
33    pub read_only: bool,
34    /// JSON Schema `writeOnly` at the schema level. Generators that
35    /// distinguish request from response shapes can opt to drop
36    /// `write_only` types from their response surface.
37    #[serde(default, skip_serializing_if = "crate::is_false")]
38    pub write_only: bool,
39    /// Per-schema `externalDocs` (OAS Schema Object).
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub external_docs: Option<crate::ExternalDocs>,
42    /// JSON Schema `default` at the schema level. `ValueRef` indexes
43    /// into [`crate::Ir::values`]; compound defaults are pooled there.
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub default: Option<ValueRef>,
46    /// OAS `example` / `examples` on the schema. Named entries; 3.0
47    /// `example: <literal>` lands under the synthetic key `"_default"`.
48    #[serde(default, skip_serializing_if = "Vec::is_empty")]
49    pub examples: Vec<(String, crate::Example)>,
50    /// OAS `xml` block — name override, namespace, prefix, attribute
51    /// placement, array wrapping. None unless the spec declared one.
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub xml: Option<crate::XmlObject>,
54    pub definition: TypeDef,
55    /// `x-*` extensions declared on the schema. Each entry pairs a key
56    /// with a [`ValueRef`] into [`crate::Ir::values`]; compound values
57    /// are pooled there.
58    #[serde(default, skip_serializing_if = "Vec::is_empty")]
59    pub extensions: Vec<(String, ValueRef)>,
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub location: Option<SpecLocation>,
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65#[serde(tag = "def", rename_all = "kebab-case")]
66pub enum TypeDef {
67    Primitive(PrimitiveType),
68    Object(ObjectType),
69    Array(ArrayType),
70    EnumString(EnumStringType),
71    EnumInt(EnumIntType),
72    Union(UnionType),
73    /// JSON's `null` as a unit type. The canonical singleton lives in
74    /// [`crate::Ir::types`] under id [`NULL_ID`]; `T | null` is expressed
75    /// as a [`UnionType`] whose variants list contains a `Null` reference
76    /// (canonicalized to last). See issue #107.
77    Null,
78}
79
80/// Canonical pool id for the [`TypeDef::Null`] singleton. See issue #107.
81pub const NULL_ID: &str = "null";
82
83// ---- primitives ----
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct PrimitiveType {
87    pub kind: PrimitiveKind,
88    pub constraints: PrimitiveConstraints,
89}
90
91/// JSON Schema's `type` keyword values, minus the variants that have
92/// their own IR shapes (`object` / `array` / `null`). The `format`
93/// keyword and any width/semantic refinement (`int32` / `int64` /
94/// `float` / `double` / `date` / `uuid` / `byte` / `decimal` / etc.)
95/// land on [`PrimitiveConstraints::format_extension`] verbatim.
96/// Plugins decide whether to produce a richer target-language type
97/// based on the format string. This keeps the IR uniform and
98/// orthogonal — adding new formats never requires an IR/WIT/bindgen
99/// roundtrip.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
101#[serde(rename_all = "kebab-case")]
102pub enum PrimitiveKind {
103    /// JSON Schema `type: "string"`.
104    String,
105    /// JSON Schema `type: "integer"`. Width refinements
106    /// (`int32` / `int64`) live in `format_extension`.
107    Integer,
108    /// JSON Schema `type: "number"`. Width refinements
109    /// (`float` / `double`) and `decimal` live in `format_extension`.
110    Number,
111    /// JSON Schema `type: "boolean"`.
112    Bool,
113}
114
115#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
116pub struct PrimitiveConstraints {
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub minimum: Option<ValueRef>,
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub maximum: Option<ValueRef>,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub exclusive_minimum: Option<ValueRef>,
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub exclusive_maximum: Option<ValueRef>,
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub multiple_of: Option<ValueRef>,
127    #[serde(default, skip_serializing_if = "Option::is_none")]
128    pub min_length: Option<u64>,
129    #[serde(default, skip_serializing_if = "Option::is_none")]
130    pub max_length: Option<u64>,
131    #[serde(default, skip_serializing_if = "Option::is_none")]
132    pub pattern: Option<String>,
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    pub format_extension: Option<String>,
135    /// JSON Schema 2020-12 `contentEncoding` (e.g. `base64`,
136    /// `base32`). Describes how the string value is encoded; the
137    /// decoded payload may have its own media type and schema.
138    /// String-only — populated for `PrimitiveKind::String` schemas.
139    #[serde(default, skip_serializing_if = "Option::is_none")]
140    pub content_encoding: Option<String>,
141    /// JSON Schema 2020-12 `contentMediaType` (e.g. `image/png`,
142    /// `application/json`). The media type of the decoded payload.
143    #[serde(default, skip_serializing_if = "Option::is_none")]
144    pub content_media_type: Option<String>,
145    /// OAS 3.2 / JSON Schema 2020-12 `contentSchema` — schema for the
146    /// decoded payload (after applying `contentEncoding`). Carries a
147    /// `TypeRef` into [`crate::Ir::types`] so generators that decode
148    /// `contentEncoding` content can validate / shape it.
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub content_schema: Option<TypeRef>,
151}
152
153// ---- arrays ----
154
155#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156pub struct ArrayType {
157    pub items: TypeRef,
158    pub constraints: ArrayConstraints,
159}
160
161#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
162pub struct ArrayConstraints {
163    #[serde(default, skip_serializing_if = "Option::is_none")]
164    pub min_items: Option<u64>,
165    #[serde(default, skip_serializing_if = "Option::is_none")]
166    pub max_items: Option<u64>,
167    #[serde(default)]
168    pub unique_items: bool,
169}
170
171// ---- objects ----
172
173#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub struct ObjectType {
175    pub properties: Vec<Property>,
176    pub additional_properties: AdditionalProperties,
177    pub constraints: ObjectConstraints,
178}
179
180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct Property {
182    pub name: String,
183    #[serde(rename = "type")]
184    pub r#type: TypeRef,
185    /// `true` when the spec lists this property name in the parent
186    /// schema's `required` array. Moved here from
187    /// `ObjectType.required` for a uniform spelling with
188    /// `Parameter.required`.
189    #[serde(default)]
190    pub required: bool,
191    /// JSON Schema `title` — short human label.
192    #[serde(default, skip_serializing_if = "Option::is_none")]
193    pub title: Option<String>,
194    /// JSON Schema / OAS `description` (CommonMark).
195    #[serde(default, skip_serializing_if = "Option::is_none")]
196    pub description: Option<String>,
197    /// JSON Schema 2020-12 `deprecated`.
198    #[serde(default)]
199    pub deprecated: bool,
200    #[serde(default)]
201    pub read_only: bool,
202    #[serde(default)]
203    pub write_only: bool,
204    /// Per-schema `externalDocs` (OAS Schema Object).
205    #[serde(default, skip_serializing_if = "Option::is_none")]
206    pub external_docs: Option<crate::ExternalDocs>,
207    /// JSON Schema `default` for the property. `ValueRef` indexes
208    /// into [`crate::Ir::values`]; compound defaults are pooled there.
209    #[serde(default, skip_serializing_if = "Option::is_none")]
210    pub default: Option<ValueRef>,
211    /// OAS `example` / `examples` for this property. Named entries;
212    /// 3.0 `example: <literal>` lands under the synthetic key
213    /// `"_default"`.
214    #[serde(default, skip_serializing_if = "Vec::is_empty")]
215    pub examples: Vec<(String, crate::Example)>,
216    /// `x-*` extensions declared on the property's schema. Each entry
217    /// pairs a key with a [`ValueRef`] into [`crate::Ir::values`];
218    /// compound values are pooled there.
219    #[serde(default, skip_serializing_if = "Vec::is_empty")]
220    pub extensions: Vec<(String, ValueRef)>,
221}
222
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
224#[serde(tag = "kind", rename_all = "kebab-case")]
225pub enum AdditionalProperties {
226    Forbidden,
227    Any,
228    Typed { r#type: TypeRef },
229}
230
231#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
232pub struct ObjectConstraints {
233    #[serde(default, skip_serializing_if = "Option::is_none")]
234    pub min_properties: Option<u64>,
235    #[serde(default, skip_serializing_if = "Option::is_none")]
236    pub max_properties: Option<u64>,
237}
238
239// ---- enums ----
240
241#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
242pub struct EnumStringType {
243    pub values: Vec<EnumStringValue>,
244}
245
246/// One value in a string-typed enum. OAS / JSON Schema does not define
247/// per-value documentation, so this is a bare value. Per-value docs
248/// would have to come from a vendor extension and are out of scope.
249#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250pub struct EnumStringValue {
251    pub value: String,
252}
253
254#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
255pub struct EnumIntType {
256    pub values: Vec<EnumIntValue>,
257    pub kind: IntKind,
258}
259
260/// One value in an integer-typed enum. OAS / JSON Schema does not
261/// define per-value documentation; see [`EnumStringValue`].
262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
263pub struct EnumIntValue {
264    pub value: i64,
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
268#[serde(rename_all = "kebab-case")]
269pub enum IntKind {
270    Int32,
271    Int64,
272}
273
274// ---- unions ----
275
276#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
277pub struct UnionType {
278    pub variants: Vec<UnionVariant>,
279    #[serde(default, skip_serializing_if = "Option::is_none")]
280    pub discriminator: Option<Discriminator>,
281    pub kind: UnionKind,
282}
283
284#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
285pub struct UnionVariant {
286    #[serde(rename = "type")]
287    pub r#type: TypeRef,
288    #[serde(default, skip_serializing_if = "Option::is_none")]
289    pub tag: Option<String>,
290}
291
292#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
293#[serde(rename_all = "kebab-case")]
294pub enum UnionKind {
295    OneOf,
296    AnyOf,
297}
298
299#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
300pub struct Discriminator {
301    pub property_name: String,
302    pub mapping: Vec<(String, TypeRef)>,
303    /// `x-*` extensions declared on the discriminator object. Each
304    /// entry pairs a key with a [`ValueRef`] into [`crate::Ir::values`];
305    /// compound extension values are pooled there.
306    #[serde(default, skip_serializing_if = "Vec::is_empty")]
307    pub extensions: Vec<(String, ValueRef)>,
308}