Skip to content

Commit 9ead175

Browse files
committed
Handle API errors better, add search options, async optimizations,
1 parent 16b1e33 commit 9ead175

File tree

16 files changed

+268
-78
lines changed

16 files changed

+268
-78
lines changed

ScryfallApi.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScryfallApi.Client", "src\S
77
EndProject
88
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{C470746D-B24E-4759-B7A3-6C8EDD801544}"
99
EndProject
10-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScryfallApi.WebSample", "samples\ScryfallApi.WebSample\ScryfallApi.WebSample.csproj", "{383BF6C1-47B7-47E8-8428-B3A114EFC3B0}"
10+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScryfallApi.WebSample", "samples\ScryfallApi.WebSample\ScryfallApi.WebSample.csproj", "{383BF6C1-47B7-47E8-8428-B3A114EFC3B0}"
1111
EndProject
1212
Global
1313
GlobalSection(SolutionConfigurationPlatforms) = preSolution

samples/ScryfallApi.WebSample/Pages/Search.cshtml

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,46 @@
77
<div class="row">
88
<div class="col-md-12">
99
<p>
10-
Search Query:
10+
Search Query:
1111
</p>
1212
<div asp-validation-summary="All"></div>
1313
<form method="POST">
14-
<div>Name: <input asp-for="Query" />
14+
<div>
15+
Name: <input asp-for="Query" />
1516
<input type="submit" value="Search" />
16-
</div>
17+
</div>
1718
</form>
1819
</div>
1920
</div>
20-
@if(@Model.Results != null)
21+
@if (@Model?.Results?.Data != null && Model.Results.Data.Count > 0)
2122
{
22-
@foreach(var card in @Model.Results?.Data)
23-
{
24-
<h3>Result: <a href="@card.ScryfallUri">@card.Name</a></h3>
25-
<div class="row">
26-
<div class="col-md-12">
27-
<a href="@card.ScryfallUri">
28-
@if (card.Layout.Equals("transform", StringComparison.OrdinalIgnoreCase))
23+
@foreach (var card in @Model.Results?.Data)
24+
{
25+
<h3>Result: <a href="@card.ScryfallUri">@card.Name</a></h3>
26+
<div class="row">
27+
<div class="col-md-12">
28+
<a href="@card.ScryfallUri">
29+
@if (card.Layout.Equals("transform", StringComparison.OrdinalIgnoreCase))
30+
{
31+
foreach (var face in card.CardFaces)
2932
{
30-
foreach (var face in card.CardFaces)
31-
{
32-
<img src="@face.ImageUris["normal"]" style="max-width: 320px;" />
33-
}
33+
<img src="@face.ImageUris["normal"]" style="max-width: 320px;" />
3434
}
35-
else
36-
{
37-
<img src="@card.ImageUris["normal"]" style="max-width: 320px;" />
38-
}
39-
</a>
40-
</div>
41-
</div>
42-
}
43-
}
35+
}
36+
else
37+
{
38+
<img src="@card.ImageUris["normal"]" style="max-width: 320px;" />
39+
}
40+
</a>
41+
</div>
42+
</div>
43+
}
44+
}
45+
else
46+
{
47+
<div class="row">
48+
<div class="col-sm-12">
49+
<p>No cards found!</p>
50+
</div>
51+
</div>
52+
}

samples/ScryfallApi.WebSample/Pages/Search.cshtml.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
42
using System.Threading.Tasks;
53
using Microsoft.AspNetCore.Mvc;
64
using Microsoft.AspNetCore.Mvc.RazorPages;
@@ -17,7 +15,7 @@ public class SearchModel : PageModel
1715
public ResultList<Card> Results { get; set; }
1816

1917
[BindProperty]
20-
public string Query {get; set;}
18+
public string Query { get; set; }
2119

2220
public SearchModel(ScryfallApiClient scryfallApi)
2321
{
@@ -30,7 +28,13 @@ public void OnGet()
3028

3129
public async Task<ActionResult> OnPostAsync()
3230
{
33-
Results = await _scryfallApi.Cards.Search(Query, 1, CardSort.Cmc );
31+
var options = new SearchOptions
32+
{
33+
Sort = SearchOptions.CardSort.Cmc,
34+
Direction = SearchOptions.SortDirection.Desc
35+
};
36+
37+
Results = await _scryfallApi.Cards.Search(Query, 1, options);
3438

3539
return Page();
3640
}

src/ScryfallApi.Client/Apis/BaseRestService.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.Extensions.Caching.Memory;
55
using Microsoft.Extensions.Logging;
66
using Newtonsoft.Json;
7+
using ScryfallApi.Client.Models;
78

89
namespace ScryfallApi.Client.Apis
910
{
@@ -20,7 +21,7 @@ protected BaseRestService(HttpClient httpClient, ILogger logger = null, IMemoryC
2021
_cache = cache;
2122
}
2223

23-
protected async Task<T> GetAsync<T>(string resourceUrl, bool useCache = true)
24+
protected async Task<T> GetAsync<T>(string resourceUrl, bool useCache = true) where T : BaseItem
2425
{
2526
if (string.IsNullOrWhiteSpace(resourceUrl))
2627
throw new ArgumentNullException(nameof(resourceUrl));
@@ -42,13 +43,17 @@ protected async Task<T> GetAsync<T>(string resourceUrl, bool useCache = true)
4243
_logger?.LogDebug("Retrieving URI: {0}", cacheKey);
4344
var response = await _httpClient.GetAsync(resourceUrl).ConfigureAwait(false);
4445
_logger?.LogDebug("Response code: {0}", response.StatusCode);
45-
if (!response.IsSuccessStatusCode)
46-
throw new HttpRequestException(); // TODO: Return a message
4746

4847
var json = await response.Content.ReadAsStringAsync();
4948
_logger?.LogTrace("Response text: {0}", json);
50-
// TODO: The scryfall API can sometimes return an Error object, detect it and throw
49+
5150
var obj = JsonConvert.DeserializeObject<T>(json);
51+
if (obj.Object.Equals("error", StringComparison.OrdinalIgnoreCase))
52+
{
53+
obj.Error = JsonConvert.DeserializeObject<Error>(json);
54+
return obj;
55+
}
56+
5257
_cache?.Set(_httpClient.BaseAddress.AbsoluteUri + resourceUrl, obj);
5358

5459
return obj;
Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
1-
using System;
2-
using System.Net.Http;
1+
using System.Net.Http;
32
using System.Threading.Tasks;
3+
using System.Web;
44
using Microsoft.Extensions.Caching.Memory;
55
using Microsoft.Extensions.Logging;
66
using ScryfallApi.Client.Models;
7+
using static ScryfallApi.Client.Models.SearchOptions;
78

89
namespace ScryfallApi.Client.Apis
910
{
1011
public class Cards : BaseRestService, ICards
1112
{
1213
internal Cards(HttpClient httpClient, ILogger logger, IMemoryCache cache = null) : base(httpClient, logger, cache) { }
1314

14-
public async Task<ResultList<Card>> Get(int page) => await GetAsync<ResultList<Card>>($"/cards?page={page}");
15-
public async Task<Card> GetRandom() => await GetAsync<Card>($"/cards/random", false);
15+
public Task<ResultList<Card>> Get(int page) => GetAsync<ResultList<Card>>($"/cards?page={page}");
1616

17-
//TODO : Add more search options,
18-
public async Task<ResultList<Card>> Search(string query, int page, CardSort sort = CardSort.Cmc) => await GetAsync<ResultList<Card>>($"/cards/search?q={query}&page={page}&order={sort.ToString().ToLowerInvariant()}");
17+
public Task<Card> GetRandom() => GetAsync<Card>($"/cards/random", false);
18+
19+
public Task<ResultList<Card>> Search(string query, int page, CardSort sort) =>
20+
Search(query, page, new SearchOptions { Sort = sort });
21+
22+
public Task<ResultList<Card>> Search(string query, int page, SearchOptions options = default)
23+
{
24+
if (page < 1) page = 1;
25+
query = HttpUtility.UrlEncode(query);
26+
return GetAsync<ResultList<Card>>($"/cards/search?q={query}&page={page}&{options.BuildQueryString()}");
27+
}
1928
}
2029
}

src/ScryfallApi.Client/Apis/Catalogs.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ public class Catalogs : BaseRestService, ICatalogs
1010
{
1111
internal Catalogs(HttpClient httpClient, ILogger logger, IMemoryCache cache = null) : base(httpClient, logger, cache) { }
1212

13-
public async Task<string[]> ListCardNames() => (await GetAsync<Catalog>("/catalog/card-names")).Data;
14-
public async Task<string[]> ListWordBank() => (await GetAsync<Catalog>("/catalog/word-bank")).Data;
15-
public async Task<string[]> ListCreatureTypes() => (await GetAsync<Catalog>("/catalog/creature-types")).Data;
16-
public async Task<string[]> ListPlaneswalkerTypes() => (await GetAsync<Catalog>("/catalog/planeswalker-types")).Data;
17-
public async Task<string[]> ListLandTypes() => (await GetAsync<Catalog>("/catalog/land-types")).Data;
18-
public async Task<string[]> ListSpellTypes() => (await GetAsync<Catalog>("/catalog/spell-types")).Data;
19-
public async Task<string[]> ListEnchantmentTypes() => (await GetAsync<Catalog>("/catalog/enchantment-types")).Data;
20-
public async Task<string[]> ListArtifactTypes() => (await GetAsync<Catalog>("/catalog/artifact-types")).Data;
21-
public async Task<string[]> ListPowers() => (await GetAsync<Catalog>("/catalog/powers")).Data;
22-
public async Task<string[]> ListToughnesses() => (await GetAsync<Catalog>("/catalog/toughnesses")).Data;
23-
public async Task<string[]> ListLoyalties() => (await GetAsync<Catalog>("/catalog/loyalties")).Data;
24-
public async Task<string[]> ListWatermarks() => (await GetAsync<Catalog>("/catalog/watermarks")).Data;
13+
public async Task<string[]> ListCardNames() => (await GetAsync<Catalog>("/catalog/card-names").ConfigureAwait(false)).Data;
14+
public async Task<string[]> ListWordBank() => (await GetAsync<Catalog>("/catalog/word-bank").ConfigureAwait(false)).Data;
15+
public async Task<string[]> ListCreatureTypes() => (await GetAsync<Catalog>("/catalog/creature-types").ConfigureAwait(false)).Data;
16+
public async Task<string[]> ListPlaneswalkerTypes() => (await GetAsync<Catalog>("/catalog/planeswalker-types").ConfigureAwait(false)).Data;
17+
public async Task<string[]> ListLandTypes() => (await GetAsync<Catalog>("/catalog/land-types").ConfigureAwait(false)).Data;
18+
public async Task<string[]> ListSpellTypes() => (await GetAsync<Catalog>("/catalog/spell-types").ConfigureAwait(false)).Data;
19+
public async Task<string[]> ListEnchantmentTypes() => (await GetAsync<Catalog>("/catalog/enchantment-types").ConfigureAwait(false)).Data;
20+
public async Task<string[]> ListArtifactTypes() => (await GetAsync<Catalog>("/catalog/artifact-types").ConfigureAwait(false)).Data;
21+
public async Task<string[]> ListPowers() => (await GetAsync<Catalog>("/catalog/powers").ConfigureAwait(false)).Data;
22+
public async Task<string[]> ListToughnesses() => (await GetAsync<Catalog>("/catalog/toughnesses").ConfigureAwait(false)).Data;
23+
public async Task<string[]> ListLoyalties() => (await GetAsync<Catalog>("/catalog/loyalties").ConfigureAwait(false)).Data;
24+
public async Task<string[]> ListWatermarks() => (await GetAsync<Catalog>("/catalog/watermarks").ConfigureAwait(false)).Data;
2525
}
2626
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
using System.Threading.Tasks;
22
using ScryfallApi.Client.Models;
3+
using static ScryfallApi.Client.Models.SearchOptions;
34

45
namespace ScryfallApi.Client.Apis
56
{
67
public interface ICards
78
{
9+
Task<Card> GetRandom();
810
Task<ResultList<Card>> Get(int page);
911
Task<ResultList<Card>> Search(string query, int page, CardSort sort);
10-
Task<Card> GetRandom();
12+
Task<ResultList<Card>> Search(string query, int page, SearchOptions options);
1113
}
1214
}

src/ScryfallApi.Client/Apis/Sets.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ internal Sets(HttpClient httpClient, ILogger logger, IMemoryCache cache = null)
1414
/// Returns a List object of all Sets on Scryfall.
1515
/// </summary>
1616
/// <returns></returns>
17-
public async Task<ResultList<Set>> Get() => await GetAsync<ResultList<Set>>("/sets");
17+
public Task<ResultList<Set>> Get() => GetAsync<ResultList<Set>>("/sets");
1818

1919
/// <summary>
2020
/// Returns a Set with the given set code. The code can be either the code or the mtgo_code for the set.
2121
/// </summary>
2222
/// <param name="setCode"></param>
2323
/// <returns></returns>
24-
public async Task<Set> Get(string setCode) => await GetAsync<Set>($"/sets/{setCode}");
24+
public Task<Set> Get(string setCode) => GetAsync<Set>($"/sets/{setCode}");
2525
}
2626
}

src/ScryfallApi.Client/Apis/Symbology.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ internal Symbology(HttpClient httpClient, ILogger logger, IMemoryCache cache = n
1414
/// Retrieve all card symbols
1515
/// </summary>
1616
/// <returns></returns>
17-
public async Task<ResultList<Symbol>> Get() => await GetAsync<ResultList<Symbol>>("/symbology");
17+
public Task<ResultList<Symbol>> Get() => GetAsync<ResultList<Symbol>>("/symbology");
1818

1919
/// <summary>
2020
/// Parses the given mana cost parameter and returns Scryfall’s interpretation.
2121
/// </summary>
2222
/// <returns></returns>
23-
public async Task<ManaCost> ParseMana(string cost) => await GetAsync<ManaCost>("/symbology/parse-mana");
23+
public Task<ManaCost> ParseMana(string cost) => GetAsync<ManaCost>("/symbology/parse-mana");
2424
}
2525
}

src/ScryfallApi.Client/Models/BaseItem.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ public abstract class BaseItem
66
{
77
[JsonProperty("object")]
88
public string Object { get; set; }
9+
10+
internal Error Error { get; set; }
911
}
1012
}

0 commit comments

Comments
 (0)