feat: Added typegen for jellyfin structs and enums
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -4106,9 +4106,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.94"
|
version = "1.0.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -5934,7 +5934,10 @@ dependencies = [
|
|||||||
name = "typegen"
|
name = "typegen"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iref = { version = "3.2.2", features = ["serde"] }
|
iref = { version = "3.2.2", features = ["serde"] }
|
||||||
reqwest = "0.12.24"
|
reqwest = { version = "0.12.24", features = ["json"] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.145"
|
||||||
thiserror = "2.0.17"
|
thiserror = "2.0.17"
|
||||||
|
|||||||
5414
api/src/jellyfin.rs
Normal file
5414
api/src/jellyfin.rs
Normal file
File diff suppressed because it is too large
Load Diff
104
api/src/lib.rs
104
api/src/lib.rs
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod jellyfin;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum JellyfinApiError {
|
pub enum JellyfinApiError {
|
||||||
@@ -28,15 +29,16 @@ impl JellyfinClient {
|
|||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub async fn authenticate(&mut self) -> Result<()> {
|
pub async fn authenticate(&mut self) -> Result<()> {
|
||||||
// self.post("Users/AuthenticateByName")
|
self.post("Users/AuthenticateByName")
|
||||||
// .json(AuthenticateUserByName {
|
.json(&jellyfin::AuthenticateUserByName {
|
||||||
// username: self.config.username.clone(),
|
username: Some(self.config.username.clone()),
|
||||||
// pw: self.config.password.clone(),
|
pw: Some(self.config.password.clone()),
|
||||||
// })
|
})
|
||||||
// .send()
|
.send()
|
||||||
// .await
|
.await?;
|
||||||
// }
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
@@ -46,87 +48,3 @@ pub struct JellyfinConfig {
|
|||||||
pub server_url: iref::IriBuf,
|
pub server_url: iref::IriBuf,
|
||||||
pub device_id: String,
|
pub device_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub struct AuthenticationResult {
|
|
||||||
user: UserDto,
|
|
||||||
session_info: Option<SessionInfoDto>,
|
|
||||||
access_token: Option<String>,
|
|
||||||
server_id: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub struct UserDto {}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub struct SerssionInfoDto {}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub struct AuthenticateUserByName {
|
|
||||||
username: String,
|
|
||||||
pw: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub struct QuickConnectDto {
|
|
||||||
secret: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct User {
|
|
||||||
id: String,
|
|
||||||
configuration: Configuration,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct Configuration {
|
|
||||||
audio_language_preference: Option<String>,
|
|
||||||
play_default_audio_track: bool,
|
|
||||||
subtitle_language_preference: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct Items {
|
|
||||||
items: Vec<MediaItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub struct MediaItem {
|
|
||||||
// #[serde(rename = "Id")]
|
|
||||||
pub id: String,
|
|
||||||
// #[serde(rename = "Name")]
|
|
||||||
pub name: String,
|
|
||||||
// #[serde(rename = "Type")]
|
|
||||||
pub type_: String,
|
|
||||||
// #[serde(rename = "Path")]
|
|
||||||
pub path: Option<String>,
|
|
||||||
// #[serde(rename = "CollectionType")]
|
|
||||||
pub collection_type: Option<String>,
|
|
||||||
// #[serde(rename = "ProductionYear")]
|
|
||||||
pub year: Option<i32>,
|
|
||||||
// #[serde(rename = "Overview")]
|
|
||||||
pub overview: Option<String>,
|
|
||||||
// #[serde(rename = "CommunityRating")]
|
|
||||||
pub imdb_rating: Option<f32>,
|
|
||||||
// #[serde(rename = "CriticRating")]
|
|
||||||
pub critic_rating: Option<i32>,
|
|
||||||
// #[serde(rename = "RunTimeTicks")]
|
|
||||||
pub runtime_ticks: Option<i64>,
|
|
||||||
// #[serde(rename = "SeriesId")]
|
|
||||||
pub series_id: Option<String>,
|
|
||||||
// #[serde(rename = "SeriesName")]
|
|
||||||
pub series_name: Option<String>,
|
|
||||||
// #[serde(rename = "ParentIndexNumber")]
|
|
||||||
pub parent_index_number: Option<i64>,
|
|
||||||
// #[serde(rename = "IndexNumber")]
|
|
||||||
pub index_number: Option<i64>,
|
|
||||||
}
|
|
||||||
|
|||||||
3897
api/types.rs
3897
api/types.rs
File diff suppressed because it is too large
Load Diff
24
flake.lock
generated
24
flake.lock
generated
@@ -3,11 +3,11 @@
|
|||||||
"advisory-db": {
|
"advisory-db": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1761631338,
|
"lastModified": 1762774274,
|
||||||
"narHash": "sha256-F6dlUrDiShwhMfPR+WoVmaQguGdEwjW9SI4nKlkay7c=",
|
"narHash": "sha256-tigj2sBL6S7zmjpt5JdXtvtGrClvja+/LAnmpU6+MV4=",
|
||||||
"owner": "rustsec",
|
"owner": "rustsec",
|
||||||
"repo": "advisory-db",
|
"repo": "advisory-db",
|
||||||
"rev": "2e45336771e36acf5bcefe7c99280ab214719707",
|
"rev": "df17e8c0d170b71c0a4cca3f165c30030a526060",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -18,11 +18,11 @@
|
|||||||
},
|
},
|
||||||
"crane": {
|
"crane": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1760924934,
|
"lastModified": 1762538466,
|
||||||
"narHash": "sha256-tuuqY5aU7cUkR71sO2TraVKK2boYrdW3gCSXUkF4i44=",
|
"narHash": "sha256-8zrIPl6J+wLm9MH5ksHcW7BUHo7jSNOu0/hA0ohOOaM=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "c6b4d5308293d0d04fcfeee92705017537cad02f",
|
"rev": "0cea393fffb39575c46b7a0318386467272182fe",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -106,11 +106,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1761373498,
|
"lastModified": 1762844143,
|
||||||
"narHash": "sha256-Q/uhWNvd7V7k1H1ZPMy/vkx3F8C13ZcdrKjO7Jv7v0c=",
|
"narHash": "sha256-SlybxLZ1/e4T2lb1czEtWVzDCVSTvk9WLwGhmxFmBxI=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6a08e6bb4e46ff7fcbb53d409b253f6bad8a28ce",
|
"rev": "9da7f1cf7f8a6e2a7cb3001b048546c92a8258b4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -138,11 +138,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1761705569,
|
"lastModified": 1763001554,
|
||||||
"narHash": "sha256-dqljv29XldlKvdTwFw8GkxOQHrz3/13yxdwHW8+nzBI=",
|
"narHash": "sha256-wsfhRTuxu6f06RMmP4JWcq3wWRlmYtQaJZ6b3f+EJ94=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "bca7909cb02f5139e0a490b0ff4bae775ea3ebf6",
|
"rev": "315d97eb753cee8e1aa039a5e622b84d32a454bb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
6
justfile
Normal file
6
justfile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
typegen:
|
||||||
|
@echo "Generating jellyfin type definitions..."
|
||||||
|
cd typegen && cargo run
|
||||||
|
cp typegen/jellyfin.rs api/src/jellyfin.rs
|
||||||
|
rm typegen/jellyfin.rs
|
||||||
|
|
||||||
@@ -4,7 +4,10 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
heck = "0.5.0"
|
||||||
indexmap = { version = "2.12.0", features = ["serde"] }
|
indexmap = { version = "2.12.0", features = ["serde"] }
|
||||||
|
prettyplease = "0.2.37"
|
||||||
|
proc-macro2 = "1.0.103"
|
||||||
quote = "1.0.41"
|
quote = "1.0.41"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.145"
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
use heck::*;
|
||||||
use indexmap::IndexMap;
|
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)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||||
pub struct JellyfinOpenapi {
|
pub struct JellyfinOpenapi {
|
||||||
@@ -16,21 +21,87 @@ pub struct Schema {
|
|||||||
_type: Types,
|
_type: Types,
|
||||||
properties: Option<indexmap::IndexMap<String, Property>>,
|
properties: Option<indexmap::IndexMap<String, Property>>,
|
||||||
#[serde(rename = "oneOf")]
|
#[serde(rename = "oneOf")]
|
||||||
one_of: Option<Vec<EnumVariant>>,
|
one_of: Option<Vec<RefName>>,
|
||||||
|
#[serde(rename = "enum")]
|
||||||
|
_enum: Option<Vec<String>>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||||
pub struct EnumVariant {
|
pub struct RefName {
|
||||||
#[serde(rename = "$ref")]
|
#[serde(rename = "$ref")]
|
||||||
_ref: String,
|
_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)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||||
pub struct Property {
|
pub struct Property {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
_type: Option<Types>,
|
_type: Option<Types>,
|
||||||
nullable: Option<bool>,
|
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)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||||
@@ -58,7 +129,7 @@ fn main() {
|
|||||||
.components
|
.components
|
||||||
.schemas
|
.schemas
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(k, v)| v.one_of.is_some())
|
.filter(|(_, v)| v._enum.is_some())
|
||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -67,14 +138,86 @@ fn main() {
|
|||||||
.map(|(key, value)| {
|
.map(|(key, value)| {
|
||||||
let fields = value
|
let fields = value
|
||||||
.properties
|
.properties
|
||||||
.unwrap()
|
.as_ref()
|
||||||
|
.expect("Possible properties")
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, _type)| format!("{}:{}", name, _type.is_));
|
.map(|(name, _type)| {
|
||||||
parse_quote! {
|
let og_name = name.clone();
|
||||||
pub struct #key {
|
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();
|
.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