feat: Added typegen for jellyfin structs and enums
This commit is contained in:
@@ -4,7 +4,10 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
heck = "0.5.0"
|
||||
indexmap = { version = "2.12.0", features = ["serde"] }
|
||||
prettyplease = "0.2.37"
|
||||
proc-macro2 = "1.0.103"
|
||||
quote = "1.0.41"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use heck::*;
|
||||
use indexmap::IndexMap;
|
||||
use syn::{FieldsNamed, parse_quote, token::Enum};
|
||||
|
||||
const KEYWORDS: &[&str] = &[
|
||||
"type", "match", "enum", "struct", "fn", "mod", "pub", "use", "crate", "self", "super", "as",
|
||||
"in", "let", "mut", "ref", "static", "trait", "where",
|
||||
];
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||
pub struct JellyfinOpenapi {
|
||||
@@ -16,21 +21,87 @@ pub struct Schema {
|
||||
_type: Types,
|
||||
properties: Option<indexmap::IndexMap<String, Property>>,
|
||||
#[serde(rename = "oneOf")]
|
||||
one_of: Option<Vec<EnumVariant>>,
|
||||
one_of: Option<Vec<RefName>>,
|
||||
#[serde(rename = "enum")]
|
||||
_enum: Option<Vec<String>>,
|
||||
description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||
pub struct EnumVariant {
|
||||
pub struct RefName {
|
||||
#[serde(rename = "$ref")]
|
||||
_ref: String,
|
||||
}
|
||||
|
||||
impl RefName {
|
||||
pub fn get_type_name(&self) -> String {
|
||||
self._ref.split('/').last().unwrap().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||
pub struct Property {
|
||||
#[serde(rename = "type")]
|
||||
_type: Option<Types>,
|
||||
nullable: Option<bool>,
|
||||
format: Option<String>,
|
||||
items: Option<Box<Property>>,
|
||||
#[serde(rename = "additionalProperties")]
|
||||
additional_properties: Option<Box<Property>>,
|
||||
#[serde(rename = "enum")]
|
||||
_enum: Option<Vec<String>>,
|
||||
#[serde(rename = "allOf")]
|
||||
all_of: Option<Vec<RefName>>,
|
||||
description: Option<String>,
|
||||
#[serde(rename = "$ref")]
|
||||
_ref: Option<String>,
|
||||
}
|
||||
|
||||
impl Property {
|
||||
pub fn to_string(&self) -> String {
|
||||
let out = match self._type {
|
||||
Some(Types::String) => "String".to_string(),
|
||||
Some(Types::Integer) => match self.format.as_deref() {
|
||||
Some("int32") => "i32".to_string(),
|
||||
Some("int64") => "i64".to_string(),
|
||||
_ => "i32".to_string(),
|
||||
},
|
||||
Some(Types::Boolean) => "bool".to_string(),
|
||||
Some(Types::Number) => "f64".to_string(),
|
||||
Some(Types::Array) => {
|
||||
if let Some(ref items) = self.items {
|
||||
format!("Vec<{}>", items.to_string())
|
||||
} else {
|
||||
"Vec<()>".to_string()
|
||||
}
|
||||
}
|
||||
Some(Types::Object) => {
|
||||
if let Some(properties) = &self.additional_properties {
|
||||
format!(
|
||||
"std::collections::HashMap<String, {}>",
|
||||
properties.to_string()
|
||||
)
|
||||
} else {
|
||||
"std::collections::HashMap<String, serde_json::Value>".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
None => {
|
||||
if let Some(ref _ref) = self._ref {
|
||||
_ref.split('/').last().unwrap().to_string()
|
||||
} else if let Some(ref all_of) = self.all_of {
|
||||
all_of[0].get_type_name()
|
||||
} else {
|
||||
"()".into()
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(true) = self.nullable {
|
||||
format!("Option<{}>", out)
|
||||
} else {
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||
@@ -58,7 +129,7 @@ fn main() {
|
||||
.components
|
||||
.schemas
|
||||
.iter()
|
||||
.filter(|(k, v)| v.one_of.is_some())
|
||||
.filter(|(_, v)| v._enum.is_some())
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
|
||||
@@ -67,14 +138,86 @@ fn main() {
|
||||
.map(|(key, value)| {
|
||||
let fields = value
|
||||
.properties
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.expect("Possible properties")
|
||||
.iter()
|
||||
.map(|(name, _type)| format!("{}:{}", name, _type.is_));
|
||||
parse_quote! {
|
||||
.map(|(name, _type)| {
|
||||
let og_name = name.clone();
|
||||
let name = modify_keyword(&name.to_snake_case());
|
||||
let _type = _type.to_string();
|
||||
let _type = if _type.contains(key) {
|
||||
_type.replace(&format!("<{}>", key), format!("<Box<{}>>", key).as_str())
|
||||
} else {
|
||||
_type
|
||||
};
|
||||
syn::Field {
|
||||
attrs: syn::parse_quote! {
|
||||
#[serde(rename = #og_name)]
|
||||
},
|
||||
mutability: syn::FieldMutability::None,
|
||||
vis: syn::Visibility::Public(syn::Token,
|
||||
)),
|
||||
ident: Some(syn::Ident::new(&name, proc_macro2::Span::call_site())),
|
||||
colon_token: Some(syn::token::Colon(proc_macro2::Span::call_site())),
|
||||
ty: syn::parse_str(&_type).expect("Failed to parse type"),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<syn::Field>>();
|
||||
let key = modify_keyword(key);
|
||||
let key = syn::Ident::new(&key.to_pascal_case(), proc_macro2::Span::call_site());
|
||||
let tokens = quote::quote! {
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct #key {
|
||||
|
||||
#(#fields),*
|
||||
}
|
||||
}
|
||||
};
|
||||
syn::parse2(tokens).expect("Failed to parse struct")
|
||||
})
|
||||
.collect();
|
||||
|
||||
let syn_enums = enums
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
let variants = value
|
||||
._enum
|
||||
.as_ref()
|
||||
.expect("Possible oneOf")
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
// let variant_name = modify_keyword(&ref_name.to_pascal_case());
|
||||
syn::Ident::new(&variant.to_pascal_case(), proc_macro2::Span::call_site())
|
||||
})
|
||||
.collect::<Vec<syn::Ident>>();
|
||||
let key = modify_keyword(key);
|
||||
let key = syn::Ident::new(&key.to_pascal_case(), proc_macro2::Span::call_site());
|
||||
let tokens = quote::quote! {
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum #key {
|
||||
#(#variants),*
|
||||
}
|
||||
};
|
||||
syn::parse2(tokens).expect("Failed to parse enum")
|
||||
})
|
||||
.collect::<Vec<syn::ItemEnum>>();
|
||||
|
||||
let file = syn::File {
|
||||
shebang: None,
|
||||
attrs: vec![],
|
||||
items: syn_structs
|
||||
.into_iter()
|
||||
.map(syn::Item::Struct)
|
||||
.chain(syn_enums.into_iter().map(syn::Item::Enum))
|
||||
.collect(),
|
||||
};
|
||||
let code = prettyplease::unparse(&file);
|
||||
std::fs::write("jellyfin.rs", code).expect("Unable to write file");
|
||||
}
|
||||
|
||||
fn modify_keyword(name: &str) -> String {
|
||||
if KEYWORDS.contains(&name) {
|
||||
format!("_{}", name)
|
||||
} else {
|
||||
name.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user