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 @@ -1251,7 +1251,6 @@ private void GenerateTypeClassInterface(BuilderModel model)
{
var className = DeriveClassName(model.TypeId);
var interfaceName = GetInterfaceName(className);
var hasMethods = HasChainableMethods(model);

WriteLine("// ============================================================================");
WriteLine($"// {interfaceName}");
Expand All @@ -1264,14 +1263,16 @@ private void GenerateTypeClassInterface(BuilderModel model)
var setters = model.Capabilities.Where(c => c.CapabilityKind == AtsCapabilityKind.PropertySetter).ToList();
var contextMethods = model.Capabilities.Where(c => c.CapabilityKind == AtsCapabilityKind.InstanceMethod).ToList();
var otherMethods = model.Capabilities.Where(c => c.CapabilityKind == AtsCapabilityKind.Method).ToList();
var standardMethods = contextMethods.Concat(otherMethods).ToList();
var hasMethods = standardMethods.Count > 0;

var properties = GroupPropertiesByName(getters, setters);
foreach (var prop in properties)
{
GenerateInterfaceProperty(prop.PropertyName, prop.Getter, prop.Setter);
}

foreach (var method in contextMethods.Concat(otherMethods))
foreach (var method in standardMethods)
{
GenerateTypeClassInterfaceMethod(className, method);
}
Expand All @@ -1286,7 +1287,7 @@ private void GenerateTypeClassInterface(BuilderModel model)

var promiseInterfaceName = GetPromiseInterfaceName(className);
WriteLine($"export interface {promiseInterfaceName} extends PromiseLike<{interfaceName}> {{");
foreach (var method in contextMethods.Concat(otherMethods))
foreach (var method in standardMethods)
{
GenerateTypeClassInterfaceMethod(className, method);
}
Expand Down Expand Up @@ -1518,7 +1519,8 @@ private void GenerateBuilderMethod(BuilderModel builder, AtsCapabilityInfo capab
private void GenerateArgsObjectWithConditionals(
string targetParamName,
List<AtsParameterInfo> requiredParams,
List<AtsParameterInfo> optionalParams)
List<AtsParameterInfo> optionalParams,
string indent = " ")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: I would prefer an indent level, and then repeated the indent chars by the level.

{
// Build the required args inline
var requiredArgs = new List<string> { $"{targetParamName}: this._handle" };
Expand All @@ -1527,13 +1529,13 @@ private void GenerateArgsObjectWithConditionals(
requiredArgs.Add(GetRpcArgumentEntry(param));
}

WriteLine($" const rpcArgs: Record<string, unknown> = {{ {string.Join(", ", requiredArgs)} }};");
WriteLine($"{indent}const rpcArgs: Record<string, unknown> = {{ {string.Join(", ", requiredArgs)} }};");

// Conditionally add optional params
foreach (var param in optionalParams)
{
var rpcExpression = GetRpcArgumentExpression(param);
WriteLine($" if ({param.Name} !== undefined) rpcArgs.{param.Name} = {rpcExpression};");
WriteLine($"{indent}if ({param.Name} !== undefined) rpcArgs.{param.Name} = {rpcExpression};");
}
}

Expand Down Expand Up @@ -1795,7 +1797,7 @@ private string GenerateCallbackTypeSignature(IReadOnlyList<AtsCallbackParameterI
return $"({paramsString}) => Promise<{returnType}>";
}

private void GenerateCallbackRegistration(AtsParameterInfo callbackParam)
private void GenerateCallbackRegistration(AtsParameterInfo callbackParam, string indent = " ")
{
var callbackParameters = callbackParam.CallbackParameters;
var isOptional = callbackParam.IsOptional || callbackParam.IsNullable;
Expand All @@ -1819,33 +1821,34 @@ private void GenerateCallbackRegistration(AtsParameterInfo callbackParam)
// For optional callbacks, wrap the registration in a conditional
if (isOptional)
{
WriteLine($" const {callbackName}Id = {callbackName} ? registerCallback(async ({paramSignature}) => {{");
WriteLine($"{indent}const {callbackName}Id = {callbackName} ? registerCallback(async ({paramSignature}) => {{");
}
else
{
WriteLine($" const {callbackName}Id = registerCallback(async ({paramSignature}) => {{");
WriteLine($"{indent}const {callbackName}Id = registerCallback(async ({paramSignature}) => {{");
}

// Generate the callback body
GenerateCallbackBody(callbackParam, callbackParameters);
GenerateCallbackBody(callbackParam, callbackParameters, indent);

// Close the callback registration
if (isOptional)
{
WriteLine(" }) : undefined;");
WriteLine(indent + "}) : undefined;");
}
else
{
WriteLine(" });");
WriteLine(indent + "});");
}
}

/// <summary>
/// Generates the body of a callback function.
/// </summary>
private void GenerateCallbackBody(AtsParameterInfo callbackParam, IReadOnlyList<AtsCallbackParameterInfo>? callbackParameters)
private void GenerateCallbackBody(AtsParameterInfo callbackParam, IReadOnlyList<AtsCallbackParameterInfo>? callbackParameters, string indent)
{
var callbackName = callbackParam.Name;
var bodyIndent = $"{indent} ";

// Check if callback has a return type - if so, we need to return the value
var hasReturnType = callbackParam.CallbackReturnType != null
Expand All @@ -1855,7 +1858,7 @@ private void GenerateCallbackBody(AtsParameterInfo callbackParam, IReadOnlyList<
if (callbackParameters is null || callbackParameters.Count == 0)
{
// No parameters - just call the callback
WriteLine($" {returnPrefix}await {callbackName}();");
WriteLine($"{bodyIndent}{returnPrefix}await {callbackName}();");
}
else if (callbackParameters.Count == 1)
{
Expand All @@ -1866,22 +1869,22 @@ private void GenerateCallbackBody(AtsParameterInfo callbackParam, IReadOnlyList<

if (cbTypeId == AtsConstants.CancellationToken)
{
WriteLine($" const {cbParam.Name} = CancellationToken.fromValue({cbParam.Name}Data);");
WriteLine($"{bodyIndent}const {cbParam.Name} = CancellationToken.fromValue({cbParam.Name}Data);");
}
else if (_wrapperClassNames.TryGetValue(cbTypeId, out var wrapperClassName))
{
// For types with wrapper classes, create an instance of the wrapper
var handleType = GetHandleTypeName(cbTypeId);
WriteLine($" const {cbParam.Name}Handle = wrapIfHandle({cbParam.Name}Data) as {handleType};");
WriteLine($" const {cbParam.Name} = new {GetImplementationClassName(wrapperClassName)}({cbParam.Name}Handle, this._client);");
WriteLine($"{bodyIndent}const {cbParam.Name}Handle = wrapIfHandle({cbParam.Name}Data) as {handleType};");
WriteLine($"{bodyIndent}const {cbParam.Name} = new {GetImplementationClassName(wrapperClassName)}({cbParam.Name}Handle, this._client);");
}
else
{
// For raw handle types, just wrap and cast
WriteLine($" const {cbParam.Name} = wrapIfHandle({cbParam.Name}Data) as {tsType};");
WriteLine($"{bodyIndent}const {cbParam.Name} = wrapIfHandle({cbParam.Name}Data) as {tsType};");
}

WriteLine($" {returnPrefix}await {callbackName}({cbParam.Name});");
WriteLine($"{bodyIndent}{returnPrefix}await {callbackName}({cbParam.Name});");
}
else
{
Expand All @@ -1895,24 +1898,24 @@ private void GenerateCallbackBody(AtsParameterInfo callbackParam, IReadOnlyList<

if (cbTypeId == AtsConstants.CancellationToken)
{
WriteLine($" const {cbParam.Name} = CancellationToken.fromValue(args.p{i});");
WriteLine($"{bodyIndent}const {cbParam.Name} = CancellationToken.fromValue({callbackArgName});");
}
else if (_wrapperClassNames.TryGetValue(cbTypeId, out var wrapperClassName))
{
// For types with wrapper classes, create an instance of the wrapper
var handleType = GetHandleTypeName(cbTypeId);
WriteLine($" const {cbParam.Name}Handle = wrapIfHandle({callbackArgName}) as {handleType};");
WriteLine($" const {cbParam.Name} = new {GetImplementationClassName(wrapperClassName)}({cbParam.Name}Handle, this._client);");
WriteLine($"{bodyIndent}const {cbParam.Name}Handle = wrapIfHandle({callbackArgName}) as {handleType};");
WriteLine($"{bodyIndent}const {cbParam.Name} = new {GetImplementationClassName(wrapperClassName)}({cbParam.Name}Handle, this._client);");
}
else
{
// For raw handle types, just wrap and cast
WriteLine($" const {cbParam.Name} = wrapIfHandle({callbackArgName}) as {tsType};");
WriteLine($"{bodyIndent}const {cbParam.Name} = wrapIfHandle({callbackArgName}) as {tsType};");
}
callArgs.Add(cbParam.Name);
}

WriteLine($" {returnPrefix}await {callbackName}({string.Join(", ", callArgs)});");
WriteLine($"{bodyIndent}{returnPrefix}await {callbackName}({string.Join(", ", callArgs)});");
}
}

Expand Down Expand Up @@ -2079,7 +2082,6 @@ private void GenerateTypeClass(BuilderModel model)
var handleType = GetHandleTypeName(model.TypeId);
var className = DeriveClassName(model.TypeId);
var implementationClassName = GetImplementationClassName(className);
var hasMethods = HasChainableMethods(model);

GenerateTypeClassInterface(model);

Expand All @@ -2093,9 +2095,8 @@ private void GenerateTypeClass(BuilderModel model)
var setters = model.Capabilities.Where(c => c.CapabilityKind == AtsCapabilityKind.PropertySetter).ToList();
var contextMethods = model.Capabilities.Where(c => c.CapabilityKind == AtsCapabilityKind.InstanceMethod).ToList();
var otherMethods = model.Capabilities.Where(c => c.CapabilityKind == AtsCapabilityKind.Method).ToList();

// Combine methods for thenable generation
var allMethods = contextMethods.Concat(otherMethods).ToList();
var hasMethods = allMethods.Count > 0;

WriteLine($"/**");
WriteLine($" * Type class for {className}.");
Expand Down
1 change: 1 addition & 0 deletions src/Aspire.Hosting/Ats/AtsTypeMappings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

// Core types (from Aspire.Hosting namespace)
[assembly: AspireExport(typeof(IDistributedApplicationBuilder))]
[assembly: AspireExport(typeof(IDistributedApplicationPipeline))]
[assembly: AspireExport(typeof(DistributedApplication))]

// Note: DistributedApplicationExecutionContext has [AspireExport(ExposeProperties = true)] on the type itself
Expand Down
39 changes: 39 additions & 0 deletions src/Aspire.Hosting/Ats/PipelineExports.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,45 @@ namespace Aspire.Hosting.Ats;
/// </summary>
internal static class PipelineExports
{
/// <summary>
/// Adds an application-level pipeline step in a TypeScript-friendly shape.
/// </summary>
/// <param name="pipeline">The distributed application pipeline.</param>
/// <param name="stepName">The unique name of the pipeline step.</param>
/// <param name="callback">The callback to execute when the step runs.</param>
/// <param name="dependsOn">Optional step names that this step depends on.</param>
/// <param name="requiredBy">Optional step names that require this step.</param>
[AspireExport(Description = "Adds a pipeline step to the application")]
public static void AddStep(
this global::Aspire.Hosting.Pipelines.IDistributedApplicationPipeline pipeline,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why reference the type like this?

string stepName,
Func<PipelineStepContext, Task> callback,
string[]? dependsOn = null,
string[]? requiredBy = null)
{
ArgumentNullException.ThrowIfNull(pipeline);
ArgumentException.ThrowIfNullOrEmpty(stepName);
ArgumentNullException.ThrowIfNull(callback);

pipeline.AddStep(stepName, callback, dependsOn, requiredBy);
}

/// <summary>
/// Registers a pipeline configuration callback in a TypeScript-friendly shape.
/// </summary>
/// <param name="pipeline">The distributed application pipeline.</param>
/// <param name="callback">The callback to execute during pipeline configuration.</param>
[AspireExport(Description = "Configures the application pipeline via a callback")]
public static void Configure(
this global::Aspire.Hosting.Pipelines.IDistributedApplicationPipeline pipeline,
Func<PipelineConfigurationContext, Task> callback)
{
ArgumentNullException.ThrowIfNull(pipeline);
ArgumentNullException.ThrowIfNull(callback);

pipeline.AddPipelineConfiguration(callback);
}

/// <summary>
/// Adds a key-value pair to the pipeline summary with a Markdown-formatted value.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal static class ManifestPublishingExtensions
/// </summary>
/// <param name="pipeline">The pipeline to add the manifest publishing step to.</param>
/// <returns>The pipeline for chaining.</returns>
[AspireExportIgnore(Reason = "Manifest publishing is an internal pipeline step and not part of the polyglot AppHost surface.")]
public static IDistributedApplicationPipeline AddManifestPublishing(this IDistributedApplicationPipeline pipeline)
{
var step = new PipelineStep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9332,6 +9332,18 @@ func (s *IDistributedApplicationBuilder) ExecutionContext() (*DistributedApplica
return result.(*DistributedApplicationExecutionContext), nil
}

// Pipeline gets the Pipeline property
func (s *IDistributedApplicationBuilder) Pipeline() (*IDistributedApplicationPipeline, error) {
reqArgs := map[string]any{
"context": SerializeValue(s.Handle()),
}
result, err := s.Client().InvokeCapability("Aspire.Hosting/IDistributedApplicationBuilder.pipeline", reqArgs)
if err != nil {
return nil, err
}
return result.(*IDistributedApplicationPipeline), nil
}

// UserSecretsManager gets the UserSecretsManager property
func (s *IDistributedApplicationBuilder) UserSecretsManager() (*IUserSecretsManager, error) {
reqArgs := map[string]any{
Expand Down Expand Up @@ -9593,6 +9605,49 @@ func (s *IDistributedApplicationEventing) Unsubscribe(subscription *DistributedA
return err
}

// IDistributedApplicationPipeline wraps a handle for Aspire.Hosting/Aspire.Hosting.Pipelines.IDistributedApplicationPipeline.
type IDistributedApplicationPipeline struct {
HandleWrapperBase
}

// NewIDistributedApplicationPipeline creates a new IDistributedApplicationPipeline.
func NewIDistributedApplicationPipeline(handle *Handle, client *AspireClient) *IDistributedApplicationPipeline {
return &IDistributedApplicationPipeline{
HandleWrapperBase: NewHandleWrapperBase(handle, client),
}
}

// AddStep adds a pipeline step to the application
func (s *IDistributedApplicationPipeline) AddStep(stepName string, callback func(...any) any, dependsOn []string, requiredBy []string) error {
reqArgs := map[string]any{
"pipeline": SerializeValue(s.Handle()),
}
reqArgs["stepName"] = SerializeValue(stepName)
if callback != nil {
reqArgs["callback"] = RegisterCallback(callback)
}
if dependsOn != nil {
reqArgs["dependsOn"] = SerializeValue(dependsOn)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pre-existing issue in the Go generator: callback is a required parameter (validated with ArgumentNullException.ThrowIfNull on the C# side), but the generated code wraps callback serialization in if callback != nil, silently omitting it from rpc args if nil is passed instead of failing. Same pattern appears in the Java addStepImpl generated code (if (callbackId != null)). Not blocking — just noting the generator emits a nil guard for a parameter that should never be nil.

}
if requiredBy != nil {
reqArgs["requiredBy"] = SerializeValue(requiredBy)
}
_, err := s.Client().InvokeCapability("Aspire.Hosting/addStep", reqArgs)
return err
}

// Configure configures the application pipeline via a callback
func (s *IDistributedApplicationPipeline) Configure(callback func(...any) any) error {
reqArgs := map[string]any{
"pipeline": SerializeValue(s.Handle()),
}
if callback != nil {
reqArgs["callback"] = RegisterCallback(callback)
}
_, err := s.Client().InvokeCapability("Aspire.Hosting/configure", reqArgs)
return err
}

// IDistributedApplicationResourceEvent wraps a handle for Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationResourceEvent.
type IDistributedApplicationResourceEvent struct {
HandleWrapperBase
Expand Down Expand Up @@ -18560,6 +18615,9 @@ func init() {
RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder", func(h *Handle, c *AspireClient) any {
return NewIDistributedApplicationBuilder(h, c)
})
RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.Pipelines.IDistributedApplicationPipeline", func(h *Handle, c *AspireClient) any {
return NewIDistributedApplicationPipeline(h, c)
})
RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.DistributedApplication", func(h *Handle, c *AspireClient) any {
return NewDistributedApplication(h, c)
})
Expand Down
Loading
Loading