factorio_api_gen/
schema.rs1use serde::Deserialize;
2
3#[derive(Debug, Deserialize)]
4pub struct RuntimeApi {
5 pub application_version: String,
6 pub api_version: u32,
7 pub classes: Vec<Class>,
8 pub events: Vec<Event>,
9 pub defines: Vec<Define>,
10 pub global_objects: Vec<GlobalObject>,
11 #[serde(default)]
12 pub global_functions: Vec<Method>,
13 #[serde(default)]
14 pub concepts: Vec<Concept>,
15}
16
17#[derive(Debug, Deserialize)]
18pub struct Class {
19 pub name: String,
20 #[serde(default)]
21 pub description: String,
22 #[serde(default)]
23 pub r#abstract: bool,
24 #[serde(default)]
25 pub parent: Option<String>,
26 #[serde(default)]
27 pub methods: Vec<Method>,
28 #[serde(default)]
29 pub attributes: Vec<Attribute>,
30}
31
32#[derive(Debug, Deserialize)]
33pub struct Event {
34 pub name: String,
35 #[serde(default)]
36 pub description: String,
37 #[serde(default)]
38 pub filter: Option<String>,
39 #[serde(default)]
40 pub data: Vec<Parameter>,
41}
42
43#[derive(Debug, Deserialize)]
44pub struct Concept {
45 pub name: String,
46 #[serde(default)]
47 pub description: String,
48 #[serde(rename = "type")]
49 pub type_name: ApiType,
50}
51
52#[derive(Debug, Deserialize)]
53pub struct Define {
54 pub name: String,
55 #[serde(default)]
56 pub description: String,
57 #[serde(default)]
58 pub values: Vec<DefineValue>,
59 #[serde(default)]
60 pub subkeys: Vec<Define>,
61}
62
63#[derive(Debug, Deserialize)]
64pub struct DefineValue {
65 pub name: String,
66 #[serde(default)]
67 pub description: String,
68}
69
70#[derive(Debug, Deserialize)]
71pub struct GlobalObject {
72 pub name: String,
73 #[serde(default)]
74 pub description: String,
75 #[serde(rename = "type")]
76 pub type_name: ApiType,
77}
78
79#[derive(Debug, Deserialize)]
80pub struct Method {
81 pub name: String,
82 #[serde(default)]
83 pub description: String,
84 #[serde(default)]
85 pub parameters: Vec<Parameter>,
86 #[serde(default)]
87 pub return_values: Vec<Parameter>,
88 #[serde(default)]
89 pub format: MethodFormat,
90}
91
92#[derive(Debug, Default, Deserialize)]
93pub struct MethodFormat {
94 #[serde(default)]
95 pub takes_table: bool,
96}
97
98#[derive(Debug, Deserialize)]
99pub struct Attribute {
100 pub name: String,
101 #[serde(default)]
102 pub description: String,
103 #[serde(default)]
104 pub read_type: Option<ApiType>,
105 #[serde(default)]
106 pub write_type: Option<ApiType>,
107 #[serde(default)]
108 pub optional: bool,
109}
110
111#[derive(Debug, Clone, Deserialize)]
112pub struct Parameter {
113 #[serde(default)]
114 pub name: String,
115 #[serde(default)]
116 pub description: String,
117 #[serde(rename = "type")]
118 pub type_name: ApiType,
119 #[serde(default)]
120 pub optional: bool,
121 #[serde(default)]
123 pub order: u32,
124}
125
126#[derive(Debug, Clone, Deserialize)]
127#[serde(transparent)]
128pub struct ApiType(pub serde_json::Value);
129
130impl ApiType {
131 pub fn as_simple_name(&self) -> Option<&str> {
132 self.0.as_str()
133 }
134
135 pub fn complex_type(&self) -> Option<&str> {
136 self.0.get("complex_type").and_then(|value| value.as_str())
137 }
138
139 pub fn child_type(&self, key: &str) -> Option<ApiType> {
140 self.0.get(key).cloned().map(ApiType)
141 }
142
143 pub fn options(&self) -> Vec<ApiType> {
144 self.0
145 .get("options")
146 .and_then(|value| value.as_array())
147 .map(|values| values.iter().cloned().map(ApiType).collect())
148 .unwrap_or_default()
149 }
150
151 pub fn tuple_values(&self) -> Vec<ApiType> {
153 self.0
154 .get("values")
155 .and_then(|v| v.as_array())
156 .map(|values| values.iter().cloned().map(ApiType).collect())
157 .unwrap_or_default()
158 }
159
160 pub fn literal_kind(&self) -> Option<&'static str> {
166 if self.complex_type() != Some("literal") {
168 return None;
169 }
170 let value = self.0.get("value")?;
171 if value.is_string() {
172 Some("string")
173 } else if value.is_number() {
174 Some("number")
175 } else if value.is_boolean() {
176 Some("boolean")
177 } else {
178 None
179 }
180 }
181
182 pub fn parameters(&self) -> Vec<(String, ApiType, bool)> {
184 self.0
185 .get("parameters")
186 .and_then(|value| value.as_array())
187 .map(|params| {
188 params
189 .iter()
190 .filter_map(|p| {
191 let name = p.get("name")?.as_str()?.to_string();
192 let ty = ApiType(p.get("type")?.clone());
193 let optional = p.get("optional").and_then(|v| v.as_bool()).unwrap_or(false);
194 Some((name, ty, optional))
195 })
196 .collect()
197 })
198 .unwrap_or_default()
199 }
200
201 pub fn attributes(&self) -> Vec<(String, ApiType, bool)> {
205 self.0
206 .get("attributes")
207 .and_then(|value| value.as_array())
208 .map(|attrs| {
209 attrs
210 .iter()
211 .filter_map(|a| {
212 let name = a.get("name")?.as_str()?.to_string();
213 let ty = a
214 .get("read_type")
215 .or_else(|| a.get("write_type"))
216 .cloned()
217 .map(ApiType)?;
218 let optional = a.get("optional").and_then(|v| v.as_bool()).unwrap_or(false);
219 Some((name, ty, optional))
220 })
221 .collect()
222 })
223 .unwrap_or_default()
224 }
225
226 pub fn is_flag_set_dictionary(&self) -> bool {
228 if self.complex_type() != Some("dictionary") {
229 return false;
230 }
231 let Some(value) = self.child_type("value") else {
232 return false;
233 };
234 value.complex_type() == Some("literal")
235 && value.0.get("value").and_then(serde_json::Value::as_bool) == Some(true)
236 }
237
238 pub fn non_nil_options(&self) -> Vec<ApiType> {
240 self.options()
241 .into_iter()
242 .filter(|option| option.as_simple_name() != Some("nil"))
243 .collect()
244 }
245
246 pub fn is_homog_string_literal_union(&self) -> bool {
248 if self.complex_type() != Some("union") {
249 return false;
250 }
251 let non_nil = self.non_nil_options();
252 !non_nil.is_empty()
253 && non_nil
254 .iter()
255 .all(|option| option.literal_kind() == Some("string"))
256 }
257
258 pub fn string_literal_values(&self) -> Vec<String> {
262 if !self.is_homog_string_literal_union() {
263 return Vec::new();
264 }
265 self.non_nil_options()
266 .into_iter()
267 .filter_map(|option| {
268 option
269 .0
270 .get("value")
271 .and_then(|value| value.as_str())
272 .map(str::to_string)
273 })
274 .collect()
275 }
276
277 pub fn union_has_nil(&self) -> bool {
279 self.complex_type() == Some("union")
280 && self
281 .options()
282 .iter()
283 .any(|option| option.as_simple_name() == Some("nil"))
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::ApiType;
290
291 fn parse(json: &str) -> ApiType {
292 ApiType(serde_json::from_str(json).expect("json"))
293 }
294
295 #[test]
296 fn detects_homog_string_literal_union() {
297 let ty = parse(
298 r#"{
299 "complex_type": "union",
300 "options": [
301 {"complex_type": "literal", "value": "left"},
302 {"complex_type": "literal", "value": "right"},
303 "nil"
304 ]
305 }"#,
306 );
307 assert!(ty.is_homog_string_literal_union());
308 assert!(ty.union_has_nil());
309 assert_eq!(
310 ty.string_literal_values(),
311 vec!["left".to_string(), "right".to_string()]
312 );
313 }
314
315 #[test]
316 fn rejects_heterogeneous_union() {
317 let ty = parse(
318 r#"{
319 "complex_type": "union",
320 "options": ["LuaEntity", "LuaEquipment"]
321 }"#,
322 );
323 assert!(!ty.is_homog_string_literal_union());
324 assert!(ty.string_literal_values().is_empty());
325 }
326
327 #[test]
328 fn rejects_non_union() {
329 let ty = parse(r#""string""#);
330 assert!(!ty.is_homog_string_literal_union());
331 }
332}