Skip to content

Commit 64c4440

Browse files
committed
in-memory database configs
1 parent 08a5dc6 commit 64c4440

File tree

11 files changed

+119
-34
lines changed

11 files changed

+119
-34
lines changed

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ structopt = "0.3"
3232
bytes = "1.0.0"
3333
toml = "0.4.2"
3434
convert_case = "0.4"
35+
clap = { version = "2.33", default-features = false }
3536

3637
[[bin]]
37-
name = "cargo-actix"
38+
name = "actix-cli"
3839
path = "src/main.rs"

README.md

+10-5
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ CLI to create web services with Actix
55
- [x] Fault-tolerant option can be enabled to use Bastion
66
- [x] Logger middleware option can be enabled with `--request-logger` flag
77
- [x] Basic CRUD controllers
8-
- [ ] Databa configurations
9-
- [ ] Read routes configs from `Config.toml`
8+
- [ ] `Unwrap`s to Crate Error
9+
- [ ] Database configurations
1010
- [ ] Auth middleware option can be enabled with `--auth` flag
11-
- [ ] Read models configs from `Config.toml`
1211
- [ ] Basic Docker config can be enabled with flag
12+
- [ ] Read routes configs from `Config.toml`
13+
- [ ] Read models configs from `Config.toml`
14+
1315

1416
> Not defining a Database will mean a `Context` will be created to support a basic `HashMap`.
1517
@@ -26,17 +28,19 @@ CLI to create web services with Actix
2628

2729
```sh
2830
actix-cli 0.1.0
29-
A CLI to create actix-web projects boilerplate.
31+
A CLI to create actix-web projects boilerplate
3032

3133
USAGE:
32-
actix-cli [OPTIONS] --name <name>
34+
actix-cli [OPTIONS] --context <context> --name <name>
3335

3436
FLAGS:
3537
-h, --help Prints help information
3638
-V, --version Prints version information
3739

3840
OPTIONS:
3941
-c, --config-file <config-file> Config.toml file path
42+
--context <context> Which database configuration. Currently only `InMemory` allowed [possible
43+
values: InMemory]
4044
-f, --fault-tolerant <fault-tolerant> Enables Bastion for fault tolerant system [default: true]
4145
-n, --name <name> Defines project name in Cargo.toml
4246
-r, --request-logger <request-logger> Enables request logger as `[IP:%a DATETIME:%t REQUEST:\"%r\" STATUS: %s
@@ -48,6 +52,7 @@ OPTIONS:
4852
* `request-logger`: `bool`.
4953
* `name`: `String`.
5054
* `config-file`: `std::path::PathBuf`.
55+
* `context`: case insetive enum containing `InMemory` and in the future `PostgresDB` and `DynamoDB`.
5156
5257
### Example
5358

src/config/crud.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ fn create_crud_routes(routes: &std::collections::BTreeMap<String, Value>, name:
6363
"delete" => Ok(format!("
6464
.route(\"{}\", web::delete().to(delete_{}))", route ,name)),
6565
"list" => Ok(format!("
66-
.route(\"{}\", web::post().to(show_{}))", route ,name)),
66+
.route(\"{}\", web::get().to(show_{}))", route ,name)),
6767
_ => Err(ActixCliError::UnknwonCrudItem)
6868
}?;
6969
routes_def.push_str(&route_config);

src/database/context.rs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use clap::arg_enum;
2+
use convert_case::{Case, Casing};
3+
4+
arg_enum! {
5+
#[derive(Debug, Clone)]
6+
pub enum PossibleContexts {
7+
InMemory
8+
}
9+
}
10+
11+
pub fn possible_contexts(_: PossibleContexts, model_name: String, name: String) -> String {
12+
in_memory()
13+
.replace("@", &model_name.to_case(Case::Pascal))
14+
.replace("{name}", &name.to_case(Case::Snake))
15+
}
16+
17+
const fn in_memory() -> &'static str {
18+
" use {name}::model::{name}::@;
19+
let context: std::collections::HashMap<String, @> = std::collections::HashMap::new();"
20+
}

src/database/mod.rs

+12-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::io::prelude::*;
33
use std::fs;
44
use convert_case::{Case, Casing};
55

6+
pub mod context;
7+
68
use crate::config::crud::CrudConfig;
79
use crate::error::ActixCliError;
810

@@ -25,13 +27,12 @@ pub fn create_crud_model_rs(name: String, model_config: Option<CrudConfig>) -> R
2527

2628
fn database_info(project_name: &str, module_name: &str) -> String {
2729
let obj = module_name.to_case(Case::Pascal);
28-
"use crate::{project_name}::model::{project_name}::{@, @Update};
30+
"use crate::{project_name}::model::{project_name}::{@, @Update, @Response};
2931
use std::collections::HashMap;
3032
pub struct Context(HashMap<String, @>);
3133
3234
impl Context {
33-
pub fn new() -> Self {
34-
let map: HashMap<String, @> = HashMap::new();
35+
pub fn new(map: HashMap<String, @>) -> Self {
3536
Context{0: map}
3637
}
3738
@@ -56,10 +57,14 @@ impl Context {
5657
}
5758
}
5859
59-
pub fn all(&mut self) -> Vec<Object> {
60-
self.0.iter()
61-
.map(|(k,v)| *v)
62-
.collect::<Vec<Object>>()
60+
pub fn all(&mut self) -> Vec<ObjectResponse> {
61+
self.0
62+
.iter()
63+
.map(|(k, v)| ObjectResponse {
64+
object: v.clone(),
65+
id: k.to_string(),
66+
})
67+
.collect::<Vec<ObjectResponse>>()
6368
}
6469
6570
pub fn update(&mut self, key: String, value: ObjectUpdate) -> bool {

src/input.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::error::ActixCliError;
2+
use crate::database::context::PossibleContexts;
23
use structopt::StructOpt;
34
use std::path::PathBuf;
45

@@ -19,6 +20,9 @@ pub struct Opt {
1920
/// Config.toml file path
2021
#[structopt(short, long, parse(from_os_str))]
2122
pub config_file: Option<PathBuf>,
23+
/// Which database configuration. Currently only `InMemory` allowed.
24+
#[structopt(long, possible_values = &PossibleContexts::variants(), case_insensitive = true)]
25+
pub context: PossibleContexts
2226

2327
}
2428

src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn main() {
2323
let configs = config::crud::create_crud_info(toml, opt.name.clone()).unwrap();
2424

2525
// main.rs
26-
let _ = main_core::create_main(opt.name.clone(), opt.fault_tolerant, opt.request_logger).unwrap();
26+
let _ = main_core::create_main(opt.name.clone(), opt.fault_tolerant, opt.request_logger, opt.context, configs.clone().unwrap().model_name).unwrap();
2727
let _ = main_core::routes::create_routes_rs(opt.name.clone(), configs.clone()).unwrap();
2828
let _ = main_core::controller::create_controllers_rs(opt.name.clone(), configs.clone()).unwrap();
2929
if configs.is_some() {

src/main_core/controller.rs

+42-11
Original file line numberDiff line numberDiff line change
@@ -11,50 +11,81 @@ fn crud_controller(name: &str, object: &str) -> String {
1111
format!("
1212
use crate::{}::model::{}::*;
1313
use crate::{}::database::Context;
14+
use std::sync::{{Arc, Mutex}};
1415
1516
use actix_web::{{web, HttpResponse, Responder}};
1617
17-
pub async fn create_{}(state: web::Data<Context>, info: web::Json<@object>) -> impl Responder {{
18+
pub async fn create_{}(state: web::Data<Arc<Mutex<Context>>>, info: web::Bytes) -> impl Responder {{
1819
let id = uuid::Uuid::new_v4();
1920
20-
unimplemented!()
21+
let bytes_str = match String::from_utf8(info.to_vec()) {{
22+
Ok(text) => Ok(text),
23+
Err(_) => Err(\"bytes parse error\")
24+
}};
25+
26+
let info = serde_json::from_str(&bytes_str.unwrap()).unwrap();
27+
let mut state = state.lock().unwrap();
28+
match state.create(id.to_string(), info) {{
29+
true => HttpResponse::Created().body(id.to_string()),
30+
false => HttpResponse::Conflict().finish(),
31+
}}
2132
}}
2233
23-
pub async fn show_{}(state: web::Data<Context>) -> impl Responder {{
24-
unimplemented!()
34+
pub async fn show_{}(state: web::Data<Arc<Mutex<Context>>>) -> impl Responder {{
35+
let mut state = state.lock().unwrap();
36+
let response = state.all();
37+
HttpResponse::Ok().json(response)
2538
}}
2639
27-
pub async fn delete_{}(id: web::Path<String>, state: web::Data<Context>) -> impl Responder {{
40+
pub async fn delete_{}(id: web::Path<String>, state: web::Data<Arc<Mutex<Context>>>) -> impl Responder {{
2841
let uuid = id.to_string();
2942
3043
if uuid::Uuid::parse_str(&uuid).is_err() {{
3144
return HttpResponse::BadRequest().body(\"Id must be a Uuid::V4\");
3245
}}
3346
34-
unimplemented!()
47+
let mut state = state.lock().unwrap();
48+
match state.delete(id.to_string()) {{
49+
true => HttpResponse::Ok().finish(),
50+
false => HttpResponse::BadRequest().finish(),
51+
}}
3552
}}
3653
37-
pub async fn get_{}(id: web::Path<String>, state: web::Data<Context>) -> impl Responder {{
54+
pub async fn get_{}(id: web::Path<String>, state: web::Data<Arc<Mutex<Context>>>) -> impl Responder {{
3855
let uuid = id.to_string();
3956
4057
if uuid::Uuid::parse_str(&uuid).is_err() {{
4158
return HttpResponse::BadRequest().body(\"Id must be a Uuid::V4\");
4259
}}
4360
44-
unimplemented!()
61+
let mut state = state.lock().unwrap();
62+
match state.get(uuid) {{
63+
Some(body) => HttpResponse::Ok().json(body),
64+
None => HttpResponse::BadRequest().finish(),
65+
}}
4566
}}
4667
4768
pub async fn update_{}(
4869
id: web::Path<String>,
49-
info: web::Json<@objectUpdate>,
50-
state: web::Data<Context>) -> impl Responder {{
70+
info: web::Bytes,
71+
state: web::Data<Arc<Mutex<Context>>>) -> impl Responder {{
5172
let uuid = id.to_string();
5273
5374
if uuid::Uuid::parse_str(&uuid).is_err() {{
5475
return HttpResponse::BadRequest().body(\"Id must be a Uuid::V4\");
5576
}}
77+
78+
let bytes_str = match String::from_utf8(info.to_vec()) {{
79+
Ok(text) => Ok(text),
80+
Err(_) => Err(\"bytes parse error\")
81+
}};
5682
57-
unimplemented!()
83+
let info: ObjectUpdate = serde_json::from_str(&bytes_str.unwrap()).unwrap();
84+
let mut state = state.lock().unwrap();
85+
match state.update(id.to_string(), info) {{
86+
true => HttpResponse::Ok().finish(),
87+
false => HttpResponse::BadRequest().finish(),
88+
}}
5889
}}
5990
", name, name, name, object, object, object, object, object).replace("@object", &object.to_case(Case::Pascal))
6091
}

src/main_core/mod.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::fs::OpenOptions;
22
use std::io::prelude::*;
33
use crate::error::ActixCliError;
4+
use crate::database::context::{possible_contexts, PossibleContexts};
45

56
pub mod routes;
67
pub mod controller;
@@ -11,21 +12,25 @@ const fn main_content() -> &'static str {
1112
use actix_web::middleware::{DefaultHeaders, Logger};
1213
use actix_web::{App, HttpServer};
1314
{bastion_use}
15+
use std::sync::{Arc, Mutex};
1416
use uuid::Uuid;
1517
1618
mod {name};
1719
mod {name}_web;
1820
1921
use {name}_web::routes::app_routes;
20-
//{project_use}
22+
use {name}::database::Context;
2123
2224
#[actix_rt::main]
2325
async fn {bastion_main_fn}() -> Result<(), std::io::Error> {
2426
{not_bastion}
2527
26-
HttpServer::new(|| {
28+
{context}
29+
let data = Arc::new(Mutex::new(Context::new(context)));
30+
31+
HttpServer::new(move || {
2732
App::new()
28-
//{data}
33+
.data(data.clone())
2934
{logger}
3035
.configure(app_routes)
3136
})
@@ -68,7 +73,7 @@ const fn logger() -> &'static str {
6873
"#
6974
}
7075

71-
pub fn create_main(name: String, bastion: bool, request_logger: bool) -> Result<(), ActixCliError> {
76+
pub fn create_main(name: String, bastion: bool, request_logger: bool, context: PossibleContexts, model_name: String) -> Result<(), ActixCliError> {
7277
let mut main = String::from(main_content());
7378

7479
if let Some(idx) = main.find("{bastion_main}") {
@@ -111,7 +116,9 @@ pub fn create_main(name: String, bastion: bool, request_logger: bool) -> Result<
111116
}
112117
}
113118

114-
let main = main.replace("{name}", &name.replace("-", "_"));
119+
let main = main
120+
.replace("{name}", &name.replace("-", "_"))
121+
.replace("{context}", &possible_contexts(context, model_name, name.clone()));
115122
let mut file = OpenOptions::new()
116123
.write(true)
117124
.open(format!("./{}/src/main.rs", name))?;

src/main_core/model.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ pub fn create_crud_model_rs(name: String, model_config: Option<CrudConfig>) -> R
5454
eprintln!("Couldn't create src/{}/model/{}.rs: {}", _name, _name, e);
5555
}
5656

57-
if let Err(e) = writeln!(file, "{} ", serde_obj_update(&model_config.unwrap().model_name, model.clone()))
57+
if let Err(e) = writeln!(file, "{} ", serde_obj_update(&model_config.clone().unwrap().model_name, model.clone()))
58+
{
59+
eprintln!("Couldn't create src/{}/model/{}.rs: {}", _name, _name, e);
60+
}
61+
62+
if let Err(e) = writeln!(file, "{} ", serde_obj_response(&model_config.unwrap().model_name))
5863
{
5964
eprintln!("Couldn't create src/{}/model/{}.rs: {}", _name, _name, e);
6065
}
@@ -74,4 +79,12 @@ fn serde_obj_update(name: &str, model: String) -> String {
7479
+ &model.replace(": ", ": Option<")
7580
.replace(",", ">,")
7681
.replace(&name.to_case(Case::Pascal), &update)
77-
}
82+
}
83+
84+
fn serde_obj_response(name: &str) -> String {
85+
let response = name.to_case(Case::Pascal) + "Response";
86+
String::from(DERIVE) + "
87+
pub struct " + &response + "
88+
{pub object: "+ &name.to_case(Case::Pascal) + ", pub id: String,}"
89+
}
90+

src/main_core/routes.rs

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ pub fn app_routes(config: &mut web::ServiceConfig) {
2525
const fn modules() -> &'static str {
2626
r#"
2727
pub mod controllers;
28-
//pub mod schema;
2928
pub mod routes;
3029
//pub mod middleware;
3130
"#

0 commit comments

Comments
 (0)