diff --git a/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs b/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs index 84837a90a2..4f94b5e3a4 100644 --- a/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs +++ b/src/Service.GraphQLBuilder/GraphQLStoredProcedureBuilder.cs @@ -1,13 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Globalization; +using System.Net; using System.Text.Json; using Azure.DataApiBuilder.Config.DatabasePrimitives; using Azure.DataApiBuilder.Config.ObjectModel; +using Azure.DataApiBuilder.Service.Exceptions; +using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars; using Azure.DataApiBuilder.Service.GraphQLBuilder.Sql; using HotChocolate.Language; using HotChocolate.Types; +using HotChocolate.Types.NodaTime; +using NodaTime.Text; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLNaming; +using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedHotChocolateTypes; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLUtils; namespace Azure.DataApiBuilder.Service.GraphQLBuilder @@ -122,5 +129,53 @@ public static FieldDefinitionNode GetDefaultResultFieldForStoredProcedure() type: new StringType().ToTypeNode(), directives: new List()); } + + /// + /// Translates a JSON string or number value defined as a stored procedure's default value + /// within the runtime configuration to a GraphQL {Type}ValueNode which represents + /// the associated GraphQL type. The target value type is referenced from the passed in parameterDefinition which + /// holds database schema metadata. + /// + /// String representation of default value defined in runtime config. + /// Database schema metadata for stored procedure parameter which include value and value type. + /// Tuple where first item is the string representation of a GraphQLType (e.g. "Byte", "Int", "Decimal") + /// and the second item is the GraphQL {type}ValueNode. + /// Raised when parameter casting fails due to unsupported type. + private static Tuple ConvertValueToGraphQLType(string defaultValueFromConfig, ParameterDefinition parameterDefinition) + { + string paramValueType = SchemaConverter.GetGraphQLTypeFromSystemType(type: parameterDefinition.SystemType); + + try + { + Tuple valueNode = paramValueType switch + { + UUID_TYPE => new(UUID_TYPE, new UuidType().ParseValue(Guid.Parse(defaultValueFromConfig))), + BYTE_TYPE => new(BYTE_TYPE, new IntValueNode(byte.Parse(defaultValueFromConfig))), + SHORT_TYPE => new(SHORT_TYPE, new IntValueNode(short.Parse(defaultValueFromConfig))), + INT_TYPE => new(INT_TYPE, new IntValueNode(int.Parse(defaultValueFromConfig))), + LONG_TYPE => new(LONG_TYPE, new IntValueNode(long.Parse(defaultValueFromConfig))), + SINGLE_TYPE => new(SINGLE_TYPE, new SingleType().ParseValue(float.Parse(defaultValueFromConfig))), + FLOAT_TYPE => new(FLOAT_TYPE, new FloatValueNode(double.Parse(defaultValueFromConfig))), + DECIMAL_TYPE => new(DECIMAL_TYPE, new FloatValueNode(decimal.Parse(defaultValueFromConfig))), + STRING_TYPE => new(STRING_TYPE, new StringValueNode(defaultValueFromConfig)), + BOOLEAN_TYPE => new(BOOLEAN_TYPE, new BooleanValueNode(bool.Parse(defaultValueFromConfig))), + DATETIME_TYPE => new(DATETIME_TYPE, new DateTimeType().ParseResult( + DateTime.Parse(defaultValueFromConfig, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal))), + BYTEARRAY_TYPE => new(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(Convert.FromBase64String(defaultValueFromConfig))), + LOCALTIME_TYPE => new(LOCALTIME_TYPE, new LocalTimeType().ParseResult(LocalTimePattern.ExtendedIso.Parse(defaultValueFromConfig).Value)), + _ => throw new NotSupportedException(message: $"The {defaultValueFromConfig} parameter's value type [{paramValueType}] is not supported.") + }; + + return valueNode; + } + catch (Exception error) + { + throw new DataApiBuilderException( + message: $"The parameter value {defaultValueFromConfig} provided in configuration cannot be converted to the type {paramValueType}", + statusCode: HttpStatusCode.InternalServerError, + subStatusCode: DataApiBuilderException.SubStatusCodes.GraphQLMapping, + innerException: error); + } + } } } diff --git a/src/Service.GraphQLBuilder/GraphQLUtils.cs b/src/Service.GraphQLBuilder/GraphQLUtils.cs index 52cb6fbf7d..7128b2049b 100644 --- a/src/Service.GraphQLBuilder/GraphQLUtils.cs +++ b/src/Service.GraphQLBuilder/GraphQLUtils.cs @@ -2,20 +2,14 @@ // Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Net; -using Azure.DataApiBuilder.Config.DatabasePrimitives; using Azure.DataApiBuilder.Config.ObjectModel; using Azure.DataApiBuilder.Service.Exceptions; -using Azure.DataApiBuilder.Service.GraphQLBuilder.CustomScalars; using Azure.DataApiBuilder.Service.GraphQLBuilder.Directives; using Azure.DataApiBuilder.Service.GraphQLBuilder.Queries; -using Azure.DataApiBuilder.Service.GraphQLBuilder.Sql; using HotChocolate.Language; using HotChocolate.Resolvers; using HotChocolate.Types; -using HotChocolate.Types.NodaTime; -using NodaTime.Text; using static Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes.SupportedHotChocolateTypes; namespace Azure.DataApiBuilder.Service.GraphQLBuilder @@ -46,9 +40,9 @@ public static bool IsModelType(ObjectType objectType) public static bool IsBuiltInType(ITypeNode typeNode) { - HashSet inBuiltTypes = new() + HashSet builtInTypes = new() { - "ID", + "ID", // Required for CosmosDB UUID_TYPE, BYTE_TYPE, SHORT_TYPE, @@ -64,7 +58,7 @@ public static bool IsBuiltInType(ITypeNode typeNode) LOCALTIME_TYPE }; string name = typeNode.NamedType().Name.Value; - return inBuiltTypes.Contains(name); + return builtInTypes.Contains(name); } /// @@ -225,56 +219,6 @@ public static ObjectType UnderlyingGraphQLEntityType(IType type) return UnderlyingGraphQLEntityType(type.InnerType()); } - /// - /// Translates a JSON string or number value defined in the runtime configuration to a GraphQL {Type}ValueNode which represents - /// the associated GraphQL type. The target value type is referenced from the passed in parameterDefinition which - /// holds database schema metadata. - /// - /// String representation of default value defined in runtime config. - /// Database schema metadata for stored procedure parameter which include value and value type. - /// Tuple where first item is the string representation of a GraphQLType (e.g. "Byte", "Int", "Decimal") - /// and the second item is the GraphQL {type}ValueNode - /// Raised when parameter casting fails due to unsupported type. - public static Tuple ConvertValueToGraphQLType(string defaultValueFromConfig, ParameterDefinition parameterDefinition) - { - string paramValueType = SchemaConverter.GetGraphQLTypeFromSystemType(type: parameterDefinition.SystemType); - - try - { - Tuple valueNode = paramValueType switch - { - BYTE_TYPE => new(BYTE_TYPE, new IntValueNode(byte.Parse(defaultValueFromConfig))), - SHORT_TYPE => new(SHORT_TYPE, new IntValueNode(short.Parse(defaultValueFromConfig))), - INT_TYPE => new(INT_TYPE, new IntValueNode(int.Parse(defaultValueFromConfig))), - LONG_TYPE => new(LONG_TYPE, new IntValueNode(long.Parse(defaultValueFromConfig))), - STRING_TYPE => new(STRING_TYPE, new StringValueNode(defaultValueFromConfig)), - BOOLEAN_TYPE => new(BOOLEAN_TYPE, new BooleanValueNode(bool.Parse(defaultValueFromConfig))), - SINGLE_TYPE => new(SINGLE_TYPE, new SingleType().ParseValue(float.Parse(defaultValueFromConfig))), - FLOAT_TYPE => new(FLOAT_TYPE, new FloatValueNode(double.Parse(defaultValueFromConfig))), - DECIMAL_TYPE => new(DECIMAL_TYPE, new FloatValueNode(decimal.Parse(defaultValueFromConfig))), - DATETIME_TYPE => new(DATETIME_TYPE, new DateTimeType().ParseResult( - DateTime.Parse(defaultValueFromConfig, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AssumeUniversal))), - BYTEARRAY_TYPE => new(BYTEARRAY_TYPE, new ByteArrayType().ParseValue(Convert.FromBase64String(defaultValueFromConfig))), - LOCALTIME_TYPE => new(LOCALTIME_TYPE, new LocalTimeType().ParseResult(LocalTimePattern.ExtendedIso.Parse(defaultValueFromConfig).Value)), - _ => throw new NotSupportedException(message: $"The {defaultValueFromConfig} parameter's value type [{paramValueType}] is not supported.") - }; - - return valueNode; - } - catch (Exception error) when ( - error is FormatException || - error is OverflowException || - error is ArgumentException || - error is NotSupportedException) - { - throw new DataApiBuilderException( - message: $"The parameter value {defaultValueFromConfig} provided in configuration cannot be converted to the type {paramValueType}", - statusCode: HttpStatusCode.InternalServerError, - subStatusCode: DataApiBuilderException.SubStatusCodes.GraphQLMapping, - innerException: error); - } - } - /// /// Generates the datasource name from the GraphQL context. /// diff --git a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs index a77a5f9dfa..50fdb327c2 100644 --- a/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs +++ b/src/Service.Tests/GraphQLBuilder/Sql/StoredProcedureBuilderTests.cs @@ -56,6 +56,8 @@ public class StoredProcedureBuilderTests [DataRow(typeof(DateTimeOffset), DATETIME_TYPE, "11/19/2012 10:57:11 AM -08:00", false, DisplayName = "DateTimeOffset")] [DataRow(typeof(TimeOnly), LOCALTIME_TYPE, "10:57:11.0000", false, DisplayName = "LocalTime")] [DataRow(typeof(byte[]), BYTEARRAY_TYPE, "AgQGCAoMDhASFA==", false, DisplayName = "Byte[]")] + [DataRow(typeof(Guid), UUID_TYPE, "f58b7b58-62c9-4b97-ab60-75de70793f66", false, DisplayName = "GraphQL UUID/ SystemType GUID")] + [DataRow(typeof(string), STRING_TYPE, "f58b7b58-62c9-4b97-ab60-75de70793f66", false, DisplayName = "DB/SystemType String -> GUID value -> Resolve as GraphQL string")] public void StoredProcedure_ParameterValueTypeResolution( Type systemType, string expectedGraphQLType, diff --git a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs index dc684d3eb2..0f121f21c1 100644 --- a/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs +++ b/src/Service.Tests/SqlTests/GraphQLSupportedTypesTests/GraphQLSupportedTypesTestsBase.cs @@ -314,6 +314,8 @@ public async Task TestTimeTypePrecisionCheck(string gqlValue, int count) [DataRow(INT_TYPE, "0")] [DataRow(INT_TYPE, "-9999")] [DataRow(INT_TYPE, "null")] + [DataRow(UUID_TYPE, "3a1483a5-9ac2-4998-bcf3-78a28078c6ac")] + [DataRow(UUID_TYPE, "null")] [DataRow(LONG_TYPE, "0")] [DataRow(LONG_TYPE, "9000000000000000000")] [DataRow(LONG_TYPE, "-9000000000000000000")] @@ -419,6 +421,7 @@ public async Task InsertInvalidTimeIntoTimeTypeColumn(string type, string value) [DataRow(TIME_TYPE, "\"23:59:59.9999999\"")] [DataRow(TIME_TYPE, "null")] [DataRow(BYTEARRAY_TYPE, "V2hhdGNodSBkb2luZyBkZWNvZGluZyBvdXIgdGVzdCBiYXNlNjQgc3RyaW5ncz8=")] + [DataRow(UUID_TYPE, "3a1483a5-9ac2-4998-bcf3-78a28078c6ac")] public async Task InsertIntoTypeColumnWithArgument(string type, object value) { if (!IsSupportedType(type))