= Scryfall SDK for Rust
:icons: font
:toc:
:sectanchors:
:sectnums:
:source-highlighter: highlight.js
:highlightjs-theme: monokai-sublime

image:https://img.shields.io/github/workflow/status/dgian/scryfall-sdk-rust/CI?label=CI&style=for-the-badge[Build Status (GitHub Actions),link=https://github.com/dgian/scryfall-sdk-rust/actions]
image:https://img.shields.io/docsrs/scryfall_sdk_rust?label=docs.rs&style=for-the-badge[link="https://docs.rs/scryfall_sdk_rust"]
image:https://img.shields.io/crates/v/scryfall_sdk_rust?style=for-the-badge[link="https://crates.io/crates/scryfall_sdk_rust"]

This is a basic SDK written in https://www.rust-lang.org[Rust] for the amazing https://scryfall.com[Scryfall search-engine], a powerful search tool for Magic: The Gathering cards, sets etc.

This SDK is implemented mainly for learning and practicing Rust skills.

IMPORTANT: This is currently a WIP.

== Requirements
* *Minimum Rust version*: `1.61`

== Usage

First add the dependency to `Cargo.toml`: 

[source, toml]
----
[dependencies]
scryfall_sdk_rust = "0.1"
----

The SDK exposes two HTTP clients using https://crates.io/crates/reqwest[reqwest crate]:

[loweralpha]
. async client (default) via `Scryfall` struct
. blocking client via `ScryfallBlocking` struct

NOTE: In order to use the blocking client you have to enable the `blocking` optional feature in Cargo.toml.

[source, toml]
----
[dependencies]
scryfall_sdk_rust = {version = "0.1", features = ["blocking"] }
----

=== Examples

In order to use the SDK, you have to take an instance of either client
and make a request with it using one of the <<_resources, provided resources>>.

For example:

.Get a single card by Scryfall id (async)
[source, rust]
----
use std::error::Error;
use scryfall_sdk_rust::{CardResource, Scryfall};

#[tokio::main] // <1>
async fn main() -> Result<(), Box<dyn Error>> {
    let scryfall = Scryfall::default(); // <2>

    let card = scryfall.request(
        &CardResource::ById("f295b713-1d6a-43fd-910d-fb35414bf58a")
    ).await?; // <3>

    Ok(println!("{:?}", card))
}
----
<1> Using https://crates.io/crates/tokio[tokio runtime] for the examples, but you can use anyone you like
<2> https://api.scryfall.com[Default scryfall api].You can also instantiate the client using `::from_url(&str)` to pass a custom url.
<3> Make an async request to retrieve the card's data

==== Card search

Scryfall provides a https://scryfall.com/docs/syntax[very powerful search syntax] which you
can leverage in order to search for cards. *Note* currently the SDK provides only usage of row `q` string in the query parmaeter.
Future versions will provide a Rust fluent api supporting the search keywords separately.

.Find red creatures with 3 power (async)
[source,rust]
----
use std::error::Error;
use scryfall_sdk_rust::{CardPageResource, Scryfall};
use scryfall_sdk_rust::resources::cards::SearchQueryParams;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let scryfall = Scryfall::default();

    let search = CardPageResource::Search(
        SearchQueryParams::with_q("c%3Ared+pow%3D3") // <1>
    );

    let cards = scryfall.request(&search).await?;

    Ok(println!("{:?}", cards))
}
----
<1> Construct the search query based on fulltext search syntax (c:red + pow=3)

==== Error response handling

In the previous very basic examples, any kind of error,
either an error response from the API, or a client error
is propagated to the `Result` return of `main` function.
This is done through the special `?` rust operator which propagates
errors to the caller of a function.

In reality, you will probably want to handle those errors at some point,
at least the error responses (e.g. 404) from the API. 
The `request` function of both clients returns a `Result<M, ErrorBody>`
which should contain either the Model for the expected object (e.g. Card) in case of success, or an `ErrorBody` in case of an error. For more info on the error response payloads (ErrorBody) see https://scryfall.com/docs/api/errors[Scryfall documentation].

An example of a possible error handling is the following

[source,rust]
----
use std::error::Error;
use scryfall_sdk_rust::{CardResource, Scryfall};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let scryfall = Scryfall::default();

    let card = scryfall.request(
        &CardResource::ById("f295b713-1d6a-43fd-910d-fb35414bf58a")
    ).await; // <1>

    println!("{:?}", card
        .map_err(|e| format!("Error {}: {}", e.status, e.details)
    )); // <2>

    let error = scryfall.request(
        &CardResource::ById("invalid")
    ).await; // <3>

    Ok(println!("{:?}", error
        .map_err(|e| format!("Error {}: {}", e.status, e.details))
    )) // <4>
}
----
<1> Get the details for an existing card. Notice the absence of `?` after `await` as opposed to the previous example.
<2> Printing here, will print the card details and not error as `map_err` does not affect the `Response::Ok` type of `Result`
<3> Get the details of a non-existing card should return an error (404)
<4> Printing here will print the details of the error response (status and detailed message) as the `Result` is now a `Response::Err` containing the respective `ErrorBody`

For client errors, e.g. when the Scryfall API server cannot be resolved,
or when the json response cannot be decoded for some reason,
a special `ErrorBody` will be returned. This will have `code = CLIENT_ERR`
and `status = 599` with `details` containing the original error cause.


[#_resources]
== List of implemented resources

The following are currently implemented:

- `CardResource` -> https://scryfall.com/docs/api/cards (single)
- `CardPageResource` -> https://scryfall.com/docs/api/cards (page/search)
- `CardCatalogResource` -> https://scryfall.com/docs/api/cards/autocomplete
- `CardCollectionResource` -> https://scryfall.com/docs/api/cards/collection
- `BulkDataListResource` -> https://scryfall.com/docs/api/bulk-data (list)
- `BulkDataResource` -> https://scryfall.com/docs/api/bulk-data (single)
- `CatalogResource` -> https://scryfall.com/docs/api/catalogs
- `CardSymbolsResource` -> https://scryfall.com/docs/api/card-symbols/all
- `ManaCostResource` -> https://scryfall.com/docs/api/card-symbols/parse-mana
- `CardSetListResource` -> https://scryfall.com/docs/api/sets (list)
- `CardSetResource` -> https://scryfall.com/docs/api/sets (single)
- `RulingListResource` -> https://scryfall.com/docs/api/rulings
