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`)
* 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)

View File

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

View File

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

View File

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

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