overhaul; break up store/questions; fix up apis; entity/dto approach
This commit is contained in:
parent
0c685d755c
commit
9f312ba630
53
src/api.rs
53
src/api.rs
@ -1,11 +1,7 @@
|
|||||||
use axum::{
|
use crate::*;
|
||||||
extract::{Path, State},
|
|
||||||
http::StatusCode,
|
use self::{question::QuestionDTO, store::Store};
|
||||||
response::{IntoResponse, Response},
|
|
||||||
Json,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::question::{Question, Store};
|
|
||||||
/**
|
/**
|
||||||
GET /questions (empty body; return JSON)
|
GET /questions (empty body; return JSON)
|
||||||
POST /questions (JSON body; return HTTP status code)
|
POST /questions (JSON body; return HTTP status code)
|
||||||
@ -15,34 +11,43 @@ POST /answers (www-url-encoded body; return HTTP status code)
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
pub async fn read_question(State(store): State<Store>, Path(id): Path<u8>) -> Response {
|
pub async fn read_question(State(store): State<Store>, Path(id): Path<u8>) -> Response {
|
||||||
//TODO
|
match store.fetch(id) {
|
||||||
(StatusCode::OK, " Get Questions").into_response()
|
Ok(question) => question.to_dto(id).into_response(),
|
||||||
|
Err(e) => (StatusCode::NOT_FOUND, e).into_response(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_all_questions(State(store): State<Store>) -> Response {
|
pub async fn read_questions(State(store): State<Store>) -> Response {
|
||||||
//TODO
|
(StatusCode::OK, Json(store.fetch_all())).into_response()
|
||||||
(StatusCode::OK, " Get Questions").into_response()
|
|
||||||
}
|
}
|
||||||
pub async fn create_question(
|
pub async fn create_question(
|
||||||
State(store): State<Store>,
|
State(store): State<Store>,
|
||||||
Json(question): Json<Question>,
|
Json(question_dto): Json<QuestionDTO>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
//TODO
|
//Normally, the server should generate the id, user provided id's (and the whole request) should be rejected.
|
||||||
(StatusCode::CREATED, "Post Questions").into_response()
|
//QuestionDTO id then would be an option, but that makes to/from entity conversion more tricky.. todo
|
||||||
|
let (id, question) = question_dto.to_entity();
|
||||||
|
match store.add(id, question) {
|
||||||
|
Ok(question) => (StatusCode::CREATED, Json(&question.to_dto(id))).into_response(),
|
||||||
|
Err(e) => (StatusCode::CONFLICT, e).into_response(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub async fn update_question(
|
pub async fn update_question(
|
||||||
State(store): State<Store>,
|
State(store): State<Store>,
|
||||||
Path(id): Path<u8>,
|
Json(question_dto): Json<QuestionDTO>,
|
||||||
Json(question): Json<Question>,
|
|
||||||
) -> Response {
|
) -> Response {
|
||||||
//TODO
|
let (id, question) = question_dto.to_entity();
|
||||||
(StatusCode::CREATED, "Put Questions..").into_response()
|
match store.update(id, question) {
|
||||||
|
Ok(question) => question.to_dto(id).into_response(),
|
||||||
|
Err(e) => (StatusCode::NOT_FOUND, e).into_response(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub async fn delete_question(State(store): State<Store>, Path(id): Path<u8>) -> Response {
|
pub async fn delete_question(State(store): State<Store>, Path(id): Path<u8>) -> Response {
|
||||||
//TODO
|
match store.remove(id) {
|
||||||
(StatusCode::OK, "Delete Questions..").into_response()
|
Ok(question) => question.to_dto(id).into_response(),
|
||||||
|
Err(e) => (StatusCode::NOT_FOUND, e).into_response(),
|
||||||
}
|
}
|
||||||
pub async fn create_answer(State(store): State<Store> /*TODO */) -> Response {
|
}
|
||||||
//TODO
|
pub async fn create_answer(State(_store): State<Store> /*TODO */) -> Response {
|
||||||
(StatusCode::CREATED, "Post Answers..").into_response()
|
todo!()
|
||||||
}
|
}
|
||||||
|
29
src/main.rs
29
src/main.rs
@ -1,36 +1,45 @@
|
|||||||
mod api;
|
mod api;
|
||||||
mod err;
|
|
||||||
mod question;
|
mod question;
|
||||||
|
mod store;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
|
extract::{Path, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
routing::{delete, get, post, put},
|
routing::{delete, get, post, put},
|
||||||
Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
//use std::sync::Arc;
|
||||||
|
//use tokio::sync::{self,RwLock};
|
||||||
|
|
||||||
async fn handle() -> Response {
|
async fn handle() -> Response {
|
||||||
(StatusCode::OK, "Visiting the root").into_response()
|
(StatusCode::OK, "Visiting the root").into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handler_404() -> Response {
|
||||||
|
(StatusCode::NOT_FOUND, "404 Not Found").into_response()
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let store = question::Store::new();
|
let store = store::Store::new();
|
||||||
let ip = SocketAddr::new([127, 0, 0, 1].into(), 3000);
|
|
||||||
let listener = tokio::net::TcpListener::bind(ip).await.unwrap();
|
let ip: SocketAddr = SocketAddr::new([127, 0, 0, 1].into(), 3000);
|
||||||
let apis = Router::new()
|
let listener: tokio::net::TcpListener = tokio::net::TcpListener::bind(ip).await.unwrap();
|
||||||
|
let apis: Router = Router::new()
|
||||||
.route("/question/:id", get(api::read_question))
|
.route("/question/:id", get(api::read_question))
|
||||||
.route("/questions", get(api::read_all_questions))
|
.route("/questions", get(api::read_questions))
|
||||||
.route("/question", post(api::create_question))
|
.route("/question", post(api::create_question))
|
||||||
.route("/question/:id", put(api::update_question))
|
.route("/question/:id", put(api::update_question))
|
||||||
.route("/question/:id", delete(api::delete_question))
|
.route("/question/:id", delete(api::delete_question))
|
||||||
.route("/answers", post(api::create_answer))
|
.route("/answers", post(api::create_answer))
|
||||||
//.nest(path, router)
|
//.nest(path, router)
|
||||||
.with_state(store)
|
.with_state(store)
|
||||||
.fallback(err::handler_404);
|
.fallback(handler_404);
|
||||||
let app = Router::new().route("/", get(handle)).merge(apis);
|
let app: Router = Router::new().route("/", get(handle)).merge(apis);
|
||||||
|
|
||||||
axum::serve(listener, app).await.unwrap();
|
axum::serve(listener, app).await.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -1,79 +1,54 @@
|
|||||||
use std::collections::HashMap;
|
use crate::*;
|
||||||
|
|
||||||
use axum::{
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
http::StatusCode,
|
pub struct QuestionDTO {
|
||||||
response::{IntoResponse, Response},
|
pub id: u8,
|
||||||
Json,
|
pub title: String,
|
||||||
};
|
pub content: String,
|
||||||
use serde::{Deserialize, Serialize};
|
pub tags: Option<Vec<String>>,
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) struct Store {
|
|
||||||
questions: HashMap<u8, Question>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Store {
|
impl QuestionDTO {
|
||||||
pub fn new() -> Self {
|
pub fn to_entity(&self) -> (u8, Question) {
|
||||||
Store {
|
(
|
||||||
questions: Self::init(),
|
self.id,
|
||||||
|
Question {
|
||||||
|
title: self.title.clone(),
|
||||||
|
content: self.content.clone(),
|
||||||
|
tags: self.tags.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn init() -> HashMap<u8, Question> {
|
impl IntoResponse for &QuestionDTO {
|
||||||
let file = include_str!("./questions.json");
|
fn into_response(self) -> Response {
|
||||||
serde_json::from_str(file).expect("can't read questions.json")
|
(StatusCode::OK, Json(&self)).into_response()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(mut self, question: Question) -> Result<Question, String> {
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
match self.questions.get(&question.id) {
|
pub struct Question {
|
||||||
Some(_) => Err(format!("Question with id {} already exists", question.id)),
|
|
||||||
None => Ok(self
|
|
||||||
.questions
|
|
||||||
.insert(question.id.clone(), question)
|
|
||||||
.unwrap()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn remove(mut self, id: u8) -> Result<Question, String> {
|
|
||||||
match self.questions.remove(&id) {
|
|
||||||
Some(question) => Ok(question),
|
|
||||||
None => Err(format!("Question with id {} does not exist", id)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn fetch_one(self, id: u8) -> Result<Question, String> {
|
|
||||||
match self.questions.get(&id) {
|
|
||||||
Some(question) => Ok(question.clone()),
|
|
||||||
None => Err(format!("Question with id {} does not exist", id)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn fetch_all(self) -> Vec<Question> {
|
|
||||||
self.questions.values().cloned().collect()
|
|
||||||
}
|
|
||||||
pub fn update(mut self, question: Question) -> Result<Question, String> {
|
|
||||||
match self.questions.get(&question.id) {
|
|
||||||
Some(_) => Ok(self
|
|
||||||
.questions
|
|
||||||
.insert(question.id.clone(), question)
|
|
||||||
.unwrap()),
|
|
||||||
None => Err(format!("Question with id {} does not exists", question.id)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
|
||||||
pub(crate) struct Question {
|
|
||||||
id: u8,
|
|
||||||
title: String,
|
title: String,
|
||||||
content: String,
|
content: String,
|
||||||
tags: Option<Vec<String>>,
|
tags: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Question {
|
impl Question {
|
||||||
fn new(id: u8, title: String, content: String, tags: Option<Vec<String>>) -> Self {
|
pub fn _new(_id: u8, title: String, content: String, tags: Option<Vec<String>>) -> Self {
|
||||||
Question {
|
Question {
|
||||||
id,
|
|
||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
tags,
|
tags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn to_dto(&self, id: u8) -> QuestionDTO {
|
||||||
|
QuestionDTO {
|
||||||
|
id,
|
||||||
|
title: self.title.clone(),
|
||||||
|
content: self.content.clone(),
|
||||||
|
tags: self.tags.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl IntoResponse for &Question {
|
impl IntoResponse for &Question {
|
||||||
fn into_response(self) -> Response {
|
fn into_response(self) -> Response {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
[
|
||||||
{
|
{
|
||||||
"1" : {
|
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"title": "How?",
|
"title": "How?",
|
||||||
"content": "Please help!",
|
"content": "Please help!",
|
||||||
"tags": ["general"]
|
"tags": ["general"]
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
]
|
58
src/store.rs
Normal file
58
src/store.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
use crate::*;
|
||||||
|
|
||||||
|
use self::question::{Question, QuestionDTO};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Store {
|
||||||
|
questions: HashMap<u8, Question>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Store {
|
||||||
|
questions: Self::init(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn init() -> HashMap<u8, Question> {
|
||||||
|
let file = include_str!("./questions.json");
|
||||||
|
let a: Vec<QuestionDTO> = serde_json::from_str(file).expect("can't read questions.json");
|
||||||
|
println!("init");
|
||||||
|
a.into_iter()
|
||||||
|
.map(|question_dto: QuestionDTO| question_dto.to_entity())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(mut self, id: u8, question: Question) -> Result<Question, String> {
|
||||||
|
if self.questions.contains_key(&id) {
|
||||||
|
return Err(format!("Question with id {} already exists", id));
|
||||||
|
}
|
||||||
|
match self.questions.insert(id, question.clone()) {
|
||||||
|
None => Ok(question), //none since key cannot already exist
|
||||||
|
_ => Err("Server Error".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn remove(mut self, id: u8) -> Result<Question, String> {
|
||||||
|
match self.questions.remove(&id) {
|
||||||
|
Some(question) => Ok(question),
|
||||||
|
None => Err(format!("Question with id {} does not exist", id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn fetch(self, id: u8) -> Result<Question, String> {
|
||||||
|
match self.questions.get(&id) {
|
||||||
|
Some(question) => Ok(question.clone()),
|
||||||
|
None => Err(format!("Question with id {} does not exists", id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn fetch_all(self) -> Vec<Question> {
|
||||||
|
self.questions.values().cloned().collect()
|
||||||
|
}
|
||||||
|
pub fn update(mut self, id: u8, question: Question) -> Result<Question, String> {
|
||||||
|
if !self.questions.contains_key(&id) {
|
||||||
|
return Err(format!("Question with id {} does not exists", id));
|
||||||
|
}
|
||||||
|
match self.questions.insert(id, question) {
|
||||||
|
Some(question) => Ok(question),
|
||||||
|
None => Err("Server Error".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user