Skip to content

Commit

Permalink
Add custom attribute for OptionValue and OptionLabel and allow empty …
Browse files Browse the repository at this point in the history
…string
  • Loading branch information
standeren committed Nov 12, 2024
1 parent 002b6e4 commit 4c50a5c
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 126 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace Altinn.Studio.Designer.Exceptions.Options;

/// <summary>
/// Indicates that an error occurred during json serialization of options.
/// </summary>
[Serializable]
public class InvalidOptionsFormatException : Exception
{
/// <inheritdoc/>
public InvalidOptionsFormatException()
{
}

/// <inheritdoc/>
public InvalidOptionsFormatException(string message) : base(message)
{
}

/// <inheritdoc/>
public InvalidOptionsFormatException(string message, Exception innerException) : base(message, innerException)
{
}
}
6 changes: 6 additions & 0 deletions backend/src/Designer/Filters/Options/OptionsErrorCodes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Altinn.Studio.Designer.Filters.Options;

public class OptionsErrorCodes
{
public const string InvalidOptionsFormat = nameof(InvalidOptionsFormat);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Net;
using Altinn.Studio.Designer.Exceptions.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Altinn.Studio.Designer.Filters.Options;

public class OptionsExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
base.OnException(context);

if (context.ActionDescriptor is not ControllerActionDescriptor)
{
return;
}

if (context.Exception is InvalidOptionsFormatException)
{
context.Result = new ObjectResult(ProblemDetailsUtils.GenerateProblemDetails(context.Exception, OptionsErrorCodes.InvalidOptionsFormat, HttpStatusCode.BadRequest)) { StatusCode = (int)HttpStatusCode.BadRequest };
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ namespace Altinn.Studio.Designer.Filters;
public static class ProblemDetailsExtensionsCodes
{
public const string ErrorCode = "errorCode";

public const string Detail = "detail";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;

namespace Altinn.Studio.Designer.Helpers.JsonConverterHelpers;

public class NotNullableAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
return new ValidationResult("The field is required.");
}
return ValidationResult.Success;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Altinn.Studio.Designer.Exceptions.Options;

namespace Altinn.Studio.Designer.Helpers.JsonConverterHelpers;

public class OptionConverter : JsonConverter<object>
public class OptionValueConverter : JsonConverter<object>
{
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Expand All @@ -14,7 +15,7 @@ public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS
JsonTokenType.Number when reader.TryGetDouble(out double d) => d,
JsonTokenType.True => true,
JsonTokenType.False => false,
_ => throw new JsonException($"Unsupported JSON token for Option.Value: {reader.TokenType}")
_ => throw new InvalidOptionsFormatException($"Unsupported JSON token for Value field, {typeToConvert}: {reader.TokenType}.")
};
}

Expand All @@ -32,7 +33,7 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp
writer.WriteBooleanValue(b);
break;
default:
throw new JsonException("Unsupported type for Option.Value.");
throw new InvalidOptionsFormatException($"{value} is an unsupported type for Value field.");
}
}
}
2 changes: 2 additions & 0 deletions backend/src/Designer/Infrastructure/MvcConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Altinn.Studio.Designer.Filters.DataModeling;
using Altinn.Studio.Designer.Filters.Git;
using Altinn.Studio.Designer.Filters.IO;
using Altinn.Studio.Designer.Filters.Options;
using Altinn.Studio.Designer.ModelBinding;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -26,6 +27,7 @@ public static IServiceCollection ConfigureMvc(this IServiceCollection services)
options.Filters.Add(typeof(DataModelingExceptionFilterAttribute));
options.Filters.Add(typeof(GitExceptionFilterAttribute));
options.Filters.Add(typeof(IoExceptionFilterAttribute));
options.Filters.Add(typeof(OptionsExceptionFilterAttribute));
})
.AddNewtonsoftJson(options => options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()));

Expand Down
11 changes: 5 additions & 6 deletions backend/src/Designer/Models/Option.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using Altinn.Studio.Designer.Helpers.JsonConverterHelpers;

Expand All @@ -12,17 +11,17 @@ public class Option
/// <summary>
/// Value that connects the option to the data model.
/// </summary>
[Required]
[NotNullable]
[JsonPropertyName("value")]
[JsonConverter(typeof(OptionConverter))]
public object Value { get; set; }
[JsonConverter(typeof(OptionValueConverter))]
public required object Value { get; set; }

/// <summary>
/// Label to present to the user.
/// </summary>
[Required]
[NotNullable]
[JsonPropertyName("label")]
public string Label { get; set; }
public required string Label { get; set; }

/// <summary>
/// Description, typically displayed below the label.
Expand Down
17 changes: 0 additions & 17 deletions backend/src/Designer/Services/Implementation/OptionsService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -73,28 +72,12 @@ public async Task<List<Option>> UploadNewOption(string org, string repo, string
List<Option> deserializedOptions = JsonSerializer.Deserialize<List<Option>>(payload.OpenReadStream(),
new JsonSerializerOptions { WriteIndented = true, AllowTrailingCommas = true });

IEnumerable<Option> result = deserializedOptions.Where(option => IsNullOrEmptyOptionValue(option) || string.IsNullOrEmpty(option.Label));
if (result.Any())
{
throw new JsonException("Uploaded file is missing one of the following attributes for an option: value or label.");
}

var altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, repo, developer);
await altinnAppGitRepository.CreateOrOverwriteOptionsList(optionsListId, deserializedOptions, cancellationToken);

return deserializedOptions;
}

bool IsNullOrEmptyOptionValue(object value)
{
if (value == null)
{
return true;
}

return value is string stringOption && string.IsNullOrEmpty(stringOption);
}

/// <inheritdoc />
public void DeleteOptionsList(string org, string repo, string developer, string optionsListId)
{
Expand Down
Loading

0 comments on commit 4c50a5c

Please sign in to comment.