add native calls
This commit is contained in:
parent
e932d6effa
commit
752d63e6de
|
@ -9,7 +9,7 @@ members = ["migration/Cargo.toml"]
|
|||
[features]
|
||||
default = ["napi"]
|
||||
noarray = []
|
||||
napi = ["dep:napi", "dep:napi-derive"]
|
||||
napi = ["dep:napi", "dep:napi-derive", "dep:radix_fmt"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
@ -33,8 +33,9 @@ tokio = { version = "1.28.1", features = ["full"] }
|
|||
utoipa = "3.3.0"
|
||||
|
||||
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
|
||||
napi = { version = "2.12.0", default-features = false, features = ["napi4", "tokio_rt"], optional = true }
|
||||
napi = { version = "2.12.0", default-features = false, features = ["napi6", "tokio_rt"], optional = true }
|
||||
napi-derive = { version = "2.12.0", optional = true }
|
||||
radix_fmt = { version = "1.0.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.3.0"
|
||||
|
|
|
@ -34,13 +34,13 @@
|
|||
},
|
||||
"scripts": {
|
||||
"artifacts": "napi artifacts",
|
||||
"build": "napi build --platform --release ./built/",
|
||||
"build": "napi build --features napi --platform --release ./built/",
|
||||
"build:debug": "napi build --platform",
|
||||
"prepublishOnly": "napi prepublish -t npm",
|
||||
"test": "ava",
|
||||
"universal": "napi universal",
|
||||
"version": "napi version",
|
||||
"cargo:unit": "cargo test unit_test",
|
||||
"cargo:integration": "cargo test --no-default-features int_test -- --test-threads=1"
|
||||
"cargo:integration": "cargo test --no-default-features -F noarray int_test -- --test-threads=1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use sea_orm::error::DbErr;
|
||||
|
||||
use crate::impl_into_napi_error;
|
||||
|
||||
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
#[error("The database connections have not been initialized yet")]
|
||||
|
@ -7,3 +9,5 @@ pub enum Error {
|
|||
#[error("ORM error: {0}")]
|
||||
OrmError(#[from] DbErr),
|
||||
}
|
||||
|
||||
impl_into_napi_error!(Error);
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
pub mod error;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use error::Error;
|
||||
use sea_orm::{Database, DbConn};
|
||||
|
||||
static DB_CONN: once_cell::sync::OnceCell<DbConn> = once_cell::sync::OnceCell::new();
|
||||
|
||||
pub async fn init_database(connection_uri: impl Into<String>) -> Result<(), Error> {
|
||||
let conn = Database::connect(connection_uri.into()).await?;
|
||||
pub async fn init_database(conn_uri: impl Into<String>) -> Result<(), Error> {
|
||||
let conn = Database::connect(conn_uri.into()).await?;
|
||||
DB_CONN.get_or_init(move || conn);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -15,6 +16,17 @@ pub fn get_database() -> Result<&'static DbConn, Error> {
|
|||
DB_CONN.get().ok_or(Error::Uninitialized)
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "napi")] {
|
||||
use napi_derive::napi;
|
||||
|
||||
#[napi]
|
||||
pub async fn native_init_database(conn_uri: String) -> napi::Result<()> {
|
||||
init_database(conn_uri).await.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_test {
|
||||
use super::{error::Error, get_database};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub mod database;
|
||||
pub mod macros;
|
||||
pub mod model;
|
||||
pub mod util;
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
#[macro_export]
|
||||
macro_rules! impl_into_napi_error {
|
||||
($a:ty) => {
|
||||
#[cfg(feature = "napi")]
|
||||
impl Into<napi::Error> for $a {
|
||||
fn into(self) -> napi::Error {
|
||||
napi::Error::from_reason(self.to_string())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
use crate::impl_into_napi_error;
|
||||
|
||||
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
#[error("Failed to parse string")]
|
||||
#[error("Failed to parse string: {0}")]
|
||||
ParseError(#[from] parse_display::ParseError),
|
||||
#[error("Failed to get database connection")]
|
||||
#[error("Failed to get database connection: {0}")]
|
||||
DbConnError(#[from] crate::database::error::Error),
|
||||
#[error("Database operation error: {0}")]
|
||||
DbOperationError(#[from] sea_orm::DbErr),
|
||||
|
@ -10,9 +12,4 @@ pub enum Error {
|
|||
NotFound,
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi")]
|
||||
impl Into<napi::Error> for Error {
|
||||
fn into(self) -> napi::Error {
|
||||
napi::Error::from_reason(self.to_string())
|
||||
}
|
||||
}
|
||||
impl_into_napi_error!(Error);
|
||||
|
|
|
@ -5,13 +5,18 @@ use schemars::JsonSchema;
|
|||
|
||||
use super::error::Error;
|
||||
|
||||
/// Repositories have a packer that converts a database model to its
|
||||
/// corresponding API schema.
|
||||
#[async_trait]
|
||||
pub trait Repository<T: JsonSchema> {
|
||||
async fn pack(self) -> Result<T, Error>;
|
||||
/// Retrieves one model by its id and pack it.
|
||||
async fn pack_by_id(id: String) -> Result<T, Error>;
|
||||
}
|
||||
|
||||
mod macros {
|
||||
/// Provides the default implementation of
|
||||
/// [crate::model::repository::Repository::pack_by_id].
|
||||
macro_rules! impl_pack_by_id {
|
||||
($a:ty, $b:ident) => {
|
||||
match <$a>::find_by_id($b)
|
||||
|
|
|
@ -23,8 +23,9 @@ pub trait Schema<T: JsonSchema> {
|
|||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "napi")] {
|
||||
pub use antenna::napi::AntennaSchema as Antenna;
|
||||
pub use antenna::napi::AntennaSrc;
|
||||
// Will be disabled once we completely migrate to rust
|
||||
pub use antenna::NativeAntennaSchema as Antenna;
|
||||
pub use antenna::NativeAntennaSrc as AntennaSrc;
|
||||
} else {
|
||||
pub use antenna::Antenna;
|
||||
pub use antenna::AntennaSrc;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use cfg_if::cfg_if;
|
||||
use jsonschema::JSONSchema;
|
||||
use once_cell::sync::Lazy;
|
||||
use parse_display::FromStr;
|
||||
|
@ -60,62 +61,62 @@ impl Schema<Self> for super::Antenna {}
|
|||
pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| super::Antenna::validator());
|
||||
// ----
|
||||
|
||||
#[cfg(feature = "napi")]
|
||||
pub mod napi {
|
||||
use napi::bindgen_prelude::*;
|
||||
use napi_derive::napi;
|
||||
use parse_display::FromStr;
|
||||
use schemars::JsonSchema;
|
||||
use utoipa::ToSchema;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "napi")] {
|
||||
use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
|
||||
use napi_derive::napi;
|
||||
|
||||
use crate::model::{entity::antenna, repository::Repository};
|
||||
use crate::model::entity::antenna;
|
||||
use crate::model::repository::Repository;
|
||||
|
||||
#[napi]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, JsonSchema, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AntennaSchema {
|
||||
pub id: String,
|
||||
pub created_at: String,
|
||||
pub name: String,
|
||||
pub keywords: Vec<Vec<String>>,
|
||||
pub exclude_keywords: Vec<Vec<String>>,
|
||||
#[schema(inline)]
|
||||
pub src: AntennaSrc,
|
||||
pub user_list_id: Option<String>,
|
||||
pub user_group_id: Option<String>,
|
||||
pub users: Vec<String>,
|
||||
pub instances: Vec<String>,
|
||||
#[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]
|
||||
#[derive(Debug, FromStr, PartialEq, Eq, JsonSchema, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[display(style = "camelCase")]
|
||||
#[display("'{}'")]
|
||||
pub enum AntennaSrc {
|
||||
Home,
|
||||
All,
|
||||
Users,
|
||||
List,
|
||||
Group,
|
||||
Instances,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl AntennaSchema {
|
||||
/// For NAPI because [chrono] is not supported.
|
||||
#[napi]
|
||||
pub async fn pack_by_id(id: String) -> napi::Result<AntennaSchema> {
|
||||
antenna::Model::pack_by_id(id).await.map_err(Into::into)
|
||||
#[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<Vec<String>>,
|
||||
pub exclude_keywords: Vec<Vec<String>>,
|
||||
#[schema(inline)]
|
||||
pub src: NativeAntennaSrc,
|
||||
pub user_list_id: Option<String>,
|
||||
pub user_group_id: Option<String>,
|
||||
pub users: Vec<String>,
|
||||
pub instances: Vec<String>,
|
||||
#[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]
|
||||
#[derive(Debug, FromStr, PartialEq, Eq, JsonSchema, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[display(style = "camelCase")]
|
||||
#[display("'{}'")]
|
||||
pub enum NativeAntennaSrc {
|
||||
Home,
|
||||
All,
|
||||
Users,
|
||||
List,
|
||||
Group,
|
||||
Instances,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl NativeAntennaSchema {
|
||||
#[napi]
|
||||
pub async fn pack_by_id(id: String) -> napi::Result<NativeAntennaSchema> {
|
||||
antenna::Model::pack_by_id(id).await.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,31 @@
|
|||
//! ID generation utility based on [cuid2]
|
||||
|
||||
use cuid2::CuidConstructor;
|
||||
use cfg_if::cfg_if;
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use crate::impl_into_napi_error;
|
||||
|
||||
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
|
||||
#[error("ID generator has not been initialized yet")]
|
||||
pub struct ErrorUninitialized;
|
||||
|
||||
static GENERATOR: OnceCell<CuidConstructor> = OnceCell::new();
|
||||
impl_into_napi_error!(ErrorUninitialized);
|
||||
|
||||
pub fn init_id(length: u16) {
|
||||
GENERATOR.get_or_init(move || CuidConstructor::new().with_length(length));
|
||||
static FINGERPRINT: OnceCell<String> = OnceCell::new();
|
||||
static GENERATOR: OnceCell<cuid2::CuidConstructor> = OnceCell::new();
|
||||
|
||||
/// Initializes Cuid2 generator. Must be called before any [create_id].
|
||||
pub fn init_id(length: u16, fingerprint: impl Into<String>) {
|
||||
FINGERPRINT.get_or_init(move || format!("{}{}", fingerprint.into(), cuid2::create_id()));
|
||||
GENERATOR.get_or_init(move || {
|
||||
cuid2::CuidConstructor::new()
|
||||
.with_length(length)
|
||||
.with_fingerprinter(|| FINGERPRINT.get().unwrap().clone())
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns Cuid2 with the length specified by [init_id]. Must be called after
|
||||
/// [init_id], otherwise returns [ErrorUninitialized].
|
||||
pub fn create_id() -> Result<String, ErrorUninitialized> {
|
||||
match GENERATOR.get() {
|
||||
None => Err(ErrorUninitialized),
|
||||
|
@ -20,6 +33,30 @@ pub fn create_id() -> Result<String, ErrorUninitialized> {
|
|||
}
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "napi")] {
|
||||
use radix_fmt::radix_36;
|
||||
use std::cmp;
|
||||
use napi::bindgen_prelude::BigInt;
|
||||
use napi_derive::napi;
|
||||
|
||||
const TIME_2000: u64 = 946_684_800_000;
|
||||
|
||||
/// Calls [init_id] inside. Must be called before [native_create_id].
|
||||
#[napi]
|
||||
pub fn native_init_id_generator(length: u16, fingerprint: String) {
|
||||
init_id(length, fingerprint);
|
||||
}
|
||||
|
||||
/// Generates
|
||||
#[napi]
|
||||
pub fn native_create_id(date_num: BigInt) -> String {
|
||||
let time = cmp::max(date_num.get_u64().1 - TIME_2000, 0);
|
||||
format!("{:0>8}{}", radix_36(time).to_string(), create_id().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_test {
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
|
@ -30,7 +67,7 @@ mod unit_test {
|
|||
#[test]
|
||||
fn can_generate_unique_ids() {
|
||||
assert_eq!(id::create_id(), Err(id::ErrorUninitialized));
|
||||
id::init_id(12);
|
||||
id::init_id(12, "");
|
||||
assert_eq!(id::create_id().unwrap().len(), 12);
|
||||
assert_ne!(id::create_id().unwrap(), id::create_id().unwrap());
|
||||
let id1 = thread::spawn(|| id::create_id().unwrap());
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
|
||||
/// Generate random string based on [thread_rng] and [Alphanumeric].
|
||||
pub fn gen_string(length: u16) -> String {
|
||||
thread_rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
|
@ -8,6 +9,12 @@ pub fn gen_string(length: u16) -> String {
|
|||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi")]
|
||||
#[napi_derive::napi]
|
||||
pub fn native_random_str(length: u16) -> String {
|
||||
gen_string(length)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_test {
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
|
|
Loading…
Reference in New Issue