Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b4f5cd7
minor: added long path support to prerequisite
radhgupta Oct 22, 2025
ccef568
Merge branch 'main' of https://github.com/microsoft/typespec
radhgupta Oct 23, 2025
3486047
Merge branch 'main' of https://github.com/microsoft/typespec
radhgupta Oct 31, 2025
25e823a
Merge branch 'main' of https://github.com/microsoft/typespec
radhgupta Nov 4, 2025
7958158
Merge branch 'main' of https://github.com/microsoft/typespec
radhgupta Nov 13, 2025
5227fce
Merge branch 'main' of https://github.com/microsoft/typespec
radhgupta Nov 13, 2025
175f199
Merge remote-tracking branch 'upstream/main'
radhgupta Nov 14, 2025
b39a7f4
Merge branch 'main' of https://github.com/microsoft/typespec
radhgupta Dec 1, 2025
699978e
Merge branch 'main' of https://github.com/microsoft/typespec
radhgupta Dec 15, 2025
d0909f3
Merge remote-tracking branch 'upstream/main'
radhgupta Jan 6, 2026
17ae707
Merge remote-tracking branch 'upstream/main'
radhgupta Jan 7, 2026
339819f
Merge remote-tracking branch 'upstream/main'
radhgupta Jan 13, 2026
9cb6c4c
Merge remote-tracking branch 'upstream/main'
radhgupta Jan 21, 2026
6606677
csv encoding
radhgupta Jan 21, 2026
fbbfaf5
geenrate script
radhgupta Jan 22, 2026
4be7888
serealization format used
radhgupta Jan 26, 2026
b2757da
Refactoring
radhgupta Jan 27, 2026
434bb4a
refactoring
radhgupta Jan 27, 2026
1e737ab
Merge branch 'main' into csv-encoding
radhgupta Jan 27, 2026
4013b4a
added unit tests
radhgupta Jan 29, 2026
1ca3af7
Merge branch 'csv-encoding' of https://github.com/radhgupta/typespec …
radhgupta Jan 29, 2026
759da07
refactored code
radhgupta Jan 30, 2026
38659d3
added unit test
radhgupta Jan 30, 2026
7d6c272
used input convert to convert form str to enum
radhgupta Jan 30, 2026
e64270a
removed tryparse
radhgupta Jan 30, 2026
756378c
removed tryparse
radhgupta Jan 30, 2026
31225bd
removed tryparse
radhgupta Jan 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ function fromSdkModelProperty(
serializationOptions: sdkProperty.serializationOptions,
// A property is defined to be metadata if it is marked `@header`, `@cookie`, `@query`, `@path`.
isHttpMetadata: isHttpMetadata(sdkContext, sdkProperty),
encode: sdkProperty.encode,
} as InputModelProperty;

if (property) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export interface InputModelProperty extends InputPropertyTypeBase {
serializationOptions: SerializationOptions;
flatten: boolean;
isHttpMetadata: boolean;
encode?: string;
}

export type InputProperty = InputModelProperty | InputParameter;
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1259,5 +1259,75 @@ public void TestSerializationTypeNameMatchesModelProviderName()
Assert.AreEqual($"Deserialize{model.Name}", deserializationMethod.Signature.Name,
"Deserialization method name should use ModelProvider name");
}

[TestCase("commaDelimited", ",")]
[TestCase("spaceDelimited", " ")]
[TestCase("pipeDelimited", "|")]
[TestCase("newlineDelimited", "\\n")]
public void TestArrayEncodingSerializationStatement(string encoding, string expectedDelimiter)
{
Enum.TryParse<ArrayKnownEncoding>(encoding, ignoreCase: true, out var arrayEncoding);
var arrayType = new InputArrayType("TestArray", "TypeSpec.Array", InputPrimitiveType.String);
var arrayProperty = new InputModelProperty(
"TestArray",
"Test array property summary",
"Test array property",
arrayType,
true,
false,
null,
false,
"testArray",
false,
false,
null,
new(json: new("testArray")),
arrayEncoding);

var properties = new List<InputModelProperty> { arrayProperty };
var inputModel = new InputModelType("TestModel", "TestNamespace", "TestModel", "public", null, null, "Test model.", InputModelTypeUsage.Input, properties, null, Array.Empty<InputModelType>(), null, null, new Dictionary<string, InputModelType>(), null, false, new(), false);

var (_, serialization) = CreateModelAndSerialization(inputModel);
var writeMethod = serialization.BuildJsonModelWriteCoreMethod();
var methodBody = writeMethod.BodyStatements!.ToDisplayString();

Assert.IsTrue(methodBody.Contains($"string.Join(\"{expectedDelimiter}\", TestArray)"),
$"Expected serialization to use string.Join with delimiter '{expectedDelimiter}', but got: {methodBody}");
}

[TestCase("commaDelimited", ",")]
[TestCase("spaceDelimited", " ")]
[TestCase("pipeDelimited", "|")]
[TestCase("newlineDelimited", "\\n")]
public void TestArrayEncodingDeserializationStatement(string encoding, string expectedDelimiter)
{
Enum.TryParse<ArrayKnownEncoding>(encoding, ignoreCase: true, out var arrayEncoding);
var arrayType = new InputArrayType("TestArray", "TypeSpec.Array", InputPrimitiveType.String);
var arrayProperty = new InputModelProperty(
"TestArray",
"Test array property summary",
"Test array property",
arrayType,
true,
false,
null,
false,
"testArray",
false,
false,
null,
new(json: new("testArray")),
arrayEncoding);

var properties = new List<InputModelProperty> { arrayProperty };
var inputModel = new InputModelType("TestModel", "TestNamespace", "TestModel", "public", null, null, "Test model.", InputModelTypeUsage.Input, properties, null, Array.Empty<InputModelType>(), null, null, new Dictionary<string, InputModelType>(), null, false, new(), false);

var (_, serialization) = CreateModelAndSerialization(inputModel);
var deserializeMethod = serialization.BuildDeserializationMethod();
var methodBody = deserializeMethod.BodyStatements!.ToDisplayString();

Assert.IsTrue(methodBody.Contains($".Split('{expectedDelimiter}')") || methodBody.Contains($".Split(\"{expectedDelimiter}\")"),
$"Expected deserialization to use Split with delimiter '{expectedDelimiter}', but got: {methodBody}");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;

namespace Microsoft.TypeSpec.Generator.Input.Extensions
{
public static class ArrayKnownEncodingExtensions
{
/// <summary>
/// Converts ArrayKnownEncoding to SerializationFormat.
/// </summary>
public static SerializationFormat ToSerializationFormat(this ArrayKnownEncoding encoding)
{
return encoding switch
{
ArrayKnownEncoding.CommaDelimited => SerializationFormat.Array_CommaDelimited,
ArrayKnownEncoding.SpaceDelimited => SerializationFormat.Array_SpaceDelimited,
ArrayKnownEncoding.PipeDelimited => SerializationFormat.Array_PipeDelimited,
ArrayKnownEncoding.NewlineDelimited => SerializationFormat.Array_NewlineDelimited,
_ => throw new ArgumentOutOfRangeException(nameof(encoding), encoding, "Unknown array encoding")
};
}

/// <summary>
/// Get the delimiter string for array serialization format.
/// </summary>
public static bool TryGetDelimiter(SerializationFormat format, out string? delimiter)
{
delimiter = format switch
{
SerializationFormat.Array_CommaDelimited => ",",
SerializationFormat.Array_SpaceDelimited => " ",
SerializationFormat.Array_PipeDelimited => "|",
SerializationFormat.Array_NewlineDelimited => "\n",
_ => null
};
return delimiter != null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.TypeSpec.Generator.Input
{
/// <summary>
/// Represents the known encoding formats for arrays.
/// </summary>
public enum ArrayKnownEncoding
{
/// <summary>
/// Comma-delimited array encoding
/// </summary>
CommaDelimited,

/// <summary>
/// Space-delimited array encoding
/// </summary>
SpaceDelimited,

/// <summary>
/// Pipe-delimited array encoding
/// </summary>
PipeDelimited,

/// <summary>
/// Newline-delimited array encoding
/// </summary>
NewlineDelimited
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.TypeSpec.Generator.Input.Extensions;

namespace Microsoft.TypeSpec.Generator.Input
{
public class InputModelProperty : InputProperty
Expand All @@ -18,7 +20,8 @@ public InputModelProperty(
bool isHttpMetadata,
bool isApiVersion,
InputConstant? defaultValue,
InputSerializationOptions serializationOptions)
InputSerializationOptions serializationOptions,
ArrayKnownEncoding? encode = null)
: base(name, summary, doc, type, isRequired, isReadOnly, access, serializedName, isApiVersion, defaultValue)
{
Name = name;
Expand All @@ -30,11 +33,13 @@ public InputModelProperty(
IsDiscriminator = isDiscriminator;
IsHttpMetadata = isHttpMetadata;
SerializationOptions = serializationOptions;
Encode = encode;
}

public bool IsDiscriminator { get; internal set; }
public InputSerializationOptions? SerializationOptions { get; internal set; }
public bool IsHttpMetadata { get; internal set; }
public ArrayKnownEncoding? Encode { get; internal set; }

/// <summary>
/// Updates the properties of the input model property.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ internal static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader rea
isHttpMetadata: false,
isApiVersion: false,
defaultValue: null,
serializationOptions: null!);
serializationOptions: null!,
encode: null);
resolver.AddReference(id, property);

string? kind = null;
Expand All @@ -63,6 +64,7 @@ internal static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader rea
bool isDiscriminator = false;
IReadOnlyList<InputDecoratorInfo>? decorators = null;
InputSerializationOptions? serializationOptions = null;
string? encodeString = null;

while (reader.TokenType != JsonTokenType.EndObject)
{
Expand All @@ -81,7 +83,8 @@ internal static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader rea
|| reader.TryReadString("serializedName", ref serializedName)
|| reader.TryReadBoolean("isApiVersion", ref isApiVersion)
|| reader.TryReadComplexType("defaultValue", options, ref defaultValue)
|| reader.TryReadComplexType("serializationOptions", options, ref serializationOptions);
|| reader.TryReadComplexType("serializationOptions", options, ref serializationOptions)
|| reader.TryReadString("encode", ref encodeString);

if (!isKnownProperty)
{
Expand All @@ -103,6 +106,7 @@ internal static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader rea
property.SerializedName = serializedName ?? serializationOptions?.Json?.Name ?? name;
property.IsApiVersion = isApiVersion;
property.DefaultValue = defaultValue;
property.Encode = Enum.TryParse<ArrayKnownEncoding>(encodeString, ignoreCase: true, out var encode) ? encode : null;

return property;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,9 @@ public enum SerializationFormat
Bytes_Base64Url,
Bytes_Base64,
Int_String,
Array_CommaDelimited,
Array_SpaceDelimited,
Array_PipeDelimited,
Array_NewlineDelimited,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class PropertyProvider
public FieldProvider? BackingField { get; set; }
public PropertyProvider? BaseProperty { get; set; }
public bool IsRef { get; private set; }
public SerializationFormat SerializationFormat => _serializationFormat;

/// <summary>
/// Converts this property to a parameter.
Expand Down Expand Up @@ -91,7 +92,7 @@ private PropertyProvider(InputProperty inputProperty, CSharpType propertyType, T
}

EnclosingType = enclosingType;
_serializationFormat = CodeModelGenerator.Instance.TypeFactory.GetSerializationFormat(inputProperty.Type);
_serializationFormat = GetSerializationFormat(inputProperty);
_isRequiredNonNullableConstant = inputProperty.IsRequired && propertyType is { IsLiteral: true, IsNullable: false };
var propHasSetter = PropertyHasSetter(propertyType, inputProperty);
MethodSignatureModifiers setterModifier = propHasSetter ? MethodSignatureModifiers.Public : MethodSignatureModifiers.None;
Expand Down Expand Up @@ -334,5 +335,21 @@ public void Update(
BuildDocs();
}
}

private SerializationFormat GetSerializationFormat(InputProperty inputProperty)
{
// Handle array encoding from InputModelProperty
if (inputProperty is InputModelProperty modelProperty &&
inputProperty.Type is InputArrayType)
{
var arrayEncoding = modelProperty.Encode;
if (arrayEncoding.HasValue)
{
return arrayEncoding.Value.ToSerializationFormat();
}
}

return CodeModelGenerator.Instance.TypeFactory.GetSerializationFormat(inputProperty.Type);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ public static ScopedApi<string> Format(ScopedApi<string> format, params ValueExp
public static ScopedApi<bool> IsNullOrWhiteSpace(ScopedApi<string> value, params ValueExpression[] args)
=> Static<string>().Invoke(nameof(string.IsNullOrWhiteSpace), args.Prepend(value).ToArray()).As<bool>();

public static ScopedApi<bool> IsNullOrEmpty(ScopedApi<string> value)
=> Static<string>().Invoke(nameof(string.IsNullOrEmpty), [value]).As<bool>();

public static ScopedApi<string> Join(ValueExpression separator, ValueExpression values)
=> Static<string>().Invoke(nameof(string.Join), [separator, values]).As<string>();

public static ValueExpression Split(this ScopedApi<string> stringExpression, ValueExpression separator)
=> stringExpression.Invoke(nameof(string.Split), [separator], null, false);

public static ScopedApi<string> Substring(this ScopedApi<string> stringExpression, ValueExpression startIndex)
=> stringExpression.Invoke(nameof(string.Substring), [startIndex], null, false).As<string>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.ClientModel;
using System.Collections.Generic;
using System.Threading.Tasks;
using Encode._Array;
using Encode._Array._Property;
using NUnit.Framework;

namespace TestProjects.Spector.Tests.Http.Encode.Array
{
public class EncodeArrayTests : SpectorTestBase
{
[SpectorTest]
public Task CommaDelimited() => Test(async (host) =>
{
var testData = new List<string> { "blue", "red", "green" };
var body = new CommaDelimitedArrayProperty(testData);

ClientResult<CommaDelimitedArrayProperty> result = await new ArrayClient(host, null).GetPropertyClient().CommaDelimitedAsync(body);
Assert.AreEqual(200, result.GetRawResponse().Status);
Assert.AreEqual(testData, result.Value.Value);
});

[SpectorTest]
public Task SpaceDelimited() => Test(async (host) =>
{
var testData = new List<string> { "blue", "red", "green" };
var body = new SpaceDelimitedArrayProperty(testData);

ClientResult<SpaceDelimitedArrayProperty> result = await new ArrayClient(host, null).GetPropertyClient().SpaceDelimitedAsync(body);
Assert.AreEqual(200, result.GetRawResponse().Status);
Assert.AreEqual(testData, result.Value.Value);
});

[SpectorTest]
public Task PipeDelimited() => Test(async (host) =>
{
var testData = new List<string> { "blue", "red", "green" };
var body = new PipeDelimitedArrayProperty(testData);

ClientResult<PipeDelimitedArrayProperty> result = await new ArrayClient(host, null).GetPropertyClient().PipeDelimitedAsync(body);
Assert.AreEqual(200, result.GetRawResponse().Status);
Assert.AreEqual(testData, result.Value.Value);
});

[SpectorTest]
public Task NewlineDelimited() => Test(async (host) =>
{
var testData = new List<string> { "blue", "red", "green" };
var body = new NewlineDelimitedArrayProperty(testData);

ClientResult<NewlineDelimitedArrayProperty> result = await new ArrayClient(host, null).GetPropertyClient().NewlineDelimitedAsync(body);
Assert.AreEqual(200, result.GetRawResponse().Status);
Assert.AreEqual(testData, result.Value.Value);
});


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\client\structure\multi-client\src\Client.Structure.Service.Multi.Client.csproj" Aliases="ClientStructureMultiClient" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\client\structure\renamed-operation\src\Client.Structure.Service.Renamed.Operation.csproj" Aliases="ClientStructureRenamedOperation" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\client\structure\two-operation-group\src\Client.Structure.Service.TwoOperationGroup.csproj" Aliases="ClientStructureTwoOperationGroup" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\encode\array\src\Encode.Array.csproj" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\documentation\src\Documentation.csproj" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\encode\bytes\src\Encode.Bytes.csproj" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\encode\datetime\src\Encode.Datetime.csproj" />
Expand Down
Loading