Improve error message for export operations in batch/transaction bundles#5475
Improve error message for export operations in batch/transaction bundles#5475tranhoangtu-it wants to merge 2 commits intomicrosoft:mainfrom
Conversation
…t#1459) When a $export operation is included in a batch or transaction bundle, the server previously returned a cryptic "Accept header is not supported" error because the internal routing couldn't set the required export-specific headers. Now detects $export URLs early in bundle entry processing and returns a clear error: "Export operations ($export) are not supported within batch or transaction bundles."
|
Required metadata labels:
|
There was a problem hiding this comment.
Pull request overview
Improves the server’s response when a $export operation is included inside a batch/transaction Bundle, replacing a downstream “unsupported Accept header” failure with an explicit, user-friendly validation error before routing.
Changes:
- Add an early guard in bundle request generation to detect
$exportURLs and fail fast with a clearer exception. - Introduce a new localized resource string for the error message.
- Regenerate the resources designer accessor for the new string.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/Microsoft.Health.Fhir.Shared.Api/Features/Resources/Bundle/BundleHandler.cs |
Adds early detection of $export in bundle entry request URLs and throws a validation exception with a clearer message. |
src/Microsoft.Health.Fhir.Core/Resources.resx |
Adds ExportOperationNotSupportedInBundle string used by the new validation path. |
src/Microsoft.Health.Fhir.Core/Resources.Designer.cs |
Adds the strongly-typed accessor for the new resource string. |
Files not reviewed (1)
- src/Microsoft.Health.Fhir.Core/Resources.Designer.cs: Language not supported
| if (requestUrl != null && requestUrl.Contains("$export", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| throw new RequestNotValidException(Core.Resources.ExportOperationNotSupportedInBundle); |
There was a problem hiding this comment.
The $export detection uses requestUrl.Contains("$export", ...), which will also match query-string values (e.g., Patient?name=$export) and can falsely reject non-export requests. Consider parsing requestUrl as a Uri and checking only the path segments for a segment equal to $export (after unescaping), rather than searching the full URL string.
| if (requestUrl != null && requestUrl.Contains("$export", StringComparison.OrdinalIgnoreCase)) | |
| { | |
| throw new RequestNotValidException(Core.Resources.ExportOperationNotSupportedInBundle); | |
| if (!string.IsNullOrEmpty(requestUrl)) | |
| { | |
| Uri exportDetectionUri = new Uri(LocalHost, requestUrl); | |
| string exportDetectionPath = exportDetectionUri.GetComponents(UriComponents.Path, UriFormat.Unescaped); | |
| bool hasExportPathSegment = exportDetectionPath | |
| .Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries) | |
| .Any(segment => segment.Equals("$export", StringComparison.OrdinalIgnoreCase)); | |
| if (hasExportPathSegment) | |
| { | |
| throw new RequestNotValidException(Core.Resources.ExportOperationNotSupportedInBundle); | |
| } |
| if (requestUrl != null && requestUrl.Contains("$export", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| throw new RequestNotValidException(Core.Resources.ExportOperationNotSupportedInBundle); | ||
| } |
There was a problem hiding this comment.
This throws RequestNotValidException with the default issue type (invalid). Since the message explicitly says the operation is not supported (and the linked issue asks for Not Supported), pass OperationOutcomeConstants.IssueType.NotSupported (or use an exception type that sets it) so OperationOutcome.issue.code matches the semantics.
| // Export operations are not supported within bundles (batch or transaction). | ||
| // Return a clear error instead of the cryptic Accept header error that occurs | ||
| // when the internal routing fails to set required export headers. | ||
| if (requestUrl != null && requestUrl.Contains("$export", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| throw new RequestNotValidException(Core.Resources.ExportOperationNotSupportedInBundle); | ||
| } |
There was a problem hiding this comment.
There are existing xUnit tests for bundle handling under src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/Resources/Bundle/, but this new $export-in-bundle guard is not covered. Add a unit test that submits a batch/transaction bundle containing an entry with a $export URL and asserts the thrown exception/OperationOutcome contains the new resource message (and the intended issue type).
There was a problem hiding this comment.
@tranhoangtu-it could you add a unit test for this in BundleHandlerTests.cs?
… type - Split URL on '?' to check path only, avoiding false positive on query strings like Patient?name=$export - Use OperationOutcomeConstants.IssueType.NotSupported per issue requirement and FHIR spec for unsupported operations
|
Addressed Copilot review:
|
| string path = requestUrl.Split('?')[0]; | ||
| if (path.Contains("$export", StringComparison.OrdinalIgnoreCase)) |
There was a problem hiding this comment.
Local variable path declared here conflicts with the existing string path = ... later in GenerateRequest (C# disallows redeclaring a local from an enclosing scope), which will cause a build error (CS0136). Rename this variable (e.g., requestPath) or reuse the later URI-parsed path for the $export check to avoid duplicate parsing.
| string path = requestUrl.Split('?')[0]; | |
| if (path.Contains("$export", StringComparison.OrdinalIgnoreCase)) | |
| string requestPath = requestUrl.Split('?')[0]; | |
| if (requestPath.Contains("$export", StringComparison.OrdinalIgnoreCase)) |
| throw new RequestNotValidException( | ||
| Core.Resources.ExportOperationNotSupportedInBundle, | ||
| OperationOutcomeConstants.IssueType.NotSupported); | ||
| } |
There was a problem hiding this comment.
PR description states this should return OperationOutcome.issue.code = "invalid", but the code throws RequestNotValidException with IssueType.NotSupported (which maps to not-supported). Either update the PR description/test plan to reflect not-supported, or change the issue type here to Invalid if that’s the intended behavior.
| // Export operations are not supported within bundles (batch or transaction). | ||
| // Return a clear error instead of the cryptic Accept header error that occurs | ||
| // when the internal routing fails to set required export headers. | ||
| // Check the path component only to avoid false positives from query string values. | ||
| if (requestUrl != null) | ||
| { | ||
| string path = requestUrl.Split('?')[0]; | ||
| if (path.Contains("$export", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| throw new RequestNotValidException( | ||
| Core.Resources.ExportOperationNotSupportedInBundle, | ||
| OperationOutcomeConstants.IssueType.NotSupported); | ||
| } | ||
| } |
There was a problem hiding this comment.
There is an existing unit test (BundleHandlerTests.GivenABundleWithAnExportPost_WhenProcessed_ThenItIsProcessedCorrectly) that currently expects /$export inside a batch bundle to succeed. With this new guard, that test will fail and should be updated (and replaced with assertions that a bundle containing $export returns the new ExportOperationNotSupportedInBundle message and not-supported issue type).
Summary
Fixes #1459.
When users include a
$exportoperation in a batch or transaction bundle, the server previously returned a cryptic error: "Value supplied for the 'Accept' header is not supported." This occurs because the internal bundle routing cannot set the required Accept and Prefer headers that export operations depend on.Changes
BundleHandler.cs: Added early detection of$exportURLs in bundle entries. Returns a clearRequestNotValidExceptionwith an informative error message before the request enters the routing pipeline.Resources.resx/Resources.Designer.cs: Added new resource stringExportOperationNotSupportedInBundle.New error message
This returns HTTP 400 with
OperationOutcome.issue.code = "invalid", which aligns with the FHIR specification for invalid request parameters.Test plan
$exportentry → receives clear error message$export→ processes normally (no regression)$exportrequests → continue to work as beforeThird contribution as a certified HL7 FHIR Implementer. Also building FHIRBridge — an open-source FHIR integration toolkit.