From 810426ceb06042d229fc29eb07c6e01207cfd35e Mon Sep 17 00:00:00 2001 From: eason <30045503+Eason0729@users.noreply.github.com> Date: Fri, 8 Dec 2023 00:26:37 +0800 Subject: [PATCH] test: :white_check_mark: able to connected to backend! --- backend/entity/src/announcement.rs | 4 +- backend/entity/src/contest.rs | 14 +- backend/entity/src/problem.rs | 10 +- backend/entity/src/submit.rs | 8 +- backend/entity/src/token.rs | 4 +- backend/entity/src/user.rs | 4 +- backend/justfile | 7 +- .../src/m20231207_000001_create_table.rs | 159 ++++++++++++++---- backend/src/controller/judger/mod.rs | 20 ++- backend/src/controller/token.rs | 8 +- backend/src/endpoint/problem.rs | 4 +- backend/src/endpoint/submit.rs | 6 +- backend/src/endpoint/token.rs | 4 +- backend/src/endpoint/util/error.rs | 3 + backend/src/init/db.rs | 61 ++----- backend/src/macro_tool.rs | 11 +- 16 files changed, 190 insertions(+), 137 deletions(-) diff --git a/backend/entity/src/announcement.rs b/backend/entity/src/announcement.rs index cb63e6a1..9eb92783 100644 --- a/backend/entity/src/announcement.rs +++ b/backend/entity/src/announcement.rs @@ -9,9 +9,9 @@ pub struct Model { pub id: i32, pub title: String, pub content: String, - #[sea_orm(column_type = "Timestamp")] + #[sea_orm(column_type = "Time")] pub create_at: chrono::NaiveDateTime, - #[sea_orm(column_type = "Timestamp", on_update = "current_timestamp")] + #[sea_orm(column_type = "Time", on_update = "current_timestamp")] pub update_at: chrono::NaiveDateTime, } diff --git a/backend/entity/src/contest.rs b/backend/entity/src/contest.rs index 3c6c8b23..30d0233d 100644 --- a/backend/entity/src/contest.rs +++ b/backend/entity/src/contest.rs @@ -8,18 +8,18 @@ pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub hoster: i32, - #[sea_orm(column_type = "Timestamp")] + #[sea_orm(column_type = "Time")] pub begin: chrono::NaiveDateTime, - #[sea_orm(column_type = "Timestamp")] + #[sea_orm(column_type = "Time")] pub end: chrono::NaiveDateTime, pub title: String, pub content: String, pub tags: String, #[sea_orm(column_type = "Binary(BlobSize::Blob(None))", nullable)] pub password: Option>, - #[sea_orm(column_type = "Timestamp")] + #[sea_orm(column_type = "Time")] pub create_at: chrono::NaiveDateTime, - #[sea_orm(column_type = "Timestamp", on_update = "current_timestamp")] + #[sea_orm(column_type = "Time", on_update = "current_timestamp")] pub update_at: chrono::NaiveDateTime, pub public: bool, } @@ -28,10 +28,8 @@ pub struct Model { pub enum Relation { #[sea_orm(has_many = "super::user_contest::Entity")] UserContest, - #[sea_orm( - has_one = "super::problem::Entity" - )] - Problem + #[sea_orm(has_one = "super::problem::Entity")] + Problem, } impl Related for Entity { diff --git a/backend/entity/src/problem.rs b/backend/entity/src/problem.rs index 72ab9bb5..7b3eec4b 100644 --- a/backend/entity/src/problem.rs +++ b/backend/entity/src/problem.rs @@ -13,16 +13,16 @@ pub struct Model { pub submit_count: u32, #[sea_orm(column_type = "Float")] pub ac_rate: f32, - pub memory: u64, - pub time: u64, + pub memory: i64, + pub time: i64, pub difficulty: u32, pub public: bool, pub tags: String, pub title: String, pub content: String, - #[sea_orm(column_type = "Timestamp")] + #[sea_orm(column_type = "Time")] pub create_at: chrono::NaiveDateTime, - #[sea_orm(column_type = "Timestamp", on_update = "current_timestamp")] + #[sea_orm(column_type = "Time", on_update = "current_timestamp")] pub update_at: chrono::NaiveDateTime, pub match_rule: i32, } @@ -48,7 +48,7 @@ pub enum Relation { from = "Column::ContestId", to = "super::contest::Column::Id" )] - Contest + Contest, } impl Related for Entity { diff --git a/backend/entity/src/submit.rs b/backend/entity/src/submit.rs index 9d409d30..e8a239e2 100644 --- a/backend/entity/src/submit.rs +++ b/backend/entity/src/submit.rs @@ -9,15 +9,15 @@ pub struct Model { pub id: i32, pub user_id: Option, pub problem_id: i32, - #[sea_orm(column_type = "Timestamp")] + #[sea_orm(column_type = "Time")] pub upload_at: chrono::NaiveDateTime, - pub time: Option, - pub accuracy: Option, + pub time: Option, + pub accuracy: Option, pub committed: bool, pub lang: String, #[sea_orm(column_type = "Binary(BlobSize::Blob(None))")] pub code: Vec, - pub memory: Option, + pub memory: Option, pub pass_case: i32, pub status: Option, pub accept: bool, diff --git a/backend/entity/src/token.rs b/backend/entity/src/token.rs index e6e47cd6..bf48587c 100644 --- a/backend/entity/src/token.rs +++ b/backend/entity/src/token.rs @@ -10,8 +10,8 @@ pub struct Model { pub user_id: i32, #[sea_orm(column_type = "Binary(BlobSize::Blob(None))")] pub rand: Vec, - pub permission: u64, - #[sea_orm(column_type = "Timestamp")] + pub permission: u32, + #[sea_orm(column_type = "Time")] pub expiry: chrono::NaiveDateTime, } diff --git a/backend/entity/src/user.rs b/backend/entity/src/user.rs index 1d29e201..87195484 100644 --- a/backend/entity/src/user.rs +++ b/backend/entity/src/user.rs @@ -7,12 +7,12 @@ use sea_orm::entity::prelude::*; pub struct Model { #[sea_orm(primary_key)] pub id: i32, - pub permission: u64, + pub permission: u32, pub score: u32, pub username: String, #[sea_orm(column_type = "Binary(BlobSize::Blob(None))")] pub password: Vec, - #[sea_orm(column_type = "Timestamp")] + #[sea_orm(column_type = "Time")] pub create_at: chrono::NaiveDateTime, } diff --git a/backend/justfile b/backend/justfile index e60ec610..f921b65f 100644 --- a/backend/justfile +++ b/backend/justfile @@ -10,6 +10,8 @@ prepare: sea-orm-cli migrate -u sqlite://database/backend.sqlite?mode=rwc entity-codegen: + rm database/backend.sqlite + sea-orm-cli migrate -u sqlite://database/backend.sqlite?mode=rwc sea-orm-cli generate entity -u sqlite://database/backend.sqlite?mode=rwc -o entity/src rm entity/src/mod.rs rm entity/src/prelude.rs @@ -20,10 +22,5 @@ release-docker: cp ../cert/*.pem . docker build . --build-arg ARCH=$(uname -m) -t mdoj-backend -refresh: - rm database/backend.sqlite - sea-orm-cli migrate -u sqlite://database/backend.sqlite?mode=rwc - just entity-codegen - run: cargo run \ No newline at end of file diff --git a/backend/migration/src/m20231207_000001_create_table.rs b/backend/migration/src/m20231207_000001_create_table.rs index 1e10196c..c2f4dc11 100644 --- a/backend/migration/src/m20231207_000001_create_table.rs +++ b/backend/migration/src/m20231207_000001_create_table.rs @@ -136,14 +136,21 @@ impl MigrationTrait for Migration { .primary_key(), ) .col(ColumnDef::new(Announcement::Title).string().not_null()) - .col(ColumnDef::new(Announcement::Content).string().not_null().default("")) .col( - ColumnDef::new(Announcement::CreateAt).date_time() + ColumnDef::new(Announcement::Content) + .string() + .not_null() + .default(""), + ) + .col( + ColumnDef::new(Announcement::CreateAt) + .date_time() .not_null() .extra(CREATE_AT.to_string()), ) .col( - ColumnDef::new(Announcement::UpdateAt).date_time() + ColumnDef::new(Announcement::UpdateAt) + .date_time() .not_null() .extra(UPDATE_AT.to_string()), ) @@ -163,31 +170,35 @@ impl MigrationTrait for Migration { .primary_key(), ) .col(ColumnDef::new(Contest::Hoster).integer().not_null()) + .col(ColumnDef::new(Contest::Begin).date_time().not_null()) + .col(ColumnDef::new(Contest::End).date_time().not_null()) + .col(ColumnDef::new(Contest::Title).text().not_null()) .col( - ColumnDef::new(Contest::Begin) - .date_time() - .not_null(), - ) - .col( - ColumnDef::new(Contest::End) - .date_time() - .not_null(), + ColumnDef::new(Contest::Content) + .text() + .not_null() + .default(""), ) - .col(ColumnDef::new(Contest::Title).text().not_null()) - .col(ColumnDef::new(Contest::Content).text().not_null().default("")) .col(ColumnDef::new(Contest::Tags).text().not_null().default("")) .col(ColumnDef::new(Contest::Password).binary().null()) .col( - ColumnDef::new(Contest::CreateAt).date_time() + ColumnDef::new(Contest::CreateAt) + .date_time() .not_null() .extra(CREATE_AT.to_string()), ) .col( - ColumnDef::new(Contest::UpdateAt).date_time() + ColumnDef::new(Contest::UpdateAt) + .date_time() .not_null() .extra(UPDATE_AT.to_string()), ) - .col(ColumnDef::new(Contest::Public).boolean().not_null().default(false)) + .col( + ColumnDef::new(Contest::Public) + .boolean() + .not_null() + .default(false), + ) .to_owned(), ) .await?; @@ -217,9 +228,19 @@ impl MigrationTrait for Migration { .from(Education::Table, Education::UserId) .to(User::Table, User::Id), ) - .col(ColumnDef::new(Education::Tags).text().not_null().default("")) + .col( + ColumnDef::new(Education::Tags) + .text() + .not_null() + .default(""), + ) .col(ColumnDef::new(Education::Title).text().not_null()) - .col(ColumnDef::new(Education::Content).text().not_null().default("")) + .col( + ColumnDef::new(Education::Content) + .text() + .not_null() + .default(""), + ) .to_owned(), ) .await?; @@ -243,23 +264,55 @@ impl MigrationTrait for Migration { .to(User::Table, User::Id), ) .col(ColumnDef::new(Problem::ContestId).integer().null()) - .col(ColumnDef::new(Problem::AcceptCount).integer().not_null().default(0)) - .col(ColumnDef::new(Problem::SubmitCount).integer().not_null().default(0)) - .col(ColumnDef::new(Problem::AcRate).float().not_null().default(0.0)) + .col( + ColumnDef::new(Problem::AcceptCount) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(Problem::SubmitCount) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(Problem::AcRate) + .float() + .not_null() + .default(0.0), + ) .col(ColumnDef::new(Problem::Memory).big_unsigned().not_null()) .col(ColumnDef::new(Problem::Time).big_unsigned().not_null()) - .col(ColumnDef::new(Problem::Difficulty).unsigned().not_null().default(512)) - .col(ColumnDef::new(Problem::Public).boolean().not_null().default(false)) + .col( + ColumnDef::new(Problem::Difficulty) + .unsigned() + .not_null() + .default(512), + ) + .col( + ColumnDef::new(Problem::Public) + .boolean() + .not_null() + .default(false), + ) .col(ColumnDef::new(Problem::Tags).text().not_null().default("")) .col(ColumnDef::new(Problem::Title).text().not_null()) - .col(ColumnDef::new(Problem::Content).text().not_null().default("")) .col( - ColumnDef::new(Problem::CreateAt).date_time() + ColumnDef::new(Problem::Content) + .text() + .not_null() + .default(""), + ) + .col( + ColumnDef::new(Problem::CreateAt) + .date_time() .not_null() .extra(CREATE_AT.to_string()), ) .col( - ColumnDef::new(Problem::UpdateAt).date_time() + ColumnDef::new(Problem::UpdateAt) + .date_time() .not_null() .extra(UPDATE_AT.to_string()), ) @@ -300,14 +353,34 @@ impl MigrationTrait for Migration { ) .col(ColumnDef::new(Submit::Time).big_unsigned().null()) .col(ColumnDef::new(Submit::Accuracy).big_unsigned().null()) - .col(ColumnDef::new(Submit::Committed).boolean().not_null().default(false)) + .col( + ColumnDef::new(Submit::Committed) + .boolean() + .not_null() + .default(false), + ) .col(ColumnDef::new(Submit::Lang).text().not_null()) .col(ColumnDef::new(Submit::Code).not_null().binary()) .col(ColumnDef::new(Submit::Memory).big_unsigned().null()) - .col(ColumnDef::new(Submit::PassCase).integer().not_null().default(0)) + .col( + ColumnDef::new(Submit::PassCase) + .integer() + .not_null() + .default(0), + ) .col(ColumnDef::new(Submit::Status).unsigned().null()) - .col(ColumnDef::new(Submit::Accept).boolean().not_null().default(false)) - .col(ColumnDef::new(Submit::Score).unsigned().not_null().default(0)) + .col( + ColumnDef::new(Submit::Accept) + .boolean() + .not_null() + .default(false), + ) + .col( + ColumnDef::new(Submit::Score) + .unsigned() + .not_null() + .default(0), + ) .to_owned(), ) .await?; @@ -363,12 +436,13 @@ impl MigrationTrait for Migration { .to(User::Table, User::Id), ) .col(ColumnDef::new(Token::Rand).binary().not_null()) - .col(ColumnDef::new(Token::Permission).big_unsigned().not_null().default(0)) .col( - ColumnDef::new(Token::Expiry) - .date_time() - .not_null(), + ColumnDef::new(Token::Permission) + .big_unsigned() + .not_null() + .default(0), ) + .col(ColumnDef::new(Token::Expiry).date_time().not_null()) .to_owned(), ) .await?; @@ -384,12 +458,18 @@ impl MigrationTrait for Migration { .auto_increment() .primary_key(), ) - .col(ColumnDef::new(User::Permission).big_unsigned().not_null().default(0)) + .col( + ColumnDef::new(User::Permission) + .big_unsigned() + .not_null() + .default(0), + ) .col(ColumnDef::new(User::Score).unsigned().not_null().default(0)) .col(ColumnDef::new(User::Username).text().not_null()) .col(ColumnDef::new(User::Password).binary().not_null()) .col( - ColumnDef::new(User::CreateAt).date_time() + ColumnDef::new(User::CreateAt) + .date_time() .not_null() .extra(CREATE_AT.to_string()), ) @@ -422,7 +502,12 @@ impl MigrationTrait for Migration { .from(UserContest::Table, UserContest::UserId) .to(User::Table, User::Id), ) - .col(ColumnDef::new(UserContest::Score).integer().not_null().default(0)) + .col( + ColumnDef::new(UserContest::Score) + .integer() + .not_null() + .default(0), + ) .to_owned(), ) .await?; diff --git a/backend/src/controller/judger/mod.rs b/backend/src/controller/judger/mod.rs index 42beb470..4e80b857 100644 --- a/backend/src/controller/judger/mod.rs +++ b/backend/src/controller/judger/mod.rs @@ -2,7 +2,7 @@ mod pubsub; mod route; use std::sync::Arc; -use crate::{grpc::TonicStream, report_internal}; +use crate::{grpc::TonicStream, ofl, report_internal}; use futures::Future; use leaky_bucket::RateLimiter; use sea_orm::{ActiveModelTrait, ActiveValue, EntityTrait, QueryOrder}; @@ -86,8 +86,8 @@ impl From for Status { pub struct Submit { user: i32, problem: i32, - time_limit: u64, - memory_limit: u64, + time_limit: i64, + memory_limit: i64, lang: Uuid, code: Vec, } @@ -189,8 +189,8 @@ impl SubmitController { model.score = ActiveValue::Set(result); model.status = ActiveValue::Set(Some(Into::::into(status) as u32)); model.pass_case = ActiveValue::Set(running_case); - model.time = ActiveValue::Set(Some(time)); - model.memory = ActiveValue::Set(Some(mem)); + model.time = ActiveValue::Set(Some(time.try_into().unwrap_or(i64::MAX))); + model.memory = ActiveValue::Set(Some(mem.try_into().unwrap_or(i64::MAX))); if let Err(err) = model.update(DB.get().unwrap()).await { log::warn!("failed to commit the judge result: {}", err); @@ -223,7 +223,11 @@ impl SubmitController { let submit_id = submit_model.id.as_ref().to_owned(); let tx = self.pubsub.publish(submit_id); - let scores = testcases.iter().rev().map(|x| x.score as u32).collect::>(); + let scores = testcases + .iter() + .rev() + .map(|x| x.score as u32) + .collect::>(); let tests = testcases .into_iter() @@ -239,8 +243,8 @@ impl SubmitController { .judge(JudgeRequest { lang_uid: submit.lang.to_string(), code: submit.code, - memory: submit.memory_limit, - time: submit.time_limit, + memory: submit.memory_limit as u64, + time: submit.time_limit as u64, rule: problem.match_rule, tests, }) diff --git a/backend/src/controller/token.rs b/backend/src/controller/token.rs index a1111f38..28235364 100644 --- a/backend/src/controller/token.rs +++ b/backend/src/controller/token.rs @@ -43,7 +43,7 @@ impl From for tonic::Status { #[derive(Clone)] struct CachedToken { user_id: i32, - permission: u64, + permission: u32, expiry: NaiveDateTime, } @@ -217,11 +217,11 @@ macro_rules! set_bit_value { paste::paste! { impl $item{ pub fn [](&self)->bool{ - let filter = 1_u64<<($pos); + let filter = 1_u32<<($pos); (self.0&filter) == filter } pub fn [](&mut self,value:bool){ - let filter = 1_u64<<($pos); + let filter = 1_u32<<($pos); if (self.0&filter == filter) ^ value{ self.0 ^= filter; } @@ -232,7 +232,7 @@ macro_rules! set_bit_value { } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct UserPermBytes(pub u64); +pub struct UserPermBytes(pub u32); impl UserPermBytes { pub fn strict_ge(&self, other: Self) -> bool { diff --git a/backend/src/endpoint/problem.rs b/backend/src/endpoint/problem.rs index e8aaf647..45c893f6 100644 --- a/backend/src/endpoint/problem.rs +++ b/backend/src/endpoint/problem.rs @@ -89,8 +89,8 @@ impl From for ProblemFullInfo { tags: value.tags.clone(), difficulty: value.difficulty, public: value.public, - time: value.time, - memory: value.memory, + time: value.time as u64, + memory: value.memory as u64, info: value.into(), } } diff --git a/backend/src/endpoint/submit.rs b/backend/src/endpoint/submit.rs index 9bbd2f80..3406b819 100644 --- a/backend/src/endpoint/submit.rs +++ b/backend/src/endpoint/submit.rs @@ -49,9 +49,9 @@ impl From for SubmitInfo { score: value.score, state: JudgeResult { code: Into::::into(db_code).into(), - accuracy: value.accuracy, - time: value.time, - memory: value.memory, + accuracy: value.accuracy.map(|x| x as u64), + time: value.time.map(|x| x as u64), + memory: value.memory.map(|x| x as u64), }, } } diff --git a/backend/src/endpoint/token.rs b/backend/src/endpoint/token.rs index 32d38b4d..f450692a 100644 --- a/backend/src/endpoint/token.rs +++ b/backend/src/endpoint/token.rs @@ -68,7 +68,7 @@ impl TokenSet for Arc { Ok(Response::new(TokenInfo { token: token.into(), - permission: model.permission, + permission: model.permission as u64, expiry: into_prost(expiry), })) } else { @@ -96,7 +96,7 @@ impl TokenSet for Arc { let (token, expiry) = self.token.add(&user, dur).await?; return Ok(Response::new(TokenInfo { token: token.into(), - permission: perm.0, + permission: perm.0 as u64, expiry: into_prost(expiry), })); } diff --git a/backend/src/endpoint/util/error.rs b/backend/src/endpoint/util/error.rs index 7be57d80..6c099f18 100644 --- a/backend/src/endpoint/util/error.rs +++ b/backend/src/endpoint/util/error.rs @@ -20,6 +20,8 @@ pub enum Error { InvaildUUID(#[from] uuid::Error), #[error("Function should be unreachable!")] Unreachable(&'static str), + #[error("Number too large(or small)")] + NumberTooLarge, } impl From for tonic::Status { @@ -59,6 +61,7 @@ impl From for tonic::Status { ) } Error::Unreachable(x) => report_internal!(error, "{}", x), + Error::NumberTooLarge => tonic::Status::failed_precondition("number too large"), } } } diff --git a/backend/src/init/db.rs b/backend/src/init/db.rs index 350b701a..3426c69e 100644 --- a/backend/src/init/db.rs +++ b/backend/src/init/db.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use ring::digest; use sea_orm::{ ActiveModelTrait, ActiveValue, ConnectionTrait, Database, DatabaseConnection, EntityTrait, - Schema, + PaginatorTrait, Schema, }; use tokio::fs; use tokio::sync::OnceCell; @@ -14,28 +14,13 @@ use crate::controller::token::UserPermBytes; pub static DB: OnceCell = OnceCell::const_new(); pub async fn init(config: &GlobalConfig) { + // sqlite://database/backend.sqlite?mode=rwc let uri = format!("sqlite://{}", config.database.path.clone()); - match Database::connect(&uri).await { - Ok(db) => { - DB.set(db).unwrap(); - } - Err(_) => { - log::info!("Database connection failed, creating database"); - - fs::File::create(PathBuf::from(config.database.path.clone())) - .await - .unwrap(); - - let db: DatabaseConnection = Database::connect(&uri).await.unwrap(); - - first_migration(config, &db).await; - - DB.set(db).unwrap(); - - log::info!("Database created"); - } - } + let db = Database::connect(&uri) + .await + .expect("fail connecting to database"); + init_user(config, &db).await; } fn hash(config: &GlobalConfig, src: &str) -> Vec { digest::digest( @@ -46,38 +31,12 @@ fn hash(config: &GlobalConfig, src: &str) -> Vec { .to_vec() } -async fn create_table(db: &DatabaseConnection, entity: E) -where - E: EntityTrait, -{ - log::info!("Creating table: {}", entity.table_name()); - let builder = db.get_database_backend(); - let stmt = builder.build( - Schema::new(builder) - .create_table_from_entity(entity) - .if_not_exists(), - ); - - match db.execute(stmt).await { - Ok(_) => log::info!("Migrated {}", entity.table_name()), - Err(e) => log::info!("Error: {}", e), +pub async fn init_user(config: &GlobalConfig, db: &DatabaseConnection) { + if entity::user::Entity::find().count(db).await.unwrap() != 0 { + return; } -} - -pub async fn first_migration(config: &GlobalConfig, db: &DatabaseConnection) { - log::info!("Start migration"); - // create tables - create_table(db, entity::user::Entity).await; - create_table(db, entity::token::Entity).await; - create_table(db, entity::announcement::Entity).await; - create_table(db, entity::contest::Entity).await; - create_table(db, entity::education::Entity).await; - create_table(db, entity::problem::Entity).await; - create_table(db, entity::submit::Entity).await; - create_table(db, entity::test::Entity).await; - create_table(db, entity::user_contest::Entity).await; - // generate admin@admin + log::info!("Setting up admin@admin"); let mut perm = UserPermBytes::default(); perm.grant_link(true); diff --git a/backend/src/macro_tool.rs b/backend/src/macro_tool.rs index 4e583536..b20ec656 100644 --- a/backend/src/macro_tool.rs +++ b/backend/src/macro_tool.rs @@ -27,7 +27,7 @@ macro_rules! report_internal { macro_rules! fill_exist_active_model { ($target:expr,$src:expr , $field:ident) => { if let Some(x) = $src.$field { - $target.$field = ActiveValue::Set(x); + $target.$field = ActiveValue::Set(crate::ofl!(x)); } }; ($target:expr,$src:expr, $field:ident, $($ext:ident),+) => { @@ -39,10 +39,17 @@ macro_rules! fill_exist_active_model { #[macro_export] macro_rules! fill_active_model { ($target:expr,$src:expr , $field:ident) => { - $target.$field = ActiveValue::Set($src.$field); + $target.$field = ActiveValue::Set(crate::ofl!($src.$field)); }; ($target:expr,$src:expr, $field:ident, $($ext:ident),+) => { fill_active_model!($target,$src, $field); fill_active_model!($target,$src, $($ext),+); }; } + +#[macro_export] +macro_rules! ofl { + ($n:expr) => { + $n.try_into().map_err(|_| Error::NumberTooLarge)? + }; +}