feat(api): port discord/callback to rust

This commit is contained in:
alyssa 2024-12-29 21:48:28 +00:00
parent 3e194d7c8a
commit 882e9b66f2
9 changed files with 327 additions and 43 deletions

View file

@ -16,6 +16,7 @@ struct ModelField {
patch: ElemPatchability,
json: Option<Expr>,
is_privacy: bool,
default: Option<Expr>,
}
fn parse_field(field: syn::Field) -> ModelField {
@ -25,6 +26,7 @@ fn parse_field(field: syn::Field) -> ModelField {
patch: ElemPatchability::None,
json: None,
is_privacy: false,
default: None,
};
for attr in field.attrs.iter() {
@ -59,6 +61,12 @@ fn parse_field(field: syn::Field) -> ModelField {
}
f.json = Some(nv.value.clone());
}
"default" => {
if f.default.is_some() {
panic!("cannot set default multiple times for same field");
}
f.default = Some(nv.value.clone());
}
_ => panic!("unknown attribute"),
},
Meta::List(_) => panic!("unknown attribute"),
@ -69,6 +77,10 @@ fn parse_field(field: syn::Field) -> ModelField {
panic!("must have json name to be publicly patchable");
}
if f.json.is_some() && f.is_privacy {
panic!("cannot set custom json name for privacy field");
}
f
}
@ -96,7 +108,7 @@ pub fn pk_model(
panic!("fields of a struct must be named");
};
println!("{}: {:#?}", tname, fields);
// println!("{}: {:#?}", tname, fields);
let tfields = mk_tfields(fields.clone());
let from_json = mk_tfrom_json(fields.clone());
@ -126,7 +138,7 @@ pub fn pk_model(
#from_json
}
pub fn to_json(self) -> String {
pub fn to_json(self) -> serde_json::Value {
#to_json
}
}
@ -150,7 +162,7 @@ pub fn pk_model(
#patch_to_sql
}
pub fn to_json(self) -> String {
pub fn to_json(self) -> serde_json::Value {
#patch_to_json
}
}
@ -165,7 +177,7 @@ fn mk_tfields(fields: Vec<ModelField>) -> TokenStream {
let name = f.name.clone();
let ty = f.ty.clone();
quote! {
#name: #ty,
pub #name: #ty,
}
})
.collect()
@ -183,8 +195,14 @@ fn mk_tto_json(fields: Vec<ModelField>) -> TokenStream {
.filter_map(|f| {
f.json.as_ref().map(|v| {
let tname = f.name.clone();
quote! {
#v: self.#tname,
if let Some(default) = f.default.as_ref() {
quote! {
#v: self.#tname.unwrap_or(#default),
}
} else {
quote! {
#v: self.#tname,
}
}
})
})
@ -206,12 +224,12 @@ fn mk_tto_json(fields: Vec<ModelField>) -> TokenStream {
.collect();
quote! {
serde_json::to_string(&serde_json::json!({
serde_json::json!({
#fielddefs
"privacy": {
#privacyfielddefs
}
})).expect("json serializing generated models should not fail")
})
}
}
@ -222,7 +240,7 @@ fn mk_patch_fields(fields: Vec<ModelField>) -> TokenStream {
let name = f.name.clone();
let ty = f.ty.clone();
quote! {
#name: Option<#ty>,
pub #name: Option<#ty>,
}
})
.collect()

35
lib/models/src/_util.rs Normal file
View file

@ -0,0 +1,35 @@
// postgres enums created in c# pluralkit implementations are "fake", i.e. they
// are actually ints in the database rather than postgres enums, because dapper
// does not support postgres enums
// here, we add some impls to support this kind of enum in sqlx
// there is probably a better way to do this, but works for now.
// note: caller needs to implement From<i32> for their type
macro_rules! fake_enum_impls {
($n:ident) => {
impl Type<Postgres> for $n {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_name("INT4")
}
}
impl From<$n> for i32 {
fn from(enum_value: $n) -> Self {
enum_value as i32
}
}
impl<'r, DB: Database> Decode<'r, DB> for $n
where
i32: Decode<'r, DB>,
{
fn decode(
value: <DB as Database>::ValueRef<'r>,
) -> Result<Self, Box<dyn Error + 'static + Send + Sync>> {
let value = <i32 as Decode<DB>>::decode(value)?;
Ok(Self::from(value))
}
}
};
}
pub(crate) use fake_enum_impls;

View file

@ -1,2 +1,11 @@
mod system;
pub use system::*;
mod _util;
macro_rules! model {
($n:ident) => {
mod $n;
pub use $n::*;
};
}
model!(system);
model!(system_config);

View file

@ -6,54 +6,31 @@ use chrono::NaiveDateTime;
use sqlx::{postgres::PgTypeInfo, Database, Decode, Postgres, Type};
use uuid::Uuid;
use crate::_util::fake_enum_impls;
// todo: fix this
pub type SystemId = i32;
// // todo: move this
// todo: move this
#[derive(serde::Serialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum PrivacyLevel {
#[serde(rename = "public")]
Public = 1,
#[serde(rename = "private")]
Private = 2,
Public,
Private,
}
impl Type<Postgres> for PrivacyLevel {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_name("INT4")
}
}
impl From<PrivacyLevel> for i32 {
fn from(enum_value: PrivacyLevel) -> Self {
enum_value as i32
}
}
fake_enum_impls!(PrivacyLevel);
impl From<i32> for PrivacyLevel {
fn from(value: i32) -> Self {
match value {
1 => PrivacyLevel::Public,
2 => PrivacyLevel::Private,
_ => unimplemented!(),
_ => unreachable!(),
}
}
}
struct MyType;
impl<'r, DB: Database> Decode<'r, DB> for PrivacyLevel
where
i32: Decode<'r, DB>,
{
fn decode(
value: <DB as Database>::ValueRef<'r>,
) -> Result<Self, Box<dyn Error + 'static + Send + Sync>> {
let value = <i32 as Decode<DB>>::decode(value)?;
Ok(Self::from(value))
}
}
#[pk_model]
struct System {
id: SystemId,

View file

@ -0,0 +1,89 @@
use model_macros::pk_model;
use sqlx::{postgres::PgTypeInfo, Database, Decode, Postgres, Type};
use std::error::Error;
use crate::{SystemId, _util::fake_enum_impls};
pub const DEFAULT_MEMBER_LIMIT: i32 = 1000;
pub const DEFAULT_GROUP_LIMIT: i32 = 250;
#[derive(serde::Serialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
enum HidPadFormat {
#[serde(rename = "off")]
None,
Left,
Right,
}
fake_enum_impls!(HidPadFormat);
impl From<i32> for HidPadFormat {
fn from(value: i32) -> Self {
match value {
0 => HidPadFormat::None,
1 => HidPadFormat::Left,
2 => HidPadFormat::Right,
_ => unreachable!(),
}
}
}
#[derive(serde::Serialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
enum ProxySwitchAction {
Off,
New,
Add,
}
fake_enum_impls!(ProxySwitchAction);
impl From<i32> for ProxySwitchAction {
fn from(value: i32) -> Self {
match value {
0 => ProxySwitchAction::Off,
1 => ProxySwitchAction::New,
2 => ProxySwitchAction::Add,
_ => unreachable!(),
}
}
}
#[pk_model]
struct SystemConfig {
system: SystemId,
#[json = "timezone"]
ui_tz: String,
#[json = "pings_enabled"]
pings_enabled: bool,
#[json = "latch_timeout"]
latch_timeout: Option<i32>,
#[json = "member_default_private"]
member_default_private: bool,
#[json = "group_default_private"]
group_default_private: bool,
#[json = "show_private_info"]
show_private_info: bool,
#[json = "member_limit"]
#[default = DEFAULT_MEMBER_LIMIT]
member_limit_override: Option<i32>,
#[json = "group_limit"]
#[default = DEFAULT_GROUP_LIMIT]
group_limit_override: Option<i32>,
#[json = "case_sensitive_proxy_tags"]
case_sensitive_proxy_tags: bool,
#[json = "proxy_error_message_enabled"]
proxy_error_message_enabled: bool,
#[json = "hid_display_split"]
hid_display_split: bool,
#[json = "hid_display_caps"]
hid_display_caps: bool,
#[json = "hid_list_padding"]
hid_list_padding: HidPadFormat,
#[json = "proxy_switch"]
proxy_switch: ProxySwitchAction,
#[json = "name_format"]
name_format: String,
#[json = "description_templates"]
description_templates: Vec<String>,
}