Skip to content
Merged
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
18 changes: 6 additions & 12 deletions advanced/samples/dotnet/wit/stream-service/Models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,19 +287,17 @@ public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTrans

}

[Temporalio.Converters.TemporalTransferTypeConverter(typeof(CreateStreamResponse.TransferTypeConverter))]
[Temporalio.Converters.TemporalTransferTypeConverter(typeof(CreateStreamResult.TransferTypeConverter))]
[GeneratedCode("nexgen", null)]
public record CreateStreamResponse
public record CreateStreamResult
{
public StreamRef? Stream { get; init; }
public bool? Created { get; init; }

internal static CreateStreamResponse FromTransferType(StreamSample.V1.CreateStreamResponse wire)
internal static CreateStreamResult FromTransferType(StreamSample.V1.CreateStreamResponse wire)
{
return new CreateStreamResponse()
return new CreateStreamResult()
{
Stream = wire.Stream == null ? null : StreamRef.FromTransferType(wire.Stream),
Created = wire.Created == false ? null : wire.Created,
};
}

Expand All @@ -310,10 +308,6 @@ internal StreamSample.V1.CreateStreamResponse ToTransferType()
{
proto.Stream = (StreamSample.V1.StreamRef)stream.ToTransferType();
}
if (Created is { } created)
{
proto.Created = created;
}
return proto;
}

Expand All @@ -326,10 +320,10 @@ public sealed class TransferTypeConverter : Temporalio.Converters.ITemporalTrans
public System.Type TransferType => typeof(StreamSample.V1.CreateStreamResponse);

/// <summary>Converts a model value to its transfer type.</summary>
public object? ToTransferType(object? value) => value is null ? null : ((CreateStreamResponse)value).ToTransferType();
public object? ToTransferType(object? value) => value is null ? null : ((CreateStreamResult)value).ToTransferType();

/// <summary>Converts a transfer-type value to this model.</summary>
public object? FromTransferType(object? transferType) => transferType is null ? null : CreateStreamResponse.FromTransferType((StreamSample.V1.CreateStreamResponse)transferType);
public object? FromTransferType(object? transferType) => transferType is null ? null : CreateStreamResult.FromTransferType((StreamSample.V1.CreateStreamResponse)transferType);
}

}
Expand Down
8 changes: 4 additions & 4 deletions advanced/samples/dotnet/wit/stream-service/Operations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,16 @@ public static class Operations
private const string StreamServiceEndpoint = "streams";

[GeneratedCode("nexgen", null)]
private static async Task<CreateStreamResponse> CreateStreamAsync(CreateStreamRequest request)
private static async Task<Stream> CreateStreamAsync(CreateStreamRequest request)
{
var client = Workflow.CreateNexusWorkflowClient<IStreamService>(StreamServiceEndpoint);
var result = await client.ExecuteNexusOperationAsync<CreateStreamResponse>(svc => svc.CreateStream(request)).ConfigureAwait(true);
return result;
var result = await client.ExecuteNexusOperationAsync<CreateStreamResult>(svc => svc.CreateStream(request)).ConfigureAwait(true);
return new Stream((result.Stream ?? throw new System.InvalidOperationException("CreateStream answered without `stream`")));
}

/// <param name="options">Options for the operation.</param>
[GeneratedCode("nexgen", null)]
public static Task<CreateStreamResponse> CreateStreamAsync(CreateStreamOptions? options = null)
public static Task<Stream> CreateStreamAsync(CreateStreamOptions? options = null)
{
var request = new CreateStreamRequest()
{
Expand Down
4 changes: 2 additions & 2 deletions advanced/samples/dotnet/wit/stream-service/Services.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal interface IStreamService
{
[GeneratedCode("nexgen", null)]
[NexusOperation("CreateStream")]
CreateStreamResponse CreateStream(CreateStreamRequest request);
CreateStreamResult CreateStream(CreateStreamRequest request);

[GeneratedCode("nexgen", null)]
[NexusOperation("AppendRecords")]
Expand Down Expand Up @@ -71,7 +71,7 @@ internal static class NexgenOperationRegistry

internal static IReadOnlyDictionary<(string Service, string Operation), INexgenOperationInfo> Operations { get; } = new Dictionary<(string Service, string Operation), INexgenOperationInfo>
{
[("streamsample.v1.StreamService", "CreateStream")] = new NexgenOperationInfo<CreateStreamRequest, CreateStreamResponse>(
[("streamsample.v1.StreamService", "CreateStream")] = new NexgenOperationInfo<CreateStreamRequest, CreateStreamResult>(
StreamServiceServiceDefinition.Operations["CreateStream"]),
[("streamsample.v1.StreamService", "AppendRecords")] = new NexgenOperationInfo<AppendRecordsRequest, AppendRecordsResponse>(
StreamServiceServiceDefinition.Operations["AppendRecords"]),
Expand Down
12 changes: 5 additions & 7 deletions advanced/samples/inputs/stream-service.wit
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ interface stream-service {
namespace: placeholder,
}

/// @nexus.proto "streamsample.v1.CreateStreamResponse" typescript-import="#streamsample"
record create-stream-response {
%stream: option<stream-ref>,
created: option<bool>,
}

/// @nexus.proto "streamsample.v1.AppendRecordsRequest" typescript-import="#streamsample"
record append-records-request {
%stream: option<stream-ref>,
Expand Down Expand Up @@ -124,8 +118,12 @@ interface stream-service {
describe: func() -> describe-stream-response;
}

// The stream binds from the stream field of the response.
/// @nexus.proto "streamsample.v1.CreateStreamResponse" typescript-import="#streamsample"
type create-stream-result = own<%stream>;

/// @nexus.operation name="CreateStream"
create-stream: func(request: create-stream-request) -> create-stream-response;
create-stream: func(request: create-stream-request) -> create-stream-result;

/// @nexus.operation name="AppendRecords"
append-records: func(request: append-records-request) -> append-records-response;
Expand Down
22 changes: 9 additions & 13 deletions advanced/samples/python/wit/stream_service/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,13 @@ def to_transfer_type(


@dataclasses.dataclass(slots=True)
class CreateStreamResponse:
class CreateStreamResult:
stream: StreamRef | None = None
created: bool | None = None


class _CreateStreamResponseTransferTypeConverter(
class _CreateStreamResultTransferTypeConverter(
temporalio.converter.TransferTypeConverter[
CreateStreamResponse, streamsample.v1.stream_service_pb2.CreateStreamResponse
CreateStreamResult, streamsample.v1.stream_service_pb2.CreateStreamResponse
]
):
transfer_type: (
Expand All @@ -131,35 +130,32 @@ class _CreateStreamResponseTransferTypeConverter(
def from_transfer_type(
self,
value: streamsample.v1.stream_service_pb2.CreateStreamResponse,
type_hint: type[CreateStreamResponse],
) -> CreateStreamResponse:
return CreateStreamResponse(
type_hint: type[CreateStreamResult],
) -> CreateStreamResult:
return CreateStreamResult(
stream=_StreamRefTransferTypeConverter().from_transfer_type(
value.stream, StreamRef
)
if value.HasField("stream")
else None,
created=value.created if bool(value.created) else None,
)

@typing_extensions.override
def to_transfer_type(
self,
value: CreateStreamResponse,
value: CreateStreamResult,
) -> streamsample.v1.stream_service_pb2.CreateStreamResponse:
message = streamsample.v1.stream_service_pb2.CreateStreamResponse()
if value.stream is not None:
message.stream.CopyFrom(
_StreamRefTransferTypeConverter().to_transfer_type(value.stream)
)
if value.created is not None:
message.created = value.created
return message


temporalio.converter.transfer_type_convertible(
_CreateStreamResponseTransferTypeConverter
)(CreateStreamResponse) # pyright: ignore[reportUnusedCallResult]
_CreateStreamResultTransferTypeConverter
)(CreateStreamResult) # pyright: ignore[reportUnusedCallResult]


@dataclasses.dataclass(slots=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,37 @@

from ..models import (
CreateStreamRequest,
CreateStreamResponse,
CreateStreamResult,
StreamRef,
)
from .._resources import Stream


async def _create_stream(
request: CreateStreamRequest,
) -> temporalio.workflow.NexusOperationHandle[CreateStreamResponse,]:
) -> Stream:
nexus_client = temporalio.workflow.create_nexus_client(
service="streamsample.v1.StreamService",
endpoint="streams",
)
return await nexus_client.start_operation(
handle = await nexus_client.start_operation(
operation="CreateStream",
input=request,
output_type=CreateStreamResponse,
output_type=CreateStreamResult,
)
result = await handle
if result.stream is None:
raise RuntimeError("CreateStream answered without `stream`")
return Stream(
stream=result.stream,
)


async def create_stream(
*,
ref: StreamRef | None = None,
retention: datetime.timedelta | None = None,
) -> temporalio.workflow.NexusOperationHandle[CreateStreamResponse,]:
) -> Stream:
request = CreateStreamRequest(
ref=ref,
retention=retention,
Expand Down
4 changes: 2 additions & 2 deletions advanced/samples/python/wit/stream_service/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
CloseStreamRequest,
CloseStreamResponse,
CreateStreamRequest,
CreateStreamResponse,
CreateStreamResult,
DescribeStreamRequest,
DescribeStreamResponse,
ReadRecordsRequest,
Expand All @@ -21,7 +21,7 @@
class StreamService:
create_stream: Operation[
CreateStreamRequest,
CreateStreamResponse,
CreateStreamResult,
] = Operation(name="CreateStream")

append_records: Operation[
Expand Down
23 changes: 10 additions & 13 deletions advanced/samples/typescript/wit/stream-service/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,33 @@ function requiredField<T>(
return value;
}

export interface CreateStreamResponse {
export interface CreateStreamResult {
stream?: StreamRef;
created?: boolean;
}

function createStreamResponseFromProto(
function createStreamResultFromProto(
proto: streamsample.v1.ICreateStreamResponse,
): CreateStreamResponse {
): CreateStreamResult {
return {
stream: proto.stream == null ? undefined : streamRefFromProto(proto.stream),
created: proto.created ?? undefined,
};
}

function createStreamResponseToProto(
model: CreateStreamResponse,
function createStreamResultToProto(
model: CreateStreamResult,
): streamsample.v1.ICreateStreamResponse {
return {
stream: model.stream == null ? undefined : streamRefToProto(model.stream),
created: model.created,
};
}

export const createStreamResponseTransferTypeConverter = {
fromTransferType(value: streamsample.v1.ICreateStreamResponse): CreateStreamResponse {
return createStreamResponseFromProto(value);
export const createStreamResultTransferTypeConverter = {
fromTransferType(value: streamsample.v1.ICreateStreamResponse): CreateStreamResult {
return createStreamResultFromProto(value);
},

toTransferType(value: CreateStreamResponse): streamsample.v1.ICreateStreamResponse {
return createStreamResponseToProto(value);
toTransferType(value: CreateStreamResult): streamsample.v1.ICreateStreamResponse {
return createStreamResultToProto(value);
},
};
export interface StreamRef {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,32 @@

import * as workflow from "@temporalio/workflow";
import { streamService } from "../services";
import type { CreateStreamResponse, CreateStreamRequest } from "../models";
import type { CreateStreamRequest } from "../models";
import { Stream } from "../resources";

function requiredField<T>(
value: T | null | undefined,
owner: string,
field: string,
): T {
if (value == null) {
throw new Error(`missing required field ${owner}.${field}`);
}
return value;
}

/**
* @param request - Request for the operation.
*/
export async function createStream(
request: CreateStreamRequest,
): Promise<workflow.NexusOperationHandle<CreateStreamResponse>> {
export async function createStream(request: CreateStreamRequest): Promise<Stream> {
const client = workflow.createNexusServiceClient({
service: streamService,
endpoint: "streams",
});
return await client.startOperation(streamService.operations.createStream, request);
const handle = await client.startOperation(
streamService.operations.createStream,
request,
);
const result = await handle.result();
return new Stream(requiredField(result.stream, "resource", "stream"));
}
8 changes: 4 additions & 4 deletions advanced/samples/typescript/wit/stream-service/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as nexus from "nexus-rpc";
import {
createStreamResponseTransferTypeConverter,
createStreamResultTransferTypeConverter,
createStreamRequestTransferTypeConverter,
appendRecordsResponseTransferTypeConverter,
appendRecordsRequestTransferTypeConverter,
Expand All @@ -14,7 +14,7 @@ import {
describeStreamRequestTransferTypeConverter,
} from "./models";
import type {
CreateStreamResponse,
CreateStreamResult,
CreateStreamRequest,
AppendRecordsResponse,
AppendRecordsRequest,
Expand All @@ -27,10 +27,10 @@ import type {
} from "./models";

export const streamService = nexus.service("streamsample.v1.StreamService", {
createStream: nexus.operation<CreateStreamRequest, CreateStreamResponse>({
createStream: nexus.operation<CreateStreamRequest, CreateStreamResult>({
name: "CreateStream",
inputType: { transferTypeConverter: createStreamRequestTransferTypeConverter },
outputType: { transferTypeConverter: createStreamResponseTransferTypeConverter },
outputType: { transferTypeConverter: createStreamResultTransferTypeConverter },
}),
appendRecords: nexus.operation<AppendRecordsRequest, AppendRecordsResponse>({
name: "AppendRecords",
Expand Down
1 change: 1 addition & 0 deletions changelog/fixed/required-reindeer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A Python or .NET operation that returns a resource checks each required resource field that the response fills in, and raises a clear error when the response leaves it empty.
21 changes: 20 additions & 1 deletion src/generator/dotnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3654,7 +3654,26 @@ fn operation_transform_expression(
.bindings
.iter()
.map(|binding| {
resource_return_binding_expr(resource, binding, request_expr, result_expr, api_plan)
let expr = resource_return_binding_expr(
resource,
binding,
request_expr,
result_expr,
api_plan,
);
// A response model field is always nullable, so a required one is checked.
match &binding.source {
ResolvedResourceBindingSource::ResultField {
proto_field_name, ..
} if !binding.optional => format!(
"({expr} ?? throw new System.InvalidOperationException({}))",
csharp_string_literal(&format!(
"{} answered without `{proto_field_name}`",
operation.wire_name
))
),
_ => expr,
}
})
.collect::<Vec<_>>();
return format!(
Expand Down
Loading
Loading