diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..aabf43e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,27 @@ +# Mark generated files +src/ArcadeDotnet/Models/** linguist-generated=true +src/ArcadeDotnet/Core/ModelBase.cs linguist-generated=true +src/ArcadeDotnet/Core/ModelConverter.cs linguist-generated=true +src/ArcadeDotnet/Core/ParamsBase.cs linguist-generated=true + +# Line endings - preserve original for generated code +src/ArcadeDotnet/Models/** -text +src/ArcadeDotnet/Core/ModelBase.cs -text +src/ArcadeDotnet/Core/ModelConverter.cs -text +src/ArcadeDotnet/Core/ParamsBase.cs -text +src/ArcadeDotnet/Services/** -text + +# New code uses LF +src/ArcadeDotnet/Extensions/** text eol=lf +src/ArcadeDotnet.Tests/Extensions/** text eol=lf +examples/** text eol=lf +docs/** text eol=lf +.github/** text eol=lf + +# Binary files +*.dll binary +*.exe binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..08994db --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +## Description + +## Type of Change +- [ ] Bug fix +- [ ] New feature +- [ ] Breaking change +- [ ] Documentation + +## Testing +- [ ] Tests pass +- [ ] Build succeeds + +## Checklist +- [ ] Code formatted (`dotnet format`) +- [ ] Documentation updated diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c42a16b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + exclude: ^\.devcontainer/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 53a8496..2f58063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 0.2.0 (2025-01-XX) + +### Breaking Changes + +* **Client Configuration**: Constructor now requires `ArcadeClientOptions` instead of object initializer syntax +* **HttpClient**: Removed from public API. Inject via `ArcadeClientOptions.HttpClient` for dependency injection support +* **Type Names**: Renamed `HttpRequest`/`HttpResponse` → `ArcadeRequest`/`ArcadeResponse` to avoid ASP.NET naming conflicts + +### Features + +* **ArcadeClientOptions**: New strongly-typed configuration class with environment variable support +* **ArcadeClientFactory**: Convenient factory methods with shared HttpClient instance +* **Parameterless Constructor**: Creates client using `ARCADE_API_KEY` and `ARCADE_BASE_URL` environment variables +* **XML Documentation**: Comprehensive documentation added to all public APIs + +### Improvements + +* Applied modern C# 12 patterns (primary constructors, expression-bodied members, string interpolation) +* Added 69 behavior-focused unit tests covering edge cases and architectural validation +* Proper dependency injection support for `HttpClient` +* All exception types now sealed with XML documentation +* Improved separation of concerns and architectural patterns + ## 0.1.0 (2025-10-29) Full Changelog: [v0.0.1...v0.1.0](https://github.com/ArcadeAI/arcade-dotnet/compare/v0.0.1...v0.1.0) diff --git a/README.md b/README.md index 44d2c11..951059e 100644 --- a/README.md +++ b/README.md @@ -30,19 +30,79 @@ This library requires .NET 8 or later. See the [`examples`](examples) directory for complete and runnable examples. +### Execute a Tool + +**Simple tool (no OAuth):** ```csharp -using System; using ArcadeDotnet; using ArcadeDotnet.Models.Tools; -// Configured using the ARCADE_API_KEY and ARCADE_BASE_URL environment variables -ArcadeClient client = new(); +var client = new ArcadeClient(); + +var executeParams = new ToolExecuteParams +{ + ToolName = "CheckArcadeEngineHealth" // Example: simple tool +}; + +var result = await client.Tools.Execute(executeParams); +result.Validate(); +Console.WriteLine($"Execution ID: {result.ExecutionID}"); +Console.WriteLine($"Status: {result.Status}"); +``` + +**Tool requiring OAuth (e.g., GitHub):** +```csharp +// Step 1: Authorize the tool +var authResponse = await client.Tools.Authorize(new ToolAuthorizeParams +{ + ToolName = "GitHub.ListRepositories" +}); + +// Step 2: After OAuth completes, execute with UserID +var executeParams = new ToolExecuteParams +{ + ToolName = "GitHub.ListRepositories", + UserID = authResponse.UserID // From authorization response +}; + +var result = await client.Tools.Execute(executeParams); +``` + +### List Available Tools + +```csharp +using ArcadeDotnet; -ToolExecuteParams parameters = new() { ToolName = "Google.ListEmails" }; +var client = new ArcadeClient(); +var tools = await client.Tools.List(); +tools.Validate(); +Console.WriteLine($"Found {tools.Items?.Count ?? 0} tools"); +``` + +### With Options + +```csharp +using ArcadeDotnet; +using System.Net.Http; + +var client = new ArcadeClient(new ArcadeClientOptions +{ + ApiKey = "your-api-key", + BaseUrl = new Uri("https://api.arcade.dev"), + HttpClient = new HttpClient() // Optional: inject your own HttpClient +}); +``` -var executeToolResponse = await client.Tools.Execute(parameters); +### Using Factory -Console.WriteLine(executeToolResponse); +```csharp +using ArcadeDotnet; + +// Factory method with shared HttpClient +var client = ArcadeClientFactory.Create("your-api-key"); + +// Or using environment variables +var clientFromEnv = ArcadeClientFactory.Create(); ``` ## Client Configuration @@ -53,25 +113,30 @@ Configure the client using environment variables: using ArcadeDotnet; // Configured using the ARCADE_API_KEY and ARCADE_BASE_URL environment variables -ArcadeClient client = new(); +var client = new ArcadeClient(); ``` -Or manually: +Or with explicit options: ```csharp using ArcadeDotnet; - -ArcadeClient client = new() { APIKey = "My API Key" }; +using System.Net.Http; + +var client = new ArcadeClient(new ArcadeClientOptions +{ + ApiKey = "your-api-key", + BaseUrl = new Uri("https://api.arcade.dev"), + HttpClient = new HttpClient() // Optional +}); ``` -Or using a combination of the two approaches. - See this table for the available options: -| Property | Environment variable | Required | Default value | -| --------- | -------------------- | -------- | -------------------------- | -| `APIKey` | `ARCADE_API_KEY` | true | - | -| `BaseUrl` | `ARCADE_BASE_URL` | true | `"https://api.arcade.dev"` | +| Property | Environment variable | Required | Default value | +| ------------ | ------------------- | -------- | ------------------------- | +| `ApiKey` | `ARCADE_API_KEY` | true | - | +| `BaseUrl` | `ARCADE_BASE_URL` | false | `"https://api.arcade.dev"` | +| `HttpClient` | - | false | New instance created | ## Requests and responses diff --git a/examples/AspNetCoreExample/AspNetCoreExample.csproj b/examples/AspNetCoreExample/AspNetCoreExample.csproj new file mode 100644 index 0000000..9fc90b8 --- /dev/null +++ b/examples/AspNetCoreExample/AspNetCoreExample.csproj @@ -0,0 +1,9 @@ + + + net8.0 + enable + + + + + diff --git a/examples/AspNetCoreExample/Program.cs b/examples/AspNetCoreExample/Program.cs new file mode 100644 index 0000000..bb32b3d --- /dev/null +++ b/examples/AspNetCoreExample/Program.cs @@ -0,0 +1,27 @@ +using ArcadeDotnet; +using ArcadeDotnet.Extensions; +using Microsoft.AspNetCore.Mvc; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddArcadeClient( + builder.Configuration["Arcade:ApiKey"] ?? throw new InvalidOperationException("Arcade:ApiKey not configured") +); + +var app = builder.Build(); + +app.MapGet("/tools", async (IArcadeClient arcade) => +{ + var tools = await arcade.Tools.List(); + tools.Validate(); + return Results.Ok(new { count = tools.Items?.Count ?? 0 }); +}); + +app.MapGet("/health", async (IArcadeClient arcade) => +{ + var health = await arcade.Health.Check(); + health.Validate(); + return Results.Ok(new { healthy = health.Healthy }); +}); + +app.Run(); diff --git a/examples/AspNetCoreExample/appsettings.json b/examples/AspNetCoreExample/appsettings.json new file mode 100644 index 0000000..c380ef8 --- /dev/null +++ b/examples/AspNetCoreExample/appsettings.json @@ -0,0 +1,5 @@ +{ + "Arcade": { + "ApiKey": "your-api-key-here" + } +} diff --git a/examples/BasicExample/BasicExample.csproj b/examples/BasicExample/BasicExample.csproj new file mode 100644 index 0000000..871847b --- /dev/null +++ b/examples/BasicExample/BasicExample.csproj @@ -0,0 +1,14 @@ + + + + + + + + Exe + net8.0 + enable + enable + + + diff --git a/examples/BasicExample/Program.cs b/examples/BasicExample/Program.cs new file mode 100644 index 0000000..026dc27 --- /dev/null +++ b/examples/BasicExample/Program.cs @@ -0,0 +1,154 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using ArcadeDotnet; +using ArcadeDotnet.Models.Tools; +using ArcadeDotnet.Exceptions; +using ArcadeDotnet.Models; + +namespace Examples; + +/// +/// Basic example demonstrating how to use the Arcade SDK. +/// +class Program +{ + static async Task Main(string[] args) + { + // Create client using environment variables + var client = new ArcadeClient(); + Console.WriteLine($"Connected to: {client.BaseUrl}\n"); + + // Example 1: Execute a Simple Tool (No OAuth Required) + Console.WriteLine("=== Example 1: Execute Simple Tool (No OAuth) ==="); + Console.WriteLine(" Note: Most tools require OAuth. This example shows the pattern."); + Console.WriteLine(" For a working example, use a tool that doesn't require authentication."); + try + { + // Example: Execute a tool (this will likely require UserID for most tools) + // In practice, you'd use a tool that doesn't need OAuth like math operations + var executeParams = new ToolExecuteParams + { + ToolName = "CheckArcadeEngineHealth", // Example tool name + // UserID = "user-id" // Required for most tools + }; + + var result = await client.Tools.Execute(executeParams); + result.Validate(); + + Console.WriteLine($"✅ Tool executed successfully!"); + Console.WriteLine($" Execution ID: {result.ExecutionID}"); + Console.WriteLine($" Status: {result.Status}"); + Console.WriteLine($" Success: {result.Success}"); + } + catch (ArcadeBadRequestException ex) + { + Console.WriteLine($" ⚠️ Expected: Most tools require UserID or specific parameters"); + Console.WriteLine($" Error: {ex.Message}"); + Console.WriteLine($" Tip: Use Tools.Authorize() first for OAuth tools"); + } + catch (ArcadeNotFoundException ex) + { + Console.WriteLine($"❌ Tool not found: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"❌ Error: {ex.GetType().Name}: {ex.Message}"); + } + + // Example 2: Execute Tool Requiring OAuth (GitHub Example) + Console.WriteLine("\n=== Example 2: Tool Requiring OAuth (GitHub) ==="); + try + { + // For tools requiring OAuth, you need to authorize first + // This example shows the pattern (GitHub tools require OAuth) + var authorizeParams = new ToolAuthorizeParams + { + ToolName = "GitHub.ListRepositories" // Example GitHub tool + }; + + Console.WriteLine(" Authorizing tool access..."); + var authResponse = await client.Tools.Authorize(authorizeParams); + authResponse.Validate(); + + Console.WriteLine($" ✅ Authorization initiated!"); + if (authResponse.Status != null) + { + Console.WriteLine($" Status: {authResponse.Status.Value}"); + } + if (!string.IsNullOrEmpty(authResponse.URL)) + { + Console.WriteLine($" OAuth URL: {authResponse.URL}"); + } + Console.WriteLine($" Note: Complete OAuth flow, then use UserID in Execute()"); + + // After OAuth completes, execute with UserID: + // var executeParams = new ToolExecuteParams + // { + // ToolName = "GitHub.ListRepositories", + // UserID = "user-id-from-oauth-flow" + // }; + } + catch (ArcadeNotFoundException ex) + { + Console.WriteLine($" ⚠️ Tool not found (this is expected if GitHub tools aren't available)"); + Console.WriteLine($" Error: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($" ⚠️ Error: {ex.GetType().Name}: {ex.Message}"); + Console.WriteLine(" Note: This demonstrates the OAuth authorization pattern"); + } + + // Example 3: List Available Tools + Console.WriteLine("\n=== Example 3: List Available Tools ==="); + try + { + var tools = await client.Tools.List(); + tools.Validate(); + var count = tools.Items?.Count ?? 0; + Console.WriteLine($"✅ Found {count} available tools"); + + if (tools.Items != null && tools.Items.Count > 0) + { + Console.WriteLine($" First tool: {tools.Items[0].Name}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"❌ Error: {ex.GetType().Name}: {ex.Message}"); + } + + // Example 4: Get Tool Details + Console.WriteLine("\n=== Example 4: Get Tool Details ==="); + try + { + var toolParams = new ToolGetParams { Name = "Google.ListEmails" }; + var tool = await client.Tools.Get(toolParams); + tool.Validate(); + Console.WriteLine($"✅ Tool retrieved: {tool.Name}"); + Console.WriteLine($" Description: {tool.Description ?? "N/A"}"); + } + catch (ArcadeNotFoundException ex) + { + Console.WriteLine($"❌ Tool not found: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"❌ Error: {ex.GetType().Name}: {ex.Message}"); + } + + // Example 5: Health Check + Console.WriteLine("\n=== Example 5: Health Check ==="); + try + { + var health = await client.Health.Check(); + health.Validate(); + Console.WriteLine($"✅ Health check passed: {health.Healthy}"); + } + catch (Exception ex) + { + Console.WriteLine($"❌ Error: {ex.GetType().Name}: {ex.Message}"); + } + } +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..dfef402 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,27 @@ +# Examples + +This directory contains runnable examples demonstrating how to use the Arcade C# SDK. + +## BasicExample + +Demonstrates basic SDK usage including: +- Creating clients with different configuration methods +- Using environment variables +- Using `ArcadeClientOptions` +- Using `ArcadeClientFactory` +- Listing tools +- Health checks + +### Running + +```bash +cd BasicExample +export ARCADE_API_KEY="your-api-key" +dotnet run +``` + +## Requirements + +- .NET 8 SDK +- Valid Arcade API key (set via `ARCADE_API_KEY` environment variable) + diff --git a/src/ArcadeDotnet.Tests/ArcadeClientEdgeCasesTest.cs b/src/ArcadeDotnet.Tests/ArcadeClientEdgeCasesTest.cs new file mode 100644 index 0000000..bd17910 --- /dev/null +++ b/src/ArcadeDotnet.Tests/ArcadeClientEdgeCasesTest.cs @@ -0,0 +1,159 @@ +using System; +using System.Net.Http; +using ArcadeDotnet.Exceptions; + +namespace ArcadeDotnet.Tests; + +public class ArcadeClientEdgeCasesTest +{ + [Fact] + public void Constructor_WithNullOptions_ShouldThrow() + { + // Act & Assert + Assert.Throws(() => new ArcadeClient(null!)); + } + + [Fact] + public void Constructor_CreatesMultipleClients_ShouldHaveIndependentHttpClients() + { + // Arrange + var httpClient1 = new HttpClient(); + var httpClient2 = new HttpClient(); + + // Act + var client1 = new ArcadeClient(new ArcadeClientOptions + { + ApiKey = "key1", + HttpClient = httpClient1 + }); + + var client2 = new ArcadeClient(new ArcadeClientOptions + { + ApiKey = "key2", + HttpClient = httpClient2 + }); + + // Assert - Different configurations + Assert.Equal("key1", client1.APIKey); + Assert.Equal("key2", client2.APIKey); + Assert.NotSame(client1, client2); + } + + [Fact] + public void BaseUrl_WithTrailingSlash_ShouldNormalize() + { + // Arrange + var options = new ArcadeClientOptions + { + ApiKey = "test", + BaseUrl = new Uri("https://api.test.com/") + }; + + // Act + var client = new ArcadeClient(options); + + // Assert + Assert.Equal("https://api.test.com/", client.BaseUrl.ToString()); + } + + [Fact] + public void Constructor_WithVeryLongApiKey_ShouldWork() + { + // Arrange + var longKey = new string('a', 1000); // 1000 character key + var options = new ArcadeClientOptions + { + ApiKey = longKey, + HttpClient = new HttpClient() + }; + + // Act + var client = new ArcadeClient(options); + + // Assert + Assert.Equal(longKey, client.APIKey); + } + + [Fact] + public void Constructor_WithSpecialCharactersInApiKey_ShouldWork() + { + // Arrange + var specialKey = "key!@#$%^&*()_+-=[]{}|;':\",./<>?"; + var options = new ArcadeClientOptions + { + ApiKey = specialKey, + HttpClient = new HttpClient() + }; + + // Act + var client = new ArcadeClient(options); + + // Assert + Assert.Equal(specialKey, client.APIKey); + } + + [Theory] + [InlineData("https://api.arcade.dev")] + [InlineData("https://staging.arcade.dev")] + [InlineData("http://localhost:3000")] + [InlineData("https://custom-domain.com:8080")] + public void Constructor_WithDifferentBaseUrls_ShouldAcceptAll(string baseUrl) + { + // Arrange + var options = new ArcadeClientOptions + { + ApiKey = "test", + BaseUrl = new Uri(baseUrl), + HttpClient = new HttpClient() + }; + + // Act + var client = new ArcadeClient(options); + + // Assert + Assert.StartsWith(baseUrl, client.BaseUrl.ToString()); + } + + [Fact] + public void Services_CalledMultipleTimes_ShouldReturnSameInstance() + { + // Arrange + var client = new ArcadeClient(new ArcadeClientOptions + { + ApiKey = "test", + HttpClient = new HttpClient() + }); + + // Act + var admin1 = client.Admin; + var admin2 = client.Admin; + var auth1 = client.Auth; + var auth2 = client.Auth; + + // Assert - Should return same instances (not create new each time) + Assert.Same(admin1, admin2); + Assert.Same(auth1, auth2); + } + + [Fact] + public void Constructor_Parameterless_WithInvalidEnvironmentBaseUrl_ShouldUseDefault() + { + // Arrange + Environment.SetEnvironmentVariable("ARCADE_API_KEY", "test-key"); + Environment.SetEnvironmentVariable("ARCADE_BASE_URL", "not-a-valid-url"); + + try + { + // Act - Invalid URL should be ignored and default used + var client = new ArcadeClient(); + + // Assert - Should use default base URL + Assert.Equal(new Uri(ArcadeClientOptions.DefaultBaseUrl), client.BaseUrl); + } + finally + { + Environment.SetEnvironmentVariable("ARCADE_API_KEY", null); + Environment.SetEnvironmentVariable("ARCADE_BASE_URL", null); + } + } +} diff --git a/src/ArcadeDotnet.Tests/ArcadeClientFactoryTest.cs b/src/ArcadeDotnet.Tests/ArcadeClientFactoryTest.cs new file mode 100644 index 0000000..5510f07 --- /dev/null +++ b/src/ArcadeDotnet.Tests/ArcadeClientFactoryTest.cs @@ -0,0 +1,85 @@ +using System; +using ArcadeDotnet.Exceptions; + +namespace ArcadeDotnet.Tests; + +public class ArcadeClientFactoryTest +{ + [Fact] + public void Create_WithoutParameters_ShouldCreateClientWithSharedHttpClient() + { + // Arrange + Environment.SetEnvironmentVariable("ARCADE_API_KEY", "test-factory-key"); + + try + { + // Act + var client1 = ArcadeClientFactory.Create(); + var client2 = ArcadeClientFactory.Create(); + + // Assert + Assert.NotNull(client1); + Assert.NotNull(client2); + Assert.Equal("test-factory-key", client1.APIKey); + Assert.Equal("test-factory-key", client2.APIKey); + // Both should use same shared HttpClient (verify by reference if possible) + } + finally + { + Environment.SetEnvironmentVariable("ARCADE_API_KEY", null); + } + } + + [Theory] + [InlineData("sk_test_123")] + [InlineData("api_key_prod_456")] + [InlineData("my-custom-key")] + public void Create_WithApiKey_ShouldUseProvidedKey(string apiKey) + { + // Act + var client = ArcadeClientFactory.Create(apiKey); + + // Assert + Assert.NotNull(client); + Assert.Equal(apiKey, client.APIKey); + Assert.Equal(new Uri(ArcadeClientOptions.DefaultBaseUrl), client.BaseUrl); + } + + [Fact] + public void Create_WithoutEnvironmentVariable_ShouldThrowForMissingApiKey() + { + // Arrange + Environment.SetEnvironmentVariable("ARCADE_API_KEY", null); + + // Act & Assert + var exception = Assert.Throws(() => + ArcadeClientFactory.Create()); + + Assert.Contains("API key is required", exception.Message); + } + + [Fact] + public void Create_ShouldReuseHttpClientAcrossInstances() + { + // Arrange + Environment.SetEnvironmentVariable("ARCADE_API_KEY", "test-key"); + + try + { + // Act + var client1 = ArcadeClientFactory.Create(); + var client2 = ArcadeClientFactory.Create(); + + // Assert - Both clients should be usable + Assert.NotNull(client1.Tools); + Assert.NotNull(client2.Tools); + Assert.NotNull(client1.Auth); + Assert.NotNull(client2.Auth); + } + finally + { + Environment.SetEnvironmentVariable("ARCADE_API_KEY", null); + } + } +} + diff --git a/src/ArcadeDotnet.Tests/ArcadeClientOptionsTest.cs b/src/ArcadeDotnet.Tests/ArcadeClientOptionsTest.cs new file mode 100644 index 0000000..062e092 --- /dev/null +++ b/src/ArcadeDotnet.Tests/ArcadeClientOptionsTest.cs @@ -0,0 +1,78 @@ +using System; +using System.Net.Http; + +namespace ArcadeDotnet.Tests; + +public class ArcadeClientOptionsTest +{ + [Fact] + public void Options_WithAllProperties_ShouldSetCorrectly() + { + // Arrange & Act + var options = new ArcadeClientOptions + { + ApiKey = "test-key", + BaseUrl = new Uri("https://test.api.com"), + HttpClient = new HttpClient() + }; + + // Assert + Assert.Equal("test-key", options.ApiKey); + Assert.Equal("https://test.api.com/", options.BaseUrl!.ToString()); + Assert.NotNull(options.HttpClient); + } + + [Fact] + public void Options_WithMinimalProperties_ShouldAllowNulls() + { + // Arrange & Act + var options = new ArcadeClientOptions + { + ApiKey = "test-key" + }; + + // Assert + Assert.Equal("test-key", options.ApiKey); + Assert.Null(options.BaseUrl); + Assert.Null(options.HttpClient); + } + + [Theory] + [InlineData("ARCADE_API_KEY")] + [InlineData("ARCADE_BASE_URL")] + public void Constants_ShouldMatchExpectedValues(string expected) + { + // Assert + Assert.Contains(expected, new[] + { + ArcadeClientOptions.ApiKeyEnvironmentVariable, + ArcadeClientOptions.BaseUrlEnvironmentVariable + }); + } + + [Fact] + public void DefaultBaseUrl_ShouldBeArcadeProduction() + { + // Assert + Assert.Equal("https://api.arcade.dev", ArcadeClientOptions.DefaultBaseUrl); + } + + [Fact] + public void Options_AsRecord_ShouldSupportWithExpression() + { + // Arrange + var original = new ArcadeClientOptions + { + ApiKey = "original-key", + BaseUrl = new Uri("https://original.com") + }; + + // Act + var modified = original with { ApiKey = "new-key" }; + + // Assert + Assert.Equal("new-key", modified.ApiKey); + Assert.Equal(original.BaseUrl, modified.BaseUrl); // Unchanged + } +} + diff --git a/src/ArcadeDotnet.Tests/ArcadeClientTest.cs b/src/ArcadeDotnet.Tests/ArcadeClientTest.cs new file mode 100644 index 0000000..e7dc910 --- /dev/null +++ b/src/ArcadeDotnet.Tests/ArcadeClientTest.cs @@ -0,0 +1,178 @@ +using System; +using System.Net.Http; +using ArcadeDotnet.Exceptions; + +namespace ArcadeDotnet.Tests; + +public class ArcadeClientTest +{ + [Fact] + public void Constructor_Parameterless_ShouldReadFromEnvironment() + { + // Arrange + Environment.SetEnvironmentVariable("ARCADE_API_KEY", "env-key"); + Environment.SetEnvironmentVariable("ARCADE_BASE_URL", "https://custom.api.com"); + + try + { + // Act + var client = new ArcadeClient(); + + // Assert + Assert.Equal("env-key", client.APIKey); + Assert.Equal("https://custom.api.com/", client.BaseUrl.ToString()); + } + finally + { + Environment.SetEnvironmentVariable("ARCADE_API_KEY", null); + Environment.SetEnvironmentVariable("ARCADE_BASE_URL", null); + } + } + + [Fact] + public void Constructor_Parameterless_WithoutApiKey_ShouldThrow() + { + // Arrange + Environment.SetEnvironmentVariable("ARCADE_API_KEY", null); + + // Act & Assert + var exception = Assert.Throws(() => new ArcadeClient()); + Assert.Contains("API key is required", exception.Message); + } + + [Fact] + public void Constructor_WithOptions_ShouldUseProvidedValues() + { + // Arrange + var httpClient = new HttpClient(); + var options = new ArcadeClientOptions + { + ApiKey = "options-key", + BaseUrl = new Uri("https://options.api.com"), + HttpClient = httpClient + }; + + // Act + var client = new ArcadeClient(options); + + // Assert + Assert.Equal("options-key", client.APIKey); + Assert.Equal("https://options.api.com/", client.BaseUrl.ToString()); + } + + [Fact] + public void Constructor_WithNullApiKey_ShouldThrow() + { + // Arrange + var options = new ArcadeClientOptions + { + ApiKey = null, + HttpClient = new HttpClient() + }; + + // Act & Assert + Assert.Throws(() => new ArcadeClient(options)); + } + + [Fact] + public void Constructor_WithEmptyApiKey_ShouldWork() + { + // Arrange - Empty string is technically valid (API will reject it) + var options = new ArcadeClientOptions + { + ApiKey = "", + HttpClient = new HttpClient() + }; + + // Act - Should not throw, API will handle validation + var client = new ArcadeClient(options); + + // Assert + Assert.Equal("", client.APIKey); + } + + [Fact] + public void Constructor_WithoutBaseUrl_ShouldUseDefault() + { + // Arrange + var options = new ArcadeClientOptions + { + ApiKey = "test-key", + HttpClient = new HttpClient() + }; + + // Act + var client = new ArcadeClient(options); + + // Assert + Assert.Equal(ArcadeClientOptions.DefaultBaseUrl, client.BaseUrl.ToString().TrimEnd('/')); + } + + [Fact] + public void Constructor_WithoutHttpClient_ShouldCreateNew() + { + // Arrange + var options = new ArcadeClientOptions + { + ApiKey = "test-key" + }; + + // Act + var client = new ArcadeClient(options); + + // Assert - Should not throw, services should be initialized + Assert.NotNull(client.Admin); + Assert.NotNull(client.Auth); + Assert.NotNull(client.Chat); + Assert.NotNull(client.Tools); + Assert.NotNull(client.Workers); + } + + [Fact] + public void Services_ShouldBeInitializedAndAccessible() + { + // Arrange + var client = new ArcadeClient(new ArcadeClientOptions + { + ApiKey = "test-key", + HttpClient = new HttpClient() + }); + + // Act & Assert - All services should be available + Assert.NotNull(client.Admin); + Assert.NotNull(client.Auth); + Assert.NotNull(client.Chat); + Assert.NotNull(client.Tools); + Assert.NotNull(client.Workers); + + // Nested services + Assert.NotNull(client.Admin.UserConnections); + Assert.NotNull(client.Admin.AuthProviders); + Assert.NotNull(client.Admin.Secrets); + Assert.NotNull(client.Chat.Completions); + Assert.NotNull(client.Tools.Scheduled); + Assert.NotNull(client.Tools.Formatted); + } + + [Fact] + public void Client_ShouldNotExposeHttpClient() + { + // Assert - HttpClient should not be on public interface + var type = typeof(ArcadeClient); + var property = type.GetProperty("HttpClient", + System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); + + Assert.Null(property); // Should not be publicly accessible + } + + [Fact] + public void Client_ShouldHaveHealthService() + { + // Assert - Health should be on interface for operational monitoring + var interfaceType = typeof(IArcadeClient); + var healthProperty = interfaceType.GetProperty("Health"); + + Assert.NotNull(healthProperty); // Health service available + } +} + diff --git a/src/ArcadeDotnet.Tests/Core/ApiEnumTest.cs b/src/ArcadeDotnet.Tests/Core/ApiEnumTest.cs new file mode 100644 index 0000000..3a73f82 --- /dev/null +++ b/src/ArcadeDotnet.Tests/Core/ApiEnumTest.cs @@ -0,0 +1,114 @@ +using System; +using System.Text.Json; +using ArcadeDotnet.Core; +using ArcadeDotnet.Exceptions; +using ArcadeDotnet.Models.AuthorizationResponseProperties; + +namespace ArcadeDotnet.Tests.Core; + +public class ApiEnumTest +{ + [Theory] + [InlineData("not_started", Status.NotStarted)] + [InlineData("pending", Status.Pending)] + [InlineData("completed", Status.Completed)] + [InlineData("failed", Status.Failed)] + public void ApiEnum_ShouldConvertFromStringToEnum(string rawValue, Status expectedEnum) + { + // Arrange + var json = JsonSerializer.SerializeToElement(rawValue); + var apiEnum = new ApiEnum(json); + + // Act + var enumValue = apiEnum.Value(); + var rawResult = apiEnum.Raw(); + + // Assert + Assert.Equal(expectedEnum, enumValue); + Assert.Equal(rawValue, rawResult); + } + + [Fact] + public void ApiEnum_ImplicitConversionToRaw_ShouldWork() + { + // Arrange + var json = JsonSerializer.SerializeToElement("pending"); + ApiEnum apiEnum = new(json); + + // Act + string raw = apiEnum; // Implicit conversion + + // Assert + Assert.Equal("pending", raw); + } + + [Fact] + public void ApiEnum_ImplicitConversionToEnum_ShouldWork() + { + // Arrange + var json = JsonSerializer.SerializeToElement("completed"); + ApiEnum apiEnum = new(json); + + // Act + Status status = apiEnum; // Implicit conversion + + // Assert + Assert.Equal(Status.Completed, status); + } + + [Fact] + public void ApiEnum_ImplicitConversionFromRaw_ShouldWork() + { + // Act + ApiEnum apiEnum = "failed"; + + // Assert + Assert.Equal(Status.Failed, apiEnum.Value()); + Assert.Equal("failed", apiEnum.Raw()); + } + + [Fact] + public void ApiEnum_ImplicitConversionFromEnum_ShouldWork() + { + // Act + ApiEnum apiEnum = Status.Pending; + + // Assert + Assert.Equal(Status.Pending, apiEnum.Value()); + } + + [Fact] + public void Validate_WithValidEnumValue_ShouldNotThrow() + { + // Arrange + ApiEnum apiEnum = Status.Completed; + + // Act & Assert + apiEnum.Validate(); // Should not throw + } + + [Fact] + public void Validate_WithInvalidEnumValue_ShouldThrow() + { + // Arrange - Create an invalid enum value using a valid string that maps to undefined enum + var json = JsonSerializer.SerializeToElement("invalid_status_value"); + var apiEnum = new ApiEnum(json); + + // Act & Assert + var exception = Assert.Throws(() => apiEnum.Validate()); + Assert.Contains("not a valid member", exception.Message); + } + + [Fact] + public void Raw_WithNullJson_ShouldThrowArcadeInvalidDataException() + { + // Arrange + var json = JsonSerializer.SerializeToElement(null); + var apiEnum = new ApiEnum(json); + + // Act & Assert + var exception = Assert.Throws(() => apiEnum.Raw()); + Assert.Contains("Failed to deserialize", exception.Message); + } +} + diff --git a/src/ArcadeDotnet.Tests/Core/ArcadeRequestTest.cs b/src/ArcadeDotnet.Tests/Core/ArcadeRequestTest.cs new file mode 100644 index 0000000..b36e119 --- /dev/null +++ b/src/ArcadeDotnet.Tests/Core/ArcadeRequestTest.cs @@ -0,0 +1,91 @@ +using System.Net.Http; +using ArcadeDotnet.Core; +using ArcadeDotnet.Models.Tools; + +namespace ArcadeDotnet.Tests.Core; + +public class ArcadeRequestTest +{ + [Fact] + public void Constructor_WithMethodAndParams_ShouldSetProperties() + { + // Arrange + var method = HttpMethod.Post; + var parameters = new ToolExecuteParams { ToolName = "TestTool" }; + + // Act + var request = new ArcadeRequest(method, parameters); + + // Assert + Assert.Equal(HttpMethod.Post, request.Method); + Assert.Equal("TestTool", request.Params.ToolName); + } + + [Theory] + [InlineData("GET")] + [InlineData("POST")] + [InlineData("PUT")] + [InlineData("DELETE")] + [InlineData("PATCH")] + public void Constructor_WithDifferentHttpMethods_ShouldWork(string methodName) + { + // Arrange + var method = new HttpMethod(methodName); + var parameters = new ToolExecuteParams { ToolName = "Test" }; + + // Act + var request = new ArcadeRequest(method, parameters); + + // Assert + Assert.Equal(methodName, request.Method.Method); + } + + [Fact] + public void Record_ShouldSupportDeconstructionundefined() + { + // Arrange + var request = new ArcadeRequest( + HttpMethod.Post, + new ToolExecuteParams { ToolName = "Test" } + ); + + // Act + var (method, params_) = request; + + // Assert + Assert.Equal(HttpMethod.Post, method); + Assert.Equal("Test", params_.ToolName); + } + + [Fact] + public void Record_ShouldSupportWithExpression() + { + // Arrange + var original = new ArcadeRequest( + HttpMethod.Post, + new ToolExecuteParams { ToolName = "Original" } + ); + + // Act + var modified = original with { Method = HttpMethod.Get }; + + // Assert + Assert.Equal(HttpMethod.Get, modified.Method); + Assert.Equal("Original", modified.Params.ToolName); // Unchanged + } + + [Fact] + public void Record_WithSameValues_ShouldBeEqual() + { + // Arrange + var params1 = new ToolExecuteParams { ToolName = "Test" }; + var params2 = new ToolExecuteParams { ToolName = "Test" }; + + var request1 = new ArcadeRequest(HttpMethod.Post, params1); + var request2 = new ArcadeRequest(HttpMethod.Post, params2); + + // Assert - Records have value equality + Assert.Equal(request1.Method, request2.Method); + } +} + diff --git a/src/ArcadeDotnet.Tests/Core/ArcadeResponseTest.cs b/src/ArcadeDotnet.Tests/Core/ArcadeResponseTest.cs new file mode 100644 index 0000000..370d9a8 --- /dev/null +++ b/src/ArcadeDotnet.Tests/Core/ArcadeResponseTest.cs @@ -0,0 +1,119 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using ArcadeDotnet.Core; +using ArcadeDotnet.Exceptions; +using ArcadeDotnet.Models; + +namespace ArcadeDotnet.Tests.Core; + +public class ArcadeResponseTest +{ + [Fact] + public async Task Deserialize_WithValidJson_ShouldReturnObject() + { + // Arrange + var json = JsonSerializer.Serialize(new { message = "test", name = "error" }); + var httpResponse = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(json, Encoding.UTF8, "application/json") + }; + var response = new ArcadeResponse { Message = httpResponse }; + + // Act + var result = await response.Deserialize(); + + // Assert + Assert.NotNull(result); + Assert.Equal("test", result.Message); + Assert.Equal("error", result.Name); + } + + [Fact] + public async Task Deserialize_WithNullContent_ShouldThrowArcadeInvalidDataException() + { + // Arrange + var httpResponse = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("null", Encoding.UTF8, "application/json") + }; + var response = new ArcadeResponse { Message = httpResponse }; + + // Act & Assert + await Assert.ThrowsAsync(() => + response.Deserialize()); + } + + [Fact] + public async Task Deserialize_WithInvalidJson_ShouldThrowArcadeInvalidDataException() + { + // Arrange + var httpResponse = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("invalid json{", Encoding.UTF8, "application/json") + }; + var response = new ArcadeResponse { Message = httpResponse }; + + // Act & Assert + await Assert.ThrowsAsync(() => + response.Deserialize()); + } + + [Fact] + public async Task Dispose_ShouldDisposeUnderlyingHttpResponseMessage() + { + // Arrange + var httpResponse = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("test") + }; + var response = new ArcadeResponse { Message = httpResponse }; + + // Act + response.Dispose(); + + // Assert - Accessing disposed content should throw + await Assert.ThrowsAsync(() => + httpResponse.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task Response_ShouldWorkWithUsingStatement() + { + // Arrange + var json = JsonSerializer.Serialize(new { message = "test" }); + var httpResponse = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(json, Encoding.UTF8, "application/json") + }; + + // Act & Assert + Error result = null; + using (var response = new ArcadeResponse { Message = httpResponse }) + { + result = await response.Deserialize(); + } + + Assert.NotNull(result); + Assert.Equal("test", result.Message); + // After using block, should be disposed + await Assert.ThrowsAsync(() => + httpResponse.Content.ReadAsStringAsync()); + } + + [Fact] + public void Record_WithSameMessage_ShouldBeEqual() + { + // Arrange + var httpResponse = new HttpResponseMessage(HttpStatusCode.OK); + var response1 = new ArcadeResponse { Message = httpResponse }; + var response2 = new ArcadeResponse { Message = httpResponse }; + + // Assert - Records with same reference should be equal + Assert.Equal(response1.Message, response2.Message); + } +} + diff --git a/src/ArcadeDotnet.Tests/Exceptions/ArcadeExceptionFactoryTest.cs b/src/ArcadeDotnet.Tests/Exceptions/ArcadeExceptionFactoryTest.cs new file mode 100644 index 0000000..f6e2beb --- /dev/null +++ b/src/ArcadeDotnet.Tests/Exceptions/ArcadeExceptionFactoryTest.cs @@ -0,0 +1,113 @@ +using System; +using System.Net; +using ArcadeDotnet.Exceptions; + +namespace ArcadeDotnet.Tests.Exceptions; + +public class ArcadeExceptionFactoryTest +{ + [Theory] + [InlineData(400, typeof(ArcadeBadRequestException))] + [InlineData(401, typeof(ArcadeUnauthorizedException))] + [InlineData(403, typeof(ArcadeForbiddenException))] + [InlineData(404, typeof(ArcadeNotFoundException))] + [InlineData(422, typeof(ArcadeUnprocessableEntityException))] + [InlineData(429, typeof(ArcadeRateLimitException))] + public void CreateApiException_WithSpecificStatusCodes_ShouldReturnCorrectExceptionType( + int statusCode, + Type expectedType) + { + // Arrange + var httpStatusCode = (HttpStatusCode)statusCode; + var responseBody = "test error response"; + + // Act + var exception = ArcadeExceptionFactory.CreateApiException(httpStatusCode, responseBody); + + // Assert + Assert.IsType(expectedType, exception); + Assert.Equal(httpStatusCode, exception.StatusCode); + Assert.Equal(responseBody, exception.ResponseBody); + } + + [Theory] + [InlineData(405)] // Method Not Allowed + [InlineData(409)] // Conflict + [InlineData(418)] // I'm a teapot + [InlineData(451)] // Unavailable For Legal Reasons + public void CreateApiException_WithOther4xxCodes_ShouldReturnArcade4xxException(int statusCode) + { + // Arrange + var httpStatusCode = (HttpStatusCode)statusCode; + + // Act + var exception = ArcadeExceptionFactory.CreateApiException(httpStatusCode, "error"); + + // Assert + Assert.IsType(exception); + Assert.Equal(httpStatusCode, exception.StatusCode); + } + + [Theory] + [InlineData(500)] // Internal Server Error + [InlineData(502)] // Bad Gateway + [InlineData(503)] // Service Unavailable + [InlineData(504)] // Gateway Timeout + public void CreateApiException_With5xxCodes_ShouldReturnArcade5xxException(int statusCode) + { + // Arrange + var httpStatusCode = (HttpStatusCode)statusCode; + + // Act + var exception = ArcadeExceptionFactory.CreateApiException(httpStatusCode, "server error"); + + // Assert + Assert.IsType(exception); + Assert.Equal(httpStatusCode, exception.StatusCode); + } + + [Theory] + [InlineData(200)] // OK (shouldn't normally create exception for this) + [InlineData(204)] // No Content + [InlineData(300)] // Multiple Choices + [InlineData(600)] // Non-standard + public void CreateApiException_WithUnexpectedCodes_ShouldReturnUnexpectedStatusCodeException( + int statusCode) + { + // Arrange + var httpStatusCode = (HttpStatusCode)statusCode; + + // Act + var exception = ArcadeExceptionFactory.CreateApiException(httpStatusCode, "unexpected"); + + // Assert + Assert.IsType(exception); + Assert.Equal(httpStatusCode, exception.StatusCode); + } + + [Fact] + public void CreateApiException_ShouldIncludeResponseBodyInException() + { + // Arrange + var responseBody = "Detailed error message from API"; + + // Act + var exception = ArcadeExceptionFactory.CreateApiException(HttpStatusCode.BadRequest, responseBody); + + // Assert + Assert.Equal(responseBody, exception.ResponseBody); + Assert.Contains(responseBody, exception.Message); + } + + [Fact] + public void CreateApiException_WithEmptyResponseBody_ShouldStillWork() + { + // Arrange & Act + var exception = ArcadeExceptionFactory.CreateApiException(HttpStatusCode.NotFound, string.Empty); + + // Assert + Assert.NotNull(exception); + Assert.Equal(string.Empty, exception.ResponseBody); + } +} + diff --git a/src/ArcadeDotnet.Tests/Exceptions/ExceptionHierarchyTest.cs b/src/ArcadeDotnet.Tests/Exceptions/ExceptionHierarchyTest.cs new file mode 100644 index 0000000..ffc3b99 --- /dev/null +++ b/src/ArcadeDotnet.Tests/Exceptions/ExceptionHierarchyTest.cs @@ -0,0 +1,66 @@ +using System; +using ArcadeDotnet.Exceptions; + +namespace ArcadeDotnet.Tests.Exceptions; + +public class ExceptionHierarchyTest +{ + [Theory] + [InlineData(400, typeof(ArcadeBadRequestException))] + [InlineData(401, typeof(ArcadeUnauthorizedException))] + [InlineData(403, typeof(ArcadeForbiddenException))] + [InlineData(404, typeof(ArcadeNotFoundException))] + public void All4xxExceptions_ShouldInheritFromArcade4xxException(int statusCode, Type exceptionType) + { + // Arrange + var exception = ArcadeExceptionFactory.CreateApiException( + (System.Net.HttpStatusCode)statusCode, + "test"); + + // Assert + Assert.IsAssignableFrom(exception); + Assert.IsType(exceptionType, exception); + } + + [Theory] + [InlineData(500)] + [InlineData(502)] + [InlineData(503)] + public void All5xxExceptions_ShouldInheritFromArcadeApiException(int statusCode) + { + // Arrange + var exception = ArcadeExceptionFactory.CreateApiException( + (System.Net.HttpStatusCode)statusCode, + "test"); + + // Assert + Assert.IsAssignableFrom(exception); + Assert.IsType(exception); + } + + [Fact] + public void AllExceptions_ShouldInheritFromArcadeException() + { + // Assert + Assert.IsAssignableFrom(new ArcadeIOException("test")); + Assert.IsAssignableFrom(new ArcadeInvalidDataException("test")); + } + + [Theory] + [InlineData(typeof(ArcadeBadRequestException))] + [InlineData(typeof(ArcadeUnauthorizedException))] + [InlineData(typeof(ArcadeForbiddenException))] + [InlineData(typeof(ArcadeNotFoundException))] + [InlineData(typeof(ArcadeUnprocessableEntityException))] + [InlineData(typeof(ArcadeRateLimitException))] + [InlineData(typeof(Arcade5xxException))] + [InlineData(typeof(ArcadeUnexpectedStatusCodeException))] + [InlineData(typeof(ArcadeInvalidDataException))] + [InlineData(typeof(ArcadeIOException))] + public void LeafExceptions_ShouldBeSealed(Type exceptionType) + { + // Assert - All leaf exception classes should be sealed + Assert.True(exceptionType.IsSealed, $"{exceptionType.Name} should be sealed"); + } +} + diff --git a/src/ArcadeDotnet.Tests/Extensions/ServiceCollectionExtensionsTest.cs b/src/ArcadeDotnet.Tests/Extensions/ServiceCollectionExtensionsTest.cs new file mode 100644 index 0000000..61403d5 --- /dev/null +++ b/src/ArcadeDotnet.Tests/Extensions/ServiceCollectionExtensionsTest.cs @@ -0,0 +1,83 @@ +using System; +using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; +using ArcadeDotnet.Extensions; + +namespace ArcadeDotnet.Tests.Extensions; + +public class ServiceCollectionExtensionsTest +{ + [Fact] + public void AddArcadeClient_WithApiKey_ShouldRegisterClient() + { + var services = new ServiceCollection(); + services.AddArcadeClient("test-api-key"); + var provider = services.BuildServiceProvider(); + var client = provider.GetService(); + + Assert.NotNull(client); + Assert.Equal("test-api-key", client.APIKey); + } + + [Fact] + public void AddArcadeClient_WithEnvironmentVariables_ShouldUseEnvironmentVars() + { + Environment.SetEnvironmentVariable("ARCADE_API_KEY", "env-api-key"); + var services = new ServiceCollection(); + + try + { + services.AddArcadeClient(); + var provider = services.BuildServiceProvider(); + var client = provider.GetService(); + + Assert.NotNull(client); + Assert.Equal("env-api-key", client.APIKey); + } + finally + { + Environment.SetEnvironmentVariable("ARCADE_API_KEY", null); + } + } + + [Fact] + public void AddArcadeClient_ShouldUseSingletonLifetime() + { + var services = new ServiceCollection(); + services.AddArcadeClient("test-key"); + var provider = services.BuildServiceProvider(); + + var client1 = provider.GetService(); + var client2 = provider.GetService(); + + Assert.Same(client1, client2); + } + + [Fact] + public void AddArcadeClient_ShouldUseHttpClientFactory() + { + var services = new ServiceCollection(); + services.AddArcadeClient("test-key"); + var provider = services.BuildServiceProvider(); + + var factory = provider.GetService(); + var client = provider.GetService(); + + Assert.NotNull(factory); + Assert.NotNull(client); + } + + [Fact] + public void AddArcadeClient_WithCustomBaseUrl_ShouldUseCustomUrl() + { + var services = new ServiceCollection(); + var customUrl = new Uri("https://custom.api.dev"); + + services.AddArcadeClient("test-key", customUrl); + var provider = services.BuildServiceProvider(); + var client = provider.GetService(); + + Assert.NotNull(client); + Assert.Equal(customUrl, client.BaseUrl); + } +} diff --git a/src/ArcadeDotnet.Tests/Services/Admin/AuthProviders/AuthProviderServiceTest.cs b/src/ArcadeDotnet.Tests/Services/Admin/AuthProviders/AuthProviderServiceTest.cs index 8f54baa..28ea218 100644 --- a/src/ArcadeDotnet.Tests/Services/Admin/AuthProviders/AuthProviderServiceTest.cs +++ b/src/ArcadeDotnet.Tests/Services/Admin/AuthProviders/AuthProviderServiceTest.cs @@ -7,7 +7,7 @@ public class AuthProviderServiceTest : TestBase [Fact] public async Task Create_Works() { - var authProviderResponse = await this.client.Admin.AuthProviders.Create( + var authProviderResponse = await this.Client.Admin.AuthProviders.Create( new() { ID = "id" } ); authProviderResponse.Validate(); @@ -16,14 +16,14 @@ public async Task Create_Works() [Fact] public async Task List_Works() { - var authProviders = await this.client.Admin.AuthProviders.List(); + var authProviders = await this.Client.Admin.AuthProviders.List(); authProviders.Validate(); } [Fact] public async Task Delete_Works() { - var authProviderResponse = await this.client.Admin.AuthProviders.Delete( + var authProviderResponse = await this.Client.Admin.AuthProviders.Delete( new() { ID = "id" } ); authProviderResponse.Validate(); @@ -32,14 +32,14 @@ public async Task Delete_Works() [Fact] public async Task Get_Works() { - var authProviderResponse = await this.client.Admin.AuthProviders.Get(new() { ID = "id" }); + var authProviderResponse = await this.Client.Admin.AuthProviders.Get(new() { ID = "id" }); authProviderResponse.Validate(); } [Fact] public async Task Patch_Works() { - var authProviderResponse = await this.client.Admin.AuthProviders.Patch(new() { ID = "id" }); + var authProviderResponse = await this.Client.Admin.AuthProviders.Patch(new() { ID = "id" }); authProviderResponse.Validate(); } } diff --git a/src/ArcadeDotnet.Tests/Services/Admin/Secrets/SecretServiceTest.cs b/src/ArcadeDotnet.Tests/Services/Admin/Secrets/SecretServiceTest.cs index 1297168..a72ae27 100644 --- a/src/ArcadeDotnet.Tests/Services/Admin/Secrets/SecretServiceTest.cs +++ b/src/ArcadeDotnet.Tests/Services/Admin/Secrets/SecretServiceTest.cs @@ -7,13 +7,13 @@ public class SecretServiceTest : TestBase [Fact] public async Task List_Works() { - var secrets = await this.client.Admin.Secrets.List(); + var secrets = await this.Client.Admin.Secrets.List(); secrets.Validate(); } [Fact] public async Task Delete_Works() { - await this.client.Admin.Secrets.Delete(new() { SecretID = "secret_id" }); + await this.Client.Admin.Secrets.Delete(new() { SecretID = "secret_id" }); } } diff --git a/src/ArcadeDotnet.Tests/Services/Admin/UserConnections/UserConnectionServiceTest.cs b/src/ArcadeDotnet.Tests/Services/Admin/UserConnections/UserConnectionServiceTest.cs index 4aa0fc7..bd5fbc5 100644 --- a/src/ArcadeDotnet.Tests/Services/Admin/UserConnections/UserConnectionServiceTest.cs +++ b/src/ArcadeDotnet.Tests/Services/Admin/UserConnections/UserConnectionServiceTest.cs @@ -7,13 +7,13 @@ public class UserConnectionServiceTest : TestBase [Fact] public async Task List_Works() { - var page = await this.client.Admin.UserConnections.List(); + var page = await this.Client.Admin.UserConnections.List(); page.Validate(); } [Fact] public async Task Delete_Works() { - await this.client.Admin.UserConnections.Delete(new() { ID = "id" }); + await this.Client.Admin.UserConnections.Delete(new() { ID = "id" }); } } diff --git a/src/ArcadeDotnet.Tests/Services/Auth/AuthServiceTest.cs b/src/ArcadeDotnet.Tests/Services/Auth/AuthServiceTest.cs index 581e49c..b07bf4c 100644 --- a/src/ArcadeDotnet.Tests/Services/Auth/AuthServiceTest.cs +++ b/src/ArcadeDotnet.Tests/Services/Auth/AuthServiceTest.cs @@ -7,7 +7,7 @@ public class AuthServiceTest : TestBase [Fact] public async Task Authorize_Works() { - var authorizationResponse = await this.client.Auth.Authorize( + var authorizationResponse = await this.Client.Auth.Authorize( new() { AuthRequirement = new() @@ -26,7 +26,7 @@ public async Task Authorize_Works() [Fact] public async Task ConfirmUser_Works() { - var confirmUserResponse = await this.client.Auth.ConfirmUser( + var confirmUserResponse = await this.Client.Auth.ConfirmUser( new() { FlowID = "flow_id", UserID = "user_id" } ); confirmUserResponse.Validate(); @@ -35,7 +35,7 @@ public async Task ConfirmUser_Works() [Fact] public async Task Status_Works() { - var authorizationResponse = await this.client.Auth.Status(new() { ID = "id" }); + var authorizationResponse = await this.Client.Auth.Status(new() { ID = "id" }); authorizationResponse.Validate(); } } diff --git a/src/ArcadeDotnet.Tests/Services/Chat/Completions/CompletionServiceTest.cs b/src/ArcadeDotnet.Tests/Services/Chat/Completions/CompletionServiceTest.cs index 7f36cc6..5ee8ee9 100644 --- a/src/ArcadeDotnet.Tests/Services/Chat/Completions/CompletionServiceTest.cs +++ b/src/ArcadeDotnet.Tests/Services/Chat/Completions/CompletionServiceTest.cs @@ -7,7 +7,7 @@ public class CompletionServiceTest : TestBase [Fact] public async Task Create_Works() { - var chatResponse = await this.client.Chat.Completions.Create(); + var chatResponse = await this.Client.Chat.Completions.Create(); chatResponse.Validate(); } } diff --git a/src/ArcadeDotnet.Tests/Services/Health/HealthServiceTest.cs b/src/ArcadeDotnet.Tests/Services/Health/HealthServiceTest.cs index fd3bfef..9c036f7 100644 --- a/src/ArcadeDotnet.Tests/Services/Health/HealthServiceTest.cs +++ b/src/ArcadeDotnet.Tests/Services/Health/HealthServiceTest.cs @@ -7,7 +7,7 @@ public class HealthServiceTest : TestBase [Fact] public async Task Check_Works() { - var healthSchema = await this.client.Health.Check(); + var healthSchema = await this.Client.Health.Check(); healthSchema.Validate(); } } diff --git a/src/ArcadeDotnet.Tests/Services/Tools/Formatted/FormattedServiceTest.cs b/src/ArcadeDotnet.Tests/Services/Tools/Formatted/FormattedServiceTest.cs index 0858427..03ad605 100644 --- a/src/ArcadeDotnet.Tests/Services/Tools/Formatted/FormattedServiceTest.cs +++ b/src/ArcadeDotnet.Tests/Services/Tools/Formatted/FormattedServiceTest.cs @@ -7,14 +7,14 @@ public class FormattedServiceTest : TestBase [Fact] public async Task List_Works() { - var page = await this.client.Tools.Formatted.List(); + var page = await this.Client.Tools.Formatted.List(); page.Validate(); } [Fact] public async Task Get_Works() { - var formatted = await this.client.Tools.Formatted.Get(new() { Name = "name" }); + var formatted = await this.Client.Tools.Formatted.Get(new() { Name = "name" }); _ = formatted; } } diff --git a/src/ArcadeDotnet.Tests/Services/Tools/Scheduled/ScheduledServiceTest.cs b/src/ArcadeDotnet.Tests/Services/Tools/Scheduled/ScheduledServiceTest.cs index f2d582a..8deda96 100644 --- a/src/ArcadeDotnet.Tests/Services/Tools/Scheduled/ScheduledServiceTest.cs +++ b/src/ArcadeDotnet.Tests/Services/Tools/Scheduled/ScheduledServiceTest.cs @@ -7,14 +7,14 @@ public class ScheduledServiceTest : TestBase [Fact] public async Task List_Works() { - var page = await this.client.Tools.Scheduled.List(); + var page = await this.Client.Tools.Scheduled.List(); page.Validate(); } [Fact] public async Task Get_Works() { - var scheduled = await this.client.Tools.Scheduled.Get(new() { ID = "id" }); + var scheduled = await this.Client.Tools.Scheduled.Get(new() { ID = "id" }); scheduled.Validate(); } } diff --git a/src/ArcadeDotnet.Tests/Services/Tools/ToolServiceTest.cs b/src/ArcadeDotnet.Tests/Services/Tools/ToolServiceTest.cs index e9b6a93..efe4609 100644 --- a/src/ArcadeDotnet.Tests/Services/Tools/ToolServiceTest.cs +++ b/src/ArcadeDotnet.Tests/Services/Tools/ToolServiceTest.cs @@ -7,14 +7,14 @@ public class ToolServiceTest : TestBase [Fact] public async Task List_Works() { - var page = await this.client.Tools.List(); + var page = await this.Client.Tools.List(); page.Validate(); } [Fact] public async Task Authorize_Works() { - var authorizationResponse = await this.client.Tools.Authorize( + var authorizationResponse = await this.Client.Tools.Authorize( new() { ToolName = "tool_name" } ); authorizationResponse.Validate(); @@ -23,14 +23,14 @@ public async Task Authorize_Works() [Fact] public async Task Execute_Works() { - var executeToolResponse = await this.client.Tools.Execute(new() { ToolName = "tool_name" }); + var executeToolResponse = await this.Client.Tools.Execute(new() { ToolName = "tool_name" }); executeToolResponse.Validate(); } [Fact] public async Task Get_Works() { - var toolDefinition = await this.client.Tools.Get(new() { Name = "name" }); + var toolDefinition = await this.Client.Tools.Get(new() { Name = "name" }); toolDefinition.Validate(); } } diff --git a/src/ArcadeDotnet.Tests/Services/Workers/WorkerServiceTest.cs b/src/ArcadeDotnet.Tests/Services/Workers/WorkerServiceTest.cs index 413d918..5f84a8b 100644 --- a/src/ArcadeDotnet.Tests/Services/Workers/WorkerServiceTest.cs +++ b/src/ArcadeDotnet.Tests/Services/Workers/WorkerServiceTest.cs @@ -7,48 +7,48 @@ public class WorkerServiceTest : TestBase [Fact] public async Task Create_Works() { - var workerResponse = await this.client.Workers.Create(new() { ID = "id" }); + var workerResponse = await this.Client.Workers.Create(new() { ID = "id" }); workerResponse.Validate(); } [Fact] public async Task Update_Works() { - var workerResponse = await this.client.Workers.Update(new() { ID = "id" }); + var workerResponse = await this.Client.Workers.Update(new() { ID = "id" }); workerResponse.Validate(); } [Fact] public async Task List_Works() { - var page = await this.client.Workers.List(); + var page = await this.Client.Workers.List(); page.Validate(); } [Fact] public async Task Delete_Works() { - await this.client.Workers.Delete(new() { ID = "id" }); + await this.Client.Workers.Delete(new() { ID = "id" }); } [Fact] public async Task Get_Works() { - var workerResponse = await this.client.Workers.Get(new() { ID = "id" }); + var workerResponse = await this.Client.Workers.Get(new() { ID = "id" }); workerResponse.Validate(); } [Fact] public async Task Health_Works() { - var workerHealthResponse = await this.client.Workers.Health(new() { ID = "id" }); + var workerHealthResponse = await this.Client.Workers.Health(new() { ID = "id" }); workerHealthResponse.Validate(); } [Fact] public async Task Tools_Works() { - var page = await this.client.Workers.Tools(new() { ID = "id" }); + var page = await this.Client.Workers.Tools(new() { ID = "id" }); page.Validate(); } } diff --git a/src/ArcadeDotnet.Tests/TestBase.cs b/src/ArcadeDotnet.Tests/TestBase.cs index 549320d..43490b8 100644 --- a/src/ArcadeDotnet.Tests/TestBase.cs +++ b/src/ArcadeDotnet.Tests/TestBase.cs @@ -1,20 +1,22 @@ using System; +using System.Net.Http; using ArcadeDotnet; namespace ArcadeDotnet.Tests; -public class TestBase +public abstract class TestBase { - protected IArcadeClient client; + protected readonly IArcadeClient Client; - public TestBase() + protected TestBase() { - client = new ArcadeClient() + Client = new ArcadeClient(new ArcadeClientOptions { BaseUrl = new Uri( Environment.GetEnvironmentVariable("TEST_API_BASE_URL") ?? "http://localhost:4010" ), - APIKey = "My API Key", - }; + ApiKey = "My API Key", + HttpClient = new HttpClient() + }); } } diff --git a/src/ArcadeDotnet/ArcadeClient.cs b/src/ArcadeDotnet/ArcadeClient.cs index 19b6a8c..e1f6213 100644 --- a/src/ArcadeDotnet/ArcadeClient.cs +++ b/src/ArcadeDotnet/ArcadeClient.cs @@ -12,115 +12,156 @@ namespace ArcadeDotnet; -public sealed class ArcadeClient : IArcadeClient +/// +/// The main client for interacting with the Arcade API. +/// +/// +/// When using dependency injection, register as a singleton. +/// +public sealed partial class ArcadeClient : IArcadeClient { - public HttpClient HttpClient { get; init; } = new(); + private readonly HttpClient _httpClient; - Lazy _baseUrl = new(() => - new Uri(Environment.GetEnvironmentVariable("ARCADE_BASE_URL") ?? "https://api.arcade.dev") - ); - public Uri BaseUrl - { - get { return _baseUrl.Value; } - init { _baseUrl = new(() => value); } - } + /// + /// Gets the base URL for the API. + /// + public Uri BaseUrl { get; } - Lazy _apiKey = new(() => - Environment.GetEnvironmentVariable("ARCADE_API_KEY") - ?? throw new ArcadeInvalidDataException( - string.Format("{0} cannot be null", nameof(APIKey)), - new ArgumentNullException(nameof(APIKey)) - ) - ); - public string APIKey - { - get { return _apiKey.Value; } - init { _apiKey = new(() => value); } - } + /// + /// Gets the API key used for authorization. + /// + public string APIKey { get; } - readonly Lazy _admin; - public IAdminService Admin - { - get { return _admin.Value; } - } + /// + /// Gets the admin service for administrative operations. + /// + public IAdminService Admin { get; } - readonly Lazy _auth; - public IAuthService Auth - { - get { return _auth.Value; } - } + /// + /// Gets the authentication service. + /// + public IAuthService Auth { get; } - readonly Lazy _health; - public IHealthService Health - { - get { return _health.Value; } - } + /// + /// Gets the health check service. + /// + public IHealthService Health { get; } - readonly Lazy _chat; - public IChatService Chat - { - get { return _chat.Value; } - } + /// + /// Gets the chat service. + /// + public IChatService Chat { get; } - readonly Lazy _tools; - public IToolService Tools - { - get { return _tools.Value; } - } + /// + /// Gets the tool service. + /// + public IToolService Tools { get; } - readonly Lazy _workers; - public IWorkerService Workers - { - get { return _workers.Value; } - } + /// + /// Gets the worker service. + /// + public IWorkerService Workers { get; } - public async Task Execute(HttpRequest request) - where T : ParamsBase + /// + /// Executes an API request and returns the response. + /// + /// The type of parameters. + /// The request to execute. + /// The API response. + /// Thrown when an I/O error occurs. + /// Thrown when the API returns an error. + public async Task Execute(ArcadeRequest request) + where TParams : ParamsBase { + ArgumentNullException.ThrowIfNull(request); + ArgumentNullException.ThrowIfNull(request.Params); + using HttpRequestMessage requestMessage = new(request.Method, request.Params.Url(this)) { Content = request.Params.BodyContent(), }; request.Params.AddHeadersToRequest(requestMessage, this); + HttpResponseMessage responseMessage; try { - responseMessage = await this - .HttpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead) + responseMessage = await _httpClient + .SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead) .ConfigureAwait(false); } - catch (HttpRequestException e1) + catch (HttpRequestException ex) { - throw new ArcadeIOException("I/O exception", e1); + throw new ArcadeIOException("I/O exception occurred during HTTP request", ex); } + if (!responseMessage.IsSuccessStatusCode) { try { - throw ArcadeExceptionFactory.CreateApiException( - responseMessage.StatusCode, - await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false) - ); + var responseBody = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); + throw ArcadeExceptionFactory.CreateApiException(responseMessage.StatusCode, responseBody); } - catch (HttpRequestException e) + catch (HttpRequestException ex) { - throw new ArcadeIOException("I/O Exception", e); + throw new ArcadeIOException("I/O exception occurred while reading error response", ex); } finally { responseMessage.Dispose(); } } - return new() { Message = responseMessage }; + + return new ArcadeResponse { Message = responseMessage }; } - public ArcadeClient() + + /// + /// Initializes a new instance using configuration from environment variables. + /// + /// + /// Reads ARCADE_API_KEY and ARCADE_BASE_URL from environment variables. + /// Creates a new HttpClient instance for this client. + /// + public ArcadeClient() : this(new ArcadeClientOptions + { + ApiKey = Environment.GetEnvironmentVariable(ArcadeClientOptions.ApiKeyEnvironmentVariable), + BaseUrl = ArcadeClientOptions.TryParseBaseUrl(Environment.GetEnvironmentVariable(ArcadeClientOptions.BaseUrlEnvironmentVariable)) + }) { - _admin = new(() => new AdminService(this)); - _auth = new(() => new AuthService(this)); - _health = new(() => new HealthService(this)); - _chat = new(() => new ChatService(this)); - _tools = new(() => new ToolService(this)); - _workers = new(() => new WorkerService(this)); } + + /// + /// Initializes a new instance with the specified options. + /// + /// The configuration options. + /// Thrown when options is null. + /// Thrown when required configuration is missing. + public ArcadeClient(ArcadeClientOptions options) + { + ArgumentNullException.ThrowIfNull(options); + + // Configure base URL + BaseUrl = options.BaseUrl + ?? new Uri(ArcadeClientOptions.DefaultBaseUrl); + + // Configure API key (required) + APIKey = options.ApiKey + ?? throw new ArcadeInvalidDataException( + $"API key is required. Set via {nameof(ArcadeClientOptions)}.{nameof(ArcadeClientOptions.ApiKey)} " + + $"or {ArcadeClientOptions.ApiKeyEnvironmentVariable} environment variable."); + + // HttpClient: use provided or create new (caller responsible for disposal) + _httpClient = options.HttpClientFactory?.CreateClient(options.HttpClientName ?? "ArcadeClient") + ?? options.HttpClient + ?? new HttpClient(); + + // Initialize services + Admin = new AdminService(this); + Auth = new AuthService(this); + Health = new HealthService(this); + Chat = new ChatService(this); + Tools = new ToolService(this); + Workers = new WorkerService(this); + } + } diff --git a/src/ArcadeDotnet/ArcadeClientFactory.cs b/src/ArcadeDotnet/ArcadeClientFactory.cs new file mode 100644 index 0000000..354b5a1 --- /dev/null +++ b/src/ArcadeDotnet/ArcadeClientFactory.cs @@ -0,0 +1,41 @@ +using System; +using System.Net.Http; + +namespace ArcadeDotnet; + +/// +/// Factory for creating ArcadeClient instances with convenient defaults. +/// +public static class ArcadeClientFactory +{ + private static readonly Lazy _sharedHttpClient = new(() => new HttpClient()); + + /// + /// Creates a client using environment variables and a shared HttpClient. + /// + /// A new . + public static ArcadeClient Create() + { + return new ArcadeClient(new ArcadeClientOptions + { + ApiKey = Environment.GetEnvironmentVariable(ArcadeClientOptions.ApiKeyEnvironmentVariable), + BaseUrl = ArcadeClientOptions.TryParseBaseUrl(Environment.GetEnvironmentVariable(ArcadeClientOptions.BaseUrlEnvironmentVariable)), + HttpClient = _sharedHttpClient.Value + }); + } + + /// + /// Creates a client with the specified API key and a shared HttpClient. + /// + /// The API key. + /// A new . + public static ArcadeClient Create(string apiKey) + { + return new ArcadeClient(new ArcadeClientOptions + { + ApiKey = apiKey, + HttpClient = _sharedHttpClient.Value + }); + } + +} diff --git a/src/ArcadeDotnet/ArcadeClientOptions.cs b/src/ArcadeDotnet/ArcadeClientOptions.cs new file mode 100644 index 0000000..c978f7a --- /dev/null +++ b/src/ArcadeDotnet/ArcadeClientOptions.cs @@ -0,0 +1,66 @@ +using System; +using System.Net.Http; + +namespace ArcadeDotnet; + +/// +/// Configuration options for the Arcade API client. +/// +public sealed record ArcadeClientOptions +{ + /// + /// Environment variable name for the API key. + /// + public const string ApiKeyEnvironmentVariable = "ARCADE_API_KEY"; + + /// + /// Environment variable name for the base URL. + /// + public const string BaseUrlEnvironmentVariable = "ARCADE_BASE_URL"; + + /// + /// Default base URL for the Arcade API. + /// + public const string DefaultBaseUrl = "https://api.arcade.dev"; + + /// + /// Gets the API key for authentication. + /// + public string? ApiKey { get; init; } + + /// + /// Gets the base URL for the API. + /// + public Uri? BaseUrl { get; init; } + + /// + /// Gets the HttpClient instance to use for requests. + /// If not provided, ArcadeClient will use a shared instance (not recommended for production). + /// + public HttpClient? HttpClient { get; init; } + + /// + /// Gets the IHttpClientFactory to use for creating HttpClient instances. + /// + public IHttpClientFactory? HttpClientFactory { get; init; } + + /// + /// Gets the named HttpClient name to use with IHttpClientFactory. + /// + public string HttpClientName { get; init; } = "ArcadeClient"; + + internal static Uri? TryParseBaseUrl(string? url) + { + if (string.IsNullOrEmpty(url)) + return null; + + try + { + return new Uri(url); + } + catch (UriFormatException) + { + return null; + } + } +} diff --git a/src/ArcadeDotnet/ArcadeDotnet.csproj b/src/ArcadeDotnet/ArcadeDotnet.csproj index 6056029..3c59188 100644 --- a/src/ArcadeDotnet/ArcadeDotnet.csproj +++ b/src/ArcadeDotnet/ArcadeDotnet.csproj @@ -6,7 +6,7 @@ SDK Code Generation Arcade C# MIT enable - 0.1.0 + 0.2.0 net8.0 latest @@ -31,5 +31,7 @@ + + diff --git a/src/ArcadeDotnet/Core/ApiEnum.cs b/src/ArcadeDotnet/Core/ApiEnum.cs index 257d4d0..f53e6d0 100644 --- a/src/ArcadeDotnet/Core/ApiEnum.cs +++ b/src/ArcadeDotnet/Core/ApiEnum.cs @@ -5,55 +5,111 @@ namespace ArcadeDotnet.Core; +/// +/// Represents an enumeration value that can be serialized to and from both raw and enum types. +/// +/// The raw type used for serialization (typically or ). +/// The enumeration type that represents the strongly-typed values. +/// The JSON element containing the serialized value. +/// +/// This record struct provides a bridge between raw API values and strongly-typed enums, +/// allowing for both type-safe access and handling of unknown values. +/// It supports implicit conversions to both raw and enum types for convenient usage. +/// public record struct ApiEnum(JsonElement Json) where TEnum : struct, Enum { + /// + /// Gets the raw value of the enumeration. + /// + /// The raw value as type . + /// Thrown when the JSON element cannot be deserialized to the raw type. public readonly TRaw Raw() => - JsonSerializer.Deserialize(this.Json, ModelBase.SerializerOptions) - ?? throw new ArcadeInvalidDataException( - string.Format("{0} cannot be null", nameof(this.Json)) - ); + JsonSerializer.Deserialize(Json, ModelBase.SerializerOptions) + ?? throw new ArcadeInvalidDataException($"Failed to deserialize {nameof(Json)} to {typeof(TRaw).Name}"); + /// + /// Gets the strongly-typed enum value. + /// + /// The enum value as type . public readonly TEnum Value() => - JsonSerializer.Deserialize(this.Json, ModelBase.SerializerOptions); + JsonSerializer.Deserialize(Json, ModelBase.SerializerOptions); + /// + /// Validates that the enum value is defined in the enumeration type. + /// + /// Thrown when the value is not a defined member of the enumeration. + /// + /// Use this method to ensure the API returned a known enum value rather than an undefined value. + /// public readonly void Validate() { - if (!Enum.IsDefined(Value())) + var value = Value(); + if (!Enum.IsDefined(typeof(TEnum), value)) { - throw new ArcadeInvalidDataException("Invalid enum value"); + throw new ArcadeInvalidDataException( + $"Value '{value}' is not a valid member of enum type {typeof(TEnum).Name}" + ); } } + /// + /// Implicitly converts the to its raw value. + /// + /// The enum wrapper to convert. public static implicit operator TRaw(ApiEnum value) => value.Raw(); + /// + /// Implicitly converts the to its enum value. + /// + /// The enum wrapper to convert. public static implicit operator TEnum(ApiEnum value) => value.Value(); + /// + /// Implicitly converts a raw value to an . + /// + /// The raw value to convert. public static implicit operator ApiEnum(TRaw value) => new(JsonSerializer.SerializeToElement(value, ModelBase.SerializerOptions)); + /// + /// Implicitly converts an enum value to an . + /// + /// The enum value to convert. public static implicit operator ApiEnum(TEnum value) => new(JsonSerializer.SerializeToElement(value, ModelBase.SerializerOptions)); } -sealed class ApiEnumConverter : JsonConverter> +/// +/// JSON converter for types. +/// +/// The raw type used for serialization. +/// The enumeration type. +public sealed class ApiEnumConverter : JsonConverter> where TEnum : struct, Enum { + /// + /// Reads and converts the JSON to an . + /// + /// The reader to read JSON from. + /// The type of object to convert to. + /// The serializer options to use. + /// The converted value. public override ApiEnum Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options - ) - { - return new(JsonSerializer.Deserialize(ref reader, options)); - } + ) => new(JsonSerializer.Deserialize(ref reader, options)); + /// + /// Writes the specified value as JSON. + /// + /// The writer to write JSON to. + /// The enum value to convert to JSON. + /// The serializer options to use. public override void Write( Utf8JsonWriter writer, ApiEnum value, JsonSerializerOptions options - ) - { - JsonSerializer.Serialize(writer, value.Json, options); - } + ) => JsonSerializer.Serialize(writer, value.Json, options); } diff --git a/src/ArcadeDotnet/Core/ArcadeRequest.cs b/src/ArcadeDotnet/Core/ArcadeRequest.cs new file mode 100644 index 0000000..4282848 --- /dev/null +++ b/src/ArcadeDotnet/Core/ArcadeRequest.cs @@ -0,0 +1,12 @@ +using System.Net.Http; + +namespace ArcadeDotnet.Core; + +/// +/// Represents an API request with strongly-typed parameters. +/// +/// The type of request parameters. +/// The HTTP method for the request. +/// The request parameters. +public sealed record ArcadeRequest(HttpMethod Method, TParams Params) + where TParams : ParamsBase; diff --git a/src/ArcadeDotnet/Core/ArcadeResponse.cs b/src/ArcadeDotnet/Core/ArcadeResponse.cs new file mode 100644 index 0000000..c2cb483 --- /dev/null +++ b/src/ArcadeDotnet/Core/ArcadeResponse.cs @@ -0,0 +1,51 @@ +using System; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using ArcadeDotnet.Exceptions; + +namespace ArcadeDotnet.Core; + +/// +/// Represents an API response with deserialization capabilities. +/// +/// +/// Implements . Always use within a using statement to ensure proper resource disposal. +/// +public sealed record ArcadeResponse : IDisposable +{ + /// + /// Gets the underlying HTTP response message. + /// + public required HttpResponseMessage Message { get; init; } + + /// + /// Deserializes the response content to the specified type. + /// + /// The type to deserialize into. + /// The deserialized object. + /// Thrown when deserialization fails. + /// Thrown when an I/O error occurs. + public async Task Deserialize() + { + try + { + return JsonSerializer.Deserialize( + await Message.Content.ReadAsStreamAsync().ConfigureAwait(false), + ModelBase.SerializerOptions + ) ?? throw new ArcadeInvalidDataException("Response content cannot be null or deserialization failed"); + } + catch (HttpRequestException ex) + { + throw new ArcadeIOException("I/O error occurred while reading response content", ex); + } + } + + /// + /// Disposes the underlying HTTP response resources. + /// + public void Dispose() + { + Message.Dispose(); + } +} diff --git a/src/ArcadeDotnet/Core/HttpRequest.cs b/src/ArcadeDotnet/Core/HttpRequest.cs deleted file mode 100644 index 9d5422b..0000000 --- a/src/ArcadeDotnet/Core/HttpRequest.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Net.Http; - -namespace ArcadeDotnet.Core; - -public sealed class HttpRequest

- where P : ParamsBase -{ - public required HttpMethod Method { get; init; } - - public required P Params { get; init; } -} diff --git a/src/ArcadeDotnet/Core/HttpResponse.cs b/src/ArcadeDotnet/Core/HttpResponse.cs deleted file mode 100644 index e30874b..0000000 --- a/src/ArcadeDotnet/Core/HttpResponse.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using ArcadeDotnet.Exceptions; - -namespace ArcadeDotnet.Core; - -public sealed class HttpResponse : IDisposable -{ - public required HttpResponseMessage Message { get; init; } - - public async Task Deserialize() - { - try - { - return JsonSerializer.Deserialize( - await Message.Content.ReadAsStreamAsync().ConfigureAwait(false), - ModelBase.SerializerOptions - ) ?? throw new ArcadeInvalidDataException("Response cannot be null"); - } - catch (HttpRequestException e) - { - throw new ArcadeIOException("I/O Exception", e); - } - } - - public void Dispose() - { - this.Message.Dispose(); - } -} diff --git a/src/ArcadeDotnet/Core/IVariant.cs b/src/ArcadeDotnet/Core/IVariant.cs index 23a224c..738a0f5 100644 --- a/src/ArcadeDotnet/Core/IVariant.cs +++ b/src/ArcadeDotnet/Core/IVariant.cs @@ -1,8 +1,27 @@ namespace ArcadeDotnet.Core; -interface IVariant +///

+/// Interface for creating variant types that wrap values. +/// +/// The variant type implementing this interface. +/// The type of value being wrapped by the variant. +/// +/// This interface enables the creation of strongly-typed wrappers around values, +/// useful for implementing the variant pattern in API models. +/// +internal interface IVariant where TVariant : IVariant { + /// + /// Creates a variant instance from a value. + /// + /// The value to wrap in the variant. + /// A new variant instance containing the specified value. static abstract TVariant From(TValue value); + + /// + /// Gets the wrapped value. + /// + /// The value contained in this variant. TValue Value { get; } } diff --git a/src/ArcadeDotnet/Exceptions/Arcade4xxException.cs b/src/ArcadeDotnet/Exceptions/Arcade4xxException.cs index e1770f6..30e067c 100644 --- a/src/ArcadeDotnet/Exceptions/Arcade4xxException.cs +++ b/src/ArcadeDotnet/Exceptions/Arcade4xxException.cs @@ -2,8 +2,15 @@ namespace ArcadeDotnet.Exceptions; +/// +/// Exception thrown when the API returns a 4xx client error status code. +/// public class Arcade4xxException : ArcadeApiException { + /// + /// Initializes a new instance of the class. + /// + /// The HTTP request exception that is the cause of the current exception, or null if no inner exception is specified. public Arcade4xxException(HttpRequestException? innerException = null) : base(innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/Arcade5xxException.cs b/src/ArcadeDotnet/Exceptions/Arcade5xxException.cs index 4bba36a..cba6092 100644 --- a/src/ArcadeDotnet/Exceptions/Arcade5xxException.cs +++ b/src/ArcadeDotnet/Exceptions/Arcade5xxException.cs @@ -2,8 +2,15 @@ namespace ArcadeDotnet.Exceptions; -public class Arcade5xxException : ArcadeApiException +/// +/// Exception thrown when the API returns a 5xx server error status code. +/// +public sealed class Arcade5xxException : ArcadeApiException { + /// + /// Initializes a new instance of the class. + /// + /// The HTTP request exception that is the cause of the current exception, or null if no inner exception is specified. public Arcade5xxException(HttpRequestException? innerException = null) : base(innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/ArcadeApiException.cs b/src/ArcadeDotnet/Exceptions/ArcadeApiException.cs index e0cc812..c8890a9 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeApiException.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeApiException.cs @@ -4,32 +4,63 @@ namespace ArcadeDotnet.Exceptions; +/// +/// Exception thrown when the API returns an error status code. +/// +[Serializable] public class ArcadeApiException : ArcadeException { + /// + /// Gets the HTTP request exception that caused this exception. + /// + /// + /// The that is the cause of the current exception. + /// + /// Thrown when the inner exception is null. public new HttpRequestException InnerException { get { if (base.InnerException == null) { - throw new ArgumentNullException(); + throw new InvalidOperationException("InnerException is null"); } return (HttpRequestException)base.InnerException; } } - public ArcadeApiException(string message, HttpRequestException? innerException = null) - : base(message, innerException) { } - - protected ArcadeApiException(HttpRequestException? innerException) - : base(innerException) { } - + /// + /// Gets the HTTP status code returned by the API. + /// + /// + /// The returned by the API. + /// public required HttpStatusCode StatusCode { get; init; } + /// + /// Gets the response body returned by the API. + /// + /// + /// The response body as a string, which may contain error details from the API. + /// public required string ResponseBody { get; init; } - public override string Message - { - get { return string.Format("Status Code: {0}\n{1}", StatusCode, ResponseBody); } - } + /// + /// Gets a message that describes the current exception. + /// + /// + /// A string containing the HTTP status code and response body. + /// + public override string Message => $"Status Code: {StatusCode}\n{ResponseBody}"; + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The HTTP request exception that is the cause of the current exception, or null if no inner exception is specified. + public ArcadeApiException(string message, HttpRequestException? innerException = null) + : base(message, innerException) { } + + internal ArcadeApiException(HttpRequestException? innerException) + : base(innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/ArcadeBadRequestException.cs b/src/ArcadeDotnet/Exceptions/ArcadeBadRequestException.cs index 119cbca..b7de3c9 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeBadRequestException.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeBadRequestException.cs @@ -2,8 +2,15 @@ namespace ArcadeDotnet.Exceptions; -public class ArcadeBadRequestException : Arcade4xxException +/// +/// Exception thrown when the API returns a 400 Bad Request status code. +/// +public sealed class ArcadeBadRequestException : Arcade4xxException { + /// + /// Initializes a new instance of the class. + /// + /// The HTTP request exception that is the cause of the current exception, or null if no inner exception is specified. public ArcadeBadRequestException(HttpRequestException? innerException = null) : base(innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/ArcadeException.cs b/src/ArcadeDotnet/Exceptions/ArcadeException.cs index f96f585..05e4ab1 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeException.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeException.cs @@ -3,11 +3,24 @@ namespace ArcadeDotnet.Exceptions; +/// +/// Base exception for all Arcade API exceptions. +/// +[Serializable] public class ArcadeException : Exception { + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or null if no inner exception is specified. public ArcadeException(string message, Exception? innerException = null) : base(message, innerException) { } + /// + /// Initializes a new instance of the class with an HTTP request exception. + /// + /// The HTTP request exception that is the cause of the current exception. protected ArcadeException(HttpRequestException? innerException) : base(null, innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/ArcadeExceptionFactory.cs b/src/ArcadeDotnet/Exceptions/ArcadeExceptionFactory.cs index f7df7f7..8fa4ee6 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeExceptionFactory.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeExceptionFactory.cs @@ -2,8 +2,17 @@ namespace ArcadeDotnet.Exceptions; -public class ArcadeExceptionFactory +/// +/// Factory for creating exception instances based on HTTP status codes. +/// +public static class ArcadeExceptionFactory { + /// + /// Creates an appropriate exception for the given HTTP status code. + /// + /// The HTTP status code. + /// The response body containing error details. + /// An or derived type. public static ArcadeApiException CreateApiException( HttpStatusCode statusCode, string responseBody diff --git a/src/ArcadeDotnet/Exceptions/ArcadeForbiddenException.cs b/src/ArcadeDotnet/Exceptions/ArcadeForbiddenException.cs index b2689af..1140f43 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeForbiddenException.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeForbiddenException.cs @@ -2,8 +2,15 @@ namespace ArcadeDotnet.Exceptions; -public class ArcadeForbiddenException : Arcade4xxException +/// +/// Exception thrown when the API returns a 403 Forbidden status code. +/// +public sealed class ArcadeForbiddenException : Arcade4xxException { + /// + /// Initializes a new instance of the class. + /// + /// The HTTP request exception that is the cause of the current exception, or null if no inner exception is specified. public ArcadeForbiddenException(HttpRequestException? innerException = null) : base(innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/ArcadeIOException.cs b/src/ArcadeDotnet/Exceptions/ArcadeIOException.cs index 84f1c26..73aaf86 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeIOException.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeIOException.cs @@ -3,20 +3,36 @@ namespace ArcadeDotnet.Exceptions; -public class ArcadeIOException : ArcadeException +/// +/// Exception thrown when an I/O error occurs during an API request. +/// +[Serializable] +public sealed class ArcadeIOException : ArcadeException { + /// + /// Gets the HTTP request exception that caused this exception. + /// + /// + /// The that is the cause of the current exception. + /// + /// Thrown when the inner exception is null. public new HttpRequestException InnerException { get { if (base.InnerException == null) { - throw new ArgumentNullException(); + throw new InvalidOperationException("InnerException is null"); } return (HttpRequestException)base.InnerException; } } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The HTTP request exception that is the cause of the current exception, or null if no inner exception is specified. public ArcadeIOException(string message, HttpRequestException? innerException = null) : base(message, innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/ArcadeInvalidDataException.cs b/src/ArcadeDotnet/Exceptions/ArcadeInvalidDataException.cs index 9cb9724..cc94b56 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeInvalidDataException.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeInvalidDataException.cs @@ -2,8 +2,17 @@ namespace ArcadeDotnet.Exceptions; -public class ArcadeInvalidDataException : ArcadeException +/// +/// Exception thrown when invalid data is encountered. +/// +[Serializable] +public sealed class ArcadeInvalidDataException : ArcadeException { + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or null if no inner exception is specified. public ArcadeInvalidDataException(string message, Exception? innerException = null) : base(message, innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/ArcadeNotFoundException.cs b/src/ArcadeDotnet/Exceptions/ArcadeNotFoundException.cs index 059c618..aa8e613 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeNotFoundException.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeNotFoundException.cs @@ -2,8 +2,15 @@ namespace ArcadeDotnet.Exceptions; -public class ArcadeNotFoundException : Arcade4xxException +/// +/// Exception thrown when the API returns a 404 Not Found status code. +/// +public sealed class ArcadeNotFoundException : Arcade4xxException { + /// + /// Initializes a new instance of the class. + /// + /// The HTTP request exception that is the cause of the current exception, or null if no inner exception is specified. public ArcadeNotFoundException(HttpRequestException? innerException = null) : base(innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/ArcadeRateLimitException.cs b/src/ArcadeDotnet/Exceptions/ArcadeRateLimitException.cs index 6eb7c34..9b84d5e 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeRateLimitException.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeRateLimitException.cs @@ -2,8 +2,15 @@ namespace ArcadeDotnet.Exceptions; -public class ArcadeRateLimitException : Arcade4xxException +/// +/// Exception thrown when the API returns a 429 Too Many Requests status code. +/// +public sealed class ArcadeRateLimitException : Arcade4xxException { + /// + /// Initializes a new instance of the class. + /// + /// The HTTP request exception that is the cause of the current exception, or null if no inner exception is specified. public ArcadeRateLimitException(HttpRequestException? innerException = null) : base(innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/ArcadeUnauthorizedException.cs b/src/ArcadeDotnet/Exceptions/ArcadeUnauthorizedException.cs index e386192..bfde690 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeUnauthorizedException.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeUnauthorizedException.cs @@ -2,8 +2,15 @@ namespace ArcadeDotnet.Exceptions; -public class ArcadeUnauthorizedException : Arcade4xxException +/// +/// Exception thrown when the API returns a 401 Unauthorized status code. +/// +public sealed class ArcadeUnauthorizedException : Arcade4xxException { + /// + /// Initializes a new instance of the class. + /// + /// The HTTP request exception that is the cause of the current exception, or null if no inner exception is specified. public ArcadeUnauthorizedException(HttpRequestException? innerException = null) : base(innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/ArcadeUnexpectedStatusCodeException.cs b/src/ArcadeDotnet/Exceptions/ArcadeUnexpectedStatusCodeException.cs index fc0b6c7..69f408f 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeUnexpectedStatusCodeException.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeUnexpectedStatusCodeException.cs @@ -2,8 +2,15 @@ namespace ArcadeDotnet.Exceptions; -public class ArcadeUnexpectedStatusCodeException : ArcadeApiException +/// +/// Exception thrown when the API returns an unexpected HTTP status code. +/// +public sealed class ArcadeUnexpectedStatusCodeException : ArcadeApiException { + /// + /// Initializes a new instance of the class. + /// + /// The HTTP request exception that is the cause of the current exception, or null if no inner exception is specified. public ArcadeUnexpectedStatusCodeException(HttpRequestException? innerException = null) : base(innerException) { } } diff --git a/src/ArcadeDotnet/Exceptions/ArcadeUnprocessableEntityException.cs b/src/ArcadeDotnet/Exceptions/ArcadeUnprocessableEntityException.cs index 1b2e954..ea26122 100644 --- a/src/ArcadeDotnet/Exceptions/ArcadeUnprocessableEntityException.cs +++ b/src/ArcadeDotnet/Exceptions/ArcadeUnprocessableEntityException.cs @@ -2,8 +2,15 @@ namespace ArcadeDotnet.Exceptions; -public class ArcadeUnprocessableEntityException : Arcade4xxException +/// +/// Exception thrown when the API returns a 422 Unprocessable Entity status code. +/// +public sealed class ArcadeUnprocessableEntityException : Arcade4xxException { + /// + /// Initializes a new instance of the class. + /// + /// The HTTP request exception that is the cause of the current exception, or null if no inner exception is specified. public ArcadeUnprocessableEntityException(HttpRequestException? innerException = null) : base(innerException) { } } diff --git a/src/ArcadeDotnet/Extensions/ServiceCollectionExtensions.cs b/src/ArcadeDotnet/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..ed42486 --- /dev/null +++ b/src/ArcadeDotnet/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,72 @@ +using System; +using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace ArcadeDotnet.Extensions; + +/// +/// Extension methods for configuring ArcadeClient in dependency injection containers. +/// +public static class ServiceCollectionExtensions +{ + /// + /// Adds ArcadeClient services to the dependency injection container. + /// + public static IServiceCollection AddArcadeClient( + this IServiceCollection services, + string apiKey, + Uri? baseUrl = null) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(apiKey); + + services.AddHttpClient("ArcadeClient", client => + { + client.BaseAddress = baseUrl ?? new Uri(ArcadeClientOptions.DefaultBaseUrl); + client.DefaultRequestHeaders.UserAgent.ParseAdd("arcade-dotnet/0.2.0"); + }); + + services.AddSingleton(sp => + { + var httpClientFactory = sp.GetRequiredService(); + return new ArcadeClient(new ArcadeClientOptions + { + ApiKey = apiKey, + BaseUrl = baseUrl, + HttpClientFactory = httpClientFactory + }); + }); + + return services; + } + + /// + /// Adds ArcadeClient services using environment variables. + /// + public static IServiceCollection AddArcadeClient(this IServiceCollection services) + { + ArgumentNullException.ThrowIfNull(services); + + var baseUrl = ArcadeClientOptions.TryParseBaseUrl( + Environment.GetEnvironmentVariable(ArcadeClientOptions.BaseUrlEnvironmentVariable)); + + services.AddHttpClient("ArcadeClient", client => + { + client.BaseAddress = baseUrl ?? new Uri(ArcadeClientOptions.DefaultBaseUrl); + client.DefaultRequestHeaders.UserAgent.ParseAdd("arcade-dotnet/0.2.0"); + }); + + services.AddSingleton(sp => + { + var httpClientFactory = sp.GetRequiredService(); + return new ArcadeClient(new ArcadeClientOptions + { + ApiKey = Environment.GetEnvironmentVariable(ArcadeClientOptions.ApiKeyEnvironmentVariable), + BaseUrl = baseUrl, + HttpClientFactory = httpClientFactory + }); + }); + + return services; + } +} diff --git a/src/ArcadeDotnet/IArcadeClient.cs b/src/ArcadeDotnet/IArcadeClient.cs index 3fed6be..4f34f53 100644 --- a/src/ArcadeDotnet/IArcadeClient.cs +++ b/src/ArcadeDotnet/IArcadeClient.cs @@ -11,16 +11,20 @@ namespace ArcadeDotnet; +/// +/// Interface for the Arcade API client. +/// public interface IArcadeClient { - HttpClient HttpClient { get; init; } - - Uri BaseUrl { get; init; } + /// + /// Gets the base URL for the API. + /// + Uri BaseUrl { get; } /// - /// API key used for authorization in header + /// Gets the API key used for authorization. /// - string APIKey { get; init; } + string APIKey { get; } IAdminService Admin { get; } @@ -34,6 +38,14 @@ public interface IArcadeClient IWorkerService Workers { get; } - Task Execute(HttpRequest request) - where T : ParamsBase; + /// + /// Executes an API request and returns the response. + /// + /// The type of parameters. + /// The request to execute. + /// The API response. + /// Thrown when an I/O error occurs. + /// Thrown when the API returns an error. + Task Execute(ArcadeRequest request) + where TParams : ParamsBase; } diff --git a/src/ArcadeDotnet/Models/Admin/AuthProviders/AuthProviderCreateParamsProperties/Oauth2Properties/AuthorizeRequestProperties/RequestContentType.cs b/src/ArcadeDotnet/Models/Admin/AuthProviders/AuthProviderCreateParamsProperties/Oauth2Properties/AuthorizeRequestProperties/RequestContentType.cs index b1618e6..2c1943c 100644 --- a/src/ArcadeDotnet/Models/Admin/AuthProviders/AuthProviderCreateParamsProperties/Oauth2Properties/AuthorizeRequestProperties/RequestContentType.cs +++ b/src/ArcadeDotnet/Models/Admin/AuthProviders/AuthProviderCreateParamsProperties/Oauth2Properties/AuthorizeRequestProperties/RequestContentType.cs @@ -12,7 +12,7 @@ public enum RequestContentType ApplicationJson, } -sealed class RequestContentTypeConverter : JsonConverter +internal sealed class RequestContentTypeConverter : JsonConverter { public override RequestContentType Read( ref Utf8JsonReader reader, @@ -42,7 +42,7 @@ JsonSerializerOptions options "application/x-www-form-urlencoded", RequestContentType.ApplicationJson => "application/json", _ => throw new ArcadeInvalidDataException( - string.Format("Invalid value '{0}' in {1}", value, nameof(value)) + $"Invalid value '{value}' in {nameof(value)}" ), }, options diff --git a/src/ArcadeDotnet/Models/Admin/AuthProviders/AuthProviderCreateParamsProperties/Oauth2Properties/AuthorizeRequestProperties/ResponseContentType.cs b/src/ArcadeDotnet/Models/Admin/AuthProviders/AuthProviderCreateParamsProperties/Oauth2Properties/AuthorizeRequestProperties/ResponseContentType.cs index 4a97579..35d784f 100644 --- a/src/ArcadeDotnet/Models/Admin/AuthProviders/AuthProviderCreateParamsProperties/Oauth2Properties/AuthorizeRequestProperties/ResponseContentType.cs +++ b/src/ArcadeDotnet/Models/Admin/AuthProviders/AuthProviderCreateParamsProperties/Oauth2Properties/AuthorizeRequestProperties/ResponseContentType.cs @@ -12,7 +12,7 @@ public enum ResponseContentType ApplicationJson, } -sealed class ResponseContentTypeConverter : JsonConverter +internal sealed class ResponseContentTypeConverter : JsonConverter { public override ResponseContentType Read( ref Utf8JsonReader reader, @@ -43,7 +43,7 @@ JsonSerializerOptions options "application/x-www-form-urlencoded", ResponseContentType.ApplicationJson => "application/json", _ => throw new ArcadeInvalidDataException( - string.Format("Invalid value '{0}' in {1}", value, nameof(value)) + $"Invalid value '{value}' in {nameof(value)}" ), }, options diff --git a/src/ArcadeDotnet/Properties/AssemblyInfo.cs b/src/ArcadeDotnet/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..53cf08a --- /dev/null +++ b/src/ArcadeDotnet/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +// Allow test project to access internal types and members +[assembly: InternalsVisibleTo("ArcadeDotnet.Tests")] + diff --git a/src/ArcadeDotnet/Services/Admin/AdminService.cs b/src/ArcadeDotnet/Services/Admin/AdminService.cs index ebcab64..001964a 100644 --- a/src/ArcadeDotnet/Services/Admin/AdminService.cs +++ b/src/ArcadeDotnet/Services/Admin/AdminService.cs @@ -7,28 +7,31 @@ namespace ArcadeDotnet.Services.Admin; public sealed class AdminService : IAdminService { - public AdminService(IArcadeClient client) - { - _userConnections = new(() => new UserConnectionService(client)); - _authProviders = new(() => new AuthProviderService(client)); - _secrets = new(() => new SecretService(client)); - } + /// + /// Gets the user connections service. + /// + public IUserConnectionService UserConnections { get; } - readonly Lazy _userConnections; - public IUserConnectionService UserConnections - { - get { return _userConnections.Value; } - } + /// + /// Gets the auth providers service. + /// + public IAuthProviderService AuthProviders { get; } - readonly Lazy _authProviders; - public IAuthProviderService AuthProviders - { - get { return _authProviders.Value; } - } + /// + /// Gets the secrets service. + /// + public ISecretService Secrets { get; } - readonly Lazy _secrets; - public ISecretService Secrets + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. + public AdminService(IArcadeClient client) { - get { return _secrets.Value; } + ArgumentNullException.ThrowIfNull(client); + UserConnections = new UserConnectionService(client); + AuthProviders = new AuthProviderService(client); + Secrets = new SecretService(client); } } diff --git a/src/ArcadeDotnet/Services/Admin/AuthProviders/AuthProviderService.cs b/src/ArcadeDotnet/Services/Admin/AuthProviders/AuthProviderService.cs index a288fcd..1edf130 100644 --- a/src/ArcadeDotnet/Services/Admin/AuthProviders/AuthProviderService.cs +++ b/src/ArcadeDotnet/Services/Admin/AuthProviders/AuthProviderService.cs @@ -1,3 +1,4 @@ +using System; using System.Net.Http; using System.Threading.Tasks; using ArcadeDotnet.Core; @@ -7,67 +8,52 @@ namespace ArcadeDotnet.Services.Admin.AuthProviders; public sealed class AuthProviderService : IAuthProviderService { - readonly IArcadeClient _client; + private readonly IArcadeClient _client; + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. public AuthProviderService(IArcadeClient client) { + ArgumentNullException.ThrowIfNull(client); _client = client; } public async Task Create(AuthProviderCreateParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Post, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Post, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task List(AuthProviderListParams? parameters = null) { parameters ??= new(); - - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Delete(AuthProviderDeleteParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Delete, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Delete, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Get(AuthProviderGetParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Patch(AuthProviderPatchParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Patch, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Patch, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } } diff --git a/src/ArcadeDotnet/Services/Admin/Secrets/SecretService.cs b/src/ArcadeDotnet/Services/Admin/Secrets/SecretService.cs index 0a37baf..4b98674 100644 --- a/src/ArcadeDotnet/Services/Admin/Secrets/SecretService.cs +++ b/src/ArcadeDotnet/Services/Admin/Secrets/SecretService.cs @@ -1,3 +1,4 @@ +using System; using System.Net.Http; using System.Threading.Tasks; using ArcadeDotnet.Core; @@ -7,34 +8,30 @@ namespace ArcadeDotnet.Services.Admin.Secrets; public sealed class SecretService : ISecretService { - readonly IArcadeClient _client; + private readonly IArcadeClient _client; + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. public SecretService(IArcadeClient client) { + ArgumentNullException.ThrowIfNull(client); _client = client; } public async Task List(SecretListParams? parameters = null) { parameters ??= new(); - - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Delete(SecretDeleteParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Delete, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); - return; + var request = new ArcadeRequest(HttpMethod.Delete, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); } } diff --git a/src/ArcadeDotnet/Services/Admin/UserConnections/UserConnectionService.cs b/src/ArcadeDotnet/Services/Admin/UserConnections/UserConnectionService.cs index d3e4e22..67becc1 100644 --- a/src/ArcadeDotnet/Services/Admin/UserConnections/UserConnectionService.cs +++ b/src/ArcadeDotnet/Services/Admin/UserConnections/UserConnectionService.cs @@ -1,3 +1,4 @@ +using System; using System.Net.Http; using System.Threading.Tasks; using ArcadeDotnet.Core; @@ -7,10 +8,16 @@ namespace ArcadeDotnet.Services.Admin.UserConnections; public sealed class UserConnectionService : IUserConnectionService { - readonly IArcadeClient _client; + private readonly IArcadeClient _client; + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. public UserConnectionService(IArcadeClient client) { + ArgumentNullException.ThrowIfNull(client); _client = client; } @@ -19,24 +26,14 @@ public async Task List( ) { parameters ??= new(); - - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Delete(UserConnectionDeleteParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Delete, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); - return; + var request = new ArcadeRequest(HttpMethod.Delete, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); } } diff --git a/src/ArcadeDotnet/Services/Auth/AuthService.cs b/src/ArcadeDotnet/Services/Auth/AuthService.cs index 34a8d1d..9d745b3 100644 --- a/src/ArcadeDotnet/Services/Auth/AuthService.cs +++ b/src/ArcadeDotnet/Services/Auth/AuthService.cs @@ -1,3 +1,4 @@ +using System; using System.Net.Http; using System.Threading.Tasks; using ArcadeDotnet.Core; @@ -6,45 +7,57 @@ namespace ArcadeDotnet.Services.Auth; +/// +/// Service for handling authentication and authorization operations. +/// public sealed class AuthService : IAuthService { - readonly IArcadeClient _client; + private readonly IArcadeClient _client; + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. public AuthService(IArcadeClient client) { + ArgumentNullException.ThrowIfNull(client); _client = client; } + /// + /// Starts the authorization process. + /// + /// The authorization parameters. + /// The authorization response. public async Task Authorize(AuthAuthorizeParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Post, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Post, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } + /// + /// Confirms a user's details during authorization. + /// + /// The confirmation parameters. + /// The confirmation response. public async Task ConfirmUser(AuthConfirmUserParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Post, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Post, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } + /// + /// Checks the authorization status. + /// + /// The status parameters. + /// The authorization status. public async Task Status(AuthStatusParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } } diff --git a/src/ArcadeDotnet/Services/Chat/ChatService.cs b/src/ArcadeDotnet/Services/Chat/ChatService.cs index 89b0186..04ea954 100644 --- a/src/ArcadeDotnet/Services/Chat/ChatService.cs +++ b/src/ArcadeDotnet/Services/Chat/ChatService.cs @@ -5,14 +5,19 @@ namespace ArcadeDotnet.Services.Chat; public sealed class ChatService : IChatService { - public ChatService(IArcadeClient client) - { - _completions = new(() => new CompletionService(client)); - } + /// + /// Gets the completions service. + /// + public ICompletionService Completions { get; } - readonly Lazy _completions; - public ICompletionService Completions + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. + public ChatService(IArcadeClient client) { - get { return _completions.Value; } + ArgumentNullException.ThrowIfNull(client); + Completions = new CompletionService(client); } } diff --git a/src/ArcadeDotnet/Services/Chat/Completions/CompletionService.cs b/src/ArcadeDotnet/Services/Chat/Completions/CompletionService.cs index fef80dd..a280ac0 100644 --- a/src/ArcadeDotnet/Services/Chat/Completions/CompletionService.cs +++ b/src/ArcadeDotnet/Services/Chat/Completions/CompletionService.cs @@ -1,3 +1,4 @@ +using System; using System.Net.Http; using System.Threading.Tasks; using ArcadeDotnet.Core; @@ -8,23 +9,24 @@ namespace ArcadeDotnet.Services.Chat.Completions; public sealed class CompletionService : ICompletionService { - readonly IArcadeClient _client; + private readonly IArcadeClient _client; + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. public CompletionService(IArcadeClient client) { + ArgumentNullException.ThrowIfNull(client); _client = client; } public async Task Create(CompletionCreateParams? parameters = null) { parameters ??= new(); - - HttpRequest request = new() - { - Method = HttpMethod.Post, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Post, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } } diff --git a/src/ArcadeDotnet/Services/Health/HealthService.cs b/src/ArcadeDotnet/Services/Health/HealthService.cs index 99d4ef3..f1db64d 100644 --- a/src/ArcadeDotnet/Services/Health/HealthService.cs +++ b/src/ArcadeDotnet/Services/Health/HealthService.cs @@ -1,3 +1,4 @@ +using System; using System.Net.Http; using System.Threading.Tasks; using ArcadeDotnet.Core; @@ -7,23 +8,24 @@ namespace ArcadeDotnet.Services.Health; public sealed class HealthService : IHealthService { - readonly IArcadeClient _client; + private readonly IArcadeClient _client; + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. public HealthService(IArcadeClient client) { + ArgumentNullException.ThrowIfNull(client); _client = client; } public async Task Check(HealthCheckParams? parameters = null) { parameters ??= new(); - - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } } diff --git a/src/ArcadeDotnet/Services/Tools/Formatted/FormattedService.cs b/src/ArcadeDotnet/Services/Tools/Formatted/FormattedService.cs index 5f80e74..41ad831 100644 --- a/src/ArcadeDotnet/Services/Tools/Formatted/FormattedService.cs +++ b/src/ArcadeDotnet/Services/Tools/Formatted/FormattedService.cs @@ -1,3 +1,4 @@ +using System; using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; @@ -8,34 +9,31 @@ namespace ArcadeDotnet.Services.Tools.Formatted; public sealed class FormattedService : IFormattedService { - readonly IArcadeClient _client; + private readonly IArcadeClient _client; + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. public FormattedService(IArcadeClient client) { + ArgumentNullException.ThrowIfNull(client); _client = client; } public async Task List(FormattedListParams? parameters = null) { parameters ??= new(); - - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Get(FormattedGetParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } } diff --git a/src/ArcadeDotnet/Services/Tools/Scheduled/ScheduledService.cs b/src/ArcadeDotnet/Services/Tools/Scheduled/ScheduledService.cs index 3f333a0..8e517ba 100644 --- a/src/ArcadeDotnet/Services/Tools/Scheduled/ScheduledService.cs +++ b/src/ArcadeDotnet/Services/Tools/Scheduled/ScheduledService.cs @@ -1,3 +1,4 @@ +using System; using System.Net.Http; using System.Threading.Tasks; using ArcadeDotnet.Core; @@ -7,34 +8,31 @@ namespace ArcadeDotnet.Services.Tools.Scheduled; public sealed class ScheduledService : IScheduledService { - readonly IArcadeClient _client; + private readonly IArcadeClient _client; + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. public ScheduledService(IArcadeClient client) { + ArgumentNullException.ThrowIfNull(client); _client = client; } public async Task List(ScheduledListParams? parameters = null) { parameters ??= new(); - - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Get(ScheduledGetParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } } diff --git a/src/ArcadeDotnet/Services/Tools/ToolService.cs b/src/ArcadeDotnet/Services/Tools/ToolService.cs index e72c699..2b5c027 100644 --- a/src/ArcadeDotnet/Services/Tools/ToolService.cs +++ b/src/ArcadeDotnet/Services/Tools/ToolService.cs @@ -11,66 +11,57 @@ namespace ArcadeDotnet.Services.Tools; public sealed class ToolService : IToolService { - readonly IArcadeClient _client; + private readonly IArcadeClient _client; - public ToolService(IArcadeClient client) - { - _client = client; - _scheduled = new(() => new ScheduledService(client)); - _formatted = new(() => new FormattedService(client)); - } + /// + /// Gets the scheduled tools service. + /// + public IScheduledService Scheduled { get; } - readonly Lazy _scheduled; - public IScheduledService Scheduled - { - get { return _scheduled.Value; } - } + /// + /// Gets the formatted tools service. + /// + public IFormattedService Formatted { get; } - readonly Lazy _formatted; - public IFormattedService Formatted + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. + public ToolService(IArcadeClient client) { - get { return _formatted.Value; } + ArgumentNullException.ThrowIfNull(client); + _client = client; + Scheduled = new ScheduledService(client); + Formatted = new FormattedService(client); } public async Task List(ToolListParams? parameters = null) { parameters ??= new(); - - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Authorize(ToolAuthorizeParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Post, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Post, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Execute(ToolExecuteParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Post, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Post, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Get(ToolGetParams parameters) { - HttpRequest request = new() { Method = HttpMethod.Get, Params = parameters }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } } diff --git a/src/ArcadeDotnet/Services/Workers/WorkerService.cs b/src/ArcadeDotnet/Services/Workers/WorkerService.cs index 6a90c28..bd98c31 100644 --- a/src/ArcadeDotnet/Services/Workers/WorkerService.cs +++ b/src/ArcadeDotnet/Services/Workers/WorkerService.cs @@ -1,3 +1,4 @@ +using System; using System.Net.Http; using System.Threading.Tasks; using ArcadeDotnet.Core; @@ -7,89 +8,65 @@ namespace ArcadeDotnet.Services.Workers; public sealed class WorkerService : IWorkerService { - readonly IArcadeClient _client; + private readonly IArcadeClient _client; + /// + /// Initializes a new instance of the class. + /// + /// The Arcade client instance. + /// Thrown when is null. public WorkerService(IArcadeClient client) { + ArgumentNullException.ThrowIfNull(client); _client = client; } public async Task Create(WorkerCreateParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Post, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Post, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Update(WorkerUpdateParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Patch, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Patch, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task List(WorkerListParams? parameters = null) { parameters ??= new(); - - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Delete(WorkerDeleteParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Delete, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); - return; + var request = new ArcadeRequest(HttpMethod.Delete, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); } public async Task Get(WorkerGetParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Health(WorkerHealthParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } public async Task Tools(WorkerToolsParams parameters) { - HttpRequest request = new() - { - Method = HttpMethod.Get, - Params = parameters, - }; - using var response = await this._client.Execute(request).ConfigureAwait(false); + var request = new ArcadeRequest(HttpMethod.Get, parameters); + using var response = await _client.Execute(request).ConfigureAwait(false); return await response.Deserialize().ConfigureAwait(false); } }