Skip to content

Commit

Permalink
Add json converters for recipes
Browse files Browse the repository at this point in the history
  • Loading branch information
sliekens committed Jan 25, 2025
1 parent 4e2bb8e commit 518e892
Show file tree
Hide file tree
Showing 113 changed files with 3,559 additions and 64 deletions.
23 changes: 22 additions & 1 deletion GW2SDK.Tests/Features/Hero/Crafting/Recipes/Recipes.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using GuildWars2.Chat;
using System.Text.Json;
using GuildWars2.Chat;
using GuildWars2.Hero.Crafting.Recipes;
using GuildWars2.Tests.TestInfrastructure;

namespace GuildWars2.Tests.Features.Hero.Crafting.Recipes;
Expand Down Expand Up @@ -44,4 +46,23 @@ public async Task Can_be_enumerated()
Assert.Equal(chatLink.ToString(), chatLinkRoundtrip.ToString());
}
}

[Fact]
public async Task Can_be_serialized()
{
// The JsonLinesHttpMessageHandler simulates the behavior of the real API
// because bulk enumeration quickly exhausts the API rate limit
using var httpClient =
new HttpClient(new JsonLinesHttpMessageHandler("Data/recipes.jsonl.gz"));
var sut = new Gw2Client(httpClient);
await foreach (var original in sut.Hero.Crafting.Recipes
.GetRecipesBulk(cancellationToken: TestContext.Current.CancellationToken)
.ValueOnly(TestContext.Current.CancellationToken))
{
var json = JsonSerializer.Serialize(original);
var roundTrip = JsonSerializer.Deserialize<Recipe>(json);
Assert.IsType(original.GetType(), roundTrip);
Assert.Equal(original, roundTrip);
}
}
}
3 changes: 3 additions & 0 deletions GW2SDK/.filenesting.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"add": {
"Json.cs": [
".cs"
],
"JsonConverter.cs": [
".cs"
]
}
}
Expand Down
5 changes: 4 additions & 1 deletion GW2SDK/Features/Hero/Crafting/GuildIngredient.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
namespace GuildWars2.Hero.Crafting;
using System.Text.Json.Serialization;

namespace GuildWars2.Hero.Crafting;

/// <summary>Information about a guild consumable or decoration ingredient required to craft a guild upgrade.</summary>
[PublicAPI]
[JsonConverter(typeof(GuildIngredientJsonConverter))]
[DataTransferObject]
public sealed record GuildIngredient
{
Expand Down
36 changes: 36 additions & 0 deletions GW2SDK/Features/Hero/Crafting/GuildIngredientJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using GuildWars2.Json;

namespace GuildWars2.Hero.Crafting;

internal sealed class GuildIngredientJsonConverter : JsonConverter<GuildIngredient>
{
public override GuildIngredient Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var json = JsonDocument.ParseValue(ref reader);
return Read(json.RootElement);
}

public override void Write(Utf8JsonWriter writer, GuildIngredient value, JsonSerializerOptions options)
{
Write(writer, value);
}

public static GuildIngredient Read(JsonElement json)
{
return new GuildIngredient
{
UpgradeId = json.GetProperty("upgrade_id").GetInt32(),
Count = json.GetProperty("count").GetInt32()
};
}

public static void Write(Utf8JsonWriter writer, GuildIngredient value)
{
writer.WriteStartObject();
writer.WriteNumber("upgrade_id", value.UpgradeId);
writer.WriteNumber("count", value.Count);
writer.WriteEndObject();
}
}
38 changes: 38 additions & 0 deletions GW2SDK/Features/Hero/Crafting/IngredientJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using GuildWars2.Json;

namespace GuildWars2.Hero.Crafting;

internal sealed class IngredientJsonConverter : JsonConverter<Ingredient>
{
public override Ingredient Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var json = JsonDocument.ParseValue(ref reader);
return Read(json.RootElement);
}

public override void Write(Utf8JsonWriter writer, Ingredient value, JsonSerializerOptions options)
{
Write(writer, value);
}

public static Ingredient Read(JsonElement json)
{
return new Ingredient
{
Kind = json.GetProperty("kind").GetEnum<IngredientKind>(),
Id = json.GetProperty("id").GetInt32(),
Count = json.GetProperty("count").GetInt32()
};
}

public static void Write(Utf8JsonWriter writer, Ingredient value)
{
writer.WriteStartObject();
writer.WriteString("kind", value.Kind.ToString());
writer.WriteNumber("id", value.Id);
writer.WriteNumber("count", value.Count);
writer.WriteEndObject();
}
}
5 changes: 4 additions & 1 deletion GW2SDK/Features/Hero/Crafting/Recipes/AmuletRecipe.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace GuildWars2.Hero.Crafting.Recipes;
using System.Text.Json.Serialization;

namespace GuildWars2.Hero.Crafting.Recipes;

/// <summary>Information about a recipe for crafting an amulet.</summary>
[PublicAPI]
[JsonConverter(typeof(AmuletRecipeJsonConverter))]
public sealed record AmuletRecipe : Recipe;
51 changes: 51 additions & 0 deletions GW2SDK/Features/Hero/Crafting/Recipes/AmuletRecipeJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using GuildWars2.Hero.Crafting.Disciplines;
using GuildWars2.Json;

namespace GuildWars2.Hero.Crafting.Recipes;

internal sealed class AmuletRecipeJsonConverter : JsonConverter<AmuletRecipe>
{
public const string DiscriminatorValue = "amulet_recipe";

public override AmuletRecipe Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var json = JsonDocument.ParseValue(ref reader);
return Read(json.RootElement);
}

public override void Write(Utf8JsonWriter writer, AmuletRecipe value, JsonSerializerOptions options)
{
Write(writer, value);
}

public static AmuletRecipe Read(JsonElement json)
{
if (!json.GetProperty(RecipeJsonConverter.DiscriminatorName).ValueEquals(DiscriminatorValue))
{
ThrowHelper.ThrowInvalidDiscriminator(json.GetProperty(RecipeJsonConverter.DiscriminatorName).GetString());
}

return new AmuletRecipe
{
Id = json.GetProperty("id").GetInt32(),
OutputItemId = json.GetProperty("output_item_id").GetInt32(),
OutputItemCount = json.GetProperty("output_item_count").GetInt32(),
MinRating = json.GetProperty("min_rating").GetInt32(),
TimeToCraft = TimeSpan.FromMilliseconds(json.GetProperty("time_to_craft_ms").GetDouble()),
Disciplines = json.GetProperty("disciplines").GetList(static value => value.GetEnum<CraftingDisciplineName>()),
Flags = RecipeFlagsJsonConverter.Read(json.GetProperty("flags")),
Ingredients = json.GetProperty("ingredients").GetList(IngredientJsonConverter.Read),
ChatLink = json.GetProperty("chat_link").GetStringRequired()
};
}

public static void Write(Utf8JsonWriter writer, AmuletRecipe value)
{
writer.WriteStartObject();
writer.WriteString(RecipeJsonConverter.DiscriminatorName, DiscriminatorValue);
RecipeJsonConverter.WriteCommonProperties(writer, value);
writer.WriteEndObject();
}
}
5 changes: 4 additions & 1 deletion GW2SDK/Features/Hero/Crafting/Recipes/AxeRecipe.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace GuildWars2.Hero.Crafting.Recipes;
using System.Text.Json.Serialization;

namespace GuildWars2.Hero.Crafting.Recipes;

/// <summary>Information about a recipe for crafting an axe.</summary>
[PublicAPI]
[JsonConverter(typeof(AxeRecipeJsonConverter))]
public sealed record AxeRecipe : Recipe;
51 changes: 51 additions & 0 deletions GW2SDK/Features/Hero/Crafting/Recipes/AxeRecipeJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using GuildWars2.Hero.Crafting.Disciplines;
using GuildWars2.Json;

namespace GuildWars2.Hero.Crafting.Recipes;

internal sealed class AxeRecipeJsonConverter : JsonConverter<AxeRecipe>
{
public const string DiscriminatorValue = "axe_recipe";

public override AxeRecipe Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var json = JsonDocument.ParseValue(ref reader);
return Read(json.RootElement);
}

public override void Write(Utf8JsonWriter writer, AxeRecipe value, JsonSerializerOptions options)
{
Write(writer, value);
}

public static AxeRecipe Read(JsonElement json)
{
if (!json.GetProperty(RecipeJsonConverter.DiscriminatorName).ValueEquals(DiscriminatorValue))
{
ThrowHelper.ThrowInvalidDiscriminator(json.GetProperty(RecipeJsonConverter.DiscriminatorName).GetString());
}

return new AxeRecipe
{
Id = json.GetProperty("id").GetInt32(),
OutputItemId = json.GetProperty("output_item_id").GetInt32(),
OutputItemCount = json.GetProperty("output_item_count").GetInt32(),
MinRating = json.GetProperty("min_rating").GetInt32(),
TimeToCraft = TimeSpan.FromMilliseconds(json.GetProperty("time_to_craft_ms").GetDouble()),
Disciplines = json.GetProperty("disciplines").GetList(static value => value.GetEnum<CraftingDisciplineName>()),
Flags = RecipeFlagsJsonConverter.Read(json.GetProperty("flags")),
Ingredients = json.GetProperty("ingredients").GetList(IngredientJsonConverter.Read),
ChatLink = json.GetProperty("chat_link").GetStringRequired()
};
}

public static void Write(Utf8JsonWriter writer, AxeRecipe value)
{
writer.WriteStartObject();
writer.WriteString(RecipeJsonConverter.DiscriminatorName, DiscriminatorValue);
RecipeJsonConverter.WriteCommonProperties(writer, value);
writer.WriteEndObject();
}
}
5 changes: 4 additions & 1 deletion GW2SDK/Features/Hero/Crafting/Recipes/BackpackRecipe.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace GuildWars2.Hero.Crafting.Recipes;
using System.Text.Json.Serialization;

namespace GuildWars2.Hero.Crafting.Recipes;

/// <summary>Information about a recipe for crafting a backpack.</summary>
[PublicAPI]
[JsonConverter(typeof(BackpackRecipeJsonConverter))]
public sealed record BackpackRecipe : Recipe;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using GuildWars2.Hero.Crafting.Disciplines;
using GuildWars2.Json;

namespace GuildWars2.Hero.Crafting.Recipes;

internal sealed class BackpackRecipeJsonConverter : JsonConverter<BackpackRecipe>
{
public const string DiscriminatorValue = "backpack_recipe";

public override BackpackRecipe Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var json = JsonDocument.ParseValue(ref reader);
return Read(json.RootElement);
}

public override void Write(Utf8JsonWriter writer, BackpackRecipe value, JsonSerializerOptions options)
{
Write(writer, value);
}

public static BackpackRecipe Read(JsonElement json)
{
if (!json.GetProperty(RecipeJsonConverter.DiscriminatorName).ValueEquals(DiscriminatorValue))
{
ThrowHelper.ThrowInvalidDiscriminator(json.GetProperty(RecipeJsonConverter.DiscriminatorName).GetString());
}

return new BackpackRecipe
{
Id = json.GetProperty("id").GetInt32(),
OutputItemId = json.GetProperty("output_item_id").GetInt32(),
OutputItemCount = json.GetProperty("output_item_count").GetInt32(),
MinRating = json.GetProperty("min_rating").GetInt32(),
TimeToCraft = TimeSpan.FromMilliseconds(json.GetProperty("time_to_craft_ms").GetDouble()),
Disciplines = json.GetProperty("disciplines").GetList(static value => value.GetEnum<CraftingDisciplineName>()),
Flags = RecipeFlagsJsonConverter.Read(json.GetProperty("flags")),
Ingredients = json.GetProperty("ingredients").GetList(IngredientJsonConverter.Read),
ChatLink = json.GetProperty("chat_link").GetStringRequired()
};
}

public static void Write(Utf8JsonWriter writer, BackpackRecipe value)
{
writer.WriteStartObject();
writer.WriteString(RecipeJsonConverter.DiscriminatorName, DiscriminatorValue);
RecipeJsonConverter.WriteCommonProperties(writer, value);
writer.WriteEndObject();
}
}
5 changes: 4 additions & 1 deletion GW2SDK/Features/Hero/Crafting/Recipes/BagRecipe.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace GuildWars2.Hero.Crafting.Recipes;
using System.Text.Json.Serialization;

namespace GuildWars2.Hero.Crafting.Recipes;

/// <summary>Information about a recipe for crafting a bag.</summary>
[PublicAPI]
[JsonConverter(typeof(BagRecipeJsonConverter))]
public sealed record BagRecipe : Recipe;
51 changes: 51 additions & 0 deletions GW2SDK/Features/Hero/Crafting/Recipes/BagRecipeJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using GuildWars2.Hero.Crafting.Disciplines;
using GuildWars2.Json;

namespace GuildWars2.Hero.Crafting.Recipes;

internal sealed class BagRecipeJsonConverter : JsonConverter<BagRecipe>
{
public const string DiscriminatorValue = "bag_recipe";

public override BagRecipe Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var json = JsonDocument.ParseValue(ref reader);
return Read(json.RootElement);
}

public override void Write(Utf8JsonWriter writer, BagRecipe value, JsonSerializerOptions options)
{
Write(writer, value);
}

public static BagRecipe Read(JsonElement json)
{
if (!json.GetProperty(RecipeJsonConverter.DiscriminatorName).ValueEquals(DiscriminatorValue))
{
ThrowHelper.ThrowInvalidDiscriminator(json.GetProperty(RecipeJsonConverter.DiscriminatorName).GetString());
}

return new BagRecipe
{
Id = json.GetProperty("id").GetInt32(),
OutputItemId = json.GetProperty("output_item_id").GetInt32(),
OutputItemCount = json.GetProperty("output_item_count").GetInt32(),
MinRating = json.GetProperty("min_rating").GetInt32(),
TimeToCraft = TimeSpan.FromMilliseconds(json.GetProperty("time_to_craft_ms").GetDouble()),
Disciplines = json.GetProperty("disciplines").GetList(static value => value.GetEnum<CraftingDisciplineName>()),
Flags = RecipeFlagsJsonConverter.Read(json.GetProperty("flags")),
Ingredients = json.GetProperty("ingredients").GetList(IngredientJsonConverter.Read),
ChatLink = json.GetProperty("chat_link").GetStringRequired()
};
}

public static void Write(Utf8JsonWriter writer, BagRecipe value)
{
writer.WriteStartObject();
writer.WriteString(RecipeJsonConverter.DiscriminatorName, DiscriminatorValue);
RecipeJsonConverter.WriteCommonProperties(writer, value);
writer.WriteEndObject();
}
}
7 changes: 5 additions & 2 deletions GW2SDK/Features/Hero/Crafting/Recipes/BootsRecipe.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace GuildWars2.Hero.Crafting.Recipes;
using System.Text.Json.Serialization;

/// <summary>Information about a recipe for crafting a pair of boots.</summary>
namespace GuildWars2.Hero.Crafting.Recipes;

/// <summary>Information about a recipe for crafting boots.</summary>
[PublicAPI]
[JsonConverter(typeof(BootsRecipeJsonConverter))]
public sealed record BootsRecipe : Recipe;
Loading

0 comments on commit 518e892

Please sign in to comment.