1#![forbid(unsafe_code)]
15
16pub mod bindings;
17pub mod convert;
18
19use forge_ir as ir;
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum BindgenError {
24 DanglingTypeRef(String),
27 DanglingDiscriminator { type_ref: String },
30 DuplicateTypeId(String),
32 BadStatusRange(u8),
34 DanglingValueRef(u32),
36}
37
38impl core::fmt::Display for BindgenError {
39 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40 match self {
41 BindgenError::DanglingTypeRef(r) => write!(f, "dangling type ref: {r}"),
42 BindgenError::DanglingDiscriminator { type_ref } => {
43 write!(f, "dangling discriminator type ref: {type_ref}")
44 }
45 BindgenError::DuplicateTypeId(id) => write!(f, "duplicate type id: {id}"),
46 BindgenError::BadStatusRange(c) => write!(f, "invalid status range class: {c}"),
47 BindgenError::DanglingValueRef(r) => write!(f, "dangling value ref: {r}"),
48 }
49 }
50}
51
52impl std::error::Error for BindgenError {}
53
54pub fn validate_refs(ir: &ir::Ir) -> Result<(), BindgenError> {
58 let mut ids = std::collections::HashSet::with_capacity(ir.types.len());
59 for t in &ir.types {
60 if !ids.insert(t.id.as_str()) {
61 return Err(BindgenError::DuplicateTypeId(t.id.clone()));
62 }
63 }
64 let check = |r: &str| -> Result<(), BindgenError> {
65 if ids.contains(r) {
66 Ok(())
67 } else {
68 Err(BindgenError::DanglingTypeRef(r.to_string()))
69 }
70 };
71
72 for t in &ir.types {
73 match &t.definition {
74 ir::TypeDef::Array(a) => check(&a.items)?,
75 ir::TypeDef::Object(o) => {
76 for p in &o.properties {
77 check(&p.r#type)?;
78 }
79 if let ir::AdditionalProperties::Typed { r#type } = &o.additional_properties {
80 check(r#type)?;
81 }
82 }
83 ir::TypeDef::Union(u) => {
84 for v in &u.variants {
85 check(&v.r#type)?;
86 }
87 if let Some(d) = &u.discriminator {
88 for (_, r) in &d.mapping {
89 if !ids.contains(r.as_str()) {
90 return Err(BindgenError::DanglingDiscriminator {
91 type_ref: r.clone(),
92 });
93 }
94 }
95 }
96 }
97 ir::TypeDef::Primitive(_)
98 | ir::TypeDef::EnumString(_)
99 | ir::TypeDef::EnumInt(_)
100 | ir::TypeDef::Null => {}
101 }
102 }
103 for op in &ir.operations {
104 for p in op
105 .path_params
106 .iter()
107 .chain(&op.query_params)
108 .chain(&op.header_params)
109 .chain(&op.cookie_params)
110 {
111 check(&p.r#type)?;
112 }
113 if let Some(b) = &op.request_body {
114 for c in &b.content {
115 check(&c.r#type)?;
116 }
117 }
118 for r in &op.responses {
119 for c in &r.content {
120 check(&c.r#type)?;
121 }
122 }
123 }
124 Ok(())
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn dangling_ref_is_rejected() {
133 let bad = ir::Ir {
134 info: ir::ApiInfo {
135 title: "x".into(),
136 version: "0".into(),
137 description: None,
138 summary: None,
139 terms_of_service: None,
140 contact: None,
141 license_name: None,
142 license_url: None,
143 license_identifier: None,
144 extensions: vec![],
145 },
146 operations: vec![],
147 types: vec![ir::NamedType {
148 id: "Wrap".into(),
149 original_name: None,
150 documentation: None,
151 title: None,
152 read_only: false,
153 write_only: false,
154 external_docs: None,
155 default: None,
156 examples: vec![],
157 xml: None,
158 definition: ir::TypeDef::Array(ir::ArrayType {
159 items: "Missing".into(),
160 constraints: ir::ArrayConstraints::default(),
161 }),
162 extensions: vec![],
163 location: None,
164 }],
165 security_schemes: vec![],
166 servers: vec![],
167 webhooks: vec![],
168 external_docs: None,
169 tags: vec![],
170 json_schema_dialect: None,
171 self_url: None,
172 values: vec![],
173 };
174 assert_eq!(
175 validate_refs(&bad).unwrap_err(),
176 BindgenError::DanglingTypeRef("Missing".into())
177 );
178 }
179}