use cfg_if::cfg_if; use jsonschema::JSONSchema; use once_cell::sync::Lazy; use parse_display::FromStr; use schemars::JsonSchema; use utoipa::ToSchema; use super::Schema; use crate::model; use crate::model::entity::sea_orm_active_enums::AntennaSrcEnum; #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Antenna { pub id: String, pub created_at: chrono::DateTime, pub name: String, pub keywords: Vec>, pub exclude_keywords: Vec>, #[schema(inline)] pub src: AntennaSrc, pub user_list_id: Option, pub user_group_id: Option, pub users: Vec, pub instances: Vec, #[serde(default)] pub case_sensitive: bool, #[serde(default)] pub notify: bool, #[serde(default)] pub with_replies: bool, #[serde(default)] pub with_file: bool, #[serde(default)] pub has_unread_note: bool, } #[derive(Clone, Debug, FromStr, PartialEq, Eq, JsonSchema, ToSchema)] #[serde(rename_all = "camelCase")] #[display(style = "camelCase")] #[display("'{}'")] pub enum AntennaSrc { Home, All, Users, List, Group, Instances, } impl TryFrom for super::AntennaSrc { type Error = model::error::Error; fn try_from(value: AntennaSrcEnum) -> Result { value.to_string().parse().map_err(model::error::Error::from) } } // ---- TODO: could be macro impl Schema for super::Antenna {} pub static VALIDATOR: Lazy = Lazy::new(super::Antenna::validator); // ---- cfg_if! { if #[cfg(feature = "napi")] { use napi::bindgen_prelude::{FromNapiValue, ToNapiValue}; use napi_derive::napi; use crate::model::entity::antenna; use crate::model::repository::Repository; /// For NAPI because [chrono] is not supported. #[napi(object)] #[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)] #[serde(rename_all = "camelCase")] pub struct NativeAntennaSchema { pub id: String, pub created_at: String, pub name: String, pub keywords: Vec>, pub exclude_keywords: Vec>, #[schema(inline)] pub src: NativeAntennaSrc, pub user_list_id: Option, pub user_group_id: Option, pub users: Vec, pub instances: Vec, #[serde(default)] pub case_sensitive: bool, #[serde(default)] pub notify: bool, #[serde(default)] pub with_replies: bool, #[serde(default)] pub with_file: bool, #[serde(default)] pub has_unread_note: bool, } #[napi(string_enum)] #[derive(Debug, FromStr, PartialEq, Eq, JsonSchema, ToSchema)] #[display("'{}'")] #[allow(non_camel_case_types)] pub enum NativeAntennaSrc { home, all, users, list, group, instances, } #[napi] pub async fn native_pack_antenna_by_id(id: String) -> napi::Result { antenna::Model::pack_by_id(id).await.map_err(Into::into) } } } #[cfg(test)] mod unit_test { use cfg_if::cfg_if; use pretty_assertions::assert_eq; use serde_json::json; use crate::model::{entity::sea_orm_active_enums::AntennaSrcEnum, schema::AntennaSrc}; use super::VALIDATOR; #[test] fn src_from_active_enum() { let src = AntennaSrc::try_from(AntennaSrcEnum::All).unwrap(); cfg_if! { if #[cfg(feature = "napi")] { assert_eq!(src, AntennaSrc::all); } else { assert_eq!(src, AntennaSrc::All); } } } #[test] fn antenna_valid() { let instance = json!({ "id": "9fil64s6g7cskdrb", "createdAt": "2023-05-24T06:56:14.323Z", "name": "Valid Antenna", "keywords": [["first", "keyword"], ["second"]], "excludeKeywords": [["excluding", "keywrods"], ["from", "antenna"]], "src": "users", // "userListId" and "userGroupId" can be null or be omitted "userListId": null, "users": ["9fil64s6g7cskdrb", "9fil66brl1udxau2"], "instances": [], // "caseSensitive", "notify", "withReplies", "withFile", and // "hasUnreadNote" are false if ommited "notify": false, "withReplies": false, "withFile": false, "hasUnreadNote": false, }); assert!(VALIDATOR.is_valid(&instance)); } #[test] fn antenna_invalid() { let instance = json!({ // "id" is required "id": null, // trailing "Z" is missing "createdAt": "2023-05-24T07:36:34.389", // "name" is required // "keywords" must be an array "keywords": "invalid keyword", // "excludeKeywords" is required "excludeKeywords": null, // "src" must be one of "home", "all", "users", "list", "group", and // "instances" "src": "invalid_src", // "userListId" is string "userListId": ["9f4ziiqfxw"], // "users" must be an array of strings "users": [1, "9fil64s6g7cskdrb"], "instances": ["9fil65jzhtjpi3xn"], // "caseSensitive" is boolean "caseSensitive": 0, "notify": true, "withReplies": true, "withFile": true, "hasUnreadNote": true, }); let result = VALIDATOR .validate(&instance) .expect_err("validation must fail"); let mut paths: Vec = result .map(|e| e.instance_path.to_string()) .filter(|e| !e.is_empty()) .collect(); paths.sort(); assert_eq!( paths, vec![ "/caseSensitive", #[cfg(not(feature = "napi"))] "/createdAt", "/excludeKeywords", "/id", "/keywords", "/src", "/userListId", "/users/0" ] ); } }