WASM SDK implementation
This commit is contained in:
parent
8ab6dd9803
commit
fded21f285
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
|
@ -13,6 +13,7 @@ members = [
|
||||||
"ext_calckey_model",
|
"ext_calckey_model",
|
||||||
"fe_calckey",
|
"fe_calckey",
|
||||||
"magnetar_common",
|
"magnetar_common",
|
||||||
|
"magnetar_sdk",
|
||||||
"core"
|
"core"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
async-trait = "0.1"
|
||||||
axum = "0.6"
|
axum = "0.6"
|
||||||
cached = "0.44"
|
cached = "0.44"
|
||||||
cfg-if = "1"
|
cfg-if = "1"
|
||||||
|
@ -29,15 +31,19 @@ dotenvy = "0.15"
|
||||||
futures-core = "0.3"
|
futures-core = "0.3"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
headers = "0.3"
|
headers = "0.3"
|
||||||
|
http = "0.2"
|
||||||
hyper = "0.14"
|
hyper = "0.14"
|
||||||
|
js-sys = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
miette = "5.9"
|
miette = "5.9"
|
||||||
percent-encoding = "2.2"
|
percent-encoding = "2.2"
|
||||||
redis = "0.23"
|
redis = "0.23"
|
||||||
|
reqwest = "0.11"
|
||||||
sea-orm = "0.11"
|
sea-orm = "0.11"
|
||||||
sea-orm-migration = "0.11"
|
sea-orm-migration = "0.11"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
serde-wasm-bindgen = "0.5"
|
||||||
strum = "0.25"
|
strum = "0.25"
|
||||||
tera = { version = "1", default-features = false }
|
tera = { version = "1", default-features = false }
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
@ -48,8 +54,12 @@ tower = "0.4"
|
||||||
tower-http = "0.4"
|
tower-http = "0.4"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
ts-rs = "6"
|
||||||
url = "2.3"
|
url = "2.3"
|
||||||
walkdir = "2.3"
|
walkdir = "2.3"
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
wasm-bindgen-futures = "0.4"
|
||||||
|
web-sys = "0.3"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
magnetar_core = { path = "./core" }
|
magnetar_core = { path = "./core" }
|
||||||
|
@ -57,6 +67,7 @@ magnetar_common = { path = "./magnetar_common" }
|
||||||
magnetar_webfinger = { path = "./ext_webfinger" }
|
magnetar_webfinger = { path = "./ext_webfinger" }
|
||||||
magnetar_nodeinfo = { path = "./ext_nodeinfo" }
|
magnetar_nodeinfo = { path = "./ext_nodeinfo" }
|
||||||
magnetar_calckey_model = { path = "./ext_calckey_model" }
|
magnetar_calckey_model = { path = "./ext_calckey_model" }
|
||||||
|
magnetar_sdk = { path = "./magnetar_sdk" }
|
||||||
|
|
||||||
cached = { workspace = true }
|
cached = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
|
@ -83,3 +94,6 @@ percent-encoding = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
|
@ -0,0 +1,46 @@
|
||||||
|
[package]
|
||||||
|
name = "magnetar_sdk"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["rlib", "cdylib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
reqwest = ["dep:reqwest"]
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.features]
|
||||||
|
default = ["reqwest"]
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
magnetar_sdk_macros = { path = "./macros" }
|
||||||
|
|
||||||
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
|
reqwest = { workspace = true, features = ["json"], optional = true }
|
||||||
|
|
||||||
|
http = { workspace = true }
|
||||||
|
async-trait = { workspace = true }
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
|
||||||
|
ts-rs = { workspace = true, features = ["chrono", "chrono-impl"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
wasm-bindgen = { workspace = true }
|
||||||
|
wasm-bindgen-futures = { workspace = true }
|
||||||
|
serde-wasm-bindgen = { workspace = true }
|
||||||
|
chrono = { workspace = true, features = ["serde", "wasm-bindgen", "js-sys"] }
|
||||||
|
js-sys = { workspace = true }
|
||||||
|
web-sys = { workspace = true, features = [
|
||||||
|
"Headers",
|
||||||
|
"Request",
|
||||||
|
"RequestInit",
|
||||||
|
"RequestMode",
|
||||||
|
"Response",
|
||||||
|
"Window",
|
||||||
|
"UrlSearchParams"
|
||||||
|
] }
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "magnetar_sdk_macros"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||||
|
quote = "1"
|
|
@ -0,0 +1,237 @@
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use syn::parse::Parse;
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::{Expr, ExprLit, ExprPath, Ident, Lit, Meta, MetaNameValue, Token};
|
||||||
|
|
||||||
|
struct Field {
|
||||||
|
name: Ident,
|
||||||
|
ty: Ident,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PackInput {
|
||||||
|
struct_name: syn::Ident,
|
||||||
|
fields: Vec<Field>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for PackInput {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let struct_name = input.parse()?;
|
||||||
|
let _ = input.parse::<Token![,]>()?;
|
||||||
|
|
||||||
|
let mut fields = Vec::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let ty = input.parse()?;
|
||||||
|
let _alias_keyword = input.parse::<Token![as]>()?;
|
||||||
|
let name = input.parse()?;
|
||||||
|
|
||||||
|
fields.push(Field { name, ty });
|
||||||
|
|
||||||
|
if input.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = input.parse::<Token![&]>()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(PackInput {
|
||||||
|
struct_name,
|
||||||
|
fields,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn pack(item: TokenStream) -> TokenStream {
|
||||||
|
let parsed = syn::parse_macro_input!(item as PackInput);
|
||||||
|
let struct_name = &parsed.struct_name;
|
||||||
|
let fields = &parsed.fields;
|
||||||
|
|
||||||
|
let names = fields.iter().map(|f| &f.name);
|
||||||
|
let types = fields.iter().map(|f| &f.ty);
|
||||||
|
|
||||||
|
let export_path = format!("bindings/packed/{}.ts", struct_name);
|
||||||
|
|
||||||
|
let expanded = quote::quote! {
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export, export_to = #export_path)]
|
||||||
|
pub struct #struct_name {
|
||||||
|
#(
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub #names: #types
|
||||||
|
),*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TokenStream::from(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(Endpoint, attributes(name, endpoint, method, request, response))]
|
||||||
|
pub fn derive_endpoint(item: TokenStream) -> TokenStream {
|
||||||
|
let input = syn::parse_macro_input!(item as syn::DeriveInput);
|
||||||
|
|
||||||
|
let struct_name = input.ident;
|
||||||
|
assert!(input.generics.params.is_empty(), "generics not supported");
|
||||||
|
let where_clause = input.generics.where_clause;
|
||||||
|
assert!(where_clause.is_none(), "where clauses not supported");
|
||||||
|
let data = match input.data {
|
||||||
|
syn::Data::Struct(data) => data,
|
||||||
|
_ => panic!("only unit structs are supported"),
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
matches!(data.fields, syn::Fields::Unit),
|
||||||
|
"only unit structs are supported"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut name = struct_name.to_string();
|
||||||
|
let mut endpoint = None;
|
||||||
|
let mut method = None;
|
||||||
|
let mut request = None;
|
||||||
|
let mut response = None;
|
||||||
|
|
||||||
|
let mut found = HashSet::new();
|
||||||
|
|
||||||
|
let struct_attrs = input.attrs;
|
||||||
|
|
||||||
|
for struct_attr in struct_attrs {
|
||||||
|
if !struct_attr.meta.path().is_ident("endpoint") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if struct_attr.style != syn::AttrStyle::Outer {
|
||||||
|
panic!("expected outer attribute");
|
||||||
|
}
|
||||||
|
|
||||||
|
let Meta::List(list) = struct_attr.meta else {
|
||||||
|
panic!("expected a list of attributes");
|
||||||
|
};
|
||||||
|
|
||||||
|
let nested = list
|
||||||
|
.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)
|
||||||
|
.expect("expected a list of attributes");
|
||||||
|
|
||||||
|
for kvp in nested {
|
||||||
|
let key = kvp
|
||||||
|
.path
|
||||||
|
.get_ident()
|
||||||
|
.expect("expected a single identifier")
|
||||||
|
.to_owned();
|
||||||
|
let value = kvp.value;
|
||||||
|
|
||||||
|
if found.contains(&key) {
|
||||||
|
panic!("duplicate attribute: {}", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
match key.to_string().as_ref() {
|
||||||
|
"name" => {
|
||||||
|
let Expr::Lit(ExprLit { lit: Lit::Str(new_name), .. }) = value else {
|
||||||
|
panic!("expected a string literal");
|
||||||
|
};
|
||||||
|
|
||||||
|
name = new_name.value();
|
||||||
|
}
|
||||||
|
"endpoint" => {
|
||||||
|
let Expr::Lit(ExprLit { lit: Lit::Str(endpoint_val), .. }) = value else {
|
||||||
|
panic!("expected a string literal");
|
||||||
|
};
|
||||||
|
|
||||||
|
endpoint = Some(endpoint_val.value());
|
||||||
|
}
|
||||||
|
"method" => {
|
||||||
|
let Expr::Path(ExprPath { path, .. }) = value else {
|
||||||
|
panic!("expected a an identifier");
|
||||||
|
};
|
||||||
|
|
||||||
|
method = Some(path);
|
||||||
|
}
|
||||||
|
"request" => {
|
||||||
|
let Expr::Path(ExprPath { path, .. }) = value else {
|
||||||
|
panic!("expected a an identifier");
|
||||||
|
};
|
||||||
|
|
||||||
|
request = Some(path);
|
||||||
|
}
|
||||||
|
"response" => {
|
||||||
|
let Expr::Path(ExprPath { path, .. }) = value else {
|
||||||
|
panic!("expected an identifier")
|
||||||
|
};
|
||||||
|
|
||||||
|
response = Some(path);
|
||||||
|
}
|
||||||
|
_ => panic!("unknown attribute: {}", key),
|
||||||
|
}
|
||||||
|
|
||||||
|
found.insert(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let endpoint = endpoint.expect("missing endpoint attribute");
|
||||||
|
let method = method.expect("missing method attribute");
|
||||||
|
let request = request.expect("missing request attribute");
|
||||||
|
let response = response.expect("missing response attribute");
|
||||||
|
|
||||||
|
let ts_path = format!("bindings/endpoints/{}.ts", name);
|
||||||
|
|
||||||
|
let expanded = quote::quote! {
|
||||||
|
impl Default for #struct_name {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Endpoint for #struct_name {
|
||||||
|
const NAME: &'static str = #name;
|
||||||
|
const ENDPOINT: &'static str = #endpoint;
|
||||||
|
const METHOD: Method = #method;
|
||||||
|
|
||||||
|
type Request = #request;
|
||||||
|
type Response = #response;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[test]
|
||||||
|
fn export_bindings_getuserbyid() {
|
||||||
|
#struct_name::export().expect("could not export type");
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TS for #struct_name {
|
||||||
|
const EXPORT_TO: Option<&'static str> = Some(#ts_path);
|
||||||
|
|
||||||
|
fn decl() -> String {
|
||||||
|
format!(
|
||||||
|
"interface {} {{\n \
|
||||||
|
endpoint: \"{}\";\n \
|
||||||
|
method: \"{}\";\n \
|
||||||
|
request: {};\n \
|
||||||
|
response: {};\n\
|
||||||
|
}}
|
||||||
|
",
|
||||||
|
Self::name(),
|
||||||
|
Self::ENDPOINT,
|
||||||
|
Self::METHOD,
|
||||||
|
<#struct_name as Endpoint>::Request::name(),
|
||||||
|
<#struct_name as Endpoint>::Response::name()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name() -> String {
|
||||||
|
#struct_name::NAME.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dependencies() -> Vec<Dependency> {
|
||||||
|
vec![
|
||||||
|
Dependency::from_ty::<<#struct_name as Endpoint>::Request>().unwrap(),
|
||||||
|
Dependency::from_ty::<<#struct_name as Endpoint>::Response>().unwrap(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transparent() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
TokenStream::from(expanded)
|
||||||
|
}
|
|
@ -0,0 +1,277 @@
|
||||||
|
use crate::client::ApiClientImpl;
|
||||||
|
use crate::endpoints::list::match_endpoint;
|
||||||
|
use crate::endpoints::{Endpoint, ErrorKind, ResponseError};
|
||||||
|
use crate::Client;
|
||||||
|
use http::Method;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
pub struct FetchClient {
|
||||||
|
base_url: String,
|
||||||
|
application: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FetchClient {
|
||||||
|
pub fn new(base_url: &str, application: &str) -> Self {
|
||||||
|
FetchClient {
|
||||||
|
base_url: base_url.to_string(),
|
||||||
|
application: application.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Client for FetchClient {
|
||||||
|
fn base_url(&self) -> &str {
|
||||||
|
&self.base_url
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn call<'a, I, O, E>(
|
||||||
|
&self,
|
||||||
|
endpoint: &E,
|
||||||
|
data: &I,
|
||||||
|
path_params: &impl AsRef<[(&'a str, &'a str)]>,
|
||||||
|
) -> Result<O, ResponseError>
|
||||||
|
where
|
||||||
|
I: Serialize + DeserializeOwned + Send + 'static,
|
||||||
|
O: Serialize + DeserializeOwned + Send + 'static,
|
||||||
|
E: Endpoint<Request = I, Response = O> + Send + 'static,
|
||||||
|
{
|
||||||
|
let url = endpoint.template_path(self.base_url(), path_params);
|
||||||
|
let url = if E::METHOD == Method::GET {
|
||||||
|
let search = web_sys::UrlSearchParams::new_with_str_sequence_sequence(
|
||||||
|
&serde_wasm_bindgen::to_value(data).unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
format!("{}?{}", url, search.to_string())
|
||||||
|
} else {
|
||||||
|
url
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut opts = web_sys::RequestInit::new();
|
||||||
|
opts.method(E::METHOD.as_str());
|
||||||
|
if E::METHOD != Method::GET {
|
||||||
|
opts.body(Some(&serde_wasm_bindgen::to_value(data).unwrap()));
|
||||||
|
}
|
||||||
|
let headers = web_sys::Headers::new().unwrap();
|
||||||
|
headers.set("Content-Type", "application/json").unwrap();
|
||||||
|
headers.set("User-Agent", &self.application).unwrap();
|
||||||
|
opts.headers(&headers);
|
||||||
|
|
||||||
|
let req = web_sys::Request::new_with_str_and_init(&url, &opts).unwrap();
|
||||||
|
|
||||||
|
let js_response = wasm_bindgen_futures::JsFuture::from(
|
||||||
|
web_sys::window()
|
||||||
|
.ok_or_else(|| ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "FetchClient:NoWindow".to_string(),
|
||||||
|
message: "No window object".to_string(),
|
||||||
|
status: None,
|
||||||
|
})?
|
||||||
|
.fetch_with_request(&req),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "FetchClient:FetchFail".to_string(),
|
||||||
|
message: format!("{:#?}", e),
|
||||||
|
status: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let response = web_sys::Response::from(js_response);
|
||||||
|
let status = response.status();
|
||||||
|
|
||||||
|
if (400..=599).contains(&status) {
|
||||||
|
let body = wasm_bindgen_futures::JsFuture::from(response.json().map_err(|e| {
|
||||||
|
ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "FetchClient:ResponseErrorPromiseFail".to_string(),
|
||||||
|
message: format!("{:#?}", e),
|
||||||
|
status: Some(status),
|
||||||
|
}
|
||||||
|
})?)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ResponseError {
|
||||||
|
kind: ErrorKind::ApiError,
|
||||||
|
code: "FetchClient:ResponseErrorFail".to_string(),
|
||||||
|
message: format!("{:#?}", e),
|
||||||
|
status: Some(status),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let data = serde_wasm_bindgen::from_value::<ResponseError>(body).map_err(|e| {
|
||||||
|
ResponseError {
|
||||||
|
kind: ErrorKind::ApiError,
|
||||||
|
code: "FetchClient:ResponseErrorDeserializeFail".to_string(),
|
||||||
|
message: format!("{:#?}", e),
|
||||||
|
status: Some(status),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
return Err(data);
|
||||||
|
} else if (200..=299).contains(&status) {
|
||||||
|
if status == 204 {
|
||||||
|
return if let Some(val) = E::default_response() {
|
||||||
|
Ok(val)
|
||||||
|
} else {
|
||||||
|
Err(ResponseError {
|
||||||
|
kind: ErrorKind::ApiError,
|
||||||
|
code: "FetchClient:ResponseError204".to_string(),
|
||||||
|
message: "Response is empty".to_string(),
|
||||||
|
status: Some(status),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = wasm_bindgen_futures::JsFuture::from(response.json().map_err(|e| {
|
||||||
|
ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "FetchClient:ResponseJsonPromiseFail".to_string(),
|
||||||
|
message: format!("{:#?}", e),
|
||||||
|
status: None,
|
||||||
|
}
|
||||||
|
})?)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "FetchClient:ResponseJsonFail".to_string(),
|
||||||
|
message: format!("{:#?}", e),
|
||||||
|
status: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let data = serde_wasm_bindgen::from_value::<O>(body).map_err(|e| ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "FetchClient:ResponseErrorDeserializeFail".to_string(),
|
||||||
|
message: e.to_string(),
|
||||||
|
status: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
return Ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ResponseError {
|
||||||
|
kind: ErrorKind::ApiError,
|
||||||
|
code: "FetchClient:ApiUnknownStatusError".to_string(),
|
||||||
|
message: response.status().to_string(),
|
||||||
|
status: Some(response.status()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter_with_clone)]
|
||||||
|
pub struct ApiClientOptions {
|
||||||
|
base_url: String,
|
||||||
|
application: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct ApiClient {
|
||||||
|
client: ApiClientImpl,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl ApiClient {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(implementation: &str, options: ApiClientOptions) -> Option<ApiClient> {
|
||||||
|
match implementation {
|
||||||
|
"fetch" => Some(Self {
|
||||||
|
client: ApiClientImpl::Fetch(FetchClient::new(
|
||||||
|
&options.base_url,
|
||||||
|
&options.application,
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
#[cfg(feature = "reqwest")]
|
||||||
|
"reqwest" => Some(Self {
|
||||||
|
client: ApiClientImpl::Reqwest(crate::client::reqwest::ReqwestClient::new(
|
||||||
|
&options.base_url,
|
||||||
|
&options.application,
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn call<I: Serialize, E: Endpoint, O: DeserializeOwned>(
|
||||||
|
&self,
|
||||||
|
endpoint: &E,
|
||||||
|
data: &I,
|
||||||
|
path_params: &[(&str, &str)],
|
||||||
|
) -> Result<O, ResponseError>
|
||||||
|
where
|
||||||
|
I: Serialize + DeserializeOwned + Send + 'static,
|
||||||
|
O: Serialize + DeserializeOwned + Send + 'static,
|
||||||
|
E: Endpoint<Request = I, Response = O> + Send + 'static,
|
||||||
|
{
|
||||||
|
match &self.client {
|
||||||
|
ApiClientImpl::Fetch(client) => {
|
||||||
|
client.call::<I, O, E>(endpoint, data, &path_params).await
|
||||||
|
}
|
||||||
|
#[cfg(feature = "reqwest")]
|
||||||
|
ApiClientImpl::Reqwest(client) => {
|
||||||
|
client.call::<I, O, E>(endpoint, data, &path_params).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter_with_clone)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct EndpointLike {
|
||||||
|
pub path: String,
|
||||||
|
pub method: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn api_call(
|
||||||
|
client: &ApiClient,
|
||||||
|
endpoint_like: &EndpointLike,
|
||||||
|
data: JsValue,
|
||||||
|
path_params: JsValue,
|
||||||
|
) -> Result<JsValue, ResponseError> {
|
||||||
|
let method = Method::try_from(endpoint_like.method.as_str()).map_err(|e| ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "Bindgen::ParseMethod".to_string(),
|
||||||
|
message: e.to_string(),
|
||||||
|
status: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let endpoint = match_endpoint(&endpoint_like.path, &method).ok_or_else(|| ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "Bindgen::MatchEndpoint".to_string(),
|
||||||
|
message: format!(
|
||||||
|
"No endpoint `{}` with method `{}` found",
|
||||||
|
endpoint_like.path, method
|
||||||
|
),
|
||||||
|
status: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let input = serde_wasm_bindgen::from_value(data).map_err(|e| ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "Bindgen::DeserializeInput".to_string(),
|
||||||
|
message: e.to_string(),
|
||||||
|
status: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let path_params = serde_wasm_bindgen::from_value::<HashMap<String, String>>(path_params)
|
||||||
|
.map_err(|e| ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "Bindgen::DeserializePathParams".to_string(),
|
||||||
|
message: e.to_string(),
|
||||||
|
status: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let path_params_borrow = path_params
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.as_str(), v.as_str()))
|
||||||
|
.collect::<Vec<(&str, &str)>>();
|
||||||
|
|
||||||
|
serde_wasm_bindgen::to_value(&client.call(&endpoint, &input, &path_params_borrow).await?)
|
||||||
|
.map_err(|e| ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "Bindgen::SerializeOutput".to_string(),
|
||||||
|
message: e.to_string(),
|
||||||
|
status: None,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
#[cfg(feature = "reqwest")]
|
||||||
|
pub mod reqwest;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub mod fetch;
|
||||||
|
|
||||||
|
pub enum ApiClientImpl {
|
||||||
|
#[cfg(feature = "reqwest")]
|
||||||
|
Reqwest(reqwest::ReqwestClient),
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
Fetch(fetch::FetchClient),
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
use crate::endpoints::{Endpoint, ErrorKind, ResponseError};
|
||||||
|
use crate::Client;
|
||||||
|
use http::header::{CONTENT_TYPE, USER_AGENT};
|
||||||
|
use http::{HeaderMap, HeaderValue, Method, StatusCode};
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
pub struct ReqwestClient {
|
||||||
|
client: reqwest::Client,
|
||||||
|
base_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReqwestClient {
|
||||||
|
pub fn new(base_url: &str, application: &str) -> Self {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(
|
||||||
|
CONTENT_TYPE,
|
||||||
|
HeaderValue::from_str("application/json").unwrap(),
|
||||||
|
);
|
||||||
|
headers.insert(USER_AGENT, HeaderValue::from_str(application).unwrap());
|
||||||
|
|
||||||
|
let client_builder = reqwest::ClientBuilder::new().default_headers(headers);
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let client_builder = { client_builder.https_only(true) };
|
||||||
|
|
||||||
|
let client = client_builder.build().unwrap();
|
||||||
|
|
||||||
|
ReqwestClient {
|
||||||
|
client,
|
||||||
|
base_url: base_url.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Client for ReqwestClient {
|
||||||
|
fn base_url(&self) -> &str {
|
||||||
|
&self.base_url
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn call<'a, I, O, E>(
|
||||||
|
&self,
|
||||||
|
endpoint: &E,
|
||||||
|
data: &I,
|
||||||
|
path_params: &impl AsRef<[(&'a str, &'a str)]>,
|
||||||
|
) -> Result<O, ResponseError>
|
||||||
|
where
|
||||||
|
I: Serialize + DeserializeOwned + Send + 'static,
|
||||||
|
O: Serialize + DeserializeOwned + Send + 'static,
|
||||||
|
E: Endpoint<Request = I, Response = O> + Send + 'static,
|
||||||
|
{
|
||||||
|
let url = endpoint.template_path(self.base_url(), path_params);
|
||||||
|
|
||||||
|
let req = self.client.request(E::METHOD, &url);
|
||||||
|
|
||||||
|
let req = if E::METHOD == Method::GET {
|
||||||
|
req.query(&data)
|
||||||
|
} else {
|
||||||
|
req.json(&data)
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = req.send().await.map_err(|e| ResponseError {
|
||||||
|
kind: ErrorKind::Other,
|
||||||
|
code: "ReqwestClient:Fail".to_string(),
|
||||||
|
message: e.to_string(),
|
||||||
|
status: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let status = response.status();
|
||||||
|
if status.is_client_error() || status.is_server_error() {
|
||||||
|
match response.json::<ResponseError>().await {
|
||||||
|
Ok(res) => Err(res),
|
||||||
|
Err(e) => Err(ResponseError {
|
||||||
|
kind: ErrorKind::ApiError,
|
||||||
|
code: "ReqwestClient:ApiGenericError".to_string(),
|
||||||
|
message: e.to_string(),
|
||||||
|
status: Some(status.as_u16()),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} else if status.is_success() {
|
||||||
|
if status == StatusCode::NO_CONTENT.as_u16() {
|
||||||
|
return if let Some(val) = E::default_response() {
|
||||||
|
Ok(val)
|
||||||
|
} else {
|
||||||
|
Err(ResponseError {
|
||||||
|
kind: ErrorKind::ApiError,
|
||||||
|
code: "ReqwestClient:ResponseError204".to_string(),
|
||||||
|
message: "Response is empty".to_string(),
|
||||||
|
status: Some(status.as_u16()),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = response.json::<O>().await.map_err(|e| ResponseError {
|
||||||
|
kind: ErrorKind::ApiError,
|
||||||
|
code: "ReqwestClient:JsonError".to_string(),
|
||||||
|
message: e.to_string(),
|
||||||
|
status: Some(status.as_u16()),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
} else {
|
||||||
|
Err(ResponseError {
|
||||||
|
kind: ErrorKind::ApiError,
|
||||||
|
code: "ReqwestClient:ApiUnknownStatusError".to_string(),
|
||||||
|
message: status.to_string(),
|
||||||
|
status: Some(status.as_u16()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
use crate::endpoints::Endpoint;
|
||||||
|
|
||||||
|
macro_rules! match_from {
|
||||||
|
(($endpoint:expr, $method:expr) in [$($e:ty,)*]) => {
|
||||||
|
match ($endpoint, $method) {
|
||||||
|
$(
|
||||||
|
(<$e as Endpoint>::ENDPOINT, &<$e as Endpoint>::METHOD) => Some(<$e as Default>::default()),
|
||||||
|
)*
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn match_endpoint(endpoint: &str, method: &http::Method) -> Option<impl Endpoint> {
|
||||||
|
match_from!(
|
||||||
|
(endpoint, method)
|
||||||
|
in [
|
||||||
|
crate::endpoints::user::GetUserById,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
pub(crate) mod list;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
use http::Method;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))]
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, TS)]
|
||||||
|
pub struct ResponseError {
|
||||||
|
pub kind: ErrorKind,
|
||||||
|
pub status: Option<u16>,
|
||||||
|
pub code: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
|
||||||
|
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, TS)]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
#[default]
|
||||||
|
ApiError,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, TS)]
|
||||||
|
pub struct Empty;
|
||||||
|
|
||||||
|
impl From<Empty> for () {
|
||||||
|
fn from(_: Empty) -> Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Endpoint {
|
||||||
|
const NAME: &'static str;
|
||||||
|
const ENDPOINT: &'static str;
|
||||||
|
const METHOD: Method;
|
||||||
|
|
||||||
|
type Request: Serialize + DeserializeOwned + Send + Sync + 'static;
|
||||||
|
type Response: Serialize + DeserializeOwned + Send + Sync + 'static;
|
||||||
|
|
||||||
|
fn default_response() -> Option<Self::Response> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn template_path<'a>(&self, base_path: &str, var: &impl AsRef<[(&'a str, &'a str)]>) -> String {
|
||||||
|
let mut path_suffix = Self::ENDPOINT.to_string();
|
||||||
|
|
||||||
|
for (key, value) in var.as_ref() {
|
||||||
|
path_suffix = path_suffix.replace(&format!(":{}", key), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("{}{}", base_path, path_suffix)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
use crate::endpoints::Endpoint;
|
||||||
|
use http::Method;
|
||||||
|
use magnetar_sdk_macros::Endpoint;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::{Dependency, TS};
|
||||||
|
|
||||||
|
use crate::types::user::PackUserProfileFull;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct UserByIdReq {
|
||||||
|
id: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
detail: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Endpoint, Deserialize)]
|
||||||
|
#[endpoint(
|
||||||
|
endpoint = "/users/:user_id",
|
||||||
|
method = Method::GET,
|
||||||
|
request = UserByIdReq,
|
||||||
|
response = PackUserProfileFull
|
||||||
|
)]
|
||||||
|
pub struct GetUserById;
|
|
@ -0,0 +1,23 @@
|
||||||
|
pub mod client;
|
||||||
|
pub mod endpoints;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
use crate::endpoints::Endpoint;
|
||||||
|
use endpoints::ResponseError;
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
pub trait Client {
|
||||||
|
fn base_url(&self) -> &str;
|
||||||
|
|
||||||
|
async fn call<'a, I, O, E>(
|
||||||
|
&self,
|
||||||
|
endpoint: &E,
|
||||||
|
data: &I,
|
||||||
|
path_params: &impl AsRef<[(&'a str, &'a str)]>,
|
||||||
|
) -> Result<O, ResponseError>
|
||||||
|
where
|
||||||
|
I: Serialize + DeserializeOwned + Send + 'static,
|
||||||
|
O: Serialize + DeserializeOwned + Send + 'static,
|
||||||
|
E: Endpoint<Request = I, Response = O> + Send + 'static;
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use crate::types::user::PackUserBase;
|
||||||
|
|
||||||
|
use magnetar_sdk_macros::pack;
|
||||||
|
|
||||||
|
use crate::types::Id;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct ImageMeta {
|
||||||
|
pub width: Option<u64>,
|
||||||
|
pub height: Option<u64>,
|
||||||
|
pub orientation: Option<u64>,
|
||||||
|
pub color: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct DriveFolderBase {
|
||||||
|
pub name: String,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
pub comment: Option<String>,
|
||||||
|
pub file_count: u64,
|
||||||
|
pub folder_count: u64,
|
||||||
|
pub parent_id: Option<String>,
|
||||||
|
pub user_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(PackDriveFolderBase, Id as id & DriveFolderBase as folder);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct DriveFolderParentExt {
|
||||||
|
pub folder: Box<DriveFolderBase>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackDriveFolderWithParent,
|
||||||
|
Id as id & DriveFolderBase as folder & DriveFolderParentExt as parent
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct DriveFileBase {
|
||||||
|
pub name: String,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
pub size: u64,
|
||||||
|
pub sha256: String,
|
||||||
|
pub mime_type: String,
|
||||||
|
pub image_meta: ImageMeta,
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub thumbnail_url: Option<String>,
|
||||||
|
pub sensitive: bool,
|
||||||
|
pub comment: Option<String>,
|
||||||
|
pub folder_id: Option<String>,
|
||||||
|
pub user_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(PackDriveFileBase, Id as id & DriveFileBase as file);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct DriveFileFolderExt {
|
||||||
|
pub folder: Box<PackDriveFolderBase>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackDriveFileWithFolder,
|
||||||
|
Id as id & DriveFileBase as file & DriveFileFolderExt as folder
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct DriveFileUserExt {
|
||||||
|
pub user: Box<PackUserBase>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackDriveFileWithUser,
|
||||||
|
Id as id & DriveFileBase as file & DriveFileUserExt as user
|
||||||
|
);
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackDriveFileFull,
|
||||||
|
Id as id & DriveFileBase as file & DriveFileFolderExt as folder & DriveFileUserExt as user
|
||||||
|
);
|
|
@ -0,0 +1,22 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use crate::types::Id;
|
||||||
|
use magnetar_sdk_macros::pack;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct EmojiBase {
|
||||||
|
pub shortcode: String,
|
||||||
|
pub url: String,
|
||||||
|
pub static_url: String,
|
||||||
|
pub visible_in_picker: bool,
|
||||||
|
pub category: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(PackEmojiBase, Id as id & EmojiBase as emoji);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct EmojiContext(pub Vec<String>);
|
|
@ -0,0 +1,44 @@
|
||||||
|
pub mod drive;
|
||||||
|
pub mod emoji;
|
||||||
|
pub mod note;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Id {
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub enum NotificationType {
|
||||||
|
Follow,
|
||||||
|
Mention,
|
||||||
|
Reply,
|
||||||
|
Renote,
|
||||||
|
Quote,
|
||||||
|
Reaction,
|
||||||
|
PollVote,
|
||||||
|
PollEnded,
|
||||||
|
FollowRequest,
|
||||||
|
FollowRequestAccepted,
|
||||||
|
App,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct NotificationSettings {
|
||||||
|
pub enabled: Vec<NotificationType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub enum FollowVisibility {
|
||||||
|
Public,
|
||||||
|
Followers,
|
||||||
|
Private,
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
use crate::types::emoji::EmojiContext;
|
||||||
|
use crate::types::user::{PackUserBase, UserBase};
|
||||||
|
use crate::types::Id;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use crate::types::drive::PackDriveFileBase;
|
||||||
|
|
||||||
|
use magnetar_sdk_macros::pack;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub enum NoteVisibility {
|
||||||
|
Public,
|
||||||
|
Home,
|
||||||
|
Followers,
|
||||||
|
Specified,
|
||||||
|
Direct,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct PollChoice {
|
||||||
|
pub title: String,
|
||||||
|
pub votes_count: u64,
|
||||||
|
pub voted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct PollBase {
|
||||||
|
pub expires_at: DateTime<Utc>,
|
||||||
|
pub expired: bool,
|
||||||
|
pub multiple_choice: bool,
|
||||||
|
pub options: Vec<PollChoice>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(PackPollBase, Id as id & PollBase as poll);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct NoteBase {
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
pub cw: Option<String>,
|
||||||
|
pub uri: Option<String>,
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub text: String,
|
||||||
|
pub visibility: NoteVisibility,
|
||||||
|
pub user: Box<UserBase>,
|
||||||
|
pub parent_note_id: Option<String>,
|
||||||
|
pub renoted_note_id: Option<String>,
|
||||||
|
pub reply_count: u64,
|
||||||
|
pub renote_count: u64,
|
||||||
|
pub hashtags: Vec<String>,
|
||||||
|
pub reactions: Vec<PackReactionBase>,
|
||||||
|
pub emojis: EmojiContext,
|
||||||
|
pub local_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(PackNoteBase, Id as id & NoteBase as note);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct NoteAttachmentExt {
|
||||||
|
pub attachments: Vec<PackDriveFileBase>,
|
||||||
|
pub poll: Option<PollBase>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackNoteWithAttachments,
|
||||||
|
Id as id & NoteBase as note & NoteAttachmentExt as attachment
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
pub struct NoteDetailExt {
|
||||||
|
pub poll: Option<PollBase>,
|
||||||
|
pub parent_note: Option<Box<NoteBase>>,
|
||||||
|
pub renoted_note: Option<Box<NoteBase>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackNoteFull,
|
||||||
|
Id as id & NoteBase as note & NoteAttachmentExt as attachment & NoteDetailExt as detail
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
pub struct ReactionBase {
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
pub user_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(PackReactionBase, Id as id & ReactionBase as reaction);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
pub struct ReactionUserExt {
|
||||||
|
pub user: Box<PackUserBase>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackReactionWithUser,
|
||||||
|
Id as id & ReactionBase as reaction & ReactionUserExt as user
|
||||||
|
);
|
|
@ -0,0 +1,187 @@
|
||||||
|
use crate::types::emoji::EmojiContext;
|
||||||
|
use crate::types::note::PackNoteBase;
|
||||||
|
use crate::types::{Id, NotificationSettings};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use magnetar_sdk_macros::pack;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub enum AvatarDecoration {
|
||||||
|
None,
|
||||||
|
CatEars,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub enum SpeechTransform {
|
||||||
|
None,
|
||||||
|
Cat,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct ProfileField {
|
||||||
|
name: String,
|
||||||
|
value: String,
|
||||||
|
verified_at: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct UserBase {
|
||||||
|
pub acct: String,
|
||||||
|
pub username: String,
|
||||||
|
pub display_name: String,
|
||||||
|
pub host: Option<String>,
|
||||||
|
pub speech_transform: SpeechTransform,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
pub avatar_url: Option<String>,
|
||||||
|
pub avatar_blurhash: Option<String>,
|
||||||
|
pub avatar_color: Option<String>,
|
||||||
|
pub avatar_decoration: AvatarDecoration,
|
||||||
|
pub admin: bool,
|
||||||
|
pub moderator: bool,
|
||||||
|
pub bot: bool,
|
||||||
|
pub emojis: EmojiContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(PackUserBase, Id as id & UserBase as user);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct UserProfileExt {
|
||||||
|
pub locked: bool,
|
||||||
|
pub silenced: bool,
|
||||||
|
pub suspended: bool,
|
||||||
|
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub location: Option<String>,
|
||||||
|
pub birthday: Option<DateTime<Utc>>,
|
||||||
|
pub fields: Vec<ProfileField>,
|
||||||
|
|
||||||
|
pub follower_count: u64,
|
||||||
|
pub following_count: u64,
|
||||||
|
pub note_count: u64,
|
||||||
|
|
||||||
|
pub url: Option<String>,
|
||||||
|
|
||||||
|
pub moved_to_uri: Option<String>,
|
||||||
|
pub also_known_as: Option<String>,
|
||||||
|
|
||||||
|
pub banner_url: Option<String>,
|
||||||
|
pub banner_color: Option<String>,
|
||||||
|
pub banner_blurhash: Option<String>,
|
||||||
|
|
||||||
|
pub public_reactions: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackUserProfile,
|
||||||
|
Id as id & UserBase as user & UserProfileExt as profile
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct UserProfilePinsEx {
|
||||||
|
pub pinned_notes: Vec<PackNoteBase>,
|
||||||
|
// pub pinned_page: Option<Page>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackUserProfileFull,
|
||||||
|
Id as id
|
||||||
|
& UserBase as user
|
||||||
|
& UserProfileExt as profile
|
||||||
|
& UserProfilePinsEx as pins
|
||||||
|
& UserRelationExt as relation
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct UserRelationExt {
|
||||||
|
pub last_fetched_at: Option<DateTime<Utc>>,
|
||||||
|
pub follows_you: bool,
|
||||||
|
pub you_follow: bool,
|
||||||
|
pub blocks_you: bool,
|
||||||
|
pub you_block: bool,
|
||||||
|
pub mute: bool,
|
||||||
|
pub mute_renotes: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackUserRelation,
|
||||||
|
Id as id & UserBase as user & UserRelationExt as relation
|
||||||
|
);
|
||||||
|
pack!(
|
||||||
|
PackUserProfileRelation,
|
||||||
|
Id as id & UserBase as user & UserProfileExt as profile & UserRelationExt as relation
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct UserAuthOverviewExt {
|
||||||
|
pub two_factor_enabled: bool,
|
||||||
|
pub use_passwordless_login: bool,
|
||||||
|
pub security_keys: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackUserAuthOverview,
|
||||||
|
Id as id & UserBase as user & UserAuthOverviewExt as auth
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct UserSelfExt {
|
||||||
|
pub avatar_id: Option<String>,
|
||||||
|
pub banner_id: Option<String>,
|
||||||
|
pub email_announcements: bool,
|
||||||
|
pub always_mark_sensitive: bool,
|
||||||
|
pub reject_bot_follow_requests: bool,
|
||||||
|
pub reject_crawlers: bool,
|
||||||
|
pub reject_ai_training: bool,
|
||||||
|
pub has_unread_announcements: bool,
|
||||||
|
pub has_unread_antenna: bool,
|
||||||
|
pub has_unread_notifications: bool,
|
||||||
|
pub has_pending_follow_requests: bool,
|
||||||
|
pub word_mutes: Vec<String>,
|
||||||
|
pub instance_mutes: Vec<String>,
|
||||||
|
pub notification_settings: NotificationSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(
|
||||||
|
PackUserSelf,
|
||||||
|
Id as id & UserBase as user & UserSelfExt as self_info
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct UserDetailExt {
|
||||||
|
pub uri: Option<String>,
|
||||||
|
pub updated_at: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct SecurityKeyBase {
|
||||||
|
pub name: String,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
pub last_used_at: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pack!(PackSecurityKeyBase, Id as id & SecurityKeyBase as key);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct UserSecretsExt {
|
||||||
|
pub email: Option<String>,
|
||||||
|
pub email_verified: bool,
|
||||||
|
pub security_keys: Vec<SecurityKeyBase>,
|
||||||
|
}
|
||||||
|
pack!(
|
||||||
|
PackUserSelfAll,
|
||||||
|
Id as id & UserBase as user & UserSelfExt as self_info & UserSecretsExt as secrets
|
||||||
|
);
|
Loading…
Reference in New Issue