factorio_exporter/
api.rs

1use std::{collections::HashMap, fs, path::Path};
2
3use itertools::Itertools;
4use semver::Version;
5use serde_derive::Deserialize;
6use tracing::debug;
7
8use crate::{FactorioExporterError, Result};
9
10/// Loads the API definition from the definition file
11///
12/// This file is usually called `runtime-api.json` and can be found in the
13/// `factorio/doc-html` directory of a full (not headless or demo) Factorio
14/// installation, or on the [API website]. Its content can be browsed in a [nice
15/// format] as well.
16///
17/// [API website]: https://lua-api.factorio.com/latest/runtime-api.json
18/// [nice format]: https://lua-api.factorio.com/
19///
20/// # Arguments
21///
22/// * `api_file_path` - File system path to read the API definition from.
23///
24/// # Example
25///
26/// ```no_run
27/// use factorio_exporter::{ load_api, FactorioExporterError };
28/// use std::path::PathBuf;
29///
30/// let api_spec = PathBuf::from("/home/user/factorio/doc-html/runtime-api.json");
31/// let api = load_api(&api_spec)?;
32///
33/// # Ok::<(), FactorioExporterError>(())
34/// ```
35pub fn load_api(api_file_path: &Path) -> Result<Api> {
36    debug!("Loading API definition file from {}", &api_file_path.display());
37
38    if !api_file_path.is_file() {
39        return Err(FactorioExporterError::FileNotFoundError { file: api_file_path.into() });
40    }
41
42    let s = fs::read_to_string(api_file_path)?;
43    let api: Api = serde_json::from_str(&s)?;
44
45    debug!("parsed API, got {} classes and {} concepts", &api.classes.len(), &api.concepts.len());
46    Ok(api)
47}
48
49/// Meta description of the factorio API.
50///
51/// [Documentation](https://lua-api.factorio.com/latest/json-docs.html)
52/// [JSON data](https://lua-api.factorio.com/latest/runtime-api.json)
53///
54/// This is for API version 3, introduced with Factorio 1.1.62
55#[derive(Debug, Deserialize)]
56#[serde(from = "RawApi")]
57pub struct Api {
58    pub application_version: Version,
59    pub classes: HashMap<String, Class>,
60    pub concepts: HashMap<String, Concept>,
61}
62
63#[derive(Debug, Deserialize)]
64struct RawApi {
65    application: String,
66    application_version: Version,
67    api_version: i32,
68    stage: String,
69    classes: Vec<Class>,
70    concepts: Vec<Concept>,
71}
72
73impl From<RawApi> for Api {
74    fn from(raw: RawApi) -> Self {
75        assert!(raw.application == "factorio");
76        assert!(raw.api_version == 3);
77        assert!(raw.stage == "runtime");
78
79        Api {
80            application_version: raw.application_version,
81            classes: raw.classes.into_iter().map(|class| (class.name.clone(), class)).collect(),
82            concepts: raw
83                .concepts
84                .into_iter()
85                .map(|concept| (concept.name.clone(), concept))
86                .collect(),
87        }
88    }
89}
90
91#[derive(Debug, Deserialize)]
92#[serde(from = "RawClass")]
93pub struct Class {
94    pub name: String,
95    pub attributes: HashMap<String, Attribute>,
96    pub description: String,
97    pub notes: Option<Vec<String>>,
98    pub examples: Option<Vec<String>>,
99    pub order: u64,
100    pub base_classes: Option<Vec<String>>,
101}
102
103pub trait HasAttributes {
104    fn attributes(&self) -> Vec<&Attribute>;
105}
106
107impl HasAttributes for Class {
108    fn attributes(&self) -> Vec<&Attribute> {
109        self.attributes
110            .values()
111            // TODO: Support subclasses and base classes
112            .filter(|a| a.read != Some(false) && a.subclasses.is_none())
113            .sorted_by_key(|attr| &attr.order)
114            .collect_vec()
115    }
116}
117
118#[derive(Debug, Deserialize)]
119struct RawClass {
120    name: String,
121    attributes: Vec<Attribute>,
122    description: String,
123    notes: Option<Vec<String>>,
124    examples: Option<Vec<String>>,
125    order: u64,
126    base_classes: Option<Vec<String>>,
127}
128
129impl From<RawClass> for Class {
130    fn from(raw: RawClass) -> Self {
131        Class {
132            name: raw.name,
133            attributes: raw.attributes.into_iter().map(|attr| (attr.name.clone(), attr)).collect(),
134            description: raw.description,
135            notes: raw.notes,
136            examples: raw.examples,
137            order: raw.order,
138            base_classes: raw.base_classes,
139        }
140    }
141}
142
143#[derive(Debug, Deserialize)]
144pub struct Attribute {
145    pub name: String,
146    pub r#type: Type,
147    pub optional: bool,
148    pub order: u64,
149    pub description: String,
150    pub read: Option<bool>,
151    pub subclasses: Option<Vec<String>>,
152}
153
154#[derive(Debug, Deserialize)]
155pub struct VariantParameterGroup {
156    pub name: String,
157    pub order: u64,
158    pub parameters: Vec<Attribute>,
159}
160
161#[derive(Debug, Deserialize)]
162#[serde(untagged)]
163pub enum LiteralValue {
164    String(String),
165    Boolean(bool),
166}
167
168#[derive(Debug, Deserialize)]
169pub struct Concept {
170    pub name: String,
171    pub r#type: Type,
172    pub description: String,
173    pub notes: Option<Vec<String>>,
174    pub examples: Option<Vec<String>>,
175    pub order: u64,
176}
177
178impl HasAttributes for Concept {
179    fn attributes(&self) -> Vec<&Attribute> {
180        if let Type::Table { parameters, variant_parameter_groups } = &self.r#type {
181            variant_parameter_groups
182                .iter()
183                .flatten()
184                .flat_map(|group| &group.parameters)
185                .chain(parameters)
186                .sorted_by_key(|&a| &a.name)
187                .dedup_by(|a, b| a.name == b.name)
188                .sorted_by_key(|&a| &a.order)
189                .collect_vec()
190        } else {
191            Vec::new()
192        }
193    }
194}
195
196pub fn is_number(r#type: &Type) -> bool {
197    use self::Type::*;
198    match r#type {
199        Int8 | Int | UInt8 | UInt16 | UInt | UInt64 | Double | Float | Number => true,
200        Union { options } => options.iter().all(is_number),
201        _ => false,
202    }
203}
204
205#[derive(Debug, Deserialize)]
206#[serde(from = "RawType")]
207pub enum Type {
208    Int8,
209    Int,
210    UInt8,
211    UInt16,
212    UInt,
213    UInt64,
214    Double,
215    Float,
216    Number,
217    String,
218    Boolean,
219
220    NamedType {
221        name: String,
222    },
223
224    Array {
225        value: Box<Type>,
226    },
227
228    Dictionary {
229        key: Box<Type>,
230        value: Box<Type>,
231    },
232
233    Literal {
234        value: LiteralValue,
235        description: Option<String>,
236    },
237
238    LuaCustomTable {
239        key: Box<Type>,
240        value: Box<Type>,
241    },
242
243    Struct {
244        attributes: Vec<Attribute>,
245    },
246
247    Table {
248        parameters: Vec<Attribute>,
249        variant_parameter_groups: Option<Vec<VariantParameterGroup>>,
250    },
251
252    Tuple {
253        parameters: Vec<Attribute>,
254    },
255
256    Type {
257        value: Box<Type>,
258    },
259
260    Union {
261        options: Vec<Type>,
262    },
263}
264
265impl<'a> From<RawType<'a>> for Type {
266    fn from(raw: RawType<'a>) -> Self {
267        use ComplexType::*;
268        use RawType::*;
269        match raw {
270            String("int8") => Self::Int8,
271            String("int") => Self::Int,
272            String("uint8") => Self::UInt8,
273            String("uint16") => Self::UInt16,
274            String("uint") => Self::UInt,
275            String("uint64") => Self::UInt64,
276            String("double") => Self::Double,
277            String("float") => Self::Float,
278            String("number") => Self::Number,
279            String("string" | "LocalisedString") => Self::String,
280            String("boolean") => Self::Boolean,
281
282            String(name) => Self::NamedType { name: name.into() },
283
284            Complex(Array { value }) => Self::Array { value },
285            Complex(Dictionary { key, value }) => Self::Dictionary { key, value },
286            Complex(Literal { value, description }) => Self::Literal { value, description },
287            Complex(LuaCustomTable { key, value }) => Self::LuaCustomTable { key, value },
288            Complex(Struct { attributes }) => Self::Struct { attributes },
289            Complex(Table { parameters, variant_parameter_groups }) => {
290                Self::Table { parameters, variant_parameter_groups }
291            }
292            Complex(Tuple { parameters }) => Self::Tuple { parameters },
293            Complex(Type { value }) => Self::Type { value },
294            Complex(Union { options }) => Self::Union { options },
295        }
296    }
297}
298
299#[derive(Debug, Deserialize)]
300#[serde(untagged)]
301enum RawType<'a> {
302    String(&'a str),
303    Complex(ComplexType),
304}
305
306#[derive(Debug, Deserialize)]
307#[serde(tag = "complex_type")]
308#[serde(rename_all = "lowercase")]
309enum ComplexType {
310    Array {
311        value: Box<Type>,
312    },
313
314    Dictionary {
315        key: Box<Type>,
316        value: Box<Type>,
317    },
318
319    Literal {
320        value: LiteralValue,
321        description: Option<String>,
322    },
323
324    #[serde(rename = "LuaCustomTable")]
325    LuaCustomTable {
326        key: Box<Type>,
327        value: Box<Type>,
328    },
329
330    Struct {
331        attributes: Vec<Attribute>,
332    },
333
334    Table {
335        parameters: Vec<Attribute>,
336        variant_parameter_groups: Option<Vec<VariantParameterGroup>>,
337    },
338
339    Tuple {
340        parameters: Vec<Attribute>,
341    },
342
343    Type {
344        value: Box<Type>,
345    },
346
347    Union {
348        options: Vec<Type>,
349    },
350}