< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Evaluation.FileEvaluationCaptureStore
Assembly: NexusLabs.Needlr.AgentFramework.Evaluation
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Evaluation/FileEvaluationCaptureStore.cs
Line coverage
100%
Covered lines: 33
Uncovered lines: 0
Coverable lines: 33
Total lines: 85
Line coverage: 100%
Branch coverage
90%
Covered branches: 9
Total branches: 10
Branch coverage: 90%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
TryGetAsync()83.33%66100%
SaveAsync()100%44100%
GetPath(...)100%11100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Evaluation/FileEvaluationCaptureStore.cs

#LineLine coverage
 1using System.Text.Json;
 2
 3using Microsoft.Extensions.AI;
 4
 5namespace NexusLabs.Needlr.AgentFramework.Evaluation;
 6
 7/// <summary>
 8/// Disk-backed <see cref="IEvaluationCaptureStore"/> that persists each response
 9/// as a single JSON file under a caller-supplied directory. File names are the
 10/// request hash plus a <c>.json</c> extension.
 11/// </summary>
 12/// <remarks>
 13/// Writes are atomic per file via write-then-rename. Reads return <see langword="null"/>
 14/// when no file exists for the requested key, and propagate any other I/O error.
 15/// </remarks>
 16public sealed class FileEvaluationCaptureStore : IEvaluationCaptureStore
 17{
 118    private static readonly JsonSerializerOptions _serializerOptions = new()
 119    {
 120        WriteIndented = true,
 121    };
 22
 23    private readonly string _directoryPath;
 24
 25    /// <summary>
 26    /// Creates a store that reads from and writes to <paramref name="directoryPath"/>.
 27    /// The directory is created on first write if it does not already exist.
 28    /// </summary>
 29    /// <param name="directoryPath">Absolute or relative path to the cache directory.</param>
 1030    public FileEvaluationCaptureStore(string directoryPath)
 31    {
 1032        ArgumentException.ThrowIfNullOrWhiteSpace(directoryPath);
 733        _directoryPath = directoryPath;
 734    }
 35
 36    /// <inheritdoc />
 37    public async Task<ChatResponse?> TryGetAsync(
 38        string key,
 39        CancellationToken cancellationToken)
 40    {
 541        ArgumentException.ThrowIfNullOrWhiteSpace(key);
 42
 343        var path = GetPath(key);
 344        if (!File.Exists(path))
 45        {
 146            return null;
 47        }
 48
 249        await using var stream = File.OpenRead(path);
 250        var payload = await JsonSerializer
 251            .DeserializeAsync<CapturedChatResponsePayload>(stream, _serializerOptions, cancellationToken)
 252            .ConfigureAwait(false);
 253        return payload?.ToChatResponse();
 354    }
 55
 56    /// <inheritdoc />
 57    public async Task SaveAsync(
 58        string key,
 59        ChatResponse response,
 60        CancellationToken cancellationToken)
 61    {
 562        ArgumentException.ThrowIfNullOrWhiteSpace(key);
 563        ArgumentNullException.ThrowIfNull(response);
 64
 465        Directory.CreateDirectory(_directoryPath);
 466        var finalPath = GetPath(key);
 467        var tempPath = finalPath + ".tmp";
 68
 469        var payload = CapturedChatResponsePayload.FromChatResponse(response);
 470        await using (var stream = File.Create(tempPath))
 71        {
 472            await JsonSerializer
 473                .SerializeAsync(stream, payload, _serializerOptions, cancellationToken)
 474                .ConfigureAwait(false);
 75        }
 76
 477        if (File.Exists(finalPath))
 78        {
 179            File.Delete(finalPath);
 80        }
 481        File.Move(tempPath, finalPath);
 482    }
 83
 784    private string GetPath(string key) => Path.Combine(_directoryPath, key + ".json");
 85}