use std::{collections::HashMap, fs, path::Path};
use itertools::Itertools;
use semver::Version;
use serde_derive::Deserialize;
use tracing::debug;
use crate::Result;
const RUNTIME_API_DEFINITION: &str = "doc-html/runtime-api.json";
pub fn load_api(factorio_dir: &Path) -> Result<Api> {
let api_file_path = factorio_dir.join(RUNTIME_API_DEFINITION);
debug!(
"Loading API definition file from {}",
&api_file_path.display()
);
let s = fs::read_to_string(api_file_path)?;
let api: Api = serde_json::from_str(&s)?;
debug!(
"parsed API, got {} classes and {} concepts",
&api.classes.len(),
&api.concepts.len()
);
Ok(api)
}
#[derive(Debug, Deserialize)]
#[serde(from = "RawApi")]
pub struct Api {
pub application_version: Version,
pub classes: HashMap<String, Class>,
pub concepts: HashMap<String, Concept>,
}
#[derive(Debug, Deserialize)]
struct RawApi {
application: String,
application_version: Version,
api_version: i32,
stage: String,
classes: Vec<Class>,
concepts: Vec<Concept>,
}
impl From<RawApi> for Api {
fn from(raw: RawApi) -> Self {
assert!(raw.application == "factorio");
assert!(raw.api_version == 3);
assert!(raw.stage == "runtime");
Api {
application_version: raw.application_version,
classes: raw
.classes
.into_iter()
.map(|class| (class.name.clone(), class))
.collect(),
concepts: raw
.concepts
.into_iter()
.map(|concept| (concept.name.clone(), concept))
.collect(),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(from = "RawClass")]
pub struct Class {
pub name: String,
pub attributes: HashMap<String, Attribute>,
pub description: String,
pub notes: Option<Vec<String>>,
pub examples: Option<Vec<String>>,
pub order: u64,
pub base_classes: Option<Vec<String>>,
}
pub trait HasAttributes {
fn attributes(&self) -> Vec<&Attribute>;
}
impl HasAttributes for Class {
fn attributes(&self) -> Vec<&Attribute> {
self.attributes
.values()
.filter(|a| a.read != Some(false) && a.subclasses == None)
.sorted_by_key(|attr| &attr.order)
.collect_vec()
}
}
#[derive(Debug, Deserialize)]
struct RawClass {
name: String,
attributes: Vec<Attribute>,
description: String,
notes: Option<Vec<String>>,
examples: Option<Vec<String>>,
order: u64,
base_classes: Option<Vec<String>>,
}
impl From<RawClass> for Class {
fn from(raw: RawClass) -> Self {
Class {
name: raw.name,
attributes: raw
.attributes
.into_iter()
.map(|attr| (attr.name.clone(), attr))
.collect(),
description: raw.description,
notes: raw.notes,
examples: raw.examples,
order: raw.order,
base_classes: raw.base_classes,
}
}
}
#[derive(Debug, Deserialize)]
pub struct Attribute {
pub name: String,
pub r#type: Type,
pub optional: bool,
pub order: u64,
pub description: String,
pub read: Option<bool>,
pub subclasses: Option<Vec<String>>,
}
#[derive(Debug, Deserialize)]
pub struct VariantParameterGroup {
pub name: String,
pub order: u64,
pub parameters: Vec<Attribute>,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum LiteralValue {
String(String),
Boolean(bool),
}
#[derive(Debug, Deserialize)]
pub struct Concept {
pub name: String,
pub r#type: Type,
pub description: String,
pub notes: Option<Vec<String>>,
pub examples: Option<Vec<String>>,
pub order: u64,
}
impl HasAttributes for Concept {
fn attributes(&self) -> Vec<&Attribute> {
if let Type::Table {
parameters,
variant_parameter_groups,
} = &self.r#type
{
variant_parameter_groups
.iter()
.flatten()
.flat_map(|group| &group.parameters)
.chain(parameters)
.sorted_by_key(|&a| &a.name)
.dedup_by(|a, b| a.name == b.name)
.sorted_by_key(|&a| &a.order)
.collect_vec()
} else {
Vec::new()
}
}
}
pub fn is_number(r#type: &Type) -> bool {
use self::Type::*;
match r#type {
Int8 | Int | UInt8 | UInt16 | UInt | UInt64 | Double | Float | Number => true,
Union { options } => options.iter().all(is_number),
_ => false,
}
}
#[derive(Debug, Deserialize)]
#[serde(from = "RawType")]
pub enum Type {
Int8,
Int,
UInt8,
UInt16,
UInt,
UInt64,
Double,
Float,
Number,
String,
Boolean,
NamedType {
name: String,
},
Array {
value: Box<Type>,
},
Dictionary {
key: Box<Type>,
value: Box<Type>,
},
Literal {
value: LiteralValue,
description: Option<String>,
},
LuaCustomTable {
key: Box<Type>,
value: Box<Type>,
},
Struct {
attributes: Vec<Attribute>,
},
Table {
parameters: Vec<Attribute>,
variant_parameter_groups: Option<Vec<VariantParameterGroup>>,
},
Tuple {
parameters: Vec<Attribute>,
},
Type {
value: Box<Type>,
},
Union {
options: Vec<Type>,
},
}
impl<'a> From<RawType<'a>> for Type {
fn from(raw: RawType<'a>) -> Self {
use ComplexType::*;
use RawType::*;
match raw {
String("int8") => Self::Int8,
String("int") => Self::Int,
String("uint8") => Self::UInt8,
String("uint16") => Self::UInt16,
String("uint") => Self::UInt,
String("uint64") => Self::UInt64,
String("double") => Self::Double,
String("float") => Self::Float,
String("number") => Self::Number,
String("string" | "LocalisedString") => Self::String,
String("boolean") => Self::Boolean,
String(name) => Self::NamedType { name: name.into() },
Complex(Array { value }) => Self::Array { value },
Complex(Dictionary { key, value }) => Self::Dictionary { key, value },
Complex(Literal { value, description }) => Self::Literal { value, description },
Complex(LuaCustomTable { key, value }) => Self::LuaCustomTable { key, value },
Complex(Struct { attributes }) => Self::Struct { attributes },
Complex(Table {
parameters,
variant_parameter_groups,
}) => Self::Table {
parameters,
variant_parameter_groups,
},
Complex(Tuple { parameters }) => Self::Tuple { parameters },
Complex(Type { value }) => Self::Type { value },
Complex(Union { options }) => Self::Union { options },
}
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum RawType<'a> {
String(&'a str),
Complex(ComplexType),
}
#[derive(Debug, Deserialize)]
#[serde(tag = "complex_type")]
#[serde(rename_all = "lowercase")]
enum ComplexType {
Array {
value: Box<Type>,
},
Dictionary {
key: Box<Type>,
value: Box<Type>,
},
Literal {
value: LiteralValue,
description: Option<String>,
},
#[serde(rename = "LuaCustomTable")]
LuaCustomTable {
key: Box<Type>,
value: Box<Type>,
},
Struct {
attributes: Vec<Attribute>,
},
Table {
parameters: Vec<Attribute>,
variant_parameter_groups: Option<Vec<VariantParameterGroup>>,
},
Tuple {
parameters: Vec<Attribute>,
},
Type {
value: Box<Type>,
},
Union {
options: Vec<Type>,
},
}