Skip to content

Commit ad17ffb

Browse files
committed
Add support for writing the JSON file response indented
1 parent 13c7390 commit ad17ffb

14 files changed

Lines changed: 110 additions & 33 deletions

File tree

shared/Http/HttpFileResponseDefinition.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ public sealed class HttpFileResponseDefinition
44
{
55
public bool Cache { get; }
66
public ContentDispositionType DispositionType { get; }
7+
public bool IndentJson { get; }
78

8-
public HttpFileResponseDefinition(bool cache, ContentDispositionType dispositionType)
9+
public HttpFileResponseDefinition(bool cache, ContentDispositionType dispositionType, bool indentJson)
910
{
1011
Cache = cache;
1112
DispositionType = dispositionType;
13+
IndentJson = indentJson;
1214
}
1315
}
1416
}

src/Dibix.Http.Host/Runtime/HttpResponseFormatter.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using System.Text.Json;
34
using System.Threading;
45
using System.Threading.Tasks;
56
using Dibix.Http.Server;
@@ -26,7 +27,7 @@ public HttpResponseFormatter(HttpResponse response)
2627
}
2728
else
2829
{
29-
await WriteJsonResponse(result, cancellationToken).ConfigureAwait(false);
30+
await WriteJsonResponse(result, writeIndented: false, cancellationToken).ConfigureAwait(false);
3031
}
3132

3233
return null;
@@ -62,23 +63,31 @@ void AppendFileName(ResponseHeaders responseHeaders, string fileName)
6263

6364
case IJsonFileMetadata jsonFileMetadata:
6465
AppendFileName(responseHeaders, jsonFileMetadata.FileName);
65-
await WriteJsonResponse(result, cancellationToken).ConfigureAwait(false);
66+
await WriteJsonResponse(result, fileResponse.IndentJson, cancellationToken).ConfigureAwait(false);
6667
break;
6768

6869
default:
6970
throw new InvalidOperationException($"Unexpected file result type: {result.GetType()}");
7071
}
7172
}
7273

73-
private async Task WriteJsonResponse(object? result, CancellationToken cancellationToken)
74+
private async Task WriteJsonResponse(object? result, bool writeIndented, CancellationToken cancellationToken)
7475
{
7576
if (result == null)
7677
{
7778
_response.StatusCode = StatusCodes.Status204NoContent;
7879
return;
7980
}
8081

81-
await _response.WriteAsJsonAsync(result, cancellationToken: cancellationToken).ConfigureAwait(false);
82+
if (writeIndented)
83+
{
84+
JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web) { WriteIndented = true };
85+
await _response.WriteAsJsonAsync(result, jsonSerializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
86+
}
87+
else
88+
{
89+
await _response.WriteAsJsonAsync(result, cancellationToken: cancellationToken).ConfigureAwait(false);
90+
}
8291
}
8392
}
8493
}

src/Dibix.Sdk.CodeGeneration/Lookup/SqlStatementDefinitionActionTargetDefinitionResolver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ private bool TryGetStatementDefinitionByProbing(string targetName, out SqlStatem
110110
private static void CollectResponse(ActionDefinition actionDefinition, SqlStatementDefinition definition)
111111
{
112112
if (definition.FileResult != null)
113-
actionDefinition.SetFileResponse(new ActionFileResponse(HttpMediaType.Binary, cache: false, dispositionType: ContentDispositionType.Attachment), definition.FileResult.Location);
113+
actionDefinition.SetFileResponse(new ActionFileResponse(HttpMediaType.Binary, cache: false, dispositionType: ContentDispositionType.Attachment, indentJson: null), definition.FileResult.Location);
114114
else
115115
actionDefinition.DefaultResponseType = definition.ResultType;
116116
}

src/Dibix.Sdk.CodeGeneration/Model/ActionFileResponse.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ public sealed class ActionFileResponse
77
public string MediaType { get; }
88
public bool Cache { get; }
99
public ContentDispositionType DispositionType { get; }
10+
public SourceLocation? IndentJson { get; }
1011

11-
public ActionFileResponse(string mediaType, bool cache, ContentDispositionType dispositionType)
12+
public ActionFileResponse(string mediaType, bool cache, ContentDispositionType dispositionType, SourceLocation? indentJson)
1213
{
1314
MediaType = mediaType;
1415
Cache = cache;
1516
DispositionType = dispositionType;
17+
IndentJson = indentJson;
1618
}
1719
}
1820
}

src/Dibix.Sdk.CodeGeneration/Output/ApiDescriptionWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ private void WriteActionConfiguration(CodeGenerationContext context, StringWrite
196196
}
197197

198198
if (action.FileResponse != null)
199-
writer.WriteLine($"{variableName}.FileResponse = new HttpFileResponseDefinition(cache: {ComputeConstantLiteral(context, action.FileResponse.Cache)}, dispositionType: {ComputeConstantLiteral(context, action.FileResponse.DispositionType)});");
199+
writer.WriteLine($"{variableName}.FileResponse = new HttpFileResponseDefinition(cache: {ComputeConstantLiteral(context, action.FileResponse.Cache)}, dispositionType: {ComputeConstantLiteral(context, action.FileResponse.DispositionType)}, indentJson: false);");
200200

201201
foreach (int disabledAutoDetectionStatusCode in action.DisabledAutoDetectionStatusCodes)
202202
{

src/Dibix.Sdk.CodeGeneration/Output/PackageMetadataUnit.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@ private static IEnumerable<HttpActionDefinitionMetadata> CollectActions(CodeGene
4949
{
5050
foreach (ActionDefinition actionDefinition in controllerDefinition.Actions)
5151
{
52+
ActionFileResponse fileResponse = actionDefinition.FileResponse;
5253
HttpActionDefinitionMetadata actionMetadata = new HttpActionDefinitionMetadata
5354
(
5455
actionName: actionDefinition.OperationId,
5556
relativeNamespace: actionDefinition.Target.RelativeNamespace,
5657
uri: new Uri(RouteBuilder.BuildRoute(model.AreaName, controllerDefinition.Name, actionDefinition.ChildRoute), UriKind.Relative),
5758
method: actionDefinition.Method,
5859
childRoute: actionDefinition.ChildRoute?.Value,
59-
fileResponse: actionDefinition.FileResponse != null ? new HttpFileResponseDefinition(actionDefinition.FileResponse.Cache, actionDefinition.FileResponse.DispositionType) : null,
60+
fileResponse: fileResponse != null ? new HttpFileResponseDefinition(fileResponse.Cache, fileResponse.DispositionType, indentJson: fileResponse.IndentJson != null) : null,
6061
description: actionDefinition.Description,
6162
modelContextProtocolType: actionDefinition.ModelContextProtocolType,
6263
securitySchemes: actionDefinition.SecuritySchemes.Requirements.Select(x => x.Scheme.SchemeName).ToArray(),

src/Dibix.Sdk.CodeGeneration/Registration/ControllerDefinitionProvider.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,13 +368,15 @@ private static bool TryReadFileResponse(JObject action, out ActionFileResponse f
368368

369369
JProperty mediaTypeProperty = fileResponseValue.Property("mediaType");
370370
JProperty cacheProperty = fileResponseValue.Property("cache");
371+
JProperty indentJsonProperty = fileResponseValue.Property("indentJson");
371372

372373
string mediaType = (string)mediaTypeProperty?.Value ?? HttpMediaType.Binary;
373374
bool cache = (bool?)cacheProperty?.Value ?? true;
375+
SourceLocation? indentJson = (bool?)indentJsonProperty?.Value == true ? indentJsonProperty.GetSourceInfo() : null;
374376
if (!Enum.TryParse((string)fileResponseValue.Property("dispositionType")?.Value, true, out ContentDispositionType dispositionType))
375377
dispositionType = ContentDispositionType.Inline;
376378

377-
fileResponse = new ActionFileResponse(mediaType, cache, dispositionType);
379+
fileResponse = new ActionFileResponse(mediaType, cache, dispositionType, indentJson);
378380
location = fileResponseProperty.GetSourceInfo();
379381

380382
return true;

src/Dibix.Sdk.CodeGeneration/Schema/dibix.endpoints.schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,9 @@
373373
"cache": {
374374
"type": "boolean"
375375
},
376+
"indentJson": {
377+
"type": "boolean"
378+
},
376379
"dispositionType": {
377380
"type": "string",
378381
"enum": [

src/Dibix.Sdk.CodeGeneration/Validation/EndpointModelValidator.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ public bool Validate(CodeGenerationModel model)
2929
.ToArray();
3030

3131
// Use non-short-circuit operator to collect all compiler errors
32-
bool isValid = ValidateActions(actions) & ValidateEquivalentPaths(actions) & ValidateDuplicateMethods(actions) && ValidateAmbiguousActionNames(actions);
32+
bool isValid = ValidateActions(actions)
33+
& ValidateEquivalentPaths(actions)
34+
& ValidateDuplicateMethods(actions)
35+
& ValidateAmbiguousActionNames(actions);
3336
return isValid;
3437
}
3538

@@ -128,7 +131,12 @@ private bool ValidateAmbiguousActionNames(IEnumerable<ActionRegistration> action
128131

129132
private bool ValidateAction(ActionDefinition action)
130133
{
131-
bool isValid = ValidateReservedPathSegments(action) && ValidateParameters(action) && ValidateBodyAllowedForMethod(action) && ValidateMcpDescription(action);
134+
// Use non-short-circuit operator to collect all compiler errors
135+
bool isValid = ValidateReservedPathSegments(action)
136+
& ValidateParameters(action)
137+
& ValidateBodyAllowedForMethod(action)
138+
& ValidateMcpDescription(action)
139+
& ValidateIndentJson(action);
132140
return isValid;
133141
}
134142

@@ -207,6 +215,28 @@ private bool ValidateMcpDescription(ActionDefinition actionDefinition)
207215
return false;
208216
}
209217

218+
private bool ValidateIndentJson(ActionDefinition actionDefinition)
219+
{
220+
SourceLocation? indentJsonLocation = actionDefinition.FileResponse?.IndentJson;
221+
if (indentJsonLocation == null)
222+
return true;
223+
224+
bool result = true;
225+
if (actionDefinition.Target is ReflectionActionTarget)
226+
{
227+
_logger.LogError("The 'indentJson' property is not supported for actions targeting methods in external assemblies", indentJsonLocation.Value);
228+
result = false;
229+
}
230+
231+
if (actionDefinition.FileResponse.MediaType != HttpMediaType.Json)
232+
{
233+
_logger.LogError("The 'indentJson' property is only supported for media type 'application/json'", indentJsonLocation.Value);
234+
result = false;
235+
}
236+
237+
return result;
238+
}
239+
210240
// When using the BODY.$RAW property source, the raw body will be passed to as a stream to an SqlParameter.
211241
// The stream will be only accessed asynchronously if one of the async ADO.NET methods is used.
212242
// Otherwise, it will cause this exception in the ASP.NET core host:

tests/Dibix.Sdk.Tests.Database/Endpoints/GenericEndpoint.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"fileResponse": {
9696
"mediaType": "image/*",
9797
"cache": false,
98+
"indentJson": false,
9899
"dispositionType": "attachment"
99100
},
100101
"authorization": "none"

0 commit comments

Comments
 (0)