1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
//! Rulings represent Oracle rulings, Wizards of the Coast set release notes, or
//! Scryfall notes for a particular card.
//!
//! If two cards have the same name, they will have the same set of rulings
//! objects. If a card has rulings, it usually has more than one.
//!
//! Rulings with a `Scryfall` source have been added by the Scryfall team,
//! either to provide additional context for the card, or explain how the card
//! works in an unofficial format (such as Duel Commander).

use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::list::ListIter;
use crate::uri::Uri;
use crate::util::{API_RULING, CARDS_URL};

/// Rulings represent Oracle rulings, Wizards of the Coast set release notes, or
/// Scryfall notes for a particular card.
// If two cards have the same name, they will have the same set of rulings objects. If a card has
// rulings, it usually has more than one.
//
// Rulings with a scryfall source have been added by the Scryfall team, either to provide additional
// context for the card, or explain how the card works in an unofficial format (such as Duel
// Commander).
/// ---
///
/// For more information, refer to the [official docs](https://scryfall.com/docs/api/rulings).
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Ruling {
    /// A unique ID for the oracle identity of the card this ruling is about.
    /// This value is consistent across reprinted card editions, and unique
    /// among different cards with the same name (tokens, Unstable variants,
    /// etc).
    pub oracle_id: Uuid,

    /// A computer-readable string indicating which company produced this
    /// ruling, either wotc or scryfall.
    pub source: Source,

    /// The date when the ruling or note was published.
    pub published_at: NaiveDate,

    /// The text of the ruling.
    pub comment: String,
}

/// The two possible ruling sources
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[serde(rename_all = "snake_case")]
#[allow(missing_docs)]
pub enum Source {
    Wotc,
    Scryfall,
}

impl Ruling {
    /// Returns a List of rulings for a card with the given Multiverse ID. If
    /// the card has multiple multiverse IDs, this method can find either of
    /// them.
    ///
    /// # Examples
    /// ```rust
    /// use scryfall::ruling::Ruling;
    /// use futures::stream::StreamExt;
    /// use futures::future;
    /// # tokio_test::block_on(async {
    /// assert!(
    ///     Ruling::multiverse_id(3255)
    ///         .await
    ///         .unwrap()
    ///         .into_stream()
    ///         .map(Result::unwrap)
    ///         .any(|r| future::ready(r.comment.ends_with("Yes, this is a bit weird.")))
    ///         .await
    /// );
    /// # })
    /// ```
    ///
    /// ```rust
    /// use scryfall::ruling::Ruling;
    /// use futures::stream::StreamExt;
    /// use futures::future;
    /// # tokio_test::block_on(async {
    /// assert!(
    ///     Ruling::multiverse_id(3255)
    ///         .await
    ///         .unwrap()
    ///         .into_stream_buffered(10)
    ///         .map(Result::unwrap)
    ///         .any(|r| future::ready(r.comment.ends_with("Yes, this is a bit weird.")))
    ///         .await
    /// );
    /// # })
    /// ```
    pub async fn multiverse_id(id: usize) -> crate::Result<ListIter<Self>> {
        Uri::from(
            CARDS_URL
                .join("multiverse/")?
                .join(&format!("{}/", id))?
                .join(API_RULING)?,
        )
        .fetch_iter()
        .await
    }

    /// Returns rulings for a card with the given MTGO ID (also known as the
    /// Catalog ID). The ID can either be the card’s `mtgo_id` or its
    /// `mtgo_foil_id`.
    ///
    /// # Examples
    /// ```rust
    /// use scryfall::ruling::Ruling;
    /// use futures::stream::StreamExt;
    /// use futures::future;
    /// # tokio_test::block_on(async {
    /// assert!(
    ///     Ruling::mtgo_id(57934)
    ///         .await
    ///         .unwrap()
    ///         .into_stream()
    ///         .map(Result::unwrap)
    ///         .any(|r| future::ready(r.comment.ends_with("You read the whole contract, right?")))
    ///         .await
    /// );
    /// # })
    /// ```
    ///
    /// ```rust
    /// use scryfall::ruling::Ruling;
    /// use futures::stream::StreamExt;
    /// use futures::future;
    /// # tokio_test::block_on(async {
    /// assert!(
    ///     Ruling::mtgo_id(57934)
    ///         .await
    ///         .unwrap()
    ///         .into_stream_buffered(10)
    ///         .map(Result::unwrap)
    ///         .any(|r| future::ready(r.comment.ends_with("You read the whole contract, right?")))
    ///         .await
    /// );
    /// # })
    /// ```
    pub async fn mtgo_id(id: usize) -> crate::Result<ListIter<Self>> {
        Uri::from(
            CARDS_URL
                .join("mtgo/")?
                .join(&format!("{}/", id))?
                .join(API_RULING)?,
        )
        .fetch_iter()
        .await
    }

    /// Returns rulings for a card with the given Magic: The Gathering Arena ID.
    ///
    /// ```rust
    /// use scryfall::ruling::Ruling;
    /// use futures::stream::StreamExt;
    /// use futures::future;
    /// # tokio_test::block_on(async {
    /// assert!(
    ///     Ruling::arena_id(67462)
    ///         .await
    ///         .unwrap()
    ///         .into_stream()
    ///         .map(Result::unwrap)
    ///         .any(|r| {
    ///             future::ready(r.comment
    ///                 .starts_with("Once a chapter ability has triggered,"))
    ///         })
    ///         .await
    /// );
    /// # })
    /// ```
    ///
    /// ```rust
    /// use scryfall::ruling::Ruling;
    /// use futures::stream::StreamExt;
    /// use futures::future;
    /// # tokio_test::block_on(async {
    /// assert!(
    ///     Ruling::arena_id(67462)
    ///         .await
    ///         .unwrap()
    ///         .into_stream_buffered(10)
    ///         .map(Result::unwrap)
    ///         .any(|r| {
    ///             future::ready(r.comment
    ///                 .starts_with("Once a chapter ability has triggered,"))
    ///         })
    ///         .await
    /// );
    /// # })
    /// ```
    pub async fn arena_id(id: usize) -> crate::Result<ListIter<Self>> {
        Uri::from(
            CARDS_URL
                .join("arena/")?
                .join(&format!("{}/", id))?
                .join(API_RULING)?,
        )
        .fetch_iter()
        .await
    }

    /// Returns a List of rulings for the card with the given set code and
    /// collector number.
    ///
    /// # Examples
    /// ```rust
    /// use scryfall::ruling::Ruling;
    /// use futures::stream::StreamExt;
    /// use futures::future;
    /// # tokio_test::block_on(async {
    /// assert!(
    ///     Ruling::set_and_number("bfz", 17)
    ///         .await
    ///         .unwrap()
    ///         .into_stream()
    ///         .map(Result::unwrap)
    ///         .any(|r| future::ready(r.comment == "Yes, your opponent can’t even. We know."))
    ///         .await
    /// );
    /// # })
    /// ```
    ///
    /// ```rust
    /// use scryfall::ruling::Ruling;
    /// use futures::stream::StreamExt;
    /// use futures::future;
    /// # tokio_test::block_on(async {
    /// assert!(
    ///     Ruling::set_and_number("bfz", 17)
    ///         .await
    ///         .unwrap()
    ///         .into_stream_buffered(10)
    ///         .map(Result::unwrap)
    ///         .any(|r| future::ready(r.comment == "Yes, your opponent can’t even. We know."))
    ///         .await
    /// );
    /// # })
    /// ```
    pub async fn set_and_number(set: &str, number: u32) -> crate::Result<ListIter<Self>> {
        Uri::from(
            CARDS_URL
                .join(&format!("{}/{}/", set, number))?
                .join(API_RULING)?,
        )
        .fetch_iter()
        .await
    }

    /// Returns a List of rulings for a card with the given Scryfall ID.
    ///
    /// # Examples
    /// ```rust
    /// use scryfall::ruling::Ruling;
    /// use futures::stream::StreamExt;
    /// use futures::future;
    /// # tokio_test::block_on(async {
    /// assert!(
    ///     Ruling::uuid("f2b9983e-20d4-4d12-9e2c-ec6d9a345787".parse().unwrap())
    ///         .await
    ///         .unwrap()
    ///         .into_stream()
    ///         .map(Result::unwrap)
    ///         .any(|r| future::ready(r.comment == "It must flip like a coin and not like a Frisbee."))
    ///         .await
    /// );
    /// # })
    /// ```
    ///
    /// ```rust
    /// use scryfall::ruling::Ruling;
    /// use futures::stream::StreamExt;
    /// use futures::future;
    /// # tokio_test::block_on(async {
    /// assert!(
    ///     Ruling::uuid("f2b9983e-20d4-4d12-9e2c-ec6d9a345787".parse().unwrap())
    ///         .await
    ///         .unwrap()
    ///         .into_stream_buffered(10)
    ///         .map(Result::unwrap)
    ///         .any(|r| future::ready(r.comment == "It must flip like a coin and not like a Frisbee."))
    ///         .await
    /// );
    /// # })
    /// ```
    pub async fn uuid(id: Uuid) -> crate::Result<ListIter<Self>> {
        Uri::from(CARDS_URL.join(&format!("{}/", id))?.join(API_RULING)?)
            .fetch_iter()
            .await
    }
}