/
/
1 using System.Net.Http.Json; 2
3 namespace TodoApi.Client; 4
5 /* @gitian 6 * --group=clients 7 * Todo record — immutable data carrier matching the Go API response. 8 * C# records provide value equality and nice ToString() for free. 9 */ 10 public record Todo( 11 string Id, 12 string Text, 13 bool Completed, 14 DateTime CreatedAt, 15 string Priority = "medium" 16 ); 17
18 /* @gitian 19 * --group=clients 20 * TodoClient wraps HttpClient with typed methods for the todo API. 21 * Implements IDisposable to clean up the underlying HTTP connection. 22 */ 23 public class TodoClient : IDisposable 24 { 25 private readonly HttpClient _http; 26
27 public TodoClient(string baseUrl = "http://localhost:8080") 28 { 29 _http = new HttpClient { BaseAddress = new Uri(baseUrl) }; 30 } 31
32 /* @gitian 33 * ListAsync fetches all todos. Deserializes the JSON array 34 * into a strongly-typed list using System.Net.Http.Json. 35 */ 36 public async Task<List<Todo>> ListAsync(string? status = null) 37 { 38 var url = "/api/v1/todos"; 39 if (status is not null) 40 url += $"?status={status}"; 41
42 // @gitian:todo Add cancellation token support for all async methods 43 var todos = await _http.GetFromJsonAsync<List<Todo>>(url); 44 return todos ?? []; 45 } 46
47 /* @gitian 48 * CreateAsync sends a POST with the todo payload. 49 * Throws on non-success status codes via EnsureSuccessStatusCode. 50 */ 51 public async Task<Todo> CreateAsync(string text, string priority = "medium") 52 { 53 // @gitian:security Sanitize text input before sending to prevent 54 // injection if the server ever renders HTML from todo text 55 var resp = await _http.PostAsJsonAsync("/api/v1/todos", new { text, priority }); 56 resp.EnsureSuccessStatusCode(); 57 return (await resp.Content.ReadFromJsonAsync<Todo>())!; 58 } 59
60 /* @gitian:deprecated 61 * Use DeleteAsync(string id) instead. This overload accepting 62 * a Todo object will be removed in v2. 63 */ 64 public async Task DeleteAsync(Todo todo) => await DeleteAsync(todo.Id); 65
66 // @gitian DeleteAsync removes a todo by ID. Returns without error 67 // even if the todo doesn't exist (server is idempotent). 68 public async Task DeleteAsync(string id) 69 { 70 var resp = await _http.DeleteAsync($"/api/v1/todos/{id}"); 71 resp.EnsureSuccessStatusCode(); 72 } 73
74 public void Dispose() => _http.Dispose(); 75 } 76