-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ivarne/warning fixes #328
Ivarne/warning fixes #328
Changes from 7 commits
c092b50
903e8d6
01d7f40
d130cab
de0ef73
1c73f06
a3dc24a
b7bb5c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
#nullable enable | ||
|
||
using System.Net; | ||
using System.Security.Claims; | ||
using Altinn.App.Api.Helpers.RequestHandling; | ||
|
@@ -113,18 +115,13 @@ public async Task<ActionResult> Create( | |
[FromRoute] Guid instanceGuid, | ||
[FromQuery] string dataType) | ||
{ | ||
if (string.IsNullOrWhiteSpace(dataType)) | ||
{ | ||
return BadRequest("Element type must be provided."); | ||
} | ||
|
||
/* The Body of the request is read much later when it has been made sure it is worth it. */ | ||
|
||
try | ||
{ | ||
Application application = await _appMetadata.GetApplicationMetadata(); | ||
DataType dataTypeFromMetadata = application.DataTypes.FirstOrDefault(e => e.Id.Equals(dataType, StringComparison.InvariantCultureIgnoreCase)); | ||
|
||
DataType? dataTypeFromMetadata = application.DataTypes.FirstOrDefault(e => e.Id.Equals(dataType, StringComparison.InvariantCultureIgnoreCase)); | ||
|
||
if (dataTypeFromMetadata == null) | ||
{ | ||
|
@@ -158,7 +155,7 @@ public async Task<ActionResult> Create( | |
(bool validationRestrictionSuccess, List<ValidationIssue> errors) = DataRestrictionValidation.CompliesWithDataRestrictions(Request, dataTypeFromMetadata); | ||
if (!validationRestrictionSuccess) | ||
{ | ||
return new BadRequestObjectResult(await GetErrorDetails(errors)); | ||
return BadRequest(await GetErrorDetails(errors)); | ||
} | ||
|
||
StreamContent streamContent = Request.CreateContentStream(); | ||
|
@@ -175,11 +172,11 @@ public async Task<ActionResult> Create( | |
Description = errorMessage | ||
}; | ||
_logger.LogError(errorMessage); | ||
return new BadRequestObjectResult(await GetErrorDetails(new List<ValidationIssue> { error })); | ||
return BadRequest(await GetErrorDetails(new List<ValidationIssue> { error })); | ||
} | ||
|
||
bool parseSuccess = Request.Headers.TryGetValue("Content-Disposition", out StringValues headerValues); | ||
string filename = parseSuccess ? DataRestrictionValidation.GetFileNameFromHeader(headerValues) : string.Empty; | ||
string? filename = parseSuccess ? DataRestrictionValidation.GetFileNameFromHeader(headerValues) : null; | ||
|
||
IEnumerable<FileAnalysisResult> fileAnalysisResults = new List<FileAnalysisResult>(); | ||
if (FileAnalysisEnabledForDataType(dataTypeFromMetadata)) | ||
|
@@ -196,7 +193,7 @@ public async Task<ActionResult> Create( | |
|
||
if (!fileValidationSuccess) | ||
{ | ||
return new BadRequestObjectResult(await GetErrorDetails(validationIssues)); | ||
return BadRequest(await GetErrorDetails(validationIssues)); | ||
} | ||
|
||
fileStream.Seek(0, SeekOrigin.Begin); | ||
|
@@ -258,7 +255,7 @@ public async Task<ActionResult> Get( | |
return NotFound($"Did not find instance {instance}"); | ||
} | ||
|
||
DataElement dataElement = instance.Data.FirstOrDefault(m => m.Id.Equals(dataGuid.ToString())); | ||
DataElement? dataElement = instance.Data.FirstOrDefault(m => m.Id.Equals(dataGuid.ToString())); | ||
|
||
if (dataElement == null) | ||
{ | ||
|
@@ -319,7 +316,7 @@ public async Task<ActionResult> Put( | |
return Conflict($"Cannot update data element of archived or deleted instance {instanceOwnerPartyId}/{instanceGuid}"); | ||
} | ||
|
||
DataElement dataElement = instance.Data.FirstOrDefault(m => m.Id.Equals(dataGuid.ToString())); | ||
DataElement? dataElement = instance.Data.FirstOrDefault(m => m.Id.Equals(dataGuid.ToString())); | ||
|
||
if (dataElement == null) | ||
{ | ||
|
@@ -340,11 +337,11 @@ public async Task<ActionResult> Put( | |
return await PutFormData(org, app, instance, dataGuid, dataType); | ||
} | ||
|
||
DataType dataTypeFromMetadata = (await _appMetadata.GetApplicationMetadata()).DataTypes.FirstOrDefault(e => e.Id.Equals(dataType, StringComparison.InvariantCultureIgnoreCase)); | ||
DataType? dataTypeFromMetadata = (await _appMetadata.GetApplicationMetadata()).DataTypes.FirstOrDefault(e => e.Id.Equals(dataType, StringComparison.InvariantCultureIgnoreCase)); | ||
(bool validationRestrictionSuccess, List<ValidationIssue> errors) = DataRestrictionValidation.CompliesWithDataRestrictions(Request, dataTypeFromMetadata); | ||
if (!validationRestrictionSuccess) | ||
{ | ||
return new BadRequestObjectResult(await GetErrorDetails(errors)); | ||
return BadRequest(await GetErrorDetails(errors)); | ||
} | ||
|
||
return await PutBinaryData(instanceOwnerPartyId, instanceGuid, dataGuid); | ||
|
@@ -386,7 +383,7 @@ public async Task<ActionResult> Delete( | |
return Conflict($"Cannot delete data element of archived or deleted instance {instanceOwnerPartyId}/{instanceGuid}"); | ||
} | ||
|
||
DataElement dataElement = instance.Data.Find(m => m.Id.Equals(dataGuid.ToString())); | ||
DataElement? dataElement = instance.Data.Find(m => m.Id.Equals(dataGuid.ToString())); | ||
|
||
if (dataElement == null) | ||
{ | ||
|
@@ -421,21 +418,19 @@ private ActionResult ExceptionResponse(Exception exception, string message) | |
{ | ||
_logger.LogError(exception, message); | ||
|
||
if (exception is PlatformHttpException) | ||
if (exception is PlatformHttpException phe) | ||
{ | ||
PlatformHttpException phe = exception as PlatformHttpException; | ||
return StatusCode((int)phe.Response.StatusCode, phe.Message); | ||
} | ||
else if (exception is ServiceException) | ||
else if (exception is ServiceException se) | ||
{ | ||
ServiceException se = exception as ServiceException; | ||
return StatusCode((int)se.StatusCode, se.Message); | ||
} | ||
|
||
return StatusCode(500, $"{message}"); | ||
} | ||
|
||
private async Task<ActionResult> CreateBinaryData(Instance instanceBefore, string dataType, string contentType, string filename, Stream fileStream) | ||
private async Task<ActionResult> CreateBinaryData(Instance instanceBefore, string dataType, string contentType, string? filename, Stream fileStream) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checked the call chain for this method in regards to filename beeing null. Seems to me that DataRestrictionValidation.CompliesWithDataRestrictions that is called from the Post Data method should ensure that the filename is not null, but it's not quite obvious so the compiler might not catch up on it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, I only traced it to being read from a header I think is optional, so I figured it might be null. We should probably store the file with a filename anyway, so a better solution to the nullability problem might be to throw or use |
||
{ | ||
int instanceOwnerPartyId = int.Parse(instanceBefore.Id.Split("/")[0]); | ||
Guid instanceGuid = Guid.Parse(instanceBefore.Id.Split("/")[1]); | ||
|
@@ -459,7 +454,7 @@ private async Task<ActionResult> CreateAppModelData( | |
{ | ||
Guid instanceGuid = Guid.Parse(instance.Id.Split("/")[1]); | ||
|
||
object appModel; | ||
object? appModel; | ||
|
||
string classRef = _appResourcesService.GetClassRefForLogicDataType(dataType); | ||
|
||
|
@@ -472,7 +467,7 @@ private async Task<ActionResult> CreateAppModelData( | |
ModelDeserializer deserializer = new ModelDeserializer(_logger, _appModel.GetModelType(classRef)); | ||
appModel = await deserializer.DeserializeAsync(Request.Body, Request.ContentType); | ||
|
||
if (!string.IsNullOrEmpty(deserializer.Error)) | ||
if (!string.IsNullOrEmpty(deserializer.Error) || appModel is null) | ||
{ | ||
return BadRequest(deserializer.Error); | ||
} | ||
|
@@ -510,7 +505,7 @@ private async Task<ActionResult> GetBinaryData( | |
|
||
if (dataStream != null) | ||
{ | ||
string userOrgClaim = User.GetOrg(); | ||
string? userOrgClaim = User.GetOrg(); | ||
if (userOrgClaim == null || !org.Equals(userOrgClaim, StringComparison.InvariantCultureIgnoreCase)) | ||
{ | ||
await _instanceClient.UpdateReadStatus(instanceOwnerPartyId, instanceGuid, "read"); | ||
|
@@ -588,7 +583,7 @@ private async Task<ActionResult> GetFormData( | |
|
||
await _dataProcessor.ProcessDataRead(instance, dataGuid, appModel); | ||
|
||
string userOrgClaim = User.GetOrg(); | ||
string? userOrgClaim = User.GetOrg(); | ||
if (userOrgClaim == null || !org.Equals(userOrgClaim, StringComparison.InvariantCultureIgnoreCase)) | ||
{ | ||
await _instanceClient.UpdateReadStatus(instanceOwnerId, instanceGuid, "read"); | ||
|
@@ -602,7 +597,7 @@ private async Task<ActionResult> PutBinaryData(int instanceOwnerPartyId, Guid in | |
if (Request.Headers.TryGetValue("Content-Disposition", out StringValues headerValues)) | ||
{ | ||
var contentDispositionHeader = ContentDispositionHeaderValue.Parse(headerValues.ToString()); | ||
_logger.LogInformation("Content-Disposition: {ContentDisposition}", headerValues); | ||
_logger.LogInformation("Content-Disposition: {ContentDisposition}", headerValues.ToString()); | ||
DataElement dataElement = await _dataClient.UpdateBinaryData(new InstanceIdentifier(instanceOwnerPartyId, instanceGuid), Request.ContentType, contentDispositionHeader.FileName.ToString(), dataGuid, Request.Body); | ||
SelfLinkHelper.SetDataAppSelfLinks(instanceOwnerPartyId, instanceGuid, dataElement, Request); | ||
|
||
|
@@ -620,7 +615,7 @@ private async Task<ActionResult> PutFormData(string org, string app, Instance in | |
Guid instanceGuid = Guid.Parse(instance.Id.Split("/")[1]); | ||
|
||
ModelDeserializer deserializer = new ModelDeserializer(_logger, _appModel.GetModelType(classRef)); | ||
object serviceModel = await deserializer.DeserializeAsync(Request.Body, Request.ContentType); | ||
object? serviceModel = await deserializer.DeserializeAsync(Request.Body, Request.ContentType); | ||
|
||
if (!string.IsNullOrEmpty(deserializer.Error)) | ||
{ | ||
|
@@ -632,7 +627,7 @@ private async Task<ActionResult> PutFormData(string org, string app, Instance in | |
return BadRequest("No data found in content"); | ||
} | ||
|
||
Dictionary<string, object> changedFields = await JsonHelper.ProcessDataWriteWithDiff(instance, dataGuid, serviceModel, _dataProcessor, _logger); | ||
Dictionary<string, object?>? changedFields = await JsonHelper.ProcessDataWriteWithDiff(instance, dataGuid, serviceModel, _dataProcessor, _logger); | ||
|
||
await UpdatePresentationTextsOnInstance(instance, dataType, serviceModel); | ||
await UpdateDataValuesOnInstance(instance, dataType, serviceModel); | ||
|
@@ -652,8 +647,10 @@ private async Task<ActionResult> PutFormData(string org, string app, Instance in | |
string dataUrl = updatedDataElement.SelfLinks.Apps; | ||
if (changedFields is not null) | ||
{ | ||
CalculationResult calculationResult = new CalculationResult(updatedDataElement); | ||
calculationResult.ChangedFields = changedFields; | ||
CalculationResult calculationResult = new(updatedDataElement) | ||
{ | ||
ChangedFields = changedFields | ||
}; | ||
return StatusCode((int)HttpStatusCode.SeeOther, calculationResult); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
|
@@ -38,10 +40,10 @@ public OptionsController(IAppOptionsService appOptionsService) | |
[HttpGet("{optionsId}")] | ||
public async Task<IActionResult> Get( | ||
[FromRoute] string optionsId, | ||
[FromQuery] string language, | ||
[FromQuery] string? language, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we set "nb" as default param value instead. Makes the fact that nb is the default more obvious both in the code and swagger There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was my first attempt, but in C# you can't put a default value in the middle of a parameter list. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, didn't think of that during my review. Don't but to much effort into fixing my comment! |
||
[FromQuery] Dictionary<string, string> queryParams) | ||
{ | ||
AppOptions appOptions = await _appOptionsService.GetOptionsAsync(optionsId, language, queryParams); | ||
AppOptions appOptions = await _appOptionsService.GetOptionsAsync(optionsId, language ?? "nb", queryParams); | ||
if (appOptions.Options == null) | ||
{ | ||
return NotFound(); | ||
|
@@ -74,12 +76,12 @@ public async Task<IActionResult> Get( | |
[FromRoute] int instanceOwnerPartyId, | ||
[FromRoute] Guid instanceGuid, | ||
[FromRoute] string optionsId, | ||
[FromQuery] string language, | ||
[FromQuery] string? language, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we set "nb" as default param value instead. Makes the fact that nb is the default more obvious both in the code and swagger |
||
[FromQuery] Dictionary<string, string> queryParams) | ||
{ | ||
var instanceIdentifier = new InstanceIdentifier(instanceOwnerPartyId, instanceGuid); | ||
|
||
AppOptions appOptions = await _appOptionsService.GetOptionsAsync(instanceIdentifier, optionsId, language, queryParams); | ||
AppOptions appOptions = await _appOptionsService.GetOptionsAsync(instanceIdentifier, optionsId, language ?? "nb", queryParams); | ||
|
||
// Only return NotFound if we can't find an options provider. | ||
// If we find the options provider, but it doesnt' have values, return empty list. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will change the error response to the user as not providing dataType param will lead to BadReuqest from line 128
Element type {dataType} not allowed for instance {instanceOwnerPartyId}/{instanceGuid}.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the addition
#nullable enable
on line 1 will trigger asp net to returnBadRequest
with aProblem
json structure that explains thatdataType
is a required part of the query string, because it isn't nullable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tested this? Might be som error in my test setup, but when I tested it locally it didn't return bad request with Problem json when I omitted the dataType param
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... Not here specifically. I got that behaviour (breaking a test) when running tests under net8.0 with
<nullable>enabled</nullable>
in project.json. Therefore I was very careful with [FromQuery] parameters that was not nullable. Maybe it's only when nullability is enabled globally, maybe only net8.0?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You were right. The difference was that I tested this with
OptionsController
and it was annotated with[ApiController]
which causes automatic BadRequest for non nullable query strings when nullable is enabled. I wrote a test for this "BadRequest" response and added the annotation to make the test work in ab43649.There is no difference between dovnet 6-8 or file scoped/project scoped nullable