diff --git a/src/Config/Entity.cs b/src/Config/Entity.cs index ce6b2b1c1e..dbcd069764 100644 --- a/src/Config/Entity.cs +++ b/src/Config/Entity.cs @@ -154,7 +154,7 @@ public void TryPopulateSourceFields() } else { - ObjectType = ConvertSourceType(objectSource.Type); + ObjectType = objectSource.Type; SourceName = objectSource.Name; Parameters = objectSource.Parameters; KeyFields = objectSource.KeyFields; @@ -165,23 +165,6 @@ public void TryPopulateSourceFields() throw new JsonException(message: $"Source not one of string or object"); } } - - /// - /// Tries to convert the given string sourceType into one of the supported SourceType enums - /// Throws an exception if not a case-insensitive match - /// - private static SourceType ConvertSourceType(string? sourceType) - { - // If sourceType is not explicitly specified, we assume it is a Table - return sourceType is null ? SourceType.Table - : sourceType.ToLowerInvariant() switch - { - "table" => SourceType.Table, - "view" => SourceType.View, - "stored-procedure" => SourceType.StoredProcedure, - _ => throw new JsonException(message: "Source type must be one of: [table, view, stored-procedure]") - }; - } } /// @@ -196,13 +179,52 @@ private static SourceType ConvertSourceType(string? sourceType) /// The field(s) to be used as primary keys. /// Support tracked in #547 public record DatabaseObjectSource( - string Type, + [property: JsonConverter(typeof(SourceTypeEnumJsonConverter))] + SourceType Type, [property: JsonPropertyName("object")] string Name, Dictionary? Parameters, [property: JsonPropertyName("key-fields")] Array KeyFields); + /// + /// Class to specify custom converter used while deserialising json config + /// to SourceType and serializing from SourceType to string. + /// Tries to convert the given string sourceType into one of the supported SourceType enums + /// Throws an exception if not a case-insensitive match + /// + public class SourceTypeEnumJsonConverter : JsonConverter + { + public const string STORED_PROCEDURE = "stored-procedure"; + + /// + public override SourceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? type = reader.GetString(); + if (STORED_PROCEDURE.Equals(type)) + { + return SourceType.StoredProcedure; + } + + if (Enum.TryParse(type, ignoreCase: true, out SourceType sourceType)) + { + return sourceType; + } + else + { + throw new JsonException($"Invalid Source Type: {type}." + + $" Valid values are {STORED_PROCEDURE}, {SourceType.Table}, and {SourceType.View}."); + } + } + + /// + public override void Write(Utf8JsonWriter writer, SourceType value, JsonSerializerOptions options) + { + string valueToWrite = value is SourceType.StoredProcedure ? STORED_PROCEDURE : value.ToString().ToLower(); + writer.WriteStringValue(valueToWrite); + } + } + /// /// Supported source types as defined by json schema /// diff --git a/src/Service.Tests/Authorization/AuthorizationHelpers.cs b/src/Service.Tests/Authorization/AuthorizationHelpers.cs index 8951848d5f..50d4c6533d 100644 --- a/src/Service.Tests/Authorization/AuthorizationHelpers.cs +++ b/src/Service.Tests/Authorization/AuthorizationHelpers.cs @@ -58,7 +58,7 @@ public static AuthorizationResolver InitAuthorizationResolver(RuntimeConfig runt /// public static RuntimeConfig InitRuntimeConfig( string entityName = TEST_ENTITY, - string entitySource = TEST_ENTITY, + object? entitySource = null, string roleName = "Reader", Operation operation = Operation.Create, HashSet? includedCols = null, @@ -69,6 +69,11 @@ public static RuntimeConfig InitRuntimeConfig( { Field? fieldsForRole = null; + if (entitySource is null) + { + entitySource = TEST_ENTITY; + } + if (includedCols is not null || excludedCols is not null) { // Only create object for Fields if inc/exc cols is not null. diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 6b349ea336..edea620cd0 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -99,6 +99,76 @@ public async Task TestNoConfigReturnsServiceUnavailable(string[] args) Assert.AreEqual(HttpStatusCode.ServiceUnavailable, result.StatusCode); } + /// + /// Checks correct serialization and deserialization of Source Type from + /// Enum to String and vice-versa. + /// Consider both cases for source as an object and as a string + /// + [DataTestMethod] + [DataRow(true, SourceType.StoredProcedure, "stored-procedure", DisplayName = "source is a stored-procedure")] + [DataRow(true, SourceType.Table, "table", DisplayName = "source is a table")] + [DataRow(true, SourceType.View, "view", DisplayName = "source is a view")] + [DataRow(false, null, null, DisplayName = "source is just string")] + public void TestCorrectSerializationOfSourceObject( + bool isDatabaseObjectSource, + SourceType sourceObjectType, + string sourceTypeName) + { + object entitySource; + if (isDatabaseObjectSource) + { + entitySource = new DatabaseObjectSource( + Type: sourceObjectType, + Name: "sourceName", + Parameters: null, + KeyFields: null + ); + } + else + { + entitySource = "sourceName"; + } + + RuntimeConfig runtimeConfig = AuthorizationHelpers.InitRuntimeConfig( + entityName: "MyEntity", + entitySource: entitySource, + roleName: "Anonymous", + operation: Operation.All, + includedCols: null, + excludedCols: null, + databasePolicy: null + ); + + string runtimeConfigJson = JsonSerializer.Serialize(runtimeConfig); + + if (isDatabaseObjectSource) + { + Assert.IsTrue(runtimeConfigJson.Contains(sourceTypeName)); + } + + Mock logger = new(); + Assert.IsTrue(RuntimeConfig.TryGetDeserializedConfig( + runtimeConfigJson, + out RuntimeConfig deserializedRuntimeConfig, + logger.Object)); + + Assert.IsTrue(deserializedRuntimeConfig.Entities.ContainsKey("MyEntity")); + deserializedRuntimeConfig.Entities["MyEntity"].TryPopulateSourceFields(); + Assert.AreEqual("sourceName", deserializedRuntimeConfig.Entities["MyEntity"].SourceName); + + JsonElement sourceJson = (JsonElement)deserializedRuntimeConfig.Entities["MyEntity"].Source; + if (isDatabaseObjectSource) + { + Assert.AreEqual(JsonValueKind.Object, sourceJson.ValueKind); + Assert.AreEqual(sourceObjectType, deserializedRuntimeConfig.Entities["MyEntity"].ObjectType); + } + else + { + Assert.AreEqual(JsonValueKind.String, sourceJson.ValueKind); + Assert.AreEqual("sourceName", deserializedRuntimeConfig.Entities["MyEntity"].Source.ToString()); + } + } + [TestMethod("Validates that once the configuration is set, the config controller isn't reachable.")] public async Task TestConflictAlreadySetConfiguration() {