녹 방지

TL;DR: This is a post for beginners where I show:

  • How I structured the files in the project for better documentation: every file in the same folder;
  • How I coded a "query builder" to handle different calls without repeating code: using a generic parameter;
  • How I coded test cases within the documentation: nothing special, just remarks on async tests;
  • How I created High Order Functions (HOF) to emulate that behaviour we have in Rust's Option and Iterator, for example: created a struct with functions that return the struct itself;
  • How I used serde to deserialize the Json: nothing special, just some extra remarks;
  • How I tested it as if it were a crate without exporting it to crates.io: just add the lib.rs in the dependencies of the new project.

나는 Magic: The Gathering API를 위해 포장기를 구축했다(그들 자신의 견해에 따르면 이것은 SDK이다).내가 이렇게 하는 것은 다음과 같은 가능성을 제공할 수 있는 개인 프로젝트를 원하기 때문이다.
  • reqwest 판자 상자 사용하기;
  • 다른 프로그래머를 위해 코드를 작성한다(예를 들어 그들이 사용할 수 있도록 허용한다HOFs. 예를 들어 cards.filter().types("creature").colors("red").name("dragon")...).
  • 파일에 있는 테스트를 포함하여 판자 상자로 기록한다.
  • Github 조작을 실현(편집: 내가 나중에 이걸 했어. 너는 그것을 찾을 수 있어).
  • 내가 Magic를 선택한 이유는 수집(MTG) API(MTG의 책벌레 제외)는 매우 간단한 API이기 때문이다.
    이것은 하나의 강좌가 아니다. 나는 단지 이 노력이 나로 하여금 재미있는 선택을 하게 할 뿐이라고 강조할 뿐이다.그리고 이것은 초보자에게 주는 것이다.나는 내가 자세히 읽고 The Book, 그리고 Rust by example, 그리고 약간 ASync를 가지고 놀았던 사람들에게 무슨 신선한 말을 할지 의심스럽다.

    The result can be found here.


    프로젝트 구조


    나는 두 가지 선택이 있다.

    내가 왼쪽을 선택한 데는 두 가지 이유가 있다.
  • 쪽지 상자를 사용하는 사람이 GET 입력할 수 있도록 허용한다use mtgsdk
  • 이러한 방식으로 문서가 첫 페이지에 모든 내용을 표시합니다.만약 내가 오른쪽의 옵션을 선택한다면 문서에는 모듈use mtgsdk::mtgsdk만 보일 것이다. 나는 이것이 쿨한 아이들이 어떻게 하는 것이 아니라는 것을 발견했다.
  • 방법(왼쪽 옵션):

    If you want to see for yourself, fork/download the repository and type cargo doc --open


    방법(올바른 선택):

    아마도 첫 번째 사진과Rust by example는 모든 방법이 어떻게 실현되었는지 충분히 설명할 수 있을 것이다.그러나 분명히 보기 위해서 내가 말하고자 하는 것은 왼쪽을 원한다면 네가 해야 할 일은 네 mtgsdk에서mods를 성명하는 것이다.그렇지 않으면 단일 모듈 이름으로 폴더를 만들고 lib.rs 파일을 만들고 mod.rsmod를 사용하며 pub mod의 폴더 이름만 성명해야 합니다(이 예에서 lib.rslib.rs만 포함됩니다pub mod mtgsdk;.

    쿼리 생성기


    내가 말한 바와 같이 이 API는 GET가지 방법밖에 없다. reqwest 그것을 어떻게 처리하는지에 대해 우리는 토론할 것이 많지 않다. 왜냐하면 이것은 기본적으로 하나의 URL만 전달하기 때문이다curl에서처럼.

    I am not saying that this is all that reqwest does; it is not. I am saying that for this API we don't actually need anything else that accessing the URL and parsing the Json (more on this later).


    그러나 저는 모든 모듈에서 중복reqwest::get(url)을 하지 않고 쿼리 생성기를 만들었습니다. 쿼리 생성기는 url를 수신하고 되돌아오는 Result<T, StatusCode>입니다. 그 중에서 T는 각종 호출(카드, 형식 등) 데이터를 포함하는 구조입니다.
    한 장소에서reqwest의 사용을 유지하는 것 외에 오류를 처리하고 보내는 것도 허용합니다. StatusCode 따라서 이 박스를 사용하는 개발자는 오류를 쉽게 처리할 수 있습니다.다음은 코드와 몇 가지 추가 주석입니다.
    async fn build<T>(url: String) -> Result<T, StatusCode>
    where
        //This is a requirement for Serde; I will talk about it below.
        T: DeserializeOwned,
    {
        let response = reqwest::get(url).await;
    
        // I am using match instead of "if let" or "?" 
        // to make what's happening here crystal clear
        match &response {
            Ok(r) => {
                if r.status() != StatusCode::OK {
                    return Err(r.status());
                }
            }
            Err(e) => {
                if e.is_status() {
                    return Err(e.status().unwrap());
                } else {
                    return Err(StatusCode::BAD_REQUEST);
                }
            }
        }
    
        // This is where de magic (and most problems) occur.
        // Again, more on this later.
        let content = response.unwrap().json::<T>().await;
    
        match content {
            Ok(s) => Ok(s),
            Err(e) => {
                println!("{:?}", e);
                Err(StatusCode::BAD_REQUEST)
            }
        }
    }
    
    매우 간단합니다. 호출build() 함수의 함수는 T에 대응하는 유형을 알려 줍니다. 이 유형은 Deserialize 특성을 가진 구조이기 때문에reqwest의 json()는 우리를 위해 힘든 작업을 완성할 수 있습니다.

    문서 테스트


    러스트북documentation section이 아주 좋아요.이 내용을 읽는 것 외에, 나는 내가 사용하는 쪽지 상자에서 문서를 어떻게 관리하는지에 대한 예시만 보았다.
    내가 강조하고자 하는 것은 문서에 테스트를 삽입하는 것이다.

    이 테스트는 이 책에서 논의한 내용으로 실행될 것이기 때문에 나는 이 점을 강조하지 않을 것이다.나에게 있어서 구체적인 것은 내가 테스트async에서 호출하는 것이다. 이것은 두 가지 작은 조정이 필요하다.
  • 비동기 블록에 있어야 합니다.
  • 나는 어떤 것도 돌려줄 수 없다(그래서 없다await?,because it returns the error.
  • 고급 함수


    나는 HOF 을 말할 줄 모르며, 함수식 프로그래밍을 설명하는 것은 말할 것도 없다.내가 이렇게 한 것은 이와 같은 구축기 모델이 아니기 때문이다.
    let mut get_cards_request = api.cards().all_filtered(
        CardFilter::builder()
            .game_format(GameFormat::Standard)
            .cardtypes_or(&[CardType::Instant, CardType::Sorcery])
            .converted_mana_cost(2)
            .rarities(&[CardRarity::Rare, CardRarity::MythicRare])
            .build(),
        );
    
    let mut cards: Vec<CardDetail> = Vec::new();
    loop {
        let response = get_cards_request.next_page().await?
        let cards = response.content;
        if cards.is_empty() {
            break;
        }
        filtered_cards.extend(cards);
    }
    println!("Filtered Cards: {:?}", filtered_cards);
    
    ...나는 이런 물건을 원한다.
    let response = cards::filter()
        .game_format("standard")
        .type_field("instant|sorcery")
        .cmc(2)
        .rarity("rare|mythic")
        .all()
        .await;
    
    println!("Filtered cards: {:?}", response.unwrap());
    
    왜?개발자로서 OptionIterator, 그리고 warp 이런 판자 상자가 어떻게 이 점을 실현하는지 녹이 얼룩덜룩하기 때문이다.

    기능성 어떻게


    함수 filter()Where 라는 구조를 되돌려줍니다. 이 구조는 벡터가 있으며, 추가할 필터를 모두 저장합니다.
    pub struct Where<'a> {
        query: Vec<(&'a str, String)>,
    }
    
    pub fn filter<'a>() -> Where<'a> {
        Where { query: Vec::new() }
    }
    
    따라서 내가 유사response = mtgsdk::card::filter()의 조작을 실행할 때 변수response는 Wherestruct로 내부에서 이루어진 모든 함수를 호출할 수 있다. 예를 들어 다음과 같다.
    impl<'a> Where<'a> {
        pub fn game_format(mut self, input: &'a str) -> Self {
            self.query.push(("gameFormat", String::from(input)));
            self
        }
    }
    
    기본적으로 내가 Where를 호출한 다음에 함수filter(), game_format(), type_field()cmc()를 추가할 때 나는 이렇게 했다.
  • 사용 rarity() 구조 구축 Where
  • 호출filter()game_format() 내부에서 이루어지고 같은 Where로 되돌아온다
  • Where에서 되돌아오는 type_field() 호출Where
  • game_format()에서 되돌아오는 cmc() 호출Where
  • type_field()에서 되돌아오는 rarity() 호출Where
  • cmc()에서 되돌아온 all() 호출Where, 최종 반환 카드 벡터:
  • pub async fn all(mut self) -> Result<Vec<Card>, StatusCode> {
        let val = self.query.remove(0);
        let mut filter = format!("?{}={}", val.0, val.1);
    
        for (k, v) in self.query.into_iter() {
            filter = format!("{}&{}={}", filter, k, v);
        }
    
        let cards: Result<RootAll, StatusCode> = query_builder::filter("cards", &filter).await;
    
        match cards {
            Ok(t) => Ok(t.cards),
            Err(e) => Err(e),
        }
    }
    
    이렇게

    Json 역정렬


    약속한 대로.
    #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
    #[serde(rename_all = "camelCase")]
    pub struct Set {
        pub code: String,
        pub name: String,
        #[serde(rename = "type")]
        pub type_field: String,
        #[serde(default)]
        pub booster: Vec<Booster>,
        pub release_date: String,
        pub block: Option<String>,
        pub online_only: Option<bool>,
        pub gatherer_code: Option<String>,
        pub old_code: Option<String>,
        pub magic_cards_info_code: Option<String>,
        pub border: Option<String>,
        pub expansion: Option<String>,
        pub mkm_name: Option<String>,
        pub mkm_id: Option<u32>,
    }
    
    나는 몇 가지 일을 제기하고 싶다.
  • 이거 공구가 도움이 많이 돼요.
  • rarity()가 설명이 부족하면 유사#[serde(rename_all = "camelCase")]의 구조 필드가 API에서 호출된 필드release_date에서 데이터를 받을 수 있도록 허용한다.
  • 다음 두 가지 방법으로 옵션 필드를 처리할 수 있습니다.
  • 사용transform, 나는 Option을 더 좋아한다. 이런 상황에서 발송하지 않은 필드releaseDate나 필드가 비어 있는 경우None면 박스를 사용하는 개발자는 절대적인 확정성을 가지기 때문이다
  • 사용Some 필수 필드에 사용할 수 있습니다. 이 경우 API가 해당 필드를 보낸 것이 틀림없습니다.
  • 테스트


    나는 그것을 새로운 항목으로 가져오고 싶지만, 나는 그것을 판자 상자에 넣고 싶지 않다.이오.어떻게?이렇게:
    새 항목#[serde(default)]에 다음과 같은 내용이 추가되었습니다.
    [dependencies]
    mtgsdk = { path = "../mtgsdk" }
    tokio = { version = "1", features = ["full"] }
    
    그게 다야.내 Cargo.toml에서 나는 단지 그것을 판자 상자로 사용할 뿐이다.
    use mtgsdk::cards;
    
    #[tokio::main]
    async fn main() {
        let result = cards::find(46012).await;
    
        if let Ok(card) = result{
            println!("{}", card.name)
        };
    }
    
    책에서 말한 것에 충실하고 싶다면integration testing 도움이 될 수 있다.
    안녕히 계십시오.🙃
    표지 사진 작성자Wayne Low

    좋은 웹페이지 즐겨찾기