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 | ir::TypeDef::Any => {}
102 }
103 }
104 for op in &ir.operations {
105 for p in op
106 .path_params
107 .iter()
108 .chain(&op.query_params)
109 .chain(&op.header_params)
110 .chain(&op.cookie_params)
111 {
112 check(&p.r#type)?;
113 }
114 if let Some(b) = &op.request_body {
115 for c in &b.content {
116 check(&c.r#type)?;
117 }
118 }
119 for r in &op.responses {
120 for c in &r.content {
121 check(&c.r#type)?;
122 }
123 }
124 }
125 Ok(())
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn dangling_ref_is_rejected() {
134 let bad = ir::Ir {
135 info: ir::ApiInfo {
136 title: "x".into(),
137 version: "0".into(),
138 summary: None,
139 description: None,
140 terms_of_service: None,
141 contact: None,
142 license_name: None,
143 license_url: None,
144 license_identifier: None,
145 extensions: vec![],
146 },
147 operations: vec![],
148 types: vec![ir::NamedType {
149 id: "Wrap".into(),
150 original_name: None,
151 title: None,
152 description: None,
153 deprecated: false,
154 read_only: false,
155 write_only: false,
156 external_docs: None,
157 default: None,
158 examples: vec![],
159 xml: None,
160 definition: ir::TypeDef::Array(ir::ArrayType {
161 items: "Missing".into(),
162 constraints: ir::ArrayConstraints::default(),
163 }),
164 extensions: vec![],
165 location: None,
166 }],
167 security_schemes: vec![],
168 servers: vec![],
169 webhooks: vec![],
170 external_docs: None,
171 tags: vec![],
172 json_schema_dialect: None,
173 self_url: None,
174 values: vec![],
175 };
176 assert_eq!(
177 validate_refs(&bad).unwrap_err(),
178 BindgenError::DanglingTypeRef("Missing".into())
179 );
180 }
181}