REST API 녹 슬림 + 휨 2: POST

19886 단어 rustrestwarpapi
네가 돌아오는 것을 보니 매우 기쁘다!두 번째 섹션에서는 API의 첫 번째 함수 방법인 POST을 구축합니다.

곡속교전!

The code for this part is available .

POST은 일부 데이터를 삽입하기 위해 서버에 요청을 보냅니다.이 데이터는 JSON 형식으로 전송됩니다.그리고 이 프로그램의 작업은 JSON을 해석하고 저장하는 것이다. (메모리에만 있다. 나는 이 시리즈에서 ORM을 토론하지 않을 것이다. 아마도 다른 시리즈에 있을 것이다.)
홀로그램 갑판 생성 시뮬레이션 (만약 당신이 스타크래프트를 좋아하지 않는다면 이 말을 무시하십시오.)이 점을 감안하여 나는 KISS원칙을 따르고 이를'simulation id'와'simulation name'의 키 값으로 설정했다.
우선, 내가 무엇을 열거해야 하는지 알았을 때, 나는 이전의 list()list_sims(), handle_list()handle_list_sims()으로 바꾸었다.
그리고 저는 try_create() 테스트 함수를 만들었습니다. 이전의 테스트와 매우 비슷합니다. 이것은 새로운 필터를 사용하여 방법(이번은 POST)을 같은 /holodeck 경로로 보내고 좋은 답을 기대합니다(what could go wrong in the holodeck?).
// Add "models" in the already existing line "use super::filters;":
use super::{filters,models};


#[tokio::test]
async fn try_create() {
    let db = models::new_db();
    let api = filters::post_sim(db);

    let response = request()
        .method("POST")
        .path("/holodeck")
        .json(&models::Simulation{
            id: 1,
            name: String::from("The Big Goodbye")
        })
        .reply(&api)
        .await;

    assert_eq!(response.status(), StatusCode::CREATED);
}
발생할 수 있는 문제(기타 제외)는 중복 항목으로 인한 충돌이다.그래서 나도 이 때문에 테스트를 했다.
#[tokio::test]
async fn try_create_duplicates() {
    let db = models::new_db();
    let api = filters::post_sim(db);

    let response = request()
        .method("POST")
        .path("/holodeck")
        .json(&models::Simulation{
            id: 1,
            name: String::from("Bride Of Chaotica!")
        })
        .reply(&api)
        .await;

    assert_eq!(response.status(), StatusCode::CREATED);

    let response = request()
        .method("POST")
        .path("/holodeck")
        .json(&models::Simulation{
            id: 1,
            name: String::from("Bride Of Chaotica!")
        })
        .reply(&api)
        .await;

    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
JSON이 전달하는 Simulation의 구조는 아직 정의가 필요하다. (이것은 테스트 모듈의 use super::modelsuse문장을 주의해야 한다. 나는 어떤 문장도 언급하는 것을 잊지 않기를 바란다.)post_sim()을 작성하기 전에 세 가지가 필요합니다. [1] 처리와 지구화 데이터의 유형(메모리에만 있음), [2] JSON 본문 하나, 그리고 [3] 데이터를 포장하는 방법입니다.
[1]부터: 나는 먼저 serde 판자 상자로 서열화를 처리해야 하기 때문에 [dependencies]Cargo.toml 아래에 이 줄을 추가했다.
serde = { version = "1", features = ["derive"]}
이 문제를 해결한 후에 저는 다시 lib.rs으로 돌아가서 mod을 만들었습니다. 이것은 typestruct이 있습니다. serde 매크로 때문에 자동으로 JSON 형식의 데이터를 서열화할 수 있습니다.아, 그리고 없는 곳이 없는 new() 함수(이것은 수첩에서 일반적으로 알려주는 실현 중의 함수가 아니다. 왜냐하면 나는 type에서 Db을 사용했기 때문이다. 왜냐하면 나는 그것을 struct에 끼워 넣고 싶지 않기 때문이다).
mod models {
    use serde::{Deserialize, Serialize};
    use std::collections::HashSet;
    use std::sync::Arc;
    use tokio::sync::Mutex;

    #[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
    pub struct Simulation {
        pub id: u64,
        pub name: String,
    }

    pub type Db = Arc<Mutex<HashSet<Simulation>>>;

    pub fn new_db() -> Db {
        Arc::new(Mutex::new(HashSet::new()))
    }
}
지금 나는 왜 이런 스마트 포인터를 선택했을까?
  • 짧게 대답: cool kids이 이렇게 했기 때문이다.
  • 의 똑똑한 대답: 나는 Arc에 좋은 대체 방안이 없다고 생각한다. 왜냐하면 나는 해시집의 라인에 대한 안전한 인용이 필요하기 때문이다.Mutex에 관해서, 나는 본래 RwLock으로 대체하여, 동시에 읽을 수 있도록 할 수 있었지만, 상하문을 고려하면, 이것은 필요없을 것 같다.
  • 그런데 상호 배척 자물쇠 안에 뭐가 들어있죠?"왜 해시집이야?"물어봐.나는HashMap이 명백한 선택이라는 것에 동의한다. 나도 벡터가 쉽게 실현되는 것에 동의한다. 그러나HashSet을 선택한 것은 내가HashMap에서 중요한 장점과 벡터의 장점을 얻었기 때문이다.그러나 실제 해석은 내가 이 시리즈의 두 번째 부분에서 보류한 것이기 때문에 이 점에서 너는 반드시 나를 믿어야 한다.
    [2]: JSON body 함수는 앞에서 언급한 쿨한 아이의 예에서 복제한 것으로 filters mod에 놓여 있다.그것은 크지 않으면 어떤 JSON체도 받아들인다.
    // This line already exists; I just added "models"
    use super::{handlers, models}; 
    
    fn json_body() -> impl Filter<Extract = (models::Simulation,), Error = warp::Rejection> + Clone {
        warp::body::content_length_limit(1024 * 16).and(warp::body::json())
    }
    
    그것을 묶기 위해서[3], 우리는 filters의 또 다른 새로운 함수가 필요하다.이 함수 post_sim()은 JSON 주체와 Db(우리 Arc)를 수신하여 처리 프로그램 handle_create_sim()에 보냅니다.
    pub fn post_sim(db: models::Db) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
        let db_map = warp::any()
            .map(move || db.clone());
    
        warp::path!("holodeck")
            .and(warp::post())
            .and(json_body())
            .and(db_map)
            .and_then(handlers::handle_create_sim)
    }
    
    여기서 더 이해하기 어려울 것 같은 것은 let db_map이다.warp::anycatch all이다.즉, 그것은 아무것도 거르지 않는 필터이다.그래서 우리가 여기서 하는 일은 우리의 데이터베이스가 필터에 포장되어 있는지 확인하는 것이다. 그러면 우리는 그것을 네가 본 .and(db_map)에 붙일 수 있다.
    and 트리거된 handle_create_sim()에서 handlers 모듈 내부로 들어가면 다음과 같다.
    // Add this "use" below the others
    use super::models;
    
    pub async fn handle_create_sim(sim: models::Simulation, db: models::Db) -> Result<impl warp::Reply, Infallible> {
        let mut map = db.lock().await;
    
        if let Some(result) = map.get(&sim){
            return Ok(warp::reply::with_status(
                format!("Simulation #{} already exists under the name {}", result.id, result.name), 
                StatusCode::BAD_REQUEST,
            ));
        }
    
        map.insert(sim.clone());
        Ok(warp::reply::with_status(format!("Simulation #{} created", sim.id), StatusCode::CREATED))
    }
    
    이것은 JSON(Simulation)과 HashSet(db:db)을 받아들인다. 항목이 이미 존재하면 오류를 되돌려주거나 JSON 데이터를 HashSet에 삽입하거나 그렇지 않으면 success를 되돌려줍니다.

    Here having a HashMap would clearly be the better solution, as it would allow us to compare keys. We'll solve this in part 2.


    나는 본래 Ok(StatusCode::CREATED)Ok(StatusCode::BAD_REQUEST)으로 warp::reply::with_status을 대체할 수 있었지만, 나는 잘못을 처리할 생각이 없기 때문에, 나는 이것이 적어도 내가 할 수 있는 일이라고 생각한다.
    $ cargo test
    
    running 3 tests
    test tests::try_create ... ok
    test tests::try_create_duplicates ... ok
    test tests::try_list ... ok
    
    test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
    
    그래, 그래, 그래.

    다음 회'왜곡과의 교전'에서...
    다음은 GET 방법입니다. 이것은 매개 변수 처리와 (최종) 처리 이 해시 집합을 볼 수 있음을 의미합니다.
    🖖

    좋은 웹페이지 즐겨찾기