From db75ec502d4c0cc1b368c688b09ab2b58ea01818 Mon Sep 17 00:00:00 2001 From: Jeremy Yin Date: Mon, 3 Jul 2023 22:43:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/fetch_all_pokemons.rs | 28 ++++++++++++++ src/api/mod.rs | 4 ++ src/domain/entity.rs | 2 +- src/domain/fetch_all_pokemons.rs | 65 ++++++++++++++++++++++++++++++++ src/domain/mod.rs | 3 +- src/repo/pokemon.rs | 18 +++++++++ 6 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 src/api/fetch_all_pokemons.rs create mode 100644 src/domain/fetch_all_pokemons.rs diff --git a/src/api/fetch_all_pokemons.rs b/src/api/fetch_all_pokemons.rs new file mode 100644 index 0000000..283b7cf --- /dev/null +++ b/src/api/fetch_all_pokemons.rs @@ -0,0 +1,28 @@ +use std::sync::Arc; +use serde::Serialize; +use crate::api::Status; +use crate::domain::fetch_all_pokemons; +use crate::repo::pokemon::Repository; + +#[derive(Serialize)] +struct Response { + number: u16, + name: String, + types: Vec, +} + + +pub fn serve(repo: Arc) -> rouille::Response { + match fetch_all_pokemons::execute(repo) { + Ok(res) => rouille::Response::json( + &res.into_iter().map(|p| Response { + number: p.number, + name: p.name, + types: p.types, + }).collect::>(), + ), + Err(fetch_all_pokemons::Error::Unknown) => { + rouille::Response::from(Status::InternalServerError) + } + } +} \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs index b8a2b35..160e02a 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -4,6 +4,7 @@ use crate::repo::pokemon::Repository; mod health; mod create_pokemon; +mod fetch_all_pokemons; pub fn serve(url: &str, repo: Arc) { @@ -14,6 +15,9 @@ pub fn serve(url: &str, repo: Arc) { }, (POST)(/) => { create_pokemon::serve(repo.clone(), req) + }, + (GET)(/) => { + fetch_all_pokemons::serve(repo.clone()) }, _ => { rouille::Response::from(rouille::Response::empty_404()) diff --git a/src/domain/entity.rs b/src/domain/entity.rs index 5327b8c..afd30d9 100644 --- a/src/domain/entity.rs +++ b/src/domain/entity.rs @@ -15,7 +15,7 @@ impl Pokemon { } } -#[derive(Debug,PartialOrd, PartialEq, Copy, Clone)] +#[derive(Debug,PartialOrd, Ord, PartialEq, Eq, Copy, Clone)] pub struct PokemonNumber(u16); impl TryFrom for PokemonNumber { diff --git a/src/domain/fetch_all_pokemons.rs b/src/domain/fetch_all_pokemons.rs new file mode 100644 index 0000000..018c705 --- /dev/null +++ b/src/domain/fetch_all_pokemons.rs @@ -0,0 +1,65 @@ +use std::sync::Arc; +use crate::domain::create_pokemon::Response; +use crate::repo::pokemon::{FetchAllError, Repository}; + +pub enum Error { + Unknown, +} + + +pub fn execute(repo: Arc) -> Result, Error>{ + match repo.fetch_all() { + Ok(pokemons) => Ok(pokemons.into_iter().map(|p| Response { + number: u16::from(p.number), + name: String::from(p.name), + types: Vec::::from(p.types), + }).collect()), + Err(FetchAllError::Unknown) => Err(Error::Unknown), + } +} + + +#[cfg(test)] +mod tests { + use std::sync::Arc; + use crate::domain::entity::{PokemonName, PokemonNumber, PokemonTypes}; + use crate::domain::fetch_all_pokemons::{Error}; + use crate::domain::fetch_all_pokemons::execute; + use crate::repo::pokemon::{InMemoryRepository, Repository}; + + #[test] + fn it_should_return_an_unkown_error_when_an_unexpected_error_happens() { + let repo = Arc::new(InMemoryRepository::new().with_error()); + let res = execute(repo); + match res { + Err(Error::Unknown) => {}, + _ => unreachable!(), + } + } + #[test] + fn it_should_return_all_pokemons_ordered_by_increasing_number_otherwise() { + let repo = Arc::new(InMemoryRepository::new()); + repo.insert( + PokemonNumber::pikachu(), + PokemonName::pikachu(), + PokemonTypes::pikachu(), + ).ok(); + repo.insert( + PokemonNumber::charmander(), + PokemonName::charmander(), + PokemonTypes::charmander(), + ).ok(); + let res = execute(repo); + match res { + Ok(res) => { + assert_eq!(res[0].number, u16::from(PokemonNumber::charmander())); + assert_eq!(res[0].name, String::from(PokemonName::charmander())); + assert_eq!(res[0].types, Vec::::from(PokemonTypes::charmander())); + assert_eq!(res[1].number, u16::from(PokemonNumber::pikachu())); + assert_eq!(res[1].name, String::from(PokemonName::pikachu())); + assert_eq!(res[1].types, Vec::::from(PokemonTypes::pikachu())); + }, + Err(Error::Unknown) => {} + } + } +} \ No newline at end of file diff --git a/src/domain/mod.rs b/src/domain/mod.rs index fd01c4e..0a664ee 100644 --- a/src/domain/mod.rs +++ b/src/domain/mod.rs @@ -1,2 +1,3 @@ pub mod create_pokemon; -pub mod entity; \ No newline at end of file +pub mod entity; +pub mod fetch_all_pokemons; \ No newline at end of file diff --git a/src/repo/pokemon.rs b/src/repo/pokemon.rs index d206a91..0edd6b7 100644 --- a/src/repo/pokemon.rs +++ b/src/repo/pokemon.rs @@ -3,7 +3,9 @@ use crate::domain::entity::{Pokemon, PokemonName, PokemonNumber, PokemonTypes}; pub trait Repository: Send + Sync { fn insert(&self, number: PokemonNumber, name: PokemonName, types: PokemonTypes) -> Result; + fn fetch_all(&self) -> Result, FetchAllError>; } + #[derive(Debug)] pub struct InMemoryRepository { error: bool, @@ -31,6 +33,10 @@ pub enum InsertError { Error, } +pub enum FetchAllError { + Unknown, +} + impl Repository for InMemoryRepository { fn insert(&self, number: PokemonNumber, name: PokemonName, types: PokemonTypes) -> Result { if self.error { @@ -51,4 +57,16 @@ impl Repository for InMemoryRepository { println!("{number:?} {:?}", self.pokemons); Ok(pokemon) } + fn fetch_all(&self) -> Result, FetchAllError> { + if self.error { + return Err(FetchAllError::Unknown); + } + let lock = match self.pokemons.lock() { + Ok(lock) => lock, + Err(_) => return Err(FetchAllError::Unknown), + }; + let mut pokemons = lock.to_vec(); + pokemons.sort_by(|a, b| a.number.cmp(&b.number)); + Ok(pokemons) + } }