From 8587f60caa1316d6fd62403f1de19808627f9775 Mon Sep 17 00:00:00 2001 From: Ethan Henderson Date: Mon, 12 Feb 2024 21:59:19 -0700 Subject: [PATCH] Add custom domain support (#103) (#108) Squashed commits: - Fix example code in readme - Add example for a custom API domain - Remove unused config variable "apiRegionAsSubdomain" - Removed proxy documentation - Rename ApiURL variable to fit code style - Add support for region as a header parameter, and more specific URL building - Moved RegionConfig to Camille.RiotGames.Enums, and changed its values casing - Remove generated license callout - Correct variable in apiUrl description - Clarify apiUrl description - Move RegionHeaderKey to instead be RegionKey, and use it for the query parameter - Use the new RegionKey variable - Do not branch off of the URL contents - Use RegionKey to build the URL without string checking - Change naming region->route - Simplify query param editing code (.NET is a mess) --- README.md | 430 +++++++++--------- .../Camille.RiotGames.csproj | 14 +- .../gen/RiotGamesApiConfig.cs.dt | 1 + src/Camille.RiotGames/gen/configspec.json | 17 +- .../src/Enums/RouteConfig.cs | 15 + .../src/Util/RegionalRequester.cs | 41 +- 6 files changed, 297 insertions(+), 221 deletions(-) create mode 100644 src/Camille.RiotGames/src/Enums/RouteConfig.cs diff --git a/README.md b/README.md index ff8bd1e6..61672c71 100644 --- a/README.md +++ b/README.md @@ -1,215 +1,215 @@ -# Camille - -[![Github Actions](https://img.shields.io/github/workflow/status/MingweiSamuel/Camille/CI/release/3.x.x?label=release/3.x.x&logo=github&style=flat-square)](https://github.com/MingweiSamuel/Camille/actions?query=workflow%3ACI+branch%3Arelease%2F3.x.x) [![NuGet Stable](https://img.shields.io/nuget/v/Camille.RiotGames.svg?style=flat-square)](https://www.nuget.org/packages/Camille.RiotGames/) [![NuGet Pre Release](https://img.shields.io/nuget/vpre/Camille.RiotGames.svg?style=flat-square)](https://www.nuget.org/packages/Camille.RiotGames/absoluteLatest) [![API Reference](https://img.shields.io/badge/docfx-Camille-brightgreen.svg?style=flat-square)](http://www.mingweisamuel.com/Camille/) - -C# Library for the [Riot Games API](https://developer.riotgames.com/) - -Camille's goals are _speed_, _reliability_, and _maintainability_. Camille handles rate limits and large requests with ease. -Data classes are automatically generated from the -[Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)). - -## Features - -* Fast, asynchronous, thread-safe. -* Automatically retries failed requests. -* Automatic up-to-date nightlies, reflecting Riot API changes within 24 hours. -* Multi-targeted: .NET Standard 1.1+, .NET Framework 4.5+, .NET Core 2.0, .NET Standard 2.1+, .NET Core 3.0+. -* Highly-configurable. -* Riot API LOL-V4, TFT, LOR support. -* C# 8 nullable support. - -## Installation - -[Install via NuGet](https://www.nuget.org/packages?sortby=created-desc&q=Camille&prerel=True) (`Camille.RiotGames`). - -Type the following in the package manager console: - - Install-Package Camille.RiotGames -IncludePrerelease - -Or use the .NET CLI: - - dotnet add package Camille.RiotGames --prerelease - -## Usage - -All API interactions are done using a `RiotApi` instance. -`RiotApi.NewInstance` takes either just an API key (for default settings) or a `IRiotApiConfig` instance (for custom settings). - -```c# -using Camille.Enums; -using Camille.RiotGames; -``` - -```c# -var riotApi = RiotGamesApi.NewInstance("RGAPI-12345678-abcd-1234-abcd-123456abcdef"); -// OR -var riotApi = RiotGamesApi.NewInstance( - new RiotApiConfig.Builder("RGAPI-12345678-abcd-1234-abcd-123456abcdef") - { - MaxConcurrentRequests = 200, - Retries = 10, - // ... - }.Build() -); -``` -You can find all configuration options [here](https://github.com/MingweiSamuel/Camille/blob/gh-pages/v/3.x.x/_gen/Camille.RiotGames/RiotGamesApiConfig.cs). - -API methods are divided up into respective endpoints, corresponding to the left bar of the [API reference](https://developer.riotgames.com/api-methods/). - -### Example - -#### Print Summoner's Top Champions - -```c# -// Get summoners by name synchronously. (Note: async is faster as it allows simultaneous requests). -var summoners = new[] -{ - riotApi.SummonerV4().GetBySummonerName(PlatformRoute.NA1, "jAnna kendrick"), - riotApi.SummonerV4().GetBySummonerName(PlatformRoute.NA1, "lug nuts k") -}; - -foreach (var summoner in summoners) -{ - Console.WriteLine($"{summoner.Name}'s Top 10 Champs:"); - - var masteries = - riotApi.ChampionMasteryV4().GetAllChampionMasteries(PlatformRoute.NA1, summoner.Id); - - for (var i = 0; i < 10; i++) - { - var mastery = masteries[i]; - // Get champion for this mastery. - var champ = (Champion) mastery.ChampionId; - // print i, champ id, champ mastery points, and champ level - Console.WriteLine("{0,3}) {1,-16} {2,10:N0} ({3})", i + 1, champ.ToString(), - mastery.ChampionPoints, mastery.ChampionLevel); - } - Console.WriteLine(); -} -``` - -Output (2022-01-17): -``` -Janna Kendrick's Top 10 Champs: - 1) EKKO 1,803,701 (7) - 2) PYKE 266,410 (7) - 3) SYLAS 193,439 (7) - 4) MASTERYI 134,140 (7) - 5) MORDEKAISER 127,937 (7) - 6) YASUO 93,904 (7) - 7) VIEGO 88,267 (7) - 8) AHRI 82,106 (7) - 9) JINX 76,788 (6) - 10) POPPY 74,982 (7) - -LugnutsK's Top 10 Champs: - 1) ZYRA 841,132 (7) - 2) SORAKA 81,793 (6) - 3) MORGANA 62,917 (5) - 4) SONA 55,073 (6) - 5) NAMI 49,917 (6) - 6) JANNA 46,563 (5) - 7) BRAND 46,401 (5) - 8) TARIC 41,302 (6) - 9) EKKO 40,245 (5) - 10) POPPY 33,859 (5) - ``` - - - -### API Reference - -[Automatically generated API Reference](http://www.mingweisamuel.com/Camille/) - -## Source Code - -Projects are located in [`src/`](https://github.com/MingweiSamuel/Camille/tree/release/3.x.x/src). - -### Generated Classes - -The majority of the code in Camille is automatically generated. The generated sources are not commited to -the master branch, but can be viewed [in the `gh-pages` branch](https://github.com/MingweiSamuel/Camille/tree/gh-pages/v/3.x.x/_gen) -or when building locally. - -The actual code for generating these classes is in the -[`srcgen` folder](https://github.com/MingweiSamuel/Camille/tree/release/3.x.x/srcgen). -The C#-generating code is in `*.cs.dt` files and is written in NodeJS, using -[doT.js templates](https://olado.github.io/doT/index.html). +# Camille + +[![Github Actions](https://img.shields.io/github/workflow/status/MingweiSamuel/Camille/CI/release/3.x.x?label=release/3.x.x&logo=github&style=flat-square)](https://github.com/MingweiSamuel/Camille/actions?query=workflow%3ACI+branch%3Arelease%2F3.x.x) [![NuGet Stable](https://img.shields.io/nuget/v/Camille.RiotGames.svg?style=flat-square)](https://www.nuget.org/packages/Camille.RiotGames/) [![NuGet Pre Release](https://img.shields.io/nuget/vpre/Camille.RiotGames.svg?style=flat-square)](https://www.nuget.org/packages/Camille.RiotGames/absoluteLatest) [![API Reference](https://img.shields.io/badge/docfx-Camille-brightgreen.svg?style=flat-square)](http://www.mingweisamuel.com/Camille/) + +C# Library for the [Riot Games API](https://developer.riotgames.com/) + +Camille's goals are _speed_, _reliability_, and _maintainability_. Camille handles rate limits and large requests with ease. +Data classes are automatically generated from the +[Riot API Reference](https://developer.riotgames.com/api-methods/) ([Swagger](http://www.mingweisamuel.com/riotapi-schema/tool/)). + +## Features + +* Fast, asynchronous, thread-safe. +* Automatically retries failed requests. +* Automatic up-to-date nightlies, reflecting Riot API changes within 24 hours. +* Multi-targeted: .NET Standard 1.1+, .NET Framework 4.5+, .NET Core 2.0, .NET Standard 2.1+, .NET Core 3.0+. +* Highly-configurable. +* Riot API LOL-V4, TFT, LOR support. +* C# 8 nullable support. + +## Installation + +[Install via NuGet](https://www.nuget.org/packages?sortby=created-desc&q=Camille&prerel=True) (`Camille.RiotGames`). + +Type the following in the package manager console: + + Install-Package Camille.RiotGames -IncludePrerelease + +Or use the .NET CLI: + + dotnet add package Camille.RiotGames --prerelease + +## Usage + +All API interactions are done using a `RiotApi` instance. +`RiotApi.NewInstance` takes either just an API key (for default settings) or a `IRiotApiConfig` instance (for custom settings). + +```c# +using Camille.Enums; +using Camille.RiotGames; +``` + +```c# +var riotApi = RiotGamesApi.NewInstance("RGAPI-12345678-abcd-1234-abcd-123456abcdef"); +// OR +var riotApi = RiotGamesApi.NewInstance( + new RiotGamesApiConfig.Builder("RGAPI-12345678-abcd-1234-abcd-123456abcdef") + { + MaxConcurrentRequests = 200, + Retries = 10, + // ... + }.Build() +); +``` +You can find all configuration options [here](https://github.com/MingweiSamuel/Camille/blob/gh-pages/v/3.x.x/_gen/Camille.RiotGames/RiotGamesApiConfig.cs). + +API methods are divided up into respective endpoints, corresponding to the left bar of the [API reference](https://developer.riotgames.com/api-methods/). + +### Example + +#### Print Summoner's Top Champions + +```c# +// Get summoners by name synchronously. (Note: async is faster as it allows simultaneous requests). +var summoners = new[] +{ + riotApi.SummonerV4().GetBySummonerName(PlatformRoute.NA1, "jAnna kendrick"), + riotApi.SummonerV4().GetBySummonerName(PlatformRoute.NA1, "lug nuts k") +}; + +foreach (var summoner in summoners) +{ + Console.WriteLine($"{summoner.Name}'s Top 10 Champs:"); + + var masteries = + riotApi.ChampionMasteryV4().GetAllChampionMasteriesByPUUID(PlatformRoute.NA1, summoner.Puuid); + + for (var i = 0; i < 10; i++) + { + var mastery = masteries[i]; + // Get champion for this mastery. + var champ = (Champion) mastery.ChampionId; + // print i, champ id, champ mastery points, and champ level + Console.WriteLine("{0,3}) {1,-16} {2,10:N0} ({3})", i + 1, champ.ToString(), + mastery.ChampionPoints, mastery.ChampionLevel); + } + Console.WriteLine(); +} +``` + +Output (2022-01-17): +``` +Janna Kendrick's Top 10 Champs: + 1) EKKO 1,803,701 (7) + 2) PYKE 266,410 (7) + 3) SYLAS 193,439 (7) + 4) MASTERYI 134,140 (7) + 5) MORDEKAISER 127,937 (7) + 6) YASUO 93,904 (7) + 7) VIEGO 88,267 (7) + 8) AHRI 82,106 (7) + 9) JINX 76,788 (6) + 10) POPPY 74,982 (7) + +LugnutsK's Top 10 Champs: + 1) ZYRA 841,132 (7) + 2) SORAKA 81,793 (6) + 3) MORGANA 62,917 (5) + 4) SONA 55,073 (6) + 5) NAMI 49,917 (6) + 6) JANNA 46,563 (5) + 7) BRAND 46,401 (5) + 8) TARIC 41,302 (6) + 9) EKKO 40,245 (5) + 10) POPPY 33,859 (5) + ``` + + + +### API Reference + +[Automatically generated API Reference](http://www.mingweisamuel.com/Camille/) + +## Source Code + +Projects are located in [`src/`](https://github.com/MingweiSamuel/Camille/tree/release/3.x.x/src). + +### Generated Classes + +The majority of the code in Camille is automatically generated. The generated sources are not commited to +the master branch, but can be viewed [in the `gh-pages` branch](https://github.com/MingweiSamuel/Camille/tree/gh-pages/v/3.x.x/_gen) +or when building locally. + +The actual code for generating these classes is in the +[`srcgen` folder](https://github.com/MingweiSamuel/Camille/tree/release/3.x.x/srcgen). +The C#-generating code is in `*.cs.dt` files and is written in NodeJS, using +[doT.js templates](https://olado.github.io/doT/index.html). diff --git a/src/Camille.RiotGames/Camille.RiotGames.csproj b/src/Camille.RiotGames/Camille.RiotGames.csproj index 87b3bfd6..2ba01893 100644 --- a/src/Camille.RiotGames/Camille.RiotGames.csproj +++ b/src/Camille.RiotGames/Camille.RiotGames.csproj @@ -2,7 +2,7 @@ - netstandard1.1;net45;net461;netstandard2.1;netcoreapp3.1;net6.0;net7.0 + net45;net461;netstandard2.1;netcoreapp3.1;net6.0;net7.0 Camille.RiotGames Camille.RiotGames Riot Games API library. Fully rate limited, automatic retrying, thread-safe. Up-to-date nightlies. @@ -17,6 +17,18 @@ + + + $(NuGetPackageRoot)\microsoft.netframework.referenceassemblies.net45\1.0.3\build\.NETFramework\v4.5\System.Web.dll + + + $(NuGetPackageRoot)\microsoft.netframework.referenceassemblies.net461\1.0.3\build\.NETFramework\v4.6.1\System.Web.dll + + + $(NuGetPackageRoot)\microsoft.netcore.app.ref\3.1.0\ref\netcoreapp3.1\System.Web.dll + + + diff --git a/src/Camille.RiotGames/gen/RiotGamesApiConfig.cs.dt b/src/Camille.RiotGames/gen/RiotGamesApiConfig.cs.dt index b04bf1c5..af5c6534 100644 --- a/src/Camille.RiotGames/gen/RiotGamesApiConfig.cs.dt +++ b/src/Camille.RiotGames/gen/RiotGamesApiConfig.cs.dt @@ -6,6 +6,7 @@ // Do not directly edit. // Generated on {{= (new Date).toISOString() }} using System; +using Camille.RiotGames.Enums; namespace Camille.RiotGames { diff --git a/src/Camille.RiotGames/gen/configspec.json b/src/Camille.RiotGames/gen/configspec.json index ca2269dc..c5bafaba 100644 --- a/src/Camille.RiotGames/gen/configspec.json +++ b/src/Camille.RiotGames/gen/configspec.json @@ -3,6 +3,21 @@ "type": "string", "desc": "Riot Games API key." }, + "apiUrl": { + "type": "string", + "desc": "URL to call for the Riot Games API. Include {0} for the region/route, if you want it in the URL - Not necessary if apiCallRouteConfig is RouteConfig.InUrlAsRouteQueryParameter.", + "val": "\"https://{0}.api.riotgames.com\"" + }, + "apiRouteConfig": { + "type": "RouteConfig", + "desc": "How the API call should include the region/route.", + "val": "RouteConfig.InUrl" + }, + "routeKey": { + "type": "string", + "desc": "The query or header name to use for the region/route. Only used if RouteConfig is set to InHeader or InQueryParam.", + "val": "\"Riot-Region\"" + }, "maxConcurrentRequests": { "type": "int", "desc": "Maximum number of concurrent requests allowed.", @@ -33,4 +48,4 @@ "desc": "A backoff strategy for 429s with missing Retry-After headers. Returns seconds.", "val": "(retries, num429s, num5xxs) => 0.5 + Math.Pow(2, num5xxs + num429s)" } -} \ No newline at end of file +} diff --git a/src/Camille.RiotGames/src/Enums/RouteConfig.cs b/src/Camille.RiotGames/src/Enums/RouteConfig.cs new file mode 100644 index 00000000..55106e8b --- /dev/null +++ b/src/Camille.RiotGames/src/Enums/RouteConfig.cs @@ -0,0 +1,15 @@ +namespace Camille.RiotGames.Enums +{ + public enum RouteConfig + { + /// The Region should be in the URL, inserted where "{0}" is (using string.Format) + InUrl, + + /// The Region should be in the URL query parameters + InQueryParam, + + /// The Region should be a header instead of in the URL + /// + InHeader, + } +} diff --git a/src/Camille.RiotGames/src/Util/RegionalRequester.cs b/src/Camille.RiotGames/src/Util/RegionalRequester.cs index 077cc70c..79b8fdc5 100644 --- a/src/Camille.RiotGames/src/Util/RegionalRequester.cs +++ b/src/Camille.RiotGames/src/Util/RegionalRequester.cs @@ -4,8 +4,9 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using System.Web; using Camille.Core; -using Camille.Enums; +using Camille.RiotGames.Enums; namespace Camille.RiotGames.Util { @@ -16,8 +17,6 @@ namespace Camille.RiotGames.Util /// public class RegionalRequester { - /// Root url for Riot API requests. - private const string RiotRootUrl = ".api.riotgames.com"; // TODO: configure in settings? /// Request header name for the Riot API key. private const string RiotKeyHeader = "X-Riot-Token"; @@ -37,12 +36,35 @@ public class RegionalRequester /// private readonly HttpClient _client = new HttpClient(); + /// + /// The region/route for this requester. + /// + private string _route; + public RegionalRequester(IRiotGamesApiConfig config, string route) { _config = config; _appRateLimit = new RateLimit(RateLimitType.Application, config); + _route = route; + + // If the region is to be in the url. + if (_config.ApiRouteConfig == RouteConfig.InUrl) + { + _client.BaseAddress = new Uri(string.Format(config.ApiUrl, route)); + } + // If the region is to be in the url query parameters or headers. + else + { + _client.BaseAddress = new Uri(config.ApiUrl); + } - _client.BaseAddress = new Uri($"https://{route}{RiotRootUrl}"); + // If the region is to be in the header. + if (_config.ApiRouteConfig == RouteConfig.InHeader) + { + _client.DefaultRequestHeaders.Add(config.RouteKey, route); + } + + // Include the API key (can set config.ApiKey to "" for use with proxies). _client.DefaultRequestHeaders.Add(RiotKeyHeader, config.ApiKey); } @@ -63,10 +85,21 @@ public RegionalRequester(IRiotGamesApiConfig config, string route) public async Task Send(string methodId, HttpRequestMessage request, CancellationToken token, bool ignoreAppRateLimits) { + // If the route is in the query param we need to add it to each request. + if (_config.ApiRouteConfig == RouteConfig.InQueryParam) + { + // Append the route as a query parameter. + var query = HttpUtility.ParseQueryString(request.RequestUri.Query); + query.Add(_config.RouteKey, _route); + // Use path-only URL, scheme/host is in _client.BaseAddress. + request.RequestUri = new Uri($"{request.RequestUri.AbsolutePath}?{query}", UriKind.RelativeOrAbsolute); + } + HttpResponseMessage? response = null; var retries = 0; var num429s = 0; var num5xxs = 0; + for (; retries <= _config.Retries; retries++) { // Get token.