From 8b2f89ecdcaf12250539c7426ec202ce25327a6d Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 22:41:46 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`RockDB`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @pmdevers. * https://github.com/pmdevers/MinimalKafka/pull/68#issuecomment-2740751648 The following files were modified: * `examples/KafkaAdventure/Extensions/KafkaBuilderExtensions.cs` * `examples/KafkaAdventure/Features/CommandProcessor/ProcessorFeature.cs` * `examples/KafkaAdventure/Features/Input/InputFeature.cs` * `examples/KafkaAdventure/Features/Input/InputHub.cs` * `examples/KafkaAdventure/Features/Locations/LocationContext.cs` * `examples/KafkaAdventure/Features/Locations/LocationsFeature.cs` * `examples/KafkaAdventure/Features/Movement/MovementFeature.cs` * `examples/KafkaAdventure/Features/PlayerLocation/PlayerLocations.cs` * `examples/KafkaAdventure/StreamStore.cs` * `examples/KafkaAdventure/wwwroot/index.js` * `src/MinimalKafka.RocksDB/ByteSerializer.cs` * `src/MinimalKafka.RocksDB/IByteSerializer.cs` * `src/MinimalKafka.RocksDB/KafkaBuilderExtensions.cs` * `src/MinimalKafka.RocksDB/RocksDBStreamStore.cs` * `src/MinimalKafka/KafkaExtensions.cs` * `test/MinimalKafka.RockDB.Tests/UnitTest1.cs` --- .../Extensions/KafkaBuilderExtensions.cs | 7 ++++- .../CommandProcessor/ProcessorFeature.cs | 21 ++++++++++++-- .../Features/Input/InputFeature.cs | 11 +++++++- .../KafkaAdventure/Features/Input/InputHub.cs | 11 +++++++- .../Features/Locations/LocationContext.cs | 5 +++- .../Features/Locations/LocationsFeature.cs | 8 +++++- .../Features/Movement/MovementFeature.cs | 13 +++++++-- .../PlayerLocation/PlayerLocations.cs | 6 +++- examples/KafkaAdventure/StreamStore.cs | 19 +++++++++++-- examples/KafkaAdventure/wwwroot/index.js | 28 ++++++++++++++++++- src/MinimalKafka.RocksDB/ByteSerializer.cs | 15 +++++++++- src/MinimalKafka.RocksDB/IByteSerializer.cs | 15 ++++++++-- .../KafkaBuilderExtensions.cs | 8 ++++-- .../RocksDBStreamStore.cs | 24 +++++++++++++++- src/MinimalKafka/KafkaExtensions.cs | 6 +++- test/MinimalKafka.RockDB.Tests/UnitTest1.cs | 10 +++++++ 16 files changed, 186 insertions(+), 21 deletions(-) diff --git a/examples/KafkaAdventure/Extensions/KafkaBuilderExtensions.cs b/examples/KafkaAdventure/Extensions/KafkaBuilderExtensions.cs index bdcf5a4..4515208 100644 --- a/examples/KafkaAdventure/Extensions/KafkaBuilderExtensions.cs +++ b/examples/KafkaAdventure/Extensions/KafkaBuilderExtensions.cs @@ -1,10 +1,15 @@ -using MinimalKafka.Builders; +using MinimalKafka.Builders; using MinimalKafka.Extension; namespace KafkaAdventure.Extensions; public static class KafkaBuilderExtensions { + /// + /// Configures the Kafka convention builder with a client ID set to the specified feature name and a group ID combining the feature name and the current ASP.NET Core environment. + /// + /// The name to use as the Kafka client ID and as part of the group ID. + /// The modified instance. public static IKafkaConventionBuilder AsFeature(this IKafkaConventionBuilder builder, string featureName) { builder.WithClientId(featureName); diff --git a/examples/KafkaAdventure/Features/CommandProcessor/ProcessorFeature.cs b/examples/KafkaAdventure/Features/CommandProcessor/ProcessorFeature.cs index 0425f01..06af070 100644 --- a/examples/KafkaAdventure/Features/CommandProcessor/ProcessorFeature.cs +++ b/examples/KafkaAdventure/Features/CommandProcessor/ProcessorFeature.cs @@ -1,4 +1,4 @@ -using KafkaAdventure.Extensions; +using KafkaAdventure.Extensions; using MinimalKafka.Extension; using MinimalKafka.Stream; @@ -6,6 +6,18 @@ namespace KafkaAdventure.Features.CommandProcessor; public static class ProcessorFeature { + /// + /// Configures the Kafka stream processor for handling game commands on the web application. + /// + /// + /// Sets up processing for the "game-commands" Kafka topic, routing incoming commands to appropriate topics based on their type: + /// - "HELP" commands produce a list of available commands to "game-response". + /// - "GO" and "LOOK" commands produce movement instructions to "game-movement". + /// - "INVENTORY" commands are forwarded to "game-inventory". + /// - "ECHO" commands produce an echo response to "game-response". + /// - Unrecognized commands produce an error response to "game-response". + /// Registers the entire pipeline as a feature named "Commands". + /// public static void MapProcessor(this WebApplication app) { Console.WriteLine("Starting Up ProcessorFeature"); @@ -52,7 +64,12 @@ public static void MapProcessor(this WebApplication app) public record Command(string Cmd, string[] Args) { - public bool IsCommand(string s) => Cmd.StartsWith(s, StringComparison.InvariantCultureIgnoreCase); + /// +/// Determines whether the command string starts with the specified value, ignoring case. +/// +/// The string to compare against the start of the command. +/// True if the command starts with the specified string; otherwise, false. +public bool IsCommand(string s) => Cmd.StartsWith(s, StringComparison.InvariantCultureIgnoreCase); }; public record Response(string Command, string Value); diff --git a/examples/KafkaAdventure/Features/Input/InputFeature.cs b/examples/KafkaAdventure/Features/Input/InputFeature.cs index 924c958..46299b7 100644 --- a/examples/KafkaAdventure/Features/Input/InputFeature.cs +++ b/examples/KafkaAdventure/Features/Input/InputFeature.cs @@ -1,4 +1,4 @@ -using KafkaAdventure.Extensions; +using KafkaAdventure.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using MinimalKafka; @@ -8,6 +8,10 @@ namespace KafkaAdventure.Features.Input; public static class InputFeature { + /// + /// Configures the application to map the InputHub SignalR hub and the "game-response" Kafka topic for input handling. + /// + /// The application type implementing both IEndpointRouteBuilder and IApplicationBuilder. public static void MapInput(this T app) where T : IEndpointRouteBuilder, IApplicationBuilder { @@ -17,6 +21,11 @@ public static void MapInput(this T app) .AsFeature("Input"); } + /// + /// Sends a response message to all SignalR clients in the specified game group. + /// + /// The identifier of the SignalR group representing the game session. + /// The response containing the message to send to clients. public static async Task HandleAsync([FromServices] IHubContext hub, [FromKey] string gameId, [FromValue] Response response) { await hub.Clients.Group(gameId).SendAsync("ReceiveMessage", response.Value); diff --git a/examples/KafkaAdventure/Features/Input/InputHub.cs b/examples/KafkaAdventure/Features/Input/InputHub.cs index 23fc3ff..298ca34 100644 --- a/examples/KafkaAdventure/Features/Input/InputHub.cs +++ b/examples/KafkaAdventure/Features/Input/InputHub.cs @@ -1,4 +1,4 @@ -using Confluent.Kafka; +using Confluent.Kafka; using Microsoft.AspNetCore.SignalR; namespace KafkaAdventure.Features; @@ -8,6 +8,10 @@ public class InputHub( IProducer command ) : Hub { + /// + /// Adds the current connection to the specified game group and sends introductory messages to all clients in that group. + /// + /// The identifier of the game group to join. public async Task JoinGame(string gameId) { await Groups.AddToGroupAsync(Context.ConnectionId, gameId); @@ -17,6 +21,11 @@ public async Task JoinGame(string gameId) await Clients.Group(gameId).SendAsync("ReceiveMessage", "Type your commands to explore the world. Type 'help' for a list of commands."); } + /// + /// Processes a client message by parsing it into a command and arguments, then produces a Command message to the "game-commands" Kafka topic for the specified game. + /// + /// The identifier of the game to which the command applies. + /// The message from the client, expected to contain a command and optional arguments separated by spaces. public async Task SendMessage(string gameId, string message) { if(string.IsNullOrWhiteSpace(message)) diff --git a/examples/KafkaAdventure/Features/Locations/LocationContext.cs b/examples/KafkaAdventure/Features/Locations/LocationContext.cs index 854319a..8ccf130 100644 --- a/examples/KafkaAdventure/Features/Locations/LocationContext.cs +++ b/examples/KafkaAdventure/Features/Locations/LocationContext.cs @@ -1,10 +1,13 @@ -using System.Reflection; +using System.Reflection; using System.Text.Json; namespace KafkaAdventure.Features.Locations; public class LocationContext { + /// + /// Initializes the LocationContext with a default list containing a single forest location and its exits. + /// public LocationContext() { Locations = [ diff --git a/examples/KafkaAdventure/Features/Locations/LocationsFeature.cs b/examples/KafkaAdventure/Features/Locations/LocationsFeature.cs index 489c5dd..e5d8dcd 100644 --- a/examples/KafkaAdventure/Features/Locations/LocationsFeature.cs +++ b/examples/KafkaAdventure/Features/Locations/LocationsFeature.cs @@ -1,4 +1,4 @@ -using Confluent.Kafka; +using Confluent.Kafka; using KafkaAdventure.Extensions; using Microsoft.AspNetCore.Mvc; using MinimalKafka.Extension; @@ -8,6 +8,12 @@ namespace KafkaAdventure.Features.Locations; public static class LocationsFeature { + /// + /// Configures HTTP and Kafka stream endpoints for managing location data. + /// + /// + /// Maps a POST endpoint at /locations that accepts an array of objects and produces them to the "game-locations" Kafka topic. Also sets up a Kafka stream consumer for the "game-locations" topic, updating or adding locations in the based on incoming messages. + /// public static void MapLocations(this T app) where T : IEndpointRouteBuilder, IApplicationBuilder { diff --git a/examples/KafkaAdventure/Features/Movement/MovementFeature.cs b/examples/KafkaAdventure/Features/Movement/MovementFeature.cs index 795f7af..d153f8e 100644 --- a/examples/KafkaAdventure/Features/Movement/MovementFeature.cs +++ b/examples/KafkaAdventure/Features/Movement/MovementFeature.cs @@ -1,4 +1,4 @@ -using KafkaAdventure.Extensions; +using KafkaAdventure.Extensions; using KafkaAdventure.Features.Locations; using MinimalKafka.Extension; using MinimalKafka.Stream; @@ -7,6 +7,10 @@ namespace KafkaAdventure.Features.Movement; public static class MovementFeature { + /// + /// Configures a Kafka stream processing pipeline for handling player movement commands in a game, joining movement events with player positions and producing appropriate responses and location updates. + /// + /// The application type implementing both and . public static void MapMovement(this T app) where T : IEndpointRouteBuilder, IApplicationBuilder { @@ -48,7 +52,12 @@ public record Position(int X, int Y); public record class Movement(string Cmd, string Direction) { - public bool IsCommand(string s) => Cmd.StartsWith(s, StringComparison.InvariantCultureIgnoreCase); + /// +/// Determines whether the movement command starts with the specified string, using a case-insensitive comparison. +/// +/// The string to compare against the start of the command. +/// True if the command starts with the specified string; otherwise, false. +public bool IsCommand(string s) => Cmd.StartsWith(s, StringComparison.InvariantCultureIgnoreCase); }; public record Response(string Command, string Value); diff --git a/examples/KafkaAdventure/Features/PlayerLocation/PlayerLocations.cs b/examples/KafkaAdventure/Features/PlayerLocation/PlayerLocations.cs index a214ffe..89622b0 100644 --- a/examples/KafkaAdventure/Features/PlayerLocation/PlayerLocations.cs +++ b/examples/KafkaAdventure/Features/PlayerLocation/PlayerLocations.cs @@ -1,4 +1,4 @@ -using KafkaAdventure.Extensions; +using KafkaAdventure.Extensions; using KafkaAdventure.Features.Locations; using MinimalKafka.Extension; using MinimalKafka.Metadata; @@ -8,6 +8,10 @@ namespace KafkaAdventure.Features.PlayerLocation; public static class PlayerLocations { + /// + /// Configures a Kafka stream processing pipeline that listens for player location updates and produces descriptive responses about the player's current location and available exits. + /// + /// A type that implements . public static void MapPlayerLocations(this T app) where T : IApplicationBuilder { diff --git a/examples/KafkaAdventure/StreamStore.cs b/examples/KafkaAdventure/StreamStore.cs index 0f1eb7d..9f97d3a 100644 --- a/examples/KafkaAdventure/StreamStore.cs +++ b/examples/KafkaAdventure/StreamStore.cs @@ -1,10 +1,17 @@ -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Memory; using MinimalKafka.Stream; namespace KafkaAdventure; public class StreamStore(IMemoryCache cache) : IStreamStore { + /// + /// Adds a new value to the cache for the specified key or updates the existing value using the provided functions. + /// + /// The key associated with the value to add or update. + /// A function to create a new value if the key does not exist. + /// A function to update the existing value if the key is found. + /// A containing the added or updated value. public ValueTask AddOrUpdate(TKey key, Func create, Func update) { if(cache.TryGetValue(key!, out TValue? value) ){ @@ -15,9 +22,17 @@ public ValueTask AddOrUpdate(TKey key, Func create, Func FindAsync(Func predicate) + /// + /// Returns an empty asynchronous sequence of values, ignoring the provided predicate. + /// + public IAsyncEnumerable FindAsync(Func predicate) => AsyncEnumerable.Empty(); + /// + /// Retrieves the value associated with the specified key from the cache asynchronously. + /// + /// The key whose value should be retrieved. + /// A ValueTask containing the value if found; otherwise, null. public ValueTask FindByIdAsync(TKey key) { return new ValueTask(cache.Get(key!)); diff --git a/examples/KafkaAdventure/wwwroot/index.js b/examples/KafkaAdventure/wwwroot/index.js index 98055f3..d8a8794 100644 --- a/examples/KafkaAdventure/wwwroot/index.js +++ b/examples/KafkaAdventure/wwwroot/index.js @@ -1,4 +1,4 @@ -const inputField = document.getElementById("commandInput"); +const inputField = document.getElementById("commandInput"); const terminal = document.getElementById("terminal"); const gamewindow = document.getElementById("gamewindow"); @@ -18,11 +18,18 @@ inputField.addEventListener("keydown", function (event) { }); +/** + * Scrolls the element with ID "body" to its bottom. + */ function scrollToBottom() { const bdy = document.getElementById("body"); bdy.scrollTop = bdy.scrollHeight; } +/** + * Sends a user command to the server and displays it in the terminal. + * @param {string} command - The command string entered by the user. + */ function executeCommand(command) { const gameId = localStorage.getItem('gameid'); connection.send("SendMessage", gameId, command); @@ -33,6 +40,10 @@ function executeCommand(command) { scrollToBottom(); } +/** + * Displays a server response in the terminal with a typewriter animation effect. + * @param {string} res - The response text to display. + */ function addResponse(res) { const response = document.createElement("p"); response.className = "text-green-400"; @@ -51,6 +62,12 @@ function addResponse(res) { scrollToBottom(); } +/** + * Animates text into a specified DOM element, displaying one character at a time at a given speed. + * @param {string} elementId - The ID of the DOM element where the text will be displayed. + * @param {string} text - The text to animate. + * @param {number} speed - The delay in milliseconds between each character. + */ function slowType(elementId, text, speed) { let i = 0; const element = document.getElementById(elementId); @@ -65,6 +82,10 @@ function slowType(elementId, text, speed) { typeWriter(); } +/** + * Generates a version 4 UUID string using timestamp and randomization. + * @return {string} A randomly generated UUID in the format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. + */ function generateUUID() { // Public Domain/MIT var d = new Date().getTime();//Timestamp var d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || 0;//Time in microseconds since page-load or 0 if unsupported @@ -82,6 +103,11 @@ function generateUUID() { // Public Domain/MIT } +/** + * Initializes and maintains the SignalR connection for the game session. + * + * Retrieves or generates a unique game ID, establishes the SignalR connection, sets up a handler for incoming messages, and joins the game session. If the connection fails, it retries after a delay. + */ async function start() { try { let gameId = localStorage.getItem('gameid'); diff --git a/src/MinimalKafka.RocksDB/ByteSerializer.cs b/src/MinimalKafka.RocksDB/ByteSerializer.cs index 2d7099c..371a70a 100644 --- a/src/MinimalKafka.RocksDB/ByteSerializer.cs +++ b/src/MinimalKafka.RocksDB/ByteSerializer.cs @@ -1,9 +1,15 @@ -using System.Text.Json; +using System.Text.Json; namespace MinimalKafka.Stream.Storage.RocksDB; internal class ByteSerializer : IByteSerializer { + /// + /// Serializes the specified object to a UTF-8 encoded JSON byte array. + /// + /// The object to serialize. Must not be null. + /// A byte array containing the UTF-8 encoded JSON representation of the object. + /// Thrown if is null. public byte[] Serialize(T value) { if (value is null) @@ -12,6 +18,13 @@ public byte[] Serialize(T value) return JsonSerializer.SerializeToUtf8Bytes(value); } + /// + /// Deserializes a UTF-8 encoded JSON byte array into an object of type . + /// + /// The byte array containing the JSON data to deserialize. + /// The deserialized object of type . + /// Thrown if is null or empty. + /// Thrown if deserialization fails and returns null. public T Deserialize(byte[]? bytes) { if (bytes == null || bytes.Length == 0) diff --git a/src/MinimalKafka.RocksDB/IByteSerializer.cs b/src/MinimalKafka.RocksDB/IByteSerializer.cs index c9bfd5f..35c8493 100644 --- a/src/MinimalKafka.RocksDB/IByteSerializer.cs +++ b/src/MinimalKafka.RocksDB/IByteSerializer.cs @@ -1,4 +1,4 @@ -namespace MinimalKafka.Stream.Storage.RocksDB; +namespace MinimalKafka.Stream.Storage.RocksDB; /// /// Interface for serializing and deserializing byte arrays. @@ -10,7 +10,12 @@ public interface IByteSerializer /// /// /// - /// + /// +/// Serializes the specified value into a byte array. +/// +/// The type of the value to serialize. +/// The value to serialize. +/// A byte array representing the serialized value. byte[] Serialize(T value); /// @@ -18,6 +23,10 @@ public interface IByteSerializer /// /// /// - /// + /// +/// Deserializes a byte array into an object of type . +/// +/// The byte array to deserialize, or null. +/// The deserialized object of type . T Deserialize(byte[]? bytes); } diff --git a/src/MinimalKafka.RocksDB/KafkaBuilderExtensions.cs b/src/MinimalKafka.RocksDB/KafkaBuilderExtensions.cs index f76dbcf..7d26798 100644 --- a/src/MinimalKafka.RocksDB/KafkaBuilderExtensions.cs +++ b/src/MinimalKafka.RocksDB/KafkaBuilderExtensions.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using MinimalKafka.Builders; using MinimalKafka.Extension; using MinimalKafka.Stream.Storage.RocksDB; @@ -18,7 +18,11 @@ public static class KafkaBuilderExtensions /// /// /// - /// + /// + /// Configures the Kafka builder to use RocksDB as the stream storage backend. + /// + /// A delegate to configure the for the RocksDB instance. + /// The configured instance for fluent chaining. public static IAddKafkaBuilder UseRocksDB(this IAddKafkaBuilder builder, Action options) { var o = new RocksDBOptions(); diff --git a/src/MinimalKafka.RocksDB/RocksDBStreamStore.cs b/src/MinimalKafka.RocksDB/RocksDBStreamStore.cs index cf23029..dc98f35 100644 --- a/src/MinimalKafka.RocksDB/RocksDBStreamStore.cs +++ b/src/MinimalKafka.RocksDB/RocksDBStreamStore.cs @@ -1,4 +1,4 @@ -using RocksDbSharp; +using RocksDbSharp; namespace MinimalKafka.Stream.Storage.RocksDB; @@ -11,6 +11,11 @@ internal class RocksDBStreamStore : IStreamStore + /// + /// Initializes a new instance of the class using the specified RocksDB instance and serializer. + /// + /// The RocksDB database instance to use for storage. + /// The serializer for serializing and deserializing keys and values. public RocksDBStreamStore(RocksDb db, IByteSerializer serializer) { _db = db; @@ -22,6 +27,13 @@ public RocksDBStreamStore(RocksDb db, IByteSerializer serializer) } + /// + /// Adds a new value or updates an existing value for the specified key in the RocksDB store. + /// + /// The key to add or update. + /// A function to generate a new value if the key does not exist. + /// A function to update the existing value if the key is found. + /// The newly added or updated value. public ValueTask AddOrUpdate(T1 key, Func create, Func update) { var keyBytes = _serializer.Serialize(key); @@ -44,6 +56,11 @@ public ValueTask AddOrUpdate(T1 key, Func create, Func u return ValueTask.FromResult(newValue); } + /// + /// Asynchronously enumerates all values in the store that satisfy the specified predicate. + /// + /// A function to filter values of type T2. + /// An asynchronous stream of values matching the predicate. public async IAsyncEnumerable FindAsync(Func predicate) { using var iterator = _db.NewIterator(_columnFamily); @@ -58,6 +75,11 @@ public async IAsyncEnumerable FindAsync(Func predicate) } } + /// + /// Asynchronously retrieves a value by its key from the RocksDB column family. + /// + /// The key to search for. + /// The value associated with the specified key, or null if not found. public ValueTask FindByIdAsync(T1 key) { var keyBytes = _serializer.Serialize(key); diff --git a/src/MinimalKafka/KafkaExtensions.cs b/src/MinimalKafka/KafkaExtensions.cs index a7ed841..a9c92aa 100644 --- a/src/MinimalKafka/KafkaExtensions.cs +++ b/src/MinimalKafka/KafkaExtensions.cs @@ -1,4 +1,4 @@ -using Confluent.Kafka; +using Confluent.Kafka; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using MinimalKafka.Builders; @@ -23,6 +23,10 @@ public static class KafkaExtensions /// The to which the Kafka services will be added. /// A delegate to configure the Kafka settings using an . This allows customization /// of client ID, group ID, serializers, topic formatting, and other Kafka-related options. + /// + /// Registers MinimalKafka services and configuration into the dependency injection container, enabling Kafka producers, consumers, and topic handlers for the application. + /// + /// A delegate to configure Kafka options and conventions using the provided builder. /// The with the Kafka services registered. public static IServiceCollection AddMinimalKafka(this IServiceCollection services, Action config) { diff --git a/test/MinimalKafka.RockDB.Tests/UnitTest1.cs b/test/MinimalKafka.RockDB.Tests/UnitTest1.cs index cbde567..40ab74b 100644 --- a/test/MinimalKafka.RockDB.Tests/UnitTest1.cs +++ b/test/MinimalKafka.RockDB.Tests/UnitTest1.cs @@ -5,12 +5,18 @@ namespace MinimalKafka.RockDB.Tests; public class UnitTest1 { + /// + /// Initializes a new instance of the class and resets the RocksDB database at the specified path. + /// public UnitTest1() { RocksDBHelper.ResetDatabase("c:\\SourceCode\\rocksdb"); } + /// + /// Tests adding and retrieving a key-value pair in a RocksDB-backed stream store using dependency injection. + /// [Fact] public async Task Test1() { @@ -39,6 +45,10 @@ public async Task Test1() public static class RocksDBHelper { + /// + /// Deletes the RocksDB database directory at the specified path if it exists, removing all contents. + /// + /// The file system path to the RocksDB database directory. public static void ResetDatabase(string dbPath) { if (Directory.Exists(dbPath))