diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/db.rs | 20 | ||||
| -rw-r--r-- | src/main.rs | 24 | ||||
| -rw-r--r-- | src/models.rs | 17 | ||||
| -rw-r--r-- | src/schema.rs | 8 | ||||
| -rw-r--r-- | src/val.rs | 37 | ||||
| -rw-r--r-- | src/validator.rs | 51 | ||||
| -rw-r--r-- | src/views.rs | 45 |
7 files changed, 202 insertions, 0 deletions
diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..142aee5 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,20 @@ +use diesel::prelude::*; +use diesel::sqlite::SqliteConnection; +use dotenv::dotenv; +use std::env; +use crate::models::Post; + +pub fn establish_connection() -> SqliteConnection { + dotenv().ok(); + + let database_url = env::var("DATABASE_URL") + .expect("DATABASE_URL must be set"); + SqliteConnection::establish(&database_url) + .expect(&format!("Error connecting to {}", database_url)) +} + +pub fn create_post<'a>(conn: &SqliteConnection, post: &Post) -> Result<usize, diesel::result::Error> { + use crate::schema::posts; + // TODO(andrew): add error checking + return diesel::insert_into(posts::table).values(post).execute(conn); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e0e65a3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,24 @@ +#[macro_use] +extern crate diesel; +extern crate dotenv; + +mod views; +mod models; +mod schema; +mod db; +mod validator; + +use actix_web::{App, HttpServer}; + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + HttpServer::new(|| { + App::new() + .service(views::hello) + .service(views::echo) + .service(views::get_posts) + }) + .bind("127.0.0.1:8080")? + .run() + .await +} diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..d025cf0 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,17 @@ +use serde::{Serialize, Deserialize}; +use diesel::Queryable; +use crate::schema::posts; + +#[derive(Queryable, Insertable, Serialize, Deserialize)] +#[table_name="posts"] +pub struct Post { + pub id: i32, + pub title: String, + pub body: String, + pub published: bool +} + +#[derive(Serialize)] +pub struct PostResponse<'a> { + pub msg: &'a str +} diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..d4dc4f4 --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,8 @@ +table! { + posts (id) { + id -> Integer, + title -> Text, + body -> Text, + published -> Bool, + } +} diff --git a/src/val.rs b/src/val.rs new file mode 100644 index 0000000..6cc7dfe --- /dev/null +++ b/src/val.rs @@ -0,0 +1,37 @@ +mod validator; + +use serde::{Serialize, Deserialize}; +use serde_json; + +use validator::Validator; + +#[derive(Deserialize, Serialize)] +struct Profile { + tg_id: i32, + fullname: String, + username: String, + is_admin: bool, + is_hidden: bool +} + + +fn test_validation() -> Result<(), String> { + let profile = Profile { + tg_id: 123, + fullname: String::from("Andrew Guschin"), + username: String::from("guschin"), + is_admin: true, + is_hidden: false + }; + + let request_data = serde_json::to_string(&profile).unwrap(); + + let validator = match Validator::from_file("src/schema.json") { + Ok(val) => val, + Err(msg) => return Err(msg) + }; + let result = validator.check(&request_data); + println!("Is valid: {}", result); + + return Ok(()); +} diff --git a/src/validator.rs b/src/validator.rs new file mode 100644 index 0000000..1fa52bc --- /dev/null +++ b/src/validator.rs @@ -0,0 +1,51 @@ +extern crate serde; +extern crate serde_json; +extern crate valico; + +use std::fs::File; +use std::io::BufReader; +use std::io::Read; + +use serde_json::from_str; +use valico::json_schema; + +pub struct Validator { + schema_content: String +} + +impl Validator { + pub fn from_file(schema_path: &'static str) -> Result<Validator, String> { + let file = match File::open(schema_path) { + Ok(file) => file, + Err(_) => return Err(String::from("Could not open file")) + }; + let mut buf_reader = BufReader::new(file); + let mut content = String::new(); + match buf_reader.read_to_string(&mut content) { + Ok(_) => (), + Err(_) => return Err(String::from("Could not read from file")) + }; + return Validator::from_string(content); + } + + pub fn from_string(schema: String) -> Result<Validator, String> { + let s = match from_str(&schema) { + Ok(schema) => schema, + Err(_) => return Err(String::from("Could not parse schema")) + }; + let mut scope = json_schema::Scope::new(); + match scope.compile_and_return(s, true) { + Ok(_) => (), + Err(_) => return Err(String::from("Could not compile schema")) + }; + return Ok(Validator { schema_content: schema }); + } + + pub fn check(&self, content: &str) -> bool { + let obj = from_str(content).unwrap(); + let mut scope = json_schema::Scope::new(); + let schema = from_str(&self.schema_content).unwrap(); + let schema = scope.compile_and_return(schema, true).ok().unwrap(); + return schema.validate(&obj).is_valid(); + } +} diff --git a/src/views.rs b/src/views.rs new file mode 100644 index 0000000..3622719 --- /dev/null +++ b/src/views.rs @@ -0,0 +1,45 @@ +use actix_web::{get, post, HttpResponse, Responder}; +use diesel::prelude::*; +use crate::models::{Post, PostResponse}; +use crate::validator::Validator; +use crate::db; +use serde_json; + +#[get("/")] +pub async fn hello() -> impl Responder { + HttpResponse::Ok().body("Hello world!") +} + +#[post("/api/posts/add")] +pub async fn echo(req_body: String) -> impl Responder { + let validator = Validator::from_file("schemas/post.json").unwrap(); + let result = validator.check(&req_body); + + if !result { + return HttpResponse::BadRequest().json(PostResponse { + msg: "Message doesn't conform to schema" + }); + } + + let conn = db::establish_connection(); + let new_post: Post = serde_json::from_str(&req_body).unwrap(); + let result = db::create_post(&conn, &new_post); + return match result { + Ok(_) => HttpResponse::Ok().json(new_post), + Err(res) => HttpResponse::InternalServerError().json(PostResponse { + msg: &res.to_string() + }) + }; +} + +#[get("/api/posts")] +pub async fn get_posts() -> impl Responder { + use crate::schema::posts::dsl::*; + let connection = db::establish_connection(); + let results = posts.filter(published.eq(true)) + .limit(5) + .load::<Post>(&connection) + .expect("Error loading posts"); + HttpResponse::Ok().json(&results) +} + |