Skip to content

Feature/new#2

Merged
mareeben merged 13 commits intomasterfrom
feature/new
May 14, 2025
Merged

Feature/new#2
mareeben merged 13 commits intomasterfrom
feature/new

Conversation

@mareeben
Copy link
Contributor

@mareeben mareeben commented May 14, 2025

➕ What does this PR do?

🔨 Changes

  • Feature / enhancement
  • Bug fix
  • Chore / refactor

✅ Checklist

  • Tests added/updated
  • Docs updated (or NA)
  • No secrets in diff
  • Linked to issue #____
  • Screenshots / GIF added (UI changes)

🗒 Notes for reviewer

Summary by CodeRabbit

  • New Features

    • Added support for saving and validating crypto ordinal spot price data, including inscription details and collection name.
    • Introduced a price side field (mid, bid, ask) for FX and crypto ordinal spot price data.
    • Implemented JSON schema validation with detailed error reporting for incoming market data.
    • Enhanced API error handling and response messages for market data operations.
  • Improvements

    • Refined FX spot price schema and model to include price side information.
    • Normalized string fields and updated ID generation for improved data consistency.
    • Made several fields nullable and optional for better serialization handling.
    • Added fallback for missing timestamps during data mapping.
    • Improved repository methods with richer result reporting and robust Cosmos DB error handling.
    • Updated domain and DTO mappings for new data types and price side enums.
  • Documentation

    • Added example HTTP requests demonstrating saving FX and crypto ordinal spot price data.
  • Chores

    • Added new test project structure to the solution for future test coverage.
    • Added unit tests for market data repository interactions.

mareeben added 4 commits May 13, 2025 16:12
Introduced models, mappers, and schemas to handle crypto ordinal spot price data, including enhanced serialization and DTO support.

Updated repository methods and refactored interfaces to accommodate IMarketData changes.

Improved CosmosDB query and save operations with error handling.
Updated schema versioning, added JSON schema validation, and restructured version handling in `VersionManager` and `MarketDataRepository`. Introduced `JsonSchemaValidator` and `JsonSchemaValidatorRegistry` for schema validation. Modified domain models to dynamically compute unique IDs based on new schema rules.
Converted all string parameters to lowercase to ensure case-insensitivity during queries. Updated schemas and DTOs to handle null values and added default timestamps for missing data. Enhanced logging for validation and deserialization errors.
Renamed `SaveAsync` to `SaveMarketDataAsync` for better description, updated calling methods, and adjusted null checks on `feedIterator`. Enhanced logging in `ProcessActionResult` to capture both success and error messages. Updated HTTP request examples with corrected field values and renamed assets for consistency.
@coderabbitai
Copy link

coderabbitai bot commented May 14, 2025

Caution

Review failed

The pull request is closed.

"""

Walkthrough

This update introduces support for saving and validating multiple market data types—specifically FX spot prices and crypto ordinal spot prices—via a unified API endpoint. It adds new domain models, DTOs, mapping logic, JSON schemas, and schema validation infrastructure. The repository and related infrastructure are refactored for richer error handling and detailed result reporting.

Changes

Files/Group Change Summary
Phoenix.MarketData.Functions/HttpRequests/SaveMarketData.http Added HTTP request examples for saving FX and crypto ordinal spot price data via API.
Phoenix.MarketData.Functions/SaveDocumentToDb.cs Enhanced function to handle multiple data types/asset classes, validate query params, perform schema validation, deserialize DTOs, map to domain, save asynchronously, and handle errors with detailed responses.
Phoenix.MarketDataPlatform.sln Added test project and folder to solution, updated build configs.
src/Phoenix.MarketData.Domain/Models/BaseMarketData.cs Refactored to implement IMarketData, normalized property setters, improved ID calculation, and defensive property handling.
src/Phoenix.MarketData.Domain/Models/CryptoOrdinalSpotPriceData.cs Introduced new domain model for crypto ordinal spot price data.
src/Phoenix.MarketData.Domain/Models/FxSpotPriceData.cs Added XML docs to Price and new Side property with default.
src/Phoenix.MarketData.Domain/Models/Interfaces/IMarketData.cs Renamed interface from IMarketDataObject to IMarketData.
src/Phoenix.MarketData.Domain/PriceSide.cs Added PriceSide enum for price quote sides.
src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs Refactored repository: improved error handling, detailed result classes, normalized queries, added new retrieval methods, and supported new market data types.
src/Phoenix.MarketData.Infrastructure/Cosmos/VersionManager.cs Updated versioning logic to use new repository methods and type constraints.
src/Phoenix.MarketData.Infrastructure/Mapping/BaseMarketDataMapper.cs Changed timestamp assignment to use fallback if missing.
src/Phoenix.MarketData.Infrastructure/Mapping/CryptoOrdinalSpotPriceDataMapper.cs Added mapping logic for crypto ordinal spot price data between DTO and domain.
src/Phoenix.MarketData.Infrastructure/Mapping/FxSpotPriceDataMapper.cs Updated mapping to handle Side property and return new domain object.
src/Phoenix.MarketData.Infrastructure/Phoenix.MarketData.Infrastructure.csproj Added JsonSchema.Net dependency and schema file copy rules.
src/Phoenix.MarketData.Infrastructure/Schemas/CryptoOrdinalSpotPriceData_v1.0.0.schema.json Added new JSON schema for crypto ordinal spot price data.
src/Phoenix.MarketData.Infrastructure/Schemas/FxSpotPriceData_v1.0.0.schema.json Updated schema: changed dataType to "price.spot" and added required side property.
src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidator.cs Introduced class for validating JSON payloads against schemas.
src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidatorRegistry.cs Added registry for managing and validating against multiple JSON schemas.
src/Phoenix.MarketData.Infrastructure/Schemas/SchemaVersions.cs Updated supported schema version from "0.0.0" to "1.0.0".
src/Phoenix.MarketData.Infrastructure/Serialization/BaseMarketDataDto.cs Made several properties nullable and ignored nulls in JSON serialization; added parameterless constructor.
src/Phoenix.MarketData.Infrastructure/Serialization/CryptoOrdinalSpotPriceDataDto.cs Added DTO for crypto ordinal spot price data with serialization attributes.
src/Phoenix.MarketData.Infrastructure/Serialization/FxSpotPriceDataDto.cs Added Side property (nullable), updated constructors, and serialization handling.
src/Phoenix.MarketData.Infrastructure/Serialization/PriceSideDto.cs Added PriceSideDto enum for DTO serialization/deserialization.
Phoenix.MarketData.Infrastructure.Tests/MarketDataRepositoryTests.cs Added unit tests for MarketDataRepository covering data retrieval and saving with mocked Cosmos DB container.
Phoenix.MarketData.Infrastructure.Tests/Phoenix.MarketData.Infrastructure.Tests.csproj Added new test project file with dependencies and project reference.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SaveDocumentToDb (API)
    participant JsonSchemaValidatorRegistry
    participant Mapper
    participant MarketDataRepository
    participant CosmosDB

    Client->>SaveDocumentToDb (API): POST /SaveDocumentToDb?datatype=...&assetclass=...&schemaversion=...
    SaveDocumentToDb (API)->>JsonSchemaValidatorRegistry: Validate(payload, schema params)
    alt Schema valid
        SaveDocumentToDb (API)->>Mapper: Deserialize and map DTO to domain model
        SaveDocumentToDb (API)->>MarketDataRepository: SaveMarketDataAsync(domain model)
        MarketDataRepository->>CosmosDB: Save document
        CosmosDB-->>MarketDataRepository: Save result
        MarketDataRepository-->>SaveDocumentToDb (API): SaveMarketDataResult
        SaveDocumentToDb (API)-->>Client: 200 OK / error based on result
    else Schema invalid
        SaveDocumentToDb (API)-->>Client: 400 Bad Request (validation errors)
    end
Loading

Possibly related PRs

  • phoenixvc/Phoenix.MarketDataPlatform#1: Introduced support for region and asOfTime across domain models, schemas, and repository methods, which directly relates to the new models and repository logic in this PR.
  • phoenixvc/Phoenix.MarketDataPlatform#2: Shares similar changes in SaveDocumentToDb.cs and HTTP request examples, likely overlapping or related in handling multiple market data types and validation.

Poem

In the fields of code where data hops,
New schemas bloom and mapping never stops.
FX and ordinals, side by side,
With JSON rules our docs now ride.
The rabbit logs each spot and side,
And saves them safe, with schema as guide.
🐇✨
"""

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 229135e and 1a023f9.

📒 Files selected for processing (2)
  • Phoenix.MarketData.Infrastructure.Tests/MarketDataRepositoryTests.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Serialization/CryptoOrdinalSpotPriceDataDto.cs (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🔭 Outside diff range comments (4)
src/Phoenix.MarketData.Infrastructure/Serialization/BaseMarketDataDto.cs (2)

50-58: 🛠️ Refactor suggestion

Add [JsonConstructor] to keep constructor–deserializer behaviour predictable

Newtonsoft.Json will now prefer the (new) parameter-less ctor and set properties via reflection.
If any callers relied on the 12-parameter ctor for population (e.g., BaseMarketDataMapper.ToDto), they will silently stop working unless you decorate that ctor with [JsonConstructor].

@@
-public BaseMarketDataDto(string id, string schemaVersion, int? version, string assetId, string assetClass, 
+[JsonConstructor]
+public BaseMarketDataDto(string id, string schemaVersion, int? version, string assetId, string assetClass, 

54-70: ⚠️ Potential issue

Collection-expression [] requires C# 12 – fall back to new List<string>() for wider compatibility

Tags = tags ?? []; compiles only under C# 12/ .NET 8.0+.
If the project targets earlier versions (many Azure Functions projects are still on .NET 6/7) this line will not build.

-        Tags = tags ?? [];
+        Tags = tags ?? new List<string>();
src/Phoenix.MarketData.Domain/Models/BaseMarketData.cs (1)

146-159: 🛠️ Refactor suggestion

CalculateId() should guard against an unset AsOfDate

When AsOfDate is left at its default (0001-01-01), the generated ID becomes misleading and may collide across instruments.

-        if (string.IsNullOrEmpty(DataType) || string.IsNullOrEmpty(AssetClass) || string.IsNullOrEmpty(AssetId) || 
-            string.IsNullOrEmpty(Region) || string.IsNullOrEmpty(DocumentType))
+        if (string.IsNullOrEmpty(DataType)
+            || string.IsNullOrEmpty(AssetClass)
+            || string.IsNullOrEmpty(AssetId)
+            || string.IsNullOrEmpty(Region)
+            || string.IsNullOrEmpty(DocumentType)
+            || AsOfDate == default)
         {
             return string.Empty;
         }
src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (1)

100-108: ⚠️ Potential issue

Keep the return-type contract consistent for version-conflict scenarios

SaveMarketDataAsync is designed to never throw to the caller but to wrap any error in SaveMarketDataResult.
However, in the Conflict branch the method still throws when the max-retry threshold is exceeded, breaking the new contract and forcing callers to add try/catch again.

- if (attempt == maxRetries)
-     throw;
+ if (attempt == maxRetries)
+     return new SaveMarketDataResult
+     {
+         Success = false,
+         Message = $"Version conflict after {maxRetries} retries.",
+         Exception = ex
+     };

Aligning all branches with the same result pattern makes the API simpler and avoids an unexpected exception path.

🧹 Nitpick comments (17)
src/Phoenix.MarketData.Infrastructure/Serialization/PriceSideDto.cs (1)

5-15: Add XML documentation to the enum

The enum is well-structured with proper EnumMember attributes for serialization. Consider adding XML documentation to explain its purpose and the meaning of each value, which would improve code maintainability and API documentation.

 namespace Phoenix.MarketData.Infrastructure.Serialization;

+/// <summary>
+/// Represents the side of a price quote for serialization purposes.
+/// </summary>
 public enum PriceSideDto
 {
+    /// <summary>
+    /// Represents the middle price between bid and ask.
+    /// </summary>
     [EnumMember(Value = "Mid")]
     Mid = 0,
     
+    /// <summary>
+    /// Represents the price at which a buyer is willing to purchase.
+    /// </summary>
     [EnumMember(Value = "Bid")]
     Bid = 1,
     
+    /// <summary>
+    /// Represents the price at which a seller is willing to sell.
+    /// </summary>
     [EnumMember(Value = "Ask")]
     Ask = 2,
 }
Phoenix.MarketData.Functions/HttpRequests/SaveMarketData.http (1)

1-40: Update HTTP file with server variables for better maintainability

Consider using HTTP file variables to make the examples more maintainable, especially if the same host/port is used in multiple requests.

+@host = localhost:7006
+
 ### Save fx spot market data to cosmos db example
-POST localhost:7006/api/SaveDocumentToDb?datatype=price.spot&assetClass=fx&schemaversion=1.0.0
+POST {{host}}/api/SaveDocumentToDb?datatype=price.spot&assetClass=fx&schemaversion=1.0.0
 Content-Type: application/json

 ...

 ### Save crypto ordinal spot price data to cosmos db example
-POST localhost:7006/api/SaveDocumentToDb?datatype=price.ordinals.spot&assetClass=crypto&schemaversion=1.0.0
+POST {{host}}/api/SaveDocumentToDb?datatype=price.ordinals.spot&assetClass=crypto&schemaversion=1.0.0
 Content-Type: application/json
src/Phoenix.MarketData.Infrastructure/Schemas/FxSpotPriceData_v1.0.0.schema.json (1)

69-71: Consider case-insensitive pattern for side property

The pattern currently enforces lowercase values ("bid|mid|ask"), which might lead to validation errors if the client sends mixed-case values like "Bid" or "MID". Consider using a case-insensitive pattern or adding a description note about the required case.

-      "pattern": "^(bid|mid|ask)$",
+      "pattern": "^(?i)(bid|mid|ask)$",
       "description": "The side of the price quote, e.g., mid, bid, or ask"

Alternatively, document the case requirement:

       "pattern": "^(bid|mid|ask)$",
-      "description": "The side of the price quote, e.g., mid, bid, or ask"
+      "description": "The side of the price quote, lowercase only: mid, bid, or ask"
src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidator.cs (1)

6-7: Consider implementing a more modern result pattern.

The class returns a boolean with an out parameter for error messages, which works but isn't aligned with modern C# patterns.

Consider implementing a result pattern that encapsulates both success and error information:

+ public class ValidationResult
+ {
+     public bool IsValid { get; }
+     public string ErrorMessage { get; }
+     
+     private ValidationResult(bool isValid, string errorMessage = null)
+     {
+         IsValid = isValid;
+         ErrorMessage = errorMessage;
+     }
+     
+     public static ValidationResult Success() => new ValidationResult(true);
+     public static ValidationResult Failure(string errorMessage) => new ValidationResult(false, errorMessage);
+ }

public class JsonSchemaValidator
{
    // ... existing code ...
    
+   public ValidationResult Validate(string jsonPayload)
+   {
+       try
+       {
+           var element = JsonDocument.Parse(jsonPayload).RootElement;
+           var result = _schema.Evaluate(element, new EvaluationOptions { OutputFormat = OutputFormat.Hierarchical });
+           
+           if (result.IsValid)
+           {
+               return ValidationResult.Success();
+           }
+           
+           return ValidationResult.Failure(FormatValidationErrors(result));
+       }
+       catch (JsonException ex)
+       {
+           return ValidationResult.Failure($"Invalid JSON format: {ex.Message}");
+       }
+   }
}

You can keep the existing method for backward compatibility if needed.

src/Phoenix.MarketData.Infrastructure/Mapping/CryptoOrdinalSpotPriceDataMapper.cs (1)

7-19: Add XML documentation for class and methods.

The class and methods lack XML documentation, which would improve API discoverability and usage.

Consider adding XML documentation:

+ /// <summary>
+ /// Provides mapping functionality between <see cref="CryptoOrdinalSpotPriceData"/> domain model and 
+ /// <see cref="CryptoOrdinalSpotPriceDataDto"/> data transfer object.
+ /// </summary>
public static class CryptoOrdinalSpotPriceDataMapper
{
+   /// <summary>
+   /// Converts a domain model to a DTO.
+   /// </summary>
+   /// <param name="domain">The domain model to convert. Cannot be null.</param>
+   /// <returns>A new DTO with values from the domain model.</returns>
+   /// <exception cref="ArgumentNullException">Thrown when domain is null.</exception>
    public static CryptoOrdinalSpotPriceDataDto ToDto(CryptoOrdinalSpotPriceData domain)
    {
        ArgumentNullException.ThrowIfNull(domain);
        
        var dto = new CryptoOrdinalSpotPriceDataDto(domain.Id, domain.SchemaVersion, domain.Version,
            domain.AssetId, domain.AssetClass, domain.DataType, domain.Region, domain.DocumentType,
            domain.CreateTimestamp, domain.AsOfDate, domain.AsOfTime, domain.Tags, domain.Price, domain.Side,
            domain.CollectionName, domain.ParentInscriptionId, domain.InscriptionId, domain.InscriptionNumber);

        return dto;
    }
src/Phoenix.MarketData.Infrastructure/Schemas/CryptoOrdinalSpotPriceData_v1.0.0.schema.json (2)

2-5: Consider adding additionalProperties constraint.

The schema doesn't specify whether additional properties are allowed. This could lead to unexpected fields being present in the data.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "OrdinalPrice - Crypto",
  "description": "Schema for spot prices for ordinal assets (e.g., quantum cats).",
  "type": "object",
+  "additionalProperties": false,
  "required": ["assetId", "dataType", "assetClass", "asOfDate", "price", "collectionName", "parentInscriptionId", "inscriptionId", "inscriptionNumber"],

64-67: Update price property description.

The price property description mentions "FX instrument" which is incorrect for crypto ordinals.

"price": {
  "type": "number",
-  "description": "The spot price of the FX instrument (e.g., 1.0985 for EUR/USD)."
+  "description": "The spot price of the crypto ordinal (e.g., 10.5 BTC for a specific ordinal)."
}
src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidatorRegistry.cs (2)

17-18: Mark the dictionary as readonly to avoid accidental reassignment

The reference to _validatorPaths should not change after construction.
Changing only the mutability of the reference (not the contents) improves safety without blocking normal Add/Remove operations.

-    private Dictionary<SchemaKey, string> _validatorPaths = new Dictionary<SchemaKey, string>();
+    private readonly Dictionary<SchemaKey, string> _validatorPaths = new();

36-38: Avoid re-reading schema files on every call – cache JsonSchemaValidator instances

new JsonSchemaValidator(path) reloads the schema from disk for every validation, which is I/O-heavy and becomes a bottleneck under load.
Store JsonSchemaValidator instances in the dictionary (or a second cache) after first creation.

src/Phoenix.MarketData.Infrastructure/Serialization/CryptoOrdinalSpotPriceDataDto.cs (1)

30-49: Guard against negative prices and empty identifiers

DTOs are often the first defence line. A negative price or missing inscription IDs most likely indicates bad upstream data.

-        Price = price;
+        if (price < 0)
+            throw new ArgumentOutOfRangeException(nameof(price), "Price must be non-negative.");
+        Price = price;

-        ParentInscriptionId = parentInscriptionId;
-        InscriptionId = inscriptionId;
+        ParentInscriptionId = string.IsNullOrWhiteSpace(parentInscriptionId)
+            ? throw new ArgumentException("ParentInscriptionId is required", nameof(parentInscriptionId))
+            : parentInscriptionId;
+        InscriptionId = string.IsNullOrWhiteSpace(inscriptionId)
+            ? throw new ArgumentException("InscriptionId is required", nameof(inscriptionId))
+            : inscriptionId;
Phoenix.MarketData.Functions/SaveDocumentToDb.cs (2)

24-27: Restrict the trigger to POST – GET requests have no body

The function accepts "get" but immediately tries to read the request body.
Unless you intend to support query-only GET requests (which are not implemented), drop "get" to prevent confusing 400 responses.

-    [Function("SaveDocumentToDb")]
-    public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
+    [Function("SaveDocumentToDb")]
+    public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)

105-121: Null-safe message handling

result.Message can be null, but the code compares it to string.Empty, leading to a possible NullReferenceException.

-            var msg = result.Message != string.Empty
+            var msg = !string.IsNullOrWhiteSpace(result.Message)
                 ? result.Message
                 : $"Document saved successfully to {result.Id}.";
@@
-        var message = result.Message != string.Empty ? result.Message : $"Error saving document to {result.Id}.";
+        var message = !string.IsNullOrWhiteSpace(result.Message)
+            ? result.Message
+            : $"Error saving document to {result.Id}.";
src/Phoenix.MarketData.Infrastructure/Serialization/FxSpotPriceDataDto.cs (2)

11-13: Side can be non-nullable – it always gets a value

Because the ctor always assigns Side a PriceSideDto value (defaulting to Mid), callers will never observe a null.
Declaring it as nullable (PriceSideDto?) only complicates downstream null-checks.

-    public PriceSideDto? Side { get; set; }
+    public PriceSideDto Side { get; set; } = PriceSideDto.Mid;

14-17: Mark parameter-less ctor as internal if it is serializer-only

If you added the ctor solely for Newtonsoft.Json, consider restricting the surface area:

-    public FxSpotPriceDataDto()
+    internal FxSpotPriceDataDto()

It prevents accidental misuse while keeping the serializer happy.

src/Phoenix.MarketData.Domain/Models/BaseMarketData.cs (1)

24-134: Consider extracting the “lower-case-and-recalculate-id” pattern to reduce duplication

Each required property setter repeats the same three steps.
A small helper (e.g., SetAndInvalidate(ref field, value)) or OnPropertyChanged pattern would:

  • remove ~90 lines of boilerplate,
  • reduce the chance of forgetting the ToLowerInvariant() or _id = CalculateId() in the future.

This is a readability / maintainability win.

src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (2)

236-247: Guard against unsupported domain types earlier

MapToDto returns object, deferring type mismatch detection until runtime.
Consider making it generic to return the specific DTO type or restricting T with a common base DTO interface to get compile-time safety.

Alternatively, at least bubble up the unsupported type information:

_ => throw new NotSupportedException(
-    $"Unsupported market data type: {typeof(T).Name}")
+    $"Unsupported market data type {typeof(T).Name}. Ensure a mapper is added in MapToDto().")

This tiny change makes troubleshooting far easier when new models are introduced.


40-53: Optional: support CancellationToken for better caller control

All public async methods omit CancellationToken.
Cosmos SDK fully supports it, and long-running IO calls (writes & queries) benefit from allowing the caller to cancel (e.g., API request aborted, graceful shutdown).

-public async Task<SaveMarketDataResult> SaveMarketDataAsync<T>(T marketData, bool saveNextVersion = true)
+public async Task<SaveMarketDataResult> SaveMarketDataAsync<T>(
+    T marketData,
+    bool saveNextVersion = true,
+    CancellationToken cancellationToken = default)

Propagate the token to GetNextVersionAsync, CreateItemAsync, ReadNextAsync, etc.
This is a non-breaking additive improvement for current callers.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3891c9a and 9fa2758.

📒 Files selected for processing (23)
  • Phoenix.MarketData.Functions/HttpRequests/SaveMarketData.http (1 hunks)
  • Phoenix.MarketData.Functions/SaveDocumentToDb.cs (2 hunks)
  • Phoenix.MarketDataPlatform.sln (3 hunks)
  • src/Phoenix.MarketData.Domain/Models/BaseMarketData.cs (3 hunks)
  • src/Phoenix.MarketData.Domain/Models/CryptoOrdinalSpotPriceData.cs (1 hunks)
  • src/Phoenix.MarketData.Domain/Models/FxSpotPriceData.cs (1 hunks)
  • src/Phoenix.MarketData.Domain/Models/Interfaces/IMarketData.cs (1 hunks)
  • src/Phoenix.MarketData.Domain/PriceSide.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (5 hunks)
  • src/Phoenix.MarketData.Infrastructure/Cosmos/VersionManager.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Mapping/BaseMarketDataMapper.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Mapping/CryptoOrdinalSpotPriceDataMapper.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Mapping/FxSpotPriceDataMapper.cs (2 hunks)
  • src/Phoenix.MarketData.Infrastructure/Phoenix.MarketData.Infrastructure.csproj (2 hunks)
  • src/Phoenix.MarketData.Infrastructure/Schemas/CryptoOrdinalSpotPriceData_v1.0.0.schema.json (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Schemas/FxSpotPriceData_v1.0.0.schema.json (2 hunks)
  • src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidator.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidatorRegistry.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Schemas/SchemaVersions.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Serialization/BaseMarketDataDto.cs (3 hunks)
  • src/Phoenix.MarketData.Infrastructure/Serialization/CryptoOrdinalSpotPriceDataDto.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Serialization/FxSpotPriceDataDto.cs (2 hunks)
  • src/Phoenix.MarketData.Infrastructure/Serialization/PriceSideDto.cs (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/Phoenix.MarketData.Infrastructure/Mapping/CryptoOrdinalSpotPriceDataMapper.cs (3)
src/Phoenix.MarketData.Infrastructure/Serialization/CryptoOrdinalSpotPriceDataDto.cs (3)
  • CryptoOrdinalSpotPriceDataDto (6-50)
  • CryptoOrdinalSpotPriceDataDto (26-28)
  • CryptoOrdinalSpotPriceDataDto (30-49)
src/Phoenix.MarketData.Domain/Models/CryptoOrdinalSpotPriceData.cs (1)
  • CryptoOrdinalSpotPriceData (3-34)
src/Phoenix.MarketData.Domain/AssetClass.cs (1)
  • AssetClass (6-14)
src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidatorRegistry.cs (2)
src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidator.cs (3)
  • Validate (16-33)
  • JsonSchemaValidator (6-34)
  • JsonSchemaValidator (10-14)
src/Phoenix.MarketData.Domain/AssetClass.cs (1)
  • AssetClass (6-14)
src/Phoenix.MarketData.Infrastructure/Mapping/FxSpotPriceDataMapper.cs (3)
src/Phoenix.MarketData.Domain/Models/FxSpotPriceData.cs (1)
  • FxSpotPriceData (7-22)
src/Phoenix.MarketData.Infrastructure/Serialization/FxSpotPriceDataDto.cs (3)
  • FxSpotPriceDataDto (6-32)
  • FxSpotPriceDataDto (14-16)
  • FxSpotPriceDataDto (18-31)
src/Phoenix.MarketData.Domain/AssetClass.cs (1)
  • AssetClass (6-14)
src/Phoenix.MarketData.Infrastructure/Serialization/BaseMarketDataDto.cs (1)
src/Phoenix.MarketData.Infrastructure/Mapping/BaseMarketDataMapper.cs (1)
  • BaseMarketDataDto (8-15)
src/Phoenix.MarketData.Domain/Models/BaseMarketData.cs (1)
src/Phoenix.MarketData.Domain/AssetClass.cs (1)
  • AssetClass (6-14)
src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (7)
src/Phoenix.MarketData.Infrastructure/Cosmos/VersionManager.cs (3)
  • VersionManager (5-28)
  • VersionManager (9-12)
  • Task (14-27)
src/Phoenix.MarketData.Infrastructure/Cosmos/CosmosClientFactory.cs (1)
  • CosmosClient (9-16)
src/Phoenix.MarketData.Infrastructure/Serialization/JsonConverters/DateOnlyJsonConverter.cs (1)
  • DateOnly (25-29)
src/Phoenix.MarketData.Infrastructure/Mapping/FxSpotPriceDataMapper.cs (2)
  • FxSpotPriceData (20-48)
  • FxSpotPriceDataMapper (7-49)
src/Phoenix.MarketData.Domain/Models/FxSpotPriceData.cs (1)
  • FxSpotPriceData (7-22)
src/Phoenix.MarketData.Infrastructure/Mapping/CryptoOrdinalSpotPriceDataMapper.cs (2)
  • CryptoOrdinalSpotPriceData (21-53)
  • CryptoOrdinalSpotPriceDataMapper (7-54)
src/Phoenix.MarketData.Domain/Models/CryptoOrdinalSpotPriceData.cs (1)
  • CryptoOrdinalSpotPriceData (3-34)
🔇 Additional comments (18)
src/Phoenix.MarketData.Domain/Models/Interfaces/IMarketData.cs (1)

6-6: Interface renamed to better align with domain terminology.

The interface name change from IMarketDataObject to IMarketData improves consistency across the domain models and infrastructure layers. This renaming clarifies the purpose of the interface without changing its functionality.

src/Phoenix.MarketData.Infrastructure/Phoenix.MarketData.Infrastructure.csproj (2)

10-10: Added JSON schema validation capability.

Good addition of the JsonSchema.Net package, providing the infrastructure needed for schema validation of market data objects.


19-26: Schema files added for new market data types.

The addition of JSON schema files for Crypto Ordinal and FX Spot price data types with proper output directory configuration enables runtime validation of these market data formats.

src/Phoenix.MarketData.Infrastructure/Mapping/BaseMarketDataMapper.cs (1)

32-32: Improved null handling for CreateTimestamp.

Good enhancement to handle null timestamps by using the current UTC time as a fallback. This makes the mapping more robust and prevents potential null reference issues downstream.

src/Phoenix.MarketData.Domain/PriceSide.cs (1)

1-15: Well-defined enum for price side representation.

This new enum is a good addition to the domain model, providing a type-safe way to represent different price sides (mid, bid, ask) commonly used in market data. The use of Description attributes helps with display and serialization concerns.

src/Phoenix.MarketData.Domain/Models/FxSpotPriceData.cs (3)

9-14: Well-documented property

The XML documentation for the Price property is comprehensive and clearly explains its purpose within the domain model.


16-21: Properly documented new Side property with appropriate default

The addition of the Side property with a default value of PriceSide.Mid is well-implemented and thoroughly documented.


7-22:

Details

❓ Verification inconclusive

Verify the BaseMarketData inheritance is properly handled

Ensure the new Side property doesn't conflict with any properties or methods in the BaseMarketData class and that any required serialization/mapping logic is updated accordingly.


🏁 Script executed:

#!/bin/bash
# Check if BaseMarketData has any conflicting properties or methods
rg "public\s+(virtual\s+)?(required\s+)?(abstract\s+)?(\w+\s+)?Side(\s*\{|\s*=)" -t cs src/Phoenix.MarketData.Domain/

Length of output: 236


FxSpotPriceData – BaseMarketData inheritance checked
No Side member exists on BaseMarketData, so there’s no inheritance conflict.

• Now confirm that any serialization or mapping layers (e.g. AutoMapper profiles, DTOs, JSON converters) have been updated to include the new Side property so it’s correctly persisted and transmitted.

src/Phoenix.MarketData.Infrastructure/Schemas/FxSpotPriceData_v1.0.0.schema.json (1)

31-32: Updated dataType to be more specific

The change from "price" to "price.spot" in the dataType enum makes the schema more precise and aligns with the domain model.

src/Phoenix.MarketData.Infrastructure/Schemas/SchemaVersions.cs (1)

12-12: Schema version correctly updated to V1

The schema version has been properly updated from V0 to V1, aligning with the introduction of new JSON schemas for version "1.0.0" that support multiple market data types. The supported versions collection has been updated accordingly, and the placeholder for future versions now correctly references V2.

Also applies to: 15-15, 22-22

Phoenix.MarketDataPlatform.sln (1)

11-14: Test project added properly

The test project structure has been correctly added to the solution, including proper nesting under the tests folder and appropriate build configurations. This supports the expanded testing coverage for the enhanced market data infrastructure.

Also applies to: 24-24, 39-42

src/Phoenix.MarketData.Infrastructure/Mapping/FxSpotPriceDataMapper.cs (3)

1-2: Imports added for domain models

These imports correctly support the reference to domain models and the PriceSide enum used in the updated mapper implementation.


15-15: Side property added to DTO constructor

The ToDto method now correctly maps the new Side property from the domain model to the DTO.


20-48: Method replaced with improved implementation

The previous ApplyToDomain method has been replaced with a better ToDomain method that:

  1. Creates a new domain object instead of modifying an existing one
  2. Properly maps all properties including the new Side property
  3. Uses appropriate null handling with fallbacks
  4. Handles enum conversion with a comprehensive switch expression

This change follows better immutability practices and aligns with the enhanced domain model.

src/Phoenix.MarketData.Infrastructure/Cosmos/VersionManager.cs (2)

15-15: Interface constraint updated

The type constraint has been correctly updated from IMarketDataObject to IMarketData to align with the renamed interface in the domain model.


22-23: Repository method and result handling updated

The code now calls the updated repository method GetMarketDataByLatestVersionAsync and correctly handles the richer result type by accessing the Result property. The version increment logic remains properly implemented.

Also applies to: 26-26

src/Phoenix.MarketData.Domain/Models/CryptoOrdinalSpotPriceData.cs (1)

3-34: LGTM! The domain model is well-structured with clear documentation.

The CryptoOrdinalSpotPriceData class appropriately inherits from BaseMarketData and provides all necessary properties with XML documentation. The required modifier is used correctly for most properties.

src/Phoenix.MarketData.Infrastructure/Serialization/BaseMarketDataDto.cs (1)

39-41: Nullable CreateTimestamp mismatches non-nullable ctor argument

CreateTimestamp is now nullable, yet the ctor enforces a non-nullable argument.
Either make the parameter nullable or keep the property non-nullable to avoid confusion and double-null-checking elsewhere.

Comment on lines 68 to 72
"side": {
"type": "string",
"pattern": "^(bid|mid|ask)$",
"description": "The side of the price quote, e.g., mid, bid, or ask"
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Properly defined side property with pattern validation

The addition of the "side" property with pattern validation ensures only valid values ("bid", "mid", "ask") are accepted. However, it's not included in the required properties list despite being a necessary field.

-  "required": ["assetId", "dataType", "assetClass", "asOfDate", "price"],
+  "required": ["assetId", "dataType", "assetClass", "asOfDate", "price", "side"],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"side": {
"type": "string",
"pattern": "^(bid|mid|ask)$",
"description": "The side of the price quote, e.g., mid, bid, or ask"
}
"required": ["assetId", "dataType", "assetClass", "asOfDate", "price", "side"],

Comment on lines 16 to 33
public bool Validate(string jsonPayload, out string errorMessage)
{
var element = JsonDocument.Parse(jsonPayload).RootElement;
var result = _schema.Evaluate(element, new EvaluationOptions { OutputFormat = OutputFormat.Hierarchical });

if (result.IsValid)
{
errorMessage = null;
return true;
}

// Failed to validate
errorMessage = string.Join("; ", result.Details.Select(
e => e.InstanceLocation + ": " + (e.HasErrors
? string.Join("\n", e.Errors?.Select(kvp => kvp.Key.ToString() + " - " + kvp.Value.ToString()) ?? Array.Empty<string>())
: "")));
return false;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for JSON parsing and improve error message readability.

The Validate method doesn't handle exceptions from JsonDocument.Parse, and the error message formatting is complex.

Consider adding try-catch for JSON parsing and extracting the error formatting logic to a separate method:

public bool Validate(string jsonPayload, out string errorMessage)
{
+    try
+    {
        var element = JsonDocument.Parse(jsonPayload).RootElement;
        var result = _schema.Evaluate(element, new EvaluationOptions { OutputFormat = OutputFormat.Hierarchical });

        if (result.IsValid)
        {
            errorMessage = null;
            return true;
        }

        // Failed to validate
-        errorMessage = string.Join("; ", result.Details.Select(
-            e => e.InstanceLocation + ": " + (e.HasErrors
-                ? string.Join("\n", e.Errors?.Select(kvp => kvp.Key.ToString() + " - " + kvp.Value.ToString()) ?? Array.Empty<string>())
-                : "")));
+        errorMessage = FormatValidationErrors(result);
        return false;
+    }
+    catch (JsonException ex)
+    {
+        errorMessage = $"Invalid JSON format: {ex.Message}";
+        return false;
+    }
}

+ private static string FormatValidationErrors(EvaluationResults result)
+ {
+     return string.Join("; ", result.Details.Select(
+         e => e.InstanceLocation + ": " + (e.HasErrors
+             ? string.Join("\n", e.Errors?.Select(kvp => kvp.Key.ToString() + " - " + kvp.Value.ToString()) ?? Array.Empty<string>())
+             : "")));
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public bool Validate(string jsonPayload, out string errorMessage)
{
var element = JsonDocument.Parse(jsonPayload).RootElement;
var result = _schema.Evaluate(element, new EvaluationOptions { OutputFormat = OutputFormat.Hierarchical });
if (result.IsValid)
{
errorMessage = null;
return true;
}
// Failed to validate
errorMessage = string.Join("; ", result.Details.Select(
e => e.InstanceLocation + ": " + (e.HasErrors
? string.Join("\n", e.Errors?.Select(kvp => kvp.Key.ToString() + " - " + kvp.Value.ToString()) ?? Array.Empty<string>())
: "")));
return false;
}
public bool Validate(string jsonPayload, out string errorMessage)
{
try
{
var element = JsonDocument.Parse(jsonPayload).RootElement;
var result = _schema.Evaluate(element, new EvaluationOptions { OutputFormat = OutputFormat.Hierarchical });
if (result.IsValid)
{
errorMessage = null;
return true;
}
// Failed to validate
errorMessage = FormatValidationErrors(result);
return false;
}
catch (JsonException ex)
{
errorMessage = $"Invalid JSON format: {ex.Message}";
return false;
}
}
private static string FormatValidationErrors(EvaluationResults result)
{
return string.Join("; ", result.Details.Select(
e => e.InstanceLocation + ": " + (e.HasErrors
? string.Join("\n", e.Errors?.Select(kvp => kvp.Key.ToString() + " - " + kvp.Value.ToString())
?? Array.Empty<string>())
: "")));
}

Comment on lines +32 to +35
if (string.IsNullOrWhiteSpace(dataType) || string.IsNullOrWhiteSpace(assetClass) || string.IsNullOrEmpty("schemaversion"))
{
return new BadRequestObjectResult("Please provide 'datatype', 'assetclass' and 'schema' as query parameters.");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: schemaVersion check is against a string literal – validation always passes

string.IsNullOrEmpty("schemaversion") is always false, so the function never verifies that the caller actually supplied the schemaversion parameter.

-        if (string.IsNullOrWhiteSpace(dataType) || string.IsNullOrWhiteSpace(assetClass) || string.IsNullOrEmpty("schemaversion"))
+        if (string.IsNullOrWhiteSpace(dataType) || string.IsNullOrWhiteSpace(assetClass) || string.IsNullOrWhiteSpace(schemaVersion))

Comment on lines 73 to 98
if (dataType == "price.ordinals.spot" && assetClass == "crypto" && schemaVersion != string.Empty)
{
var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
CryptoOrdinalSpotPriceDataDto? requestData;

try
{
requestData = System.Text.Json.JsonSerializer.Deserialize<CryptoOrdinalSpotPriceDataDto>(requestBody, new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});

if (requestData == null)
{
throw new ArgumentNullException(nameof(requestData));
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deserializing request body for fx spot price.");
return new BadRequestObjectResult("Invalid request body.");
}

var result = await _repository.SaveMarketDataAsync(CryptoOrdinalSpotPriceDataMapper.ToDomain(requestData));
return ProcessActionResult(result);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add schema validation + correct logging for the crypto branch

The crypto branch skips JSON-schema validation and logs the wrong asset class on deserialization errors.

-            var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
+            var requestBody = await new StreamReader(req.Body).ReadToEndAsync();

             CryptoOrdinalSpotPriceDataDto? requestData;
             
             try
             {
+                var validated = JsonSchemaValidatorRegistry.Instance.Validate(
+                    dataType!, assetClass!, schemaVersion!, requestBody, out var errorMsg);
+                if (!validated)
+                {
+                    _logger.LogError($"Error validating request body for crypto ordinal spot price. {errorMsg}");
+                    return new BadRequestObjectResult("Could not validate request body against schema.");
+                }
+
                 requestData = System.Text.Json.JsonSerializer.Deserialize<CryptoOrdinalSpotPriceDataDto>(requestBody, new System.Text.Json.JsonSerializerOptions
                 {
                     PropertyNameCaseInsensitive = true
                 });
@@
-            catch (Exception ex)
+            catch (Exception ex)
             {
-                _logger.LogError(ex, "Error deserializing request body for fx spot price.");
+                _logger.LogError(ex, "Error deserializing request body for crypto ordinal spot price.");
                 return new BadRequestObjectResult("Invalid request body.");
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (dataType == "price.ordinals.spot" && assetClass == "crypto" && schemaVersion != string.Empty)
{
var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
CryptoOrdinalSpotPriceDataDto? requestData;
try
{
requestData = System.Text.Json.JsonSerializer.Deserialize<CryptoOrdinalSpotPriceDataDto>(requestBody, new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
if (requestData == null)
{
throw new ArgumentNullException(nameof(requestData));
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deserializing request body for fx spot price.");
return new BadRequestObjectResult("Invalid request body.");
}
var result = await _repository.SaveMarketDataAsync(CryptoOrdinalSpotPriceDataMapper.ToDomain(requestData));
return ProcessActionResult(result);
}
if (dataType == "price.ordinals.spot" && assetClass == "crypto" && schemaVersion != string.Empty)
{
var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
CryptoOrdinalSpotPriceDataDto? requestData;
try
{
// JSON‐schema validation for crypto branch
var validated = JsonSchemaValidatorRegistry.Instance.Validate(
dataType!, assetClass!, schemaVersion!, requestBody, out var errorMsg);
if (!validated)
{
_logger.LogError($"Error validating request body for crypto ordinal spot price. {errorMsg}");
return new BadRequestObjectResult("Could not validate request body against schema.");
}
requestData = System.Text.Json.JsonSerializer.Deserialize<CryptoOrdinalSpotPriceDataDto>(requestBody, new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
if (requestData == null)
{
throw new ArgumentNullException(nameof(requestData));
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deserializing request body for crypto ordinal spot price.");
return new BadRequestObjectResult("Invalid request body.");
}
var result = await _repository.SaveMarketDataAsync(CryptoOrdinalSpotPriceDataMapper.ToDomain(requestData));
return ProcessActionResult(result);
}

mareeben and others added 3 commits May 14, 2025 12:32
…tor.cs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…ory.cs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…ation

Introduced a `currency` field to the `CryptoOrdinalSpotPriceData` model and schema for enhanced data representation. Enhanced request validation against schemas using a centralized validator logic. Added unit tests for repository operations and implemented lowercasing for query parameters to ensure consistency.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (2)

196-205: LGTM! Fixed empty result handling

The implementation now correctly sets Success=false when no document is found, making the API more consistent for consumers.


306-310: ⚠️ Potential issue

Normalize query parameters in extension method for consistency

Other queries lower-case parameters to match the normalised casing strategy in BaseMarketData.
The extension method omits this, which may cause partition-key or filter mismatches.

-.WithParameter("@assetId", assetId)
-.WithParameter("@assetClass", assetClass)
-.WithParameter("@region", region)
-.WithParameter("@dataType", dataType)
-.WithParameter("@documentType", documentType);
+.WithParameter("@assetId", assetId.ToLowerInvariant())
+.WithParameter("@assetClass", assetClass.ToLowerInvariant())
+.WithParameter("@region", region.ToLowerInvariant())
+.WithParameter("@dataType", dataType.ToLowerInvariant())
+.WithParameter("@documentType", documentType.ToLowerInvariant());

Aligning casing avoids hard-to-trace "document not found" issues.

🧹 Nitpick comments (2)
src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (2)

5-5: Remove unnecessary self-import

This namespace import is redundant since the file is already in the Phoenix.MarketData.Infrastructure.Cosmos namespace (defined on line 9).

-using Phoenix.MarketData.Infrastructure.Cosmos;

263-270: Consider removing redundant initializers for nullable properties

The properties are already declared as nullable with the ? modifier, so initializing them with default values is redundant.

-public string? Id { get; init; } = string.Empty;
+public string? Id { get; init; }

-public string? Message { get; init; } = string.Empty;
+public string? Message { get; init; }

Apply the same change to the Message property in LoadMarketDataResult<T> on line 280.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b702821 and b872b90.

📒 Files selected for processing (1)
  • src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (5 hunks)
🔇 Additional comments (4)
src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (4)

22-26: LGTM! Improved testability with additional constructor

Adding a constructor that accepts a Container directly improves testability and dependency injection capabilities.


58-111: Strong improvement with structured error handling

The enhanced exception handling with specific error cases and detailed messages is excellent. This approach of returning structured error results instead of throwing exceptions provides better control for calling code and improves observability.


135-139: LGTM! Parameter normalization applied consistently

Good implementation of the string normalization using ToLowerInvariant() for all query parameters. This ensures consistency with the normalized casing in the base market data model.


244-250: LGTM! Support added for CryptoOrdinalSpotPriceData

Correctly extended the mapping logic to handle the new CryptoOrdinalSpotPriceData type.

mareeben and others added 3 commits May 14, 2025 12:40
…torRegistry.cs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Added checks to handle null results in `MarketDataRepository` queries, ensuring proper error messages are returned. Enhanced `JsonSchemaValidator` with input validation and better exception handling for file reading and JSON parsing.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (5)
src/Phoenix.MarketData.Infrastructure/Schemas/CryptoOrdinalSpotPriceData_v1.0.0.schema.json (2)

24-26: Fix incorrect description for assetClass.

The description still incorrectly states this is for 'fx' instruments, but this is a crypto ordinal schema.

"assetClass": {
  "type": "string",
  "enum": ["crypto"],
-  "description": "Specifies the asset class; fixed as 'fx' for foreign exchange instruments."
+  "description": "Specifies the asset class; fixed as 'crypto' for cryptocurrency instruments."
}

88-91: Add minimum constraint to inscriptionNumber.

The inscriptionNumber is defined as a required field that should be a number, but there's no minimum value constraint. It could potentially be 0 or negative, which may not be valid.

"inscriptionNumber": {
  "type": "number",
+  "minimum": 0,
  "description": "The inscription number of the asset."
}
src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidator.cs (1)

28-46: Add error handling for JSON parsing and improve error message readability.

The Validate method doesn't handle exceptions from JsonDocument.Parse, and the error message formatting is complex.

Consider adding try-catch for JSON parsing and extracting the error formatting logic to a separate method:

public ValidationResult Validate(string jsonPayload)
{
+    try
+    {
        var element = JsonDocument.Parse(jsonPayload).RootElement;
        var result = _schema.Evaluate(element, new EvaluationOptions
        {
            OutputFormat = OutputFormat.Hierarchical,
            ValidateAgainstMetaSchema = true
        });

        if (result.IsValid)
            return ValidationResult.Success();

        // Failed to validate
-        var errorMessage = string.Join("; ", result.Details.Select(
-            e => e.InstanceLocation + ": " + (e.HasErrors
-                ? string.Join("\n", e.Errors?.Select(kvp => kvp.Key.ToString() + " - " + kvp.Value.ToString()) ?? Array.Empty<string>())
-                : "")));
+        var errorMessage = FormatValidationErrors(result);
        return ValidationResult.Failure(errorMessage);
+    }
+    catch (JsonException ex)
+    {
+        return ValidationResult.Failure($"Invalid JSON format: {ex.Message}");
+    }
}

+ private static string FormatValidationErrors(EvaluationResults result)
+ {
+     return string.Join("; ", result.Details.Select(
+         e => e.InstanceLocation + ": " + (e.HasErrors
+             ? string.Join("\n", e.Errors?.Select(kvp => kvp.Key.ToString() + " - " + kvp.Value.ToString()) ?? Array.Empty<string>())
+             : "")));
+ }
src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (2)

139-143: Parameter normalization implemented.

Great implementation of the previously suggested normalization of query parameters to lowercase, which aligns with the normalized casing strategy in BaseMarketData.


185-241: Centralized query execution with improved error handling.

The method includes comprehensive error handling for different exception types and returns meaningful error messages with the appropriate context.

The implementation also correctly addresses the previous suggestion to return failure when no documents are found, improving the semantics of the Success flag.

🧹 Nitpick comments (4)
src/Phoenix.MarketData.Infrastructure/Schemas/CryptoOrdinalSpotPriceData_v1.0.0.schema.json (1)

65-65: Fix typo in price description.

There's a double period at the end of the description.

-  "description": "The spot price of the crypto ordinal (e.g., 0.015 in BTC for a specific ordinal).."
+  "description": "The spot price of the crypto ordinal (e.g., 0.015 in BTC for a specific ordinal)."
src/Phoenix.MarketData.Infrastructure/Serialization/CryptoOrdinalSpotPriceDataDto.cs (1)

33-39: Improve constructor readability and parameter organization.

The constructor parameters are spread across multiple lines in a way that makes it difficult to read and maintain. Consider reformatting for better readability.

[JsonConstructor]
public CryptoOrdinalSpotPriceDataDto(string id, string schemaVersion, int? version,
    string assetId, string assetClass, string dataType, string region, string documentType,
    DateTimeOffset createTimestamp, DateOnly asOfDate, TimeOnly? asOfTime, List<string> tags,
-   decimal price, PriceSide? side, string collectionName, string parentInscriptionId,
-   string inscriptionId, int inscriptionNumber, string currency) : base(id, schemaVersion, version, assetId, assetClass, dataType,
-   region, documentType, createTimestamp, asOfDate, asOfTime, tags)
+   decimal price, PriceSide? side, string collectionName, 
+   string parentInscriptionId, string inscriptionId, 
+   int inscriptionNumber, string currency) 
+   : base(id, schemaVersion, version, assetId, assetClass, dataType,
+          region, documentType, createTimestamp, asOfDate, asOfTime, tags)
src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (2)

5-5: Unnecessary namespace import.

Phoenix.MarketData.Infrastructure.Cosmos is redundant since the file is already in this namespace.

-using Phoenix.MarketData.Infrastructure.Cosmos;

197-203: Potential redundant null check.

The check for feedIterator == null is likely unnecessary as GetItemQueryIterator shouldn't return null based on Cosmos DB SDK behavior.

-                if (feedIterator == null || !feedIterator.HasMoreResults) 
+                if (!feedIterator.HasMoreResults) 
                    return new LoadMarketDataResult<T>{ Success = false, Message = "Market data not found."};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 742e487 and 229135e.

📒 Files selected for processing (16)
  • Phoenix.MarketData.Functions/HttpRequests/SaveMarketData.http (1 hunks)
  • Phoenix.MarketData.Functions/SaveDocumentToDb.cs (2 hunks)
  • Phoenix.MarketData.Infrastructure.Tests/MarketDataRepositoryTests.cs (1 hunks)
  • Phoenix.MarketData.Infrastructure.Tests/Phoenix.MarketData.Infrastructure.Tests.csproj (1 hunks)
  • src/Phoenix.MarketData.Domain/Models/BaseMarketData.cs (2 hunks)
  • src/Phoenix.MarketData.Domain/Models/CryptoOrdinalSpotPriceData.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (4 hunks)
  • src/Phoenix.MarketData.Infrastructure/Mapping/CryptoOrdinalSpotPriceDataMapper.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Schemas/CryptoOrdinalSpotPriceData_v1.0.0.schema.json (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Schemas/FxSpotPriceData_v1.0.0.schema.json (2 hunks)
  • src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidator.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidatorRegistry.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Serialization/BaseMarketDataDto.cs (3 hunks)
  • src/Phoenix.MarketData.Infrastructure/Serialization/CryptoOrdinalSpotPriceDataDto.cs (1 hunks)
  • src/Phoenix.MarketData.Infrastructure/Serialization/FxSpotPriceDataDto.cs (2 hunks)
  • src/Phoenix.MarketData.Infrastructure/Serialization/PriceSideDto.cs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • Phoenix.MarketData.Infrastructure.Tests/Phoenix.MarketData.Infrastructure.Tests.csproj
🚧 Files skipped from review as they are similar to previous changes (10)
  • Phoenix.MarketData.Functions/HttpRequests/SaveMarketData.http
  • src/Phoenix.MarketData.Infrastructure/Mapping/CryptoOrdinalSpotPriceDataMapper.cs
  • src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidatorRegistry.cs
  • src/Phoenix.MarketData.Infrastructure/Serialization/FxSpotPriceDataDto.cs
  • src/Phoenix.MarketData.Infrastructure/Serialization/PriceSideDto.cs
  • Phoenix.MarketData.Functions/SaveDocumentToDb.cs
  • src/Phoenix.MarketData.Infrastructure/Serialization/BaseMarketDataDto.cs
  • src/Phoenix.MarketData.Domain/Models/CryptoOrdinalSpotPriceData.cs
  • src/Phoenix.MarketData.Infrastructure/Schemas/FxSpotPriceData_v1.0.0.schema.json
  • src/Phoenix.MarketData.Domain/Models/BaseMarketData.cs
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidator.cs (1)
src/Phoenix.MarketData.Infrastructure/Schemas/JsonSchemaValidatorRegistry.cs (1)
  • Validate (29-47)
src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (6)
src/Phoenix.MarketData.Infrastructure/Cosmos/VersionManager.cs (3)
  • VersionManager (5-28)
  • VersionManager (9-12)
  • Task (14-27)
src/Phoenix.MarketData.Infrastructure/Cosmos/CosmosClientFactory.cs (1)
  • CosmosClient (9-16)
src/Phoenix.MarketData.Infrastructure/Mapping/FxSpotPriceDataMapper.cs (2)
  • FxSpotPriceData (20-48)
  • FxSpotPriceDataMapper (7-49)
src/Phoenix.MarketData.Domain/Models/FxSpotPriceData.cs (1)
  • FxSpotPriceData (7-22)
src/Phoenix.MarketData.Domain/Models/CryptoOrdinalSpotPriceData.cs (1)
  • CryptoOrdinalSpotPriceData (3-39)
src/Phoenix.MarketData.Infrastructure/Mapping/CryptoOrdinalSpotPriceDataMapper.cs (2)
  • CryptoOrdinalSpotPriceData (36-69)
  • CryptoOrdinalSpotPriceDataMapper (12-70)
🔇 Additional comments (11)
src/Phoenix.MarketData.Infrastructure/Cosmos/MarketDataRepository.cs (11)

21-25: Good addition: Constructor with direct Container injection.

This new constructor allows for easier testing and dependency injection by accepting a pre-configured Container directly.


35-35: Interface change from IMarketDataObject to IMarketData.

The method signature has been updated to use IMarketData instead of IMarketDataObject, which aligns with the domain model changes.


57-118: Comprehensive error handling with structured result object.

The improved error handling now returns detailed SaveMarketDataResult objects instead of throwing exceptions, making it easier for callers to handle different error scenarios appropriately.


146-146: Reuse of query execution logic.

Extracting the query execution to a separate method improves code maintainability and reduces duplication.


161-176: Added method to retrieve specific version.

This new method enhances the API by allowing retrieval of a specific version of market data, while maintaining the pattern of normalized parameters and structured results.


243-243: Updated generic constraint to IMarketData.

The MapToDto method has been updated to accept IMarketData instead of IMarketDataObject, aligning with domain interface changes.


248-248: Support for new CryptoOrdinalSpotPriceData type.

The mapper now handles the newly added CryptoOrdinalSpotPriceData domain model, ensuring DTOs can be properly created for this data type.


258-269: Well-designed result class for save operations.

The SaveMarketDataResult class provides a structured way to communicate success/failure and includes relevant metadata about the operation.


271-280: Well-designed generic result class for load operations.

The LoadMarketDataResult class provides a structured way to communicate success/failure and includes the loaded data when successful.


283-313: Useful extension method for retrieving most recent market data.

This extension method provides a convenient way to get the most recent data by date, addressing a common use case. The implementation correctly normalizes query parameters and reuses the ExecuteMarketDataFetchQuery method.


190-195: Efficient query configuration.

The query options with MaxBufferedItemCount and MaxItemCount set to 1 optimize the query execution for single-item retrieval.

Comment on lines +62 to +82
// [Fact]
// public async Task GetMarketDataAsync_ShouldReturnNull_WhenItemDoesNotExist()
// {
// // Arrange
// var id = "non-existent-id";
// var partitionKey = "partition";
//
// _mockContainer
// .Setup(c => c.ReadItemAsync<MarketData>(
// It.IsAny<string>(),
// It.IsAny<PartitionKey>(),
// It.IsAny<ItemRequestOptions>(),
// It.IsAny<CancellationToken>()))
// .ThrowsAsync(new CosmosException("Not Found", HttpStatusCode.NotFound, 0, "", 0));
//
// // Act
// var result = await _repository.GetMarketDataAsync(id, partitionKey);
//
// // Assert
// Assert.Null(result);
// }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Uncomment and complete the test for non-existent items.

The test for the scenario when an item doesn't exist is commented out. This is an important test case to ensure the repository correctly handles missing data.

Either uncomment and update this test to work with the current implementation, or remove it if it's no longer relevant. If you decide to keep it, update it to use the correct return type and assertions.

Comment on lines +57 to +59
//Assert.NotNull(result);
//Assert.Equal("Test Data", result.Data);
//Assert.Equal(id, result.Id);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Uncomment and complete the assertions.

The test is missing proper assertions. Uncomment and update these to verify the correct data is returned.

// Assert
-//Assert.NotNull(result);
-//Assert.Equal("Test Data", result.Data);
-//Assert.Equal(id, result.Id);
+Assert.NotNull(result);
+Assert.True(result.IsSuccess);
+Assert.Equal(_marketData.Price, result.Data.Price);
+Assert.Equal(_marketData.AssetId, result.Data.AssetId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//Assert.NotNull(result);
//Assert.Equal("Test Data", result.Data);
//Assert.Equal(id, result.Id);
// Assert
Assert.NotNull(result);
Assert.True(result.IsSuccess);
Assert.Equal(_marketData.Price, result.Data.Price);
Assert.Equal(_marketData.AssetId, result.Data.AssetId);

mareeben and others added 3 commits May 14, 2025 17:51
…nalSpotPriceDataDto.cs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…sts.cs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Updated test assertions to use correct property names, ensuring consistency with the updated data model. This change improves test accuracy and prevents potential false positives in validation.
@mareeben mareeben merged commit 25088c1 into master May 14, 2025
@mareeben mareeben deleted the feature/new branch May 14, 2025 15:52
@coderabbitai coderabbitai bot mentioned this pull request May 21, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments