尝试rust里面实现架构设计

This commit is contained in:
Jeremy Yin 2023-02-13 23:00:22 +08:00
commit a46b2e066b
11 changed files with 230 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
.idea

7
Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "pokemon"
version = "0.1.0"

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "pokemon"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

0
src/api/mod.rs Normal file
View File

0
src/cli/mod.rs Normal file
View File

View File

@ -0,0 +1,83 @@
use crate::domain::entity::{PokemonName, PokemonNumber, PokemonTypes};
use crate::repo::pokemon::Repository;
struct Request {
number: u16,
name: String,
types: Vec<String>,
}
enum Response {
Ok(u16),
BadRequest,
Conflict,
}
fn execute(repo: &mut dyn Repository, req: Request) -> Response {
match (
PokemonNumber::try_from(req.number),
PokemonName::try_from(req.name),
PokemonTypes::try_from(req.types)
) {
(Ok(number), Ok(_), Ok(_)) => Response::Ok(u16::from(number)),
_ => Response::BadRequest,
}
}
#[cfg(test)]
mod tests {
use crate::repo::pokemon::InMemoryRepository;
use super::*;
#[test]
fn it_should_return_the_pokemon_number_otherwise() {
let mut repo = InMemoryRepository::new();
let number = 25;
let req = Request {
number: number,
name: "Pikachu".to_string(),
types: vec!["Electric".to_string(), ],
};
let res = execute(&mut repo, req);
match res {
Response::Ok(num) => assert_eq!(num, number),
_ => unreachable!(),
}
}
#[test]
fn it_should_return_a_bad_request_error_when_request_is_invalid() {
let mut repo = InMemoryRepository::new();
let number = 25;
let req = Request {
number: number,
name: "".to_string(),
types: vec!["Electric".to_string(), ],
};
let res = execute(&mut repo, req);
match res {
Response::Ok(num) => assert_eq!(num, number),
Response::BadRequest => {},
_ => unreachable!()
}
}
#[test]
fn it_should_return_a_conflict_error_when_pokemon_number_already_exists() {
let number = PokemonNumber::try_from(25).unwrap();
let name = PokemonName::try_from("Pikachu".to_string()).unwrap();
let types = PokemonTypes::try_from(vec!["Electric".to_string(),]).unwrap();
let mut repo = InMemoryRepository::new();
repo.insert(number, name, types);
let req = Request {
number: 25,
name: "Charmander".to_string(),
types: vec!["File".to_string()],
};
let res = execute(&mut repo, req);
match res {
Response::Conflict => {},
_ => unreachable!(),
}
}
}

87
src/domain/entity.rs Normal file
View File

@ -0,0 +1,87 @@
pub struct Pokemon {
pub number: PokemonNumber,
name: PokemonName,
types: PokemonTypes,
}
impl Pokemon {
pub fn new(number: PokemonNumber, name: PokemonName, types: PokemonTypes) -> Self {
Self {
number,
name,
types,
}
}
}
pub struct PokemonNumber(u16);
impl TryFrom<u16> for PokemonNumber {
type Error = ();
fn try_from(value: u16) -> Result<Self, Self::Error> {
if value > 0 && value < 899 {
Ok(Self(value))
} else {
Err(())
}
}
}
impl From<PokemonNumber> for u16 {
fn from(p: PokemonNumber) -> Self {
p.0
}
}
pub struct PokemonName(String);
impl TryFrom<String> for PokemonName {
type Error = ();
fn try_from(value: String) -> Result<Self, Self::Error> {
if value.is_empty() {
Err(())
} else {
Ok(Self(value))
}
}
}
pub struct PokemonTypes(Vec<PokemonType>);
impl TryFrom<Vec<String>> for PokemonTypes {
type Error = ();
fn try_from(value: Vec<String>) -> Result<Self, Self::Error> {
if value.is_empty() {
Err(())
} else {
let mut pts = vec![];
for t in value.iter() {
match PokemonType::try_from(t.to_string()) {
Ok(pt) => pts.push(pt),
_ => return Err(())
}
}
Ok(Self(pts))
}
}
}
enum PokemonType {
Electric,
Fire,
}
impl TryFrom<String> for PokemonType {
type Error = ();
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"Electric" => Ok(Self::Electric),
"Fire" => Ok(Self::Fire),
_ => Err(())
}
}
}

2
src/domain/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod create_pokemon;
pub mod entity;

8
src/main.rs Normal file
View File

@ -0,0 +1,8 @@
mod repo;
mod domain;
mod cli;
mod api;
fn main() {
println!("Hello, world!");
}

1
src/repo/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod pokemon;

32
src/repo/pokemon.rs Normal file
View File

@ -0,0 +1,32 @@
use crate::domain::entity::{Pokemon, PokemonName, PokemonNumber, PokemonTypes};
pub trait Repository {
fn insert(&self, number: PokemonNumber, name: PokemonName, types: PokemonTypes) -> PokemonNumber;
}
pub struct InMemoryRepository {
pokemons: Vec<Pokemon>
}
impl InMemoryRepository {
pub fn new() -> Self {
let pokemons: Vec<Pokemon> = vec![];
Self {
pokemons
}
}
}
pub enum Insert {
Ok(PokemonNumber),
Conflict,
}
impl Repository for InMemoryRepository {
fn insert(&mut self, number: PokemonNumber, name: PokemonName, types: PokemonTypes) -> Insert {
if self.pokemons.iter().any(|pokemon| pokemon.number == number) {
return Insert::Conflict;
}
let number_clone = number.clone();
self.pokemons.push(Pokemon::new(number_clone, name, types));
Insert::Ok(number)
}
}