diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/Samples/ExampleParameterValue.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/Samples/ExampleParameterValue.cs new file mode 100644 index 00000000000..5ff6aa62201 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/Samples/ExampleParameterValue.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.TypeSpec.Generator.Expressions; +using Microsoft.TypeSpec.Generator.Input; +using Microsoft.TypeSpec.Generator.Primitives; + +namespace Microsoft.TypeSpec.Generator.ClientModel.Providers.Samples +{ + /// + /// Represents a parameter value in a sample. Supports two modes: + /// - : raw example data that will be converted to a C# expression later + /// - : a pre-built C# expression (used for known parameters like credentials, endpoints) + /// + public class ExampleParameterValue + { + public ExampleParameterValue(string name, CSharpType type, InputExampleValue value) + { + Name = name; + Type = type; + Value = value; + } + + public ExampleParameterValue(string name, CSharpType type, ValueExpression expression) + { + Name = name; + Type = type; + Expression = expression; + } + + /// + /// The parameter name. + /// + public string Name { get; } + + /// + /// The C# type of the parameter. + /// + public CSharpType Type { get; } + + /// + /// Raw example data from the spec or mock builder. Will be converted to a + /// via . + /// Mutually exclusive with . + /// + public InputExampleValue? Value { get; } + + /// + /// A pre-built C# expression. Used for known parameters (credentials, endpoints) + /// where the expression is fixed regardless of example data. + /// Mutually exclusive with . + /// + public ValueExpression? Expression { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/Samples/ExampleValueExpressionBuilder.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/Samples/ExampleValueExpressionBuilder.cs new file mode 100644 index 00000000000..0b37e91f330 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/Samples/ExampleValueExpressionBuilder.cs @@ -0,0 +1,314 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; +using Microsoft.TypeSpec.Generator.Expressions; +using Microsoft.TypeSpec.Generator.Input; +using Microsoft.TypeSpec.Generator.Primitives; +using static Microsoft.TypeSpec.Generator.Snippets.Snippet; + +namespace Microsoft.TypeSpec.Generator.ClientModel.Providers.Samples +{ + /// + /// Converts + into (C# AST nodes). + /// This is the bridge between raw mock/spec example data and generated C# code. + /// + public static class ExampleValueExpressionBuilder + { + /// + /// Converts an to a C# expression. + /// If the parameter has a pre-built expression, returns it directly. + /// Otherwise, converts the raw using type information. + /// + public static ValueExpression GetExpression(ExampleParameterValue parameterValue) + { + if (parameterValue.Expression != null) + return parameterValue.Expression; + + if (parameterValue.Value != null) + return GetExpression(parameterValue.Type, parameterValue.Value); + + return Default; + } + + /// + /// Converts an to a C# expression based on the target . + /// + public static ValueExpression GetExpression(CSharpType type, InputExampleValue exampleValue) + { + if (type.IsList) + return GetExpressionForList(type, exampleValue); + if (type.IsDictionary) + return GetExpressionForDictionary(type, exampleValue); + if (type.IsEnum) + return GetExpressionForEnum(type, exampleValue); + if (type is { IsFrameworkType: true }) + return GetExpressionForFrameworkType(type.FrameworkType, exampleValue); + + // For model types, fall back to default + return GetExpressionForModel(type, exampleValue); + } + + private static ValueExpression GetExpressionForFrameworkType(Type frameworkType, InputExampleValue exampleValue) + { + var rawValue = GetRawValue(exampleValue); + + // String + if (frameworkType == typeof(string)) + { + return rawValue is string s ? Literal(s) : Null; + } + + // Boolean + if (frameworkType == typeof(bool)) + { + return rawValue is bool b ? Literal(b) : Default; + } + + // Integer types + if (frameworkType == typeof(int)) + { + return rawValue != null ? Literal(Convert.ToInt32(rawValue)) : Default; + } + if (frameworkType == typeof(long)) + { + return rawValue != null ? Literal(Convert.ToInt64(rawValue)) : Default; + } + if (frameworkType == typeof(short)) + { + return rawValue != null ? new CastExpression(Literal(Convert.ToInt16(rawValue)), frameworkType) : Default; + } + if (frameworkType == typeof(sbyte)) + { + return rawValue != null ? new CastExpression(Literal(Convert.ToSByte(rawValue)), frameworkType) : Default; + } + if (frameworkType == typeof(byte)) + { + return rawValue != null ? new CastExpression(Literal(Convert.ToByte(rawValue)), frameworkType) : Default; + } + if (frameworkType == typeof(ushort)) + { + return rawValue != null ? new CastExpression(Literal(Convert.ToUInt16(rawValue)), frameworkType) : Default; + } + if (frameworkType == typeof(uint)) + { + return rawValue != null ? new CastExpression(Literal(Convert.ToUInt32(rawValue)), frameworkType) : Default; + } + if (frameworkType == typeof(ulong)) + { + return rawValue != null ? new CastExpression(Literal(Convert.ToUInt64(rawValue)), frameworkType) : Default; + } + + // Float types + if (frameworkType == typeof(float)) + { + return rawValue != null ? Literal(Convert.ToSingle(rawValue)) : Default; + } + if (frameworkType == typeof(double)) + { + return rawValue != null ? Literal(Convert.ToDouble(rawValue)) : Default; + } + if (frameworkType == typeof(decimal)) + { + return rawValue != null ? Literal(Convert.ToDecimal(rawValue)) : Default; + } + + // Guid + if (frameworkType == typeof(Guid)) + { + if (rawValue is string s) + return Static(typeof(Guid)).Invoke(nameof(Guid.Parse), Literal(s)); + return Default; + } + + // Uri + if (frameworkType == typeof(Uri)) + { + if (rawValue is string s) + return New.Instance(typeof(Uri), Literal(s)); + return Null; + } + + // DateTimeOffset + if (frameworkType == typeof(DateTimeOffset)) + { + if (rawValue is string s) + return Static(typeof(DateTimeOffset)).Invoke(nameof(DateTimeOffset.Parse), Literal(s)); + if (rawValue is int or long) + return Static(typeof(DateTimeOffset)).Invoke(nameof(DateTimeOffset.FromUnixTimeSeconds), Literal(Convert.ToInt64(rawValue))); + return Default; + } + + // TimeSpan + if (frameworkType == typeof(TimeSpan)) + { + if (rawValue is string s) + return Static(typeof(XmlConvert)).Invoke(nameof(XmlConvert.ToTimeSpan), Literal(s)); + if (rawValue is int or float or double) + return Static(typeof(TimeSpan)).Invoke(nameof(TimeSpan.FromSeconds), Literal(Convert.ToDouble(rawValue))); + return Default; + } + + // BinaryData + if (frameworkType == typeof(BinaryData)) + { + if (rawValue == null && exampleValue is not InputExampleValue) + return Null; + return GetExpressionForBinaryData(exampleValue); + } + + // byte[] + if (frameworkType == typeof(byte[])) + { + if (rawValue is string s) + return Static(typeof(Encoding)).Property(nameof(Encoding.UTF8)) + .Invoke(nameof(Encoding.GetBytes), Literal(s)); + return Null; + } + + // Stream + if (frameworkType == typeof(Stream)) + { + if (exampleValue is InputExampleStreamValue streamValue) + return Static(typeof(File)).Invoke(nameof(File.OpenRead), Literal(streamValue.Filename)); + return Null; + } + + // Fallback + return frameworkType.IsValueType ? Default : Null; + } + + private static ValueExpression GetExpressionForList(CSharpType listType, InputExampleValue exampleValue) + { + var elementType = listType.ElementType; + var items = new List(); + + if (exampleValue is InputExampleListValue listValue) + { + foreach (var itemValue in listValue.Values) + { + items.Add(GetExpression(elementType, itemValue)); + } + } + + return New.Array(elementType, items.ToArray()); + } + + private static ValueExpression GetExpressionForDictionary(CSharpType dictionaryType, InputExampleValue exampleValue) + { + var keyType = dictionaryType.Arguments[0]; + var valueType = dictionaryType.Arguments[1]; + var entries = new Dictionary(); + + if (exampleValue is InputExampleObjectValue objectValue) + { + foreach (var (key, value) in objectValue.Values) + { + var keyExpr = GetExpression(keyType, InputExampleValue.Value(new InputPrimitiveType(InputPrimitiveTypeKind.String, "string", "TypeSpec.string"), key)); + var valueExpr = GetExpression(valueType, value); + entries[keyExpr] = valueExpr; + } + } + + return New.Dictionary(keyType, valueType, entries); + } + + private static ValueExpression GetExpressionForEnum(CSharpType enumType, InputExampleValue exampleValue) + { + var rawValue = GetRawValue(exampleValue); + if (rawValue == null) + return Default; + + // Access the enum member by name using the type reference + var rawString = rawValue.ToString()!; + // Use the type name as a static access point: EnumType.MemberName + return new MemberExpression(Static(enumType), rawString); + } + + private static ValueExpression GetExpressionForModel(CSharpType type, InputExampleValue exampleValue) + { + // For model types, generate new ModelType() — will be enhanced in M3/M4 + // with constructor parameter analysis + if (type.IsValueType) + return Default; + return New.Instance(type); + } + + private static ValueExpression GetExpressionForBinaryData(InputExampleValue exampleValue) + { + // Build an anonymous object from the example value and wrap in BinaryData.FromObjectAsJson + var anonymousObj = GetExpressionForAnonymousObject(exampleValue); + return Static(typeof(BinaryData)).Invoke(nameof(BinaryData.FromObjectAsJson), anonymousObj); + } + + /// + /// Converts an example value to an anonymous object expression for use in + /// BinaryData.FromObjectAsJson() or BinaryContent.Create(). + /// + internal static ValueExpression GetExpressionForAnonymousObject(InputExampleValue exampleValue) + { + if (exampleValue is InputExampleObjectValue objectValue) + { + var properties = new Dictionary(); + foreach (var (key, value) in objectValue.Values) + { + var rawVal = GetRawValue(value); + // Skip null properties in anonymous objects (causes compilation errors) + if (rawVal == null && value is InputExampleRawValue) + continue; + + var valueExpr = GetExpressionForAnonymousObject(value); + properties[Identifier(key)] = valueExpr; + } + return properties.Count > 0 ? New.Anonymous(properties) : New.Instance(typeof(object)); + } + + if (exampleValue is InputExampleListValue listValue) + { + var items = new List(); + foreach (var item in listValue.Values) + { + items.Add(GetExpressionForAnonymousObject(item)); + } + return New.Array(new CSharpType(typeof(object)), items.ToArray()); + } + + if (exampleValue is InputExampleStreamValue streamValue) + { + return Static(typeof(File)).Invoke(nameof(File.OpenRead), Literal(streamValue.Filename)); + } + + // Raw value — convert to literal + var raw = GetRawValue(exampleValue); + if (raw == null) + return Null; + + return raw switch + { + string s => Literal(s), + bool b => Literal(b), + int i => Literal(i), + long l => Literal(l), + float f => Literal(f), + double d => Literal(d), + decimal m => Literal(m), + _ => Literal(raw.ToString()!) + }; + } + + /// + /// Extracts the raw value from an if it's a raw (primitive) value. + /// Returns null for non-raw values (lists, objects, streams). + /// + private static object? GetRawValue(InputExampleValue exampleValue) + { + if (exampleValue is InputExampleRawValue rawValue) + return rawValue.RawValue; + return null; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Samples/ExampleValueExpressionBuilderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Samples/ExampleValueExpressionBuilderTests.cs new file mode 100644 index 00000000000..8daea57ac77 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Samples/ExampleValueExpressionBuilderTests.cs @@ -0,0 +1,624 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Xml; +using Microsoft.TypeSpec.Generator.ClientModel.Providers.Samples; +using Microsoft.TypeSpec.Generator.Expressions; +using Microsoft.TypeSpec.Generator.Input; +using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Tests.Common; +using NUnit.Framework; + +namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Samples +{ + public class ExampleValueExpressionBuilderTests + { + // ----------------------------------------------------------------------- + // ExampleParameterValue — dual mode + // ----------------------------------------------------------------------- + + [Test] + public void ParameterValue_WithExpression_ReturnsExpression() + { + var expr = new LiteralExpression(42); + var paramValue = new ExampleParameterValue("count", new CSharpType(typeof(int)), expr); + + var result = ExampleValueExpressionBuilder.GetExpression(paramValue); + + Assert.AreSame(expr, result); + } + + [Test] + public void ParameterValue_WithValue_ConvertsToExpression() + { + var inputValue = InputExampleValue.Value(InputPrimitiveType.Int32, 1234); + var paramValue = new ExampleParameterValue("count", new CSharpType(typeof(int)), inputValue); + + var result = ExampleValueExpressionBuilder.GetExpression(paramValue); + + Assert.IsNotNull(result); + } + + [Test] + public void ParameterValue_WithNeither_ReturnsDefault() + { + // Edge case: both Value and Expression are null (shouldn't happen in practice) + var paramValue = new ExampleParameterValue("x", new CSharpType(typeof(int)), + (InputExampleValue)null!); + + // This will go through GetExpression with null value — should return Default + var result = ExampleValueExpressionBuilder.GetExpression(paramValue); + Assert.IsNotNull(result); + } + + // ----------------------------------------------------------------------- + // Framework type conversions — primitives + // ----------------------------------------------------------------------- + + [Test] + public void String_FromRawValue() + { + var result = BuildExpression(typeof(string), InputExampleValue.Value(InputPrimitiveType.String, "hello")); + Assert.IsNotNull(result); + // Should not be null/default + Assert.IsNotInstanceOf(result); + } + + [Test] + public void String_FromNull() + { + var result = BuildExpression(typeof(string), InputExampleValue.Null(InputPrimitiveType.String)); + // Null string → Null keyword + Assert.IsNotNull(result); + } + + [Test] + public void Bool_True() + { + var result = BuildExpression(typeof(bool), InputExampleValue.Value(InputPrimitiveType.Boolean, true)); + Assert.IsNotNull(result); + } + + [Test] + public void Int32_FromRawValue() + { + var result = BuildExpression(typeof(int), InputExampleValue.Value(InputPrimitiveType.Int32, 1234)); + Assert.IsNotNull(result); + } + + [Test] + public void Int64_FromRawValue() + { + var result = BuildExpression(typeof(long), InputExampleValue.Value(InputPrimitiveType.Int64, 1234L)); + Assert.IsNotNull(result); + } + + [Test] + public void Float_FromRawValue() + { + var result = BuildExpression(typeof(float), InputExampleValue.Value(InputPrimitiveType.Float32, 123.45f)); + Assert.IsNotNull(result); + } + + [Test] + public void Double_FromRawValue() + { + var result = BuildExpression(typeof(double), InputExampleValue.Value(InputPrimitiveType.Float64, 123.45)); + Assert.IsNotNull(result); + } + + [Test] + public void Decimal_FromRawValue() + { + var result = BuildExpression(typeof(decimal), InputExampleValue.Value( + new InputPrimitiveType(InputPrimitiveTypeKind.Decimal, "decimal", "TypeSpec.decimal"), 123.45m)); + Assert.IsNotNull(result); + } + + // ----------------------------------------------------------------------- + // Framework type conversions — complex types + // ----------------------------------------------------------------------- + + [Test] + public void Guid_FromString() + { + var result = BuildExpression(typeof(Guid), + InputExampleValue.Value(InputPrimitiveType.String, "73f411fe-4f43-4b4b-9cbd-6828d8f4cf9a")); + + // Should produce Guid.Parse("...") + Assert.IsInstanceOf(result); + } + + [Test] + public void Uri_FromString() + { + var result = BuildExpression(typeof(Uri), + InputExampleValue.Value(InputPrimitiveType.Url, "http://localhost:3000")); + + // Should produce new Uri("...") + Assert.IsNotNull(result); + } + + [Test] + public void DateTimeOffset_FromString() + { + var result = BuildExpression(typeof(DateTimeOffset), + InputExampleValue.Value(InputPrimitiveType.String, "2022-05-10T18:57:31.2311892Z")); + + // Should produce DateTimeOffset.Parse("...") + Assert.IsInstanceOf(result); + } + + [Test] + public void DateTimeOffset_FromUnixTimestamp() + { + var result = BuildExpression(typeof(DateTimeOffset), + InputExampleValue.Value(InputPrimitiveType.Int64, 1652209051L)); + + // Should produce DateTimeOffset.FromUnixTimeSeconds(...) + Assert.IsInstanceOf(result); + } + + [Test] + public void TimeSpan_FromIso8601String() + { + var result = BuildExpression(typeof(TimeSpan), + InputExampleValue.Value(InputPrimitiveType.String, "PT1H23M45S")); + + // Should produce XmlConvert.ToTimeSpan("...") + Assert.IsInstanceOf(result); + } + + [Test] + public void TimeSpan_FromSeconds() + { + var result = BuildExpression(typeof(TimeSpan), + InputExampleValue.Value(InputPrimitiveType.Float64, 10.5)); + + // Should produce TimeSpan.FromSeconds(...) + Assert.IsInstanceOf(result); + } + + [Test] + public void BinaryData_FromObject() + { + var objValue = InputExampleValue.Object( + InputFactory.Model("TestModel"), + new Dictionary + { + ["name"] = InputExampleValue.Value(InputPrimitiveType.String, "test") + }); + + var result = BuildExpression(typeof(BinaryData), objValue); + + // Should produce BinaryData.FromObjectAsJson(new { name = "test" }) + Assert.IsInstanceOf(result); + } + + [Test] + public void ByteArray_FromString() + { + var result = BuildExpression(typeof(byte[]), + InputExampleValue.Value(InputPrimitiveType.String, "dGVzdA==")); + + // Should produce Encoding.UTF8.GetBytes("...") + Assert.IsInstanceOf(result); + } + + [Test] + public void Stream_FromStreamValue() + { + var result = BuildExpression(typeof(Stream), + InputExampleValue.Stream(InputPrimitiveType.String, "")); + + // Should produce File.OpenRead("...") + Assert.IsInstanceOf(result); + } + + // ----------------------------------------------------------------------- + // Collections + // ----------------------------------------------------------------------- + + [Test] + public void List_FromListValue() + { + var listType = new CSharpType(typeof(IList<>), new CSharpType(typeof(int))); + var listValue = InputExampleValue.List( + new InputArrayType("list", "TypeSpec.Array", InputPrimitiveType.Int32), + new[] { InputExampleValue.Value(InputPrimitiveType.Int32, 1234) }); + + var result = ExampleValueExpressionBuilder.GetExpression(listType, listValue); + + // New.Array returns IndexableExpression wrapping NewArrayExpression + Assert.IsNotNull(result); + } + + [Test] + public void Dictionary_FromObjectValue() + { + var dictType = new CSharpType(typeof(IDictionary<,>), new CSharpType(typeof(string)), new CSharpType(typeof(int))); + var dictValue = InputExampleValue.Object( + new InputDictionaryType("dict", InputPrimitiveType.String, InputPrimitiveType.Int32), + new Dictionary + { + ["key"] = InputExampleValue.Value(InputPrimitiveType.Int32, 42) + }); + + var result = ExampleValueExpressionBuilder.GetExpression(dictType, dictValue); + + // New.Dictionary returns DictionaryExpression wrapping NewInstanceExpression + Assert.IsNotNull(result); + } + + // ----------------------------------------------------------------------- + // Enum + // ----------------------------------------------------------------------- + + [Test] + public void Enum_ProducesMemberAccess() + { + // Use a real framework enum type for testing + var enumType = new CSharpType(typeof(DayOfWeek)); + var value = InputExampleValue.Value( + InputFactory.StringEnum("DayOfWeek", [("Monday", "Monday")]), "Monday"); + + var result = ExampleValueExpressionBuilder.GetExpression(enumType, value); + + // Should produce a member expression like DayOfWeek.Monday + Assert.IsInstanceOf(result); + } + + // ----------------------------------------------------------------------- + // Model (basic) + // ----------------------------------------------------------------------- + + [Test] + public void Model_ProducesNewInstance() + { + var modelType = new CSharpType(typeof(object)); + var value = InputExampleValue.Object( + InputFactory.Model("Widget"), + new Dictionary()); + + var result = ExampleValueExpressionBuilder.GetExpression(modelType, value); + + // Non-collection, non-enum framework type falls through to framework handler + Assert.IsNotNull(result); + } + + // ----------------------------------------------------------------------- + // Anonymous object (for BinaryContent) + // ----------------------------------------------------------------------- + + [Test] + public void AnonymousObject_FromNestedValues() + { + var objValue = InputExampleValue.Object( + InputFactory.Model("Request"), + new Dictionary + { + ["name"] = InputExampleValue.Value(InputPrimitiveType.String, "test"), + ["count"] = InputExampleValue.Value(InputPrimitiveType.Int32, 5) + }); + + var result = ExampleValueExpressionBuilder.GetExpressionForAnonymousObject(objValue); + + // Should produce new { name = "test", count = 5 } + Assert.IsNotNull(result); + } + + [Test] + public void AnonymousObject_SkipsNullValues() + { + var objValue = InputExampleValue.Object( + InputFactory.Model("Request"), + new Dictionary + { + ["name"] = InputExampleValue.Value(InputPrimitiveType.String, "test"), + ["nullable"] = InputExampleValue.Null(InputPrimitiveType.String) + }); + + var result = ExampleValueExpressionBuilder.GetExpressionForAnonymousObject(objValue); + + // Should produce new { name = "test" } — nullable skipped + Assert.IsNotNull(result); + } + + [Test] + public void AnonymousObject_EmptyObject() + { + var objValue = InputExampleValue.Object( + InputFactory.Model("Empty"), + new Dictionary()); + + var result = ExampleValueExpressionBuilder.GetExpressionForAnonymousObject(objValue); + + // Empty object → new object() (wrapped in ScopedApi) + Assert.IsNotNull(result); + } + + // ----------------------------------------------------------------------- + // Null / default fallbacks + // ----------------------------------------------------------------------- + + [Test] + public void Null_ValueType_ReturnsDefault() + { + var result = BuildExpression(typeof(int), InputExampleValue.Null(InputPrimitiveType.Int32)); + Assert.IsInstanceOf(result); + } + + [Test] + public void Null_ReferenceType_ReturnsNull() + { + var result = BuildExpression(typeof(string), InputExampleValue.Null(InputPrimitiveType.String)); + Assert.IsInstanceOf(result); + } + + // ----------------------------------------------------------------------- + // Missing primitive types — cast expressions + // ----------------------------------------------------------------------- + + [Test] + public void Short_FromRawValue() + { + var result = BuildExpression(typeof(short), InputExampleValue.Value( + new InputPrimitiveType(InputPrimitiveTypeKind.Int16, "int16", "TypeSpec.int16"), (short)1234)); + Assert.IsNotNull(result); + Assert.IsInstanceOf(result); + } + + [Test] + public void SByte_FromRawValue() + { + var result = BuildExpression(typeof(sbyte), InputExampleValue.Value( + new InputPrimitiveType(InputPrimitiveTypeKind.Int8, "int8", "TypeSpec.int8"), (sbyte)123)); + Assert.IsNotNull(result); + Assert.IsInstanceOf(result); + } + + [Test] + public void Byte_FromRawValue() + { + var result = BuildExpression(typeof(byte), InputExampleValue.Value( + new InputPrimitiveType(InputPrimitiveTypeKind.UInt8, "uint8", "TypeSpec.uint8"), (byte)123)); + Assert.IsNotNull(result); + Assert.IsInstanceOf(result); + } + + [Test] + public void UShort_FromRawValue() + { + var result = BuildExpression(typeof(ushort), InputExampleValue.Value( + new InputPrimitiveType(InputPrimitiveTypeKind.UInt16, "uint16", "TypeSpec.uint16"), (ushort)1234)); + Assert.IsNotNull(result); + Assert.IsInstanceOf(result); + } + + [Test] + public void UInt_FromRawValue() + { + var result = BuildExpression(typeof(uint), InputExampleValue.Value( + new InputPrimitiveType(InputPrimitiveTypeKind.UInt32, "uint32", "TypeSpec.uint32"), (uint)1234)); + Assert.IsNotNull(result); + Assert.IsInstanceOf(result); + } + + [Test] + public void ULong_FromRawValue() + { + var result = BuildExpression(typeof(ulong), InputExampleValue.Value( + new InputPrimitiveType(InputPrimitiveTypeKind.UInt64, "uint64", "TypeSpec.uint64"), (ulong)1234)); + Assert.IsNotNull(result); + Assert.IsInstanceOf(result); + } + + // ----------------------------------------------------------------------- + // Null paths for complex types + // ----------------------------------------------------------------------- + + [Test] + public void Bool_Null_ReturnsDefault() + { + var result = BuildExpression(typeof(bool), InputExampleValue.Null(InputPrimitiveType.Boolean)); + Assert.IsInstanceOf(result); + } + + [Test] + public void Guid_Null_ReturnsDefault() + { + var result = BuildExpression(typeof(Guid), InputExampleValue.Null(InputPrimitiveType.String)); + Assert.IsInstanceOf(result); + } + + [Test] + public void Uri_Null_ReturnsNull() + { + var result = BuildExpression(typeof(Uri), InputExampleValue.Null(InputPrimitiveType.String)); + Assert.IsNotNull(result); + } + + [Test] + public void DateTimeOffset_Null_ReturnsDefault() + { + var result = BuildExpression(typeof(DateTimeOffset), InputExampleValue.Null(InputPrimitiveType.String)); + Assert.IsInstanceOf(result); + } + + [Test] + public void TimeSpan_Null_ReturnsDefault() + { + var result = BuildExpression(typeof(TimeSpan), InputExampleValue.Null(InputPrimitiveType.String)); + Assert.IsInstanceOf(result); + } + + [Test] + public void ByteArray_Null_ReturnsNull() + { + var result = BuildExpression(typeof(byte[]), InputExampleValue.Null(InputPrimitiveType.String)); + Assert.IsNotNull(result); + } + + [Test] + public void Stream_NonStreamValue_ReturnsNull() + { + var result = BuildExpression(typeof(Stream), InputExampleValue.Value(InputPrimitiveType.String, "notastream")); + Assert.IsNotNull(result); + } + + // ----------------------------------------------------------------------- + // Enum edge case + // ----------------------------------------------------------------------- + + [Test] + public void Enum_Null_ReturnsDefault() + { + var enumType = new CSharpType(typeof(DayOfWeek)); + var value = InputExampleValue.Null(InputFactory.StringEnum("DayOfWeek", [("Monday", "Monday")])); + + var result = ExampleValueExpressionBuilder.GetExpression(enumType, value); + + Assert.IsInstanceOf(result); + } + + // ----------------------------------------------------------------------- + // Collection edge cases + // ----------------------------------------------------------------------- + + [Test] + public void List_FromNonListValue_ReturnsEmptyArray() + { + var listType = new CSharpType(typeof(IList<>), new CSharpType(typeof(int))); + var nonListValue = InputExampleValue.Value(InputPrimitiveType.Int32, 1234); + + var result = ExampleValueExpressionBuilder.GetExpression(listType, nonListValue); + + // Should produce an empty array + Assert.IsNotNull(result); + } + + [Test] + public void Dictionary_FromNonObjectValue_ReturnsEmptyDictionary() + { + var dictType = new CSharpType(typeof(IDictionary<,>), new CSharpType(typeof(string)), new CSharpType(typeof(int))); + var nonObjValue = InputExampleValue.Value(InputPrimitiveType.Int32, 1234); + + var result = ExampleValueExpressionBuilder.GetExpression(dictType, nonObjValue); + + // Should produce an empty dictionary + Assert.IsNotNull(result); + } + + // ----------------------------------------------------------------------- + // Model edge case + // ----------------------------------------------------------------------- + + [Test] + public void Model_ValueType_ReturnsDefault() + { + var valueType = new CSharpType(typeof(int)); // value type but not enum/collection/known + var value = InputExampleValue.Object( + InputFactory.Model("Widget"), + new Dictionary()); + + // int is a framework type, so it goes to framework handler, not model handler + var result = ExampleValueExpressionBuilder.GetExpression(valueType, value); + Assert.IsNotNull(result); + } + + // ----------------------------------------------------------------------- + // Anonymous object edge cases + // ----------------------------------------------------------------------- + + [Test] + public void AnonymousObject_WithNestedList() + { + var listValue = InputExampleValue.List( + new InputArrayType("list", "TypeSpec.Array", InputPrimitiveType.String), + new[] { InputExampleValue.Value(InputPrimitiveType.String, "item1") }); + + var objValue = InputExampleValue.Object( + InputFactory.Model("Request"), + new Dictionary + { + ["items"] = listValue + }); + + var result = ExampleValueExpressionBuilder.GetExpressionForAnonymousObject(objValue); + Assert.IsNotNull(result); + } + + [Test] + public void AnonymousObject_WithNestedObject() + { + var innerObj = InputExampleValue.Object( + InputFactory.Model("Inner"), + new Dictionary + { + ["innerProp"] = InputExampleValue.Value(InputPrimitiveType.Int32, 42) + }); + + var outerObj = InputExampleValue.Object( + InputFactory.Model("Outer"), + new Dictionary + { + ["nested"] = innerObj + }); + + var result = ExampleValueExpressionBuilder.GetExpressionForAnonymousObject(outerObj); + Assert.IsNotNull(result); + } + + [Test] + public void AnonymousObject_FromRawPrimitive() + { + var rawValue = InputExampleValue.Value(InputPrimitiveType.String, "hello"); + + var result = ExampleValueExpressionBuilder.GetExpressionForAnonymousObject(rawValue); + Assert.IsNotNull(result); + } + + [Test] + public void AnonymousObject_FromNullRaw() + { + var nullValue = InputExampleValue.Null(InputPrimitiveType.String); + + var result = ExampleValueExpressionBuilder.GetExpressionForAnonymousObject(nullValue); + // Should return Null keyword + Assert.IsNotNull(result); + } + + // ----------------------------------------------------------------------- + // Fallback for unknown framework types + // ----------------------------------------------------------------------- + + [Test] + public void UnknownValueType_ReturnsDefault() + { + // A value type that doesn't match any specific handler + var result = BuildExpression(typeof(DateTime), InputExampleValue.Null(InputPrimitiveType.String)); + Assert.IsInstanceOf(result); + } + + [Test] + public void UnknownReferenceType_ReturnsNull() + { + // A reference type that doesn't match any specific handler + var result = BuildExpression(typeof(System.Text.StringBuilder), InputExampleValue.Null(InputPrimitiveType.String)); + Assert.IsNotNull(result); + } + + // ----------------------------------------------------------------------- + // Helpers + // ----------------------------------------------------------------------- + + private static ValueExpression BuildExpression(Type frameworkType, InputExampleValue value) + { + return ExampleValueExpressionBuilder.GetExpression(new CSharpType(frameworkType), value); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs index f3131262a05..53b57a107a3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs @@ -6,3 +6,5 @@ [assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator.Input.Tests.Perf, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] [assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator.Input.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] [assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator.Tests.Common, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] +[assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] +[assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator.ClientModel, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")]