using System.Text.Json;
using System.Text.Json.Serialization;
namespace ResolveSpec;
/// Client for the ResolveSpec JSON body protocol: POST {operation, data, options}.
public sealed class ResolveSpecClient
{
static readonly JsonSerializerOptions Json = new() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
readonly Transport _t;
public ResolveSpecClient(string baseUrl, ClientOptions? options = null) => _t = new Transport(baseUrl, options);
sealed class Request
{
[JsonPropertyName("operation")] public string Operation { get; set; } = "";
[JsonPropertyName("id")] public string[]? Id { get; set; }
[JsonPropertyName("data")] public object? Data { get; set; }
[JsonPropertyName("options")] public Options? Options { get; set; }
}
// A single id (int/long/string) goes in the URL; string[] / IEnumerable goes in the body.
static string? UrlId(object? id) => id switch
{
null => null,
string s => s,
IEnumerable => null,
_ => Convert.ToString(id, System.Globalization.CultureInfo.InvariantCulture),
};
static string[]? BodyId(object? id) => id is IEnumerable e ? e.ToArray() : null;
string Url(string schema, string entity, string? id)
{
var u = $"{_t.BaseUrl}/{Transport.Segment(schema)}/{Transport.Segment(entity)}";
return string.IsNullOrEmpty(id) ? u : $"{u}/{Transport.Segment(id)}";
}
async Task SendAsync(HttpMethod method, string url, Request? body, CancellationToken ct)
{
var json = body == null ? null : JsonSerializer.Serialize(body, Json);
var (resp, text) = await _t.SendAsync(method, url, json, null, ct).ConfigureAwait(false);
var status = (int)resp.StatusCode;
if (!resp.IsSuccessStatusCode) throw Transport.ErrorFrom(status, text, resp.ReasonPhrase);
var r = JsonSerializer.Deserialize(text, Json) ?? new Response();
if (!r.Success && r.Error != null) throw new ResolveSpecException(r.Error.Message, status, r.Error);
return r;
}
/// GET /{schema}/{entity}
public Task GetMetadataAsync(string schema, string entity, CancellationToken ct = default) =>
SendAsync(HttpMethod.Get, Url(schema, entity, null), null, ct);
public Task ReadAsync(string schema, string entity, object? id = null, Options? options = null, CancellationToken ct = default) =>
SendAsync(HttpMethod.Post, Url(schema, entity, UrlId(id)), new Request { Operation = "read", Id = BodyId(id), Options = options }, ct);
public Task CreateAsync(string schema, string entity, object data, Options? options = null, CancellationToken ct = default) =>
SendAsync(HttpMethod.Post, Url(schema, entity, null), new Request { Operation = "create", Data = data, Options = options }, ct);
public Task UpdateAsync(string schema, string entity, object data, object? id = null, Options? options = null, CancellationToken ct = default) =>
SendAsync(HttpMethod.Post, Url(schema, entity, UrlId(id)), new Request { Operation = "update", Id = BodyId(id), Data = data, Options = options }, ct);
public Task DeleteAsync(string schema, string entity, object id, CancellationToken ct = default) =>
SendAsync(HttpMethod.Post, Url(schema, entity, UrlId(id)), new Request { Operation = "delete" }, ct);
}