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`)
|
||||
* Coded API tests with mock data
|
||||
* Specific defined Error types for common errors
|
||||
* Additional comments (rustdoc)
|
||||
* Serve basic web page(s) to utilize all APIs
|
||||
#### 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)
|
||||
|
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 crate::*;
|
||||
|
||||
/**
|
||||
GET /questions (empty body; return JSON)
|
||||
POST /questions (JSON body; return HTTP status code)
|
||||
PUT /questions/:questionId (JSON body, return HTTP status code)
|
||||
DELETE /questions/:questionId (empty body; return HTTP status code)
|
||||
POST /answers (www-url-encoded body; return HTTP status code)
|
||||
*
|
||||
*/
|
||||
/// Fetches a single question using a provided id
|
||||
/// # Parameters
|
||||
/// `id`: Path parmater Id of the question to lookup
|
||||
/// # Returns
|
||||
/// If the question is found, it converts the question to its Data Transfer Object (DTO) format and returns it.
|
||||
/// If the question is not found, it returns a 404 NOT_FOUND response with the error message.
|
||||
pub async fn read_question(
|
||||
State(store): State<Arc<RwLock<Store>>>,
|
||||
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 {
|
||||
(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(
|
||||
State(store): State<Arc<RwLock<Store>>>,
|
||||
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(
|
||||
State(store): State<Arc<RwLock<Store>>>,
|
||||
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(
|
||||
State(store): State<Arc<RwLock<Store>>>,
|
||||
Path(id): Path<u8>,
|
||||
@ -56,7 +81,9 @@ pub async fn delete_question(
|
||||
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 {
|
||||
todo!()
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ async fn handle() -> Response {
|
||||
(StatusCode::OK, "Visiting the root").into_response()
|
||||
}
|
||||
|
||||
//generic handler for any not supported route/method combination
|
||||
async fn handler_404() -> Response {
|
||||
(StatusCode::NOT_FOUND, "404 Not Found").into_response()
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
/// Contains struct definitions regarding questions
|
||||
use crate::*;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
@ -8,6 +9,7 @@ pub struct QuestionDTO {
|
||||
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 {
|
||||
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)]
|
||||
pub struct Question {
|
||||
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 crate::*;
|
||||
|
||||
const DB_PATH: &str = "./questions.json";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Store {
|
||||
file: File,
|
||||
@ -8,20 +13,21 @@ pub struct 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 {
|
||||
let file: File = File::create_new("./questions.json")
|
||||
let file: File = File::create_new(DB_PATH)
|
||||
.or_else(|e| {
|
||||
if e.kind() == ErrorKind::AlreadyExists {
|
||||
File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("./questions.json")
|
||||
File::options().read(true).write(true).open(DB_PATH)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
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> =
|
||||
serde_json::from_str(&json).expect("can't read questions.json");
|
||||
let questions: HashMap<u8, Question> = questions_vec
|
||||
@ -31,6 +37,10 @@ impl Store {
|
||||
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) {
|
||||
let questions: &HashMap<u8, Question> = &self.questions;
|
||||
let questions_vec: Vec<QuestionDTO> = questions
|
||||
@ -58,7 +68,7 @@ impl Store {
|
||||
None => {
|
||||
self.flush();
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user