Skip to content

LNK-4621: data acquisition query plan rest api not properly validating values - #1383

Merged
edward-miller-lcg merged 11 commits into
devfrom
LNK-4621-Data-Acquisition-Query-Plan-REST-API-not-properly-validating-values
Feb 3, 2026
Merged

LNK-4621: data acquisition query plan rest api not properly validating values#1383
edward-miller-lcg merged 11 commits into
devfrom
LNK-4621-Data-Acquisition-Query-Plan-REST-API-not-properly-validating-values

Conversation

@edward-miller-lcg

@edward-miller-lcg edward-miller-lcg commented Jan 22, 2026

Copy link
Copy Markdown
Contributor

🛠️ Description of Changes

Add enhanced validation to Query Plan when add/updating it.

🧪 Testing Performed

Local: 1. e2e, 2. validated use cases defined in ticket.

image image

🧑‍🔬 Unit Testing

  • I have written or updated unit tests to cover my changes

📓 Documentation Updated

n.a

Summary by CodeRabbit

  • New Features

    • Introduced comprehensive validation for query plan configurations with enhanced error detection and improved error messaging.
    • Added automatic default values for paging parameters to ensure data consistency.
  • Tests

    • Added extensive test coverage for query plan validation scenarios and edge cases.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 22, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review
📝 Walkthrough

Walkthrough

This PR introduces centralized validation for Query Plans by implementing a new IQueryPlanValidator interface with comprehensive validation logic. The validator is integrated into QueryPlanManager, replacing inline validation stubs. The model is enhanced with IValidatableObject implementation and custom validation attributes. Default values are initialized for paging properties, and dependency injection configuration is updated to register the validator.

Changes

Cohort / File(s) Summary
Validation Core
DotNet/DataAcquisition.Domain/Application/Validators/QueryPlanValidator.cs
New validator implementation with IQueryPlanValidator interface, ValidationResult data structure, and QueryPlanValidator class containing extensive logic for validating query dictionaries, parameters, resource types, query ordering, and cross-references. Includes helpers for key validation, parameter type checking, and resource reference validation.
Manager Integration
DotNet/DataAcquisition.Domain/Application/Managers/QueryPlanManager.cs
Constructor now requires IQueryPlanValidator dependency. Replaced inline validation stubs with centralized _validator.ValidateQueryPlan() calls in AddAsync and UpdateAsync. Added error/warning logging and restructured exception handling. Removed private ValidateQueryOrder method.
Model Validation
DotNet/DataAcquisition.Domain/Application/Models/Http/QueryPlanApiModel.cs
Implements IValidatableObject with comprehensive Validate(ValidationContext) method. Added validation attributes ([Required], [StringLength], custom [ValidateQueryPlanConfigDictionary]) to properties. Introduced private helper methods for dictionary key and resource type validation, ISO 8601 duration validation. Legacy Validate() method retained but marked obsolete.
Custom Validation Attribute
DotNet/DataAcquisition.Domain/Infrastructure/Attributes/ValidateQueryPlanConfigDictionaryAttribute.cs
New validation attribute for Dictionary<string, IQueryConfig> that enforces non-null dictionaries with entries, non-empty keys, validates parameter and reference query configs with contextual error reporting.
Dependency Registration
DotNet/DataAcquisition.Domain/Extensions/GeneralStartupExtensions.cs
Added scoped DI registration: IQueryPlanValidatorQueryPlanValidator.
Model Property Defaults
DotNet/DataAcquisition.Domain/Infrastructure/Models/QueryConfig/Parameter/ResourceIdsParameter.cs, DotNet/DataAcquisition.Domain/Infrastructure/Models/QueryConfig/ReferenceQueryConfig.cs
Added default value initialization for Paged property: "100" in ResourceIdsParameter, 100 in ReferenceQueryConfig.
Test Updates
DotNet/ServiceTests/IntegrationTests/DataAcquisition/Managers/QueryPlanManagerTests.cs
Updated test setup to instantiate and pass IQueryPlanValidator to QueryPlanManager constructor. Updated test helpers to set Paged property on ResourceIdsParameter.
New Unit Tests
DotNet/ServiceTests/UnitTests/DataAcquisition/QueryPlanValidatorTests.cs
New comprehensive test class with 19 [Fact] test methods covering validation scenarios: valid configurations, null inputs, empty parameters, missing/invalid resource types, FHIR enum validation, duplicate keys, query ordering, paging constraints, parameter naming, cross-references, and warning conditions.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant QueryPlanManager
    participant IQueryPlanValidator
    participant IDatabase
    participant Logger

    Client->>QueryPlanManager: AddAsync(model)
    QueryPlanManager->>QueryPlanManager: Extract dictionaries
    QueryPlanManager->>IQueryPlanValidator: ValidateQueryPlan(initial, supplemental)
    IQueryPlanValidator->>IQueryPlanValidator: Validate keys, parameters, types, ordering, references
    IQueryPlanValidator-->>QueryPlanManager: ValidationResult{IsValid, Errors, Warnings}
    
    alt Validation Failed
        QueryPlanManager->>Logger: Log errors
        QueryPlanManager-->>Client: BadRequestException
    else Validation Succeeded
        QueryPlanManager->>IDatabase: CreateAsync(plan)
        IDatabase-->>QueryPlanManager: Created plan
        QueryPlanManager->>Logger: Log success with facility/type
        QueryPlanManager-->>Client: QueryPlan
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • dvargaslantana
  • nvmLantana
  • seanmcilvenna

🐰 A validator hops into the scene,
With rules both proper and pristine,
Query plans now checked from end to end,
No invalid configs to defend!
The rabbit grins—no more surprises,
Just validated, organized prizes!

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and specifically describes the main change: adding proper validation to the Query Plan REST API to prevent invalid values from being persisted.
Description check ✅ Passed The description covers the key requirements from the template: high-level overview of changes, testing performed (e2e and use case validation), unit testing confirmation, and documentation status.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from LNK-4621: centralized validation for FHIR resource names via QueryPlanValidator LNK-4621, enum validation for OperationType LNK-4621, and support for string enum values via IValidatableObject implementation LNK-4621.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing validation requirements: new validator infrastructure, QueryPlanApiModel validation enhancements, QueryPlanManager integration, model defaults (ResourceIdsParameter.Paged), and corresponding test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch LNK-4621-Data-Acquisition-Query-Plan-REST-API-not-properly-validating-values

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@DotNet/DataAcquisition.Domain/Application/Validators/QueryPlanValidator.cs`:
- Around line 324-362: The ValidateResourceIdsParameter method never increments
the ref pagedCount, so the "only one paged parameter" rule never triggers;
update ValidateResourceIdsParameter to increment pagedCount when the parameter
has a non-empty Paged value that successfully parses to a non-negative int
(i.e., inside the existing int.TryParse branch where pagedVal >= 0) so only
valid paged parameters count toward the global limit; keep not incrementing on
parse/validation failures and reference the pagedCount ref parameter and the
ResourceIdsParameter.Paged check in your change.

In
`@DotNet/ServiceTests/IntegrationTests/DataAcquisition/Managers/QueryPlanManagerTests.cs`:
- Around line 289-293: The test UpdateAsync_NotFound is asserting
BadRequestException because the validator mock currently returns an invalid
result by default; change the test to expect NotFoundException and ensure the
validator mock returns a successful validation result for
CreateValidUpdateQueryPlanModel() before calling manager.UpdateAsync(model).
Specifically, update the test setup to configure the validation mock (the
validator used by the manager) to return a valid ValidationResult/validation
success for the model and replace Assert.ThrowsAsync<BadRequestException> with
Assert.ThrowsAsync<NotFoundException> around manager.UpdateAsync(model).
- Around line 37-38: The test is creating a mock of the concrete
QueryPlanValidator instead of the IQueryPlanValidator, causing ValidateQueryPlan
to return default/invalid results; replace new Mock<QueryPlanValidator>().Object
with a mock of the interface (new Mock<IQueryPlanValidator>().Object) or
instantiate the real QueryPlanValidator for integration tests, and if mocking
configure the mock to return a successful ValidationResult for ValidateQueryPlan
before passing it into QueryPlanManager(description: QueryPlanManager,
validator: IQueryPlanValidator).

In `@DotNet/ServiceTests/UnitTests/DataAcquisition/QueryPlanValidatorTests.cs`:
- Around line 304-340: The test
ValidateQueryPlan_MultipleResourceIdsParametersInSameQuery_ReturnsError is
misnamed/constructed: it currently triggers cross-reference validation (unknown
Resource 'Encounter') and uses an empty supplementalQueries which can cause a
"SupplementalQueries cannot be null or empty" failure. Either rename the test to
reflect it verifies cross-reference errors (update method name and assert
message) or modify the Arrange section to actually exercise the "only one paged
parameter" rule by making both ResourceIdsParameter.Resource values valid (e.g.,
both "Patient"), and populate supplementalQueries with a valid IQueryConfig (so
cross-reference validation does not short-circuit); keep the Assert checking
result.IsValid == false and that result.Errors contains "Only one paged
parameter is allowed". Ensure you update the test method name or assertions
accordingly and reference ValidateQueryPlan, initialQueries,
supplementalQueries, and the test method name when making changes.
♻️ Duplicate comments (1)
DotNet/DataAcquisition.Domain/Application/Managers/QueryPlanManager.cs (1)

43-61: Validation flow looks good; consider sanitizing FacilityId for log safety.

The validation logic correctly fails fast and provides clear error messages. Static analysis flagged potential log injection at lines 49-50 and 59-60 where model.FacilityId (user input) is logged.

While structured logging with template parameters provides some protection against injection, per coding guidelines, consider sanitizing FacilityId using HtmlInputSanitizer before logging if it's not already sanitized upstream.

🧹 Nitpick comments (8)
DotNet/DataAcquisition.Domain/Infrastructure/Attributes/ValidateQueryPlanConfigDictionaryAttribute.cs (1)

80-91: Consider validating ResourceType against the FHIR resource enum.

The ValidateParameterQueryConfig method checks that ResourceType is not null/whitespace, but unlike the central QueryPlanValidator, it doesn't validate that the ResourceType value is a valid FHIR resource type. This could allow invalid resource names to pass attribute validation.

Since the PR objective is to "prevent creation of Query Plan records containing incorrectly named FHIR resources," consider adding enum validation here for consistency, or document that this attribute performs structural validation only while semantic validation occurs in QueryPlanValidator.

DotNet/DataAcquisition.Domain/Application/Managers/QueryPlanManager.cs (1)

159-165: Consider using a filtered query instead of fetching all records.

GetAllAsync() followed by Where() fetches all query plans from the database before filtering in memory. For better performance, consider using a filtered query:

♻️ Suggested optimization
-    var allPlans = await _database.QueryPlanRepository.GetAllAsync(cancellationToken);
-    var facilityPlans = allPlans.Where(q => q.FacilityId == facilityId).ToList();
+    var facilityPlans = await _database.QueryPlanRepository
+        .FindAsync(q => q.FacilityId == facilityId, cancellationToken);

This assumes a FindAsync or similar filtered query method exists on the repository. If not, this could be deferred to a future optimization.

DotNet/DataAcquisition.Domain/Application/Validators/QueryPlanValidator.cs (3)

414-419: Consider short-circuiting FHIR validation when ResourceType is empty.

The FHIR ResourceType enum validation at line 415 will execute even when config.ResourceType is null or whitespace, which was already flagged as an error above. This could result in redundant error messages (one for "required" and another for "not a valid FHIR ResourceType").

♻️ Suggested improvement
-        //validate resource string by using the Firely Resource enum
-        if (!Enum.TryParse(typeof(ResourceType), config.ResourceType, out _))
+        //validate resource string by using the Firely Resource enum
+        else if (!Enum.TryParse(typeof(ResourceType), config.ResourceType, out _))
         {
             result.IsValid = false;
             result.Errors.Add($"{prefix}: ResourceType value '{config.ResourceType}' is not a valid FHIR ResourceType.");
         }

204-209: Same short-circuit opportunity for FHIR validation.

Similar to ValidateReferenceQueryConfig, the FHIR validation will run even when ResourceType is empty, potentially producing redundant errors.

♻️ Suggested improvement
             if (config.ResourceType.Length > MaxResourceTypeLength)
             {
                 result.IsValid = false;
                 result.Errors.Add($"{prefix}: ResourceType length exceeds maximum of {MaxResourceTypeLength} characters.");
             }
 
-            //validate resource string by using the Firely Resource enum
-            if (!Enum.TryParse(typeof(ResourceType), config.ResourceType, out _))
-            {
-                result.IsValid = false;
-                result.Errors.Add($"{prefix}: Resource value '{config.ResourceType}' is not a valid FHIR ResourceType.");
-            }
+            //validate resource string by using the Firely Resource enum
+            else if (!Enum.TryParse(typeof(ResourceType), config.ResourceType, out _))
+            {
+                result.IsValid = false;
+                result.Errors.Add($"{prefix}: Resource value '{config.ResourceType}' is not a valid FHIR ResourceType.");
+            }

341-346: FHIR validation runs even when Resource is empty, causing redundant errors.

When parameter.Resource is empty, the check at line 335 will add an error, but then the FHIR enum validation at line 342 will also fail and add another error for the same root cause.

♻️ Suggested improvement
         // Validate Resource
         if (string.IsNullOrWhiteSpace(parameter.Resource))
         {
             result.IsValid = false;
             result.Errors.Add($"{prefix}: Resource is required for ResourceIdsParameter.");
         }
-
-        //validate resource string by using the Firely Resource enum
-        if (!Enum.TryParse(typeof(ResourceType), parameter.Resource, out _))
+        else if (!Enum.TryParse(typeof(ResourceType), parameter.Resource, out _))
         {
             result.IsValid = false;
             result.Errors.Add($"{prefix}: Resource value '{parameter.Resource}' is not a valid FHIR ResourceType.");
         }
DotNet/DataAcquisition.Domain/Application/Models/Http/QueryPlanApiModel.cs (3)

64-66: Consider validating unique resource types for SupplementalQueries as well.

ValidateUniqueResourceTypes is only called for InitialQueries but not for SupplementalQueries. If duplicate resource types are a concern, this validation should be consistent across both dictionaries.

♻️ Suggested change
         // Validate that there are no duplicate ResourceTypes within InitialQueries
         ValidateUniqueResourceTypes(InitialQueries, nameof(InitialQueries), results);
+        ValidateUniqueResourceTypes(SupplementalQueries, nameof(SupplementalQueries), results);

176-189: Consider catching specific exception type for XmlConvert.ToTimeSpan.

The empty catch block swallows all exceptions. While this works for validation purposes, catching the specific FormatException would be more precise and avoid masking unexpected errors.

♻️ Suggested improvement
         try
         {
             // Try to parse as TimeSpan using XmlConvert (built-in ISO 8601 parser)
             System.Xml.XmlConvert.ToTimeSpan(lookBack);
             return true;
         }
-        catch
+        catch (FormatException)
         {
             // If XmlConvert fails, do a more lenient regex check
             // This allows for valid ISO 8601 durations that might not parse to TimeSpan
             var iso8601Pattern = @"^P(?:\d+Y)?(?:\d+M)?(?:\d+W)?(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$";
             return System.Text.RegularExpressions.Regex.IsMatch(lookBack, iso8601Pattern,
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
         }

186-188: Regex pattern allows invalid duration strings like "PT" (no time components).

The regex pattern makes all components optional, which means strings like "PT" (time designator with no actual time values) would pass validation. Consider adding a check to ensure at least one component is present after P or PT.

♻️ Suggested improvement
             // If XmlConvert fails, do a more lenient regex check
             // This allows for valid ISO 8601 durations that might not parse to TimeSpan
-            var iso8601Pattern = @"^P(?:\d+Y)?(?:\d+M)?(?:\d+W)?(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$";
-            return System.Text.RegularExpressions.Regex.IsMatch(lookBack, iso8601Pattern,
+            // Ensure at least one date component, or T followed by at least one time component
+            var iso8601Pattern = @"^P(?:(?:\d+Y)|(?:\d+M)|(?:\d+W)|(?:\d+D)|(?:T(?:(?:\d+H)|(?:\d+M)|(?:\d+(?:\.\d+)?S))+))+$";
+            return System.Text.RegularExpressions.Regex.IsMatch(lookBack, iso8601Pattern,
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);

@edward-miller-lcg
edward-miller-lcg merged commit b6fb225 into dev Feb 3, 2026
16 checks passed
@edward-miller-lcg
edward-miller-lcg deleted the LNK-4621-Data-Acquisition-Query-Plan-REST-API-not-properly-validating-values branch February 3, 2026 16:50
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.

3 participants