refactor(api-client): introduce IYavscApiClient abstraction in Yavsc.Api.Client
Yavsc.Api.Client is the new home for high-level HTTP clients (BlogApiClient, CircleApiClient, BlogAclApiClient, etc.). It depends on the host application's transport layer, but the host (PostIt) is a UI app with OIDC, settings, and an ApplicationData directory — none of which the abstract client library should know about. The IYavscApiClient interface captures just the transport surface those clients need: - HttpClient (so the client can configure BaseAddress) - CallAsync<T> and CallAsync (the JSON over HTTP verb) It deliberately leaves out LoginAsync / TrySilentLoginAsync / CurrentAccessToken / HasValidSession / Settings — those are authentication and configuration concerns, not transport. They stay on the concrete YavscApiClient in PostIt.Services. The concrete YavscApiClient now implements IYavscApiClient; the existing public surface is unchanged (no breaking changes for existing call sites in PostIt or the tests). This commit only lays the foundation. The actual high-level clients (Blog/Circle/BlogAcl) land in a follow-up commit that re-uses this interface, so this one stays a small, reviewable refactor.
This commit is contained in:
parent
0e95e28327
commit
ab40af8ef1
4 changed files with 94 additions and 1 deletions
|
|
@ -25,6 +25,7 @@
|
||||||
<PackageReference Include="IdentityModel.OidcClient" />
|
<PackageReference Include="IdentityModel.OidcClient" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||||
<ProjectReference Include="../../Yavsc.Abstract/Yavsc.Abstract.csproj" />
|
<ProjectReference Include="../../Yavsc.Abstract/Yavsc.Abstract.csproj" />
|
||||||
|
<ProjectReference Include="../../Yavsc.Api.Client/Yavsc.Api.Client.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="postit-settings.json">
|
<Content Include="postit-settings.json">
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using IdentityModel.OidcClient;
|
using IdentityModel.OidcClient;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
namespace PostIt.Services;
|
namespace PostIt.Services;
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ namespace PostIt.Services;
|
||||||
/// <see cref="BearerTokenHandler"/> only refreshes once even if many
|
/// <see cref="BearerTokenHandler"/> only refreshes once even if many
|
||||||
/// concurrent requests are in flight.
|
/// concurrent requests are in flight.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class YavscApiClient : IAsyncDisposable
|
public class YavscApiClient : IYavscApiClient, IAsyncDisposable
|
||||||
{
|
{
|
||||||
// 60s of slack before the access_token's nominal expiry. Covers
|
// 60s of slack before the access_token's nominal expiry. Covers
|
||||||
// network latency + JWT validation on the server side.
|
// network latency + JWT validation on the server side.
|
||||||
|
|
|
||||||
62
src/Yavsc.Api.Client/IYavscApiClient.cs
Normal file
62
src/Yavsc.Api.Client/IYavscApiClient.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Yavsc.Api.Client;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transport surface that the high-level clients
|
||||||
|
/// (<see cref="BlogApiClient"/>, <see cref="CircleApiClient"/>,
|
||||||
|
/// <see cref="BlogAclApiClient"/>) need to do their work.
|
||||||
|
///
|
||||||
|
/// <para>This is intentionally a thin, transport-only contract. It
|
||||||
|
/// does not include the OIDC login / refresh / logout surface —
|
||||||
|
/// that lives on the concrete <c>YavscApiClient</c> in the
|
||||||
|
/// consuming application and is wired by the application
|
||||||
|
/// composition root. Splitting the two keeps <c>Yavsc.Api.Client</c>
|
||||||
|
/// usable from any host (a CLI, a unit test, a future iOS
|
||||||
|
/// client) without dragging OIDC, identity, and a <c>Settings</c>
|
||||||
|
/// POMVO everywhere.</para>
|
||||||
|
///
|
||||||
|
/// <para>Implementations are expected to:</para>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>Attach a Bearer access token to every outbound request.</item>
|
||||||
|
/// <item>Silently refresh the token on a 401 and retry once.</item>
|
||||||
|
/// <item>Serialise the request body as JSON and deserialise the
|
||||||
|
/// response body with case-insensitive property matching.</item>
|
||||||
|
/// </list>
|
||||||
|
///
|
||||||
|
/// The exception contract on non-2xx responses is
|
||||||
|
/// <see cref="HttpRequestException"/> with a message that includes
|
||||||
|
/// the response body (capped), so callers can surface the
|
||||||
|
/// server-side validation problem to the UI without losing
|
||||||
|
/// context.
|
||||||
|
/// </summary>
|
||||||
|
public interface IYavscApiClient : IAsyncDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The configured <see cref="HttpClient"/>. Clients set its
|
||||||
|
/// <c>BaseAddress</c> in their constructors to point at the
|
||||||
|
/// API host they target.
|
||||||
|
/// </summary>
|
||||||
|
HttpClient Http { get; }
|
||||||
|
|
||||||
|
/// <summary>Call a JSON endpoint with a typed return value.</summary>
|
||||||
|
/// <param name="method">HTTP verb.</param>
|
||||||
|
/// <param name="path">Path relative to <see cref="HttpClient.BaseAddress"/>.</param>
|
||||||
|
/// <param name="body">Optional request body, serialised as JSON.</param>
|
||||||
|
/// <param name="ct">Cancellation token.</param>
|
||||||
|
Task<T> CallAsync<T>(
|
||||||
|
HttpMethod method,
|
||||||
|
string path,
|
||||||
|
object? body = null,
|
||||||
|
CancellationToken ct = default);
|
||||||
|
|
||||||
|
/// <summary>Call a JSON endpoint that returns no useful body (DELETE, 204, etc.).</summary>
|
||||||
|
Task CallAsync(
|
||||||
|
HttpMethod method,
|
||||||
|
string path,
|
||||||
|
object? body = null,
|
||||||
|
CancellationToken ct = default);
|
||||||
|
}
|
||||||
29
src/Yavsc.Api.Client/Yavsc.Api.Client.csproj
Normal file
29
src/Yavsc.Api.Client/Yavsc.Api.Client.csproj
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<RootNamespace>Yavsc.Api.Client</RootNamespace>
|
||||||
|
<AssemblyName>Yavsc.Api.Client</AssemblyName>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
|
<Description>
|
||||||
|
Thin HTTP clients for the Yavsc API. Each client is a DTO↔path
|
||||||
|
mapper; all transport concerns (base URL, JSON, Bearer auth,
|
||||||
|
silent refresh on 401) are delegated to YavscApiClient, which
|
||||||
|
lives in the consuming application (PostIt).
|
||||||
|
</Description>
|
||||||
|
<RepositoryUrl>https://github.com/pazof/yavsc</RepositoryUrl>
|
||||||
|
<Library>true</Library>
|
||||||
|
<AssemblyVersion>1.0.1.0</AssemblyVersion>
|
||||||
|
<FileVersion>1.0.1.0</FileVersion>
|
||||||
|
<InformationalVersion>1.0.1-5+Branch.main.Sha.0617fc6bda7151c70559d87177e2dcfb1b60995f</InformationalVersion>
|
||||||
|
<Version>1.0.1-5</Version>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="GitVersion.MsBuild" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../Yavsc.Abstract/Yavsc.Abstract.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue