comment code
This commit is contained in:
parent
b643842c44
commit
03257ef1e3
@ -68,7 +68,6 @@ These are a few things still to be added
|
|||||||
* API testing tooling (`swagger`)
|
* API testing tooling (`swagger`)
|
||||||
* Coded API tests with mock data
|
* Coded API tests with mock data
|
||||||
* Specific defined Error types for common errors
|
* Specific defined Error types for common errors
|
||||||
* Additional comments (rustdoc)
|
|
||||||
* Serve basic web page(s) to utilize all APIs
|
* Serve basic web page(s) to utilize all APIs
|
||||||
#### Lesser priority
|
#### Lesser priority
|
||||||
* Optimize flush/file writing: Write out the JSON in a pretty structure and avoid re-writing the whole file in cases when it can be avoid (like adding 1 item)
|
* Optimize flush/file writing: Write out the JSON in a pretty structure and avoid re-writing the whole file in cases when it can be avoid (like adding 1 item)
|
||||||
|
45
src/api.rs
45
src/api.rs
@ -1,14 +1,13 @@
|
|||||||
|
/// All API route handler of the application
|
||||||
use self::{question::QuestionDTO, store::Store};
|
use self::{question::QuestionDTO, store::Store};
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/**
|
/// Fetches a single question using a provided id
|
||||||
GET /questions (empty body; return JSON)
|
/// # Parameters
|
||||||
POST /questions (JSON body; return HTTP status code)
|
/// `id`: Path parmater Id of the question to lookup
|
||||||
PUT /questions/:questionId (JSON body, return HTTP status code)
|
/// # Returns
|
||||||
DELETE /questions/:questionId (empty body; return HTTP status code)
|
/// If the question is found, it converts the question to its Data Transfer Object (DTO) format and returns it.
|
||||||
POST /answers (www-url-encoded body; return HTTP status code)
|
/// If the question is not found, it returns a 404 NOT_FOUND response with the error message.
|
||||||
*
|
|
||||||
*/
|
|
||||||
pub async fn read_question(
|
pub async fn read_question(
|
||||||
State(store): State<Arc<RwLock<Store>>>,
|
State(store): State<Arc<RwLock<Store>>>,
|
||||||
Path(id): Path<u8>,
|
Path(id): Path<u8>,
|
||||||
@ -19,10 +18,22 @@ pub async fn read_question(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches all questions
|
||||||
|
/// # Parameters
|
||||||
|
/// None (TODO - Support pagination from query parameters)
|
||||||
|
/// # Returns
|
||||||
|
/// A list of questions, empty if none exist
|
||||||
pub async fn read_questions(State(store): State<Arc<RwLock<Store>>>) -> Response {
|
pub async fn read_questions(State(store): State<Arc<RwLock<Store>>>) -> Response {
|
||||||
(StatusCode::OK, Json(store.read().await.fetch_all())).into_response()
|
(StatusCode::OK, Json(store.read().await.fetch_all())).into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new question
|
||||||
|
/// # Parameters
|
||||||
|
/// `QuestionDTO` A JSON representation of a Question including its id
|
||||||
|
/// # Returns
|
||||||
|
/// Http Created 201 and the newly created question are return upon success
|
||||||
|
/// Http Confilct 409 and a message are returned if a question with that `id`` already exists
|
||||||
|
/// Http Unprocessable Entity 422 is returned (implicitlly) for a malformed body
|
||||||
pub async fn create_question(
|
pub async fn create_question(
|
||||||
State(store): State<Arc<RwLock<Store>>>,
|
State(store): State<Arc<RwLock<Store>>>,
|
||||||
Json(question_dto): Json<QuestionDTO>,
|
Json(question_dto): Json<QuestionDTO>,
|
||||||
@ -36,6 +47,14 @@ pub async fn create_question(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates an existing question
|
||||||
|
/// At present, questions cannot be 'partially updated' - all fields must be included
|
||||||
|
/// # Parameters
|
||||||
|
/// `QuestionDTO` A JSON representation of a Question including its id
|
||||||
|
/// # Returns
|
||||||
|
/// Http Ok 200 and the updated question are return upon success
|
||||||
|
/// Http Not Found 404 and a message are returned if a question with that `id` does not already exist
|
||||||
|
/// Http Unprocessable Entity 422 is returned (implicitlly) for a malformed body
|
||||||
pub async fn update_question(
|
pub async fn update_question(
|
||||||
State(store): State<Arc<RwLock<Store>>>,
|
State(store): State<Arc<RwLock<Store>>>,
|
||||||
Json(question_dto): Json<QuestionDTO>,
|
Json(question_dto): Json<QuestionDTO>,
|
||||||
@ -47,6 +66,12 @@ pub async fn update_question(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete an existing question
|
||||||
|
/// # Parameters
|
||||||
|
/// `id`: Path parmater Id of the question to delete
|
||||||
|
/// # Returns
|
||||||
|
/// Http Ok 200 and the deleted question are return upon success
|
||||||
|
/// Http Not Found 404 and a message are returned if a question with that `id` does not exist
|
||||||
pub async fn delete_question(
|
pub async fn delete_question(
|
||||||
State(store): State<Arc<RwLock<Store>>>,
|
State(store): State<Arc<RwLock<Store>>>,
|
||||||
Path(id): Path<u8>,
|
Path(id): Path<u8>,
|
||||||
@ -56,7 +81,9 @@ pub async fn delete_question(
|
|||||||
Err(e) => (StatusCode::NOT_FOUND, e).into_response(),
|
Err(e) => (StatusCode::NOT_FOUND, e).into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Create an Answer
|
||||||
|
/// # Parameters
|
||||||
|
/// # Returns
|
||||||
pub async fn create_answer(State(_store): State<Arc<RwLock<Store>>> /*TODO */) -> Response {
|
pub async fn create_answer(State(_store): State<Arc<RwLock<Store>>> /*TODO */) -> Response {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ async fn handle() -> Response {
|
|||||||
(StatusCode::OK, "Visiting the root").into_response()
|
(StatusCode::OK, "Visiting the root").into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//generic handler for any not supported route/method combination
|
||||||
async fn handler_404() -> Response {
|
async fn handler_404() -> Response {
|
||||||
(StatusCode::NOT_FOUND, "404 Not Found").into_response()
|
(StatusCode::NOT_FOUND, "404 Not Found").into_response()
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/// Contains struct definitions regarding questions
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
@ -8,6 +9,7 @@ pub struct QuestionDTO {
|
|||||||
pub tags: Option<Vec<String>>,
|
pub tags: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Question Data Transfer Object, a representation of the expected serialized JSON formated of questions regarding requests, responses, and our question json file
|
||||||
impl QuestionDTO {
|
impl QuestionDTO {
|
||||||
pub fn to_entity(&self) -> (u8, Question) {
|
pub fn to_entity(&self) -> (u8, Question) {
|
||||||
(
|
(
|
||||||
@ -26,6 +28,7 @@ impl IntoResponse for &QuestionDTO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Question 'entity' used for in-memory interactions of questions by the store
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
pub struct Question {
|
pub struct Question {
|
||||||
title: String,
|
title: String,
|
||||||
|
22
src/store.rs
22
src/store.rs
@ -1,6 +1,11 @@
|
|||||||
|
/// Store is responsible for manageing the in-memory hashmap of questions by providing initialization read/write functions,
|
||||||
|
/// and file I/O operations to persist these questions
|
||||||
|
/// TODO - Results returning errors should use specified types, not strings
|
||||||
use self::question::{Question, QuestionDTO};
|
use self::question::{Question, QuestionDTO};
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
const DB_PATH: &str = "./questions.json";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Store {
|
pub struct Store {
|
||||||
file: File,
|
file: File,
|
||||||
@ -8,20 +13,21 @@ pub struct Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Store {
|
impl Store {
|
||||||
|
// Upon initialization, we need to read a questions.json if it exists and populate our questions hashmap from it.
|
||||||
|
// Otherwise we create questions.json.
|
||||||
|
// JSON formatting and I/O errors possible here are semi-handled with a message, but ultimetly we will panic in those cases
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let file: File = File::create_new("./questions.json")
|
let file: File = File::create_new(DB_PATH)
|
||||||
.or_else(|e| {
|
.or_else(|e| {
|
||||||
if e.kind() == ErrorKind::AlreadyExists {
|
if e.kind() == ErrorKind::AlreadyExists {
|
||||||
File::options()
|
File::options().read(true).write(true).open(DB_PATH)
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.open("./questions.json")
|
|
||||||
} else {
|
} else {
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let json = std::io::read_to_string(&file).expect("could not get json from file");
|
let json = std::io::read_to_string(&file).expect("could not get json from file");
|
||||||
|
// perhaps there is a more efficient/clever way aside from reading the json to a vector and mapping the vector to a hashmap.
|
||||||
let questions_vec: Vec<QuestionDTO> =
|
let questions_vec: Vec<QuestionDTO> =
|
||||||
serde_json::from_str(&json).expect("can't read questions.json");
|
serde_json::from_str(&json).expect("can't read questions.json");
|
||||||
let questions: HashMap<u8, Question> = questions_vec
|
let questions: HashMap<u8, Question> = questions_vec
|
||||||
@ -31,6 +37,10 @@ impl Store {
|
|||||||
Store { questions, file }
|
Store { questions, file }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Take the content of the questions hashmap, convert it to a vector of question DTOs and overwrite the file with these contents
|
||||||
|
// Not the most efficient approach if we are just adding or deleting a single question, but it does the job at our current scale
|
||||||
|
// 'flush' is also probably a misnomer
|
||||||
|
// TODO - pretty print before writing
|
||||||
fn flush(&mut self) {
|
fn flush(&mut self) {
|
||||||
let questions: &HashMap<u8, Question> = &self.questions;
|
let questions: &HashMap<u8, Question> = &self.questions;
|
||||||
let questions_vec: Vec<QuestionDTO> = questions
|
let questions_vec: Vec<QuestionDTO> = questions
|
||||||
@ -58,7 +68,7 @@ impl Store {
|
|||||||
None => {
|
None => {
|
||||||
self.flush();
|
self.flush();
|
||||||
Ok(question)
|
Ok(question)
|
||||||
} //none since key cannot already exist
|
} //Looks backwards, but insert must return none since key cannot already exist
|
||||||
_ => Err("Server Error".to_string()),
|
_ => Err("Server Error".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user