Fully moved cart and compile all

This commit is contained in:
Adrian Woźniak 2022-11-04 21:26:30 +01:00
parent f5ecbfb338
commit 7d507602c3
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
10 changed files with 283 additions and 176 deletions

View File

@ -1,9 +1,7 @@
use std::net::SocketAddr;
use std::time::Duration; use std::time::Duration;
use config::UpdateConfig; use config::UpdateConfig;
use tarpc::tokio_serde::formats::Json; use tarpc::context;
use tarpc::{client, context};
use tokio::time::sleep; use tokio::time::sleep;
#[derive(gumdrop::Options)] #[derive(gumdrop::Options)]
@ -17,24 +15,23 @@ impl UpdateConfig for Flags {}
#[tokio::main] #[tokio::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
use channels::accounts::me::Input;
let opts: Flags = gumdrop::Options::parse_args_default_or_exit(); let opts: Flags = gumdrop::Options::parse_args_default_or_exit();
let config = config::default_load(&opts); let config = config::default_load(&opts);
let client = channels::accounts::rpc::create_client(config).await; let client = channels::accounts::rpc::create_client(config).await;
let r = client.me(context::current(), 1.into()).await; let r = client
.me(
context::current(),
Input {
account_id: 1.into(),
},
)
.await;
println!("{:?}", r); println!("{:?}", r);
let hello = async move {
tokio::join! {
client.me(context::current(), 1.into()),
client.me(context::current(), 2.into()),
}
}
.await;
eprintln!("{:?}", hello);
// Let the background span processor finish. // Let the background span processor finish.
sleep(Duration::from_micros(1)).await; sleep(Duration::from_micros(1)).await;
opentelemetry::global::shutdown_tracer_provider(); opentelemetry::global::shutdown_tracer_provider();

View File

@ -14,7 +14,7 @@ use email_manager::TestMail;
use jemallocator::Jemalloc; use jemallocator::Jemalloc;
use model::{Email, Encrypt, Login, PassHash, Password, Role}; use model::{Email, Encrypt, Login, PassHash, Password, Role};
use opts::{ use opts::{
Command, CreateAccountCmd, CreateAccountOpts, GenerateHashOpts, MigrateOpts, Opts, ServerOpts, Command, CreateAccountCmd, CreateAccountOpts, GenerateHashOpts, Opts, ServerOpts,
TestMailerOpts, TestMailerOpts,
}; };
use validator::{validate_email, validate_length}; use validator::{validate_email, validate_length};
@ -58,8 +58,8 @@ async fn server(opts: ServerOpts) -> Result<()> {
let fs_manager = fs_manager::FsManager::build(app_config.clone()) let fs_manager = fs_manager::FsManager::build(app_config.clone())
.await .await
.expect("Failed to initialize file system storage"); .expect("Failed to initialize file system storage");
let cart_manager = cart_manager::CartManager::new(db.clone()).start(); let cart_manager = channels::carts::rpc::create_client(app_config.clone()).await;
let account_manager = channels::account::rpc::create_client(app_config.clone()).await; let account_manager = channels::accounts::rpc::create_client(app_config.clone()).await;
let addr = { let addr = {
let l = app_config.lock(); let l = app_config.lock();
let w = l.web(); let w = l.web();
@ -105,22 +105,6 @@ async fn server(opts: ServerOpts) -> Result<()> {
.map_err(Error::Boot) .map_err(Error::Boot)
} }
async fn migrate(opts: MigrateOpts) -> Result<()> {
use sqlx::migrate::MigrateError;
let config = config::default_load(&opts);
let db = database_manager::Database::build(config).await;
let res: std::result::Result<(), MigrateError> =
sqlx::migrate!("../migrations").run(db.pool()).await;
match res {
Ok(()) => Ok(()),
Err(e) => {
eprintln!("{e}");
std::process::exit(1);
}
}
}
async fn generate_hash(_opts: GenerateHashOpts) -> Result<()> { async fn generate_hash(_opts: GenerateHashOpts) -> Result<()> {
model::print_hash(); model::print_hash();
Ok(()) Ok(())
@ -241,7 +225,6 @@ async fn main() -> Result<()> {
let opts: Opts = gumdrop::Options::parse_args_default_or_exit(); let opts: Opts = gumdrop::Options::parse_args_default_or_exit();
match opts.cmd.unwrap_or_default() { match opts.cmd.unwrap_or_default() {
Command::Migrate(opts) => migrate(opts).await,
Command::Server(opts) => server(opts).await, Command::Server(opts) => server(opts).await,
Command::GenerateHash(opts) => generate_hash(opts).await, Command::GenerateHash(opts) => generate_hash(opts).await,
Command::CreateAccount(opts) => create_account(opts).await, Command::CreateAccount(opts) => create_account(opts).await,

View File

@ -34,8 +34,6 @@ impl UpdateConfig for Opts {
pub enum Command { pub enum Command {
#[options(help = "Run server")] #[options(help = "Run server")]
Server(ServerOpts), Server(ServerOpts),
#[options(help = "Migrate database")]
Migrate(MigrateOpts),
#[options(help = "Generate new salt for passwords")] #[options(help = "Generate new salt for passwords")]
GenerateHash(GenerateHashOpts), GenerateHash(GenerateHashOpts),
#[options(help = "Create new account")] #[options(help = "Create new account")]
@ -54,9 +52,6 @@ impl UpdateConfig for Command {
Command::Server(opts) => { Command::Server(opts) => {
opts.update_config(config); opts.update_config(config);
} }
Command::Migrate(opts) => {
opts.update_config(config);
}
Command::GenerateHash(opts) => { Command::GenerateHash(opts) => {
opts.update_config(config); opts.update_config(config);
} }

View File

@ -40,7 +40,6 @@ pub enum Error {
CriticalFailure, CriticalFailure,
Public(public::Error), Public(public::Error),
Admin(admin::Error), Admin(admin::Error),
Cart(cart_manager::Error),
Database(database_manager::Error), Database(database_manager::Error),
Email(email_manager::Error), Email(email_manager::Error),
Fs(fs_manager::Error), Fs(fs_manager::Error),
@ -78,7 +77,6 @@ impl Display for Error {
}) })
.unwrap_or_default(), .unwrap_or_default(),
Error::CriticalFailure => String::from("Something went wrong"), Error::CriticalFailure => String::from("Something went wrong"),
Error::Cart(_e) => serde_json::to_string(&self).unwrap_or_default(),
Error::Database(_e) => serde_json::to_string(&self).unwrap_or_default(), Error::Database(_e) => serde_json::to_string(&self).unwrap_or_default(),
Error::Email(_e) => serde_json::to_string(&self).unwrap_or_default(), Error::Email(_e) => serde_json::to_string(&self).unwrap_or_default(),
Error::Fs(_e) => serde_json::to_string(&self).unwrap_or_default(), Error::Fs(_e) => serde_json::to_string(&self).unwrap_or_default(),
@ -100,7 +98,6 @@ impl ResponseError for Error {
} }
Error::Admin(_) => StatusCode::BAD_REQUEST, Error::Admin(_) => StatusCode::BAD_REQUEST,
Error::Public(_) => StatusCode::BAD_REQUEST, Error::Public(_) => StatusCode::BAD_REQUEST,
Error::Cart(_) => StatusCode::BAD_REQUEST,
Error::Database(_) => StatusCode::BAD_REQUEST, Error::Database(_) => StatusCode::BAD_REQUEST,
Error::Email(_) => StatusCode::BAD_REQUEST, Error::Email(_) => StatusCode::BAD_REQUEST,
Error::Fs(_) => StatusCode::BAD_REQUEST, Error::Fs(_) => StatusCode::BAD_REQUEST,

View File

@ -2,7 +2,6 @@ use actix::Addr;
use actix_web::web::{scope, Data, Json, ServiceConfig}; use actix_web::web::{scope, Data, Json, ServiceConfig};
use actix_web::{delete, get, post, put, HttpRequest, HttpResponse}; use actix_web::{delete, get, post, put, HttpRequest, HttpResponse};
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use cart_manager::{query_cart, CartManager};
use database_manager::{query_db, Database}; use database_manager::{query_db, Database};
use model::api; use model::api;
use order_manager::{query_order, OrderManager}; use order_manager::{query_order, OrderManager};
@ -96,24 +95,32 @@ async fn shopping_cart(
#[put("/shopping-cart-item")] #[put("/shopping-cart-item")]
async fn update_cart_item( async fn update_cart_item(
cart: Data<Addr<CartManager>>, cart: Data<channels::carts::rpc::CartsClient>,
tm: Data<Addr<TokenManager>>, tm: Data<Addr<TokenManager>>,
credentials: BearerAuth, credentials: BearerAuth,
Json(payload): Json<api::UpdateItemInput>, Json(payload): Json<api::UpdateItemInput>,
) -> Result<Json<api::UpdateItemOutput>> { ) -> Result<Json<api::UpdateItemOutput>> {
use channels::carts::modify_item::Input;
let token = credentials.require_user(tm.into_inner()).await?; let token = credentials.require_user(tm.into_inner()).await?;
let item: Option<model::ShoppingCartItem> = query_cart!( let item = match cart
cart, .modify_item(
cart_manager::ModifyItem { tarpc::context::current(),
buyer_id: token.account_id(), Input {
product_id: payload.product_id, buyer_id: token.account_id(),
quantity: payload.quantity, product_id: payload.product_id,
quantity_unit: payload.quantity_unit, quantity: payload.quantity,
}, quantity_unit: payload.quantity_unit,
routes::Error::Public(super::Error::ModifyItem.into()), },
routes::Error::Public(PublicError::DatabaseConnection) )
); .await
{
Ok(res) => res.item,
Err(e) => {
tracing::error!("{}", e);
return Err(routes::Error::CriticalFailure);
}
};
match item { match item {
Some(item) => Ok(Json(api::UpdateItemOutput { Some(item) => Ok(Json(api::UpdateItemOutput {
@ -125,40 +132,63 @@ async fn update_cart_item(
#[put("/shopping-cart")] #[put("/shopping-cart")]
async fn update_cart( async fn update_cart(
cart: Data<Addr<CartManager>>, cart: Data<channels::carts::rpc::CartsClient>,
tm: Data<Addr<TokenManager>>, tm: Data<Addr<TokenManager>>,
credentials: BearerAuth, credentials: BearerAuth,
Json(payload): Json<api::UpdateCartInput>, Json(payload): Json<api::UpdateCartInput>,
) -> Result<Json<api::UpdateCartOutput>> { ) -> Result<Json<api::UpdateCartOutput>> {
let token = credentials.require_user(tm.into_inner()).await?; let token = credentials.require_user(tm.into_inner()).await?;
let items = payload let items = {
.items use channels::carts::modify_item::Input;
.into_iter() payload
.map( .items
|model::api::UpdateItemInput { .into_iter()
product_id, .map(
quantity, |model::api::UpdateItemInput {
quantity_unit, product_id,
}| cart_manager::ModifyItem { quantity,
buyer_id: token.account_id(), quantity_unit,
product_id, }| Input {
quantity, buyer_id: token.account_id(),
quantity_unit, product_id,
}, quantity,
) quantity_unit,
.collect(); },
)
.collect()
};
let res: cart_manager::ModifyCartResult = query_cart!( use channels::carts::modify_cart::{Input, Output};
cart, let res = {
cart_manager::ModifyCart { match cart
buyer_id: token.account_id(), .modify_cart(
items, tarpc::context::current(),
checkout_notes: payload.notes, Input {
payment_method: payload.payment_method, buyer_id: token.account_id(),
}, items,
routes::Error::Public(super::Error::ModifyItem.into()), checkout_notes: payload.notes,
routes::Error::Public(PublicError::DatabaseConnection) payment_method: payload.payment_method,
); },
)
.await
{
Ok(Output {
cart: Some(cart), ..
}) => cart,
Ok(Output { error: Some(e), .. }) => {
tracing::error!("{}", e);
return Err(routes::Error::Public(super::Error::ModifyItem.into()));
}
Ok(out) => {
tracing::error!("invalid output {:?}", out);
return Err(routes::Error::Public(super::Error::ModifyItem.into()));
}
Err(e) => {
tracing::error!("{}", e);
return Err(routes::Error::Public(super::Error::ModifyItem.into()));
}
}
};
Ok(Json(api::UpdateCartOutput { Ok(Json(api::UpdateCartOutput {
cart_id: res.cart_id, cart_id: res.cart_id,
@ -170,46 +200,68 @@ async fn update_cart(
#[delete("/shopping-cart-item")] #[delete("/shopping-cart-item")]
async fn delete_cart_item( async fn delete_cart_item(
db: Data<Addr<Database>>, cart: Data<channels::carts::rpc::CartsClient>,
cart: Data<Addr<CartManager>>,
tm: Data<Addr<TokenManager>>, tm: Data<Addr<TokenManager>>,
credentials: BearerAuth, credentials: BearerAuth,
Json(payload): Json<api::DeleteItemInput>, Json(payload): Json<api::DeleteItemInput>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let token = credentials.require_user(tm.into_inner()).await?; let token = credentials.require_user(tm.into_inner()).await?;
let sc: model::ShoppingCart = query_db!( let sc = {
db, use channels::carts::active_shopping_cart::{Input, Output};
database_manager::EnsureActiveShoppingCart { match cart
buyer_id: token.account_id(), .active_shopping_cart(
}, tarpc::context::current(),
routes::Error::Public(super::Error::RemoveItem.into()), Input {
routes::Error::Public(PublicError::DatabaseConnection) buyer_id: token.account_id(),
); },
)
match cart .await
.into_inner() {
.send(cart_manager::RemoveProduct { Ok(Output {
shopping_cart_id: sc.id, cart: Some(cart), ..
shopping_cart_item_id: payload.shopping_cart_item_id, }) => cart,
}) Ok(Output { error: Some(e), .. }) => {
.await tracing::error!("{}", e);
{ return Err(routes::Error::Public(super::Error::ModifyItem.into()));
Ok(Ok(_)) => Ok(HttpResponse::Ok().json(api::DeleteItemOutput { success: true })), }
Ok(Err(e)) => { Ok(out) => {
tracing::error!("{e}"); tracing::error!("invalid output {:?}", out);
Ok(HttpResponse::BadRequest().json(api::DeleteItemOutput { success: false })) return Err(routes::Error::Public(super::Error::ModifyItem.into()));
}
Err(e) => {
tracing::error!("{e}");
return Ok(
HttpResponse::BadRequest().json(api::DeleteItemOutput { success: false })
);
}
} }
Err(e) => { };
tracing::error!("{e:?}");
Err(routes::Error::Public(PublicError::DatabaseConnection)) {
use channels::carts::remove_product::Input;
match cart
.remove_product(
tarpc::context::current(),
Input {
shopping_cart_id: sc.id,
shopping_cart_item_id: payload.shopping_cart_item_id,
},
)
.await
{
Ok(_) => Ok(HttpResponse::Ok().json(api::DeleteItemOutput { success: true })),
Err(e) => {
tracing::error!("{e:?}");
Err(routes::Error::Public(PublicError::DatabaseConnection))
}
} }
} }
} }
#[get("/me")] #[get("/me")]
pub(crate) async fn me( pub(crate) async fn me(
account: Data<channels::account::rpc::AccountsClient>, account: Data<channels::accounts::rpc::AccountsClient>,
tm: Data<Addr<TokenManager>>, tm: Data<Addr<TokenManager>>,
credentials: BearerAuth, credentials: BearerAuth,
) -> routes::Result<Json<model::api::Account>> { ) -> routes::Result<Json<model::api::Account>> {
@ -218,7 +270,13 @@ pub(crate) async fn me(
.await? .await?
.account_id(); .account_id();
match account.me(tarpc::context::current(), account_id).await { match account
.me(
tarpc::context::current(),
channels::accounts::me::Input { account_id },
)
.await
{
Ok(me) => Ok(Json((me.account.unwrap(), me.addresses.unwrap()).into())), Ok(me) => Ok(Json((me.account.unwrap(), me.addresses.unwrap()).into())),
Err(e) => { Err(e) => {
tracing::error!("{}", e); tracing::error!("{}", e);

View File

@ -248,3 +248,28 @@ pub async fn modify_cart(
}) })
) )
} }
pub async fn active_shopping_cart(
input: carts::active_shopping_cart::Input,
db: Database,
) -> carts::active_shopping_cart::Output {
use carts::active_shopping_cart::Output;
tracing::debug!("{:?}", input);
let mut t = begin_t!(db);
let dbm = crate::db::EnsureActiveShoppingCart {
buyer_id: input.buyer_id,
};
let cart = match dbm.run(&mut t).await {
Ok(cart) => cart,
Err(e) => {
tracing::error!("{}", e);
t.rollback().await.ok();
return Output::error(Error::NoActiveCart);
}
};
end_t!(t, Output::cart(cart))
}

View File

@ -33,13 +33,21 @@ impl Carts for CartsServer {
crate::actions::modify_cart(input, self.db).await crate::actions::modify_cart(input, self.db).await
} }
async fn remove_cart( async fn remove_product(
self, self,
_: context::Context, _: context::Context,
input: channels::carts::remove_product::Input, input: channels::carts::remove_product::Input,
) -> channels::carts::remove_product::Output { ) -> channels::carts::remove_product::Output {
crate::actions::remove_product(input, self.db).await crate::actions::remove_product(input, self.db).await
} }
async fn active_shopping_cart(
self,
_: context::Context,
input: channels::carts::active_shopping_cart::Input,
) -> channels::carts::active_shopping_cart::Output {
crate::actions::active_shopping_cart(input, self.db).await
}
} }
pub async fn start(config: SharedAppConfig, db: Database, mqtt_client: AsyncClient) { pub async fn start(config: SharedAppConfig, db: Database, mqtt_client: AsyncClient) {

View File

@ -78,27 +78,6 @@ pub mod register {
} }
} }
// #[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)]
// pub enum AccountFailure {
// #[error("Failed to hash password")]
// FailedToHashPassword,
// #[error("Failed to save account")]
// SaveAccount,
// #[error("Internal server error")]
// InternalServerError,
// }
//
// impl TryFrom<bytes::Bytes> for AccountFailure {
// type Error = Error;
//
// fn try_from(value: bytes::Bytes) -> Result<Self, Self::Error> {
// bincode::deserialize(value.as_ref()).map_err(|e| {
// tracing::error!("{}", e);
// Error::InvalidAccountFailure
// })
// }
// }
pub mod me { pub mod me {
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Input { pub struct Input {

View File

@ -130,8 +130,41 @@ pub mod modify_cart {
} }
} }
pub mod active_shopping_cart {
use super::Error;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Input {
pub buyer_id: model::AccountId,
}
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Output {
pub error: Option<Error>,
pub cart: Option<model::ShoppingCart>,
}
impl Output {
pub fn error(error: Error) -> Self {
Self {
error: Some(error),
..Default::default()
}
}
pub fn cart(cart: model::ShoppingCart) -> Self {
Self {
cart: Some(cart),
..Default::default()
}
}
}
}
pub mod rpc { pub mod rpc {
use super::{modify_cart, modify_item, remove_product}; use config::SharedAppConfig;
use super::{active_shopping_cart, modify_cart, modify_item, remove_product};
#[tarpc::service] #[tarpc::service]
pub trait Carts { pub trait Carts {
@ -141,7 +174,31 @@ pub mod rpc {
/// Change entire shopping cart content. /// Change entire shopping cart content.
async fn modify_cart(input: modify_cart::Input) -> modify_cart::Output; async fn modify_cart(input: modify_cart::Input) -> modify_cart::Output;
/// Remove entire shopping cart. /// Remove product from shopping cart.
async fn remove_cart(input: remove_product::Input) -> remove_product::Output; async fn remove_product(input: remove_product::Input) -> remove_product::Output;
async fn active_shopping_cart(
input: active_shopping_cart::Input,
) -> active_shopping_cart::Output;
}
pub async fn create_client(config: SharedAppConfig) -> CartsClient {
use tarpc::client;
use tarpc::tokio_serde::formats::Bincode;
let addr = {
let l = config.lock();
(l.account_manager().bind.clone(), l.account_manager().port)
};
let transport = tarpc::serde_transport::tcp::connect(addr, Bincode::default);
let client = CartsClient::new(
client::Config::default(),
transport.await.expect("Failed to connect to server"),
)
.spawn();
client
} }
} }

View File

@ -5,7 +5,11 @@ use sonic_channel::{Dest, ObjDest, PushRequest, QueryRequest, SonicChannel};
#[macro_export] #[macro_export]
macro_rules! search_async_handler { macro_rules! search_async_handler {
($msg: ty, $async: ident, $res: ty) => { ($msg: ty, async fn call($($argv: ident : $arg_t: ty,)*) -> Result<Option< $res: ty >> $body: block) => {
impl $msg {
async fn call ( $($argv : $arg_t,)* ) -> Result<Option< $res >> $body
}
impl actix::Handler<$msg> for SearchManager { impl actix::Handler<$msg> for SearchManager {
type Result = actix::ResponseActFuture<Self, Result<Option<$res>>>; type Result = actix::ResponseActFuture<Self, Result<Option<$res>>>;
@ -14,13 +18,13 @@ macro_rules! search_async_handler {
match self.channels.clone() { match self.channels.clone() {
Some(channels) => { Some(channels) => {
let config = self.config.clone(); let config = self.config.clone();
Box::pin(async { $async(msg, channels, config).await }.into_actor(self)) Box::pin(async { <$msg>::call(msg, channels, config).await }.into_actor(self))
} }
None => Box::pin(async { Ok(None) }.into_actor(self)), None => Box::pin(async { Ok(None) }.into_actor(self)),
} }
} }
} }
}; }
} }
#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)] #[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)]
@ -91,28 +95,31 @@ pub struct Search {
pub lang: String, pub lang: String,
} }
search_async_handler!(Search, search, Vec<String>); pub type StringList = Vec<String>;
pub(crate) async fn search( search_async_handler!(
msg: Search, Search,
channels: Channels, async fn call(
_config: SharedAppConfig, msg: Search,
) -> Result<Option<Vec<String>>> { channels: Channels,
if let Ok(l) = channels.search.lock() { _config: SharedAppConfig,
match l.query(QueryRequest::new( ) -> Result<Option<StringList>> {
Dest::col_buc(msg.collection, msg.lang), if let Ok(l) = channels.search.lock() {
&msg.query, match l.query(QueryRequest::new(
)) { Dest::col_buc(msg.collection, msg.lang),
Ok(res) => Ok(Some(res)), &msg.query,
Err(e) => { )) {
tracing::error!("{e:?}"); Ok(res) => Ok(Some(res)),
Err(Error::QueryFailed) Err(e) => {
tracing::error!("{e:?}");
Err(Error::QueryFailed)
}
} }
} else {
Ok(None)
} }
} else {
Ok(None)
} }
} );
#[derive(actix::Message)] #[derive(actix::Message)]
#[rtype(result = "Result<Option<()>>")] #[rtype(result = "Result<Option<()>>")]
@ -123,25 +130,26 @@ pub struct CreateIndex {
pub lang: String, pub lang: String,
} }
search_async_handler!(CreateIndex, create_index, ()); search_async_handler!(
CreateIndex,
pub(crate) async fn create_index( async fn call(
msg: CreateIndex, msg: CreateIndex,
channels: Channels, channels: Channels,
_config: SharedAppConfig, _config: SharedAppConfig,
) -> Result<Option<()>> { ) -> Result<Option<()>> {
if let Ok(l) = channels.ingest.lock() { if let Ok(l) = channels.ingest.lock() {
match l.push(PushRequest::new( match l.push(PushRequest::new(
ObjDest::new(Dest::col_buc(msg.collection, msg.lang), &msg.key), ObjDest::new(Dest::col_buc(msg.collection, msg.lang), &msg.key),
&msg.value, &msg.value,
)) { )) {
Ok(_) => Ok(Some(())), Ok(_) => Ok(Some(())),
Err(e) => { Err(e) => {
tracing::error!("{e:?}"); tracing::error!("{e:?}");
Err(Error::CantCreate) Err(Error::CantCreate)
}
} }
} else {
Ok(Some(()))
} }
} else {
Ok(Some(()))
} }
} );