comment code

This commit is contained in:
David Westgate 2024-04-27 01:46:30 -07:00
parent b643842c44
commit 03257ef1e3
5 changed files with 56 additions and 16 deletions

View File

@ -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)

View File

@ -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!()
} }

View File

@ -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()
} }

View File

@ -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,

View File

@ -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()),
} }
} }