├── .gitignore ├── src ├── repositories │ ├── mod.rs │ └── pokemon.rs ├── domain │ ├── mod.rs │ ├── fetch_all_pokemons.rs │ ├── delete_pokemon.rs │ ├── fetch_pokemon.rs │ ├── entities.rs │ └── create_pokemon.rs ├── api │ ├── health.rs │ ├── delete_pokemon.rs │ ├── fetch_all_pokemons.rs │ ├── fetch_pokemon.rs │ ├── create_pokemon.rs │ └── mod.rs ├── cli │ ├── fetch_all_pokemons.rs │ ├── delete_pokemon.rs │ ├── fetch_pokemon.rs │ ├── create_pokemon.rs │ └── mod.rs └── main.rs ├── README.md ├── Cargo.toml └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.sqlite -------------------------------------------------------------------------------- /src/repositories/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod pokemon; 2 | -------------------------------------------------------------------------------- /src/domain/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create_pokemon; 2 | pub mod delete_pokemon; 3 | pub mod entities; 4 | pub mod fetch_all_pokemons; 5 | pub mod fetch_pokemon; 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pokedex 2 | 3 | Hexagonal architecture example in Rust 4 | 5 | This code comes with [articles](https://alexis-lozano.com/hexagonal-architecture-in-rust-1/). -------------------------------------------------------------------------------- /src/api/health.rs: -------------------------------------------------------------------------------- 1 | use rouille; 2 | use serde::Serialize; 3 | 4 | #[derive(Serialize)] 5 | struct Response { 6 | message: String, 7 | } 8 | 9 | pub fn serve() -> rouille::Response { 10 | rouille::Response::json(&Response { 11 | message: String::from("Gotta catch them all!"), 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pokedex" 3 | version = "0.1.0" 4 | authors = ["Alexis Lozano "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rouille = "3.2.1" 11 | serde = { version = "1.0.129", features = ["derive"] } 12 | serde_json = "1.0.66" 13 | clap = "2.33.3" 14 | dialoguer = "0.8.0" 15 | ureq = { version = "2.2.0", features = ["json"] } 16 | rusqlite = "0.26.0" 17 | -------------------------------------------------------------------------------- /src/api/delete_pokemon.rs: -------------------------------------------------------------------------------- 1 | use crate::api::Status; 2 | use crate::domain::delete_pokemon; 3 | use crate::repositories::pokemon::Repository; 4 | use rouille; 5 | use std::sync::Arc; 6 | 7 | pub fn serve(repo: Arc, number: u16) -> rouille::Response { 8 | let req = delete_pokemon::Request { number }; 9 | match delete_pokemon::execute(repo, req) { 10 | Ok(()) => rouille::Response::from(Status::Ok), 11 | Err(delete_pokemon::Error::BadRequest) => rouille::Response::from(Status::BadRequest), 12 | Err(delete_pokemon::Error::NotFound) => rouille::Response::from(Status::NotFound), 13 | Err(delete_pokemon::Error::Unknown) => rouille::Response::from(Status::InternalServerError), 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/cli/fetch_all_pokemons.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::fetch_all_pokemons; 2 | use crate::repositories::pokemon::Repository; 3 | use std::sync::Arc; 4 | 5 | #[derive(Debug)] 6 | struct Response { 7 | number: u16, 8 | name: String, 9 | types: Vec, 10 | } 11 | 12 | pub fn run(repo: Arc) { 13 | match fetch_all_pokemons::execute(repo) { 14 | Ok(res) => res.into_iter().for_each(|p| { 15 | println!( 16 | "{:?}", 17 | Response { 18 | number: p.number, 19 | name: p.name, 20 | types: p.types, 21 | } 22 | ); 23 | }), 24 | Err(fetch_all_pokemons::Error::Unknown) => println!("An unknown error occurred"), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/cli/delete_pokemon.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::prompt_number; 2 | use crate::domain::delete_pokemon; 3 | use crate::repositories::pokemon::Repository; 4 | use std::sync::Arc; 5 | 6 | pub fn run(repo: Arc) { 7 | let number = prompt_number(); 8 | 9 | let req = match number { 10 | Ok(number) => delete_pokemon::Request { number }, 11 | _ => { 12 | println!("An error occurred during the prompt"); 13 | return; 14 | } 15 | }; 16 | match delete_pokemon::execute(repo, req) { 17 | Ok(()) => println!("The Pokemon has been deleted"), 18 | Err(delete_pokemon::Error::BadRequest) => println!("The request is invalid"), 19 | Err(delete_pokemon::Error::NotFound) => println!("The Pokemon does not exist"), 20 | Err(delete_pokemon::Error::Unknown) => println!("An unknown error occurred"), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/api/fetch_all_pokemons.rs: -------------------------------------------------------------------------------- 1 | use crate::api::Status; 2 | use crate::domain::fetch_all_pokemons; 3 | use crate::repositories::pokemon::Repository; 4 | use rouille; 5 | use serde::Serialize; 6 | use std::sync::Arc; 7 | 8 | #[derive(Serialize)] 9 | struct Response { 10 | number: u16, 11 | name: String, 12 | types: Vec, 13 | } 14 | 15 | pub fn serve(repo: Arc) -> rouille::Response { 16 | match fetch_all_pokemons::execute(repo) { 17 | Ok(res) => rouille::Response::json( 18 | &res.into_iter() 19 | .map(|p| Response { 20 | number: p.number, 21 | name: p.name, 22 | types: p.types, 23 | }) 24 | .collect::>(), 25 | ), 26 | Err(fetch_all_pokemons::Error::Unknown) => { 27 | rouille::Response::from(Status::InternalServerError) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/api/fetch_pokemon.rs: -------------------------------------------------------------------------------- 1 | use crate::api::Status; 2 | use crate::domain::fetch_pokemon; 3 | use crate::repositories::pokemon::Repository; 4 | use rouille; 5 | use serde::Serialize; 6 | use std::sync::Arc; 7 | 8 | #[derive(Serialize)] 9 | struct Response { 10 | number: u16, 11 | name: String, 12 | types: Vec, 13 | } 14 | 15 | pub fn serve(repo: Arc, number: u16) -> rouille::Response { 16 | let req = fetch_pokemon::Request { number }; 17 | match fetch_pokemon::execute(repo, req) { 18 | Ok(fetch_pokemon::Response { 19 | number, 20 | name, 21 | types, 22 | }) => rouille::Response::json(&Response { 23 | number, 24 | name, 25 | types, 26 | }), 27 | Err(fetch_pokemon::Error::BadRequest) => rouille::Response::from(Status::BadRequest), 28 | Err(fetch_pokemon::Error::NotFound) => rouille::Response::from(Status::NotFound), 29 | Err(fetch_pokemon::Error::Unknown) => rouille::Response::from(Status::InternalServerError), 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/cli/fetch_pokemon.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::prompt_number; 2 | use crate::domain::fetch_pokemon; 3 | use crate::repositories::pokemon::Repository; 4 | use std::sync::Arc; 5 | 6 | #[derive(Debug)] 7 | struct Response { 8 | number: u16, 9 | name: String, 10 | types: Vec, 11 | } 12 | 13 | pub fn run(repo: Arc) { 14 | let number = prompt_number(); 15 | 16 | let req = match number { 17 | Ok(number) => fetch_pokemon::Request { number }, 18 | _ => { 19 | println!("An error occurred during the prompt"); 20 | return; 21 | } 22 | }; 23 | match fetch_pokemon::execute(repo, req) { 24 | Ok(res) => println!( 25 | "{:?}", 26 | Response { 27 | number: res.number, 28 | name: res.name, 29 | types: res.types, 30 | } 31 | ), 32 | Err(fetch_pokemon::Error::BadRequest) => println!("The request is invalid"), 33 | Err(fetch_pokemon::Error::NotFound) => println!("The Pokemon does not exist"), 34 | Err(fetch_pokemon::Error::Unknown) => println!("An unknown error occurred"), 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/cli/create_pokemon.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::{prompt_name, prompt_number, prompt_types}; 2 | use crate::domain::create_pokemon; 3 | use crate::repositories::pokemon::Repository; 4 | use std::sync::Arc; 5 | 6 | #[derive(Debug)] 7 | struct Response { 8 | number: u16, 9 | name: String, 10 | types: Vec, 11 | } 12 | 13 | pub fn run(repo: Arc) { 14 | let number = prompt_number(); 15 | let name = prompt_name(); 16 | let types = prompt_types(); 17 | 18 | let req = match (number, name, types) { 19 | (Ok(number), Ok(name), Ok(types)) => create_pokemon::Request { 20 | number, 21 | name, 22 | types, 23 | }, 24 | _ => { 25 | println!("An error occurred during the prompt"); 26 | return; 27 | } 28 | }; 29 | match create_pokemon::execute(repo, req) { 30 | Ok(res) => println!( 31 | "{:?}", 32 | Response { 33 | number: res.number, 34 | name: res.name, 35 | types: res.types, 36 | } 37 | ), 38 | Err(create_pokemon::Error::BadRequest) => println!("The request is invalid"), 39 | Err(create_pokemon::Error::Conflict) => println!("The Pokemon already exists"), 40 | Err(create_pokemon::Error::Unknown) => println!("An unknown error occurred"), 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/api/create_pokemon.rs: -------------------------------------------------------------------------------- 1 | use crate::api::Status; 2 | use crate::domain::create_pokemon; 3 | use crate::repositories::pokemon::Repository; 4 | use rouille; 5 | use serde::{Deserialize, Serialize}; 6 | use std::sync::Arc; 7 | 8 | #[derive(Deserialize)] 9 | struct Request { 10 | number: u16, 11 | name: String, 12 | types: Vec, 13 | } 14 | 15 | #[derive(Serialize)] 16 | struct Response { 17 | number: u16, 18 | name: String, 19 | types: Vec, 20 | } 21 | 22 | pub fn serve(repo: Arc, req: &rouille::Request) -> rouille::Response { 23 | let req = match rouille::input::json_input::(req) { 24 | Ok(req) => create_pokemon::Request { 25 | number: req.number, 26 | name: req.name, 27 | types: req.types, 28 | }, 29 | _ => return rouille::Response::from(Status::BadRequest), 30 | }; 31 | match create_pokemon::execute(repo, req) { 32 | Ok(create_pokemon::Response { 33 | number, 34 | name, 35 | types, 36 | }) => rouille::Response::json(&Response { 37 | number, 38 | name, 39 | types, 40 | }), 41 | Err(create_pokemon::Error::BadRequest) => rouille::Response::from(Status::BadRequest), 42 | Err(create_pokemon::Error::Conflict) => rouille::Response::from(Status::Conflict), 43 | Err(create_pokemon::Error::Unknown) => rouille::Response::from(Status::InternalServerError), 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | mod create_pokemon; 2 | mod delete_pokemon; 3 | mod fetch_all_pokemons; 4 | mod fetch_pokemon; 5 | mod health; 6 | 7 | use crate::repositories::pokemon::Repository; 8 | use std::sync::Arc; 9 | 10 | pub fn serve(url: &str, repo: Arc) { 11 | rouille::start_server(url, move |req| { 12 | router!(req, 13 | (GET) (/) => { 14 | fetch_all_pokemons::serve(repo.clone()) 15 | }, 16 | (GET) (/{number: u16}) => { 17 | fetch_pokemon::serve(repo.clone(), number) 18 | }, 19 | (GET) (/health) => { 20 | health::serve() 21 | }, 22 | (POST) (/) => { 23 | create_pokemon::serve(repo.clone(), req) 24 | }, 25 | (DELETE) (/{number: u16}) => { 26 | delete_pokemon::serve(repo.clone(), number) 27 | }, 28 | _ => { 29 | rouille::Response::from(Status::NotFound) 30 | } 31 | ) 32 | }); 33 | } 34 | 35 | enum Status { 36 | Ok, 37 | BadRequest, 38 | NotFound, 39 | Conflict, 40 | InternalServerError, 41 | } 42 | 43 | impl From for rouille::Response { 44 | fn from(status: Status) -> Self { 45 | let status_code = match status { 46 | Status::Ok => 200, 47 | Status::BadRequest => 400, 48 | Status::NotFound => 404, 49 | Status::Conflict => 409, 50 | Status::InternalServerError => 500, 51 | }; 52 | Self { 53 | status_code, 54 | headers: vec![], 55 | data: rouille::ResponseBody::empty(), 56 | upgrade: None, 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | mod cli; 3 | mod domain; 4 | mod repositories; 5 | 6 | #[macro_use] 7 | extern crate rouille; 8 | #[macro_use] 9 | extern crate clap; 10 | extern crate serde; 11 | 12 | use clap::{App, Arg, Values}; 13 | use repositories::pokemon::{AirtableRepository, InMemoryRepository, Repository, SqliteRepository}; 14 | use std::sync::Arc; 15 | 16 | fn main() { 17 | let matches = App::new(crate_name!()) 18 | .version(crate_version!()) 19 | .author(crate_authors!()) 20 | .arg(Arg::with_name("cli").long("cli").help("Runs in CLI mode")) 21 | .arg(Arg::with_name("sqlite").long("sqlite").value_name("PATH")) 22 | .arg( 23 | Arg::with_name("airtable") 24 | .long("airtable") 25 | .value_names(&["API_KEY", "WORKSPACE_ID"]), 26 | ) 27 | .get_matches(); 28 | 29 | let repo = build_repo(matches.value_of("sqlite"), matches.values_of("airtable")); 30 | 31 | match matches.occurrences_of("cli") { 32 | 0 => api::serve("localhost:8000", repo), 33 | _ => cli::run(repo), 34 | } 35 | } 36 | 37 | fn build_repo(sqlite_value: Option<&str>, airtable_values: Option) -> Arc { 38 | if let Some(values) = airtable_values { 39 | if let [api_key, workspace_id] = values.collect::>()[..] { 40 | match AirtableRepository::try_new(api_key, workspace_id) { 41 | Ok(repo) => return Arc::new(repo), 42 | _ => panic!("Error while creating airtable repo"), 43 | } 44 | } 45 | } 46 | 47 | if let Some(path) = sqlite_value { 48 | match SqliteRepository::try_new(path) { 49 | Ok(repo) => return Arc::new(repo), 50 | _ => panic!("Error while creating sqlite repo"), 51 | } 52 | } 53 | 54 | Arc::new(InMemoryRepository::new()) 55 | } 56 | -------------------------------------------------------------------------------- /src/cli/mod.rs: -------------------------------------------------------------------------------- 1 | mod create_pokemon; 2 | mod delete_pokemon; 3 | mod fetch_all_pokemons; 4 | mod fetch_pokemon; 5 | 6 | use crate::repositories::pokemon::Repository; 7 | use dialoguer::{theme::ColorfulTheme, Input, MultiSelect, Select}; 8 | use std::sync::Arc; 9 | 10 | pub fn run(repo: Arc) { 11 | loop { 12 | let choices = [ 13 | "Fetch all Pokemons", 14 | "Fetch a Pokemon", 15 | "Create a Pokemon", 16 | "Delete a Pokemon", 17 | "Exit", 18 | ]; 19 | let index = match Select::with_theme(&ColorfulTheme::default()) 20 | .with_prompt("Make your choice") 21 | .items(&choices) 22 | .default(0) 23 | .interact() 24 | { 25 | Ok(index) => index, 26 | _ => continue, 27 | }; 28 | 29 | match index { 30 | 0 => fetch_all_pokemons::run(repo.clone()), 31 | 1 => fetch_pokemon::run(repo.clone()), 32 | 2 => create_pokemon::run(repo.clone()), 33 | 3 => delete_pokemon::run(repo.clone()), 34 | 4 => break, 35 | _ => continue, 36 | }; 37 | } 38 | } 39 | 40 | pub fn prompt_number() -> Result { 41 | match Input::new().with_prompt("Pokemon number").interact_text() { 42 | Ok(number) => Ok(number), 43 | _ => Err(()), 44 | } 45 | } 46 | 47 | pub fn prompt_name() -> Result { 48 | match Input::new().with_prompt("Pokemon name").interact_text() { 49 | Ok(name) => Ok(name), 50 | _ => Err(()), 51 | } 52 | } 53 | 54 | pub fn prompt_types() -> Result, ()> { 55 | let types = ["Electric", "Fire"]; 56 | match MultiSelect::new() 57 | .with_prompt("Pokemon types") 58 | .items(&types) 59 | .interact() 60 | { 61 | Ok(indexes) => Ok(indexes 62 | .into_iter() 63 | .map(|index| String::from(types[index])) 64 | .collect::>()), 65 | _ => Err(()), 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/domain/fetch_all_pokemons.rs: -------------------------------------------------------------------------------- 1 | use crate::repositories::pokemon::{FetchAllError, Repository}; 2 | use std::sync::Arc; 3 | 4 | pub struct Response { 5 | pub number: u16, 6 | pub name: String, 7 | pub types: Vec, 8 | } 9 | 10 | pub enum Error { 11 | Unknown, 12 | } 13 | 14 | pub fn execute(repo: Arc) -> Result, Error> { 15 | match repo.fetch_all() { 16 | Ok(pokemons) => Ok(pokemons 17 | .into_iter() 18 | .map(|p| Response { 19 | number: u16::from(p.number), 20 | name: String::from(p.name), 21 | types: Vec::::from(p.types), 22 | }) 23 | .collect::>()), 24 | Err(FetchAllError::Unknown) => Err(Error::Unknown), 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | use crate::domain::entities::{PokemonName, PokemonNumber, PokemonTypes}; 32 | use crate::repositories::pokemon::InMemoryRepository; 33 | 34 | #[test] 35 | fn it_should_return_an_unknown_error_when_an_unexpected_error_happens() { 36 | let repo = Arc::new(InMemoryRepository::new().with_error()); 37 | 38 | let res = execute(repo); 39 | 40 | match res { 41 | Err(Error::Unknown) => {} 42 | _ => unreachable!(), 43 | }; 44 | } 45 | 46 | #[test] 47 | fn it_should_return_all_the_pokemons_ordered_by_increasing_number_otherwise() { 48 | let repo = Arc::new(InMemoryRepository::new()); 49 | repo.insert( 50 | PokemonNumber::pikachu(), 51 | PokemonName::pikachu(), 52 | PokemonTypes::pikachu(), 53 | ) 54 | .ok(); 55 | repo.insert( 56 | PokemonNumber::charmander(), 57 | PokemonName::charmander(), 58 | PokemonTypes::charmander(), 59 | ) 60 | .ok(); 61 | 62 | let res = execute(repo); 63 | 64 | match res { 65 | Ok(res) => { 66 | assert_eq!(res[0].number, u16::from(PokemonNumber::charmander())); 67 | assert_eq!(res[0].name, String::from(PokemonName::charmander())); 68 | assert_eq!( 69 | res[0].types, 70 | Vec::::from(PokemonTypes::charmander()) 71 | ); 72 | assert_eq!(res[1].number, u16::from(PokemonNumber::pikachu())); 73 | assert_eq!(res[1].name, String::from(PokemonName::pikachu())); 74 | assert_eq!(res[1].types, Vec::::from(PokemonTypes::pikachu())); 75 | } 76 | _ => unreachable!(), 77 | }; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/domain/delete_pokemon.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::entities::PokemonNumber; 2 | use crate::repositories::pokemon::{DeleteError, Repository}; 3 | use std::sync::Arc; 4 | 5 | pub struct Request { 6 | pub number: u16, 7 | } 8 | 9 | pub enum Error { 10 | BadRequest, 11 | NotFound, 12 | Unknown, 13 | } 14 | 15 | pub fn execute(repo: Arc, req: Request) -> Result<(), Error> { 16 | match PokemonNumber::try_from(req.number) { 17 | Ok(number) => match repo.delete(number) { 18 | Ok(()) => Ok(()), 19 | Err(DeleteError::NotFound) => Err(Error::NotFound), 20 | Err(DeleteError::Unknown) => Err(Error::Unknown), 21 | }, 22 | _ => Err(Error::BadRequest), 23 | } 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | use crate::domain::entities::{PokemonName, PokemonTypes}; 30 | use crate::repositories::pokemon::InMemoryRepository; 31 | 32 | #[test] 33 | fn it_should_return_an_unknown_error_when_an_unexpected_error_happens() { 34 | let repo = Arc::new(InMemoryRepository::new().with_error()); 35 | let req = Request::new(PokemonNumber::pikachu()); 36 | 37 | let res = execute(repo, req); 38 | 39 | match res { 40 | Err(Error::Unknown) => {} 41 | _ => unreachable!(), 42 | }; 43 | } 44 | 45 | #[test] 46 | fn it_should_return_a_bad_request_error_when_request_is_invalid() { 47 | let repo = Arc::new(InMemoryRepository::new()); 48 | let req = Request::new(PokemonNumber::bad()); 49 | 50 | let res = execute(repo, req); 51 | 52 | match res { 53 | Err(Error::BadRequest) => {} 54 | _ => unreachable!(), 55 | }; 56 | } 57 | 58 | #[test] 59 | fn it_should_return_a_not_found_error_when_the_repo_does_not_contain_the_pokemon() { 60 | let repo = Arc::new(InMemoryRepository::new()); 61 | let req = Request::new(PokemonNumber::pikachu()); 62 | 63 | let res = execute(repo, req); 64 | 65 | match res { 66 | Err(Error::NotFound) => {} 67 | _ => unreachable!(), 68 | }; 69 | } 70 | 71 | #[test] 72 | fn it_should_return_ok_otherwise() { 73 | let repo = Arc::new(InMemoryRepository::new()); 74 | repo.insert( 75 | PokemonNumber::pikachu(), 76 | PokemonName::pikachu(), 77 | PokemonTypes::pikachu(), 78 | ) 79 | .ok(); 80 | let req = Request::new(PokemonNumber::pikachu()); 81 | 82 | let res = execute(repo, req); 83 | 84 | match res { 85 | Ok(()) => {} 86 | _ => unreachable!(), 87 | }; 88 | } 89 | 90 | impl Request { 91 | fn new(number: PokemonNumber) -> Self { 92 | Self { 93 | number: u16::from(number), 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/domain/fetch_pokemon.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::entities::{Pokemon, PokemonNumber}; 2 | use crate::repositories::pokemon::{FetchOneError, Repository}; 3 | use std::sync::Arc; 4 | 5 | pub struct Request { 6 | pub number: u16, 7 | } 8 | 9 | pub struct Response { 10 | pub number: u16, 11 | pub name: String, 12 | pub types: Vec, 13 | } 14 | 15 | pub enum Error { 16 | BadRequest, 17 | NotFound, 18 | Unknown, 19 | } 20 | 21 | pub fn execute(repo: Arc, req: Request) -> Result { 22 | match PokemonNumber::try_from(req.number) { 23 | Ok(number) => match repo.fetch_one(number) { 24 | Ok(Pokemon { 25 | number, 26 | name, 27 | types, 28 | }) => Ok(Response { 29 | number: u16::from(number), 30 | name: String::from(name), 31 | types: Vec::::from(types), 32 | }), 33 | Err(FetchOneError::NotFound) => Err(Error::NotFound), 34 | Err(FetchOneError::Unknown) => Err(Error::Unknown), 35 | }, 36 | _ => Err(Error::BadRequest), 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::*; 43 | use crate::domain::entities::{PokemonName, PokemonTypes}; 44 | use crate::repositories::pokemon::InMemoryRepository; 45 | 46 | #[test] 47 | fn it_should_return_an_unknown_error_when_an_unexpected_error_happens() { 48 | let repo = Arc::new(InMemoryRepository::new().with_error()); 49 | let req = Request::new(PokemonNumber::pikachu()); 50 | 51 | let res = execute(repo, req); 52 | 53 | match res { 54 | Err(Error::Unknown) => {} 55 | _ => unreachable!(), 56 | }; 57 | } 58 | 59 | #[test] 60 | fn it_should_return_a_bad_request_error_when_request_is_invalid() { 61 | let repo = Arc::new(InMemoryRepository::new()); 62 | let req = Request::new(PokemonNumber::bad()); 63 | 64 | let res = execute(repo, req); 65 | 66 | match res { 67 | Err(Error::BadRequest) => {} 68 | _ => unreachable!(), 69 | }; 70 | } 71 | 72 | #[test] 73 | fn it_should_return_a_not_found_error_when_the_repo_does_not_contain_the_pokemon() { 74 | let repo = Arc::new(InMemoryRepository::new()); 75 | let req = Request::new(PokemonNumber::pikachu()); 76 | 77 | let res = execute(repo, req); 78 | 79 | match res { 80 | Err(Error::NotFound) => {} 81 | _ => unreachable!(), 82 | }; 83 | } 84 | 85 | #[test] 86 | fn it_should_return_the_pokemon_otherwise() { 87 | let repo = Arc::new(InMemoryRepository::new()); 88 | repo.insert( 89 | PokemonNumber::pikachu(), 90 | PokemonName::pikachu(), 91 | PokemonTypes::pikachu(), 92 | ) 93 | .ok(); 94 | let req = Request::new(PokemonNumber::pikachu()); 95 | 96 | let res = execute(repo, req); 97 | 98 | match res { 99 | Ok(res) => { 100 | assert_eq!(res.number, u16::from(PokemonNumber::pikachu())); 101 | assert_eq!(res.name, String::from(PokemonName::pikachu())); 102 | assert_eq!(res.types, Vec::::from(PokemonTypes::pikachu())); 103 | } 104 | _ => unreachable!(), 105 | }; 106 | } 107 | 108 | impl Request { 109 | fn new(number: PokemonNumber) -> Self { 110 | Self { 111 | number: u16::from(number), 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/domain/entities.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::{PartialEq, PartialOrd}; 2 | 3 | #[derive(PartialEq, Clone, PartialOrd, Ord, Eq)] 4 | pub struct PokemonNumber(u16); 5 | 6 | impl TryFrom for PokemonNumber { 7 | type Error = (); 8 | 9 | fn try_from(n: u16) -> Result { 10 | if n > 0 && n < 899 { 11 | Ok(Self(n)) 12 | } else { 13 | Err(()) 14 | } 15 | } 16 | } 17 | 18 | impl From for u16 { 19 | fn from(n: PokemonNumber) -> Self { 20 | n.0 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | impl PokemonNumber { 26 | pub fn pikachu() -> Self { 27 | Self(25) 28 | } 29 | 30 | pub fn charmander() -> Self { 31 | Self(4) 32 | } 33 | 34 | pub fn bad() -> Self { 35 | Self(0) 36 | } 37 | } 38 | 39 | #[derive(Clone)] 40 | pub struct PokemonName(String); 41 | 42 | impl TryFrom for PokemonName { 43 | type Error = (); 44 | 45 | fn try_from(n: String) -> Result { 46 | if n.is_empty() { 47 | Err(()) 48 | } else { 49 | Ok(Self(n)) 50 | } 51 | } 52 | } 53 | 54 | impl From for String { 55 | fn from(n: PokemonName) -> Self { 56 | n.0 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | impl PokemonName { 62 | pub fn pikachu() -> Self { 63 | Self(String::from("Pikachu")) 64 | } 65 | 66 | pub fn charmander() -> Self { 67 | Self(String::from("Charmander")) 68 | } 69 | 70 | pub fn bad() -> Self { 71 | Self(String::from("")) 72 | } 73 | } 74 | 75 | #[derive(Clone)] 76 | pub struct PokemonTypes(Vec); 77 | 78 | impl TryFrom> for PokemonTypes { 79 | type Error = (); 80 | 81 | fn try_from(ts: Vec) -> Result { 82 | if ts.is_empty() { 83 | Err(()) 84 | } else { 85 | let mut pts = vec![]; 86 | for t in ts.iter() { 87 | match PokemonType::try_from(String::from(t)) { 88 | Ok(pt) => pts.push(pt), 89 | _ => return Err(()), 90 | } 91 | } 92 | Ok(Self(pts)) 93 | } 94 | } 95 | } 96 | 97 | impl From for Vec { 98 | fn from(pts: PokemonTypes) -> Self { 99 | let mut ts = vec![]; 100 | for pt in pts.0.into_iter() { 101 | ts.push(String::from(pt)); 102 | } 103 | ts 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | impl PokemonTypes { 109 | pub fn pikachu() -> Self { 110 | Self(vec![PokemonType::Electric]) 111 | } 112 | 113 | pub fn charmander() -> Self { 114 | Self(vec![PokemonType::Fire]) 115 | } 116 | } 117 | 118 | #[derive(Clone)] 119 | enum PokemonType { 120 | Electric, 121 | Fire, 122 | } 123 | 124 | impl TryFrom for PokemonType { 125 | type Error = (); 126 | 127 | fn try_from(t: String) -> Result { 128 | match t.as_str() { 129 | "Electric" => Ok(Self::Electric), 130 | "Fire" => Ok(Self::Fire), 131 | _ => Err(()), 132 | } 133 | } 134 | } 135 | 136 | impl From for String { 137 | fn from(t: PokemonType) -> Self { 138 | String::from(match t { 139 | PokemonType::Electric => "Electric", 140 | PokemonType::Fire => "Fire", 141 | }) 142 | } 143 | } 144 | 145 | #[derive(Clone)] 146 | pub struct Pokemon { 147 | pub number: PokemonNumber, 148 | pub name: PokemonName, 149 | pub types: PokemonTypes, 150 | } 151 | 152 | impl Pokemon { 153 | pub fn new(number: PokemonNumber, name: PokemonName, types: PokemonTypes) -> Self { 154 | Self { 155 | number, 156 | name, 157 | types, 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/domain/create_pokemon.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::entities::{Pokemon, PokemonName, PokemonNumber, PokemonTypes}; 2 | use crate::repositories::pokemon::{InsertError, Repository}; 3 | use std::sync::Arc; 4 | 5 | pub struct Request { 6 | pub number: u16, 7 | pub name: String, 8 | pub types: Vec, 9 | } 10 | 11 | pub struct Response { 12 | pub number: u16, 13 | pub name: String, 14 | pub types: Vec, 15 | } 16 | 17 | pub enum Error { 18 | BadRequest, 19 | Conflict, 20 | Unknown, 21 | } 22 | 23 | pub fn execute(repo: Arc, req: Request) -> Result { 24 | match ( 25 | PokemonNumber::try_from(req.number), 26 | PokemonName::try_from(req.name), 27 | PokemonTypes::try_from(req.types), 28 | ) { 29 | (Ok(number), Ok(name), Ok(types)) => match repo.insert(number, name, types) { 30 | Ok(Pokemon { 31 | number, 32 | name, 33 | types, 34 | }) => Ok(Response { 35 | number: u16::from(number), 36 | name: String::from(name), 37 | types: Vec::::from(types), 38 | }), 39 | Err(InsertError::Conflict) => Err(Error::Conflict), 40 | Err(InsertError::Unknown) => Err(Error::Unknown), 41 | }, 42 | _ => Err(Error::BadRequest), 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::*; 49 | use crate::repositories::pokemon::InMemoryRepository; 50 | 51 | #[test] 52 | fn it_should_return_a_bad_request_error_when_request_is_invalid() { 53 | let repo = Arc::new(InMemoryRepository::new()); 54 | let req = Request::new( 55 | PokemonNumber::pikachu(), 56 | PokemonName::bad(), 57 | PokemonTypes::pikachu(), 58 | ); 59 | 60 | let res = execute(repo, req); 61 | 62 | match res { 63 | Err(Error::BadRequest) => {} 64 | _ => unreachable!(), 65 | }; 66 | } 67 | 68 | #[test] 69 | fn it_should_return_a_conflict_error_when_pokemon_number_already_exists() { 70 | let repo = Arc::new(InMemoryRepository::new()); 71 | repo.insert( 72 | PokemonNumber::pikachu(), 73 | PokemonName::pikachu(), 74 | PokemonTypes::pikachu(), 75 | ) 76 | .ok(); 77 | let req = Request::new( 78 | PokemonNumber::pikachu(), 79 | PokemonName::charmander(), 80 | PokemonTypes::charmander(), 81 | ); 82 | 83 | let res = execute(repo, req); 84 | 85 | match res { 86 | Err(Error::Conflict) => {} 87 | _ => unreachable!(), 88 | } 89 | } 90 | 91 | #[test] 92 | fn it_should_return_an_unknown_error_when_an_unexpected_error_happens() { 93 | let repo = Arc::new(InMemoryRepository::new().with_error()); 94 | let req = Request::new( 95 | PokemonNumber::pikachu(), 96 | PokemonName::pikachu(), 97 | PokemonTypes::pikachu(), 98 | ); 99 | 100 | let res = execute(repo, req); 101 | 102 | match res { 103 | Err(Error::Unknown) => {} 104 | _ => unreachable!(), 105 | }; 106 | } 107 | 108 | #[test] 109 | fn it_should_return_the_pokemon_otherwise() { 110 | let repo = Arc::new(InMemoryRepository::new()); 111 | let req = Request::new( 112 | PokemonNumber::pikachu(), 113 | PokemonName::pikachu(), 114 | PokemonTypes::pikachu(), 115 | ); 116 | 117 | let res = execute(repo, req); 118 | 119 | match res { 120 | Ok(res) => { 121 | assert_eq!(res.number, u16::from(PokemonNumber::pikachu())); 122 | assert_eq!(res.name, String::from(PokemonName::pikachu())); 123 | assert_eq!(res.types, Vec::::from(PokemonTypes::pikachu())); 124 | } 125 | _ => unreachable!(), 126 | }; 127 | } 128 | 129 | impl Request { 130 | fn new(number: PokemonNumber, name: PokemonName, types: PokemonTypes) -> Self { 131 | Self { 132 | number: u16::from(number), 133 | name: String::from(name), 134 | types: Vec::::from(types), 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/repositories/pokemon.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::entities::{Pokemon, PokemonName, PokemonNumber, PokemonTypes}; 2 | use rusqlite::{params, params_from_iter, Connection, Error::SqliteFailure, OpenFlags}; 3 | use serde::Deserialize; 4 | use std::sync::{Mutex, MutexGuard}; 5 | 6 | pub enum InsertError { 7 | Conflict, 8 | Unknown, 9 | } 10 | 11 | pub enum FetchAllError { 12 | Unknown, 13 | } 14 | 15 | pub enum FetchOneError { 16 | NotFound, 17 | Unknown, 18 | } 19 | 20 | pub enum DeleteError { 21 | NotFound, 22 | Unknown, 23 | } 24 | 25 | pub trait Repository: Send + Sync { 26 | fn insert( 27 | &self, 28 | number: PokemonNumber, 29 | name: PokemonName, 30 | types: PokemonTypes, 31 | ) -> Result; 32 | 33 | fn fetch_all(&self) -> Result, FetchAllError>; 34 | 35 | fn fetch_one(&self, number: PokemonNumber) -> Result; 36 | 37 | fn delete(&self, number: PokemonNumber) -> Result<(), DeleteError>; 38 | } 39 | 40 | pub struct InMemoryRepository { 41 | error: bool, 42 | pokemons: Mutex>, 43 | } 44 | 45 | impl InMemoryRepository { 46 | pub fn new() -> Self { 47 | let pokemons: Mutex> = Mutex::new(vec![]); 48 | Self { 49 | error: false, 50 | pokemons, 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | pub fn with_error(self) -> Self { 56 | Self { 57 | error: true, 58 | ..self 59 | } 60 | } 61 | } 62 | 63 | impl Repository for InMemoryRepository { 64 | fn insert( 65 | &self, 66 | number: PokemonNumber, 67 | name: PokemonName, 68 | types: PokemonTypes, 69 | ) -> Result { 70 | if self.error { 71 | return Err(InsertError::Unknown); 72 | } 73 | 74 | let mut lock = match self.pokemons.lock() { 75 | Ok(lock) => lock, 76 | _ => return Err(InsertError::Unknown), 77 | }; 78 | 79 | if lock.iter().any(|pokemon| pokemon.number == number) { 80 | return Err(InsertError::Conflict); 81 | } 82 | 83 | let pokemon = Pokemon::new(number, name, types); 84 | lock.push(pokemon.clone()); 85 | Ok(pokemon) 86 | } 87 | 88 | fn fetch_all(&self) -> Result, FetchAllError> { 89 | if self.error { 90 | return Err(FetchAllError::Unknown); 91 | } 92 | 93 | let lock = match self.pokemons.lock() { 94 | Ok(lock) => lock, 95 | _ => return Err(FetchAllError::Unknown), 96 | }; 97 | 98 | let mut pokemons = lock.to_vec(); 99 | pokemons.sort_by(|a, b| a.number.cmp(&b.number)); 100 | Ok(pokemons) 101 | } 102 | 103 | fn fetch_one(&self, number: PokemonNumber) -> Result { 104 | if self.error { 105 | return Err(FetchOneError::Unknown); 106 | } 107 | 108 | let lock = match self.pokemons.lock() { 109 | Ok(lock) => lock, 110 | _ => return Err(FetchOneError::Unknown), 111 | }; 112 | 113 | match lock.iter().find(|p| p.number == number) { 114 | Some(pokemon) => Ok(pokemon.clone()), 115 | None => Err(FetchOneError::NotFound), 116 | } 117 | } 118 | 119 | fn delete(&self, number: PokemonNumber) -> Result<(), DeleteError> { 120 | if self.error { 121 | return Err(DeleteError::Unknown); 122 | } 123 | 124 | let mut lock = match self.pokemons.lock() { 125 | Ok(lock) => lock, 126 | _ => return Err(DeleteError::Unknown), 127 | }; 128 | 129 | let index = match lock.iter().position(|p| p.number == number) { 130 | Some(index) => index, 131 | None => return Err(DeleteError::NotFound), 132 | }; 133 | 134 | lock.remove(index); 135 | Ok(()) 136 | } 137 | } 138 | 139 | pub struct AirtableRepository { 140 | url: String, 141 | auth_header: String, 142 | } 143 | 144 | impl AirtableRepository { 145 | pub fn try_new(api_key: &str, workspace_id: &str) -> Result { 146 | let url = format!("https://api.airtable.com/v0/{}/pokemons", workspace_id); 147 | let auth_header = format!("Bearer {}", api_key); 148 | 149 | if let Err(_) = ureq::get(&url).set("Authorization", &auth_header).call() { 150 | return Err(()); 151 | } 152 | 153 | Ok(Self { url, auth_header }) 154 | } 155 | 156 | fn fetch_pokemon_rows(&self, number: Option) -> Result { 157 | let url = match number { 158 | Some(number) => format!("{}?filterByFormula=number%3D{}", self.url, number), 159 | None => format!("{}?sort%5B0%5D%5Bfield%5D=number", self.url), 160 | }; 161 | 162 | let res = match ureq::get(&url) 163 | .set("Authorization", &self.auth_header) 164 | .call() 165 | { 166 | Ok(res) => res, 167 | _ => return Err(()), 168 | }; 169 | 170 | match res.into_json::() { 171 | Ok(json) => Ok(json), 172 | _ => Err(()), 173 | } 174 | } 175 | } 176 | 177 | impl Repository for AirtableRepository { 178 | fn insert( 179 | &self, 180 | number: PokemonNumber, 181 | name: PokemonName, 182 | types: PokemonTypes, 183 | ) -> Result { 184 | let json = match self.fetch_pokemon_rows(Some(u16::from(number.clone()))) { 185 | Ok(json) => json, 186 | _ => return Err(InsertError::Unknown), 187 | }; 188 | 189 | if !json.records.is_empty() { 190 | return Err(InsertError::Conflict); 191 | } 192 | 193 | let body = ureq::json!({ 194 | "records": [{ 195 | "fields": { 196 | "number": u16::from(number.clone()), 197 | "name": String::from(name.clone()), 198 | "types": Vec::::from(types.clone()), 199 | }, 200 | }], 201 | }); 202 | 203 | if let Err(_) = ureq::post(&self.url) 204 | .set("Authorization", &self.auth_header) 205 | .send_json(body) 206 | { 207 | return Err(InsertError::Unknown); 208 | } 209 | 210 | Ok(Pokemon::new(number, name, types)) 211 | } 212 | 213 | fn fetch_all(&self) -> Result, FetchAllError> { 214 | let json = match self.fetch_pokemon_rows(None) { 215 | Ok(json) => json, 216 | _ => return Err(FetchAllError::Unknown), 217 | }; 218 | 219 | let mut pokemons = vec![]; 220 | 221 | for record in json.records.into_iter() { 222 | match ( 223 | PokemonNumber::try_from(record.fields.number), 224 | PokemonName::try_from(record.fields.name), 225 | PokemonTypes::try_from(record.fields.types), 226 | ) { 227 | (Ok(number), Ok(name), Ok(types)) => { 228 | pokemons.push(Pokemon::new(number, name, types)) 229 | } 230 | _ => return Err(FetchAllError::Unknown), 231 | } 232 | } 233 | 234 | Ok(pokemons) 235 | } 236 | 237 | fn fetch_one(&self, number: PokemonNumber) -> Result { 238 | let mut json = match self.fetch_pokemon_rows(Some(u16::from(number.clone()))) { 239 | Ok(json) => json, 240 | _ => return Err(FetchOneError::Unknown), 241 | }; 242 | 243 | if json.records.is_empty() { 244 | return Err(FetchOneError::NotFound); 245 | } 246 | 247 | let record = json.records.remove(0); 248 | 249 | match ( 250 | PokemonNumber::try_from(record.fields.number), 251 | PokemonName::try_from(record.fields.name), 252 | PokemonTypes::try_from(record.fields.types), 253 | ) { 254 | (Ok(number), Ok(name), Ok(types)) => Ok(Pokemon::new(number, name, types)), 255 | _ => Err(FetchOneError::Unknown), 256 | } 257 | } 258 | 259 | fn delete(&self, number: PokemonNumber) -> Result<(), DeleteError> { 260 | let mut json = match self.fetch_pokemon_rows(Some(u16::from(number.clone()))) { 261 | Ok(json) => json, 262 | _ => return Err(DeleteError::Unknown), 263 | }; 264 | 265 | if json.records.is_empty() { 266 | return Err(DeleteError::NotFound); 267 | } 268 | 269 | let record = json.records.remove(0); 270 | 271 | match ureq::delete(&format!("{}/{}", self.url, record.id)) 272 | .set("Authorization", &self.auth_header) 273 | .call() 274 | { 275 | Ok(_) => Ok(()), 276 | _ => Err(DeleteError::Unknown), 277 | } 278 | } 279 | } 280 | 281 | #[derive(Deserialize)] 282 | struct AirtableJson { 283 | records: Vec, 284 | } 285 | 286 | #[derive(Deserialize)] 287 | struct AirtableRecord { 288 | id: String, 289 | fields: AirtableFields, 290 | } 291 | 292 | #[derive(Deserialize)] 293 | struct AirtableFields { 294 | number: u16, 295 | name: String, 296 | types: Vec, 297 | } 298 | 299 | pub struct SqliteRepository { 300 | connection: Mutex, 301 | } 302 | 303 | impl SqliteRepository { 304 | pub fn try_new(path: &str) -> Result { 305 | let connection = match Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_WRITE) 306 | { 307 | Ok(connection) => connection, 308 | _ => return Err(()), 309 | }; 310 | 311 | match connection.execute("pragma foreign_keys = 1", []) { 312 | Ok(_) => Ok(Self { 313 | connection: Mutex::new(connection), 314 | }), 315 | _ => Err(()), 316 | } 317 | } 318 | 319 | fn fetch_pokemon_rows( 320 | lock: &MutexGuard<'_, Connection>, 321 | number: Option, 322 | ) -> Result, ()> { 323 | let (query, params) = match number { 324 | Some(number) => ( 325 | "select number, name from pokemons where number = ?", 326 | vec![number], 327 | ), 328 | _ => ("select number, name from pokemons", vec![]), 329 | }; 330 | 331 | let mut stmt = match lock.prepare(query) { 332 | Ok(stmt) => stmt, 333 | _ => return Err(()), 334 | }; 335 | 336 | let mut rows = match stmt.query(params_from_iter(params)) { 337 | Ok(rows) => rows, 338 | _ => return Err(()), 339 | }; 340 | 341 | let mut pokemon_rows = vec![]; 342 | 343 | while let Ok(Some(row)) = rows.next() { 344 | match (row.get::(0), row.get::(1)) { 345 | (Ok(number), Ok(name)) => pokemon_rows.push((number, name)), 346 | _ => return Err(()), 347 | }; 348 | } 349 | 350 | Ok(pokemon_rows) 351 | } 352 | 353 | fn fetch_type_rows(lock: &MutexGuard<'_, Connection>, number: u16) -> Result, ()> { 354 | let mut stmt = match lock.prepare("select name from types where pokemon_number = ?") { 355 | Ok(stmt) => stmt, 356 | _ => return Err(()), 357 | }; 358 | 359 | let mut rows = match stmt.query([number]) { 360 | Ok(rows) => rows, 361 | _ => return Err(()), 362 | }; 363 | 364 | let mut type_rows = vec![]; 365 | 366 | while let Ok(Some(row)) = rows.next() { 367 | match row.get::(0) { 368 | Ok(name) => type_rows.push(name), 369 | _ => return Err(()), 370 | }; 371 | } 372 | 373 | Ok(type_rows) 374 | } 375 | } 376 | 377 | impl Repository for SqliteRepository { 378 | fn insert( 379 | &self, 380 | number: PokemonNumber, 381 | name: PokemonName, 382 | types: PokemonTypes, 383 | ) -> Result { 384 | let mut lock = match self.connection.lock() { 385 | Ok(lock) => lock, 386 | _ => return Err(InsertError::Unknown), 387 | }; 388 | 389 | let transaction = match lock.transaction() { 390 | Ok(transaction) => transaction, 391 | _ => return Err(InsertError::Unknown), 392 | }; 393 | 394 | match transaction.execute( 395 | "insert into pokemons (number, name) values (?, ?)", 396 | params![u16::from(number.clone()), String::from(name.clone())], 397 | ) { 398 | Ok(_) => {} 399 | Err(SqliteFailure(_, Some(message))) => { 400 | if message == "UNIQUE constraint failed: pokemons.number" { 401 | return Err(InsertError::Conflict); 402 | } else { 403 | return Err(InsertError::Unknown); 404 | } 405 | } 406 | _ => return Err(InsertError::Unknown), 407 | }; 408 | 409 | for _type in Vec::::from(types.clone()) { 410 | if let Err(_) = transaction.execute( 411 | "insert into types (pokemon_number, name) values (?, ?)", 412 | params![u16::from(number.clone()), _type], 413 | ) { 414 | return Err(InsertError::Unknown); 415 | } 416 | } 417 | 418 | match transaction.commit() { 419 | Ok(_) => Ok(Pokemon::new(number, name, types)), 420 | _ => Err(InsertError::Unknown), 421 | } 422 | } 423 | 424 | fn fetch_all(&self) -> Result, FetchAllError> { 425 | let lock = match self.connection.lock() { 426 | Ok(lock) => lock, 427 | _ => return Err(FetchAllError::Unknown), 428 | }; 429 | 430 | let pokemon_rows = match Self::fetch_pokemon_rows(&lock, None) { 431 | Ok(pokemon_rows) => pokemon_rows, 432 | _ => return Err(FetchAllError::Unknown), 433 | }; 434 | 435 | let mut pokemons = vec![]; 436 | 437 | for pokemon_row in pokemon_rows { 438 | let type_rows = match Self::fetch_type_rows(&lock, pokemon_row.0) { 439 | Ok(type_rows) => type_rows, 440 | _ => return Err(FetchAllError::Unknown), 441 | }; 442 | 443 | let pokemon = match ( 444 | PokemonNumber::try_from(pokemon_row.0), 445 | PokemonName::try_from(pokemon_row.1), 446 | PokemonTypes::try_from(type_rows), 447 | ) { 448 | (Ok(number), Ok(name), Ok(types)) => Pokemon::new(number, name, types), 449 | _ => return Err(FetchAllError::Unknown), 450 | }; 451 | 452 | pokemons.push(pokemon); 453 | } 454 | 455 | Ok(pokemons) 456 | } 457 | 458 | fn fetch_one(&self, number: PokemonNumber) -> Result { 459 | let lock = match self.connection.lock() { 460 | Ok(lock) => lock, 461 | _ => return Err(FetchOneError::Unknown), 462 | }; 463 | 464 | let mut pokemon_rows = 465 | match Self::fetch_pokemon_rows(&lock, Some(u16::from(number.clone()))) { 466 | Ok(pokemon_rows) => pokemon_rows, 467 | _ => return Err(FetchOneError::Unknown), 468 | }; 469 | 470 | if pokemon_rows.is_empty() { 471 | return Err(FetchOneError::NotFound); 472 | } 473 | 474 | let pokemon_row = pokemon_rows.remove(0); 475 | 476 | let type_rows = match Self::fetch_type_rows(&lock, pokemon_row.0) { 477 | Ok(type_rows) => type_rows, 478 | _ => return Err(FetchOneError::Unknown), 479 | }; 480 | 481 | match ( 482 | PokemonNumber::try_from(pokemon_row.0), 483 | PokemonName::try_from(pokemon_row.1), 484 | PokemonTypes::try_from(type_rows), 485 | ) { 486 | (Ok(number), Ok(name), Ok(types)) => Ok(Pokemon::new(number, name, types)), 487 | _ => Err(FetchOneError::Unknown), 488 | } 489 | } 490 | 491 | fn delete(&self, number: PokemonNumber) -> Result<(), DeleteError> { 492 | let lock = match self.connection.lock() { 493 | Ok(lock) => lock, 494 | _ => return Err(DeleteError::Unknown), 495 | }; 496 | 497 | match lock.execute( 498 | "delete from pokemons where number = ?", 499 | params![u16::from(number)], 500 | ) { 501 | Ok(0) => Err(DeleteError::NotFound), 502 | Ok(_) => Ok(()), 503 | _ => Err(DeleteError::Unknown), 504 | } 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.2.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 8 | 9 | [[package]] 10 | name = "ahash" 11 | version = "0.7.6" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 14 | dependencies = [ 15 | "getrandom", 16 | "once_cell", 17 | "version_check", 18 | ] 19 | 20 | [[package]] 21 | name = "ansi_term" 22 | version = "0.11.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 25 | dependencies = [ 26 | "winapi", 27 | ] 28 | 29 | [[package]] 30 | name = "ascii" 31 | version = "1.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" 34 | 35 | [[package]] 36 | name = "atty" 37 | version = "0.2.14" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 40 | dependencies = [ 41 | "hermit-abi", 42 | "libc", 43 | "winapi", 44 | ] 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.0.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 51 | 52 | [[package]] 53 | name = "base-x" 54 | version = "0.2.8" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" 57 | 58 | [[package]] 59 | name = "base64" 60 | version = "0.13.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 63 | 64 | [[package]] 65 | name = "bitflags" 66 | version = "1.3.2" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 69 | 70 | [[package]] 71 | name = "brotli-sys" 72 | version = "0.3.2" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" 75 | dependencies = [ 76 | "cc", 77 | "libc", 78 | ] 79 | 80 | [[package]] 81 | name = "brotli2" 82 | version = "0.3.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" 85 | dependencies = [ 86 | "brotli-sys", 87 | "libc", 88 | ] 89 | 90 | [[package]] 91 | name = "buf_redux" 92 | version = "0.8.4" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" 95 | dependencies = [ 96 | "memchr", 97 | "safemem", 98 | ] 99 | 100 | [[package]] 101 | name = "bumpalo" 102 | version = "3.7.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" 105 | 106 | [[package]] 107 | name = "cc" 108 | version = "1.0.69" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" 111 | 112 | [[package]] 113 | name = "cfg-if" 114 | version = "1.0.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 117 | 118 | [[package]] 119 | name = "chrono" 120 | version = "0.4.19" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 123 | dependencies = [ 124 | "libc", 125 | "num-integer", 126 | "num-traits", 127 | "time 0.1.43", 128 | "winapi", 129 | ] 130 | 131 | [[package]] 132 | name = "chunked_transfer" 133 | version = "1.4.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" 136 | 137 | [[package]] 138 | name = "clap" 139 | version = "2.33.3" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 142 | dependencies = [ 143 | "ansi_term", 144 | "atty", 145 | "bitflags", 146 | "strsim", 147 | "textwrap", 148 | "unicode-width", 149 | "vec_map", 150 | ] 151 | 152 | [[package]] 153 | name = "console" 154 | version = "0.14.1" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" 157 | dependencies = [ 158 | "encode_unicode", 159 | "lazy_static", 160 | "libc", 161 | "regex", 162 | "terminal_size", 163 | "unicode-width", 164 | "winapi", 165 | ] 166 | 167 | [[package]] 168 | name = "const_fn" 169 | version = "0.4.8" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" 172 | 173 | [[package]] 174 | name = "crc32fast" 175 | version = "1.2.1" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 178 | dependencies = [ 179 | "cfg-if", 180 | ] 181 | 182 | [[package]] 183 | name = "deflate" 184 | version = "0.9.1" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "5f95bf05dffba6e6cce8dfbb30def788154949ccd9aed761b472119c21e01c70" 187 | dependencies = [ 188 | "adler32", 189 | "gzip-header", 190 | ] 191 | 192 | [[package]] 193 | name = "dialoguer" 194 | version = "0.8.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "c9dd058f8b65922819fabb4a41e7d1964e56344042c26efbccd465202c23fa0c" 197 | dependencies = [ 198 | "console", 199 | "lazy_static", 200 | "tempfile", 201 | "zeroize", 202 | ] 203 | 204 | [[package]] 205 | name = "discard" 206 | version = "1.0.4" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" 209 | 210 | [[package]] 211 | name = "encode_unicode" 212 | version = "0.3.6" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 215 | 216 | [[package]] 217 | name = "fallible-iterator" 218 | version = "0.2.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 221 | 222 | [[package]] 223 | name = "fallible-streaming-iterator" 224 | version = "0.1.9" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 227 | 228 | [[package]] 229 | name = "filetime" 230 | version = "0.2.15" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" 233 | dependencies = [ 234 | "cfg-if", 235 | "libc", 236 | "redox_syscall", 237 | "winapi", 238 | ] 239 | 240 | [[package]] 241 | name = "form_urlencoded" 242 | version = "1.0.1" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 245 | dependencies = [ 246 | "matches", 247 | "percent-encoding", 248 | ] 249 | 250 | [[package]] 251 | name = "getrandom" 252 | version = "0.2.3" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 255 | dependencies = [ 256 | "cfg-if", 257 | "libc", 258 | "wasi", 259 | ] 260 | 261 | [[package]] 262 | name = "gzip-header" 263 | version = "0.3.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "0131feb3d3bb2a5a238d8a4d09f6353b7ebfdc52e77bccbf4ea6eaa751dde639" 266 | dependencies = [ 267 | "crc32fast", 268 | ] 269 | 270 | [[package]] 271 | name = "hashbrown" 272 | version = "0.11.2" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 275 | dependencies = [ 276 | "ahash", 277 | ] 278 | 279 | [[package]] 280 | name = "hashlink" 281 | version = "0.7.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" 284 | dependencies = [ 285 | "hashbrown", 286 | ] 287 | 288 | [[package]] 289 | name = "hermit-abi" 290 | version = "0.1.19" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 293 | dependencies = [ 294 | "libc", 295 | ] 296 | 297 | [[package]] 298 | name = "httparse" 299 | version = "1.5.1" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" 302 | 303 | [[package]] 304 | name = "idna" 305 | version = "0.2.3" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 308 | dependencies = [ 309 | "matches", 310 | "unicode-bidi", 311 | "unicode-normalization", 312 | ] 313 | 314 | [[package]] 315 | name = "itoa" 316 | version = "0.4.8" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 319 | 320 | [[package]] 321 | name = "js-sys" 322 | version = "0.3.55" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 325 | dependencies = [ 326 | "wasm-bindgen", 327 | ] 328 | 329 | [[package]] 330 | name = "lazy_static" 331 | version = "1.4.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 334 | 335 | [[package]] 336 | name = "libc" 337 | version = "0.2.100" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" 340 | 341 | [[package]] 342 | name = "libsqlite3-sys" 343 | version = "0.23.1" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "abd5850c449b40bacb498b2bbdfaff648b1b055630073ba8db499caf2d0ea9f2" 346 | dependencies = [ 347 | "pkg-config", 348 | "vcpkg", 349 | ] 350 | 351 | [[package]] 352 | name = "log" 353 | version = "0.4.14" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 356 | dependencies = [ 357 | "cfg-if", 358 | ] 359 | 360 | [[package]] 361 | name = "matches" 362 | version = "0.1.9" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 365 | 366 | [[package]] 367 | name = "memchr" 368 | version = "2.4.1" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 371 | 372 | [[package]] 373 | name = "mime" 374 | version = "0.3.16" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 377 | 378 | [[package]] 379 | name = "mime_guess" 380 | version = "2.0.3" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" 383 | dependencies = [ 384 | "mime", 385 | "unicase", 386 | ] 387 | 388 | [[package]] 389 | name = "multipart" 390 | version = "0.18.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" 393 | dependencies = [ 394 | "buf_redux", 395 | "httparse", 396 | "log", 397 | "mime", 398 | "mime_guess", 399 | "quick-error", 400 | "rand", 401 | "safemem", 402 | "tempfile", 403 | "twoway", 404 | ] 405 | 406 | [[package]] 407 | name = "num-integer" 408 | version = "0.1.44" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 411 | dependencies = [ 412 | "autocfg", 413 | "num-traits", 414 | ] 415 | 416 | [[package]] 417 | name = "num-traits" 418 | version = "0.2.14" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 421 | dependencies = [ 422 | "autocfg", 423 | ] 424 | 425 | [[package]] 426 | name = "num_cpus" 427 | version = "1.13.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 430 | dependencies = [ 431 | "hermit-abi", 432 | "libc", 433 | ] 434 | 435 | [[package]] 436 | name = "once_cell" 437 | version = "1.8.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 440 | 441 | [[package]] 442 | name = "percent-encoding" 443 | version = "2.1.0" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 446 | 447 | [[package]] 448 | name = "pkg-config" 449 | version = "0.3.20" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb" 452 | 453 | [[package]] 454 | name = "pokedex" 455 | version = "0.1.0" 456 | dependencies = [ 457 | "clap", 458 | "dialoguer", 459 | "rouille", 460 | "rusqlite", 461 | "serde", 462 | "serde_json", 463 | "ureq", 464 | ] 465 | 466 | [[package]] 467 | name = "ppv-lite86" 468 | version = "0.2.10" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 471 | 472 | [[package]] 473 | name = "proc-macro-hack" 474 | version = "0.5.19" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 477 | 478 | [[package]] 479 | name = "proc-macro2" 480 | version = "1.0.28" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" 483 | dependencies = [ 484 | "unicode-xid", 485 | ] 486 | 487 | [[package]] 488 | name = "quick-error" 489 | version = "1.2.3" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 492 | 493 | [[package]] 494 | name = "quote" 495 | version = "1.0.9" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 498 | dependencies = [ 499 | "proc-macro2", 500 | ] 501 | 502 | [[package]] 503 | name = "rand" 504 | version = "0.8.4" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 507 | dependencies = [ 508 | "libc", 509 | "rand_chacha", 510 | "rand_core", 511 | "rand_hc", 512 | ] 513 | 514 | [[package]] 515 | name = "rand_chacha" 516 | version = "0.3.1" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 519 | dependencies = [ 520 | "ppv-lite86", 521 | "rand_core", 522 | ] 523 | 524 | [[package]] 525 | name = "rand_core" 526 | version = "0.6.3" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 529 | dependencies = [ 530 | "getrandom", 531 | ] 532 | 533 | [[package]] 534 | name = "rand_hc" 535 | version = "0.3.1" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 538 | dependencies = [ 539 | "rand_core", 540 | ] 541 | 542 | [[package]] 543 | name = "redox_syscall" 544 | version = "0.2.10" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 547 | dependencies = [ 548 | "bitflags", 549 | ] 550 | 551 | [[package]] 552 | name = "regex" 553 | version = "1.5.4" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 556 | dependencies = [ 557 | "regex-syntax", 558 | ] 559 | 560 | [[package]] 561 | name = "regex-syntax" 562 | version = "0.6.25" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 565 | 566 | [[package]] 567 | name = "remove_dir_all" 568 | version = "0.5.3" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 571 | dependencies = [ 572 | "winapi", 573 | ] 574 | 575 | [[package]] 576 | name = "ring" 577 | version = "0.16.20" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 580 | dependencies = [ 581 | "cc", 582 | "libc", 583 | "once_cell", 584 | "spin", 585 | "untrusted", 586 | "web-sys", 587 | "winapi", 588 | ] 589 | 590 | [[package]] 591 | name = "rouille" 592 | version = "3.2.1" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "8263ea8c0988dbdd89e679d408eaa5505bb886677e997562646c2e2ba4d2e6db" 595 | dependencies = [ 596 | "base64", 597 | "brotli2", 598 | "chrono", 599 | "deflate", 600 | "filetime", 601 | "multipart", 602 | "num_cpus", 603 | "percent-encoding", 604 | "rand", 605 | "serde", 606 | "serde_derive", 607 | "serde_json", 608 | "sha1", 609 | "threadpool", 610 | "time 0.2.27", 611 | "tiny_http", 612 | "url", 613 | ] 614 | 615 | [[package]] 616 | name = "rusqlite" 617 | version = "0.26.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "6e2aebd07fccf5c0d3f1458219965b0861d45f5db9f9b2617277fa7f85179213" 620 | dependencies = [ 621 | "bitflags", 622 | "fallible-iterator", 623 | "fallible-streaming-iterator", 624 | "hashlink", 625 | "libsqlite3-sys", 626 | "memchr", 627 | "smallvec", 628 | ] 629 | 630 | [[package]] 631 | name = "rustc_version" 632 | version = "0.2.3" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 635 | dependencies = [ 636 | "semver", 637 | ] 638 | 639 | [[package]] 640 | name = "rustls" 641 | version = "0.19.1" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" 644 | dependencies = [ 645 | "base64", 646 | "log", 647 | "ring", 648 | "sct", 649 | "webpki", 650 | ] 651 | 652 | [[package]] 653 | name = "ryu" 654 | version = "1.0.5" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 657 | 658 | [[package]] 659 | name = "safemem" 660 | version = "0.3.3" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" 663 | 664 | [[package]] 665 | name = "sct" 666 | version = "0.6.1" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" 669 | dependencies = [ 670 | "ring", 671 | "untrusted", 672 | ] 673 | 674 | [[package]] 675 | name = "semver" 676 | version = "0.9.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 679 | dependencies = [ 680 | "semver-parser", 681 | ] 682 | 683 | [[package]] 684 | name = "semver-parser" 685 | version = "0.7.0" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 688 | 689 | [[package]] 690 | name = "serde" 691 | version = "1.0.129" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" 694 | dependencies = [ 695 | "serde_derive", 696 | ] 697 | 698 | [[package]] 699 | name = "serde_derive" 700 | version = "1.0.129" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3" 703 | dependencies = [ 704 | "proc-macro2", 705 | "quote", 706 | "syn", 707 | ] 708 | 709 | [[package]] 710 | name = "serde_json" 711 | version = "1.0.66" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" 714 | dependencies = [ 715 | "itoa", 716 | "ryu", 717 | "serde", 718 | ] 719 | 720 | [[package]] 721 | name = "sha1" 722 | version = "0.6.0" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" 725 | 726 | [[package]] 727 | name = "smallvec" 728 | version = "1.7.0" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" 731 | 732 | [[package]] 733 | name = "spin" 734 | version = "0.5.2" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 737 | 738 | [[package]] 739 | name = "standback" 740 | version = "0.2.17" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" 743 | dependencies = [ 744 | "version_check", 745 | ] 746 | 747 | [[package]] 748 | name = "stdweb" 749 | version = "0.4.20" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" 752 | dependencies = [ 753 | "discard", 754 | "rustc_version", 755 | "stdweb-derive", 756 | "stdweb-internal-macros", 757 | "stdweb-internal-runtime", 758 | "wasm-bindgen", 759 | ] 760 | 761 | [[package]] 762 | name = "stdweb-derive" 763 | version = "0.5.3" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" 766 | dependencies = [ 767 | "proc-macro2", 768 | "quote", 769 | "serde", 770 | "serde_derive", 771 | "syn", 772 | ] 773 | 774 | [[package]] 775 | name = "stdweb-internal-macros" 776 | version = "0.2.9" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" 779 | dependencies = [ 780 | "base-x", 781 | "proc-macro2", 782 | "quote", 783 | "serde", 784 | "serde_derive", 785 | "serde_json", 786 | "sha1", 787 | "syn", 788 | ] 789 | 790 | [[package]] 791 | name = "stdweb-internal-runtime" 792 | version = "0.1.5" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" 795 | 796 | [[package]] 797 | name = "strsim" 798 | version = "0.8.0" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 801 | 802 | [[package]] 803 | name = "syn" 804 | version = "1.0.75" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" 807 | dependencies = [ 808 | "proc-macro2", 809 | "quote", 810 | "unicode-xid", 811 | ] 812 | 813 | [[package]] 814 | name = "tempfile" 815 | version = "3.2.0" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 818 | dependencies = [ 819 | "cfg-if", 820 | "libc", 821 | "rand", 822 | "redox_syscall", 823 | "remove_dir_all", 824 | "winapi", 825 | ] 826 | 827 | [[package]] 828 | name = "terminal_size" 829 | version = "0.1.17" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 832 | dependencies = [ 833 | "libc", 834 | "winapi", 835 | ] 836 | 837 | [[package]] 838 | name = "textwrap" 839 | version = "0.11.0" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 842 | dependencies = [ 843 | "unicode-width", 844 | ] 845 | 846 | [[package]] 847 | name = "threadpool" 848 | version = "1.8.1" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" 851 | dependencies = [ 852 | "num_cpus", 853 | ] 854 | 855 | [[package]] 856 | name = "time" 857 | version = "0.1.43" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 860 | dependencies = [ 861 | "libc", 862 | "winapi", 863 | ] 864 | 865 | [[package]] 866 | name = "time" 867 | version = "0.2.27" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" 870 | dependencies = [ 871 | "const_fn", 872 | "libc", 873 | "standback", 874 | "stdweb", 875 | "time-macros", 876 | "version_check", 877 | "winapi", 878 | ] 879 | 880 | [[package]] 881 | name = "time-macros" 882 | version = "0.1.1" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" 885 | dependencies = [ 886 | "proc-macro-hack", 887 | "time-macros-impl", 888 | ] 889 | 890 | [[package]] 891 | name = "time-macros-impl" 892 | version = "0.1.2" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" 895 | dependencies = [ 896 | "proc-macro-hack", 897 | "proc-macro2", 898 | "quote", 899 | "standback", 900 | "syn", 901 | ] 902 | 903 | [[package]] 904 | name = "tiny_http" 905 | version = "0.8.2" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "9ce51b50006056f590c9b7c3808c3bd70f0d1101666629713866c227d6e58d39" 908 | dependencies = [ 909 | "ascii", 910 | "chrono", 911 | "chunked_transfer", 912 | "log", 913 | "url", 914 | ] 915 | 916 | [[package]] 917 | name = "tinyvec" 918 | version = "1.3.1" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" 921 | dependencies = [ 922 | "tinyvec_macros", 923 | ] 924 | 925 | [[package]] 926 | name = "tinyvec_macros" 927 | version = "0.1.0" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 930 | 931 | [[package]] 932 | name = "twoway" 933 | version = "0.1.8" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" 936 | dependencies = [ 937 | "memchr", 938 | ] 939 | 940 | [[package]] 941 | name = "unicase" 942 | version = "2.6.0" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 945 | dependencies = [ 946 | "version_check", 947 | ] 948 | 949 | [[package]] 950 | name = "unicode-bidi" 951 | version = "0.3.6" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" 954 | 955 | [[package]] 956 | name = "unicode-normalization" 957 | version = "0.1.19" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 960 | dependencies = [ 961 | "tinyvec", 962 | ] 963 | 964 | [[package]] 965 | name = "unicode-width" 966 | version = "0.1.8" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 969 | 970 | [[package]] 971 | name = "unicode-xid" 972 | version = "0.2.2" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 975 | 976 | [[package]] 977 | name = "untrusted" 978 | version = "0.7.1" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 981 | 982 | [[package]] 983 | name = "ureq" 984 | version = "2.2.0" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "3131cd6cb18488da91da1d10ed31e966f453c06b65bf010d35638456976a3fd7" 987 | dependencies = [ 988 | "base64", 989 | "chunked_transfer", 990 | "log", 991 | "once_cell", 992 | "rustls", 993 | "serde", 994 | "serde_json", 995 | "url", 996 | "webpki", 997 | "webpki-roots", 998 | ] 999 | 1000 | [[package]] 1001 | name = "url" 1002 | version = "2.2.2" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1005 | dependencies = [ 1006 | "form_urlencoded", 1007 | "idna", 1008 | "matches", 1009 | "percent-encoding", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "vcpkg" 1014 | version = "0.2.15" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1017 | 1018 | [[package]] 1019 | name = "vec_map" 1020 | version = "0.8.2" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1023 | 1024 | [[package]] 1025 | name = "version_check" 1026 | version = "0.9.3" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1029 | 1030 | [[package]] 1031 | name = "wasi" 1032 | version = "0.10.2+wasi-snapshot-preview1" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1035 | 1036 | [[package]] 1037 | name = "wasm-bindgen" 1038 | version = "0.2.78" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 1041 | dependencies = [ 1042 | "cfg-if", 1043 | "wasm-bindgen-macro", 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "wasm-bindgen-backend" 1048 | version = "0.2.78" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 1051 | dependencies = [ 1052 | "bumpalo", 1053 | "lazy_static", 1054 | "log", 1055 | "proc-macro2", 1056 | "quote", 1057 | "syn", 1058 | "wasm-bindgen-shared", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "wasm-bindgen-macro" 1063 | version = "0.2.78" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 1066 | dependencies = [ 1067 | "quote", 1068 | "wasm-bindgen-macro-support", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "wasm-bindgen-macro-support" 1073 | version = "0.2.78" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 1076 | dependencies = [ 1077 | "proc-macro2", 1078 | "quote", 1079 | "syn", 1080 | "wasm-bindgen-backend", 1081 | "wasm-bindgen-shared", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "wasm-bindgen-shared" 1086 | version = "0.2.78" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 1089 | 1090 | [[package]] 1091 | name = "web-sys" 1092 | version = "0.3.55" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" 1095 | dependencies = [ 1096 | "js-sys", 1097 | "wasm-bindgen", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "webpki" 1102 | version = "0.21.4" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" 1105 | dependencies = [ 1106 | "ring", 1107 | "untrusted", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "webpki-roots" 1112 | version = "0.21.1" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" 1115 | dependencies = [ 1116 | "webpki", 1117 | ] 1118 | 1119 | [[package]] 1120 | name = "winapi" 1121 | version = "0.3.9" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1124 | dependencies = [ 1125 | "winapi-i686-pc-windows-gnu", 1126 | "winapi-x86_64-pc-windows-gnu", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "winapi-i686-pc-windows-gnu" 1131 | version = "0.4.0" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1134 | 1135 | [[package]] 1136 | name = "winapi-x86_64-pc-windows-gnu" 1137 | version = "0.4.0" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1140 | 1141 | [[package]] 1142 | name = "zeroize" 1143 | version = "1.4.1" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "377db0846015f7ae377174787dd452e1c5f5a9050bc6f954911d01f116daa0cd" 1146 | --------------------------------------------------------------------------------