1use 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)]
88 pub variant_parameter_groups: Vec<VariantParameterGroup>,
89 #[serde(default)]
90 pub return_values: Vec<Parameter>,
91 #[serde(default)]
92 pub format: MethodFormat,
93}
94
95#[derive(Debug, Deserialize)]
97pub struct VariantParameterGroup {
98 pub name: String,
99 #[serde(default)]
100 pub description: String,
101 #[serde(default)]
102 pub parameters: Vec<Parameter>,
103}
104
105#[derive(Debug, Default, Deserialize)]
106pub struct MethodFormat {
107 #[serde(default)]
108 pub takes_table: bool,
109}
110
111#[derive(Debug, Deserialize)]
112pub struct Attribute {
113 pub name: String,
114 #[serde(default)]
115 pub description: String,
116 #[serde(default)]
117 pub read_type: Option<ApiType>,
118 #[serde(default)]
119 pub write_type: Option<ApiType>,
120 #[serde(default)]
121 pub optional: bool,
122}
123
124#[derive(Debug, Clone, Deserialize)]
125pub struct Parameter {
126 #[serde(default)]
127 pub name: String,
128 #[serde(default)]
129 pub description: String,
130 #[serde(rename = "type")]
131 pub type_name: ApiType,
132 #[serde(default)]
133 pub optional: bool,
134 #[serde(default)]
136 pub order: u32,
137}
138
139#[derive(Debug, Clone, Deserialize)]
140#[serde(transparent)]
141pub struct ApiType(pub serde_json::Value);
142
143impl ApiType {
144 pub fn as_simple_name(&self) -> Option<&str> {
145 self.0.as_str()
146 }
147
148 pub fn complex_type(&self) -> Option<&str> {
149 self.0.get("complex_type").and_then(|value| value.as_str())
150 }
151
152 pub fn child_type(&self, key: &str) -> Option<ApiType> {
153 self.0.get(key).cloned().map(ApiType)
154 }
155
156 pub fn options(&self) -> Vec<ApiType> {
157 self.0
158 .get("options")
159 .and_then(|value| value.as_array())
160 .map(|values| values.iter().cloned().map(ApiType).collect())
161 .unwrap_or_default()
162 }
163
164 pub fn tuple_values(&self) -> Vec<ApiType> {
166 self.0
167 .get("values")
168 .and_then(|v| v.as_array())
169 .map(|values| values.iter().cloned().map(ApiType).collect())
170 .unwrap_or_default()
171 }
172
173 pub fn literal_kind(&self) -> Option<&'static str> {
179 if self.complex_type() != Some("literal") {
181 return None;
182 }
183 let value = self.0.get("value")?;
184 if value.is_string() {
185 Some("string")
186 } else if value.is_number() {
187 Some("number")
188 } else if value.is_boolean() {
189 Some("boolean")
190 } else {
191 None
192 }
193 }
194
195 pub fn parameters(&self) -> Vec<(String, ApiType, bool)> {
197 self.0
198 .get("parameters")
199 .and_then(|value| value.as_array())
200 .map(|params| {
201 params
202 .iter()
203 .filter_map(|p| {
204 let name = p.get("name")?.as_str()?.to_string();
205 let ty = ApiType(p.get("type")?.clone());
206 let optional = p.get("optional").and_then(|v| v.as_bool()).unwrap_or(false);
207 Some((name, ty, optional))
208 })
209 .collect()
210 })
211 .unwrap_or_default()
212 }
213
214 pub fn attributes(&self) -> Vec<(String, ApiType, bool)> {
218 self.0
219 .get("attributes")
220 .and_then(|value| value.as_array())
221 .map(|attrs| {
222 attrs
223 .iter()
224 .filter_map(|a| {
225 let name = a.get("name")?.as_str()?.to_string();
226 let ty = a
227 .get("read_type")
228 .or_else(|| a.get("write_type"))
229 .cloned()
230 .map(ApiType)?;
231 let optional = a.get("optional").and_then(|v| v.as_bool()).unwrap_or(false);
232 Some((name, ty, optional))
233 })
234 .collect()
235 })
236 .unwrap_or_default()
237 }
238
239 pub fn is_flag_set_dictionary(&self) -> bool {
241 if self.complex_type() != Some("dictionary") {
242 return false;
243 }
244 let Some(value) = self.child_type("value") else {
245 return false;
246 };
247 value.complex_type() == Some("literal")
248 && value.0.get("value").and_then(serde_json::Value::as_bool) == Some(true)
249 }
250
251 pub fn non_nil_options(&self) -> Vec<ApiType> {
253 self.options()
254 .into_iter()
255 .filter(|option| option.as_simple_name() != Some("nil"))
256 .collect()
257 }
258
259 pub fn is_homog_string_literal_union(&self) -> bool {
261 if self.complex_type() != Some("union") {
262 return false;
263 }
264 let non_nil = self.non_nil_options();
265 !non_nil.is_empty()
266 && non_nil
267 .iter()
268 .all(|option| option.literal_kind() == Some("string"))
269 }
270
271 pub fn string_literal_values(&self) -> Vec<String> {
275 if !self.is_homog_string_literal_union() {
276 return Vec::new();
277 }
278 self.non_nil_options()
279 .into_iter()
280 .filter_map(|option| {
281 option
282 .0
283 .get("value")
284 .and_then(|value| value.as_str())
285 .map(str::to_string)
286 })
287 .collect()
288 }
289
290 pub fn union_has_nil(&self) -> bool {
292 self.complex_type() == Some("union")
293 && self
294 .options()
295 .iter()
296 .any(|option| option.as_simple_name() == Some("nil"))
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::ApiType;
303
304 fn parse(json: &str) -> ApiType {
305 ApiType(serde_json::from_str(json).expect("json"))
306 }
307
308 #[test]
309 fn detects_homog_string_literal_union() {
310 let ty = parse(
311 r#"{
312 "complex_type": "union",
313 "options": [
314 {"complex_type": "literal", "value": "left"},
315 {"complex_type": "literal", "value": "right"},
316 "nil"
317 ]
318 }"#,
319 );
320 assert!(ty.is_homog_string_literal_union());
321 assert!(ty.union_has_nil());
322 assert_eq!(
323 ty.string_literal_values(),
324 vec!["left".to_string(), "right".to_string()]
325 );
326 }
327
328 #[test]
329 fn rejects_heterogeneous_union() {
330 let ty = parse(
331 r#"{
332 "complex_type": "union",
333 "options": ["LuaEntity", "LuaEquipment"]
334 }"#,
335 );
336 assert!(!ty.is_homog_string_literal_union());
337 assert!(ty.string_literal_values().is_empty());
338 }
339
340 #[test]
341 fn rejects_non_union() {
342 let ty = parse(r#""string""#);
343 assert!(!ty.is_homog_string_literal_union());
344 }
345}