Return retrieved document metadata from the Nexus document client.
ci/woodpecker/push/woodpecker Pipeline failed Details

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Sergej Kern 2026-09-21 06:02:08 +02:00
parent c2360c2e9e
commit 574cadc0a1
4 changed files with 68 additions and 2 deletions

View File

@ -53,6 +53,29 @@ public sealed class NexusDocumentClient
return NexusWireStatus.ToResult(response.Status, body);
});
/// <summary>
/// Reads many documents in one RPC. Missing ids are omitted from the list.
/// </summary>
public Robotico.Result.Result<IReadOnlyList<NexusRetrievedDocument>> GetDocuments(
string collectionName,
IReadOnlyList<string> documentIds) => InvokeGeneric(() =>
{
ArgumentNullException.ThrowIfNull(documentIds);
DocumentCollectionGetManyRequest request = new() { CollectionName = collectionName };
request.DocumentIds.AddRange(documentIds);
DocumentCollectionGetManyResponse response = _client.CollectionGetMany(request, Metadata());
List<NexusRetrievedDocument> found = new(response.Documents.Count);
foreach (DocumentCollectionDocumentWire doc in response.Documents)
{
string body = doc.JsonUtf8.Length > 0
? System.Text.Encoding.UTF8.GetString(doc.JsonUtf8.Span)
: doc.JsonBody;
found.Add(new NexusRetrievedDocument(doc.DocumentId, body));
}
return NexusWireStatus.ToResult(response.Status, (IReadOnlyList<NexusRetrievedDocument>)found);
});
/// <summary>Deletes a document by id when present (idempotent).</summary>
public Robotico.Result.Result DeleteDocument(string collectionName, string documentId) => Invoke(() =>
{

View File

@ -0,0 +1,4 @@
namespace Robotico.Nexus.Client.Document;
/// <summary>One document returned by <see cref="NexusDocumentClient.GetDocuments"/>.</summary>
public readonly record struct NexusRetrievedDocument(string DocumentId, string JsonBody);

View File

@ -48,6 +48,22 @@ public class NexusDocumentClientTests : IAsyncLifetime
Assert.Equal("""{"name":"Ada"}""", body);
}
[Fact]
public void GetDocuments_ReturnsFound_AndOmitsMissing()
{
_documentClient.UpsertDocument("users", "u1", """{"name":"Ada"}""");
_documentClient.UpsertDocument("users", "u3", """{"name":"Grace"}""");
Robotico.Result.Result<IReadOnlyList<NexusRetrievedDocument>> fetched =
_documentClient.GetDocuments("users", ["u3", "missing", "u1"]);
Assert.True(fetched.IsSuccess(out IReadOnlyList<NexusRetrievedDocument>? docs, out _));
Assert.Equal(2, docs!.Count);
Assert.Equal("u3", docs[0].DocumentId);
Assert.Equal("""{"name":"Grace"}""", docs[0].JsonBody);
Assert.Equal("u1", docs[1].DocumentId);
}
[Fact]
public void GetDocument_ReturnsNull_WhenNotFound()
{

View File

@ -4,8 +4,8 @@ using Mishima.Nexus.Wire.V1;
namespace Robotico.Nexus.Client.Tests.TestSupport;
/// <summary>
/// In-memory fake standing in for a real Nexus document engine host — only the RPCs used by
/// <see cref="Robotico.Nexus.Client.Document.NexusDocumentClient"/> (Ping + single-document CRUD).
/// In-memory fake standing in for a real Nexus document engine host — the RPCs used by
/// <see cref="Robotico.Nexus.Client.Document.NexusDocumentClient"/> (Ping, CRUD, GetMany).
/// </summary>
public sealed class FakeDocumentEngineService : NexusDocumentEngineService.NexusDocumentEngineServiceBase
{
@ -36,6 +36,29 @@ public sealed class FakeDocumentEngineService : NexusDocumentEngineService.Nexus
return Task.FromResult(new DocumentGetResponse { Status = NexusStatuses.Ok, Found = false });
}
/// <inheritdoc />
public override Task<DocumentCollectionGetManyResponse> CollectionGetMany(
DocumentCollectionGetManyRequest request,
ServerCallContext context)
{
DocumentCollectionGetManyResponse response = new() { Status = NexusStatuses.Ok };
foreach (string id in request.DocumentIds)
{
if (!_documents.TryGetValue((request.CollectionName, id), out string? body))
{
continue;
}
response.Documents.Add(new DocumentCollectionDocumentWire
{
DocumentId = id,
JsonBody = body,
});
}
return Task.FromResult(response);
}
/// <inheritdoc />
public override Task<DocumentDeleteResponse> DeleteDocument(DocumentDeleteRequest request, ServerCallContext context)
{