녹 방지
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이다).내가 이렇게 하는 것은 다음과 같은 가능성을 제공할 수 있는 개인 프로젝트를 원하기 때문이다.
cards.filter().types("creature").colors("red").name("dragon")
...).이것은 하나의 강좌가 아니다. 나는 단지 이 노력이 나로 하여금 재미있는 선택을 하게 할 뿐이라고 강조할 뿐이다.그리고 이것은 초보자에게 주는 것이다.나는 내가 자세히 읽고 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.rs
와 mod
를 사용하며 pub mod
의 폴더 이름만 성명해야 합니다(이 예에서 lib.rs
는 lib.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());
왜?개발자로서 Option
와Iterator
, 그리고 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
에서 데이터를 받을 수 있도록 허용한다.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
Reference
이 문제에 관하여(녹 방지), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/rogertorres/rest-api-wrapper-with-rust-mk4텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)