Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -5,7 +5,6 @@ $failingSpecs = @(
Join-Path 'http' 'type' 'model' 'templated'
Join-Path 'http' 'type' 'file'
Join-Path 'http' 'client' 'naming' # pending until https://github.com/microsoft/typespec/issues/5653 is resolved
Join-Path 'http' 'streaming' 'jsonl'
Join-Path 'http' 'type' 'union' 'discriminated' # pending design
Join-Path 'http' 'authentication' 'noauth' 'union' # pending design
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,17 @@ private IReadOnlyList<ValueExpression> GetProtocolMethodArguments(Dictionary<str
if (ScmCodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(convenienceParam.Type, out var typeProvider) &&
typeProvider is ModelProvider paramModel)
{
AddArgument(protocolParam, paramModel.GetPropertyExpression(convenienceParam, propertySegments));
var propertyExpression = paramModel.GetPropertyExpression(convenienceParam, propertySegments);
// When the protocol parameter expects BinaryContent (i.e., it's a content parameter),
// wrap the resolved BinaryData property expression with BinaryContent.Create().
if (protocolParam.IsContentParameter)
{
AddArgument(protocolParam, RequestContentApiSnippets.Create(propertyExpression));
}
else
{
AddArgument(protocolParam, propertyExpression);
}
}
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,51 @@ public async Task MethodParameterSegments_UpdateMethod_SetsSegments()
Assert.AreEqual("param2", param.MethodParameterSegments[1].Name);
}

[Test]
public async Task MethodParameterSegments_BodyBinaryDataProperty_WrapsWithBinaryContentCreate()
{
// Test scenario: When a body parameter navigates to a BinaryData property via MethodParameterSegments,
// the convenience method should wrap the expression with BinaryContent.Create().
var streamModel = InputFactory.Model(
"StreamModel",
properties:
[
InputFactory.Property("body", InputPrimitiveType.Base64, isRequired: true),
]);

var bodyParam = InputFactory.BodyParameter("body", InputPrimitiveType.Base64, isRequired: true,
contentTypes: ["application/jsonl"], defaultContentType: "application/jsonl");
bodyParam.Update(methodParameterSegments: [
InputFactory.MethodParameter("stream", streamModel, isRequired: true, location: InputRequestLocation.None),
InputFactory.MethodParameter("body", InputPrimitiveType.Base64, isRequired: true)
]);

var serviceMethod = InputFactory.BasicServiceMethod(
"send",
InputFactory.Operation(
"send",
parameters: [bodyParam],
requestMediaTypes: ["application/jsonl"],
responses: [InputFactory.OperationResponse([204])]),
parameters: [InputFactory.MethodParameter("stream", streamModel, isRequired: true, location: InputRequestLocation.None)]);

var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]);
await MockHelpers.LoadMockGeneratorAsync(clients: () => [inputClient], inputModels: () => [streamModel]);

var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);
Assert.IsNotNull(client);

var methodCollection = new ScmMethodProviderCollection(serviceMethod, client!);
var convenienceMethod = methodCollection.FirstOrDefault(
m => m.Signature.Parameters.Any(p => p.Name == "stream") && m.Signature.Name == "Send");
Assert.IsNotNull(convenienceMethod);

var methodBody = convenienceMethod!.BodyStatements!.ToDisplayString();
// Verify that BinaryContent.Create() wraps the property access
Assert.IsTrue(methodBody.Contains("BinaryContent.Create(stream.Body)"),
$"Expected 'BinaryContent.Create(stream.Body)' in method body but got: {methodBody}");
}

[Test]
public async Task MissingOperationParamsResultInNamedArgsForSubsequent()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@
"commandName": "Executable",
"executablePath": "$(SolutionDir)/../dist/generator/Microsoft.TypeSpec.Generator.exe"
},
"http-streaming-jsonl": {
"commandLineArgs": "$(SolutionDir)/TestProjects/Spector/http/streaming/jsonl -g StubLibraryGenerator",
"commandName": "Executable",
"executablePath": "$(SolutionDir)/../dist/generator/Microsoft.TypeSpec.Generator.exe"
},
"http-type-array": {
"commandLineArgs": "$(SolutionDir)/TestProjects/Spector/http/type/array -g StubLibraryGenerator",
"commandName": "Executable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ public virtual ClientResult<RoundTripModel> NoContentType(Wrapper info, Cancella
{
Argument.AssertNotNull(info, nameof(info));

ClientResult result = NoContentType(info.P2, info.P1, info.Action, cancellationToken.ToRequestOptions());
ClientResult result = NoContentType(info.P2, info.P1, BinaryContent.Create(info.Action), cancellationToken.ToRequestOptions());
return ClientResult.FromValue((RoundTripModel)result, result.GetRawResponse());
}

Expand All @@ -347,7 +347,7 @@ public virtual async Task<ClientResult<RoundTripModel>> NoContentTypeAsync(Wrapp
{
Argument.AssertNotNull(info, nameof(info));

ClientResult result = await NoContentTypeAsync(info.P2, info.P1, info.Action, cancellationToken.ToRequestOptions()).ConfigureAwait(false);
ClientResult result = await NoContentTypeAsync(info.P2, info.P1, BinaryContent.Create(info.Action), cancellationToken.ToRequestOptions()).ConfigureAwait(false);
return ClientResult.FromValue((RoundTripModel)result, result.GetRawResponse());
}

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

using System;
using System.Threading.Tasks;
using NUnit.Framework;
using Streaming.Jsonl;

namespace TestProjects.Spector.Tests.Http.Streaming.Jsonl
{
public class JsonlTests : SpectorTestBase
{
private const string ExpectedJsonl = "{\"desc\": \"one\"}\n{\"desc\": \"two\"}\n{\"desc\": \"three\"}";

[SpectorTest]
public Task BasicSend() => Test(async (host) =>
{
var client = new JsonlClient(host, null).GetBasicClient();
var stream = StreamingJsonlModelFactory.JsonlStreamInfo(BinaryData.FromString(ExpectedJsonl));
var response = await client.SendAsync(stream);

Assert.AreEqual(204, response.GetRawResponse().Status);
});

[SpectorTest]
public Task BasicReceive() => Test(async (host) =>
{
var client = new JsonlClient(host, null).GetBasicClient();
var response = await client.ReceiveAsync();

Assert.AreEqual(200, response.GetRawResponse().Status);
BinaryDataAssert.AreEqual(BinaryData.FromString(ExpectedJsonl), response.Value);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\payload\xml\src\Payload.Xml.csproj" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\resiliency\srv-driven\v1\src\Resiliency.SrvDriven.V1.csproj" Aliases="SrvDrivenV1" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\resiliency\srv-driven\v2\src\Resiliency.SrvDriven.V2.csproj" Aliases="SrvDrivenV2" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\service\multiple-services\src\Service.MultipleServices.csproj" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\response\status-code-range\src\Response.StatusCodeRange.csproj" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\service\multiple-services\src\Service.MultipleServices.csproj" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\streaming\jsonl\src\Streaming.Jsonl.csproj" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\routes\src\Routes.csproj" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\serialization\encoded-name\json\src\Serialization.EncodedName.Json.csproj" />
<ProjectReference Include="$(RepoRoot)\TestProjects\Spector\http\server\endpoint\not-defined\src\Server.Endpoint.NotDefined.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"package-name": "Streaming.Jsonl"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project>
<ItemGroup>
<JsonSchemaSegment Include="$(MSBuildThisFileDirectory)..\..\ConfigurationSchema.json"
FilePathPattern="appsettings.*.json" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Solution>
<Project Path="src/Streaming.Jsonl.csproj" />
</Solution>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// <auto-generated/>

#nullable disable

using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Threading;
using System.Threading.Tasks;
using TypeSpec.Http.Streams;

namespace Streaming.Jsonl._Basic
{
public partial class Basic
{
protected Basic() => throw null;

internal Basic(ClientPipeline pipeline, Uri endpoint) => throw null;

public ClientPipeline Pipeline => throw null;

public virtual ClientResult Send(BinaryContent content, RequestOptions options = null) => throw null;

public virtual Task<ClientResult> SendAsync(BinaryContent content, RequestOptions options = null) => throw null;

public virtual ClientResult Send(JsonlStreamInfo stream, CancellationToken cancellationToken = default) => throw null;

public virtual Task<ClientResult> SendAsync(JsonlStreamInfo stream, CancellationToken cancellationToken = default) => throw null;

public virtual ClientResult Receive(RequestOptions options) => throw null;

public virtual Task<ClientResult> ReceiveAsync(RequestOptions options) => throw null;

public virtual ClientResult<BinaryData> Receive(CancellationToken cancellationToken = default) => throw null;

public virtual Task<ClientResult<BinaryData>> ReceiveAsync(CancellationToken cancellationToken = default) => throw null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// <auto-generated/>

#nullable disable

using System;
using System.ClientModel.Primitives;
using System.Diagnostics.CodeAnalysis;
using Streaming.Jsonl._Basic;

namespace Streaming.Jsonl
{
public partial class JsonlClient
{
public JsonlClient() : this(new Uri("http://localhost:3000"), new JsonlClientOptions()) => throw null;

internal JsonlClient(AuthenticationPolicy authenticationPolicy, Uri endpoint, JsonlClientOptions options) => throw null;

public JsonlClient(Uri endpoint, JsonlClientOptions options) : this(null, endpoint, options) => throw null;

[Experimental("SCME0002")]
public JsonlClient(JsonlClientSettings settings) : this(AuthenticationPolicy.Create(settings), settings?.Endpoint, settings?.Options) => throw null;

public ClientPipeline Pipeline => throw null;

public virtual Basic GetBasicClient() => throw null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// <auto-generated/>

#nullable disable

using System.ClientModel.Primitives;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Configuration;

namespace Streaming.Jsonl
{
public partial class JsonlClientOptions : ClientPipelineOptions
{
public JsonlClientOptions() => throw null;

[Experimental("SCME0002")]
internal JsonlClientOptions(IConfigurationSection section) : base(section) => throw null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// <auto-generated/>

#nullable disable

using System;
using System.ClientModel.Primitives;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Configuration;

namespace Streaming.Jsonl
{
[Experimental("SCME0002")]
public partial class JsonlClientSettings : ClientSettings
{
public Uri Endpoint
{
get => throw null;
set => throw null;
}

public JsonlClientOptions Options
{
get => throw null;
set => throw null;
}

protected override void BindCore(IConfigurationSection section) => throw null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// <auto-generated/>

#nullable disable

using System;
using System.ClientModel.Primitives;
using System.Text.Json;

namespace TypeSpec.Http.Streams
{
public partial class JsonlStreamInfo : IJsonModel<JsonlStreamInfo>
{
internal JsonlStreamInfo() => throw null;

protected virtual JsonlStreamInfo PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) => throw null;

protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) => throw null;

BinaryData IPersistableModel<JsonlStreamInfo>.Write(ModelReaderWriterOptions options) => throw null;

JsonlStreamInfo IPersistableModel<JsonlStreamInfo>.Create(BinaryData data, ModelReaderWriterOptions options) => throw null;

string IPersistableModel<JsonlStreamInfo>.GetFormatFromOptions(ModelReaderWriterOptions options) => throw null;

void IJsonModel<JsonlStreamInfo>.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) => throw null;

protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) => throw null;

JsonlStreamInfo IJsonModel<JsonlStreamInfo>.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => throw null;

protected virtual JsonlStreamInfo JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => throw null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// <auto-generated/>

#nullable disable

using System;

namespace TypeSpec.Http.Streams
{
public partial class JsonlStreamInfo
{
public BinaryData Body => throw null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically what you have is not incorrect. I did a quick glance at what jsonl and what it's use cases and it seems that one of the biggest uses is the ability to stream a file and serialize + deserialize each line as a JSON object. Consider the scenario where we have a very large file, where each line is a different json object. If we use BinaryData, we would need to store the entire file content into memory. I think we would need to design a solution where we can stream the payload line by line, that way not the entire file contents is stored into memory.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// <auto-generated/>

#nullable disable

using System.ClientModel.Primitives;
using Streaming.Jsonl._Basic;
using TypeSpec.Http.Streams;

namespace Streaming.Jsonl
{
[ModelReaderWriterBuildable(typeof(JsonlStreamInfo))]
public partial class StreamingJsonlContext : ModelReaderWriterContext
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// <auto-generated/>

#nullable disable

using System;
using Streaming.Jsonl._Basic;
using TypeSpec.Http.Streams;

namespace Streaming.Jsonl
{
public static partial class StreamingJsonlModelFactory
{

public static JsonlStreamInfo JsonlStreamInfo(BinaryData body = default) => throw null;
}
}
Loading
Loading