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