diff --git a/.github/workflows/polyglot-validation.yml b/.github/workflows/polyglot-validation.yml new file mode 100644 index 00000000000..c2a258559d5 --- /dev/null +++ b/.github/workflows/polyglot-validation.yml @@ -0,0 +1,176 @@ +# Polyglot SDK Validation Tests (Reusable) +# Validates Python, Go, Java, Rust, and TypeScript AppHost SDKs with Redis integration +# Uses Dockerfiles from .github/workflows/polyglot-validation/ for reproducible environments +# Uses dogfood script to install CLI and NuGet packages from the current PR +name: Polyglot SDK Validation + +on: + workflow_call: + inputs: + versionOverrideArg: + required: false + type: string + +jobs: + validate_python: + name: Python SDK Validation + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Build Python validation image + run: | + docker build \ + -f .github/workflows/polyglot-validation/Dockerfile.python \ + -t polyglot-python \ + .github/workflows/polyglot-validation/ + + - name: Run Python SDK validation + env: + GH_TOKEN: ${{ github.token }} + run: | + docker run --rm \ + -v "${{ github.workspace }}:/workspace" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e GH_TOKEN="${GH_TOKEN}" \ + -e PR_NUMBER="${{ github.event.pull_request.number }}" \ + polyglot-python + + validate_go: + name: Go SDK Validation + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Build Go validation image + run: | + docker build \ + -f .github/workflows/polyglot-validation/Dockerfile.go \ + -t polyglot-go \ + .github/workflows/polyglot-validation/ + + - name: Run Go SDK validation + env: + GH_TOKEN: ${{ github.token }} + run: | + docker run --rm \ + -v "${{ github.workspace }}:/workspace" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e GH_TOKEN="${GH_TOKEN}" \ + -e PR_NUMBER="${{ github.event.pull_request.number }}" \ + polyglot-go + + validate_java: + name: Java SDK Validation + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Build Java validation image + run: | + docker build \ + -f .github/workflows/polyglot-validation/Dockerfile.java \ + -t polyglot-java \ + .github/workflows/polyglot-validation/ + + - name: Run Java SDK validation + env: + GH_TOKEN: ${{ github.token }} + run: | + docker run --rm \ + -v "${{ github.workspace }}:/workspace" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e GH_TOKEN="${GH_TOKEN}" \ + -e PR_NUMBER="${{ github.event.pull_request.number }}" \ + polyglot-java + + validate_rust: + name: Rust SDK Validation + runs-on: ubuntu-latest + # Rust SDK has known issues - don't fail the workflow + continue-on-error: true + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Build Rust validation image + run: | + docker build \ + -f .github/workflows/polyglot-validation/Dockerfile.rust \ + -t polyglot-rust \ + .github/workflows/polyglot-validation/ + + - name: Run Rust SDK validation + env: + GH_TOKEN: ${{ github.token }} + run: | + docker run --rm \ + -v "${{ github.workspace }}:/workspace" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e GH_TOKEN="${GH_TOKEN}" \ + -e PR_NUMBER="${{ github.event.pull_request.number }}" \ + polyglot-rust + + validate_typescript: + name: TypeScript SDK Validation + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Build TypeScript validation image + run: | + docker build \ + -f .github/workflows/polyglot-validation/Dockerfile.typescript \ + -t polyglot-typescript \ + .github/workflows/polyglot-validation/ + + - name: Run TypeScript SDK validation + env: + GH_TOKEN: ${{ github.token }} + run: | + docker run --rm \ + -v "${{ github.workspace }}:/workspace" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e GH_TOKEN="${GH_TOKEN}" \ + -e PR_NUMBER="${{ github.event.pull_request.number }}" \ + polyglot-typescript + + results: + if: always() + runs-on: ubuntu-latest + name: Polyglot Validation Results + needs: [validate_python, validate_go, validate_java, validate_rust, validate_typescript] + steps: + - name: Check validation results + # Only fail on Python, Go, Java, TypeScript failures (Rust has continue-on-error) + if: >- + ${{ needs.validate_python.result == 'failure' || + needs.validate_go.result == 'failure' || + needs.validate_java.result == 'failure' || + needs.validate_typescript.result == 'failure' || + needs.validate_python.result == 'cancelled' || + needs.validate_go.result == 'cancelled' || + needs.validate_java.result == 'cancelled' || + needs.validate_typescript.result == 'cancelled' }} + run: | + echo "One or more polyglot SDK validations failed." + echo "Python: ${{ needs.validate_python.result }}" + echo "Go: ${{ needs.validate_go.result }}" + echo "Java: ${{ needs.validate_java.result }}" + echo "TypeScript: ${{ needs.validate_typescript.result }}" + exit 1 + + - name: Report Rust status + if: always() + run: | + echo "Rust SDK validation: ${{ needs.validate_rust.result }}" + if [ "${{ needs.validate_rust.result }}" == "failure" ]; then + echo "⚠️ Rust SDK validation failed (known issues - not blocking)" + fi + + - name: All validations passed + run: echo "✅ All required polyglot SDK validations passed!" diff --git a/.github/workflows/polyglot-validation/Dockerfile.go b/.github/workflows/polyglot-validation/Dockerfile.go new file mode 100644 index 00000000000..af6fb0e667b --- /dev/null +++ b/.github/workflows/polyglot-validation/Dockerfile.go @@ -0,0 +1,55 @@ +# Polyglot SDK Validation - Go +# This Dockerfile sets up an environment for validating the Go AppHost SDK +# +# Usage: +# docker build -f Dockerfile.go -t polyglot-go . +# docker run --rm \ +# -v "$(pwd):/workspace" \ +# -v /var/run/docker.sock:/var/run/docker.sock \ +# -e GH_TOKEN \ +# -e PR_NUMBER= \ +# polyglot-go +# +FROM mcr.microsoft.com/devcontainers/go:1-trixie + +# Install system dependencies (wget, docker CLI, jq for JSON manipulation) +RUN apt-get update && apt-get install -y \ + wget \ + docker.io \ + jq \ + && rm -rf /var/lib/apt/lists/* + +# Install GitHub CLI (matches inline script installation) +RUN mkdir -p -m 755 /etc/apt/keyrings \ + && wget -nv -O- https://cli.github.com/packages/githubcli-archive-keyring.gpg | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ + && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && apt-get update \ + && apt-get install -y gh \ + && rm -rf /var/lib/apt/lists/* + +# Install .NET SDK 10.0 +RUN curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 10.0 +ENV PATH="/root/.dotnet:${PATH}" +ENV DOTNET_ROOT="/root/.dotnet" + +# Pre-configure Aspire CLI path +ENV PATH="/root/.aspire/bin:${PATH}" + +WORKDIR /workspace + +COPY test-go.sh /scripts/test-go.sh +RUN chmod +x /scripts/test-go.sh + +# Entrypoint: Install Aspire CLI from PR, enable polyglot, run validation +ENTRYPOINT ["/bin/bash", "-c", "\ + set -e && \ + echo '=== Installing Aspire CLI from PR ===' && \ + chmod +x /workspace/eng/scripts/get-aspire-cli-pr.sh && \ + /workspace/eng/scripts/get-aspire-cli-pr.sh ${PR_NUMBER} && \ + aspire --version && \ + echo '=== Enabling polyglot support ===' && \ + aspire config set features:polyglotSupportEnabled true --global && \ + echo '=== Running validation ===' && \ + /scripts/test-go.sh \ +"] diff --git a/.github/workflows/polyglot-validation/Dockerfile.java b/.github/workflows/polyglot-validation/Dockerfile.java new file mode 100644 index 00000000000..11ab721c6d3 --- /dev/null +++ b/.github/workflows/polyglot-validation/Dockerfile.java @@ -0,0 +1,55 @@ +# Polyglot SDK Validation - Java +# This Dockerfile sets up an environment for validating the Java AppHost SDK +# +# Usage: +# docker build -f Dockerfile.java -t polyglot-java . +# docker run --rm \ +# -v "$(pwd):/workspace" \ +# -v /var/run/docker.sock:/var/run/docker.sock \ +# -e GH_TOKEN \ +# -e PR_NUMBER= \ +# polyglot-java +# +FROM mcr.microsoft.com/devcontainers/java:17 + +# Install system dependencies (wget, docker CLI, jq for JSON manipulation) +RUN apt-get update && apt-get install -y \ + wget \ + docker.io \ + jq \ + && rm -rf /var/lib/apt/lists/* + +# Install GitHub CLI (matches inline script installation) +RUN mkdir -p -m 755 /etc/apt/keyrings \ + && wget -nv -O- https://cli.github.com/packages/githubcli-archive-keyring.gpg | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ + && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && apt-get update \ + && apt-get install -y gh \ + && rm -rf /var/lib/apt/lists/* + +# Install .NET SDK 10.0 +RUN curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 10.0 +ENV PATH="/root/.dotnet:${PATH}" +ENV DOTNET_ROOT="/root/.dotnet" + +# Pre-configure Aspire CLI path +ENV PATH="/root/.aspire/bin:${PATH}" + +WORKDIR /workspace + +COPY test-java.sh /scripts/test-java.sh +RUN chmod +x /scripts/test-java.sh + +# Entrypoint: Install Aspire CLI from PR, enable polyglot, run validation +ENTRYPOINT ["/bin/bash", "-c", "\ + set -e && \ + echo '=== Installing Aspire CLI from PR ===' && \ + chmod +x /workspace/eng/scripts/get-aspire-cli-pr.sh && \ + /workspace/eng/scripts/get-aspire-cli-pr.sh ${PR_NUMBER} && \ + aspire --version && \ + echo '=== Enabling polyglot support ===' && \ + aspire config set features:polyglotSupportEnabled true --global && \ + echo '=== Running validation ===' && \ + /scripts/test-java.sh \ +"] diff --git a/.github/workflows/polyglot-validation/Dockerfile.python b/.github/workflows/polyglot-validation/Dockerfile.python new file mode 100644 index 00000000000..228d816e2f7 --- /dev/null +++ b/.github/workflows/polyglot-validation/Dockerfile.python @@ -0,0 +1,59 @@ +# Polyglot SDK Validation - Python +# This Dockerfile sets up an environment for validating the Python AppHost SDK +# +# Usage: +# docker build -f Dockerfile.python -t polyglot-python . +# docker run --rm \ +# -v "$(pwd):/workspace" \ +# -v /var/run/docker.sock:/var/run/docker.sock \ +# -e GH_TOKEN \ +# -e PR_NUMBER= \ +# polyglot-python +# +FROM mcr.microsoft.com/devcontainers/python:3.12 + +# Install system dependencies (wget, docker CLI, jq for JSON manipulation) +RUN apt-get update && apt-get install -y \ + wget \ + docker.io \ + jq \ + && rm -rf /var/lib/apt/lists/* + +# Install GitHub CLI (matches inline script installation) +RUN mkdir -p -m 755 /etc/apt/keyrings \ + && wget -nv -O- https://cli.github.com/packages/githubcli-archive-keyring.gpg | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ + && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && apt-get update \ + && apt-get install -y gh \ + && rm -rf /var/lib/apt/lists/* + +# Install .NET SDK 10.0 +RUN curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 10.0 +ENV PATH="/root/.dotnet:${PATH}" +ENV DOTNET_ROOT="/root/.dotnet" + +# Install uv package manager (Python-specific) +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +ENV PATH="/root/.local/bin:${PATH}" + +# Pre-configure Aspire CLI path +ENV PATH="/root/.aspire/bin:${PATH}" + +WORKDIR /workspace + +COPY test-python.sh /scripts/test-python.sh +RUN chmod +x /scripts/test-python.sh + +# Entrypoint: Install Aspire CLI from PR, enable polyglot, run validation +ENTRYPOINT ["/bin/bash", "-c", "\ + set -e && \ + echo '=== Installing Aspire CLI from PR ===' && \ + chmod +x /workspace/eng/scripts/get-aspire-cli-pr.sh && \ + /workspace/eng/scripts/get-aspire-cli-pr.sh ${PR_NUMBER} && \ + aspire --version && \ + echo '=== Enabling polyglot support ===' && \ + aspire config set features:polyglotSupportEnabled true --global && \ + echo '=== Running validation ===' && \ + /scripts/test-python.sh \ +"] diff --git a/.github/workflows/polyglot-validation/Dockerfile.rust b/.github/workflows/polyglot-validation/Dockerfile.rust new file mode 100644 index 00000000000..abb98a675bb --- /dev/null +++ b/.github/workflows/polyglot-validation/Dockerfile.rust @@ -0,0 +1,55 @@ +# Polyglot SDK Validation - Rust +# This Dockerfile sets up an environment for validating the Rust AppHost SDK +# +# Usage: +# docker build -f Dockerfile.rust -t polyglot-rust . +# docker run --rm \ +# -v "$(pwd):/workspace" \ +# -v /var/run/docker.sock:/var/run/docker.sock \ +# -e GH_TOKEN \ +# -e PR_NUMBER= \ +# polyglot-rust +# +FROM mcr.microsoft.com/devcontainers/rust:1 + +# Install system dependencies (wget, docker CLI, jq for JSON manipulation) +RUN apt-get update && apt-get install -y \ + wget \ + docker.io \ + jq \ + && rm -rf /var/lib/apt/lists/* + +# Install GitHub CLI (matches inline script installation) +RUN mkdir -p -m 755 /etc/apt/keyrings \ + && wget -nv -O- https://cli.github.com/packages/githubcli-archive-keyring.gpg | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ + && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && apt-get update \ + && apt-get install -y gh \ + && rm -rf /var/lib/apt/lists/* + +# Install .NET SDK 10.0 +RUN curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 10.0 +ENV PATH="/root/.dotnet:${PATH}" +ENV DOTNET_ROOT="/root/.dotnet" + +# Pre-configure Aspire CLI path +ENV PATH="/root/.aspire/bin:${PATH}" + +WORKDIR /workspace + +COPY test-rust.sh /scripts/test-rust.sh +RUN chmod +x /scripts/test-rust.sh + +# Entrypoint: Install Aspire CLI from PR, enable polyglot, run validation +ENTRYPOINT ["/bin/bash", "-c", "\ + set -e && \ + echo '=== Installing Aspire CLI from PR ===' && \ + chmod +x /workspace/eng/scripts/get-aspire-cli-pr.sh && \ + /workspace/eng/scripts/get-aspire-cli-pr.sh ${PR_NUMBER} && \ + aspire --version && \ + echo '=== Enabling polyglot support ===' && \ + aspire config set features:polyglotSupportEnabled true --global && \ + echo '=== Running validation ===' && \ + /scripts/test-rust.sh \ +"] diff --git a/.github/workflows/polyglot-validation/Dockerfile.typescript b/.github/workflows/polyglot-validation/Dockerfile.typescript new file mode 100644 index 00000000000..ec506cbdb31 --- /dev/null +++ b/.github/workflows/polyglot-validation/Dockerfile.typescript @@ -0,0 +1,55 @@ +# Polyglot SDK Validation - TypeScript +# This Dockerfile sets up an environment for validating the TypeScript AppHost SDK +# +# Usage: +# docker build -f Dockerfile.typescript -t polyglot-typescript . +# docker run --rm \ +# -v "$(pwd):/workspace" \ +# -v /var/run/docker.sock:/var/run/docker.sock \ +# -e GH_TOKEN \ +# -e PR_NUMBER= \ +# polyglot-typescript +# +FROM mcr.microsoft.com/devcontainers/typescript-node:22 + +# Install system dependencies (wget, docker CLI, jq for JSON manipulation) +RUN apt-get update && apt-get install -y \ + wget \ + docker.io \ + jq \ + && rm -rf /var/lib/apt/lists/* + +# Install GitHub CLI (matches inline script installation) +RUN mkdir -p -m 755 /etc/apt/keyrings \ + && wget -nv -O- https://cli.github.com/packages/githubcli-archive-keyring.gpg | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ + && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && apt-get update \ + && apt-get install -y gh \ + && rm -rf /var/lib/apt/lists/* + +# Install .NET SDK 10.0 +RUN curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 10.0 +ENV PATH="/root/.dotnet:${PATH}" +ENV DOTNET_ROOT="/root/.dotnet" + +# Pre-configure Aspire CLI path +ENV PATH="/root/.aspire/bin:${PATH}" + +WORKDIR /workspace + +COPY test-typescript.sh /scripts/test-typescript.sh +RUN chmod +x /scripts/test-typescript.sh + +# Entrypoint: Install Aspire CLI from PR, enable polyglot, run validation +ENTRYPOINT ["/bin/bash", "-c", "\ + set -e && \ + echo '=== Installing Aspire CLI from PR ===' && \ + chmod +x /workspace/eng/scripts/get-aspire-cli-pr.sh && \ + /workspace/eng/scripts/get-aspire-cli-pr.sh ${PR_NUMBER} && \ + aspire --version && \ + echo '=== Enabling polyglot support ===' && \ + aspire config set features:polyglotSupportEnabled true --global && \ + echo '=== Running validation ===' && \ + /scripts/test-typescript.sh \ +"] diff --git a/.github/workflows/polyglot-validation/test-go.sh b/.github/workflows/polyglot-validation/test-go.sh new file mode 100755 index 00000000000..ce0355af5c1 --- /dev/null +++ b/.github/workflows/polyglot-validation/test-go.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# Polyglot SDK Validation - Go +# This script validates the Go AppHost SDK with Redis integration +set -e + +echo "=== Go AppHost SDK Validation ===" + +# Verify aspire CLI is available +if ! command -v aspire &> /dev/null; then + echo "❌ Aspire CLI not found in PATH" + exit 1 +fi + +echo "Aspire CLI version:" +aspire --version + +# Create project directory +WORK_DIR=$(mktemp -d) +echo "Working directory: $WORK_DIR" +cd "$WORK_DIR" + +# Initialize Go AppHost +echo "Creating Go apphost project..." +aspire init -l go --non-interactive + +# Add Redis integration +echo "Adding Redis integration..." +aspire add Aspire.Hosting.Redis --non-interactive 2>&1 || { + echo "aspire add failed, manually updating settings.json..." + PKG_VERSION=$(aspire --version | grep -oP '\d+\.\d+\.\d+-.*' | head -1) + if [ -f ".aspire/settings.json" ]; then + if command -v jq &> /dev/null; then + jq '.packages["Aspire.Hosting.Redis"] = "'"$PKG_VERSION"'"' .aspire/settings.json > .aspire/settings.json.tmp && mv .aspire/settings.json.tmp .aspire/settings.json + fi + echo "Settings.json updated" + cat .aspire/settings.json + fi +} + +# Insert Redis code into apphost.go +echo "Configuring apphost.go with Redis..." +if grep -q "builder.Build()" apphost.go; then + sed -i '/builder.Build()/i\// Add Redis cache resource\n\t_, err = builder.AddRedis("cache", 0, nil)\n\tif err != nil {\n\t\tlog.Fatalf("Failed to add Redis: %v", err)\n\t}' apphost.go + echo "✅ Redis configuration added to apphost.go" +fi + +echo "=== apphost.go ===" +cat apphost.go + +# Run the apphost in background +echo "Starting apphost in background..." +aspire run -d > aspire.log 2>&1 & +ASPIRE_PID=$! +echo "Aspire PID: $ASPIRE_PID" + +# Poll for Redis container with retries +echo "Polling for Redis container..." +RESULT=1 +for i in {1..12}; do + echo "Attempt $i/12: Checking for Redis container..." + if docker ps | grep -q -i redis; then + echo "✅ SUCCESS: Redis container is running!" + docker ps | grep -i redis + RESULT=0 + break + fi + echo "Redis not found yet, waiting 10 seconds..." + sleep 10 +done + +if [ $RESULT -ne 0 ]; then + echo "❌ FAILURE: Redis container not found after 2 minutes" + echo "=== Docker containers ===" + docker ps + echo "=== Aspire log ===" + cat aspire.log || true +fi + +# Cleanup +echo "Stopping apphost..." +kill -9 $ASPIRE_PID 2>/dev/null || true +rm -rf "$WORK_DIR" + +exit $RESULT diff --git a/.github/workflows/polyglot-validation/test-java.sh b/.github/workflows/polyglot-validation/test-java.sh new file mode 100755 index 00000000000..3c596067881 --- /dev/null +++ b/.github/workflows/polyglot-validation/test-java.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# Polyglot SDK Validation - Java +# This script validates the Java AppHost SDK with Redis integration +set -e + +echo "=== Java AppHost SDK Validation ===" + +# Verify aspire CLI is available +if ! command -v aspire &> /dev/null; then + echo "❌ Aspire CLI not found in PATH" + exit 1 +fi + +echo "Aspire CLI version:" +aspire --version + +# Create project directory +WORK_DIR=$(mktemp -d) +echo "Working directory: $WORK_DIR" +cd "$WORK_DIR" + +# Initialize Java AppHost +echo "Creating Java apphost project..." +aspire init -l java --non-interactive + +# Add Redis integration +echo "Adding Redis integration..." +aspire add Aspire.Hosting.Redis --non-interactive 2>&1 || { + echo "aspire add failed, manually updating settings.json..." + PKG_VERSION=$(aspire --version | grep -oP '\d+\.\d+\.\d+-.*' | head -1) + if [ -f ".aspire/settings.json" ]; then + if command -v jq &> /dev/null; then + jq '.packages["Aspire.Hosting.Redis"] = "'"$PKG_VERSION"'"' .aspire/settings.json > .aspire/settings.json.tmp && mv .aspire/settings.json.tmp .aspire/settings.json + fi + echo "Settings.json updated" + cat .aspire/settings.json + fi +} + +# Insert Redis code into AppHost.java +echo "Configuring AppHost.java with Redis..." +if grep -q "builder.build()" AppHost.java; then + sed -i '/builder.build()/i\ // Add Redis cache resource\n builder.addRedis("cache", null, null);' AppHost.java + echo "✅ Redis configuration added to AppHost.java" +fi + +echo "=== AppHost.java ===" +cat AppHost.java + +# Run the apphost in background +echo "Starting apphost in background..." +aspire run -d > aspire.log 2>&1 & +ASPIRE_PID=$! +echo "Aspire PID: $ASPIRE_PID" + +# Poll for Redis container with retries +echo "Polling for Redis container..." +RESULT=1 +for i in {1..12}; do + echo "Attempt $i/12: Checking for Redis container..." + if docker ps | grep -q -i redis; then + echo "✅ SUCCESS: Redis container is running!" + docker ps | grep -i redis + RESULT=0 + break + fi + echo "Redis not found yet, waiting 10 seconds..." + sleep 10 +done + +if [ $RESULT -ne 0 ]; then + echo "❌ FAILURE: Redis container not found after 2 minutes" + echo "=== Docker containers ===" + docker ps + echo "=== Aspire log ===" + cat aspire.log || true +fi + +# Cleanup +echo "Stopping apphost..." +kill -9 $ASPIRE_PID 2>/dev/null || true +rm -rf "$WORK_DIR" + +exit $RESULT diff --git a/.github/workflows/polyglot-validation/test-python.sh b/.github/workflows/polyglot-validation/test-python.sh new file mode 100755 index 00000000000..c6a821e1084 --- /dev/null +++ b/.github/workflows/polyglot-validation/test-python.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# Polyglot SDK Validation - Python +# This script validates the Python AppHost SDK with Redis integration +set -e + +echo "=== Python AppHost SDK Validation ===" + +# Verify aspire CLI is available +if ! command -v aspire &> /dev/null; then + echo "❌ Aspire CLI not found in PATH" + exit 1 +fi + +echo "Aspire CLI version:" +aspire --version + +# Create project directory +WORK_DIR=$(mktemp -d) +echo "Working directory: $WORK_DIR" +cd "$WORK_DIR" + +# Initialize Python AppHost +echo "Creating Python apphost project..." +aspire init -l python --non-interactive + +# Add Redis integration +echo "Adding Redis integration..." +aspire add Aspire.Hosting.Redis --non-interactive 2>&1 || { + echo "aspire add failed, manually updating settings.json..." + PKG_VERSION=$(aspire --version | grep -oP '\d+\.\d+\.\d+-.*' | head -1) + if [ -f ".aspire/settings.json" ]; then + if command -v jq &> /dev/null; then + jq '.packages["Aspire.Hosting.Redis"] = "'"$PKG_VERSION"'"' .aspire/settings.json > .aspire/settings.json.tmp && mv .aspire/settings.json.tmp .aspire/settings.json + fi + echo "Settings.json updated" + cat .aspire/settings.json + fi +} + +# Insert Redis line into apphost.py +echo "Configuring apphost.py with Redis..." +if grep -q "builder.build().run()" apphost.py; then + sed -i '/builder.build().run()/i\# Add Redis cache resource\nredis = builder.add_redis("cache")' apphost.py + echo "✅ Redis configuration added to apphost.py" +fi + +echo "=== apphost.py ===" +cat apphost.py + +# Run the apphost in background +echo "Starting apphost in background..." +aspire run -d > aspire.log 2>&1 & +ASPIRE_PID=$! +echo "Aspire PID: $ASPIRE_PID" + +# Poll for Redis container with retries +echo "Polling for Redis container..." +RESULT=1 +for i in {1..12}; do + echo "Attempt $i/12: Checking for Redis container..." + if docker ps | grep -q -i redis; then + echo "✅ SUCCESS: Redis container is running!" + docker ps | grep -i redis + RESULT=0 + break + fi + echo "Redis not found yet, waiting 10 seconds..." + sleep 10 +done + +if [ $RESULT -ne 0 ]; then + echo "❌ FAILURE: Redis container not found after 2 minutes" + echo "=== Docker containers ===" + docker ps + echo "=== Aspire log ===" + cat aspire.log || true +fi + +# Cleanup +echo "Stopping apphost..." +kill -9 $ASPIRE_PID 2>/dev/null || true +rm -rf "$WORK_DIR" + +exit $RESULT diff --git a/.github/workflows/polyglot-validation/test-rust.sh b/.github/workflows/polyglot-validation/test-rust.sh new file mode 100755 index 00000000000..9f1105e0e16 --- /dev/null +++ b/.github/workflows/polyglot-validation/test-rust.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# Polyglot SDK Validation - Rust +# This script validates the Rust AppHost SDK with Redis integration +set -e + +echo "=== Rust AppHost SDK Validation ===" + +# Verify aspire CLI is available +if ! command -v aspire &> /dev/null; then + echo "❌ Aspire CLI not found in PATH" + exit 1 +fi + +echo "Aspire CLI version:" +aspire --version + +# Create project directory +WORK_DIR=$(mktemp -d) +echo "Working directory: $WORK_DIR" +cd "$WORK_DIR" + +# Initialize Rust AppHost +echo "Creating Rust apphost project..." +aspire init -l rust --non-interactive + +# Add Redis integration +echo "Adding Redis integration..." +aspire add Aspire.Hosting.Redis --non-interactive 2>&1 || { + echo "aspire add failed, manually updating settings.json..." + PKG_VERSION=$(aspire --version | grep -oP '\d+\.\d+\.\d+-.*' | head -1) + if [ -f ".aspire/settings.json" ]; then + if command -v jq &> /dev/null; then + jq '.packages["Aspire.Hosting.Redis"] = "'"$PKG_VERSION"'"' .aspire/settings.json > .aspire/settings.json.tmp && mv .aspire/settings.json.tmp .aspire/settings.json + fi + echo "Settings.json updated" + cat .aspire/settings.json + fi +} + +# Insert Redis code into src/main.rs +echo "Configuring src/main.rs with Redis..." +if [ -f "src/main.rs" ] && grep -q "builder.build()" src/main.rs; then + sed -i '/builder.build()/i\ // Add Redis cache resource\n builder.add_redis("cache", None, None)?;' src/main.rs + echo "✅ Redis configuration added to src/main.rs" +fi + +echo "=== src/main.rs ===" +[ -f "src/main.rs" ] && cat src/main.rs + +# Run the apphost in background +echo "Starting apphost in background..." +aspire run -d > aspire.log 2>&1 & +ASPIRE_PID=$! +echo "Aspire PID: $ASPIRE_PID" + +# Poll for Redis container with retries (Rust needs more time for compilation) +echo "Polling for Redis container..." +RESULT=1 +for i in {1..18}; do + echo "Attempt $i/18: Checking for Redis container..." + if docker ps | grep -q -i redis; then + echo "✅ SUCCESS: Redis container is running!" + docker ps | grep -i redis + RESULT=0 + break + fi + echo "Redis not found yet, waiting 10 seconds..." + sleep 10 +done + +if [ $RESULT -ne 0 ]; then + echo "❌ FAILURE: Redis container not found after 3 minutes" + echo "=== Docker containers ===" + docker ps + echo "=== Aspire log ===" + cat aspire.log || true +fi + +# Cleanup +echo "Stopping apphost..." +kill -9 $ASPIRE_PID 2>/dev/null || true +rm -rf "$WORK_DIR" + +exit $RESULT diff --git a/.github/workflows/polyglot-validation/test-typescript.sh b/.github/workflows/polyglot-validation/test-typescript.sh new file mode 100755 index 00000000000..c8fa5c666ec --- /dev/null +++ b/.github/workflows/polyglot-validation/test-typescript.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# Polyglot SDK Validation - TypeScript +# This script validates the TypeScript AppHost SDK with Redis integration +set -e + +echo "=== TypeScript AppHost SDK Validation ===" + +# Verify aspire CLI is available +if ! command -v aspire &> /dev/null; then + echo "❌ Aspire CLI not found in PATH" + exit 1 +fi + +echo "Aspire CLI version:" +aspire --version + +# Create project directory +WORK_DIR=$(mktemp -d) +echo "Working directory: $WORK_DIR" +cd "$WORK_DIR" + +# Initialize TypeScript AppHost +echo "Creating TypeScript apphost project..." +aspire init -l typescript --non-interactive + +# Add Redis integration +echo "Adding Redis integration..." +aspire add Aspire.Hosting.Redis --non-interactive 2>&1 || { + echo "aspire add failed, manually updating settings.json..." + PKG_VERSION=$(aspire --version | grep -oP '\d+\.\d+\.\d+-.*' | head -1) + if [ -f ".aspire/settings.json" ]; then + if command -v jq &> /dev/null; then + jq '.packages["Aspire.Hosting.Redis"] = "'"$PKG_VERSION"'"' .aspire/settings.json > .aspire/settings.json.tmp && mv .aspire/settings.json.tmp .aspire/settings.json + fi + echo "Settings.json updated" + cat .aspire/settings.json + fi +} + +# Insert Redis line into apphost.ts +echo "Configuring apphost.ts with Redis..." +if grep -q "builder.build().run()" apphost.ts; then + sed -i '/builder.build().run()/i\// Add Redis cache resource\nconst redis = await builder.addRedis("cache");' apphost.ts + echo "✅ Redis configuration added to apphost.ts" +fi + +echo "=== apphost.ts ===" +cat apphost.ts + +# Run the apphost in background +echo "Starting apphost in background..." +aspire run -d > aspire.log 2>&1 & +ASPIRE_PID=$! +echo "Aspire PID: $ASPIRE_PID" + +# Poll for Redis container with retries +echo "Polling for Redis container..." +RESULT=1 +for i in {1..12}; do + echo "Attempt $i/12: Checking for Redis container..." + if docker ps | grep -q -i redis; then + echo "✅ SUCCESS: Redis container is running!" + docker ps | grep -i redis + RESULT=0 + break + fi + echo "Redis not found yet, waiting 10 seconds..." + sleep 10 +done + +if [ $RESULT -ne 0 ]; then + echo "❌ FAILURE: Redis container not found after 2 minutes" + echo "=== Docker containers ===" + docker ps + echo "=== Aspire log ===" + cat aspire.log || true +fi + +# Cleanup +echo "Stopping apphost..." +kill -9 $ASPIRE_PID 2>/dev/null || true +rm -rf "$WORK_DIR" + +exit $RESULT diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 138c20171f9..a65b2374536 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -175,6 +175,13 @@ jobs: requiresNugets: true versionOverrideArg: ${{ inputs.versionOverrideArg }} + polyglot_validation: + name: Polyglot SDK Validation + uses: ./.github/workflows/polyglot-validation.yml + needs: build_packages + with: + versionOverrideArg: ${{ inputs.versionOverrideArg }} + cli_e2e_tests: name: Cli E2E Linux # Only run CLI E2E tests during PR builds @@ -229,6 +236,7 @@ jobs: integrations_test_lin, integrations_test_macos, integrations_test_win, + polyglot_validation, templates_test_lin, templates_test_macos, templates_test_win diff --git a/Aspire.slnx b/Aspire.slnx index 086f1caef53..c321b008e2d 100644 --- a/Aspire.slnx +++ b/Aspire.slnx @@ -358,6 +358,10 @@ + + + + @@ -447,6 +451,7 @@ + diff --git a/docs/specs/polyglot-apphost.md b/docs/specs/polyglot-apphost.md index 856c40838cd..d79e0f51a00 100644 --- a/docs/specs/polyglot-apphost.md +++ b/docs/specs/polyglot-apphost.md @@ -18,7 +18,8 @@ This document describes how the Aspire CLI supports non-.NET app hosts using the 8. [CLI Integration](#cli-integration) 9. [Configuration](#configuration) 10. [Adding New Guest Languages](#adding-new-guest-languages) -11. [Security](#security) +11. [Local Development Workflow](#local-development-workflow) +12. [Security](#security) --- @@ -1404,6 +1405,112 @@ public sealed class AtsContext --- +## Local Development Workflow + +This section explains how to develop and test custom language SDKs using a local clone of the Aspire repository. + +### Prerequisites + +1. Clone the Aspire repository: + + ```bash + git clone https://github.com/dotnet/aspire.git + cd aspire + ``` + +2. Build the repository: + + ```bash + ./build.sh # macOS/Linux + ./build.cmd # Windows + ``` + +### Enabling Polyglot Support + +Polyglot support is behind a feature flag that must be enabled before you can use non-.NET languages. Enable it using the `aspire config` command: + +```bash +# Enable polyglot support globally (recommended for SDK development) +aspire config set features:polyglotSupportEnabled true --global + +# Or enable it locally for a specific project +aspire config set features:polyglotSupportEnabled true +``` + +This enables the `--language` option on `aspire init` and `aspire new` commands, and allows the CLI to detect and run non-.NET app hosts. + +> **Note:** When running the CLI from source with `dotnet run`, use the full command: +> ```bash +> dotnet run --project /path/to/aspire/src/Aspire.Cli/Aspire.Cli.csproj -- config set features:polyglotSupportEnabled true --global +> ``` + +### Setting Up Local Development Mode + +Set the `ASPIRE_REPO_ROOT` environment variable to point to your local Aspire clone. This enables **development mode**, which: + +- Uses **project references** instead of NuGet package references for the AppHost server +- **Forces code regeneration** on every run (bypasses the package hash cache) +- Allows you to immediately test changes without publishing packages + +```bash +# macOS/Linux +export ASPIRE_REPO_ROOT="/path/to/aspire" + +# Windows (PowerShell) +$env:ASPIRE_REPO_ROOT = "D:\aspire" + +# Windows (Command Prompt) +set ASPIRE_REPO_ROOT=D:\aspire +``` + +### Scaffolding a New App + +Use `dotnet run` to execute the CLI directly from source: + +```bash +# Create a new Python app +dotnet run --project /path/to/aspire/src/Aspire.Cli/Aspire.Cli.csproj -- init -l python + +# Create a new TypeScript app +dotnet run --project /path/to/aspire/src/Aspire.Cli/Aspire.Cli.csproj -- init -l typescript +``` + +The `-l` (or `--language`) flag specifies the target language for scaffolding. + +### Running the App + +```bash +# Run the app (standard mode) +dotnet run --project /path/to/aspire/src/Aspire.Cli/Aspire.Cli.csproj -- run + +# Run with debug output +dotnet run --project /path/to/aspire/src/Aspire.Cli/Aspire.Cli.csproj -- run -d +``` + +The `-d` (or `--debug`) flag enables additional diagnostic output, useful when developing and troubleshooting language support. + +### Development Workflow Tips + +1. **Rapid iteration**: With `ASPIRE_REPO_ROOT` set, the generated SDK in `.modules/` is regenerated on each run, so changes to code generation logic are immediately reflected. + +2. **Testing code generators**: Modify your `ICodeGenerator` implementation, then run `aspire run` in a test app—the new generated code will be produced automatically. + +3. **Testing language support**: Modify your `ILanguageSupport` implementation, then use `aspire init -l ` to test scaffolding or `aspire run` to test detection and execution. + +4. **Inspecting generated code**: Check the `.modules/` folder in your test app to see the generated SDK files and verify they match your expectations. + +### Quick Reference + +| Task | Command | +|------|---------| +| Scaffold Python app | `dotnet run --project $ASPIRE_REPO_ROOT/src/Aspire.Cli/Aspire.Cli.csproj -- init -l python` | +| Scaffold TypeScript app | `dotnet run --project $ASPIRE_REPO_ROOT/src/Aspire.Cli/Aspire.Cli.csproj -- init -l typescript` | +| Run app | `dotnet run --project $ASPIRE_REPO_ROOT/src/Aspire.Cli/Aspire.Cli.csproj -- run` | +| Run with debug output | `dotnet run --project $ASPIRE_REPO_ROOT/src/Aspire.Cli/Aspire.Cli.csproj -- run -d` | +| Add integration | `dotnet run --project $ASPIRE_REPO_ROOT/src/Aspire.Cli/Aspire.Cli.csproj -- add` | + +--- + ## Security Both guest and host run locally on the same machine, started by the CLI. This is **not** remote execution. diff --git a/eng/scripts/get-aspire-cli-pr.sh b/eng/scripts/get-aspire-cli-pr.sh index 6020088dd6f..f39c5a689ce 100755 --- a/eng/scripts/get-aspire-cli-pr.sh +++ b/eng/scripts/get-aspire-cli-pr.sh @@ -1049,7 +1049,8 @@ download_and_install_from_pr() { else cli_path="$cli_install_dir/aspire" fi - save_global_settings "$cli_path" "channel" "pr-$PR_NUMBER" + # Non-fatal: channel can be set manually if this fails + save_global_settings "$cli_path" "channel" "pr-$PR_NUMBER" || true fi } diff --git a/src/Aspire.Cli/Commands/AddCommand.cs b/src/Aspire.Cli/Commands/AddCommand.cs index 55a5d69d517..5a635c8b2ef 100644 --- a/src/Aspire.Cli/Commands/AddCommand.cs +++ b/src/Aspire.Cli/Commands/AddCommand.cs @@ -245,9 +245,11 @@ await Parallel.ForEachAsync(channels, cancellationToken, async (channel, ct) => var distinctPackages = possiblePackages.DistinctBy(p => p.Package.Id); // If there is only one package, we can skip the prompt and just use it. + // In non-interactive mode, auto-select the first package. var selectedPackage = distinctPackages.Count() switch { 1 => distinctPackages.First(), + > 1 when !_hostEnvironment.SupportsInteractiveInput => distinctPackages.First(), > 1 => await _prompter.PromptForIntegrationAsync(distinctPackages, cancellationToken), _ => throw new InvalidOperationException(AddCommandStrings.UnexpectedNumberOfPackagesFound) }; @@ -262,8 +264,14 @@ await Parallel.ForEachAsync(channels, cancellationToken, async (channel, ct) => return preferredVersionPackage; } - // ... otherwise we had better prompt. + // In non-interactive mode, auto-select the latest version. var orderedPackageVersions = packageVersions.OrderByDescending(p => SemVersion.Parse(p.Package.Version), SemVersion.PrecedenceComparer); + if (!_hostEnvironment.SupportsInteractiveInput) + { + return orderedPackageVersions.First(); + } + + // ... otherwise we had better prompt. var version = await _prompter.PromptForIntegrationVersionAsync(orderedPackageVersions, cancellationToken); return version; diff --git a/src/Aspire.Cli/Projects/AppHostServerProject.cs b/src/Aspire.Cli/Projects/AppHostServerProject.cs index aec19840390..956a5117183 100644 --- a/src/Aspire.Cli/Projects/AppHostServerProject.cs +++ b/src/Aspire.Cli/Projects/AppHostServerProject.cs @@ -619,12 +619,23 @@ private XDocument CreateProductionProjectFile(string sdkVersion, IEnumerable<(st /// /// Gets the socket path for the AppHost server based on the app path. + /// On Windows, returns just the pipe name (named pipes don't use file paths). + /// On Unix/macOS, returns the full socket file path. /// public string GetSocketPath() { var pathHash = SHA256.HashData(Encoding.UTF8.GetBytes(_appPath)); var socketName = Convert.ToHexString(pathHash)[..12].ToLowerInvariant() + ".sock"; + // On Windows, named pipes use just a name, not a file path. + // The .NET NamedPipeServerStream and clients will automatically + // use the \\.\pipe\ prefix. + if (OperatingSystem.IsWindows()) + { + return socketName; + } + + // On Unix/macOS, use Unix domain sockets with a file path var socketDir = Path.Combine(Path.GetTempPath(), FolderPrefix, "sockets"); Directory.CreateDirectory(socketDir); diff --git a/src/Aspire.Cli/Projects/DefaultLanguageDiscovery.cs b/src/Aspire.Cli/Projects/DefaultLanguageDiscovery.cs index 38e974a1680..d9473782505 100644 --- a/src/Aspire.Cli/Projects/DefaultLanguageDiscovery.cs +++ b/src/Aspire.Cli/Projects/DefaultLanguageDiscovery.cs @@ -32,6 +32,34 @@ internal sealed class DefaultLanguageDiscovery : ILanguageDiscovery DetectionPatterns: ["apphost.ts"], CodeGenerator: "TypeScript", // Matches ICodeGenerator.Language AppHostFileName: "apphost.ts"), + new LanguageInfo( + LanguageId: new LanguageId(KnownLanguageId.Python), + DisplayName: KnownLanguageId.PythonDisplayName, + PackageName: "Aspire.Hosting.CodeGeneration.Python", + DetectionPatterns: ["apphost.py"], + CodeGenerator: "Python", + AppHostFileName: "apphost.py"), + new LanguageInfo( + LanguageId: new LanguageId(KnownLanguageId.Go), + DisplayName: KnownLanguageId.GoDisplayName, + PackageName: "Aspire.Hosting.CodeGeneration.Go", + DetectionPatterns: ["apphost.go"], + CodeGenerator: "Go", + AppHostFileName: "apphost.go"), + new LanguageInfo( + LanguageId: new LanguageId(KnownLanguageId.Java), + DisplayName: KnownLanguageId.JavaDisplayName, + PackageName: "Aspire.Hosting.CodeGeneration.Java", + DetectionPatterns: ["AppHost.java"], + CodeGenerator: "Java", + AppHostFileName: "AppHost.java"), + new LanguageInfo( + LanguageId: new LanguageId(KnownLanguageId.Rust), + DisplayName: KnownLanguageId.RustDisplayName, + PackageName: "Aspire.Hosting.CodeGeneration.Rust", + DetectionPatterns: ["apphost.rs"], + CodeGenerator: "Rust", + AppHostFileName: "apphost.rs"), ]; /// diff --git a/src/Aspire.Cli/Projects/GuestAppHostProject.cs b/src/Aspire.Cli/Projects/GuestAppHostProject.cs index 23855bede2d..52a82be989c 100644 --- a/src/Aspire.Cli/Projects/GuestAppHostProject.cs +++ b/src/Aspire.Cli/Projects/GuestAppHostProject.cs @@ -198,12 +198,8 @@ private async Task BuildAndGenerateSdkAsync(DirectoryInfo directory, Cancellatio // Step 3: Connect to server await using var rpcClient = await AppHostRpcClient.ConnectAsync(socketPath, cancellationToken); - // Step 4: Install dependencies using GuestRuntime - var installResult = await InstallDependenciesAsync(directory, rpcClient, cancellationToken); - if (installResult != 0) - { - return; - } + // Step 4: Install dependencies using GuestRuntime (best effort - don't block code generation) + await InstallDependenciesAsync(directory, rpcClient, cancellationToken); // Step 5: Generate SDK code via RPC await GenerateCodeViaRpcAsync( diff --git a/src/Aspire.Cli/Projects/KnownLanguageId.cs b/src/Aspire.Cli/Projects/KnownLanguageId.cs index f2247ddf022..d4f92dcd1d6 100644 --- a/src/Aspire.Cli/Projects/KnownLanguageId.cs +++ b/src/Aspire.Cli/Projects/KnownLanguageId.cs @@ -28,4 +28,44 @@ internal static class KnownLanguageId /// Short alias for TypeScript that can be used on the command line. /// public const string TypeScriptAlias = "typescript"; + + /// + /// The language ID for Python AppHost projects. + /// + public const string Python = "python"; + + /// + /// The display name for Python AppHost projects. + /// + public const string PythonDisplayName = "Python"; + + /// + /// The language ID for Go AppHost projects. + /// + public const string Go = "go"; + + /// + /// The display name for Go AppHost projects. + /// + public const string GoDisplayName = "Go"; + + /// + /// The language ID for Java AppHost projects. + /// + public const string Java = "java"; + + /// + /// The display name for Java AppHost projects. + /// + public const string JavaDisplayName = "Java"; + + /// + /// The language ID for Rust AppHost projects. + /// + public const string Rust = "rust"; + + /// + /// The display name for Rust AppHost projects. + /// + public const string RustDisplayName = "Rust"; } diff --git a/src/Aspire.Hosting.CodeGeneration.Go/Aspire.Hosting.CodeGeneration.Go.csproj b/src/Aspire.Hosting.CodeGeneration.Go/Aspire.Hosting.CodeGeneration.Go.csproj new file mode 100644 index 00000000000..3117914c5c3 --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Go/Aspire.Hosting.CodeGeneration.Go.csproj @@ -0,0 +1,29 @@ + + + + $(DefaultTargetFramework) + enable + enable + Aspire.Hosting.CodeGeneration.Go + + true + + true + true + + + + + + + + + + + + + + + + + diff --git a/src/Aspire.Hosting.CodeGeneration.Go/AtsGoCodeGenerator.cs b/src/Aspire.Hosting.CodeGeneration.Go/AtsGoCodeGenerator.cs new file mode 100644 index 00000000000..e41233e868a --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Go/AtsGoCodeGenerator.cs @@ -0,0 +1,844 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Reflection; +using System.Text; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Ats; + +namespace Aspire.Hosting.CodeGeneration.Go; + +/// +/// Generates a Go SDK using the ATS (Aspire Type System) capability-based API. +/// Produces wrapper structs that proxy capabilities via JSON-RPC. +/// +public sealed class AtsGoCodeGenerator : ICodeGenerator +{ + private static readonly HashSet s_goKeywords = new(StringComparer.Ordinal) + { + "break", "case", "chan", "const", "continue", "default", "defer", "else", + "fallthrough", "for", "func", "go", "goto", "if", "import", "interface", + "map", "package", "range", "return", "select", "struct", "switch", "type", "var" + }; + + private TextWriter _writer = null!; + private readonly Dictionary _structNames = new(StringComparer.Ordinal); + private readonly Dictionary _dtoNames = new(StringComparer.Ordinal); + private readonly Dictionary _enumNames = new(StringComparer.Ordinal); + + /// + public string Language => "Go"; + + /// + public Dictionary GenerateDistributedApplication(AtsContext context) + { + return new Dictionary(StringComparer.Ordinal) + { + ["go.mod"] = """ + module apphost/modules/aspire + + go 1.23 + """, + ["transport.go"] = GetEmbeddedResource("transport.go"), + ["base.go"] = GetEmbeddedResource("base.go"), + ["aspire.go"] = GenerateAspireSdk(context) + }; + } + + private static string GetEmbeddedResource(string name) + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = $"Aspire.Hosting.CodeGeneration.Go.Resources.{name}"; + + using var stream = assembly.GetManifestResourceStream(resourceName) + ?? throw new InvalidOperationException($"Embedded resource '{name}' not found."); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private string GenerateAspireSdk(AtsContext context) + { + using var stringWriter = new StringWriter(CultureInfo.InvariantCulture); + _writer = stringWriter; + + var capabilities = context.Capabilities; + var dtoTypes = context.DtoTypes; + var enumTypes = context.EnumTypes; + + _enumNames.Clear(); + foreach (var enumType in enumTypes) + { + _enumNames[enumType.TypeId] = SanitizeIdentifier(enumType.Name); + } + + _dtoNames.Clear(); + foreach (var dto in dtoTypes) + { + _dtoNames[dto.TypeId] = SanitizeIdentifier(dto.Name); + } + + var handleTypes = BuildHandleTypes(context); + var capabilitiesByTarget = GroupCapabilitiesByTarget(capabilities); + var collectionTypes = CollectListAndDictTypeIds(capabilities); + + WriteHeader(); + GenerateEnumTypes(enumTypes); + GenerateDtoTypes(dtoTypes); + GenerateHandleTypes(handleTypes, capabilitiesByTarget); + GenerateHandleWrapperRegistrations(handleTypes, collectionTypes); + GenerateConnectionHelpers(); + + return stringWriter.ToString(); + } + + private void WriteHeader() + { + WriteLine("// aspire.go - Capability-based Aspire SDK"); + WriteLine("// GENERATED CODE - DO NOT EDIT"); + WriteLine(); + WriteLine("package aspire"); + WriteLine(); + WriteLine("import ("); + WriteLine("\t\"fmt\""); + WriteLine("\t\"os\""); + WriteLine(")"); + WriteLine(); + } + + private void GenerateEnumTypes(IReadOnlyList enumTypes) + { + if (enumTypes.Count == 0) + { + return; + } + + WriteLine("// ============================================================================"); + WriteLine("// Enums"); + WriteLine("// ============================================================================"); + WriteLine(); + + foreach (var enumType in enumTypes) + { + if (enumType.ClrType is null) + { + continue; + } + + var enumName = _enumNames[enumType.TypeId]; + WriteLine($"// {enumName} represents {enumType.Name}."); + WriteLine($"type {enumName} string"); + WriteLine(); + WriteLine("const ("); + foreach (var member in Enum.GetNames(enumType.ClrType)) + { + var memberName = $"{enumName}{ToPascalCase(member)}"; + WriteLine($"\t{memberName} {enumName} = \"{member}\""); + } + WriteLine(")"); + WriteLine(); + } + } + + private void GenerateDtoTypes(IReadOnlyList dtoTypes) + { + if (dtoTypes.Count == 0) + { + return; + } + + WriteLine("// ============================================================================"); + WriteLine("// DTOs"); + WriteLine("// ============================================================================"); + WriteLine(); + + foreach (var dto in dtoTypes) + { + // Skip ReferenceExpression - it's defined in base.go + if (dto.TypeId == AtsConstants.ReferenceExpressionTypeId) + { + continue; + } + + var dtoName = _dtoNames[dto.TypeId]; + WriteLine($"// {dtoName} represents {dto.Name}."); + WriteLine($"type {dtoName} struct {{"); + if (dto.Properties.Count == 0) + { + WriteLine("}"); + WriteLine(); + continue; + } + + foreach (var property in dto.Properties) + { + var propertyName = ToPascalCase(property.Name); + var propertyType = MapTypeRefToGo(property.Type, property.IsOptional); + var jsonTag = $"`json:\"{property.Name},omitempty\"`"; + WriteLine($"\t{propertyName} {propertyType} {jsonTag}"); + } + WriteLine("}"); + WriteLine(); + + // Generate ToMap method for serialization + WriteLine($"// ToMap converts the DTO to a map for JSON serialization."); + WriteLine($"func (d *{dtoName}) ToMap() map[string]any {{"); + WriteLine("\treturn map[string]any{"); + foreach (var property in dto.Properties) + { + var propertyName = ToPascalCase(property.Name); + WriteLine($"\t\t\"{property.Name}\": SerializeValue(d.{propertyName}),"); + } + WriteLine("\t}"); + WriteLine("}"); + WriteLine(); + } + } + + private void GenerateHandleTypes( + IReadOnlyList handleTypes, + Dictionary> capabilitiesByTarget) + { + if (handleTypes.Count == 0) + { + return; + } + + WriteLine("// ============================================================================"); + WriteLine("// Handle Wrappers"); + WriteLine("// ============================================================================"); + WriteLine(); + + foreach (var handleType in handleTypes.OrderBy(t => t.StructName, StringComparer.Ordinal)) + { + var baseStruct = handleType.IsResourceBuilder ? "ResourceBuilderBase" : "HandleWrapperBase"; + + // Collect list/dict property fields + var listDictFields = new List<(string fieldName, string fieldType)>(); + if (capabilitiesByTarget.TryGetValue(handleType.TypeId, out var methods)) + { + foreach (var method in methods) + { + var parameters = method.Parameters + .Where(p => !string.Equals(p.Name, method.TargetParameterName ?? "builder", StringComparison.Ordinal)) + .ToList(); + + if (parameters.Count == 0 && IsListOrDictPropertyGetter(method.ReturnType)) + { + var returnType = method.ReturnType!; + var isDict = returnType.Category == AtsTypeCategory.Dict; + var wrapperType = isDict ? "AspireDict" : "AspireList"; + + string typeArgs; + if (isDict) + { + var keyType = MapTypeRefToGo(returnType.KeyType, false); + var valueType = MapTypeRefToGo(returnType.ValueType, false); + typeArgs = $"[{keyType}, {valueType}]"; + } + else + { + var elementType = MapTypeRefToGo(returnType.ElementType, false); + typeArgs = $"[{elementType}]"; + } + + var fieldName = ToCamelCase(ToPascalCase(method.MethodName)); + listDictFields.Add((fieldName, $"*{wrapperType}{typeArgs}")); + } + } + } + + WriteLine($"// {handleType.StructName} wraps a handle for {handleType.TypeId}."); + WriteLine($"type {handleType.StructName} struct {{"); + WriteLine($"\t{baseStruct}"); + foreach (var (fieldName, fieldType) in listDictFields) + { + WriteLine($"\t{fieldName} {fieldType}"); + } + WriteLine("}"); + WriteLine(); + + // Constructor + WriteLine($"// New{handleType.StructName} creates a new {handleType.StructName}."); + WriteLine($"func New{handleType.StructName}(handle *Handle, client *AspireClient) *{handleType.StructName} {{"); + WriteLine($"\treturn &{handleType.StructName}{{"); + WriteLine($"\t\t{baseStruct}: New{baseStruct}(handle, client),"); + WriteLine("\t}"); + WriteLine("}"); + WriteLine(); + + if (capabilitiesByTarget.TryGetValue(handleType.TypeId, out var allMethods)) + { + foreach (var method in allMethods) + { + GenerateCapabilityMethod(handleType.StructName, method); + } + } + } + } + + private void GenerateCapabilityMethod(string structName, AtsCapabilityInfo capability) + { + var targetParamName = capability.TargetParameterName ?? "builder"; + var methodName = ToPascalCase(capability.MethodName); + var parameters = capability.Parameters + .Where(p => !string.Equals(p.Name, targetParamName, StringComparison.Ordinal)) + .ToList(); + + // Check if this is a List/Dict property getter (no parameters, returns List/Dict) + if (parameters.Count == 0 && IsListOrDictPropertyGetter(capability.ReturnType)) + { + GenerateListOrDictProperty(structName, capability, methodName); + return; + } + + var returnType = MapTypeRefToGo(capability.ReturnType, false); + var hasReturn = capability.ReturnType.TypeId != AtsConstants.Void; + // Don't add extra * if return type already starts with * + var returnSignature = hasReturn + ? returnType.StartsWith("*", StringComparison.Ordinal) || returnType == "any" + ? $"({returnType}, error)" + : $"(*{returnType}, error)" + : "error"; + + // Build parameter list + var paramList = new StringBuilder(); + foreach (var parameter in parameters) + { + if (paramList.Length > 0) + { + paramList.Append(", "); + } + var paramName = ToCamelCase(parameter.Name); + var paramType = parameter.IsCallback + ? "func(...any) any" + : IsCancellationToken(parameter) + ? "*CancellationToken" + : MapTypeRefToGo(parameter.Type, parameter.IsOptional); + paramList.Append(CultureInfo.InvariantCulture, $"{paramName} {paramType}"); + } + + // Generate comment + if (!string.IsNullOrEmpty(capability.Description)) + { + WriteLine($"// {methodName} {char.ToLowerInvariant(capability.Description[0])}{capability.Description[1..]}"); + } + + // Use 'reqArgs' as local variable name to avoid conflict with parameters named 'args' + WriteLine($"func (s *{structName}) {methodName}({paramList}) {returnSignature} {{"); + WriteLine("\treqArgs := map[string]any{"); + WriteLine($"\t\t\"{targetParamName}\": SerializeValue(s.Handle()),"); + WriteLine("\t}"); + + foreach (var parameter in parameters) + { + var paramName = ToCamelCase(parameter.Name); + if (parameter.IsCallback) + { + WriteLine($"\tif {paramName} != nil {{"); + WriteLine($"\t\treqArgs[\"{parameter.Name}\"] = RegisterCallback({paramName})"); + WriteLine("\t}"); + continue; + } + + if (IsCancellationToken(parameter)) + { + WriteLine($"\tif {paramName} != nil {{"); + WriteLine($"\t\treqArgs[\"{parameter.Name}\"] = RegisterCancellation({paramName}, s.Client())"); + WriteLine("\t}"); + continue; + } + + // Only use nil checks for pointer types (types starting with *) + var paramTypeStr = MapTypeRefToGo(parameter.Type, parameter.IsOptional); + var isPointerType = paramTypeStr.StartsWith("*", StringComparison.Ordinal) || + paramTypeStr == "any" || + paramTypeStr.StartsWith("func(", StringComparison.Ordinal); + + if (parameter.IsOptional && isPointerType) + { + WriteLine($"\tif {paramName} != nil {{"); + WriteLine($"\t\treqArgs[\"{parameter.Name}\"] = SerializeValue({paramName})"); + WriteLine("\t}"); + } + else + { + WriteLine($"\treqArgs[\"{parameter.Name}\"] = SerializeValue({paramName})"); + } + } + + if (hasReturn) + { + WriteLine($"\tresult, err := s.Client().InvokeCapability(\"{capability.CapabilityId}\", reqArgs)"); + WriteLine("\tif err != nil {"); + WriteLine("\t\treturn nil, err"); + WriteLine("\t}"); + // Cast appropriately based on whether return type is already a pointer + if (returnType.StartsWith("*", StringComparison.Ordinal)) + { + WriteLine($"\treturn result.({returnType}), nil"); + } + else if (returnType == "any") + { + WriteLine("\treturn result, nil"); + } + else + { + WriteLine($"\treturn result.(*{returnType}), nil"); + } + } + else + { + WriteLine($"\t_, err := s.Client().InvokeCapability(\"{capability.CapabilityId}\", reqArgs)"); + WriteLine("\treturn err"); + } + + WriteLine("}"); + WriteLine(); + } + + private static bool IsListOrDictPropertyGetter(AtsTypeRef? returnType) + { + if (returnType is null) + { + return false; + } + + return returnType.Category == AtsTypeCategory.List || returnType.Category == AtsTypeCategory.Dict; + } + + private void GenerateListOrDictProperty(string structName, AtsCapabilityInfo capability, string methodName) + { + var returnType = capability.ReturnType!; + var isDict = returnType.Category == AtsTypeCategory.Dict; + + // Determine type arguments + string typeArgs; + if (isDict) + { + var keyType = MapTypeRefToGo(returnType.KeyType, false); + var valueType = MapTypeRefToGo(returnType.ValueType, false); + typeArgs = $"[{keyType}, {valueType}]"; + } + else + { + var elementType = MapTypeRefToGo(returnType.ElementType, false); + typeArgs = $"[{elementType}]"; + } + + var wrapperType = isDict ? "AspireDict" : "AspireList"; + var factoryFunc = isDict ? "NewAspireDictWithGetter" : "NewAspireListWithGetter"; + + // Generate comment + if (!string.IsNullOrEmpty(capability.Description)) + { + WriteLine($"// {methodName} {char.ToLowerInvariant(capability.Description[0])}{capability.Description[1..]}"); + } + + // Generate getter method with lazy initialization + var fieldName = ToCamelCase(methodName); + WriteLine($"func (s *{structName}) {methodName}() *{wrapperType}{typeArgs} {{"); + WriteLine($"\tif s.{fieldName} == nil {{"); + WriteLine($"\t\ts.{fieldName} = {factoryFunc}{typeArgs}(s.Handle(), s.Client(), \"{capability.CapabilityId}\")"); + WriteLine("\t}"); + WriteLine($"\treturn s.{fieldName}"); + WriteLine("}"); + WriteLine(); + } + + private void GenerateHandleWrapperRegistrations( + IReadOnlyList handleTypes, + Dictionary collectionTypes) + { + WriteLine("// ============================================================================"); + WriteLine("// Handle wrapper registrations"); + WriteLine("// ============================================================================"); + WriteLine(); + WriteLine("func init() {"); + + foreach (var handleType in handleTypes) + { + WriteLine($"\tRegisterHandleWrapper(\"{handleType.TypeId}\", func(h *Handle, c *AspireClient) any {{"); + WriteLine($"\t\treturn New{handleType.StructName}(h, c)"); + WriteLine("\t})"); + } + + foreach (var (typeId, isDict) in collectionTypes) + { + var wrapperType = isDict ? "AspireDict" : "AspireList"; + var typeArgs = isDict ? "[any, any]" : "[any]"; + WriteLine($"\tRegisterHandleWrapper(\"{typeId}\", func(h *Handle, c *AspireClient) any {{"); + WriteLine($"\t\treturn &{wrapperType}{typeArgs}{{HandleWrapperBase: NewHandleWrapperBase(h, c)}}"); + WriteLine("\t})"); + } + + WriteLine("}"); + WriteLine(); + } + + private void GenerateConnectionHelpers() + { + var builderStructName = _structNames.TryGetValue(AtsConstants.BuilderTypeId, out var name) + ? name + : "DistributedApplicationBuilder"; + + WriteLine("// ============================================================================"); + WriteLine("// Connection Helpers"); + WriteLine("// ============================================================================"); + WriteLine(); + WriteLine("// Connect establishes a connection to the AppHost server."); + WriteLine("func Connect() (*AspireClient, error) {"); + WriteLine("\tsocketPath := os.Getenv(\"REMOTE_APP_HOST_SOCKET_PATH\")"); + WriteLine("\tif socketPath == \"\" {"); + WriteLine("\t\treturn nil, fmt.Errorf(\"REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`\")"); + WriteLine("\t}"); + WriteLine("\tclient := NewAspireClient(socketPath)"); + WriteLine("\tif err := client.Connect(); err != nil {"); + WriteLine("\t\treturn nil, err"); + WriteLine("\t}"); + WriteLine("\tclient.OnDisconnect(func() { os.Exit(1) })"); + WriteLine("\treturn client, nil"); + WriteLine("}"); + WriteLine(); + WriteLine($"// CreateBuilder creates a new distributed application builder."); + WriteLine($"func CreateBuilder(options *CreateBuilderOptions) (*{builderStructName}, error) {{"); + WriteLine("\tclient, err := Connect()"); + WriteLine("\tif err != nil {"); + WriteLine("\t\treturn nil, err"); + WriteLine("\t}"); + WriteLine("\tresolvedOptions := make(map[string]any)"); + WriteLine("\tif options != nil {"); + WriteLine("\t\tfor k, v := range options.ToMap() {"); + WriteLine("\t\t\tresolvedOptions[k] = v"); + WriteLine("\t\t}"); + WriteLine("\t}"); + WriteLine("\tif _, ok := resolvedOptions[\"Args\"]; !ok {"); + WriteLine("\t\tresolvedOptions[\"Args\"] = os.Args[1:]"); + WriteLine("\t}"); + WriteLine("\tif _, ok := resolvedOptions[\"ProjectDirectory\"]; !ok {"); + WriteLine("\t\tif pwd, err := os.Getwd(); err == nil {"); + WriteLine("\t\t\tresolvedOptions[\"ProjectDirectory\"] = pwd"); + WriteLine("\t\t}"); + WriteLine("\t}"); + WriteLine("\tresult, err := client.InvokeCapability(\"Aspire.Hosting/createBuilderWithOptions\", map[string]any{\"options\": resolvedOptions})"); + WriteLine("\tif err != nil {"); + WriteLine("\t\treturn nil, err"); + WriteLine("\t}"); + WriteLine($"\treturn result.(*{builderStructName}), nil"); + WriteLine("}"); + WriteLine(); + } + + private IReadOnlyList BuildHandleTypes(AtsContext context) + { + var handleTypeIds = new HashSet(StringComparer.Ordinal); + foreach (var handleType in context.HandleTypes) + { + // Skip ReferenceExpression - it's defined in base.go + if (handleType.AtsTypeId == AtsConstants.ReferenceExpressionTypeId) + { + continue; + } + handleTypeIds.Add(handleType.AtsTypeId); + } + + foreach (var capability in context.Capabilities) + { + AddHandleTypeIfNeeded(handleTypeIds, capability.TargetType); + AddHandleTypeIfNeeded(handleTypeIds, capability.ReturnType); + foreach (var parameter in capability.Parameters) + { + AddHandleTypeIfNeeded(handleTypeIds, parameter.Type); + if (parameter.IsCallback && parameter.CallbackParameters is not null) + { + foreach (var callbackParam in parameter.CallbackParameters) + { + AddHandleTypeIfNeeded(handleTypeIds, callbackParam.Type); + } + } + } + } + + _structNames.Clear(); + foreach (var typeId in handleTypeIds) + { + _structNames[typeId] = CreateStructName(typeId); + } + + var handleTypeMap = context.HandleTypes.ToDictionary(t => t.AtsTypeId, StringComparer.Ordinal); + var results = new List(); + foreach (var typeId in handleTypeIds) + { + var isResourceBuilder = false; + if (handleTypeMap.TryGetValue(typeId, out var typeInfo)) + { + isResourceBuilder = typeInfo.ClrType is not null && + typeof(IResource).IsAssignableFrom(typeInfo.ClrType); + } + + results.Add(new GoHandleType(typeId, _structNames[typeId], isResourceBuilder)); + } + + return results; + } + + private static Dictionary> GroupCapabilitiesByTarget( + IReadOnlyList capabilities) + { + var result = new Dictionary>(StringComparer.Ordinal); + + foreach (var capability in capabilities) + { + if (string.IsNullOrEmpty(capability.TargetTypeId)) + { + continue; + } + + var targetTypes = capability.ExpandedTargetTypes.Count > 0 + ? capability.ExpandedTargetTypes + : capability.TargetType is not null + ? [capability.TargetType] + : []; + + foreach (var targetType in targetTypes) + { + if (targetType.TypeId is null) + { + continue; + } + + if (!result.TryGetValue(targetType.TypeId, out var list)) + { + list = new List(); + result[targetType.TypeId] = list; + } + list.Add(capability); + } + } + + return result; + } + + private static Dictionary CollectListAndDictTypeIds(IReadOnlyList capabilities) + { + // Maps typeId -> isDict (true for Dict, false for List) + var typeIds = new Dictionary(StringComparer.Ordinal); + foreach (var capability in capabilities) + { + AddListOrDictTypeIfNeeded(typeIds, capability.TargetType); + AddListOrDictTypeIfNeeded(typeIds, capability.ReturnType); + foreach (var parameter in capability.Parameters) + { + AddListOrDictTypeIfNeeded(typeIds, parameter.Type); + if (parameter.IsCallback && parameter.CallbackParameters is not null) + { + foreach (var callbackParam in parameter.CallbackParameters) + { + AddListOrDictTypeIfNeeded(typeIds, callbackParam.Type); + } + } + } + } + + return typeIds; + } + +#pragma warning disable IDE0060 // Remove unused parameter - keeping for API consistency with Python generator + private string MapTypeRefToGo(AtsTypeRef? typeRef, bool isOptional) +#pragma warning restore IDE0060 + { + if (typeRef is null) + { + return "any"; + } + + if (typeRef.TypeId == AtsConstants.ReferenceExpressionTypeId) + { + return "*ReferenceExpression"; + } + + var baseType = typeRef.Category switch + { + AtsTypeCategory.Primitive => MapPrimitiveType(typeRef.TypeId), + AtsTypeCategory.Enum => MapEnumType(typeRef.TypeId), + AtsTypeCategory.Handle => "*" + MapHandleType(typeRef.TypeId), + AtsTypeCategory.Dto => "*" + MapDtoType(typeRef.TypeId), + AtsTypeCategory.Callback => "func(...any) any", + AtsTypeCategory.Array => $"[]{MapTypeRefToGo(typeRef.ElementType, false)}", + AtsTypeCategory.List => typeRef.IsReadOnly + ? $"[]{MapTypeRefToGo(typeRef.ElementType, false)}" + : $"*AspireList[{MapTypeRefToGo(typeRef.ElementType, false)}]", + AtsTypeCategory.Dict => typeRef.IsReadOnly + ? $"map[{MapTypeRefToGo(typeRef.KeyType, false)}]{MapTypeRefToGo(typeRef.ValueType, false)}" + : $"*AspireDict[{MapTypeRefToGo(typeRef.KeyType, false)}, {MapTypeRefToGo(typeRef.ValueType, false)}]", + AtsTypeCategory.Union => "any", + AtsTypeCategory.Unknown => "any", + _ => "any" + }; + + // In Go, pointers are already optional (can be nil), so we don't need to wrap + return baseType; + } + + private string MapHandleType(string typeId) => + _structNames.TryGetValue(typeId, out var name) ? name : "Handle"; + + private string MapDtoType(string typeId) => + _dtoNames.TryGetValue(typeId, out var name) ? name : "map[string]any"; + + private string MapEnumType(string typeId) => + _enumNames.TryGetValue(typeId, out var name) ? name : "string"; + + private static string MapPrimitiveType(string typeId) => typeId switch + { + AtsConstants.String or AtsConstants.Char => "string", + AtsConstants.Number => "float64", + AtsConstants.Boolean => "bool", + AtsConstants.Void => "", + AtsConstants.Any => "any", + AtsConstants.DateTime or AtsConstants.DateTimeOffset or + AtsConstants.DateOnly or AtsConstants.TimeOnly => "string", + AtsConstants.TimeSpan => "float64", + AtsConstants.Guid or AtsConstants.Uri => "string", + AtsConstants.CancellationToken => "*CancellationToken", + _ => "any" + }; + + private static bool IsCancellationToken(AtsParameterInfo parameter) => + parameter.Type?.TypeId == AtsConstants.CancellationToken; + + private static void AddHandleTypeIfNeeded(HashSet handleTypeIds, AtsTypeRef? typeRef) + { + if (typeRef is null) + { + return; + } + + // Skip ReferenceExpression - it's defined in base.go + if (typeRef.TypeId == AtsConstants.ReferenceExpressionTypeId) + { + return; + } + + if (typeRef.Category == AtsTypeCategory.Handle) + { + handleTypeIds.Add(typeRef.TypeId); + } + } + + private static void AddListOrDictTypeIfNeeded(Dictionary typeIds, AtsTypeRef? typeRef) + { + if (typeRef is null) + { + return; + } + + if (typeRef.Category == AtsTypeCategory.List) + { + if (!typeRef.IsReadOnly) + { + typeIds[typeRef.TypeId] = false; // false = List + } + } + else if (typeRef.Category == AtsTypeCategory.Dict) + { + if (!typeRef.IsReadOnly) + { + typeIds[typeRef.TypeId] = true; // true = Dict + } + } + } + + private string CreateStructName(string typeId) + { + var baseName = ExtractTypeName(typeId); + var name = SanitizeIdentifier(baseName); + if (_structNames.Values.Contains(name, StringComparer.Ordinal)) + { + var assemblyName = typeId.Split('/')[0]; + var assemblyPrefix = SanitizeIdentifier(assemblyName); + name = $"{assemblyPrefix}{name}"; + } + + var counter = 1; + var candidate = name; + while (_structNames.Values.Contains(candidate, StringComparer.Ordinal)) + { + counter++; + candidate = $"{name}{counter}"; + } + + return candidate; + } + + private static string ExtractTypeName(string typeId) + { + var slashIndex = typeId.IndexOf('/', StringComparison.Ordinal); + var typeName = slashIndex >= 0 ? typeId[(slashIndex + 1)..] : typeId; + var lastDot = typeName.LastIndexOf('.'); + var plusIndex = typeName.LastIndexOf('+'); + var delimiterIndex = Math.Max(lastDot, plusIndex); + return delimiterIndex >= 0 ? typeName[(delimiterIndex + 1)..] : typeName; + } + + private static string SanitizeIdentifier(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + return "_"; + } + + var builder = new StringBuilder(name.Length); + foreach (var ch in name) + { + builder.Append(char.IsLetterOrDigit(ch) || ch == '_' ? ch : '_'); + } + + if (!char.IsLetter(builder[0]) && builder[0] != '_') + { + builder.Insert(0, '_'); + } + + var sanitized = builder.ToString(); + return s_goKeywords.Contains(sanitized) ? sanitized + "_" : sanitized; + } + + /// + /// Converts a name to PascalCase for Go exported identifiers. + /// + private static string ToPascalCase(string name) + { + if (string.IsNullOrEmpty(name)) + { + return name; + } + if (char.IsUpper(name[0])) + { + return name; + } + return char.ToUpperInvariant(name[0]) + name[1..]; + } + + /// + /// Converts a name to camelCase for Go unexported identifiers. + /// + private static string ToCamelCase(string name) + { + if (string.IsNullOrEmpty(name)) + { + return name; + } + if (char.IsLower(name[0])) + { + return name; + } + return char.ToLowerInvariant(name[0]) + name[1..]; + } + + private void WriteLine(string value = "") + { + _writer.WriteLine(value); + } + + private sealed record GoHandleType(string TypeId, string StructName, bool IsResourceBuilder); +} diff --git a/src/Aspire.Hosting.CodeGeneration.Go/GoLanguageSupport.cs b/src/Aspire.Hosting.CodeGeneration.Go/GoLanguageSupport.cs new file mode 100644 index 00000000000..e40c166ae80 --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Go/GoLanguageSupport.cs @@ -0,0 +1,146 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.Ats; + +namespace Aspire.Hosting.CodeGeneration.Go; + +/// +/// Provides language support for Go AppHosts. +/// Implements scaffolding, detection, and runtime configuration. +/// +public sealed class GoLanguageSupport : ILanguageSupport +{ + /// + /// The language/runtime identifier for Go. + /// + private const string LanguageId = "go"; + + /// + /// The code generation target language. This maps to the ICodeGenerator.Language property. + /// + private const string CodeGenTarget = "Go"; + + private const string LanguageDisplayName = "Go"; + private static readonly string[] s_detectionPatterns = ["apphost.go"]; + + /// + public string Language => LanguageId; + + /// + public Dictionary Scaffold(ScaffoldRequest request) + { + var files = new Dictionary(); + + // Create apphost.go + files["apphost.go"] = """ + // Aspire Go AppHost + // For more information, see: https://aspire.dev + + package main + + import ( + "log" + "apphost/modules/aspire" + ) + + func main() { + builder, err := aspire.CreateBuilder(nil) + if err != nil { + log.Fatalf("Failed to create builder: %v", err) + } + + // Add your resources here, for example: + // redis, _ := builder.AddRedis("cache") + // postgres, _ := builder.AddPostgres("db") + + app, err := builder.Build() + if err != nil { + log.Fatalf("Failed to build: %v", err) + } + if err := app.Run(nil); err != nil { + log.Fatalf("Failed to run: %v", err) + } + } + """; + + // Create go.mod with require and replace directives for local modules + // Go requires both a `require` directive and a `replace` directive for local modules + files["go.mod"] = """ + module apphost + + go 1.23 + + require apphost/modules/aspire v0.0.0 + + replace apphost/modules/aspire => ./.modules + """; + + // Create apphost.run.json with random ports + var random = request.PortSeed.HasValue + ? new Random(request.PortSeed.Value) + : Random.Shared; + + var httpsPort = random.Next(10000, 65000); + var httpPort = random.Next(10000, 65000); + var otlpPort = random.Next(10000, 65000); + var resourceServicePort = random.Next(10000, 65000); + + files["apphost.run.json"] = $$""" + { + "profiles": { + "https": { + "applicationUrl": "https://localhost:{{httpsPort}};http://localhost:{{httpPort}}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:{{otlpPort}}", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:{{resourceServicePort}}" + } + } + } + } + """; + + return files; + } + + /// + public DetectionResult Detect(string directoryPath) + { + var appHostPath = Path.Combine(directoryPath, "apphost.go"); + if (!File.Exists(appHostPath)) + { + return DetectionResult.NotFound; + } + + var goModPath = Path.Combine(directoryPath, "go.mod"); + if (!File.Exists(goModPath)) + { + return DetectionResult.NotFound; + } + + return DetectionResult.Found(LanguageId, "apphost.go"); + } + + /// + public RuntimeSpec GetRuntimeSpec() + { + // Note: InstallDependencies is null because "go run ." handles module + // resolution automatically, and InstallDependencies runs BEFORE code + // generation which means the .modules directory doesn't exist yet. + return new RuntimeSpec + { + Language = LanguageId, + DisplayName = LanguageDisplayName, + CodeGenLanguage = CodeGenTarget, + DetectionPatterns = s_detectionPatterns, + InstallDependencies = null, + Execute = new CommandSpec + { + Command = "go", + Args = ["run", "."] + } + }; + } +} diff --git a/src/Aspire.Hosting.CodeGeneration.Go/Resources/base.go b/src/Aspire.Hosting.CodeGeneration.Go/Resources/base.go new file mode 100644 index 00000000000..2be797c6c7f --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Go/Resources/base.go @@ -0,0 +1,185 @@ +// Package aspire provides base types and utilities for Aspire Go SDK. +package aspire + +import ( + "fmt" +) + +// HandleWrapperBase is the base type for all handle wrappers. +type HandleWrapperBase struct { + handle *Handle + client *AspireClient +} + +// NewHandleWrapperBase creates a new handle wrapper base. +func NewHandleWrapperBase(handle *Handle, client *AspireClient) HandleWrapperBase { + return HandleWrapperBase{handle: handle, client: client} +} + +// Handle returns the underlying handle. +func (h *HandleWrapperBase) Handle() *Handle { + return h.handle +} + +// Client returns the client. +func (h *HandleWrapperBase) Client() *AspireClient { + return h.client +} + +// ResourceBuilderBase extends HandleWrapperBase for resource builders. +type ResourceBuilderBase struct { + HandleWrapperBase +} + +// NewResourceBuilderBase creates a new resource builder base. +func NewResourceBuilderBase(handle *Handle, client *AspireClient) ResourceBuilderBase { + return ResourceBuilderBase{HandleWrapperBase: NewHandleWrapperBase(handle, client)} +} + +// ReferenceExpression represents a reference expression. +type ReferenceExpression struct { + Format string + Args []any +} + +// NewReferenceExpression creates a new reference expression. +func NewReferenceExpression(format string, args ...any) *ReferenceExpression { + return &ReferenceExpression{Format: format, Args: args} +} + +// RefExpr is a convenience function for creating reference expressions. +func RefExpr(format string, args ...any) *ReferenceExpression { + return NewReferenceExpression(format, args...) +} + +// ToJSON returns the reference expression as a JSON-serializable map. +func (r *ReferenceExpression) ToJSON() map[string]any { + return map[string]any{ + "$refExpr": map[string]any{ + "format": r.Format, + "args": r.Args, + }, + } +} + +// AspireList is a handle-backed list with lazy handle resolution. +type AspireList[T any] struct { + HandleWrapperBase + getterCapabilityID string + resolvedHandle *Handle +} + +// NewAspireList creates a new AspireList. +func NewAspireList[T any](handle *Handle, client *AspireClient) *AspireList[T] { + return &AspireList[T]{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + resolvedHandle: handle, + } +} + +// NewAspireListWithGetter creates a new AspireList with lazy handle resolution. +func NewAspireListWithGetter[T any](contextHandle *Handle, client *AspireClient, getterCapabilityID string) *AspireList[T] { + return &AspireList[T]{ + HandleWrapperBase: NewHandleWrapperBase(contextHandle, client), + getterCapabilityID: getterCapabilityID, + } +} + +// EnsureHandle lazily resolves the list handle. +func (l *AspireList[T]) EnsureHandle() *Handle { + if l.resolvedHandle != nil { + return l.resolvedHandle + } + if l.getterCapabilityID != "" { + result, err := l.client.InvokeCapability(l.getterCapabilityID, map[string]any{ + "context": l.handle.ToJSON(), + }) + if err == nil { + if handle, ok := result.(*Handle); ok { + l.resolvedHandle = handle + } + } + } + if l.resolvedHandle == nil { + l.resolvedHandle = l.handle + } + return l.resolvedHandle +} + +// AspireDict is a handle-backed dictionary with lazy handle resolution. +type AspireDict[K comparable, V any] struct { + HandleWrapperBase + getterCapabilityID string + resolvedHandle *Handle +} + +// NewAspireDict creates a new AspireDict. +func NewAspireDict[K comparable, V any](handle *Handle, client *AspireClient) *AspireDict[K, V] { + return &AspireDict[K, V]{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + resolvedHandle: handle, + } +} + +// NewAspireDictWithGetter creates a new AspireDict with lazy handle resolution. +func NewAspireDictWithGetter[K comparable, V any](contextHandle *Handle, client *AspireClient, getterCapabilityID string) *AspireDict[K, V] { + return &AspireDict[K, V]{ + HandleWrapperBase: NewHandleWrapperBase(contextHandle, client), + getterCapabilityID: getterCapabilityID, + } +} + +// EnsureHandle lazily resolves the dict handle. +func (d *AspireDict[K, V]) EnsureHandle() *Handle { + if d.resolvedHandle != nil { + return d.resolvedHandle + } + if d.getterCapabilityID != "" { + result, err := d.client.InvokeCapability(d.getterCapabilityID, map[string]any{ + "context": d.handle.ToJSON(), + }) + if err == nil { + if handle, ok := result.(*Handle); ok { + d.resolvedHandle = handle + } + } + } + if d.resolvedHandle == nil { + d.resolvedHandle = d.handle + } + return d.resolvedHandle +} + +// SerializeValue converts a value to its JSON representation. +func SerializeValue(value any) any { + if value == nil { + return nil + } + + switch v := value.(type) { + case *Handle: + return v.ToJSON() + case *ReferenceExpression: + return v.ToJSON() + case interface{ ToJSON() map[string]any }: + return v.ToJSON() + case interface{ Handle() *Handle }: + return v.Handle().ToJSON() + case []any: + result := make([]any, len(v)) + for i, item := range v { + result[i] = SerializeValue(item) + } + return result + case map[string]any: + result := make(map[string]any) + for k, val := range v { + result[k] = SerializeValue(val) + } + return result + case fmt.Stringer: + return v.String() + default: + return value + } +} diff --git a/src/Aspire.Hosting.CodeGeneration.Go/Resources/transport.go b/src/Aspire.Hosting.CodeGeneration.Go/Resources/transport.go new file mode 100644 index 00000000000..79e8c8c5d27 --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Go/Resources/transport.go @@ -0,0 +1,488 @@ +// Package aspire provides the ATS transport layer for JSON-RPC communication. +package aspire + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "os" + "runtime" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +// AtsErrorCodes contains standard ATS error codes. +var AtsErrorCodes = struct { + CapabilityNotFound string + HandleNotFound string + TypeMismatch string + InvalidArgument string + ArgumentOutOfRange string + CallbackError string + InternalError string +}{ + CapabilityNotFound: "CAPABILITY_NOT_FOUND", + HandleNotFound: "HANDLE_NOT_FOUND", + TypeMismatch: "TYPE_MISMATCH", + InvalidArgument: "INVALID_ARGUMENT", + ArgumentOutOfRange: "ARGUMENT_OUT_OF_RANGE", + CallbackError: "CALLBACK_ERROR", + InternalError: "INTERNAL_ERROR", +} + +// CapabilityError represents an error returned from a capability invocation. +type CapabilityError struct { + Code string `json:"code"` + Message string `json:"message"` + Capability string `json:"capability,omitempty"` +} + +func (e *CapabilityError) Error() string { + return e.Message +} + +// Handle represents a reference to a server-side object. +type Handle struct { + HandleID string `json:"$handle"` + TypeID string `json:"$type"` +} + +// ToJSON returns the handle as a JSON-serializable map. +func (h *Handle) ToJSON() map[string]string { + return map[string]string{ + "$handle": h.HandleID, + "$type": h.TypeID, + } +} + +func (h *Handle) String() string { + return fmt.Sprintf("Handle<%s>(%s)", h.TypeID, h.HandleID) +} + +// IsMarshalledHandle checks if a value is a marshalled handle. +func IsMarshalledHandle(value any) bool { + m, ok := value.(map[string]any) + if !ok { + return false + } + _, hasHandle := m["$handle"] + _, hasType := m["$type"] + return hasHandle && hasType +} + +// IsAtsError checks if a value is an ATS error. +func IsAtsError(value any) bool { + m, ok := value.(map[string]any) + if !ok { + return false + } + _, hasError := m["$error"] + return hasError +} + +// HandleWrapperFactory creates a wrapper for a handle. +type HandleWrapperFactory func(handle *Handle, client *AspireClient) any + +var ( + handleWrapperRegistry = make(map[string]HandleWrapperFactory) + handleWrapperMu sync.RWMutex +) + +// RegisterHandleWrapper registers a factory for wrapping handles of a specific type. +func RegisterHandleWrapper(typeID string, factory HandleWrapperFactory) { + handleWrapperMu.Lock() + defer handleWrapperMu.Unlock() + handleWrapperRegistry[typeID] = factory +} + +// WrapIfHandle wraps a value if it's a marshalled handle. +func WrapIfHandle(value any, client *AspireClient) any { + if !IsMarshalledHandle(value) { + return value + } + m := value.(map[string]any) + handle := &Handle{ + HandleID: m["$handle"].(string), + TypeID: m["$type"].(string), + } + if client != nil { + handleWrapperMu.RLock() + factory, ok := handleWrapperRegistry[handle.TypeID] + handleWrapperMu.RUnlock() + if ok { + return factory(handle, client) + } + } + return handle +} + +// Callback management +var ( + callbackRegistry = make(map[string]func(...any) any) + callbackMu sync.RWMutex + callbackCounter atomic.Int64 +) + +// RegisterCallback registers a callback and returns its ID. +func RegisterCallback(callback func(...any) any) string { + callbackMu.Lock() + defer callbackMu.Unlock() + id := fmt.Sprintf("callback_%d_%d", callbackCounter.Add(1), time.Now().UnixMilli()) + callbackRegistry[id] = callback + return id +} + +// UnregisterCallback removes a callback by ID. +func UnregisterCallback(callbackID string) bool { + callbackMu.Lock() + defer callbackMu.Unlock() + _, exists := callbackRegistry[callbackID] + delete(callbackRegistry, callbackID) + return exists +} + +// CancellationToken provides cooperative cancellation. +type CancellationToken struct { + cancelled atomic.Bool + callbacks []func() + mu sync.Mutex +} + +// NewCancellationToken creates a new cancellation token. +func NewCancellationToken() *CancellationToken { + return &CancellationToken{} +} + +// Cancel cancels the token and invokes all registered callbacks. +func (ct *CancellationToken) Cancel() { + if ct.cancelled.Swap(true) { + return // Already cancelled + } + ct.mu.Lock() + callbacks := ct.callbacks + ct.callbacks = nil + ct.mu.Unlock() + for _, cb := range callbacks { + cb() + } +} + +// IsCancelled returns true if the token has been cancelled. +func (ct *CancellationToken) IsCancelled() bool { + return ct.cancelled.Load() +} + +// Register registers a callback to be invoked when cancelled. +func (ct *CancellationToken) Register(callback func()) func() { + if ct.IsCancelled() { + callback() + return func() {} + } + ct.mu.Lock() + ct.callbacks = append(ct.callbacks, callback) + ct.mu.Unlock() + return func() { + ct.mu.Lock() + defer ct.mu.Unlock() + for i, cb := range ct.callbacks { + if &cb == &callback { + ct.callbacks = append(ct.callbacks[:i], ct.callbacks[i+1:]...) + break + } + } + } +} + +// RegisterCancellation registers a cancellation token with the client. +func RegisterCancellation(token *CancellationToken, client *AspireClient) string { + if token == nil { + return "" + } + id := fmt.Sprintf("ct_%d_%d", time.Now().UnixMilli(), time.Now().UnixNano()) + token.Register(func() { + client.CancelToken(id) + }) + return id +} + +// AspireClient manages the connection to the AppHost server. +type AspireClient struct { + socketPath string + conn io.ReadWriteCloser + reader *bufio.Reader + nextID atomic.Int64 + disconnectCallbacks []func() + connected bool + ioMu sync.Mutex +} + +// NewAspireClient creates a new client for the given socket path. +func NewAspireClient(socketPath string) *AspireClient { + return &AspireClient{ + socketPath: socketPath, + } +} + +// Connect establishes the connection to the AppHost server. +func (c *AspireClient) Connect() error { + if c.connected { + return nil + } + + conn, err := openConnection(c.socketPath) + if err != nil { + return fmt.Errorf("failed to connect to AppHost: %w", err) + } + + c.conn = conn + c.reader = bufio.NewReader(conn) + c.connected = true + return nil +} + +// OnDisconnect registers a callback for disconnection. +func (c *AspireClient) OnDisconnect(callback func()) { + c.disconnectCallbacks = append(c.disconnectCallbacks, callback) +} + +// InvokeCapability invokes a capability on the server. +func (c *AspireClient) InvokeCapability(capabilityID string, args map[string]any) (any, error) { + result, err := c.sendRequest("invokeCapability", []any{capabilityID, args}) + if err != nil { + return nil, err + } + if IsAtsError(result) { + errMap := result.(map[string]any)["$error"].(map[string]any) + return nil, &CapabilityError{ + Code: getString(errMap, "code"), + Message: getString(errMap, "message"), + Capability: getString(errMap, "capability"), + } + } + return WrapIfHandle(result, c), nil +} + +// CancelToken cancels a cancellation token on the server. +func (c *AspireClient) CancelToken(tokenID string) bool { + result, err := c.sendRequest("cancelToken", []any{tokenID}) + if err != nil { + return false + } + b, _ := result.(bool) + return b +} + +// Disconnect closes the connection. +func (c *AspireClient) Disconnect() { + c.connected = false + if c.conn != nil { + c.conn.Close() + c.conn = nil + } + for _, cb := range c.disconnectCallbacks { + cb() + } +} + +func (c *AspireClient) sendRequest(method string, params []any) (any, error) { + c.ioMu.Lock() + defer c.ioMu.Unlock() + + requestID := c.nextID.Add(1) + message := map[string]any{ + "jsonrpc": "2.0", + "id": requestID, + "method": method, + "params": params, + } + + if err := c.writeMessage(message); err != nil { + return nil, err + } + + // Read messages until we get our response + for { + response, err := c.readMessage() + if err != nil { + return nil, fmt.Errorf("connection closed while waiting for response: %w", err) + } + + // Check if this is a callback request from the server + if _, hasMethod := response["method"]; hasMethod { + c.handleCallbackRequest(response) + continue + } + + // This is a response - check if it's our response + if respID, ok := response["id"].(float64); ok && int64(respID) == requestID { + if errObj, hasErr := response["error"]; hasErr { + errMap := errObj.(map[string]any) + return nil, errors.New(getString(errMap, "message")) + } + return response["result"], nil + } + } +} + +func (c *AspireClient) writeMessage(message map[string]any) error { + if c.conn == nil { + return errors.New("not connected to AppHost") + } + body, err := json.Marshal(message) + if err != nil { + return err + } + header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(body)) + _, err = c.conn.Write([]byte(header)) + if err != nil { + return err + } + _, err = c.conn.Write(body) + return err +} + +func (c *AspireClient) handleCallbackRequest(message map[string]any) { + method := getString(message, "method") + requestID := message["id"] + + if method != "invokeCallback" { + if requestID != nil { + c.writeMessage(map[string]any{ + "jsonrpc": "2.0", + "id": requestID, + "error": map[string]any{"code": -32601, "message": fmt.Sprintf("Unknown method: %s", method)}, + }) + } + return + } + + params, _ := message["params"].([]any) + var callbackID string + var args any + if len(params) > 0 { + callbackID, _ = params[0].(string) + } + if len(params) > 1 { + args = params[1] + } + + result, err := invokeCallback(callbackID, args, c) + if err != nil { + c.writeMessage(map[string]any{ + "jsonrpc": "2.0", + "id": requestID, + "error": map[string]any{"code": -32000, "message": err.Error()}, + }) + return + } + c.writeMessage(map[string]any{ + "jsonrpc": "2.0", + "id": requestID, + "result": result, + }) +} + +func (c *AspireClient) readMessage() (map[string]any, error) { + if c.reader == nil { + return nil, errors.New("not connected") + } + + headers := make(map[string]string) + for { + line, err := c.reader.ReadString('\n') + if err != nil { + return nil, err + } + line = strings.TrimSpace(line) + if line == "" { + break + } + parts := strings.SplitN(line, ":", 2) + if len(parts) == 2 { + headers[strings.TrimSpace(strings.ToLower(parts[0]))] = strings.TrimSpace(parts[1]) + } + } + + lengthStr := headers["content-length"] + length, err := strconv.Atoi(lengthStr) + if err != nil || length <= 0 { + return nil, errors.New("invalid content-length") + } + + body := make([]byte, length) + _, err = io.ReadFull(c.reader, body) + if err != nil { + return nil, err + } + + var message map[string]any + if err := json.Unmarshal(body, &message); err != nil { + return nil, err + } + return message, nil +} + +func invokeCallback(callbackID string, args any, client *AspireClient) (any, error) { + if callbackID == "" { + return nil, errors.New("callback ID missing") + } + + callbackMu.RLock() + callback, ok := callbackRegistry[callbackID] + callbackMu.RUnlock() + if !ok { + return nil, fmt.Errorf("callback not found: %s", callbackID) + } + + // Convert args to positional arguments + var positionalArgs []any + if argsMap, ok := args.(map[string]any); ok { + for i := 0; ; i++ { + key := fmt.Sprintf("p%d", i) + if val, exists := argsMap[key]; exists { + positionalArgs = append(positionalArgs, WrapIfHandle(val, client)) + } else { + break + } + } + } else if args != nil { + positionalArgs = append(positionalArgs, WrapIfHandle(args, client)) + } + + return callback(positionalArgs...), nil +} + +func getString(m map[string]any, key string) string { + if v, ok := m[key]; ok { + if s, ok := v.(string); ok { + return s + } + } + return "" +} + +func openConnection(socketPath string) (io.ReadWriteCloser, error) { + if runtime.GOOS == "windows" { + // On Windows, use named pipes + pipePath := `\\.\pipe\` + socketPath + return openNamedPipe(pipePath) + } + // On Unix, use Unix domain sockets + return net.Dial("unix", socketPath) +} + +// openNamedPipe opens a Windows named pipe. +func openNamedPipe(path string) (io.ReadWriteCloser, error) { + // Use os.OpenFile for named pipes on Windows + f, err := os.OpenFile(path, os.O_RDWR, 0) + if err != nil { + return nil, err + } + return f, nil +} diff --git a/src/Aspire.Hosting.CodeGeneration.Java/Aspire.Hosting.CodeGeneration.Java.csproj b/src/Aspire.Hosting.CodeGeneration.Java/Aspire.Hosting.CodeGeneration.Java.csproj new file mode 100644 index 00000000000..0de51e54c4d --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Java/Aspire.Hosting.CodeGeneration.Java.csproj @@ -0,0 +1,29 @@ + + + + $(DefaultTargetFramework) + enable + enable + Aspire.Hosting.CodeGeneration.Java + + true + + true + true + + + + + + + + + + + + + + + + + diff --git a/src/Aspire.Hosting.CodeGeneration.Java/AtsJavaCodeGenerator.cs b/src/Aspire.Hosting.CodeGeneration.Java/AtsJavaCodeGenerator.cs new file mode 100644 index 00000000000..050a449bc0f --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Java/AtsJavaCodeGenerator.cs @@ -0,0 +1,831 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Reflection; +using System.Text; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Ats; + +namespace Aspire.Hosting.CodeGeneration.Java; + +/// +/// Generates a Java SDK using the ATS (Aspire Type System) capability-based API. +/// Produces wrapper classes that proxy capabilities via JSON-RPC. +/// +public sealed class AtsJavaCodeGenerator : ICodeGenerator +{ + private static readonly HashSet s_javaKeywords = new(StringComparer.Ordinal) + { + "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", + "class", "const", "continue", "default", "do", "double", "else", "enum", + "extends", "final", "finally", "float", "for", "goto", "if", "implements", + "import", "instanceof", "int", "interface", "long", "native", "new", "package", + "private", "protected", "public", "return", "short", "static", "strictfp", + "super", "switch", "synchronized", "this", "throw", "throws", "transient", + "try", "void", "volatile", "while", "true", "false", "null" + }; + + private TextWriter _writer = null!; + private readonly Dictionary _classNames = new(StringComparer.Ordinal); + private readonly Dictionary _dtoNames = new(StringComparer.Ordinal); + private readonly Dictionary _enumNames = new(StringComparer.Ordinal); + + /// + public string Language => "Java"; + + /// + public Dictionary GenerateDistributedApplication(AtsContext context) + { + return new Dictionary(StringComparer.Ordinal) + { + ["Transport.java"] = GetEmbeddedResource("Transport.java"), + ["Base.java"] = GetEmbeddedResource("Base.java"), + ["Aspire.java"] = GenerateAspireSdk(context) + }; + } + + private static string GetEmbeddedResource(string name) + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = $"Aspire.Hosting.CodeGeneration.Java.Resources.{name}"; + + using var stream = assembly.GetManifestResourceStream(resourceName) + ?? throw new InvalidOperationException($"Embedded resource '{name}' not found."); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private string GenerateAspireSdk(AtsContext context) + { + using var stringWriter = new StringWriter(CultureInfo.InvariantCulture); + _writer = stringWriter; + + var capabilities = context.Capabilities; + var dtoTypes = context.DtoTypes; + var enumTypes = context.EnumTypes; + + _enumNames.Clear(); + foreach (var enumType in enumTypes) + { + _enumNames[enumType.TypeId] = SanitizeIdentifier(enumType.Name); + } + + _dtoNames.Clear(); + foreach (var dto in dtoTypes) + { + _dtoNames[dto.TypeId] = SanitizeIdentifier(dto.Name); + } + + var handleTypes = BuildHandleTypes(context); + var capabilitiesByTarget = GroupCapabilitiesByTarget(capabilities); + var collectionTypes = CollectListAndDictTypeIds(capabilities); + + WriteHeader(); + GenerateEnumTypes(enumTypes); + GenerateDtoTypes(dtoTypes); + GenerateHandleTypes(handleTypes, capabilitiesByTarget); + GenerateHandleWrapperRegistrations(handleTypes, collectionTypes); + GenerateConnectionHelpers(); + WriteFooter(); + + return stringWriter.ToString(); + } + + private void WriteHeader() + { + WriteLine("// Aspire.java - Capability-based Aspire SDK"); + WriteLine("// GENERATED CODE - DO NOT EDIT"); + WriteLine(); + WriteLine("package aspire;"); + WriteLine(); + WriteLine("import java.util.*;"); + WriteLine("import java.util.function.*;"); + WriteLine(); + } + + private static void WriteFooter() + { + // Close the package-level class if needed + } + + private void GenerateEnumTypes(IReadOnlyList enumTypes) + { + if (enumTypes.Count == 0) + { + return; + } + + WriteLine("// ============================================================================"); + WriteLine("// Enums"); + WriteLine("// ============================================================================"); + WriteLine(); + + foreach (var enumType in enumTypes) + { + if (enumType.ClrType is null) + { + continue; + } + + var enumName = _enumNames[enumType.TypeId]; + WriteLine($"/** {enumType.Name} enum. */"); + WriteLine($"enum {enumName} {{"); + var members = Enum.GetNames(enumType.ClrType); + for (var i = 0; i < members.Length; i++) + { + var member = members[i]; + var memberName = ToUpperSnakeCase(member); + var suffix = i < members.Length - 1 ? "," : ";"; + WriteLine($" {memberName}(\"{member}\"){suffix}"); + } + WriteLine(); + WriteLine(" private final String value;"); + WriteLine(); + WriteLine($" {enumName}(String value) {{"); + WriteLine(" this.value = value;"); + WriteLine(" }"); + WriteLine(); + WriteLine(" public String getValue() { return value; }"); + WriteLine(); + WriteLine($" public static {enumName} fromValue(String value) {{"); + WriteLine($" for ({enumName} e : values()) {{"); + WriteLine(" if (e.value.equals(value)) return e;"); + WriteLine(" }"); + WriteLine(" throw new IllegalArgumentException(\"Unknown value: \" + value);"); + WriteLine(" }"); + WriteLine("}"); + WriteLine(); + } + } + + private void GenerateDtoTypes(IReadOnlyList dtoTypes) + { + if (dtoTypes.Count == 0) + { + return; + } + + WriteLine("// ============================================================================"); + WriteLine("// DTOs"); + WriteLine("// ============================================================================"); + WriteLine(); + + foreach (var dto in dtoTypes) + { + // Skip ReferenceExpression - it's defined in Base.java + if (dto.TypeId == AtsConstants.ReferenceExpressionTypeId) + { + continue; + } + + var dtoName = _dtoNames[dto.TypeId]; + WriteLine($"/** {dto.Name} DTO. */"); + WriteLine($"class {dtoName} {{"); + + // Fields + foreach (var property in dto.Properties) + { + var fieldName = ToCamelCase(property.Name); + var fieldType = MapTypeRefToJava(property.Type, property.IsOptional); + WriteLine($" private {fieldType} {fieldName};"); + } + WriteLine(); + + // Getters and setters + foreach (var property in dto.Properties) + { + var fieldName = ToCamelCase(property.Name); + var methodName = ToPascalCase(property.Name); + var fieldType = MapTypeRefToJava(property.Type, property.IsOptional); + + WriteLine($" public {fieldType} get{methodName}() {{ return {fieldName}; }}"); + WriteLine($" public void set{methodName}({fieldType} value) {{ this.{fieldName} = value; }}"); + } + WriteLine(); + + // toMap method for serialization + WriteLine(" public Map toMap() {"); + WriteLine(" Map map = new HashMap<>();"); + foreach (var property in dto.Properties) + { + var fieldName = ToCamelCase(property.Name); + WriteLine($" map.put(\"{property.Name}\", AspireClient.serializeValue({fieldName}));"); + } + WriteLine(" return map;"); + WriteLine(" }"); + + WriteLine("}"); + WriteLine(); + } + } + + private void GenerateHandleTypes( + IReadOnlyList handleTypes, + Dictionary> capabilitiesByTarget) + { + if (handleTypes.Count == 0) + { + return; + } + + WriteLine("// ============================================================================"); + WriteLine("// Handle Wrappers"); + WriteLine("// ============================================================================"); + WriteLine(); + + foreach (var handleType in handleTypes.OrderBy(t => t.ClassName, StringComparer.Ordinal)) + { + var baseClass = handleType.IsResourceBuilder ? "ResourceBuilderBase" : "HandleWrapperBase"; + WriteLine($"/** Wrapper for {handleType.TypeId}. */"); + WriteLine($"class {handleType.ClassName} extends {baseClass} {{"); + WriteLine($" {handleType.ClassName}(Handle handle, AspireClient client) {{"); + WriteLine(" super(handle, client);"); + WriteLine(" }"); + WriteLine(); + + if (capabilitiesByTarget.TryGetValue(handleType.TypeId, out var methods)) + { + foreach (var method in methods) + { + GenerateCapabilityMethod(method); + } + } + + WriteLine("}"); + WriteLine(); + } + } + + private void GenerateCapabilityMethod(AtsCapabilityInfo capability) + { + var targetParamName = capability.TargetParameterName ?? "builder"; + var methodName = ToCamelCase(capability.MethodName); + var parameters = capability.Parameters + .Where(p => !string.Equals(p.Name, targetParamName, StringComparison.Ordinal)) + .ToList(); + + // Check if this is a List/Dict property getter (no parameters, returns List/Dict) + if (parameters.Count == 0 && IsListOrDictPropertyGetter(capability.ReturnType)) + { + GenerateListOrDictProperty(capability, methodName); + return; + } + + var returnType = MapTypeRefToJava(capability.ReturnType, false); + var hasReturn = capability.ReturnType.TypeId != AtsConstants.Void; + + // Build parameter list + var paramList = new StringBuilder(); + foreach (var parameter in parameters) + { + if (paramList.Length > 0) + { + paramList.Append(", "); + } + var paramName = ToCamelCase(parameter.Name); + var paramType = parameter.IsCallback + ? "Function" + : IsCancellationToken(parameter) + ? "CancellationToken" + : MapTypeRefToJava(parameter.Type, parameter.IsOptional); + paramList.Append(CultureInfo.InvariantCulture, $"{paramType} {paramName}"); + } + + // Generate Javadoc + if (!string.IsNullOrEmpty(capability.Description)) + { + WriteLine($" /** {capability.Description} */"); + } + + WriteLine($" public {returnType} {methodName}({paramList}) {{"); + WriteLine(" Map reqArgs = new HashMap<>();"); + WriteLine($" reqArgs.put(\"{targetParamName}\", AspireClient.serializeValue(getHandle()));"); + + foreach (var parameter in parameters) + { + var paramName = ToCamelCase(parameter.Name); + if (parameter.IsCallback) + { + WriteLine($" if ({paramName} != null) {{"); + WriteLine($" reqArgs.put(\"{parameter.Name}\", getClient().registerCallback({paramName}));"); + WriteLine(" }"); + continue; + } + + if (IsCancellationToken(parameter)) + { + WriteLine($" if ({paramName} != null) {{"); + WriteLine($" reqArgs.put(\"{parameter.Name}\", getClient().registerCancellation({paramName}));"); + WriteLine(" }"); + continue; + } + + if (parameter.IsOptional) + { + WriteLine($" if ({paramName} != null) {{"); + WriteLine($" reqArgs.put(\"{parameter.Name}\", AspireClient.serializeValue({paramName}));"); + WriteLine(" }"); + } + else + { + WriteLine($" reqArgs.put(\"{parameter.Name}\", AspireClient.serializeValue({paramName}));"); + } + } + + if (hasReturn) + { + WriteLine($" return ({returnType}) getClient().invokeCapability(\"{capability.CapabilityId}\", reqArgs);"); + } + else + { + WriteLine($" getClient().invokeCapability(\"{capability.CapabilityId}\", reqArgs);"); + } + + WriteLine(" }"); + WriteLine(); + } + + private static bool IsListOrDictPropertyGetter(AtsTypeRef? returnType) + { + if (returnType is null) + { + return false; + } + + return returnType.Category == AtsTypeCategory.List || returnType.Category == AtsTypeCategory.Dict; + } + + private void GenerateListOrDictProperty(AtsCapabilityInfo capability, string methodName) + { + var returnType = capability.ReturnType!; + var isDict = returnType.Category == AtsTypeCategory.Dict; + var wrapperType = isDict ? "AspireDict" : "AspireList"; + + // Determine type arguments + string typeArgs; + if (isDict) + { + var keyType = MapTypeRefToJava(returnType.KeyType, false); + var valueType = MapTypeRefToJava(returnType.ValueType, false); + // Use boxed types for generics + keyType = BoxPrimitiveType(keyType); + valueType = BoxPrimitiveType(valueType); + typeArgs = $"<{keyType}, {valueType}>"; + } + else + { + var elementType = MapTypeRefToJava(returnType.ElementType, false); + // Use boxed types for generics + elementType = BoxPrimitiveType(elementType); + typeArgs = $"<{elementType}>"; + } + + var fullType = $"{wrapperType}{typeArgs}"; + var fieldName = methodName + "Field"; + + // Generate Javadoc + if (!string.IsNullOrEmpty(capability.Description)) + { + WriteLine($" /** {capability.Description} */"); + } + + // Generate private field and getter + WriteLine($" private {fullType} {fieldName};"); + WriteLine($" public {fullType} {methodName}() {{"); + WriteLine($" if ({fieldName} == null) {{"); + WriteLine($" {fieldName} = new {wrapperType}<>(getHandle(), getClient(), \"{capability.CapabilityId}\");"); + WriteLine(" }"); + WriteLine($" return {fieldName};"); + WriteLine(" }"); + WriteLine(); + } + + private static string BoxPrimitiveType(string type) + { + return type switch + { + "int" => "Integer", + "long" => "Long", + "double" => "Double", + "float" => "Float", + "boolean" => "Boolean", + "char" => "Character", + "byte" => "Byte", + "short" => "Short", + _ => type + }; + } + + private void GenerateHandleWrapperRegistrations( + IReadOnlyList handleTypes, + Dictionary collectionTypes) + { + WriteLine("// ============================================================================"); + WriteLine("// Handle wrapper registrations"); + WriteLine("// ============================================================================"); + WriteLine(); + WriteLine("/** Static initializer to register handle wrappers. */"); + WriteLine("class AspireRegistrations {"); + WriteLine(" static {"); + + foreach (var handleType in handleTypes) + { + WriteLine($" AspireClient.registerHandleWrapper(\"{handleType.TypeId}\", (h, c) -> new {handleType.ClassName}(h, c));"); + } + + foreach (var (typeId, isDict) in collectionTypes) + { + var wrapperType = isDict ? "AspireDict" : "AspireList"; + WriteLine($" AspireClient.registerHandleWrapper(\"{typeId}\", (h, c) -> new {wrapperType}(h, c));"); + } + + WriteLine(" }"); + WriteLine(); + WriteLine(" static void ensureRegistered() {"); + WriteLine(" // Called to trigger static initializer"); + WriteLine(" }"); + WriteLine("}"); + WriteLine(); + } + + private void GenerateConnectionHelpers() + { + var builderClassName = _classNames.TryGetValue(AtsConstants.BuilderTypeId, out var name) + ? name + : "DistributedApplicationBuilder"; + + WriteLine("// ============================================================================"); + WriteLine("// Connection Helpers"); + WriteLine("// ============================================================================"); + WriteLine(); + WriteLine("/** Main entry point for Aspire SDK. */"); + WriteLine("public class Aspire {"); + WriteLine(" /** Connect to the AppHost server. */"); + WriteLine(" public static AspireClient connect() throws Exception {"); + WriteLine(" AspireRegistrations.ensureRegistered();"); + WriteLine(" String socketPath = System.getenv(\"REMOTE_APP_HOST_SOCKET_PATH\");"); + WriteLine(" if (socketPath == null || socketPath.isEmpty()) {"); + WriteLine(" throw new RuntimeException(\"REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`.\");"); + WriteLine(" }"); + WriteLine(" AspireClient client = new AspireClient(socketPath);"); + WriteLine(" client.connect();"); + WriteLine(" client.onDisconnect(() -> System.exit(1));"); + WriteLine(" return client;"); + WriteLine(" }"); + WriteLine(); + WriteLine($" /** Create a new distributed application builder. */"); + WriteLine($" public static {builderClassName} createBuilder(CreateBuilderOptions options) throws Exception {{"); + WriteLine(" AspireClient client = connect();"); + WriteLine(" Map resolvedOptions = new HashMap<>();"); + WriteLine(" if (options != null) {"); + WriteLine(" resolvedOptions.putAll(options.toMap());"); + WriteLine(" }"); + WriteLine(" if (!resolvedOptions.containsKey(\"Args\")) {"); + WriteLine(" // Note: Java doesn't have easy access to command line args from here"); + WriteLine(" resolvedOptions.put(\"Args\", new String[0]);"); + WriteLine(" }"); + WriteLine(" if (!resolvedOptions.containsKey(\"ProjectDirectory\")) {"); + WriteLine(" resolvedOptions.put(\"ProjectDirectory\", System.getProperty(\"user.dir\"));"); + WriteLine(" }"); + WriteLine(" Map args = new HashMap<>();"); + WriteLine(" args.put(\"options\", resolvedOptions);"); + WriteLine($" return ({builderClassName}) client.invokeCapability(\"Aspire.Hosting/createBuilderWithOptions\", args);"); + WriteLine(" }"); + WriteLine("}"); + WriteLine(); + } + + private IReadOnlyList BuildHandleTypes(AtsContext context) + { + var handleTypeIds = new HashSet(StringComparer.Ordinal); + foreach (var handleType in context.HandleTypes) + { + // Skip ReferenceExpression - it's defined in Base.java + if (handleType.AtsTypeId == AtsConstants.ReferenceExpressionTypeId) + { + continue; + } + handleTypeIds.Add(handleType.AtsTypeId); + } + + foreach (var capability in context.Capabilities) + { + AddHandleTypeIfNeeded(handleTypeIds, capability.TargetType); + AddHandleTypeIfNeeded(handleTypeIds, capability.ReturnType); + foreach (var parameter in capability.Parameters) + { + AddHandleTypeIfNeeded(handleTypeIds, parameter.Type); + if (parameter.IsCallback && parameter.CallbackParameters is not null) + { + foreach (var callbackParam in parameter.CallbackParameters) + { + AddHandleTypeIfNeeded(handleTypeIds, callbackParam.Type); + } + } + } + } + + _classNames.Clear(); + foreach (var typeId in handleTypeIds) + { + _classNames[typeId] = CreateClassName(typeId); + } + + var handleTypeMap = context.HandleTypes.ToDictionary(t => t.AtsTypeId, StringComparer.Ordinal); + var results = new List(); + foreach (var typeId in handleTypeIds) + { + var isResourceBuilder = false; + if (handleTypeMap.TryGetValue(typeId, out var typeInfo)) + { + isResourceBuilder = typeInfo.ClrType is not null && + typeof(IResource).IsAssignableFrom(typeInfo.ClrType); + } + + results.Add(new JavaHandleType(typeId, _classNames[typeId], isResourceBuilder)); + } + + return results; + } + + private static Dictionary> GroupCapabilitiesByTarget( + IReadOnlyList capabilities) + { + var result = new Dictionary>(StringComparer.Ordinal); + + foreach (var capability in capabilities) + { + if (string.IsNullOrEmpty(capability.TargetTypeId)) + { + continue; + } + + var targetTypes = capability.ExpandedTargetTypes.Count > 0 + ? capability.ExpandedTargetTypes + : capability.TargetType is not null + ? [capability.TargetType] + : []; + + foreach (var targetType in targetTypes) + { + if (targetType.TypeId is null) + { + continue; + } + + if (!result.TryGetValue(targetType.TypeId, out var list)) + { + list = new List(); + result[targetType.TypeId] = list; + } + list.Add(capability); + } + } + + return result; + } + + private static Dictionary CollectListAndDictTypeIds(IReadOnlyList capabilities) + { + // Maps typeId -> isDict (true for Dict, false for List) + var typeIds = new Dictionary(StringComparer.Ordinal); + foreach (var capability in capabilities) + { + AddListOrDictTypeIfNeeded(typeIds, capability.TargetType); + AddListOrDictTypeIfNeeded(typeIds, capability.ReturnType); + foreach (var parameter in capability.Parameters) + { + AddListOrDictTypeIfNeeded(typeIds, parameter.Type); + if (parameter.IsCallback && parameter.CallbackParameters is not null) + { + foreach (var callbackParam in parameter.CallbackParameters) + { + AddListOrDictTypeIfNeeded(typeIds, callbackParam.Type); + } + } + } + } + + return typeIds; + } + + private string MapTypeRefToJava(AtsTypeRef? typeRef, bool isOptional) + { + if (typeRef is null) + { + return "Object"; + } + + if (typeRef.TypeId == AtsConstants.ReferenceExpressionTypeId) + { + return "ReferenceExpression"; + } + + var baseType = typeRef.Category switch + { + AtsTypeCategory.Primitive => MapPrimitiveType(typeRef.TypeId, isOptional), + AtsTypeCategory.Enum => MapEnumType(typeRef.TypeId), + AtsTypeCategory.Handle => MapHandleType(typeRef.TypeId), + AtsTypeCategory.Dto => MapDtoType(typeRef.TypeId), + AtsTypeCategory.Callback => "Function", + AtsTypeCategory.Array => $"{MapTypeRefToJava(typeRef.ElementType, false)}[]", + AtsTypeCategory.List => typeRef.IsReadOnly + ? $"List<{MapTypeRefToJava(typeRef.ElementType, false)}>" + : $"AspireList<{MapTypeRefToJava(typeRef.ElementType, false)}>", + AtsTypeCategory.Dict => typeRef.IsReadOnly + ? $"Map<{MapTypeRefToJava(typeRef.KeyType, false)}, {MapTypeRefToJava(typeRef.ValueType, false)}>" + : $"AspireDict<{MapTypeRefToJava(typeRef.KeyType, false)}, {MapTypeRefToJava(typeRef.ValueType, false)}>", + AtsTypeCategory.Union => "Object", + AtsTypeCategory.Unknown => "Object", + _ => "Object" + }; + + return baseType; + } + + private string MapHandleType(string typeId) => + _classNames.TryGetValue(typeId, out var name) ? name : "Handle"; + + private string MapDtoType(string typeId) => + _dtoNames.TryGetValue(typeId, out var name) ? name : "Map"; + + private string MapEnumType(string typeId) => + _enumNames.TryGetValue(typeId, out var name) ? name : "String"; + + private static string MapPrimitiveType(string typeId, bool isOptional) => typeId switch + { + AtsConstants.String or AtsConstants.Char => "String", + AtsConstants.Number => isOptional ? "Double" : "double", + AtsConstants.Boolean => isOptional ? "Boolean" : "boolean", + AtsConstants.Void => "void", + AtsConstants.Any => "Object", + AtsConstants.DateTime or AtsConstants.DateTimeOffset or + AtsConstants.DateOnly or AtsConstants.TimeOnly => "String", + AtsConstants.TimeSpan => isOptional ? "Double" : "double", + AtsConstants.Guid or AtsConstants.Uri => "String", + AtsConstants.CancellationToken => "CancellationToken", + _ => "Object" + }; + + private static bool IsCancellationToken(AtsParameterInfo parameter) => + parameter.Type?.TypeId == AtsConstants.CancellationToken; + + private static void AddHandleTypeIfNeeded(HashSet handleTypeIds, AtsTypeRef? typeRef) + { + if (typeRef is null) + { + return; + } + + // Skip ReferenceExpression - it's defined in Base.java + if (typeRef.TypeId == AtsConstants.ReferenceExpressionTypeId) + { + return; + } + + if (typeRef.Category == AtsTypeCategory.Handle) + { + handleTypeIds.Add(typeRef.TypeId); + } + } + + private static void AddListOrDictTypeIfNeeded(Dictionary typeIds, AtsTypeRef? typeRef) + { + if (typeRef is null) + { + return; + } + + if (typeRef.Category == AtsTypeCategory.List) + { + if (!typeRef.IsReadOnly) + { + typeIds[typeRef.TypeId] = false; // false = List + } + } + else if (typeRef.Category == AtsTypeCategory.Dict) + { + if (!typeRef.IsReadOnly) + { + typeIds[typeRef.TypeId] = true; // true = Dict + } + } + } + + private string CreateClassName(string typeId) + { + var baseName = ExtractTypeName(typeId); + var name = SanitizeIdentifier(baseName); + if (_classNames.Values.Contains(name, StringComparer.Ordinal)) + { + var assemblyName = typeId.Split('/')[0]; + var assemblyPrefix = SanitizeIdentifier(assemblyName); + name = $"{assemblyPrefix}{name}"; + } + + var counter = 1; + var candidate = name; + while (_classNames.Values.Contains(candidate, StringComparer.Ordinal)) + { + counter++; + candidate = $"{name}{counter}"; + } + + return candidate; + } + + private static string ExtractTypeName(string typeId) + { + var slashIndex = typeId.IndexOf('/', StringComparison.Ordinal); + var typeName = slashIndex >= 0 ? typeId[(slashIndex + 1)..] : typeId; + var lastDot = typeName.LastIndexOf('.'); + var plusIndex = typeName.LastIndexOf('+'); + var delimiterIndex = Math.Max(lastDot, plusIndex); + return delimiterIndex >= 0 ? typeName[(delimiterIndex + 1)..] : typeName; + } + + private static string SanitizeIdentifier(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + return "_"; + } + + var builder = new StringBuilder(name.Length); + foreach (var ch in name) + { + builder.Append(char.IsLetterOrDigit(ch) || ch == '_' ? ch : '_'); + } + + if (!char.IsLetter(builder[0]) && builder[0] != '_') + { + builder.Insert(0, '_'); + } + + var sanitized = builder.ToString(); + return s_javaKeywords.Contains(sanitized) ? sanitized + "_" : sanitized; + } + + /// + /// Converts a name to PascalCase for Java class/method names. + /// + private static string ToPascalCase(string name) + { + if (string.IsNullOrEmpty(name)) + { + return name; + } + if (char.IsUpper(name[0])) + { + return name; + } + return char.ToUpperInvariant(name[0]) + name[1..]; + } + + /// + /// Converts a name to camelCase for Java field/variable names. + /// + private static string ToCamelCase(string name) + { + if (string.IsNullOrEmpty(name)) + { + return name; + } + if (char.IsLower(name[0])) + { + return name; + } + return char.ToLowerInvariant(name[0]) + name[1..]; + } + + /// + /// Converts a name to UPPER_SNAKE_CASE for Java enum constants. + /// + private static string ToUpperSnakeCase(string name) + { + if (string.IsNullOrEmpty(name)) + { + return name; + } + + var result = new StringBuilder(); + for (var i = 0; i < name.Length; i++) + { + var c = name[i]; + if (i > 0 && char.IsUpper(c) && !char.IsUpper(name[i - 1])) + { + result.Append('_'); + } + result.Append(char.ToUpperInvariant(c)); + } + return result.ToString(); + } + + private void WriteLine(string value = "") + { + _writer.WriteLine(value); + } + + private sealed record JavaHandleType(string TypeId, string ClassName, bool IsResourceBuilder); +} diff --git a/src/Aspire.Hosting.CodeGeneration.Java/JavaLanguageSupport.cs b/src/Aspire.Hosting.CodeGeneration.Java/JavaLanguageSupport.cs new file mode 100644 index 00000000000..9b36a6a26fe --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Java/JavaLanguageSupport.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.Ats; + +namespace Aspire.Hosting.CodeGeneration.Java; + +/// +/// Provides language support for Java AppHosts. +/// Implements scaffolding, detection, and runtime configuration. +/// +public sealed class JavaLanguageSupport : ILanguageSupport +{ + /// + /// The language/runtime identifier for Java. + /// + private const string LanguageId = "java"; + + /// + /// The code generation target language. This maps to the ICodeGenerator.Language property. + /// + private const string CodeGenTarget = "Java"; + + private const string LanguageDisplayName = "Java"; + private static readonly string[] s_detectionPatterns = ["AppHost.java"]; + + /// + public string Language => LanguageId; + + /// + public Dictionary Scaffold(ScaffoldRequest request) + { + var files = new Dictionary(); + + // Create AppHost.java - must be in same package as generated code (aspire) + // because Java only allows one public class per file + files["AppHost.java"] = """ + // Aspire Java AppHost + // For more information, see: https://aspire.dev + + package aspire; + + public class AppHost { + public static void main(String[] args) { + try { + IDistributedApplicationBuilder builder = Aspire.createBuilder(null); + + // Add your resources here, for example: + // var redis = builder.addRedis("cache"); + // var postgres = builder.addPostgres("db"); + + DistributedApplication app = builder.build(); + app.run(null); + } catch (Exception e) { + System.err.println("Failed to run: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + } + """; + + // Create apphost.run.json with random ports + var random = request.PortSeed.HasValue + ? new Random(request.PortSeed.Value) + : Random.Shared; + + var httpsPort = random.Next(10000, 65000); + var httpPort = random.Next(10000, 65000); + var otlpPort = random.Next(10000, 65000); + var resourceServicePort = random.Next(10000, 65000); + + files["apphost.run.json"] = $$""" + { + "profiles": { + "https": { + "applicationUrl": "https://localhost:{{httpsPort}};http://localhost:{{httpPort}}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:{{otlpPort}}", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:{{resourceServicePort}}" + } + } + } + } + """; + + return files; + } + + /// + public DetectionResult Detect(string directoryPath) + { + var appHostPath = Path.Combine(directoryPath, "AppHost.java"); + if (!File.Exists(appHostPath)) + { + return DetectionResult.NotFound; + } + + return DetectionResult.Found(LanguageId, "AppHost.java"); + } + + /// + public RuntimeSpec GetRuntimeSpec() + { + return new RuntimeSpec + { + Language = LanguageId, + DisplayName = LanguageDisplayName, + CodeGenLanguage = CodeGenTarget, + DetectionPatterns = s_detectionPatterns, + // No separate install step - compilation happens in Execute + InstallDependencies = null, + Execute = new CommandSpec + { + // Use a shell to compile and run in sequence + // On Windows, use cmd /c; on Unix, use sh -c + Command = OperatingSystem.IsWindows() ? "cmd" : "sh", + Args = OperatingSystem.IsWindows() + ? ["/c", "javac -d . .modules\\Transport.java .modules\\Base.java .modules\\Aspire.java AppHost.java && java aspire.AppHost"] + : ["-c", "javac -d . .modules/Transport.java .modules/Base.java .modules/Aspire.java AppHost.java && java aspire.AppHost"] + } + }; + } +} diff --git a/src/Aspire.Hosting.CodeGeneration.Java/Resources/Base.java b/src/Aspire.Hosting.CodeGeneration.Java/Resources/Base.java new file mode 100644 index 00000000000..98a1aa43838 --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Java/Resources/Base.java @@ -0,0 +1,150 @@ +// Base.java - Base types and utilities for Aspire Java SDK +// GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; + +/** + * HandleWrapperBase is the base class for all handle wrappers. + */ +class HandleWrapperBase { + private final Handle handle; + private final AspireClient client; + + HandleWrapperBase(Handle handle, AspireClient client) { + this.handle = handle; + this.client = client; + } + + Handle getHandle() { + return handle; + } + + AspireClient getClient() { + return client; + } +} + +/** + * ResourceBuilderBase extends HandleWrapperBase for resource builders. + */ +class ResourceBuilderBase extends HandleWrapperBase { + ResourceBuilderBase(Handle handle, AspireClient client) { + super(handle, client); + } +} + +/** + * ReferenceExpression represents a reference expression. + */ +class ReferenceExpression { + private final String format; + private final Object[] args; + + ReferenceExpression(String format, Object... args) { + this.format = format; + this.args = args; + } + + String getFormat() { + return format; + } + + Object[] getArgs() { + return args; + } + + Map toJson() { + Map refExpr = new HashMap<>(); + refExpr.put("format", format); + refExpr.put("args", Arrays.asList(args)); + + Map result = new HashMap<>(); + result.put("$refExpr", refExpr); + return result; + } + + /** + * Creates a new reference expression. + */ + static ReferenceExpression refExpr(String format, Object... args) { + return new ReferenceExpression(format, args); + } +} + +/** + * AspireList is a handle-backed list with lazy handle resolution. + */ +class AspireList extends HandleWrapperBase { + private final String getterCapabilityId; + private Handle resolvedHandle; + + AspireList(Handle handle, AspireClient client) { + super(handle, client); + this.getterCapabilityId = null; + this.resolvedHandle = handle; + } + + AspireList(Handle contextHandle, AspireClient client, String getterCapabilityId) { + super(contextHandle, client); + this.getterCapabilityId = getterCapabilityId; + this.resolvedHandle = null; + } + + private Handle ensureHandle() { + if (resolvedHandle != null) { + return resolvedHandle; + } + if (getterCapabilityId != null) { + Map args = new HashMap<>(); + args.put("context", getHandle().toJson()); + Object result = getClient().invokeCapability(getterCapabilityId, args); + if (result instanceof Handle) { + resolvedHandle = (Handle) result; + } + } + if (resolvedHandle == null) { + resolvedHandle = getHandle(); + } + return resolvedHandle; + } +} + +/** + * AspireDict is a handle-backed dictionary with lazy handle resolution. + */ +class AspireDict extends HandleWrapperBase { + private final String getterCapabilityId; + private Handle resolvedHandle; + + AspireDict(Handle handle, AspireClient client) { + super(handle, client); + this.getterCapabilityId = null; + this.resolvedHandle = handle; + } + + AspireDict(Handle contextHandle, AspireClient client, String getterCapabilityId) { + super(contextHandle, client); + this.getterCapabilityId = getterCapabilityId; + this.resolvedHandle = null; + } + + private Handle ensureHandle() { + if (resolvedHandle != null) { + return resolvedHandle; + } + if (getterCapabilityId != null) { + Map args = new HashMap<>(); + args.put("context", getHandle().toJson()); + Object result = getClient().invokeCapability(getterCapabilityId, args); + if (result instanceof Handle) { + resolvedHandle = (Handle) result; + } + } + if (resolvedHandle == null) { + resolvedHandle = getHandle(); + } + return resolvedHandle; + } +} diff --git a/src/Aspire.Hosting.CodeGeneration.Java/Resources/Transport.java b/src/Aspire.Hosting.CodeGeneration.Java/Resources/Transport.java new file mode 100644 index 00000000000..cef9f1cefd9 --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Java/Resources/Transport.java @@ -0,0 +1,704 @@ +// Transport.java - JSON-RPC transport layer for Aspire Java SDK +// GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.io.*; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.function.*; + +/** + * Handle represents a remote object reference. + */ +class Handle { + private final String id; + private final String typeId; + + Handle(String id, String typeId) { + this.id = id; + this.typeId = typeId; + } + + String getId() { return id; } + String getTypeId() { return typeId; } + + Map toJson() { + Map result = new HashMap<>(); + result.put("$handle", id); + result.put("$type", typeId); + return result; + } + + @Override + public String toString() { + return "Handle{id='" + id + "', typeId='" + typeId + "'}"; + } +} + +/** + * CapabilityError represents an error from a capability invocation. + */ +class CapabilityError extends RuntimeException { + private final String code; + private final Object data; + + CapabilityError(String code, String message, Object data) { + super(message); + this.code = code; + this.data = data; + } + + String getCode() { return code; } + Object getData() { return data; } +} + +/** + * CancellationToken for cancelling operations. + */ +class CancellationToken { + private volatile boolean cancelled = false; + private final List listeners = new CopyOnWriteArrayList<>(); + + void cancel() { + cancelled = true; + for (Runnable listener : listeners) { + listener.run(); + } + } + + boolean isCancelled() { return cancelled; } + + void onCancel(Runnable listener) { + listeners.add(listener); + if (cancelled) { + listener.run(); + } + } +} + +/** + * AspireClient handles JSON-RPC communication with the AppHost server. + */ +class AspireClient { + private static final boolean DEBUG = System.getenv("ASPIRE_DEBUG") != null; + + private final String socketPath; + private OutputStream outputStream; + private InputStream inputStream; + private final AtomicInteger requestId = new AtomicInteger(0); + private final Map> callbacks = new ConcurrentHashMap<>(); + private final Map> cancellations = new ConcurrentHashMap<>(); + private Runnable disconnectHandler; + private volatile boolean connected = false; + + // Handle wrapper factory registry + private static final Map> handleWrappers = new ConcurrentHashMap<>(); + + public static void registerHandleWrapper(String typeId, BiFunction factory) { + handleWrappers.put(typeId, factory); + } + + public AspireClient(String socketPath) { + this.socketPath = socketPath; + } + + public void connect() throws IOException { + debug("Connecting to AppHost server at " + socketPath); + + if (isWindows()) { + connectWindowsNamedPipe(); + } else { + connectUnixSocket(); + } + + connected = true; + debug("Connected successfully"); + } + + private boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains("win"); + } + + private void connectWindowsNamedPipe() throws IOException { + // Extract just the filename from the socket path for the named pipe + String pipeName = new java.io.File(socketPath).getName(); + String pipePath = "\\\\.\\pipe\\" + pipeName; + debug("Opening Windows named pipe: " + pipePath); + + // Use RandomAccessFile to open the named pipe + RandomAccessFile pipe = new RandomAccessFile(pipePath, "rw"); + + // Create streams from the RandomAccessFile + FileDescriptor fd = pipe.getFD(); + inputStream = new FileInputStream(fd); + outputStream = new FileOutputStream(fd); + + debug("Named pipe opened successfully"); + } + + private void connectUnixSocket() throws IOException { + // Use Java 16+ Unix domain socket support + debug("Opening Unix domain socket: " + socketPath); + var address = java.net.UnixDomainSocketAddress.of(socketPath); + var channel = java.nio.channels.SocketChannel.open(address); + + // Create streams from the channel + inputStream = java.nio.channels.Channels.newInputStream(channel); + outputStream = java.nio.channels.Channels.newOutputStream(channel); + + debug("Unix domain socket opened successfully"); + } + + public void onDisconnect(Runnable handler) { + this.disconnectHandler = handler; + } + + public Object invokeCapability(String capabilityId, Map args) { + int id = requestId.incrementAndGet(); + + Map params = new HashMap<>(); + params.put("capabilityId", capabilityId); + params.put("args", args); + + Map request = new HashMap<>(); + request.put("jsonrpc", "2.0"); + request.put("id", id); + request.put("method", "invokeCapability"); + request.put("params", params); + + debug("Sending request invokeCapability with id=" + id); + + try { + sendMessage(request); + return readResponse(id); + } catch (IOException e) { + handleDisconnect(); + throw new RuntimeException("Failed to invoke capability: " + e.getMessage(), e); + } + } + + private void sendMessage(Map message) throws IOException { + String json = toJson(message); + byte[] content = json.getBytes(StandardCharsets.UTF_8); + String header = "Content-Length: " + content.length + "\r\n\r\n"; + + debug("Writing message: " + message.get("method") + " (id=" + message.get("id") + ")"); + + synchronized (outputStream) { + outputStream.write(header.getBytes(StandardCharsets.UTF_8)); + outputStream.write(content); + outputStream.flush(); + } + } + + private Object readResponse(int expectedId) throws IOException { + while (true) { + Map message = readMessage(); + + if (message.containsKey("method")) { + // This is a request from server (callback invocation) + handleServerRequest(message); + continue; + } + + // This is a response + Object idObj = message.get("id"); + int responseId = idObj instanceof Number ? ((Number) idObj).intValue() : Integer.parseInt(idObj.toString()); + + if (responseId != expectedId) { + debug("Received response for different id: " + responseId + " (expected " + expectedId + ")"); + continue; + } + + if (message.containsKey("error")) { + @SuppressWarnings("unchecked") + Map error = (Map) message.get("error"); + String code = String.valueOf(error.get("code")); + String errorMessage = String.valueOf(error.get("message")); + Object data = error.get("data"); + throw new CapabilityError(code, errorMessage, data); + } + + Object result = message.get("result"); + return unwrapResult(result); + } + } + + @SuppressWarnings("unchecked") + private Map readMessage() throws IOException { + // Read headers + StringBuilder headerBuilder = new StringBuilder(); + int contentLength = -1; + + while (true) { + String line = readLine(); + if (line.isEmpty()) { + break; + } + if (line.startsWith("Content-Length:")) { + contentLength = Integer.parseInt(line.substring(15).trim()); + } + } + + if (contentLength < 0) { + throw new IOException("No Content-Length header found"); + } + + // Read body + byte[] body = new byte[contentLength]; + int totalRead = 0; + while (totalRead < contentLength) { + int read = inputStream.read(body, totalRead, contentLength - totalRead); + if (read < 0) { + throw new IOException("Unexpected end of stream"); + } + totalRead += read; + } + + String json = new String(body, StandardCharsets.UTF_8); + debug("Received: " + json.substring(0, Math.min(200, json.length())) + "..."); + + return (Map) parseJson(json); + } + + private String readLine() throws IOException { + StringBuilder sb = new StringBuilder(); + int ch; + while ((ch = inputStream.read()) != -1) { + if (ch == '\r') { + int next = inputStream.read(); + if (next == '\n') { + break; + } + sb.append((char) ch); + if (next != -1) sb.append((char) next); + } else if (ch == '\n') { + break; + } else { + sb.append((char) ch); + } + } + return sb.toString(); + } + + @SuppressWarnings("unchecked") + private void handleServerRequest(Map request) throws IOException { + String method = (String) request.get("method"); + Object idObj = request.get("id"); + Map params = (Map) request.get("params"); + + debug("Received server request: " + method); + + Object result = null; + Map error = null; + + try { + if ("invokeCallback".equals(method)) { + String callbackId = (String) params.get("callbackId"); + List args = (List) params.get("args"); + + Function callback = callbacks.get(callbackId); + if (callback != null) { + Object[] unwrappedArgs = args.stream() + .map(this::unwrapResult) + .toArray(); + result = callback.apply(unwrappedArgs); + } else { + error = createError(-32601, "Callback not found: " + callbackId); + } + } else if ("cancel".equals(method)) { + String cancellationId = (String) params.get("cancellationId"); + Consumer handler = cancellations.get(cancellationId); + if (handler != null) { + handler.accept(null); + } + result = true; + } else { + error = createError(-32601, "Unknown method: " + method); + } + } catch (Exception e) { + error = createError(-32603, e.getMessage()); + } + + // Send response + Map response = new HashMap<>(); + response.put("jsonrpc", "2.0"); + response.put("id", idObj); + if (error != null) { + response.put("error", error); + } else { + response.put("result", serializeValue(result)); + } + + sendMessage(response); + } + + private Map createError(int code, String message) { + Map error = new HashMap<>(); + error.put("code", code); + error.put("message", message); + return error; + } + + @SuppressWarnings("unchecked") + private Object unwrapResult(Object value) { + if (value == null) { + return null; + } + + if (value instanceof Map) { + Map map = (Map) value; + + // Check for handle + if (map.containsKey("$handle")) { + String handleId = (String) map.get("$handle"); + String typeId = (String) map.get("$type"); + Handle handle = new Handle(handleId, typeId); + + BiFunction factory = handleWrappers.get(typeId); + if (factory != null) { + return factory.apply(handle, this); + } + return handle; + } + + // Check for error + if (map.containsKey("$error")) { + Map errorData = (Map) map.get("$error"); + String code = String.valueOf(errorData.get("code")); + String message = String.valueOf(errorData.get("message")); + throw new CapabilityError(code, message, errorData.get("data")); + } + + // Recursively unwrap map values + Map result = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + result.put(entry.getKey(), unwrapResult(entry.getValue())); + } + return result; + } + + if (value instanceof List) { + List list = (List) value; + List result = new ArrayList<>(); + for (Object item : list) { + result.add(unwrapResult(item)); + } + return result; + } + + return value; + } + + private void handleDisconnect() { + connected = false; + if (disconnectHandler != null) { + disconnectHandler.run(); + } + } + + public String registerCallback(Function callback) { + String id = UUID.randomUUID().toString(); + callbacks.put(id, callback); + return id; + } + + public String registerCancellation(CancellationToken token) { + String id = UUID.randomUUID().toString(); + cancellations.put(id, v -> token.cancel()); + return id; + } + + // Simple JSON serialization (no external dependencies) + public static Object serializeValue(Object value) { + if (value == null) { + return null; + } + if (value instanceof Handle) { + return ((Handle) value).toJson(); + } + if (value instanceof HandleWrapperBase) { + return ((HandleWrapperBase) value).getHandle().toJson(); + } + if (value instanceof ReferenceExpression) { + return ((ReferenceExpression) value).toJson(); + } + if (value instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (Map) value; + Map result = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + result.put(entry.getKey(), serializeValue(entry.getValue())); + } + return result; + } + if (value instanceof List) { + @SuppressWarnings("unchecked") + List list = (List) value; + List result = new ArrayList<>(); + for (Object item : list) { + result.add(serializeValue(item)); + } + return result; + } + if (value instanceof Object[]) { + Object[] array = (Object[]) value; + List result = new ArrayList<>(); + for (Object item : array) { + result.add(serializeValue(item)); + } + return result; + } + if (value instanceof Enum) { + return ((Enum) value).name(); + } + return value; + } + + // Simple JSON encoding + private String toJson(Object value) { + if (value == null) { + return "null"; + } + if (value instanceof String) { + return "\"" + escapeJson((String) value) + "\""; + } + if (value instanceof Number || value instanceof Boolean) { + return value.toString(); + } + if (value instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (Map) value; + StringBuilder sb = new StringBuilder("{"); + boolean first = true; + for (Map.Entry entry : map.entrySet()) { + if (!first) sb.append(","); + first = false; + sb.append("\"").append(escapeJson(entry.getKey())).append("\":"); + sb.append(toJson(entry.getValue())); + } + sb.append("}"); + return sb.toString(); + } + if (value instanceof List) { + @SuppressWarnings("unchecked") + List list = (List) value; + StringBuilder sb = new StringBuilder("["); + boolean first = true; + for (Object item : list) { + if (!first) sb.append(","); + first = false; + sb.append(toJson(item)); + } + sb.append("]"); + return sb.toString(); + } + if (value instanceof Object[]) { + Object[] array = (Object[]) value; + StringBuilder sb = new StringBuilder("["); + boolean first = true; + for (Object item : array) { + if (!first) sb.append(","); + first = false; + sb.append(toJson(item)); + } + sb.append("]"); + return sb.toString(); + } + return "\"" + escapeJson(value.toString()) + "\""; + } + + private String escapeJson(String s) { + StringBuilder sb = new StringBuilder(); + for (char c : s.toCharArray()) { + switch (c) { + case '"': sb.append("\\\""); break; + case '\\': sb.append("\\\\"); break; + case '\b': sb.append("\\b"); break; + case '\f': sb.append("\\f"); break; + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + default: + if (c < ' ') { + sb.append(String.format("\\u%04x", (int) c)); + } else { + sb.append(c); + } + } + } + return sb.toString(); + } + + // Simple JSON parsing + @SuppressWarnings("unchecked") + private Object parseJson(String json) { + return new JsonParser(json).parse(); + } + + private static class JsonParser { + private final String json; + private int pos = 0; + + JsonParser(String json) { + this.json = json; + } + + Object parse() { + skipWhitespace(); + return parseValue(); + } + + private Object parseValue() { + skipWhitespace(); + char c = peek(); + if (c == '{') return parseObject(); + if (c == '[') return parseArray(); + if (c == '"') return parseString(); + if (c == 't' || c == 'f') return parseBoolean(); + if (c == 'n') return parseNull(); + if (c == '-' || Character.isDigit(c)) return parseNumber(); + throw new RuntimeException("Unexpected character: " + c + " at position " + pos); + } + + private Map parseObject() { + expect('{'); + Map map = new LinkedHashMap<>(); + skipWhitespace(); + if (peek() != '}') { + do { + skipWhitespace(); + String key = parseString(); + skipWhitespace(); + expect(':'); + Object value = parseValue(); + map.put(key, value); + skipWhitespace(); + } while (tryConsume(',')); + } + expect('}'); + return map; + } + + private List parseArray() { + expect('['); + List list = new ArrayList<>(); + skipWhitespace(); + if (peek() != ']') { + do { + list.add(parseValue()); + skipWhitespace(); + } while (tryConsume(',')); + } + expect(']'); + return list; + } + + private String parseString() { + expect('"'); + StringBuilder sb = new StringBuilder(); + while (pos < json.length()) { + char c = json.charAt(pos++); + if (c == '"') return sb.toString(); + if (c == '\\') { + c = json.charAt(pos++); + switch (c) { + case '"': case '\\': case '/': sb.append(c); break; + case 'b': sb.append('\b'); break; + case 'f': sb.append('\f'); break; + case 'n': sb.append('\n'); break; + case 'r': sb.append('\r'); break; + case 't': sb.append('\t'); break; + case 'u': + String hex = json.substring(pos, pos + 4); + sb.append((char) Integer.parseInt(hex, 16)); + pos += 4; + break; + } + } else { + sb.append(c); + } + } + throw new RuntimeException("Unterminated string"); + } + + private Number parseNumber() { + int start = pos; + if (peek() == '-') pos++; + while (pos < json.length() && Character.isDigit(json.charAt(pos))) pos++; + if (pos < json.length() && json.charAt(pos) == '.') { + pos++; + while (pos < json.length() && Character.isDigit(json.charAt(pos))) pos++; + } + if (pos < json.length() && (json.charAt(pos) == 'e' || json.charAt(pos) == 'E')) { + pos++; + if (pos < json.length() && (json.charAt(pos) == '+' || json.charAt(pos) == '-')) pos++; + while (pos < json.length() && Character.isDigit(json.charAt(pos))) pos++; + } + String numStr = json.substring(start, pos); + if (numStr.contains(".") || numStr.contains("e") || numStr.contains("E")) { + return Double.parseDouble(numStr); + } + long l = Long.parseLong(numStr); + if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) { + return (int) l; + } + return l; + } + + private Boolean parseBoolean() { + if (json.startsWith("true", pos)) { + pos += 4; + return true; + } + if (json.startsWith("false", pos)) { + pos += 5; + return false; + } + throw new RuntimeException("Expected boolean at position " + pos); + } + + private Object parseNull() { + if (json.startsWith("null", pos)) { + pos += 4; + return null; + } + throw new RuntimeException("Expected null at position " + pos); + } + + private void skipWhitespace() { + while (pos < json.length() && Character.isWhitespace(json.charAt(pos))) pos++; + } + + private char peek() { + return pos < json.length() ? json.charAt(pos) : '\0'; + } + + private void expect(char c) { + skipWhitespace(); + if (pos >= json.length() || json.charAt(pos) != c) { + throw new RuntimeException("Expected '" + c + "' at position " + pos); + } + pos++; + } + + private boolean tryConsume(char c) { + skipWhitespace(); + if (pos < json.length() && json.charAt(pos) == c) { + pos++; + return true; + } + return false; + } + } + + private void debug(String message) { + if (DEBUG) { + System.err.println("[Java ATS] " + message); + } + } +} diff --git a/src/Aspire.Hosting.CodeGeneration.Python/Aspire.Hosting.CodeGeneration.Python.csproj b/src/Aspire.Hosting.CodeGeneration.Python/Aspire.Hosting.CodeGeneration.Python.csproj new file mode 100644 index 00000000000..45f966a7ca7 --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Python/Aspire.Hosting.CodeGeneration.Python.csproj @@ -0,0 +1,29 @@ + + + + $(DefaultTargetFramework) + enable + enable + Aspire.Hosting.CodeGeneration.Python + + true + + true + true + + + + + + + + + + + + + + + + + diff --git a/src/Aspire.Hosting.CodeGeneration.Python/AtsPythonCodeGenerator.cs b/src/Aspire.Hosting.CodeGeneration.Python/AtsPythonCodeGenerator.cs new file mode 100644 index 00000000000..7d995766203 --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Python/AtsPythonCodeGenerator.cs @@ -0,0 +1,787 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Reflection; +using System.Text; +using System.Text.Json; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Ats; + +namespace Aspire.Hosting.CodeGeneration.Python; + +/// +/// Generates a Python SDK using the ATS (Aspire Type System) capability-based API. +/// Produces wrapper classes that proxy capabilities via JSON-RPC. +/// +public sealed class AtsPythonCodeGenerator : ICodeGenerator +{ + private static readonly HashSet s_pythonKeywords = new(StringComparer.OrdinalIgnoreCase) + { + "false", "none", "true", "and", "as", "assert", "async", "await", "break", + "class", "continue", "def", "del", "elif", "else", "except", "finally", + "for", "from", "global", "if", "import", "in", "is", "lambda", "nonlocal", + "not", "or", "pass", "raise", "return", "try", "while", "with", "yield", + "match", "case" + }; + + private TextWriter _writer = null!; + private readonly Dictionary _classNames = new(StringComparer.Ordinal); + private readonly Dictionary _dtoNames = new(StringComparer.Ordinal); + private readonly Dictionary _enumNames = new(StringComparer.Ordinal); + + /// + public string Language => "Python"; + + /// + public Dictionary GenerateDistributedApplication(AtsContext context) + { + return new Dictionary(StringComparer.Ordinal) + { + ["transport.py"] = GetEmbeddedResource("transport.py"), + ["base.py"] = GetEmbeddedResource("base.py"), + ["aspire.py"] = GenerateAspireSdk(context) + }; + } + + private static string GetEmbeddedResource(string name) + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = $"Aspire.Hosting.CodeGeneration.Python.Resources.{name}"; + + using var stream = assembly.GetManifestResourceStream(resourceName) + ?? throw new InvalidOperationException($"Embedded resource '{name}' not found."); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private string GenerateAspireSdk(AtsContext context) + { + using var stringWriter = new StringWriter(CultureInfo.InvariantCulture); + _writer = stringWriter; + + var capabilities = context.Capabilities; + var dtoTypes = context.DtoTypes; + var enumTypes = context.EnumTypes; + + _enumNames.Clear(); + foreach (var enumType in enumTypes) + { + _enumNames[enumType.TypeId] = SanitizeIdentifier(enumType.Name); + } + + _dtoNames.Clear(); + foreach (var dto in dtoTypes) + { + _dtoNames[dto.TypeId] = SanitizeIdentifier(dto.Name); + } + + var handleTypes = BuildHandleTypes(context); + var capabilitiesByTarget = GroupCapabilitiesByTarget(capabilities); + var collectionTypes = CollectListAndDictTypeIds(capabilities); + + WriteHeader(); + GenerateEnumTypes(enumTypes); + GenerateDtoTypes(dtoTypes); + GenerateHandleTypes(handleTypes, capabilitiesByTarget); + GenerateHandleWrapperRegistrations(handleTypes, collectionTypes); + GenerateConnectionHelpers(); + + return stringWriter.ToString(); + } + + private void WriteHeader() + { + WriteLine("# aspire.py - Capability-based Aspire SDK"); + WriteLine("# GENERATED CODE - DO NOT EDIT"); + WriteLine(); + WriteLine("from __future__ import annotations"); + WriteLine(); + WriteLine("import os"); + WriteLine("import sys"); + WriteLine("from dataclasses import dataclass"); + WriteLine("from enum import Enum"); + WriteLine("from typing import Any, Callable, Dict, List"); + WriteLine(); + WriteLine("from transport import AspireClient, Handle, CapabilityError, register_callback, register_handle_wrapper, register_cancellation"); + WriteLine("from base import AspireDict, AspireList, ReferenceExpression, ref_expr, HandleWrapperBase, ResourceBuilderBase, serialize_value"); + WriteLine(); + } + + private void GenerateEnumTypes(IReadOnlyList enumTypes) + { + if (enumTypes.Count == 0) + { + return; + } + + WriteLine("# ============================================================================"); + WriteLine("# Enums"); + WriteLine("# ============================================================================"); + WriteLine(); + + foreach (var enumType in enumTypes) + { + if (enumType.ClrType is null) + { + continue; + } + + var enumName = _enumNames[enumType.TypeId]; + WriteLine($"class {enumName}(str, Enum):"); + foreach (var member in Enum.GetNames(enumType.ClrType)) + { + // Convert enum member names to UPPER_SNAKE_CASE for idiomatic Python + var memberName = ToUpperSnakeCase(member); + WriteLine($" {memberName} = \"{member}\""); + } + WriteLine(); + } + } + + private void GenerateDtoTypes(IReadOnlyList dtoTypes) + { + if (dtoTypes.Count == 0) + { + return; + } + + WriteLine("# ============================================================================"); + WriteLine("# DTOs"); + WriteLine("# ============================================================================"); + WriteLine(); + + foreach (var dto in dtoTypes) + { + var dtoName = _dtoNames[dto.TypeId]; + WriteLine("@dataclass"); + WriteLine($"class {dtoName}:"); + if (dto.Properties.Count == 0) + { + WriteLine(" pass"); + WriteLine(); + continue; + } + + foreach (var property in dto.Properties) + { + // Convert property name to snake_case for idiomatic Python + var propertyName = ToSnakeCase(property.Name); + var propertyType = MapTypeRefToPython(property.Type); + var optionalSuffix = property.IsOptional ? " | None" : string.Empty; + var defaultValue = property.IsOptional ? " = None" : string.Empty; + WriteLine($" {propertyName}: {propertyType}{optionalSuffix}{defaultValue}"); + } + + WriteLine(); + WriteLine(" def to_dict(self) -> Dict[str, Any]:"); + WriteLine(" return {"); + foreach (var property in dto.Properties) + { + // Use snake_case in Python code, but original name for JSON serialization + var propertyName = ToSnakeCase(property.Name); + WriteLine($" \"{property.Name}\": serialize_value(self.{propertyName}),"); + } + WriteLine(" }"); + WriteLine(); + } + } + + private void GenerateHandleTypes( + IReadOnlyList handleTypes, + Dictionary> capabilitiesByTarget) + { + if (handleTypes.Count == 0) + { + return; + } + + WriteLine("# ============================================================================"); + WriteLine("# Handle Wrappers"); + WriteLine("# ============================================================================"); + WriteLine(); + + foreach (var handleType in handleTypes.OrderBy(t => t.ClassName, StringComparer.Ordinal)) + { + var baseClass = handleType.IsResourceBuilder ? "ResourceBuilderBase" : "HandleWrapperBase"; + WriteLine($"class {handleType.ClassName}({baseClass}):"); + WriteLine(" def __init__(self, handle: Handle, client: AspireClient):"); + WriteLine(" super().__init__(handle, client)"); + WriteLine(); + + if (capabilitiesByTarget.TryGetValue(handleType.TypeId, out var methods)) + { + foreach (var method in methods) + { + GenerateCapabilityMethod(method); + } + } + else + { + WriteLine(" pass"); + } + + WriteLine(); + } + } + + private void GenerateCapabilityMethod(AtsCapabilityInfo capability) + { + var targetParamName = capability.TargetParameterName ?? "builder"; + // Convert method name to snake_case for idiomatic Python + var methodName = ToSnakeCase(capability.MethodName); + var parameters = capability.Parameters + .Where(p => !string.Equals(p.Name, targetParamName, StringComparison.Ordinal)) + .ToList(); + + // Check if this is a List/Dict property getter (no parameters, returns List/Dict) + if (parameters.Count == 0 && IsListOrDictPropertyGetter(capability.ReturnType)) + { + GenerateListOrDictProperty(capability, methodName); + return; + } + + var parameterList = BuildParameterList(parameters); + var returnType = MapTypeRefToPython(capability.ReturnType); + + var signature = string.IsNullOrEmpty(parameterList) + ? "self" + : $"self, {parameterList}"; + WriteLine($" def {methodName}({signature}) -> {returnType}:"); + if (!string.IsNullOrEmpty(capability.Description)) + { + WriteLine($" \"\"\"{capability.Description}\"\"\""); + } + + // Use serialize_value for the handle to convert it to JSON format + WriteLine($" args: Dict[str, Any] = {{ \"{targetParamName}\": serialize_value(self._handle) }}"); + + foreach (var parameter in parameters) + { + // Convert parameter name to snake_case for idiomatic Python + var parameterName = ToSnakeCase(parameter.Name); + if (parameter.IsCallback) + { + WriteLine($" {parameterName}_id = register_callback({parameterName}) if {parameterName} is not None else None"); + WriteLine($" if {parameterName}_id is not None:"); + WriteLine($" args[\"{parameter.Name}\"] = {parameterName}_id"); + continue; + } + + if (IsCancellationToken(parameter)) + { + WriteLine($" {parameterName}_id = register_cancellation({parameterName}, self._client) if {parameterName} is not None else None"); + WriteLine($" if {parameterName}_id is not None:"); + WriteLine($" args[\"{parameter.Name}\"] = {parameterName}_id"); + continue; + } + + if (parameter.IsOptional && parameter.DefaultValue is null) + { + WriteLine($" if {parameterName} is not None:"); + WriteLine($" args[\"{parameter.Name}\"] = serialize_value({parameterName})"); + } + else + { + WriteLine($" args[\"{parameter.Name}\"] = serialize_value({parameterName})"); + } + } + + if (capability.ReturnType.TypeId == AtsConstants.Void) + { + WriteLine($" self._client.invoke_capability(\"{capability.CapabilityId}\", args)"); + WriteLine(" return None"); + } + else + { + WriteLine($" return self._client.invoke_capability(\"{capability.CapabilityId}\", args)"); + } + WriteLine(); + } + + private static bool IsListOrDictPropertyGetter(AtsTypeRef? returnType) + { + if (returnType is null) + { + return false; + } + + return returnType.Category == AtsTypeCategory.List || returnType.Category == AtsTypeCategory.Dict; + } + + private void GenerateListOrDictProperty(AtsCapabilityInfo capability, string methodName) + { + var returnType = capability.ReturnType!; + var isDict = returnType.Category == AtsTypeCategory.Dict; + var wrapperType = isDict ? "AspireDict" : "AspireList"; + + // Determine element type for type hints + string typeHint; + if (isDict) + { + var keyType = MapTypeRefToPython(returnType.KeyType); + var valueType = MapTypeRefToPython(returnType.ValueType); + typeHint = $"{wrapperType}[{keyType}, {valueType}]"; + } + else + { + var elementType = MapTypeRefToPython(returnType.ElementType); + typeHint = $"{wrapperType}[{elementType}]"; + } + + // Generate cached property with lazy initialization + WriteLine($" @property"); + WriteLine($" def {methodName}(self) -> {typeHint}:"); + if (!string.IsNullOrEmpty(capability.Description)) + { + WriteLine($" \"\"\"{capability.Description}\"\"\""); + } + WriteLine($" if not hasattr(self, '_{methodName}'):"); + WriteLine($" self._{methodName} = {wrapperType}("); + WriteLine($" self._handle,"); + WriteLine($" self._client,"); + WriteLine($" \"{capability.CapabilityId}\""); + WriteLine($" )"); + WriteLine($" return self._{methodName}"); + WriteLine(); + } + + private void GenerateHandleWrapperRegistrations( + IReadOnlyList handleTypes, + Dictionary collectionTypes) + { + WriteLine("# ============================================================================"); + WriteLine("# Handle wrapper registrations"); + WriteLine("# ============================================================================"); + WriteLine(); + + foreach (var handleType in handleTypes) + { + WriteLine($"register_handle_wrapper(\"{handleType.TypeId}\", lambda handle, client: {handleType.ClassName}(handle, client))"); + } + + foreach (var (typeId, isDict) in collectionTypes) + { + var wrapperType = isDict ? "AspireDict" : "AspireList"; + WriteLine($"register_handle_wrapper(\"{typeId}\", lambda handle, client: {wrapperType}(handle, client))"); + } + + WriteLine(); + } + + private void GenerateConnectionHelpers() + { + var builderClassName = _classNames.TryGetValue(AtsConstants.BuilderTypeId, out var name) + ? name + : "DistributedApplicationBuilder"; + + WriteLine("# ============================================================================"); + WriteLine("# Connection Helpers"); + WriteLine("# ============================================================================"); + WriteLine(); + WriteLine("def connect() -> AspireClient:"); + WriteLine(" socket_path = os.environ.get(\"REMOTE_APP_HOST_SOCKET_PATH\")"); + WriteLine(" if not socket_path:"); + WriteLine(" raise RuntimeError(\"REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`.\")"); + WriteLine(" client = AspireClient(socket_path)"); + WriteLine(" client.connect()"); + WriteLine(" client.on_disconnect(lambda: sys.exit(1))"); + WriteLine(" return client"); + WriteLine(); + WriteLine($"def create_builder(options: Any | None = None) -> {builderClassName}:"); + WriteLine(" client = connect()"); + WriteLine(" resolved_options: Dict[str, Any] = {}"); + WriteLine(" if options is not None:"); + WriteLine(" if hasattr(options, \"to_dict\"):"); + WriteLine(" resolved_options.update(options.to_dict())"); + WriteLine(" elif isinstance(options, dict):"); + WriteLine(" resolved_options.update(options)"); + WriteLine(" resolved_options.setdefault(\"Args\", sys.argv[1:])"); + WriteLine(" resolved_options.setdefault(\"ProjectDirectory\", os.environ.get(\"ASPIRE_PROJECT_DIRECTORY\", os.getcwd()))"); + WriteLine(" result = client.invoke_capability(\"Aspire.Hosting/createBuilderWithOptions\", {\"options\": resolved_options})"); + WriteLine(" return result"); + WriteLine(); + WriteLine("# Re-export commonly used types"); + WriteLine("CapabilityError = CapabilityError"); + WriteLine("Handle = Handle"); + WriteLine("ReferenceExpression = ReferenceExpression"); + WriteLine("ref_expr = ref_expr"); + WriteLine(); + } + + private IReadOnlyList BuildHandleTypes(AtsContext context) + { + var handleTypeIds = new HashSet(StringComparer.Ordinal); + foreach (var handleType in context.HandleTypes) + { + handleTypeIds.Add(handleType.AtsTypeId); + } + + foreach (var capability in context.Capabilities) + { + AddHandleTypeIfNeeded(handleTypeIds, capability.TargetType); + AddHandleTypeIfNeeded(handleTypeIds, capability.ReturnType); + foreach (var parameter in capability.Parameters) + { + AddHandleTypeIfNeeded(handleTypeIds, parameter.Type); + if (parameter.IsCallback && parameter.CallbackParameters is not null) + { + foreach (var callbackParam in parameter.CallbackParameters) + { + AddHandleTypeIfNeeded(handleTypeIds, callbackParam.Type); + } + } + } + } + + _classNames.Clear(); + foreach (var typeId in handleTypeIds) + { + _classNames[typeId] = CreateClassName(typeId); + } + + var handleTypeMap = context.HandleTypes.ToDictionary(t => t.AtsTypeId, StringComparer.Ordinal); + var results = new List(); + foreach (var typeId in handleTypeIds) + { + var isResourceBuilder = false; + if (handleTypeMap.TryGetValue(typeId, out var typeInfo)) + { + isResourceBuilder = typeInfo.ClrType is not null && + typeof(IResource).IsAssignableFrom(typeInfo.ClrType); + } + + results.Add(new PythonHandleType(typeId, _classNames[typeId], isResourceBuilder)); + } + + return results; + } + + private static Dictionary> GroupCapabilitiesByTarget( + IReadOnlyList capabilities) + { + var result = new Dictionary>(StringComparer.Ordinal); + + foreach (var capability in capabilities) + { + if (string.IsNullOrEmpty(capability.TargetTypeId)) + { + continue; + } + + var targetTypes = capability.ExpandedTargetTypes.Count > 0 + ? capability.ExpandedTargetTypes + : capability.TargetType is not null + ? [capability.TargetType] + : []; + + foreach (var targetType in targetTypes) + { + if (targetType.TypeId is null) + { + continue; + } + + if (!result.TryGetValue(targetType.TypeId, out var list)) + { + list = new List(); + result[targetType.TypeId] = list; + } + list.Add(capability); + } + } + + return result; + } + + private static Dictionary CollectListAndDictTypeIds(IReadOnlyList capabilities) + { + // Maps typeId -> isDict (true for Dict, false for List) + var typeIds = new Dictionary(StringComparer.Ordinal); + foreach (var capability in capabilities) + { + AddListOrDictTypeIfNeeded(typeIds, capability.TargetType); + AddListOrDictTypeIfNeeded(typeIds, capability.ReturnType); + foreach (var parameter in capability.Parameters) + { + AddListOrDictTypeIfNeeded(typeIds, parameter.Type); + if (parameter.IsCallback && parameter.CallbackParameters is not null) + { + foreach (var callbackParam in parameter.CallbackParameters) + { + AddListOrDictTypeIfNeeded(typeIds, callbackParam.Type); + } + } + } + } + + return typeIds; + } + + private string BuildParameterList(List parameters) + { + if (parameters.Count == 0) + { + return string.Empty; + } + + var builder = new StringBuilder(); + for (var index = 0; index < parameters.Count; index++) + { + var parameter = parameters[index]; + if (index > 0) + { + builder.Append(", "); + } + + // Convert parameter name to snake_case for idiomatic Python + var parameterName = ToSnakeCase(parameter.Name); + var parameterType = parameter.IsCallback + ? MapCallbackTypeSignature(parameter.CallbackParameters, parameter.CallbackReturnType) + : IsCancellationToken(parameter) + ? "CancellationToken" + : MapTypeRefToPython(parameter.Type); + var defaultValue = parameter.IsOptional + ? GetDefaultValue(parameter) + : null; + + if (parameter.IsOptional && defaultValue is null) + { + parameterType += " | None"; + defaultValue = "None"; + } + + builder.Append(parameterName); + builder.Append(": "); + builder.Append(parameterType); + if (defaultValue is not null) + { + builder.Append(" = "); + builder.Append(defaultValue); + } + } + + return builder.ToString(); + } + + private string MapTypeRefToPython(AtsTypeRef? typeRef) + { + if (typeRef is null) + { + return "Any"; + } + + if (typeRef.TypeId == AtsConstants.ReferenceExpressionTypeId) + { + return nameof(ReferenceExpression); + } + + return typeRef.Category switch + { + AtsTypeCategory.Primitive => MapPrimitiveType(typeRef.TypeId), + AtsTypeCategory.Enum => MapEnumType(typeRef.TypeId), + AtsTypeCategory.Handle => MapHandleType(typeRef.TypeId), + AtsTypeCategory.Dto => MapDtoType(typeRef.TypeId), + AtsTypeCategory.Callback => "Callable[..., Any]", + AtsTypeCategory.Array => $"list[{MapTypeRefToPython(typeRef.ElementType)}]", + AtsTypeCategory.List => typeRef.IsReadOnly + ? $"list[{MapTypeRefToPython(typeRef.ElementType)}]" + : $"AspireList[{MapTypeRefToPython(typeRef.ElementType)}]", + AtsTypeCategory.Dict => typeRef.IsReadOnly + ? $"dict[{MapTypeRefToPython(typeRef.KeyType)}, {MapTypeRefToPython(typeRef.ValueType)}]" + : $"AspireDict[{MapTypeRefToPython(typeRef.KeyType)}, {MapTypeRefToPython(typeRef.ValueType)}]", + AtsTypeCategory.Union => MapUnionType(typeRef), + AtsTypeCategory.Unknown => "Any", + _ => "Any" + }; + } + + private string MapUnionType(AtsTypeRef typeRef) + { + if (typeRef.UnionTypes is null || typeRef.UnionTypes.Count == 0) + { + return "Any"; + } + + var unionTypes = typeRef.UnionTypes.Select(MapTypeRefToPython); + return string.Join(" | ", unionTypes); + } + + private string MapHandleType(string typeId) => + _classNames.TryGetValue(typeId, out var name) ? name : "Handle"; + + private string MapDtoType(string typeId) => + _dtoNames.TryGetValue(typeId, out var name) ? name : "dict[str, Any]"; + + private string MapEnumType(string typeId) => + _enumNames.TryGetValue(typeId, out var name) ? name : "str"; + + private static string MapPrimitiveType(string typeId) => typeId switch + { + AtsConstants.String or AtsConstants.Char => "str", + AtsConstants.Number => "float", + AtsConstants.Boolean => "bool", + AtsConstants.Void => "None", + AtsConstants.Any => "Any", + AtsConstants.DateTime or AtsConstants.DateTimeOffset or + AtsConstants.DateOnly or AtsConstants.TimeOnly => "str", + AtsConstants.TimeSpan => "float", + AtsConstants.Guid or AtsConstants.Uri => "str", + AtsConstants.CancellationToken => "CancellationToken", + _ => "Any" + }; + + private string MapCallbackTypeSignature( + IReadOnlyList? parameters, + AtsTypeRef? returnType) + { + var returnTypeName = MapTypeRefToPython(returnType); + if (parameters is null || parameters.Count == 0) + { + return $"Callable[[], {returnTypeName}]"; + } + + var paramTypes = string.Join(", ", parameters.Select(p => MapTypeRefToPython(p.Type))); + return $"Callable[[{paramTypes}], {returnTypeName}]"; + } + + private static bool IsCancellationToken(AtsParameterInfo parameter) => + parameter.Type?.TypeId == AtsConstants.CancellationToken; + + private static void AddHandleTypeIfNeeded(HashSet handleTypeIds, AtsTypeRef? typeRef) + { + if (typeRef is null) + { + return; + } + + if (typeRef.Category == AtsTypeCategory.Handle) + { + handleTypeIds.Add(typeRef.TypeId); + } + } + + private static void AddListOrDictTypeIfNeeded(Dictionary typeIds, AtsTypeRef? typeRef) + { + if (typeRef is null) + { + return; + } + + if (typeRef.Category == AtsTypeCategory.List) + { + if (!typeRef.IsReadOnly) + { + typeIds[typeRef.TypeId] = false; // false = List + } + } + else if (typeRef.Category == AtsTypeCategory.Dict) + { + if (!typeRef.IsReadOnly) + { + typeIds[typeRef.TypeId] = true; // true = Dict + } + } + } + + private static string? GetDefaultValue(AtsParameterInfo parameter) + { + if (parameter.DefaultValue is null) + { + return null; + } + + return parameter.DefaultValue switch + { + bool boolValue => boolValue ? "True" : "False", + string stringValue => $"\"{stringValue.Replace("\"", "\\\"", StringComparison.Ordinal)}\"", + char charValue => $"\"{charValue}\"", + int intValue => intValue.ToString(CultureInfo.InvariantCulture), + long longValue => longValue.ToString(CultureInfo.InvariantCulture), + float floatValue => floatValue.ToString(CultureInfo.InvariantCulture), + double doubleValue => doubleValue.ToString(CultureInfo.InvariantCulture), + decimal decimalValue => decimalValue.ToString(CultureInfo.InvariantCulture), + _ => "None" + }; + } + + private string CreateClassName(string typeId) + { + var baseName = ExtractTypeName(typeId); + var name = SanitizeIdentifier(baseName); + if (_classNames.Values.Contains(name, StringComparer.Ordinal)) + { + var assemblyName = typeId.Split('/')[0]; + var assemblyPrefix = SanitizeIdentifier(assemblyName); + name = $"{assemblyPrefix}{name}"; + } + + var counter = 1; + var candidate = name; + while (_classNames.Values.Contains(candidate, StringComparer.Ordinal)) + { + counter++; + candidate = $"{name}{counter}"; + } + + return candidate; + } + + private static string ExtractTypeName(string typeId) + { + var slashIndex = typeId.IndexOf('/', StringComparison.Ordinal); + var typeName = slashIndex >= 0 ? typeId[(slashIndex + 1)..] : typeId; + var lastDot = typeName.LastIndexOf('.'); + var plusIndex = typeName.LastIndexOf('+'); + var delimiterIndex = Math.Max(lastDot, plusIndex); + return delimiterIndex >= 0 ? typeName[(delimiterIndex + 1)..] : typeName; + } + + private static string SanitizeIdentifier(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + return "_"; + } + + var builder = new StringBuilder(name.Length); + foreach (var ch in name) + { + builder.Append(char.IsLetterOrDigit(ch) || ch == '_' ? ch : '_'); + } + + if (!char.IsLetter(builder[0]) && builder[0] != '_') + { + builder.Insert(0, '_'); + } + + var sanitized = builder.ToString(); + return s_pythonKeywords.Contains(sanitized) ? sanitized + "_" : sanitized; + } + + /// + /// Converts a camelCase or PascalCase identifier to snake_case for Python. + /// + private static string ToSnakeCase(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + return "_"; + } + + var snakeCase = JsonNamingPolicy.SnakeCaseLower.ConvertName(name); + return s_pythonKeywords.Contains(snakeCase) ? snakeCase + "_" : snakeCase; + } + + /// + /// Converts a camelCase or PascalCase identifier to UPPER_SNAKE_CASE for Python enum members. + /// + private static string ToUpperSnakeCase(string name) => ToSnakeCase(name).ToUpperInvariant(); + + private void WriteLine(string value = "") + { + _writer.WriteLine(value); + } + + private sealed record PythonHandleType(string TypeId, string ClassName, bool IsResourceBuilder); +} diff --git a/src/Aspire.Hosting.CodeGeneration.Python/PythonLanguageSupport.cs b/src/Aspire.Hosting.CodeGeneration.Python/PythonLanguageSupport.cs new file mode 100644 index 00000000000..6c14c0c8868 --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Python/PythonLanguageSupport.cs @@ -0,0 +1,225 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using Aspire.Hosting.Ats; + +namespace Aspire.Hosting.CodeGeneration.Python; + +/// +/// Provides language support for Python AppHosts. +/// Implements scaffolding, detection, and runtime configuration. +/// +public sealed class PythonLanguageSupport : ILanguageSupport +{ + /// + /// The language/runtime identifier for Python. + /// + private const string LanguageId = "python"; + + /// + /// The code generation target language. This maps to the ICodeGenerator.Language property. + /// + private const string CodeGenTarget = "Python"; + + private const string LanguageDisplayName = "Python"; + private static readonly string[] s_detectionPatterns = ["apphost.py"]; + + /// + public string Language => LanguageId; + + /// + public Dictionary Scaffold(ScaffoldRequest request) + { + var files = new Dictionary(); + + // Create apphost.py + files["apphost.py"] = """ + # Aspire Python AppHost + # For more information, see: https://aspire.dev + + import sys + from pathlib import Path + sys.path.insert(0, str(Path(__file__).parent / ".modules")) + + from aspire import create_builder + + builder = create_builder() + + # Add your resources here, for example: + # redis = builder.add_redis("cache") + # postgres = builder.add_postgres("db") + + builder.build().run() + """; + + // Create requirements.txt + files["requirements.txt"] = """ + # Aspire Python AppHost requirements + """; + + // Create uv-install.py + files["uv-install.py"] = """ + # Creates a venv and installs dependencies with uv. + from __future__ import annotations + + import os + import subprocess + import sys + from pathlib import Path + + + def run(command: list[str]) -> None: + result = subprocess.run(command) + if result.returncode != 0: + sys.exit(result.returncode) + + + root = Path(__file__).resolve().parent + venv_dir = root / ".venv" + python_path = venv_dir / ("Scripts" if os.name == "nt" else "bin") / ( + "python.exe" if os.name == "nt" else "python" + ) + + if not python_path.exists(): + run(["uv", "venv", str(venv_dir)]) + + run(["uv", "pip", "install", "-r", "requirements.txt", "--python", str(python_path)]) + """; + + // Create apphost.run.json with random ports + // Use PortSeed if provided (for testing), otherwise use random + var random = request.PortSeed.HasValue + ? new Random(request.PortSeed.Value) + : Random.Shared; + + var httpsPort = random.Next(10000, 65000); + var httpPort = random.Next(10000, 65000); + var otlpPort = random.Next(10000, 65000); + var resourceServicePort = random.Next(10000, 65000); + + files["apphost.run.json"] = $$""" + { + "profiles": { + "https": { + "applicationUrl": "https://localhost:{{httpsPort}};http://localhost:{{httpPort}}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:{{otlpPort}}", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:{{resourceServicePort}}" + } + } + } + } + """; + + return files; + } + + /// + public DetectionResult Detect(string directoryPath) + { + var appHostPath = Path.Combine(directoryPath, "apphost.py"); + if (!File.Exists(appHostPath)) + { + return DetectionResult.NotFound; + } + + var requirementsPath = Path.Combine(directoryPath, "requirements.txt"); + if (!File.Exists(requirementsPath)) + { + return DetectionResult.NotFound; + } + + return DetectionResult.Found(LanguageId, "apphost.py"); + } + + /// + public RuntimeSpec GetRuntimeSpec() + { + return new RuntimeSpec + { + Language = LanguageId, + DisplayName = LanguageDisplayName, + CodeGenLanguage = CodeGenTarget, + DetectionPatterns = s_detectionPatterns, + InstallDependencies = new CommandSpec + { + Command = GetPythonCommand(), + Args = ["uv-install.py"] + }, + Execute = new CommandSpec + { + Command = "uv", + Args = ["run", "python", "{appHostFile}"] + } + }; + } + + /// + /// Gets the appropriate Python command for the current platform. + /// On Windows: tries 'python' first, then 'py' (Python launcher) + /// On Linux/macOS: tries 'python3' first (more specific), then 'python' + /// + private static string GetPythonCommand() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Try 'python' first, then 'py' (Python launcher) + if (CommandExists("python")) + { + return "python"; + } + return "py"; + } + else + { + // Try 'python3' first (more specific), then 'python' + if (CommandExists("python3")) + { + return "python3"; + } + return "python"; + } + } + + /// + /// Checks if a command exists in the system PATH. + /// + private static bool CommandExists(string command) + { + try + { + var pathEnv = Environment.GetEnvironmentVariable("PATH"); + if (string.IsNullOrEmpty(pathEnv)) + { + return false; + } + + var pathSeparator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ';' : ':'; + var paths = pathEnv.Split(pathSeparator, StringSplitOptions.RemoveEmptyEntries); + + var extensions = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? new[] { ".exe", ".cmd", ".bat", "" } + : new[] { "" }; + + foreach (var path in paths) + { + foreach (var ext in extensions) + { + var fullPath = Path.Combine(path, command + ext); + if (File.Exists(fullPath)) + { + return true; + } + } + } + return false; + } + catch + { + return false; + } + } +} diff --git a/src/Aspire.Hosting.CodeGeneration.Python/Resources/base.py b/src/Aspire.Hosting.CodeGeneration.Python/Resources/base.py new file mode 100644 index 00000000000..8ba4c32d25b --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Python/Resources/base.py @@ -0,0 +1,255 @@ +# base.py - Core Aspire types: base classes, reference expressions, collections +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, Iterable, List + +from transport import AspireClient, Handle + + +class ReferenceExpression: + """Represents a reference expression passed to capabilities.""" + + def __init__(self, format_string: str, value_providers: List[Any]) -> None: + self._format_string = format_string + self._value_providers = value_providers + + @staticmethod + def create(format_string: str, *values: Any) -> "ReferenceExpression": + value_providers = [_extract_reference_value(value) for value in values] + return ReferenceExpression(format_string, value_providers) + + def to_json(self) -> Dict[str, Any]: + payload: Dict[str, Any] = {"format": self._format_string} + if self._value_providers: + payload["valueProviders"] = self._value_providers + return {"$expr": payload} + + def __str__(self) -> str: + return f"ReferenceExpression({self._format_string})" + + +def ref_expr(format_string: str, *values: Any) -> ReferenceExpression: + """Create a reference expression using a format string.""" + return ReferenceExpression.create(format_string, *values) + + +class HandleWrapperBase: + """Base wrapper for ATS handle types.""" + + def __init__(self, handle: Handle, client: AspireClient) -> None: + self._handle = handle + self._client = client + + def to_json(self) -> Dict[str, str]: + return self._handle.to_json() + + +class ResourceBuilderBase(HandleWrapperBase): + """Base class for resource builder wrappers.""" + + +class AspireList(HandleWrapperBase): + """Wrapper for mutable list handles with lazy handle resolution.""" + + def __init__( + self, + handle_or_context: Handle, + client: AspireClient, + getter_capability_id: str | None = None + ) -> None: + super().__init__(handle_or_context, client) + self._getter_capability_id = getter_capability_id + self._resolved_handle: Handle | None = None if getter_capability_id else handle_or_context + + def _ensure_handle(self) -> Handle: + """Lazily resolve the list handle by calling the getter capability.""" + if self._resolved_handle is not None: + return self._resolved_handle + if self._getter_capability_id: + self._resolved_handle = self._client.invoke_capability( + self._getter_capability_id, + {"context": self._handle} + ) + else: + self._resolved_handle = self._handle + return self._resolved_handle + + def count(self) -> int: + handle = self._ensure_handle() + return self._client.invoke_capability( + "Aspire.Hosting/List.length", + {"list": handle} + ) + + def get(self, index: int) -> Any: + handle = self._ensure_handle() + return self._client.invoke_capability( + "Aspire.Hosting/List.get", + {"list": handle, "index": index} + ) + + def add(self, item: Any) -> None: + handle = self._ensure_handle() + self._client.invoke_capability( + "Aspire.Hosting/List.add", + {"list": handle, "item": serialize_value(item)} + ) + + def remove_at(self, index: int) -> bool: + handle = self._ensure_handle() + return self._client.invoke_capability( + "Aspire.Hosting/List.removeAt", + {"list": handle, "index": index} + ) + + def clear(self) -> None: + handle = self._ensure_handle() + self._client.invoke_capability( + "Aspire.Hosting/List.clear", + {"list": handle} + ) + + def to_list(self) -> List[Any]: + handle = self._ensure_handle() + return self._client.invoke_capability( + "Aspire.Hosting/List.toArray", + {"list": handle} + ) + + def to_json(self) -> Dict[str, str]: + if self._resolved_handle is not None: + return self._resolved_handle.to_json() + return self._handle.to_json() + + +class AspireDict(HandleWrapperBase): + """Wrapper for mutable dictionary handles with lazy handle resolution.""" + + def __init__( + self, + handle_or_context: Handle, + client: AspireClient, + getter_capability_id: str | None = None + ) -> None: + super().__init__(handle_or_context, client) + self._getter_capability_id = getter_capability_id + self._resolved_handle: Handle | None = None if getter_capability_id else handle_or_context + + def _ensure_handle(self) -> Handle: + """Lazily resolve the dict handle by calling the getter capability.""" + if self._resolved_handle is not None: + return self._resolved_handle + if self._getter_capability_id: + self._resolved_handle = self._client.invoke_capability( + self._getter_capability_id, + {"context": self._handle} + ) + else: + self._resolved_handle = self._handle + return self._resolved_handle + + def count(self) -> int: + handle = self._ensure_handle() + return self._client.invoke_capability( + "Aspire.Hosting/Dict.count", + {"dict": handle} + ) + + def get(self, key: str) -> Any: + handle = self._ensure_handle() + return self._client.invoke_capability( + "Aspire.Hosting/Dict.get", + {"dict": handle, "key": key} + ) + + def set(self, key: str, value: Any) -> None: + handle = self._ensure_handle() + self._client.invoke_capability( + "Aspire.Hosting/Dict.set", + {"dict": handle, "key": key, "value": serialize_value(value)} + ) + + def contains_key(self, key: str) -> bool: + handle = self._ensure_handle() + return self._client.invoke_capability( + "Aspire.Hosting/Dict.has", + {"dict": handle, "key": key} + ) + + def remove(self, key: str) -> bool: + handle = self._ensure_handle() + return self._client.invoke_capability( + "Aspire.Hosting/Dict.remove", + {"dict": handle, "key": key} + ) + + def keys(self) -> List[str]: + handle = self._ensure_handle() + return self._client.invoke_capability( + "Aspire.Hosting/Dict.keys", + {"dict": handle} + ) + + def values(self) -> List[Any]: + handle = self._ensure_handle() + return self._client.invoke_capability( + "Aspire.Hosting/Dict.values", + {"dict": handle} + ) + + def to_dict(self) -> Dict[str, Any]: + handle = self._ensure_handle() + return self._client.invoke_capability( + "Aspire.Hosting/Dict.toObject", + {"dict": handle} + ) + + def to_json(self) -> Dict[str, str]: + if self._resolved_handle is not None: + return self._resolved_handle.to_json() + return self._handle.to_json() + + +def serialize_value(value: Any) -> Any: + if isinstance(value, ReferenceExpression): + return value.to_json() + + if isinstance(value, Handle): + return value.to_json() + + if hasattr(value, "to_json") and callable(value.to_json): + return value.to_json() + + if hasattr(value, "to_dict") and callable(value.to_dict): + return {key: serialize_value(val) for key, val in value.to_dict().items()} + + if isinstance(value, Enum): + return value.value + + if isinstance(value, list): + return [serialize_value(item) for item in value] + + if isinstance(value, tuple): + return [serialize_value(item) for item in value] + + if isinstance(value, dict): + return {key: serialize_value(val) for key, val in value.items()} + + return value + + +def _extract_reference_value(value: Any) -> Any: + if value is None: + raise ValueError("Cannot use None in reference expressions.") + + if isinstance(value, (str, int, float)): + return value + + if isinstance(value, Handle): + return value.to_json() + + if hasattr(value, "to_json") and callable(value.to_json): + return value.to_json() + + raise ValueError(f"Unsupported reference expression value: {type(value).__name__}") diff --git a/src/Aspire.Hosting.CodeGeneration.Python/Resources/transport.py b/src/Aspire.Hosting.CodeGeneration.Python/Resources/transport.py new file mode 100644 index 00000000000..a06ca8d6e23 --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Python/Resources/transport.py @@ -0,0 +1,330 @@ +# transport.py - ATS transport layer: JSON-RPC, Handle, callbacks, cancellation +from __future__ import annotations + +import asyncio +import json +import os +import socket +import threading +import time +from typing import Any, Callable, Dict, Optional + + +class AtsErrorCodes: + CapabilityNotFound = "CAPABILITY_NOT_FOUND" + HandleNotFound = "HANDLE_NOT_FOUND" + TypeMismatch = "TYPE_MISMATCH" + InvalidArgument = "INVALID_ARGUMENT" + ArgumentOutOfRange = "ARGUMENT_OUT_OF_RANGE" + CallbackError = "CALLBACK_ERROR" + InternalError = "INTERNAL_ERROR" + + +class CapabilityError(RuntimeError): + def __init__(self, error: Dict[str, Any]) -> None: + super().__init__(error.get("message", "Capability error")) + self.error = error + + @property + def code(self) -> str: + return self.error.get("code", "") + + @property + def capability(self) -> Optional[str]: + return self.error.get("capability") + + +class Handle: + def __init__(self, marshalled: Dict[str, str]) -> None: + self._handle_id = marshalled["$handle"] + self._type_id = marshalled["$type"] + + @property + def handle_id(self) -> str: + return self._handle_id + + @property + def type_id(self) -> str: + return self._type_id + + def to_json(self) -> Dict[str, str]: + return {"$handle": self._handle_id, "$type": self._type_id} + + def __str__(self) -> str: + return f"Handle<{self._type_id}>({self._handle_id})" + + +def is_marshalled_handle(value: Any) -> bool: + return isinstance(value, dict) and "$handle" in value and "$type" in value + + +def is_ats_error(value: Any) -> bool: + return isinstance(value, dict) and "$error" in value + + +_handle_wrapper_registry: Dict[str, Callable[[Handle, "AspireClient"], Any]] = {} +_callback_registry: Dict[str, Callable[..., Any]] = {} +_callback_lock = threading.Lock() +_callback_counter = 0 + + +def register_handle_wrapper(type_id: str, factory: Callable[[Handle, "AspireClient"], Any]) -> None: + _handle_wrapper_registry[type_id] = factory + + +def wrap_if_handle(value: Any, client: Optional["AspireClient"] = None) -> Any: + if is_marshalled_handle(value): + handle = Handle(value) + if client is not None: + factory = _handle_wrapper_registry.get(handle.type_id) + if factory: + return factory(handle, client) + return handle + return value + + +def register_callback(callback: Callable[..., Any]) -> str: + global _callback_counter + with _callback_lock: + _callback_counter += 1 + callback_id = f"callback_{_callback_counter}_{int(time.time() * 1000)}" + _callback_registry[callback_id] = callback + return callback_id + + +def unregister_callback(callback_id: str) -> bool: + return _callback_registry.pop(callback_id, None) is not None + + +class CancellationToken: + def __init__(self) -> None: + self._callbacks: list[Callable[[], None]] = [] + self._cancelled = False + + def cancel(self) -> None: + if self._cancelled: + return + self._cancelled = True + for callback in list(self._callbacks): + callback() + self._callbacks.clear() + + def register(self, callback: Callable[[], None]) -> Callable[[], None]: + if self._cancelled: + callback() + return lambda: None + self._callbacks.append(callback) + + def unregister() -> None: + if callback in self._callbacks: + self._callbacks.remove(callback) + + return unregister + + +def register_cancellation(token: Optional[CancellationToken], client: "AspireClient") -> Optional[str]: + if token is None: + return None + cancellation_id = f"ct_{int(time.time() * 1000)}_{id(token)}" + token.register(lambda: client.cancel_token(cancellation_id)) + return cancellation_id + + +class AspireClient: + def __init__(self, socket_path: str) -> None: + self._socket_path = socket_path + self._stream: Optional[Any] = None + self._next_id = 1 + self._disconnect_callbacks: list[Callable[[], None]] = [] + self._connected = False + self._io_lock = threading.Lock() + + def connect(self) -> None: + if self._connected: + return + self._stream = _open_stream(self._socket_path) + self._connected = True + + def on_disconnect(self, callback: Callable[[], None]) -> None: + self._disconnect_callbacks.append(callback) + + def invoke_capability(self, capability_id: str, args: Optional[Dict[str, Any]] = None) -> Any: + result = self._send_request("invokeCapability", [capability_id, args]) + if is_ats_error(result): + raise CapabilityError(result["$error"]) + return wrap_if_handle(result, self) + + def cancel_token(self, token_id: str) -> bool: + return bool(self._send_request("cancelToken", [token_id])) + + def disconnect(self) -> None: + self._connected = False + if self._stream: + try: + self._stream.close() + finally: + self._stream = None + for callback in self._disconnect_callbacks: + try: + callback() + except Exception: + pass + + def _send_request(self, method: str, params: list[Any]) -> Any: + """Send a request and wait for the response synchronously. + + On Windows named pipes, concurrent read/write from different threads + causes blocking issues. So we use a fully synchronous approach: + 1. Write the request + 2. Read messages until we get our response + 3. Handle any callback requests inline + """ + with self._io_lock: + request_id = self._next_id + self._next_id += 1 + + message = { + "jsonrpc": "2.0", + "id": request_id, + "method": method, + "params": params + } + self._write_message(message) + + # Read messages until we get our response + while True: + response = self._read_message() + if response is None: + raise RuntimeError("Connection closed while waiting for response.") + + # Check if this is a callback request from the server + if "method" in response: + self._handle_callback_request(response) + continue + + # This is a response - check if it's our response + response_id = response.get("id") + if response_id == request_id: + if "error" in response: + raise RuntimeError(response["error"].get("message", "RPC error")) + return response.get("result") + # Response for a different request (shouldn't happen in sync mode) + + def _write_message(self, message: Dict[str, Any]) -> None: + if not self._stream: + raise RuntimeError("Not connected to AppHost.") + body = json.dumps(message, separators=(",", ":")).encode("utf-8") + header = f"Content-Length: {len(body)}\r\n\r\n".encode("utf-8") + self._stream.write(header + body) + self._stream.flush() + + def _handle_callback_request(self, message: Dict[str, Any]) -> None: + """Handle a callback request from the server.""" + method = message.get("method") + request_id = message.get("id") + + if method != "invokeCallback": + if request_id is not None: + self._write_message({ + "jsonrpc": "2.0", + "id": request_id, + "error": {"code": -32601, "message": f"Unknown method: {method}"} + }) + return + + params = message.get("params", []) + callback_id = params[0] if len(params) > 0 else None + args = params[1] if len(params) > 1 else None + try: + result = _invoke_callback(callback_id, args, self) + self._write_message({"jsonrpc": "2.0", "id": request_id, "result": result}) + except Exception as exc: + self._write_message({ + "jsonrpc": "2.0", + "id": request_id, + "error": {"code": -32000, "message": str(exc)} + }) + + def _read_message(self) -> Optional[Dict[str, Any]]: + if not self._stream: + return None + headers: Dict[str, str] = {} + while True: + line = _read_line(self._stream) + if not line: + return None + if line in (b"\r\n", b"\n"): + break + key, value = line.decode("utf-8").split(":", 1) + headers[key.strip().lower()] = value.strip() + length = int(headers.get("content-length", "0")) + if length <= 0: + return None + body = _read_exact(self._stream, length) + return json.loads(body.decode("utf-8")) + + +def _invoke_callback(callback_id: str, args: Any, client: AspireClient) -> Any: + if callback_id is None: + raise RuntimeError("Callback ID missing.") + callback = _callback_registry.get(callback_id) + if callback is None: + raise RuntimeError(f"Callback not found: {callback_id}") + + positional_args: list[Any] = [] + if isinstance(args, dict): + index = 0 + while True: + key = f"p{index}" + if key not in args: + break + positional_args.append(wrap_if_handle(args[key], client)) + index += 1 + elif args is not None: + positional_args.append(wrap_if_handle(args, client)) + + result = callback(*positional_args) + if asyncio.iscoroutine(result): + return asyncio.run(result) + return result + + +def _read_exact(stream: Any, length: int) -> bytes: + data = b"" + while len(data) < length: + chunk = stream.read(length - len(data)) + if not chunk: + raise EOFError("Unexpected end of stream.") + data += chunk + return data + + +def _read_line(stream: Any) -> bytes: + """Read a line from the stream byte-by-byte. + + This is needed because readline() doesn't work reliably on Windows named pipes. + We read byte-by-byte until we hit a newline. + """ + line = b"" + while True: + byte = stream.read(1) + if not byte: + return line if line else b"" + line += byte + if byte == b"\n": + return line + + +def _open_stream(socket_path: str) -> Any: + """Open a stream to the AppHost server. + + On Windows, uses named pipes. On Unix, uses Unix domain sockets. + """ + if os.name == "nt": + pipe_path = f"\\\\.\\pipe\\{socket_path}" + import io + fd = os.open(pipe_path, os.O_RDWR | os.O_BINARY) + return io.FileIO(fd, mode='r+b', closefd=True) + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(socket_path) + return sock.makefile("rwb", buffering=0) diff --git a/src/Aspire.Hosting.CodeGeneration.Rust/Aspire.Hosting.CodeGeneration.Rust.csproj b/src/Aspire.Hosting.CodeGeneration.Rust/Aspire.Hosting.CodeGeneration.Rust.csproj new file mode 100644 index 00000000000..2ff23c58639 --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Rust/Aspire.Hosting.CodeGeneration.Rust.csproj @@ -0,0 +1,29 @@ + + + + $(DefaultTargetFramework) + enable + enable + Aspire.Hosting.CodeGeneration.Rust + + true + + true + true + + + + + + + + + + + + + + + + + diff --git a/src/Aspire.Hosting.CodeGeneration.Rust/AtsRustCodeGenerator.cs b/src/Aspire.Hosting.CodeGeneration.Rust/AtsRustCodeGenerator.cs new file mode 100644 index 00000000000..5f5f08e6f0f --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Rust/AtsRustCodeGenerator.cs @@ -0,0 +1,936 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.Reflection; +using System.Text; +using System.Text.Json; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Ats; + +namespace Aspire.Hosting.CodeGeneration.Rust; + +/// +/// Generates a Rust SDK using the ATS (Aspire Type System) capability-based API. +/// Produces wrapper structs that proxy capabilities via JSON-RPC. +/// +public sealed class AtsRustCodeGenerator : ICodeGenerator +{ + private static readonly HashSet s_rustKeywords = new(StringComparer.Ordinal) + { + "as", "async", "await", "break", "const", "continue", "crate", "dyn", "else", + "enum", "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", + "match", "mod", "move", "mut", "pub", "ref", "return", "self", "Self", + "static", "struct", "super", "trait", "true", "type", "unsafe", "use", + "where", "while", "abstract", "become", "box", "do", "final", "macro", + "override", "priv", "try", "typeof", "unsized", "virtual", "yield" + }; + + private TextWriter _writer = null!; + private readonly Dictionary _structNames = new(StringComparer.Ordinal); + private readonly Dictionary _dtoNames = new(StringComparer.Ordinal); + private readonly Dictionary _enumNames = new(StringComparer.Ordinal); + + /// + public string Language => "Rust"; + + /// + public Dictionary GenerateDistributedApplication(AtsContext context) + { + return new Dictionary(StringComparer.Ordinal) + { + ["mod.rs"] = """ + //! Aspire Rust SDK + //! GENERATED CODE - DO NOT EDIT + + pub mod transport; + pub mod base; + pub mod aspire; + + pub use transport::*; + pub use base::*; + pub use aspire::*; + """, + ["transport.rs"] = GetEmbeddedResource("transport.rs"), + ["base.rs"] = GetEmbeddedResource("base.rs"), + ["aspire.rs"] = GenerateAspireSdk(context) + }; + } + + private static string GetEmbeddedResource(string name) + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = $"Aspire.Hosting.CodeGeneration.Rust.Resources.{name}"; + + using var stream = assembly.GetManifestResourceStream(resourceName) + ?? throw new InvalidOperationException($"Embedded resource '{name}' not found."); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private string GenerateAspireSdk(AtsContext context) + { + using var stringWriter = new StringWriter(CultureInfo.InvariantCulture); + _writer = stringWriter; + + var capabilities = context.Capabilities; + var dtoTypes = context.DtoTypes; + var enumTypes = context.EnumTypes; + + _enumNames.Clear(); + foreach (var enumType in enumTypes) + { + _enumNames[enumType.TypeId] = SanitizeIdentifier(enumType.Name); + } + + _dtoNames.Clear(); + foreach (var dto in dtoTypes) + { + _dtoNames[dto.TypeId] = SanitizeIdentifier(dto.Name); + } + + var handleTypes = BuildHandleTypes(context); + var capabilitiesByTarget = GroupCapabilitiesByTarget(capabilities); + var listTypeIds = CollectListAndDictTypeIds(capabilities); + + WriteHeader(); + GenerateEnumTypes(enumTypes); + GenerateDtoTypes(dtoTypes); + GenerateHandleTypes(handleTypes, capabilitiesByTarget); + GenerateHandleWrapperRegistrations(handleTypes, listTypeIds); + GenerateConnectionHelpers(); + + return stringWriter.ToString(); + } + + private void WriteHeader() + { + WriteLine("//! aspire.rs - Capability-based Aspire SDK"); + WriteLine("//! GENERATED CODE - DO NOT EDIT"); + WriteLine(); + WriteLine("use std::collections::HashMap;"); + WriteLine("use std::sync::Arc;"); + WriteLine(); + WriteLine("use serde::{Deserialize, Serialize};"); + WriteLine("use serde_json::{json, Value};"); + WriteLine(); + WriteLine("use crate::transport::{"); + WriteLine(" AspireClient, CancellationToken, Handle,"); + WriteLine(" register_callback, register_cancellation, serialize_value,"); + WriteLine("};"); + WriteLine("use crate::base::{"); + WriteLine(" HandleWrapperBase, ResourceBuilderBase, ReferenceExpression,"); + WriteLine(" AspireList, AspireDict, serialize_handle, HasHandle,"); + WriteLine("};"); + WriteLine(); + } + + private void GenerateEnumTypes(IReadOnlyList enumTypes) + { + if (enumTypes.Count == 0) + { + return; + } + + WriteLine("// ============================================================================"); + WriteLine("// Enums"); + WriteLine("// ============================================================================"); + WriteLine(); + + foreach (var enumType in enumTypes) + { + if (enumType.ClrType is null) + { + continue; + } + + var enumName = _enumNames[enumType.TypeId]; + WriteLine($"/// {enumType.Name}"); + WriteLine("#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]"); + WriteLine($"pub enum {enumName} {{"); + var firstMember = true; + foreach (var member in Enum.GetNames(enumType.ClrType)) + { + var memberName = ToPascalCase(member); + if (firstMember) + { + WriteLine($" #[default]"); + firstMember = false; + } + WriteLine($" #[serde(rename = \"{member}\")]"); + WriteLine($" {memberName},"); + } + WriteLine("}"); + WriteLine(); + + // Generate Display trait + WriteLine($"impl std::fmt::Display for {enumName} {{"); + WriteLine(" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {"); + WriteLine(" match self {"); + foreach (var member in Enum.GetNames(enumType.ClrType)) + { + var memberName = ToPascalCase(member); + WriteLine($" Self::{memberName} => write!(f, \"{member}\"),"); + } + WriteLine(" }"); + WriteLine(" }"); + WriteLine("}"); + WriteLine(); + } + } + + private void GenerateDtoTypes(IReadOnlyList dtoTypes) + { + if (dtoTypes.Count == 0) + { + return; + } + + WriteLine("// ============================================================================"); + WriteLine("// DTOs"); + WriteLine("// ============================================================================"); + WriteLine(); + + foreach (var dto in dtoTypes) + { + // Skip ReferenceExpression - it's defined in base.rs + if (dto.TypeId == AtsConstants.ReferenceExpressionTypeId) + { + continue; + } + + var dtoName = _dtoNames[dto.TypeId]; + WriteLine($"/// {dto.Name}"); + WriteLine("#[derive(Debug, Clone, Default, Serialize, Deserialize)]"); + WriteLine($"pub struct {dtoName} {{"); + foreach (var property in dto.Properties) + { + var propertyName = ToSnakeCase(property.Name); + var propertyType = MapTypeRefToRustForDto(property.Type, property.IsOptional); + if (property.IsOptional) + { + WriteLine($" #[serde(rename = \"{property.Name}\", skip_serializing_if = \"Option::is_none\")]"); + } + else + { + WriteLine($" #[serde(rename = \"{property.Name}\")]"); + } + WriteLine($" pub {propertyName}: {propertyType},"); + } + WriteLine("}"); + WriteLine(); + + // Generate to_map method + WriteLine($"impl {dtoName} {{"); + WriteLine(" pub fn to_map(&self) -> HashMap {"); + WriteLine(" let mut map = HashMap::new();"); + foreach (var property in dto.Properties) + { + var propertyName = ToSnakeCase(property.Name); + if (property.IsOptional) + { + WriteLine($" if let Some(ref v) = self.{propertyName} {{"); + WriteLine($" map.insert(\"{property.Name}\".to_string(), serde_json::to_value(v).unwrap_or(Value::Null));"); + WriteLine(" }"); + } + else + { + WriteLine($" map.insert(\"{property.Name}\".to_string(), serde_json::to_value(&self.{propertyName}).unwrap_or(Value::Null));"); + } + } + WriteLine(" map"); + WriteLine(" }"); + WriteLine("}"); + WriteLine(); + } + } + + private void GenerateHandleTypes( + IReadOnlyList handleTypes, + Dictionary> capabilitiesByTarget) + { + if (handleTypes.Count == 0) + { + return; + } + + WriteLine("// ============================================================================"); + WriteLine("// Handle Wrappers"); + WriteLine("// ============================================================================"); + WriteLine(); + + foreach (var handleType in handleTypes.OrderBy(t => t.StructName, StringComparer.Ordinal)) + { + WriteLine($"/// Wrapper for {handleType.TypeId}"); + WriteLine($"pub struct {handleType.StructName} {{"); + WriteLine(" handle: Handle,"); + WriteLine(" client: Arc,"); + WriteLine("}"); + WriteLine(); + + // Implement HasHandle trait + WriteLine($"impl HasHandle for {handleType.StructName} {{"); + WriteLine(" fn handle(&self) -> &Handle {"); + WriteLine(" &self.handle"); + WriteLine(" }"); + WriteLine("}"); + WriteLine(); + + // Constructor and methods + WriteLine($"impl {handleType.StructName} {{"); + WriteLine(" pub fn new(handle: Handle, client: Arc) -> Self {"); + WriteLine(" Self { handle, client }"); + WriteLine(" }"); + WriteLine(); + WriteLine(" pub fn handle(&self) -> &Handle {"); + WriteLine(" &self.handle"); + WriteLine(" }"); + WriteLine(); + WriteLine(" pub fn client(&self) -> &Arc {"); + WriteLine(" &self.client"); + WriteLine(" }"); + + if (capabilitiesByTarget.TryGetValue(handleType.TypeId, out var methods)) + { + foreach (var method in methods) + { + GenerateCapabilityMethod(handleType.StructName, method); + } + } + + WriteLine("}"); + WriteLine(); + } + } + + private void GenerateCapabilityMethod(string structName, AtsCapabilityInfo capability) + { + var targetParamName = capability.TargetParameterName ?? "builder"; + var methodName = ToSnakeCase(capability.MethodName); + var parameters = capability.Parameters + .Where(p => !string.Equals(p.Name, targetParamName, StringComparison.Ordinal)) + .ToList(); + + // Check if this is a List/Dict property getter (no parameters, returns List/Dict) + if (parameters.Count == 0 && IsListOrDictPropertyGetter(capability.ReturnType)) + { + GenerateListOrDictProperty(structName, capability, methodName); + return; + } + + var returnType = MapTypeRefToRust(capability.ReturnType, false); + var hasReturn = capability.ReturnType.TypeId != AtsConstants.Void; + + // Build parameter list + var paramList = new StringBuilder(); + paramList.Append("&self"); + foreach (var parameter in parameters) + { + var paramName = ToSnakeCase(parameter.Name); + string paramType; + if (parameter.IsCallback) + { + paramType = "impl Fn(Vec) -> Value + Send + Sync + 'static"; + } + else if (IsCancellationToken(parameter)) + { + paramType = "Option<&CancellationToken>"; + } + else if (IsHandleType(parameter.Type)) + { + // Handle wrappers are passed by reference + var handleTypeName = MapTypeRefToRust(parameter.Type, false); + paramType = parameter.IsOptional ? $"Option<&{handleTypeName}>" : $"&{handleTypeName}"; + } + else + { + // Use idiomatic Rust parameter types (e.g., &str instead of String) + paramType = MapParameterTypeToRust(parameter.Type, parameter.IsOptional); + } + paramList.Append(CultureInfo.InvariantCulture, $", {paramName}: {paramType}"); + } + + // Generate doc comment + if (!string.IsNullOrEmpty(capability.Description)) + { + WriteLine(); + WriteLine($" /// {capability.Description}"); + } + + var resultType = hasReturn ? $"Result<{returnType}, Box>" : "Result<(), Box>"; + WriteLine($" pub fn {methodName}({paramList}) -> {resultType} {{"); + WriteLine(" let mut args: HashMap = HashMap::new();"); + WriteLine($" args.insert(\"{targetParamName}\".to_string(), self.handle.to_json());"); + + foreach (var parameter in parameters) + { + var paramName = ToSnakeCase(parameter.Name); + if (parameter.IsCallback) + { + WriteLine($" let callback_id = register_callback({paramName});"); + WriteLine($" args.insert(\"{parameter.Name}\".to_string(), Value::String(callback_id));"); + continue; + } + + if (IsCancellationToken(parameter)) + { + WriteLine($" if let Some(token) = {paramName} {{"); + WriteLine($" let token_id = register_cancellation(token, self.client.clone());"); + WriteLine($" args.insert(\"{parameter.Name}\".to_string(), Value::String(token_id));"); + WriteLine(" }"); + continue; + } + + // Handle wrappers need to be converted to their handle JSON + if (IsHandleType(parameter.Type)) + { + if (parameter.IsOptional) + { + WriteLine($" if let Some(ref v) = {paramName} {{"); + WriteLine($" args.insert(\"{parameter.Name}\".to_string(), v.handle().to_json());"); + WriteLine(" }"); + } + else + { + WriteLine($" args.insert(\"{parameter.Name}\".to_string(), {paramName}.handle().to_json());"); + } + continue; + } + + if (parameter.IsOptional) + { + WriteLine($" if let Some(ref v) = {paramName} {{"); + WriteLine($" args.insert(\"{parameter.Name}\".to_string(), serde_json::to_value(v).unwrap_or(Value::Null));"); + WriteLine(" }"); + } + else + { + WriteLine($" args.insert(\"{parameter.Name}\".to_string(), serde_json::to_value(&{paramName}).unwrap_or(Value::Null));"); + } + } + + WriteLine($" let result = self.client.invoke_capability(\"{capability.CapabilityId}\", args)?;"); + + if (hasReturn) + { + var returnTypeRef = capability.ReturnType; + + // Generate conversion based on return type + if (IsHandleType(returnTypeRef)) + { + var wrappedType = MapHandleType(returnTypeRef.TypeId); + WriteLine($" let handle: Handle = serde_json::from_value(result)?;"); + WriteLine($" Ok({wrappedType}::new(handle, self.client.clone()))"); + } + else if (returnTypeRef?.TypeId == AtsConstants.CancellationToken) + { + // CancellationToken needs special handling - create from handle + WriteLine($" let handle: Handle = serde_json::from_value(result)?;"); + WriteLine($" Ok(CancellationToken::new(handle, self.client.clone()))"); + } + else if (returnTypeRef?.Category == AtsTypeCategory.Dict && returnTypeRef.IsReadOnly == false) + { + // Handle-backed AspireDict + WriteLine($" let handle: Handle = serde_json::from_value(result)?;"); + WriteLine($" Ok(AspireDict::new(handle, self.client.clone()))"); + } + else if (returnTypeRef?.Category == AtsTypeCategory.List && returnTypeRef.IsReadOnly == false) + { + // Handle-backed AspireList + WriteLine($" let handle: Handle = serde_json::from_value(result)?;"); + WriteLine($" Ok(AspireList::new(handle, self.client.clone()))"); + } + else + { + WriteLine($" Ok(serde_json::from_value(result)?)"); + } + } + else + { + WriteLine(" Ok(())"); + } + + WriteLine(" }"); + } + + private static bool IsListOrDictPropertyGetter(AtsTypeRef? returnType) + { + if (returnType is null) + { + return false; + } + + return returnType.Category == AtsTypeCategory.List || returnType.Category == AtsTypeCategory.Dict; + } + +#pragma warning disable IDE0060 // Remove unused parameter - structName kept for API consistency + private void GenerateListOrDictProperty(string structName, AtsCapabilityInfo capability, string methodName) +#pragma warning restore IDE0060 + { + var returnType = capability.ReturnType!; + var isDict = returnType.Category == AtsTypeCategory.Dict; + var wrapperType = isDict ? "AspireDict" : "AspireList"; + + // Determine type arguments + string typeArgs; + if (isDict) + { + var keyType = MapTypeRefToRust(returnType.KeyType, false); + var valueType = MapTypeRefToRust(returnType.ValueType, false); + typeArgs = $"<{keyType}, {valueType}>"; + } + else + { + var elementType = MapTypeRefToRust(returnType.ElementType, false); + typeArgs = $"<{elementType}>"; + } + + var fullType = $"{wrapperType}{typeArgs}"; + + // Generate doc comment + if (!string.IsNullOrEmpty(capability.Description)) + { + WriteLine(); + WriteLine($" /// {capability.Description}"); + } + + // Generate getter method that creates AspireList/AspireDict with lazy getter + WriteLine($" pub fn {methodName}(&self) -> {fullType} {{"); + WriteLine($" {wrapperType}::with_getter(self.handle.clone(), self.client.clone(), \"{capability.CapabilityId}\")"); + WriteLine(" }"); + } + +#pragma warning disable IDE0060 // Remove unused parameter - keeping for API consistency with other generators + private void GenerateHandleWrapperRegistrations( + IReadOnlyList handleTypes, + HashSet listTypeIds) +#pragma warning restore IDE0060 + { + WriteLine("// ============================================================================"); + WriteLine("// Handle wrapper registrations"); + WriteLine("// ============================================================================"); + WriteLine(); + WriteLine("pub fn register_all_wrappers() {"); + WriteLine(" // Handle wrappers are created inline in generated code"); + WriteLine(" // This function is provided for API compatibility"); + WriteLine("}"); + WriteLine(); + } + + private void GenerateConnectionHelpers() + { + var builderStructName = _structNames.TryGetValue(AtsConstants.BuilderTypeId, out var name) + ? name + : "DistributedApplicationBuilder"; + + WriteLine("// ============================================================================"); + WriteLine("// Connection Helpers"); + WriteLine("// ============================================================================"); + WriteLine(); + WriteLine("/// Establishes a connection to the AppHost server."); + WriteLine("pub fn connect() -> Result, Box> {"); + WriteLine(" let socket_path = std::env::var(\"REMOTE_APP_HOST_SOCKET_PATH\")"); + WriteLine(" .map_err(|_| \"REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`\")?;"); + WriteLine(" let client = Arc::new(AspireClient::new(&socket_path));"); + WriteLine(" client.connect()?;"); + WriteLine(" Ok(client)"); + WriteLine("}"); + WriteLine(); + WriteLine($"/// Creates a new distributed application builder."); + WriteLine($"pub fn create_builder(options: Option) -> Result<{builderStructName}, Box> {{"); + WriteLine(" let client = connect()?;"); + WriteLine(" let mut resolved_options: HashMap = HashMap::new();"); + WriteLine(" if let Some(opts) = options {"); + WriteLine(" for (k, v) in opts.to_map() {"); + WriteLine(" resolved_options.insert(k, v);"); + WriteLine(" }"); + WriteLine(" }"); + WriteLine(" if !resolved_options.contains_key(\"Args\") {"); + WriteLine(" let args: Vec = std::env::args().skip(1).collect();"); + WriteLine(" resolved_options.insert(\"Args\".to_string(), serde_json::to_value(args).unwrap_or(Value::Null));"); + WriteLine(" }"); + WriteLine(" if !resolved_options.contains_key(\"ProjectDirectory\") {"); + WriteLine(" if let Ok(pwd) = std::env::current_dir() {"); + WriteLine(" resolved_options.insert(\"ProjectDirectory\".to_string(), Value::String(pwd.to_string_lossy().to_string()));"); + WriteLine(" }"); + WriteLine(" }"); + WriteLine(" let mut args: HashMap = HashMap::new();"); + WriteLine(" args.insert(\"options\".to_string(), serde_json::to_value(resolved_options).unwrap_or(Value::Null));"); + WriteLine(" let result = client.invoke_capability(\"Aspire.Hosting/createBuilderWithOptions\", args)?;"); + WriteLine(" let handle: Handle = serde_json::from_value(result)?;"); + WriteLine($" Ok({builderStructName}::new(handle, client))"); + WriteLine("}"); + WriteLine(); + } + + private IReadOnlyList BuildHandleTypes(AtsContext context) + { + var handleTypeIds = new HashSet(StringComparer.Ordinal); + foreach (var handleType in context.HandleTypes) + { + // Skip ReferenceExpression - it's defined in base.rs + if (handleType.AtsTypeId == AtsConstants.ReferenceExpressionTypeId) + { + continue; + } + handleTypeIds.Add(handleType.AtsTypeId); + } + + foreach (var capability in context.Capabilities) + { + AddHandleTypeIfNeeded(handleTypeIds, capability.TargetType); + AddHandleTypeIfNeeded(handleTypeIds, capability.ReturnType); + foreach (var parameter in capability.Parameters) + { + AddHandleTypeIfNeeded(handleTypeIds, parameter.Type); + if (parameter.IsCallback && parameter.CallbackParameters is not null) + { + foreach (var callbackParam in parameter.CallbackParameters) + { + AddHandleTypeIfNeeded(handleTypeIds, callbackParam.Type); + } + } + } + } + + _structNames.Clear(); + foreach (var typeId in handleTypeIds) + { + _structNames[typeId] = CreateStructName(typeId); + } + + var handleTypeMap = context.HandleTypes.ToDictionary(t => t.AtsTypeId, StringComparer.Ordinal); + var results = new List(); + foreach (var typeId in handleTypeIds) + { + var isResourceBuilder = false; + if (handleTypeMap.TryGetValue(typeId, out var typeInfo)) + { + isResourceBuilder = typeInfo.ClrType is not null && + typeof(IResource).IsAssignableFrom(typeInfo.ClrType); + } + + results.Add(new RustHandleType(typeId, _structNames[typeId], isResourceBuilder)); + } + + return results; + } + + private static Dictionary> GroupCapabilitiesByTarget( + IReadOnlyList capabilities) + { + var result = new Dictionary>(StringComparer.Ordinal); + + foreach (var capability in capabilities) + { + if (string.IsNullOrEmpty(capability.TargetTypeId)) + { + continue; + } + + var targetTypes = capability.ExpandedTargetTypes.Count > 0 + ? capability.ExpandedTargetTypes + : capability.TargetType is not null + ? [capability.TargetType] + : []; + + foreach (var targetType in targetTypes) + { + if (targetType.TypeId is null) + { + continue; + } + + if (!result.TryGetValue(targetType.TypeId, out var list)) + { + list = new List(); + result[targetType.TypeId] = list; + } + list.Add(capability); + } + } + + return result; + } + + private static HashSet CollectListAndDictTypeIds(IReadOnlyList capabilities) + { + var typeIds = new HashSet(StringComparer.Ordinal); + foreach (var capability in capabilities) + { + AddListOrDictTypeIfNeeded(typeIds, capability.TargetType); + AddListOrDictTypeIfNeeded(typeIds, capability.ReturnType); + foreach (var parameter in capability.Parameters) + { + AddListOrDictTypeIfNeeded(typeIds, parameter.Type); + if (parameter.IsCallback && parameter.CallbackParameters is not null) + { + foreach (var callbackParam in parameter.CallbackParameters) + { + AddListOrDictTypeIfNeeded(typeIds, callbackParam.Type); + } + } + } + } + + return typeIds; + } + + private string MapTypeRefToRust(AtsTypeRef? typeRef, bool isOptional) + { + if (typeRef is null) + { + return "Value"; + } + + if (typeRef.TypeId == AtsConstants.ReferenceExpressionTypeId) + { + return isOptional ? "Option" : "ReferenceExpression"; + } + + var baseType = typeRef.Category switch + { + AtsTypeCategory.Primitive => MapPrimitiveType(typeRef.TypeId), + AtsTypeCategory.Enum => MapEnumType(typeRef.TypeId), + AtsTypeCategory.Handle => MapHandleType(typeRef.TypeId), + AtsTypeCategory.Dto => MapDtoType(typeRef.TypeId), + AtsTypeCategory.Callback => "Box) -> Value + Send + Sync>", + AtsTypeCategory.Array => $"Vec<{MapTypeRefToRust(typeRef.ElementType, false)}>", + AtsTypeCategory.List => typeRef.IsReadOnly + ? $"Vec<{MapTypeRefToRust(typeRef.ElementType, false)}>" + : $"AspireList<{MapTypeRefToRust(typeRef.ElementType, false)}>", + AtsTypeCategory.Dict => typeRef.IsReadOnly + ? $"HashMap<{MapTypeRefToRust(typeRef.KeyType, false)}, {MapTypeRefToRust(typeRef.ValueType, false)}>" + : $"AspireDict<{MapTypeRefToRust(typeRef.KeyType, false)}, {MapTypeRefToRust(typeRef.ValueType, false)}>", + AtsTypeCategory.Union => "Value", + AtsTypeCategory.Unknown => "Value", + _ => "Value" + }; + + return isOptional ? $"Option<{baseType}>" : baseType; + } + + /// + /// Maps type references to Rust types for use in DTO fields. + /// Handle types are mapped to Handle (the serializable type) instead of wrapper types. + /// + private string MapTypeRefToRustForDto(AtsTypeRef? typeRef, bool isOptional) + { + if (typeRef is null) + { + return "Value"; + } + + if (typeRef.TypeId == AtsConstants.ReferenceExpressionTypeId) + { + return isOptional ? "Option" : "ReferenceExpression"; + } + + var baseType = typeRef.Category switch + { + AtsTypeCategory.Primitive => MapPrimitiveType(typeRef.TypeId), + AtsTypeCategory.Enum => MapEnumType(typeRef.TypeId), + // Use Handle directly for handle types in DTOs since Handle implements Serialize/Deserialize + AtsTypeCategory.Handle => "Handle", + AtsTypeCategory.Dto => MapDtoType(typeRef.TypeId), + AtsTypeCategory.Callback => "Value", // Callbacks can't be serialized in DTOs + AtsTypeCategory.Array => $"Vec<{MapTypeRefToRustForDto(typeRef.ElementType, false)}>", + AtsTypeCategory.List => $"Vec<{MapTypeRefToRustForDto(typeRef.ElementType, false)}>", + AtsTypeCategory.Dict => $"HashMap<{MapTypeRefToRustForDto(typeRef.KeyType, false)}, {MapTypeRefToRustForDto(typeRef.ValueType, false)}>", + AtsTypeCategory.Union => "Value", + AtsTypeCategory.Unknown => "Value", + _ => "Value" + }; + + return isOptional ? $"Option<{baseType}>" : baseType; + } + + private string MapHandleType(string typeId) => + _structNames.TryGetValue(typeId, out var name) ? name : "Handle"; + + private string MapDtoType(string typeId) => + _dtoNames.TryGetValue(typeId, out var name) ? name : "HashMap"; + + private string MapEnumType(string typeId) => + _enumNames.TryGetValue(typeId, out var name) ? name : "String"; + + private static string MapPrimitiveType(string typeId) => typeId switch + { + AtsConstants.String or AtsConstants.Char => "String", + AtsConstants.Number => "f64", + AtsConstants.Boolean => "bool", + AtsConstants.Void => "()", + AtsConstants.Any => "Value", + AtsConstants.DateTime or AtsConstants.DateTimeOffset or + AtsConstants.DateOnly or AtsConstants.TimeOnly => "String", + AtsConstants.TimeSpan => "f64", + AtsConstants.Guid or AtsConstants.Uri => "String", + AtsConstants.CancellationToken => "CancellationToken", + _ => "Value" + }; + + // Maps parameter types to more idiomatic Rust types (e.g., &str instead of String) + private string MapParameterTypeToRust(AtsTypeRef? typeRef, bool isOptional) + { + if (typeRef is null) + { + return "Value"; + } + + // For primitives that are strings, use &str for parameters (more idiomatic) + if (typeRef.Category == AtsTypeCategory.Primitive) + { + var baseType = typeRef.TypeId switch + { + AtsConstants.String or AtsConstants.Char => "&str", + AtsConstants.Number => "f64", + AtsConstants.Boolean => "bool", + AtsConstants.Void => "()", + AtsConstants.Any => "&Value", + AtsConstants.DateTime or AtsConstants.DateTimeOffset or + AtsConstants.DateOnly or AtsConstants.TimeOnly => "&str", + AtsConstants.TimeSpan => "f64", + AtsConstants.Guid or AtsConstants.Uri => "&str", + AtsConstants.CancellationToken => "&CancellationToken", + _ => "&Value" + }; + return isOptional ? $"Option<{baseType}>" : baseType; + } + + // For arrays/lists of strings, use Vec since we need owned values + // For other types, use the standard mapping + return MapTypeRefToRust(typeRef, isOptional); + } + + private static bool IsHandleType(AtsTypeRef? typeRef) => + typeRef?.Category == AtsTypeCategory.Handle + && typeRef.TypeId != AtsConstants.ReferenceExpressionTypeId; + + private static bool IsCancellationToken(AtsParameterInfo parameter) => + parameter.Type?.TypeId == AtsConstants.CancellationToken; + + private static void AddHandleTypeIfNeeded(HashSet handleTypeIds, AtsTypeRef? typeRef) + { + if (typeRef is null) + { + return; + } + + // Skip ReferenceExpression - it's defined in base.rs + if (typeRef.TypeId == AtsConstants.ReferenceExpressionTypeId) + { + return; + } + + if (typeRef.Category == AtsTypeCategory.Handle) + { + handleTypeIds.Add(typeRef.TypeId); + } + } + + private static void AddListOrDictTypeIfNeeded(HashSet typeIds, AtsTypeRef? typeRef) + { + if (typeRef is null) + { + return; + } + + if (typeRef.Category == AtsTypeCategory.List || typeRef.Category == AtsTypeCategory.Dict) + { + if (!typeRef.IsReadOnly) + { + typeIds.Add(typeRef.TypeId); + } + } + } + + private string CreateStructName(string typeId) + { + var baseName = ExtractTypeName(typeId); + var name = SanitizeIdentifier(baseName); + if (_structNames.Values.Contains(name, StringComparer.Ordinal)) + { + var assemblyName = typeId.Split('/')[0]; + var assemblyPrefix = SanitizeIdentifier(assemblyName); + name = $"{assemblyPrefix}{name}"; + } + + var counter = 1; + var candidate = name; + while (_structNames.Values.Contains(candidate, StringComparer.Ordinal)) + { + counter++; + candidate = $"{name}{counter}"; + } + + return candidate; + } + + private static string ExtractTypeName(string typeId) + { + var slashIndex = typeId.IndexOf('/', StringComparison.Ordinal); + var typeName = slashIndex >= 0 ? typeId[(slashIndex + 1)..] : typeId; + var lastDot = typeName.LastIndexOf('.'); + var plusIndex = typeName.LastIndexOf('+'); + var delimiterIndex = Math.Max(lastDot, plusIndex); + return delimiterIndex >= 0 ? typeName[(delimiterIndex + 1)..] : typeName; + } + + private static string SanitizeIdentifier(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + return "_"; + } + + var builder = new StringBuilder(name.Length); + foreach (var ch in name) + { + builder.Append(char.IsLetterOrDigit(ch) || ch == '_' ? ch : '_'); + } + + if (!char.IsLetter(builder[0]) && builder[0] != '_') + { + builder.Insert(0, '_'); + } + + var sanitized = builder.ToString(); + return s_rustKeywords.Contains(sanitized) ? $"r#{sanitized}" : sanitized; + } + + /// + /// Converts a name to PascalCase for Rust type names. + /// + private static string ToPascalCase(string name) + { + if (string.IsNullOrEmpty(name)) + { + return name; + } + if (char.IsUpper(name[0])) + { + return name; + } + return char.ToUpperInvariant(name[0]) + name[1..]; + } + + /// + /// Converts a name to snake_case for Rust identifiers. + /// + private static string ToSnakeCase(string name) + { + if (string.IsNullOrEmpty(name)) + { + return name; + } + + return JsonNamingPolicy.SnakeCaseLower.ConvertName(name); + } + + private void WriteLine(string value = "") + { + _writer.WriteLine(value); + } + + private sealed record RustHandleType(string TypeId, string StructName, bool IsResourceBuilder); +} diff --git a/src/Aspire.Hosting.CodeGeneration.Rust/Resources/base.rs b/src/Aspire.Hosting.CodeGeneration.Rust/Resources/base.rs new file mode 100644 index 00000000000..2554b29de2d --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Rust/Resources/base.rs @@ -0,0 +1,239 @@ +//! Base types for Aspire Rust SDK. + +use std::collections::HashMap; +use std::sync::Arc; + +use serde::{Serialize, Deserialize}; +use serde_json::{json, Value}; + +use crate::transport::{AspireClient, Handle}; + +/// Base type for all handle wrappers. +pub struct HandleWrapperBase { + handle: Handle, + client: Arc, +} + +impl HandleWrapperBase { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Base type for resource builders. +pub struct ResourceBuilderBase { + base: HandleWrapperBase, +} + +impl ResourceBuilderBase { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { + base: HandleWrapperBase::new(handle, client), + } + } + + pub fn handle(&self) -> &Handle { + self.base.handle() + } + + pub fn client(&self) -> &Arc { + self.base.client() + } +} + +/// A reference expression for dynamic values. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ReferenceExpression { + pub format: String, + pub args: Vec, +} + +impl ReferenceExpression { + pub fn new(format: impl Into, args: Vec) -> Self { + Self { + format: format.into(), + args, + } + } + + pub fn to_json(&self) -> Value { + json!({ + "$refExpr": { + "format": self.format, + "args": self.args + } + }) + } +} + +/// Convenience function to create a reference expression. +pub fn ref_expr(format: impl Into, args: Vec) -> ReferenceExpression { + ReferenceExpression::new(format, args) +} + +/// A handle-backed list with lazy handle resolution. +pub struct AspireList { + context_handle: Handle, + client: Arc, + getter_capability_id: Option, + resolved_handle: std::cell::OnceCell, + _marker: std::marker::PhantomData, +} + +impl AspireList { + pub fn new(handle: Handle, client: Arc) -> Self { + let resolved = std::cell::OnceCell::new(); + let _ = resolved.set(handle.clone()); + Self { + context_handle: handle, + client, + getter_capability_id: None, + resolved_handle: resolved, + _marker: std::marker::PhantomData, + } + } + + pub fn with_getter(context_handle: Handle, client: Arc, getter_capability_id: impl Into) -> Self { + Self { + context_handle, + client, + getter_capability_id: Some(getter_capability_id.into()), + resolved_handle: std::cell::OnceCell::new(), + _marker: std::marker::PhantomData, + } + } + + fn ensure_handle(&self) -> &Handle { + self.resolved_handle.get_or_init(|| { + if let Some(ref cap_id) = self.getter_capability_id { + let mut args = HashMap::new(); + args.insert("context".to_string(), self.context_handle.to_json()); + if let Ok(result) = self.client.invoke_capability(cap_id, args) { + if let Ok(handle) = serde_json::from_value::(result) { + return handle; + } + } + } + self.context_handle.clone() + }) + } + + pub fn handle(&self) -> &Handle { + self.ensure_handle() + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// A handle-backed dictionary with lazy handle resolution. +pub struct AspireDict { + context_handle: Handle, + client: Arc, + getter_capability_id: Option, + resolved_handle: std::cell::OnceCell, + _key_marker: std::marker::PhantomData, + _value_marker: std::marker::PhantomData, +} + +impl AspireDict { + pub fn new(handle: Handle, client: Arc) -> Self { + let resolved = std::cell::OnceCell::new(); + let _ = resolved.set(handle.clone()); + Self { + context_handle: handle, + client, + getter_capability_id: None, + resolved_handle: resolved, + _key_marker: std::marker::PhantomData, + _value_marker: std::marker::PhantomData, + } + } + + pub fn with_getter(context_handle: Handle, client: Arc, getter_capability_id: impl Into) -> Self { + Self { + context_handle, + client, + getter_capability_id: Some(getter_capability_id.into()), + resolved_handle: std::cell::OnceCell::new(), + _key_marker: std::marker::PhantomData, + _value_marker: std::marker::PhantomData, + } + } + + fn ensure_handle(&self) -> &Handle { + self.resolved_handle.get_or_init(|| { + if let Some(ref cap_id) = self.getter_capability_id { + let mut args = HashMap::new(); + args.insert("context".to_string(), self.context_handle.to_json()); + if let Ok(result) = self.client.invoke_capability(cap_id, args) { + if let Ok(handle) = serde_json::from_value::(result) { + return handle; + } + } + } + self.context_handle.clone() + }) + } + + pub fn handle(&self) -> &Handle { + self.ensure_handle() + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Trait for types that can be serialized to JSON. +pub trait ToJson { + fn to_json(&self) -> Value; +} + +impl ToJson for Handle { + fn to_json(&self) -> Value { + self.to_json() + } +} + +impl ToJson for ReferenceExpression { + fn to_json(&self) -> Value { + self.to_json() + } +} + +/// Serialize a value to its JSON representation. +pub fn serialize_value(value: impl Into) -> Value { + value.into() +} + +/// Serialize a handle wrapper to its JSON representation. +pub fn serialize_handle(wrapper: &impl HasHandle) -> Value { + wrapper.handle().to_json() +} + +/// Trait for types that have an underlying handle. +pub trait HasHandle { + fn handle(&self) -> &Handle; +} + +impl HasHandle for HandleWrapperBase { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl HasHandle for ResourceBuilderBase { + fn handle(&self) -> &Handle { + self.base.handle() + } +} diff --git a/src/Aspire.Hosting.CodeGeneration.Rust/Resources/transport.rs b/src/Aspire.Hosting.CodeGeneration.Rust/Resources/transport.rs new file mode 100644 index 00000000000..14a28068766 --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Rust/Resources/transport.rs @@ -0,0 +1,534 @@ +//! Aspire ATS transport layer for JSON-RPC communication. + +use std::collections::HashMap; +use std::io::{BufRead, BufReader, Read, Write}; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex, RwLock}; + +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; + +// Platform-specific connection type +#[cfg(target_os = "windows")] +type Connection = std::fs::File; +#[cfg(not(target_os = "windows"))] +type Connection = std::os::unix::net::UnixStream; + +/// Standard ATS error codes. +pub mod ats_error_codes { + pub const CAPABILITY_NOT_FOUND: &str = "CAPABILITY_NOT_FOUND"; + pub const HANDLE_NOT_FOUND: &str = "HANDLE_NOT_FOUND"; + pub const TYPE_MISMATCH: &str = "TYPE_MISMATCH"; + pub const INVALID_ARGUMENT: &str = "INVALID_ARGUMENT"; + pub const ARGUMENT_OUT_OF_RANGE: &str = "ARGUMENT_OUT_OF_RANGE"; + pub const CALLBACK_ERROR: &str = "CALLBACK_ERROR"; + pub const INTERNAL_ERROR: &str = "INTERNAL_ERROR"; +} + +/// Error returned from capability invocations. +#[derive(Debug, Clone)] +pub struct CapabilityError { + pub code: String, + pub message: String, + pub capability: Option, +} + +impl std::fmt::Display for CapabilityError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +impl std::error::Error for CapabilityError {} + +/// A reference to a server-side object. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Handle { + #[serde(rename = "$handle")] + pub handle_id: String, + #[serde(rename = "$type")] + pub type_id: String, +} + +impl Handle { + pub fn new(handle_id: String, type_id: String) -> Self { + Self { handle_id, type_id } + } + + pub fn to_json(&self) -> Value { + json!({ + "$handle": self.handle_id, + "$type": self.type_id + }) + } +} + +impl std::fmt::Display for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Handle<{}>({})", self.type_id, self.handle_id) + } +} + +/// Checks if a value is a marshalled handle. +pub fn is_marshalled_handle(value: &Value) -> bool { + if let Value::Object(obj) = value { + obj.contains_key("$handle") && obj.contains_key("$type") + } else { + false + } +} + +/// Checks if a value is an ATS error. +pub fn is_ats_error(value: &Value) -> bool { + if let Value::Object(obj) = value { + obj.contains_key("$error") + } else { + false + } +} + +/// Type alias for handle wrapper factory functions. +pub type HandleWrapperFactory = Box) -> Box + Send + Sync>; + +lazy_static::lazy_static! { + static ref HANDLE_WRAPPER_REGISTRY: RwLock> = RwLock::new(HashMap::new()); + static ref CALLBACK_REGISTRY: Mutex) -> Value + Send + Sync>>> = Mutex::new(HashMap::new()); + static ref CALLBACK_COUNTER: AtomicU64 = AtomicU64::new(0); +} + +/// Registers a handle wrapper factory for a type. +pub fn register_handle_wrapper(type_id: &str, factory: HandleWrapperFactory) { + let mut registry = HANDLE_WRAPPER_REGISTRY.write().unwrap(); + registry.insert(type_id.to_string(), factory); +} + +/// Wraps a value if it's a marshalled handle. +pub fn wrap_if_handle(value: Value, client: Option>) -> Value { + if !is_marshalled_handle(&value) { + return value; + } + + // For now, just return the value - handle wrapping will be done by generated code + value +} + +/// Registers a callback and returns its ID. +pub fn register_callback(callback: F) -> String +where + F: Fn(Vec) -> Value + Send + Sync + 'static, +{ + let id = format!( + "callback_{}_{}", + CALLBACK_COUNTER.fetch_add(1, Ordering::SeqCst), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + ); + + let mut registry = CALLBACK_REGISTRY.lock().unwrap(); + registry.insert(id.clone(), Box::new(callback)); + id +} + +/// Unregisters a callback by ID. +pub fn unregister_callback(callback_id: &str) -> bool { + let mut registry = CALLBACK_REGISTRY.lock().unwrap(); + registry.remove(callback_id).is_some() +} + +/// Cancellation token for cooperative cancellation. +pub struct CancellationToken { + handle: Option, + client: Option>, + cancelled: AtomicBool, + callbacks: Mutex>>, +} + +impl CancellationToken { + /// Create a new local cancellation token. + pub fn new_local() -> Self { + Self { + handle: None, + client: None, + cancelled: AtomicBool::new(false), + callbacks: Mutex::new(Vec::new()), + } + } + + /// Create a handle-backed cancellation token. + pub fn new(handle: Handle, client: Arc) -> Self { + Self { + handle: Some(handle), + client: Some(client), + cancelled: AtomicBool::new(false), + callbacks: Mutex::new(Vec::new()), + } + } + + /// Get the handle if this is a handle-backed token. + pub fn handle(&self) -> Option<&Handle> { + self.handle.as_ref() + } + + pub fn cancel(&self) { + if self.cancelled.swap(true, Ordering::SeqCst) { + return; + } + let callbacks: Vec<_> = { + let mut guard = self.callbacks.lock().unwrap(); + std::mem::take(&mut *guard) + }; + for cb in callbacks { + cb(); + } + } + + pub fn is_cancelled(&self) -> bool { + self.cancelled.load(Ordering::SeqCst) + } + + pub fn register(&self, callback: F) + where + F: FnOnce() + Send + 'static, + { + if self.is_cancelled() { + callback(); + return; + } + let mut guard = self.callbacks.lock().unwrap(); + guard.push(Box::new(callback)); + } +} + +impl Default for CancellationToken { + fn default() -> Self { + Self::new_local() + } +} + +/// Registers a cancellation token with the client. +pub fn register_cancellation(token: &CancellationToken, client: Arc) -> String { + let id = format!( + "ct_{}_{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + + let id_clone = id.clone(); + let client_clone = client; + token.register(move || { + let _ = client_clone.cancel_token(&id_clone); + }); + + id +} + +/// Client for communicating with the AppHost server. +pub struct AspireClient { + socket_path: String, + conn: Mutex>, + next_id: AtomicU64, + connected: AtomicBool, + disconnect_callbacks: Mutex>>, +} + +impl AspireClient { + pub fn new(socket_path: &str) -> Self { + Self { + socket_path: socket_path.to_string(), + conn: Mutex::new(None), + next_id: AtomicU64::new(1), + connected: AtomicBool::new(false), + disconnect_callbacks: Mutex::new(Vec::new()), + } + } + + /// Connects to the AppHost server. + pub fn connect(&self) -> Result<(), Box> { + if self.connected.load(Ordering::SeqCst) { + return Ok(()); + } + + let conn = open_connection(&self.socket_path)?; + *self.conn.lock().unwrap() = Some(conn); + self.connected.store(true, Ordering::SeqCst); + + eprintln!("[Rust ATS] Connected to AppHost server"); + Ok(()) + } + + /// Registers a callback for disconnection. + pub fn on_disconnect(&self, callback: F) + where + F: Fn() + Send + Sync + 'static, + { + let mut callbacks = self.disconnect_callbacks.lock().unwrap(); + callbacks.push(Box::new(callback)); + } + + /// Invokes a capability on the server. + pub fn invoke_capability( + &self, + capability_id: &str, + args: HashMap, + ) -> Result> { + let result = self.send_request("invokeCapability", json!([capability_id, args]))?; + + if is_ats_error(&result) { + if let Value::Object(obj) = &result { + if let Some(Value::Object(err_obj)) = obj.get("$error") { + return Err(Box::new(CapabilityError { + code: err_obj + .get("code") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(), + message: err_obj + .get("message") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(), + capability: err_obj + .get("capability") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + })); + } + } + } + + Ok(wrap_if_handle(result, None)) + } + + /// Cancels a cancellation token on the server. + pub fn cancel_token(&self, token_id: &str) -> Result> { + let result = self.send_request("cancelToken", json!([token_id]))?; + Ok(result.as_bool().unwrap_or(false)) + } + + /// Disconnects from the server. + pub fn disconnect(&self) { + self.connected.store(false, Ordering::SeqCst); + *self.conn.lock().unwrap() = None; + + let callbacks = self.disconnect_callbacks.lock().unwrap(); + for cb in callbacks.iter() { + cb(); + } + } + + fn send_request(&self, method: &str, params: Value) -> Result> { + let request_id = self.next_id.fetch_add(1, Ordering::SeqCst); + + let message = json!({ + "jsonrpc": "2.0", + "id": request_id, + "method": method, + "params": params + }); + + eprintln!("[Rust ATS] Sending request {} with id={}", method, request_id); + self.write_message(&message)?; + + loop { + let response = self.read_message()?; + eprintln!("[Rust ATS] Received response: {:?}", response); + + // Check if this is a callback request from the server + if response.get("method").is_some() { + self.handle_callback_request(&response)?; + continue; + } + + // Check if this is our response + if let Some(resp_id) = response.get("id").and_then(|v| v.as_u64()) { + if resp_id == request_id { + if let Some(error) = response.get("error") { + let message = error + .get("message") + .and_then(|v| v.as_str()) + .unwrap_or("Unknown error"); + return Err(message.into()); + } + return Ok(response.get("result").cloned().unwrap_or(Value::Null)); + } + } + } + } + + fn write_message(&self, message: &Value) -> Result<(), Box> { + let mut conn = self.conn.lock().unwrap(); + let conn = conn.as_mut().ok_or("Not connected to AppHost")?; + + let body = serde_json::to_string(message)?; + let header = format!("Content-Length: {}\r\n\r\n", body.len()); + + conn.write_all(header.as_bytes())?; + conn.write_all(body.as_bytes())?; + conn.flush()?; + + Ok(()) + } + + fn read_message(&self) -> Result> { + let mut conn = self.conn.lock().unwrap(); + let conn = conn.as_mut().ok_or("Not connected")?; + + // Read headers + let mut headers = HashMap::new(); + let mut reader = BufReader::new(conn.try_clone()?); + + loop { + let mut line = String::new(); + reader.read_line(&mut line)?; + let line = line.trim(); + + if line.is_empty() { + break; + } + + if let Some(idx) = line.find(':') { + let key = line[..idx].trim().to_lowercase(); + let value = line[idx + 1..].trim().to_string(); + headers.insert(key, value); + } + } + + // Read body + let content_length: usize = headers + .get("content-length") + .ok_or("Missing content-length")? + .parse()?; + + let mut body = vec![0u8; content_length]; + reader.read_exact(&mut body)?; + + let message: Value = serde_json::from_slice(&body)?; + Ok(message) + } + + fn handle_callback_request(&self, message: &Value) -> Result<(), Box> { + let method = message + .get("method") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let request_id = message.get("id").cloned(); + + if method != "invokeCallback" { + if let Some(id) = request_id { + self.write_message(&json!({ + "jsonrpc": "2.0", + "id": id, + "error": {"code": -32601, "message": format!("Unknown method: {}", method)} + }))?; + } + return Ok(()); + } + + let params = message.get("params").and_then(|v| v.as_array()); + let callback_id = params + .and_then(|p| p.first()) + .and_then(|v| v.as_str()) + .unwrap_or(""); + let args = params.and_then(|p| p.get(1)).cloned().unwrap_or(Value::Null); + + let result = invoke_callback(callback_id, &args); + + match result { + Ok(value) => { + if let Some(id) = request_id { + self.write_message(&json!({ + "jsonrpc": "2.0", + "id": id, + "result": value + }))?; + } + } + Err(e) => { + if let Some(id) = request_id { + self.write_message(&json!({ + "jsonrpc": "2.0", + "id": id, + "error": {"code": -32000, "message": e.to_string()} + }))?; + } + } + } + + Ok(()) + } +} + +fn invoke_callback(callback_id: &str, args: &Value) -> Result> { + if callback_id.is_empty() { + return Err("Callback ID missing".into()); + } + + let registry = CALLBACK_REGISTRY.lock().unwrap(); + let callback = registry + .get(callback_id) + .ok_or_else(|| format!("Callback not found: {}", callback_id))?; + + // Convert args to positional arguments + let positional_args: Vec = if let Value::Object(obj) = args { + let mut result = Vec::new(); + for i in 0.. { + let key = format!("p{}", i); + if let Some(val) = obj.get(&key) { + result.push(val.clone()); + } else { + break; + } + } + result + } else if !args.is_null() { + vec![args.clone()] + } else { + Vec::new() + }; + + Ok(callback(positional_args)) +} + +#[cfg(target_os = "windows")] +fn open_connection(socket_path: &str) -> Result> { + use std::path::Path; + + // Extract just the filename from the socket path for the named pipe + let pipe_name = Path::new(socket_path) + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or(socket_path); + let pipe_path = format!("\\\\.\\pipe\\{}", pipe_name); + eprintln!("[Rust ATS] Opening Windows named pipe: {}", pipe_path); + + let file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(&pipe_path)?; + + eprintln!("[Rust ATS] Named pipe opened successfully"); + Ok(file) +} + +#[cfg(not(target_os = "windows"))] +fn open_connection(socket_path: &str) -> Result> { + use std::os::unix::net::UnixStream; + + eprintln!("[Rust ATS] Opening Unix domain socket: {}", socket_path); + let stream = UnixStream::connect(socket_path)?; + eprintln!("[Rust ATS] Unix domain socket opened successfully"); + Ok(stream) +} + +/// Serializes a value to its JSON representation. +pub fn serialize_value(value: &Value) -> Value { + value.clone() +} diff --git a/src/Aspire.Hosting.CodeGeneration.Rust/RustLanguageSupport.cs b/src/Aspire.Hosting.CodeGeneration.Rust/RustLanguageSupport.cs new file mode 100644 index 00000000000..431efd61bed --- /dev/null +++ b/src/Aspire.Hosting.CodeGeneration.Rust/RustLanguageSupport.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.Ats; + +namespace Aspire.Hosting.CodeGeneration.Rust; + +/// +/// Provides language support for Rust AppHosts. +/// Implements scaffolding, detection, and runtime configuration. +/// +public sealed class RustLanguageSupport : ILanguageSupport +{ + /// + /// The language/runtime identifier for Rust. + /// + private const string LanguageId = "rust"; + + /// + /// The code generation target language. This maps to the ICodeGenerator.Language property. + /// + private const string CodeGenTarget = "Rust"; + + private const string LanguageDisplayName = "Rust"; + private static readonly string[] s_detectionPatterns = ["apphost.rs"]; + + /// + public string Language => LanguageId; + + /// + public Dictionary Scaffold(ScaffoldRequest request) + { + var files = new Dictionary(); + + // Create src/main.rs + files["src/main.rs"] = """ + // Aspire Rust AppHost + // For more information, see: https://aspire.dev + + #[path = "../.modules/mod.rs"] + mod aspire; + + use aspire::*; + + fn main() -> Result<(), Box> { + let builder = create_builder(None)?; + + // Add your resources here, for example: + // let redis = builder.add_redis("cache")?; + // let postgres = builder.add_postgres("db")?; + + let app = builder.build()?; + app.run(None)?; + Ok(()) + } + """; + + // Create Cargo.toml + files["Cargo.toml"] = """ + [package] + name = "apphost" + version = "0.1.0" + edition = "2021" + + [dependencies] + serde = { version = "1.0", features = ["derive"] } + serde_json = "1.0" + lazy_static = "1.4" + """; + + // Create apphost.rs marker file for detection + files["apphost.rs"] = """ + // Aspire Rust AppHost marker file + // This file is used to detect the project type. + // The actual entry point is in src/main.rs. + """; + + // Create apphost.run.json with random ports + var random = request.PortSeed.HasValue + ? new Random(request.PortSeed.Value) + : Random.Shared; + + var httpsPort = random.Next(10000, 65000); + var httpPort = random.Next(10000, 65000); + var otlpPort = random.Next(10000, 65000); + var resourceServicePort = random.Next(10000, 65000); + + files["apphost.run.json"] = $$""" + { + "profiles": { + "https": { + "applicationUrl": "https://localhost:{{httpsPort}};http://localhost:{{httpPort}}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:{{otlpPort}}", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:{{resourceServicePort}}" + } + } + } + } + """; + + return files; + } + + /// + public DetectionResult Detect(string directoryPath) + { + var appHostPath = Path.Combine(directoryPath, "apphost.rs"); + if (!File.Exists(appHostPath)) + { + return DetectionResult.NotFound; + } + + var cargoPath = Path.Combine(directoryPath, "Cargo.toml"); + if (!File.Exists(cargoPath)) + { + return DetectionResult.NotFound; + } + + return DetectionResult.Found(LanguageId, "apphost.rs"); + } + + /// + public RuntimeSpec GetRuntimeSpec() + { + return new RuntimeSpec + { + Language = LanguageId, + DisplayName = LanguageDisplayName, + CodeGenLanguage = CodeGenTarget, + DetectionPatterns = s_detectionPatterns, + // No separate install step - cargo run will build automatically + InstallDependencies = null, + Execute = new CommandSpec + { + Command = "cargo", + Args = ["run"] + } + }; + } +} diff --git a/src/Aspire.Hosting/Aspire.Hosting.csproj b/src/Aspire.Hosting/Aspire.Hosting.csproj index 5e5a3904c60..2d857fc9d27 100644 --- a/src/Aspire.Hosting/Aspire.Hosting.csproj +++ b/src/Aspire.Hosting/Aspire.Hosting.csproj @@ -116,6 +116,14 @@ + + + + + + + + diff --git a/src/Aspire.Hosting/Ats/AtsCapabilityScanner.cs b/src/Aspire.Hosting/Ats/AtsCapabilityScanner.cs index b4be4bf114b..b10b85faf33 100644 --- a/src/Aspire.Hosting/Ats/AtsCapabilityScanner.cs +++ b/src/Aspire.Hosting/Ats/AtsCapabilityScanner.cs @@ -1068,6 +1068,7 @@ private static ContextTypeCapabilitiesResult CreateContextTypeCapabilities( ReturnType = propertyTypeRef!, TargetTypeId = typeId, TargetType = contextTypeRef, + TargetParameterName = "context", ReturnsBuilder = false, CapabilityKind = AtsCapabilityKind.PropertyGetter, SourceLocation = $"{fullName}.{property.Name}" @@ -1113,6 +1114,7 @@ private static ContextTypeCapabilitiesResult CreateContextTypeCapabilities( ReturnType = contextTypeRef, TargetTypeId = typeId, TargetType = contextTypeRef, + TargetParameterName = "context", ReturnsBuilder = false, CapabilityKind = AtsCapabilityKind.PropertySetter, SourceLocation = $"{fullName}.{property.Name}" @@ -1262,6 +1264,7 @@ private static ContextTypeCapabilitiesResult CreateContextTypeCapabilities( ReturnType = returnTypeRef ?? CreateVoidTypeRef(), TargetTypeId = typeId, TargetType = instanceContextTypeRef, + TargetParameterName = "context", ReturnsBuilder = false, CapabilityKind = AtsCapabilityKind.InstanceMethod, SourceLocation = $"{contextType.FullName}.{method.Name}" diff --git a/tests/Aspire.Cli.Tests/Projects/DefaultLanguageDiscoveryTests.cs b/tests/Aspire.Cli.Tests/Projects/DefaultLanguageDiscoveryTests.cs index 3c24b27e67b..d1752760f94 100644 --- a/tests/Aspire.Cli.Tests/Projects/DefaultLanguageDiscoveryTests.cs +++ b/tests/Aspire.Cli.Tests/Projects/DefaultLanguageDiscoveryTests.cs @@ -47,6 +47,19 @@ public async Task GetAvailableLanguagesAsync_ReturnsTypeScriptLanguage() Assert.Contains("apphost.ts", typescript.DetectionPatterns); } + [Fact] + public async Task GetAvailableLanguagesAsync_ReturnsPythonLanguage() + { + var discovery = new DefaultLanguageDiscovery(); + + var languages = await discovery.GetAvailableLanguagesAsync(); + + var python = languages.FirstOrDefault(l => l.LanguageId.Value == KnownLanguageId.Python); + Assert.NotNull(python); + Assert.Equal(KnownLanguageId.PythonDisplayName, python.DisplayName); + Assert.Contains("apphost.py", python.DetectionPatterns); + } + [Theory] [InlineData("test.csproj", KnownLanguageId.CSharp)] [InlineData("Test.csproj", KnownLanguageId.CSharp)] @@ -57,6 +70,8 @@ public async Task GetAvailableLanguagesAsync_ReturnsTypeScriptLanguage() [InlineData("APPHOST.CS", KnownLanguageId.CSharp)] [InlineData("apphost.ts", "typescript/nodejs")] [InlineData("AppHost.ts", "typescript/nodejs")] + [InlineData("apphost.py", KnownLanguageId.Python)] + [InlineData("AppHost.py", KnownLanguageId.Python)] public void GetLanguageByFile_ReturnsCorrectLanguage(string fileName, string expectedLanguageId) { var discovery = new DefaultLanguageDiscovery(); @@ -71,7 +86,6 @@ public void GetLanguageByFile_ReturnsCorrectLanguage(string fileName, string exp [Theory] [InlineData("test.txt")] [InlineData("program.cs")] - [InlineData("apphost.py")] [InlineData("random.js")] public void GetLanguageByFile_ReturnsNullForUnknownFiles(string fileName) { @@ -86,6 +100,7 @@ public void GetLanguageByFile_ReturnsNullForUnknownFiles(string fileName) [Theory] [InlineData(KnownLanguageId.CSharp)] [InlineData("typescript/nodejs")] + [InlineData(KnownLanguageId.Python)] public void GetLanguageById_ReturnsCorrectLanguage(string languageId) { var discovery = new DefaultLanguageDiscovery(); diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.Go.Tests.csproj b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.Go.Tests.csproj new file mode 100644 index 00000000000..3b166e466fb --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.Go.Tests.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + + + + + + + + + + + + + + + + + + + diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs new file mode 100644 index 00000000000..217a0c9a9e8 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/AtsGoCodeGeneratorTests.cs @@ -0,0 +1,317 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Ats; +using Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes; + +namespace Aspire.Hosting.CodeGeneration.Go.Tests; + +public class AtsGoCodeGeneratorTests +{ + private readonly AtsGoCodeGenerator _generator = new(); + + // The test types are compiled into this assembly via Compile Include + private const string TestTypesAssemblyName = "Aspire.Hosting.CodeGeneration.Go.Tests"; + + [Fact] + public void Language_ReturnsGo() + { + Assert.Equal("Go", _generator.Language); + } + + [Fact] + public async Task GenerateDistributedApplication_WithTestTypes_GeneratesCorrectOutput() + { + // Arrange + var atsContext = CreateContextFromTestAssembly(); + + // Act + var files = _generator.GenerateDistributedApplication(atsContext); + + // Assert + Assert.Contains("aspire.go", files.Keys); + Assert.Contains("transport.go", files.Keys); + Assert.Contains("base.go", files.Keys); + Assert.Contains("go.mod", files.Keys); + + await Verify(files["aspire.go"], extension: "go") + .UseFileName("AtsGeneratedAspire"); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_IncludesCapabilities() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert that capabilities are discovered + Assert.NotEmpty(capabilities); + + // Check for specific capabilities (uses AssemblyName/methodName format) + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/withOptionalString"); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_DeriveCorrectMethodNames() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert method names are derived correctly + var addTestRedis = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Equal("addTestRedis", addTestRedis.MethodName); + + var withPersistence = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.Equal("withPersistence", withPersistence.MethodName); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_CapturesParameters() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert parameters are captured + var addTestRedis = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Equal(2, addTestRedis.Parameters.Count); + Assert.Equal("Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder", addTestRedis.TargetTypeId); + Assert.Contains(addTestRedis.Parameters, p => p.Name == "name" && p.Type?.TypeId == "string"); + Assert.Contains(addTestRedis.Parameters, p => p.Name == "port" && p.IsOptional); + } + + [Fact] + public void Scanner_ReturnsBuilder_TrueForResourceBuilderReturnTypes() + { + // Verify that ReturnsBuilder is correctly set to true for methods + // that return IResourceBuilder + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // addTestRedis returns IResourceBuilder - should have ReturnsBuilder = true + var addTestRedis = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.NotNull(addTestRedis); + Assert.True(addTestRedis.ReturnsBuilder, + "addTestRedis returns IResourceBuilder but ReturnsBuilder is false - fluent chaining won't work"); + + // withPersistence also returns IResourceBuilder + var withPersistence = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.NotNull(withPersistence); + Assert.True(withPersistence.ReturnsBuilder, + "withPersistence returns IResourceBuilder but ReturnsBuilder is false - fluent chaining won't work"); + } + + [Fact] + public async Task Scanner_AddTestRedis_HasCorrectTypeMetadata() + { + // Verify the entire capability object for addTestRedis + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var addTestRedis = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.NotNull(addTestRedis); + + await Verify(addTestRedis).UseFileName("AddTestRedisCapability"); + } + + [Fact] + public async Task Scanner_WithPersistence_HasCorrectExpandedTargets() + { + // Verify the entire capability object for withPersistence + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var withPersistence = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.NotNull(withPersistence); + + await Verify(withPersistence).UseFileName("WithPersistenceCapability"); + } + + [Fact] + public async Task Scanner_WithOptionalString_HasCorrectExpandedTargets() + { + // Verify withOptionalString (targets IResource, should expand to TestRedisResource) + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var withOptionalString = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withOptionalString"); + Assert.NotNull(withOptionalString); + + await Verify(withOptionalString).UseFileName("WithOptionalStringCapability"); + } + + [Fact] + public async Task Scanner_HostingAssembly_AddContainerCapability() + { + // Verify the addContainer capability from the real Aspire.Hosting assembly + var capabilities = ScanCapabilitiesFromHostingAssembly(); + + var addContainer = capabilities.FirstOrDefault(c => c.CapabilityId == "Aspire.Hosting/addContainer"); + Assert.NotNull(addContainer); + + await Verify(addContainer).UseFileName("HostingAddContainerCapability"); + } + + [Fact] + public void RuntimeType_ContainerResource_IsNotInterface() + { + // Verify that ContainerResource.IsInterface returns false using runtime reflection + var containerResourceType = typeof(ContainerResource); + + Assert.NotNull(containerResourceType); + Assert.False(containerResourceType.IsInterface, "ContainerResource should NOT be an interface"); + } + + [Fact] + public void TwoPassScanning_DeduplicatesCapabilities() + { + // Verify that when the same capability appears in multiple assemblies, + // ScanAssemblies deduplicates by CapabilityId. + var capabilities = ScanCapabilitiesFromBothAssemblies(); + + // Each capability ID should appear only once + var duplicates = capabilities + .GroupBy(c => c.CapabilityId) + .Where(g => g.Count() > 1) + .Select(g => g.Key) + .ToList(); + + Assert.Empty(duplicates); + } + + [Fact] + public void TwoPassScanning_MergesHandleTypesFromAllAssemblies() + { + // Verify that ScanAssemblies collects handle types from all assemblies + var result = CreateContextFromBothAssemblies(); + + // Should have types from Aspire.Hosting (ContainerResource, etc.) + var containerResourceType = result.HandleTypes + .FirstOrDefault(t => t.AtsTypeId.Contains("ContainerResource") && !t.AtsTypeId.Contains("IContainer")); + Assert.NotNull(containerResourceType); + + // Should have types from test assembly (TestRedisResource) + var testRedisType = result.HandleTypes + .FirstOrDefault(t => t.AtsTypeId.Contains("TestRedisResource")); + Assert.NotNull(testRedisType); + + // TestRedisResource should have IResourceWithEnvironment in its interfaces + // (inherited via ContainerResource) + var hasEnvironmentInterface = testRedisType.ImplementedInterfaces + .Any(i => i.TypeId.Contains("IResourceWithEnvironment")); + Assert.True(hasEnvironmentInterface, + "TestRedisResource should implement IResourceWithEnvironment via ContainerResource"); + } + + [Fact] + public async Task TwoPassScanning_GeneratesWithEnvironmentOnTestRedisBuilder() + { + // End-to-end test: verify that AddEnvironment appears on TestRedisResource + // in the generated Go when using 2-pass scanning. + var atsContext = CreateContextFromBothAssemblies(); + + // Generate Go + var files = _generator.GenerateDistributedApplication(atsContext); + var aspireGo = files["aspire.go"]; + + // Verify AddEnvironment appears (method should exist for resources that support it) + Assert.Contains("WithEnvironment", aspireGo); + + // Snapshot for detailed verification + await Verify(aspireGo, extension: "go") + .UseFileName("TwoPassScanningGeneratedAspire"); + } + + [Fact] + public void GeneratedCode_UsesPascalCaseMethodNames() + { + // Verify that the generated Go code uses PascalCase for exported method names + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + var aspireGo = files["aspire.go"]; + + // Go exported methods should use PascalCase + Assert.Contains("AddContainer", aspireGo); + Assert.Contains("WithEnvironment", aspireGo); + } + + [Fact] + public void GeneratedCode_HasCreateBuilderFunction() + { + // Verify that the generated Go code has a CreateBuilder function + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + var aspireGo = files["aspire.go"]; + + Assert.Contains("func CreateBuilder", aspireGo); + } + + [Fact] + public void GeneratedCode_HasGoModFile() + { + // Verify that go.mod file is generated + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + + Assert.Contains("go.mod", files.Keys); + Assert.Contains("module apphost/modules/aspire", files["go.mod"]); + } + + private static List ScanCapabilitiesFromTestAssembly() + { + var testAssembly = LoadTestAssembly(); + + // Scan capabilities from the test assembly + var result = AtsCapabilityScanner.ScanAssembly(testAssembly); + return result.Capabilities; + } + + private static AtsContext CreateContextFromTestAssembly() + { + var testAssembly = LoadTestAssembly(); + + // Scan capabilities from the test assembly + var result = AtsCapabilityScanner.ScanAssembly(testAssembly); + return result.ToAtsContext(); + } + + private static Assembly LoadTestAssembly() + { + // Get the test assembly at runtime (TypeScript tests assembly has the TestTypes) + return typeof(TestRedisResource).Assembly; + } + + private static List ScanCapabilitiesFromHostingAssembly() + { + var hostingAssembly = typeof(DistributedApplication).Assembly; + var result = AtsCapabilityScanner.ScanAssembly(hostingAssembly); + return result.Capabilities; + } + + private static List ScanCapabilitiesFromBothAssemblies() + { + var (testAssembly, hostingAssembly) = LoadBothAssemblies(); + + // Use ScanAssemblies for proper cross-assembly expansion + var result = AtsCapabilityScanner.ScanAssemblies([hostingAssembly, testAssembly]); + return result.Capabilities; + } + + private static AtsContext CreateContextFromBothAssemblies() + { + var (testAssembly, hostingAssembly) = LoadBothAssemblies(); + + // Use ScanAssemblies for proper cross-assembly expansion and enum collection + var result = AtsCapabilityScanner.ScanAssemblies([hostingAssembly, testAssembly]); + return result.ToAtsContext(); + } + + private static (Assembly testAssembly, Assembly hostingAssembly) LoadBothAssemblies() + { + var testAssembly = typeof(TestRedisResource).Assembly; + var hostingAssembly = typeof(DistributedApplication).Assembly; + return (testAssembly, hostingAssembly); + } +} diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AddTestRedisCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AddTestRedisCapability.verified.txt new file mode 100644 index 00000000000..d642b8ff273 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AddTestRedisCapability.verified.txt @@ -0,0 +1,74 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Go.Tests/addTestRedis, + MethodName: addTestRedis, + QualifiedMethodName: addTestRedis, + Description: Adds a test Redis resource, + Parameters: [ + { + Name: name, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + }, + { + Name: port, + Type: { + TypeId: number, + ClrType: int, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: true, + IsCallback: false + } + ], + ReturnType: { + TypeId: Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.AddTestRedis +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AtsGeneratedAspire.verified.go b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AtsGeneratedAspire.verified.go new file mode 100644 index 00000000000..58af17b99c2 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AtsGeneratedAspire.verified.go @@ -0,0 +1,869 @@ +// aspire.go - Capability-based Aspire SDK +// GENERATED CODE - DO NOT EDIT + +package aspire + +import ( + "fmt" + "os" +) + +// ============================================================================ +// Enums +// ============================================================================ + +// TestPersistenceMode represents TestPersistenceMode. +type TestPersistenceMode string + +const ( + TestPersistenceModeNone TestPersistenceMode = "None" + TestPersistenceModeVolume TestPersistenceMode = "Volume" + TestPersistenceModeBind TestPersistenceMode = "Bind" +) + +// TestResourceStatus represents TestResourceStatus. +type TestResourceStatus string + +const ( + TestResourceStatusPending TestResourceStatus = "Pending" + TestResourceStatusRunning TestResourceStatus = "Running" + TestResourceStatusStopped TestResourceStatus = "Stopped" + TestResourceStatusFailed TestResourceStatus = "Failed" +) + +// ============================================================================ +// DTOs +// ============================================================================ + +// TestConfigDto represents TestConfigDto. +type TestConfigDto struct { + Name string `json:"Name,omitempty"` + Port float64 `json:"Port,omitempty"` + Enabled bool `json:"Enabled,omitempty"` + OptionalField string `json:"OptionalField,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *TestConfigDto) ToMap() map[string]any { + return map[string]any{ + "Name": SerializeValue(d.Name), + "Port": SerializeValue(d.Port), + "Enabled": SerializeValue(d.Enabled), + "OptionalField": SerializeValue(d.OptionalField), + } +} + +// TestNestedDto represents TestNestedDto. +type TestNestedDto struct { + Id string `json:"Id,omitempty"` + Config *TestConfigDto `json:"Config,omitempty"` + Tags *AspireList[string] `json:"Tags,omitempty"` + Counts *AspireDict[string, float64] `json:"Counts,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *TestNestedDto) ToMap() map[string]any { + return map[string]any{ + "Id": SerializeValue(d.Id), + "Config": SerializeValue(d.Config), + "Tags": SerializeValue(d.Tags), + "Counts": SerializeValue(d.Counts), + } +} + +// TestDeeplyNestedDto represents TestDeeplyNestedDto. +type TestDeeplyNestedDto struct { + NestedData *AspireDict[string, *AspireList[*TestConfigDto]] `json:"NestedData,omitempty"` + MetadataArray []*AspireDict[string, string] `json:"MetadataArray,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *TestDeeplyNestedDto) ToMap() map[string]any { + return map[string]any{ + "NestedData": SerializeValue(d.NestedData), + "MetadataArray": SerializeValue(d.MetadataArray), + } +} + +// ============================================================================ +// Handle Wrappers +// ============================================================================ + +// IDistributedApplicationBuilder wraps a handle for Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder. +type IDistributedApplicationBuilder struct { + HandleWrapperBase +} + +// NewIDistributedApplicationBuilder creates a new IDistributedApplicationBuilder. +func NewIDistributedApplicationBuilder(handle *Handle, client *AspireClient) *IDistributedApplicationBuilder { + return &IDistributedApplicationBuilder{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// AddTestRedis adds a test Redis resource +func (s *IDistributedApplicationBuilder) AddTestRedis(name string, port float64) (*TestRedisResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["port"] = SerializeValue(port) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/addTestRedis", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestRedisResource), nil +} + +// IResource wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource. +type IResource struct { + ResourceBuilderBase +} + +// NewIResource creates a new IResource. +func NewIResource(handle *Handle, client *AspireClient) *IResource { + return &IResource{ + ResourceBuilderBase: NewResourceBuilderBase(handle, client), + } +} + +// IResourceWithConnectionString wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString. +type IResourceWithConnectionString struct { + ResourceBuilderBase +} + +// NewIResourceWithConnectionString creates a new IResourceWithConnectionString. +func NewIResourceWithConnectionString(handle *Handle, client *AspireClient) *IResourceWithConnectionString { + return &IResourceWithConnectionString{ + ResourceBuilderBase: NewResourceBuilderBase(handle, client), + } +} + +// IResourceWithEnvironment wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment. +type IResourceWithEnvironment struct { + HandleWrapperBase +} + +// NewIResourceWithEnvironment creates a new IResourceWithEnvironment. +func NewIResourceWithEnvironment(handle *Handle, client *AspireClient) *IResourceWithEnvironment { + return &IResourceWithEnvironment{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// TestCallbackContext wraps a handle for Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext. +type TestCallbackContext struct { + HandleWrapperBase +} + +// NewTestCallbackContext creates a new TestCallbackContext. +func NewTestCallbackContext(handle *Handle, client *AspireClient) *TestCallbackContext { + return &TestCallbackContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Name gets the Name property +func (s *TestCallbackContext) Name() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.name", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetName sets the Name property +func (s *TestCallbackContext) SetName(value string) (*TestCallbackContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setName", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestCallbackContext), nil +} + +// Value gets the Value property +func (s *TestCallbackContext) Value() (*float64, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.value", reqArgs) + if err != nil { + return nil, err + } + return result.(*float64), nil +} + +// SetValue sets the Value property +func (s *TestCallbackContext) SetValue(value float64) (*TestCallbackContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setValue", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestCallbackContext), nil +} + +// CancellationToken gets the CancellationToken property +func (s *TestCallbackContext) CancellationToken() (*CancellationToken, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.cancellationToken", reqArgs) + if err != nil { + return nil, err + } + return result.(*CancellationToken), nil +} + +// SetCancellationToken sets the CancellationToken property +func (s *TestCallbackContext) SetCancellationToken(value *CancellationToken) (*TestCallbackContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + if value != nil { + reqArgs["value"] = RegisterCancellation(value, s.Client()) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setCancellationToken", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestCallbackContext), nil +} + +// TestCollectionContext wraps a handle for Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext. +type TestCollectionContext struct { + HandleWrapperBase + items *AspireList[string] + metadata *AspireDict[string, string] +} + +// NewTestCollectionContext creates a new TestCollectionContext. +func NewTestCollectionContext(handle *Handle, client *AspireClient) *TestCollectionContext { + return &TestCollectionContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Items gets the Items property +func (s *TestCollectionContext) Items() *AspireList[string] { + if s.items == nil { + s.items = NewAspireListWithGetter[string](s.Handle(), s.Client(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.items") + } + return s.items +} + +// Metadata gets the Metadata property +func (s *TestCollectionContext) Metadata() *AspireDict[string, string] { + if s.metadata == nil { + s.metadata = NewAspireDictWithGetter[string, string](s.Handle(), s.Client(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.metadata") + } + return s.metadata +} + +// TestEnvironmentContext wraps a handle for Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext. +type TestEnvironmentContext struct { + HandleWrapperBase +} + +// NewTestEnvironmentContext creates a new TestEnvironmentContext. +func NewTestEnvironmentContext(handle *Handle, client *AspireClient) *TestEnvironmentContext { + return &TestEnvironmentContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Name gets the Name property +func (s *TestEnvironmentContext) Name() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.name", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetName sets the Name property +func (s *TestEnvironmentContext) SetName(value string) (*TestEnvironmentContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setName", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestEnvironmentContext), nil +} + +// Description gets the Description property +func (s *TestEnvironmentContext) Description() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.description", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetDescription sets the Description property +func (s *TestEnvironmentContext) SetDescription(value string) (*TestEnvironmentContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setDescription", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestEnvironmentContext), nil +} + +// Priority gets the Priority property +func (s *TestEnvironmentContext) Priority() (*float64, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.priority", reqArgs) + if err != nil { + return nil, err + } + return result.(*float64), nil +} + +// SetPriority sets the Priority property +func (s *TestEnvironmentContext) SetPriority(value float64) (*TestEnvironmentContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setPriority", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestEnvironmentContext), nil +} + +// TestRedisResource wraps a handle for Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource. +type TestRedisResource struct { + ResourceBuilderBase + getTags *AspireList[string] + getMetadata *AspireDict[string, string] +} + +// NewTestRedisResource creates a new TestRedisResource. +func NewTestRedisResource(handle *Handle, client *AspireClient) *TestRedisResource { + return &TestRedisResource{ + ResourceBuilderBase: NewResourceBuilderBase(handle, client), + } +} + +// WithPersistence configures the Redis resource with persistence +func (s *TestRedisResource) WithPersistence(mode TestPersistenceMode) (*TestRedisResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["mode"] = SerializeValue(mode) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withPersistence", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestRedisResource), nil +} + +// WithOptionalString adds an optional string parameter +func (s *TestRedisResource) WithOptionalString(value string, enabled bool) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + reqArgs["enabled"] = SerializeValue(enabled) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalString", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithConfig configures the resource with a DTO +func (s *TestRedisResource) WithConfig(config *TestConfigDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// GetTags gets the tags for the resource +func (s *TestRedisResource) GetTags() *AspireList[string] { + if s.getTags == nil { + s.getTags = NewAspireListWithGetter[string](s.Handle(), s.Client(), "Aspire.Hosting.CodeGeneration.Go.Tests/getTags") + } + return s.getTags +} + +// GetMetadata gets the metadata for the resource +func (s *TestRedisResource) GetMetadata() *AspireDict[string, string] { + if s.getMetadata == nil { + s.getMetadata = NewAspireDictWithGetter[string, string](s.Handle(), s.Client(), "Aspire.Hosting.CodeGeneration.Go.Tests/getMetadata") + } + return s.getMetadata +} + +// WithConnectionString sets the connection string using a reference expression +func (s *TestRedisResource) WithConnectionString(connectionString *ReferenceExpression) (*IResourceWithConnectionString, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["connectionString"] = SerializeValue(connectionString) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withConnectionString", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithConnectionString), nil +} + +// TestWithEnvironmentCallback configures environment with callback (test version) +func (s *TestRedisResource) TestWithEnvironmentCallback(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/testWithEnvironmentCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithCreatedAt sets the created timestamp +func (s *TestRedisResource) WithCreatedAt(createdAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["createdAt"] = SerializeValue(createdAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCreatedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithModifiedAt sets the modified timestamp +func (s *TestRedisResource) WithModifiedAt(modifiedAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["modifiedAt"] = SerializeValue(modifiedAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withModifiedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithCorrelationId sets the correlation ID +func (s *TestRedisResource) WithCorrelationId(correlationId string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["correlationId"] = SerializeValue(correlationId) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCorrelationId", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithOptionalCallback configures with optional callback +func (s *TestRedisResource) WithOptionalCallback(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithStatus sets the resource status +func (s *TestRedisResource) WithStatus(status TestResourceStatus) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["status"] = SerializeValue(status) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withStatus", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithNestedConfig configures with nested DTO +func (s *TestRedisResource) WithNestedConfig(config *TestNestedDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withNestedConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithValidator adds validation callback +func (s *TestRedisResource) WithValidator(validator func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if validator != nil { + reqArgs["validator"] = RegisterCallback(validator) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withValidator", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// TestWaitFor waits for another resource (test version) +func (s *TestRedisResource) TestWaitFor(dependency *IResource) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/testWaitFor", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// GetEndpoints gets the endpoints +func (s *TestRedisResource) GetEndpoints() (*[]string, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/getEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*[]string), nil +} + +// WithConnectionStringDirect sets connection string using direct interface target +func (s *TestRedisResource) WithConnectionStringDirect(connectionString string) (*IResourceWithConnectionString, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["connectionString"] = SerializeValue(connectionString) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withConnectionStringDirect", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithConnectionString), nil +} + +// WithRedisSpecific redis-specific configuration +func (s *TestRedisResource) WithRedisSpecific(option string) (*TestRedisResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["option"] = SerializeValue(option) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withRedisSpecific", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestRedisResource), nil +} + +// WithDependency adds a dependency on another resource +func (s *TestRedisResource) WithDependency(dependency *IResourceWithConnectionString) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withDependency", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithEndpoints sets the endpoints +func (s *TestRedisResource) WithEndpoints(endpoints []string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpoints"] = SerializeValue(endpoints) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithEnvironmentVariables sets environment variables +func (s *TestRedisResource) WithEnvironmentVariables(variables map[string]string) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["variables"] = SerializeValue(variables) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withEnvironmentVariables", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// GetStatusAsync gets the status of the resource asynchronously +func (s *TestRedisResource) GetStatusAsync(cancellationToken *CancellationToken) (*string, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if cancellationToken != nil { + reqArgs["cancellationToken"] = RegisterCancellation(cancellationToken, s.Client()) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/getStatusAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// WithCancellableOperation performs a cancellable operation +func (s *TestRedisResource) WithCancellableOperation(operation func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if operation != nil { + reqArgs["operation"] = RegisterCallback(operation) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCancellableOperation", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WaitForReadyAsync waits for the resource to be ready +func (s *TestRedisResource) WaitForReadyAsync(timeout float64, cancellationToken *CancellationToken) (*bool, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["timeout"] = SerializeValue(timeout) + if cancellationToken != nil { + reqArgs["cancellationToken"] = RegisterCancellation(cancellationToken, s.Client()) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/waitForReadyAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*bool), nil +} + +// TestResourceContext wraps a handle for Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext. +type TestResourceContext struct { + HandleWrapperBase +} + +// NewTestResourceContext creates a new TestResourceContext. +func NewTestResourceContext(handle *Handle, client *AspireClient) *TestResourceContext { + return &TestResourceContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Name gets the Name property +func (s *TestResourceContext) Name() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.name", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetName sets the Name property +func (s *TestResourceContext) SetName(value string) (*TestResourceContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setName", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestResourceContext), nil +} + +// Value gets the Value property +func (s *TestResourceContext) Value() (*float64, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.value", reqArgs) + if err != nil { + return nil, err + } + return result.(*float64), nil +} + +// SetValue sets the Value property +func (s *TestResourceContext) SetValue(value float64) (*TestResourceContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValue", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestResourceContext), nil +} + +// GetValueAsync invokes the GetValueAsync method +func (s *TestResourceContext) GetValueAsync() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.getValueAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetValueAsync invokes the SetValueAsync method +func (s *TestResourceContext) SetValueAsync(value string) error { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + _, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValueAsync", reqArgs) + return err +} + +// ValidateAsync invokes the ValidateAsync method +func (s *TestResourceContext) ValidateAsync() (*bool, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.validateAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*bool), nil +} + +// ============================================================================ +// Handle wrapper registrations +// ============================================================================ + +func init() { + RegisterHandleWrapper("Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext", func(h *Handle, c *AspireClient) any { + return NewTestCallbackContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext", func(h *Handle, c *AspireClient) any { + return NewTestResourceContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext", func(h *Handle, c *AspireClient) any { + return NewTestEnvironmentContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext", func(h *Handle, c *AspireClient) any { + return NewTestCollectionContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource", func(h *Handle, c *AspireClient) any { + return NewTestRedisResource(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource", func(h *Handle, c *AspireClient) any { + return NewIResource(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString", func(h *Handle, c *AspireClient) any { + return NewIResourceWithConnectionString(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder", func(h *Handle, c *AspireClient) any { + return NewIDistributedApplicationBuilder(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment", func(h *Handle, c *AspireClient) any { + return NewIResourceWithEnvironment(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/List", func(h *Handle, c *AspireClient) any { + return &AspireList[any]{HandleWrapperBase: NewHandleWrapperBase(h, c)} + }) + RegisterHandleWrapper("Aspire.Hosting/Dict", func(h *Handle, c *AspireClient) any { + return &AspireDict[any, any]{HandleWrapperBase: NewHandleWrapperBase(h, c)} + }) +} + +// ============================================================================ +// Connection Helpers +// ============================================================================ + +// Connect establishes a connection to the AppHost server. +func Connect() (*AspireClient, error) { + socketPath := os.Getenv("REMOTE_APP_HOST_SOCKET_PATH") + if socketPath == "" { + return nil, fmt.Errorf("REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`") + } + client := NewAspireClient(socketPath) + if err := client.Connect(); err != nil { + return nil, err + } + client.OnDisconnect(func() { os.Exit(1) }) + return client, nil +} + +// CreateBuilder creates a new distributed application builder. +func CreateBuilder(options *CreateBuilderOptions) (*IDistributedApplicationBuilder, error) { + client, err := Connect() + if err != nil { + return nil, err + } + resolvedOptions := make(map[string]any) + if options != nil { + for k, v := range options.ToMap() { + resolvedOptions[k] = v + } + } + if _, ok := resolvedOptions["Args"]; !ok { + resolvedOptions["Args"] = os.Args[1:] + } + if _, ok := resolvedOptions["ProjectDirectory"]; !ok { + if pwd, err := os.Getwd(); err == nil { + resolvedOptions["ProjectDirectory"] = pwd + } + } + result, err := client.InvokeCapability("Aspire.Hosting/createBuilderWithOptions", map[string]any{"options": resolvedOptions}) + if err != nil { + return nil, err + } + return result.(*IDistributedApplicationBuilder), nil +} + diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/HostingAddContainerCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/HostingAddContainerCapability.verified.txt new file mode 100644 index 00000000000..ba9342ec73c --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/HostingAddContainerCapability.verified.txt @@ -0,0 +1,74 @@ +{ + CapabilityId: Aspire.Hosting/addContainer, + MethodName: addContainer, + QualifiedMethodName: addContainer, + Description: Adds a container resource, + Parameters: [ + { + Name: name, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + }, + { + Name: image, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + } + ], + ReturnType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + ClrType: ContainerResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.ContainerResourceBuilderExtensions.AddContainer +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go new file mode 100644 index 00000000000..c80f4980418 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go @@ -0,0 +1,4790 @@ +// aspire.go - Capability-based Aspire SDK +// GENERATED CODE - DO NOT EDIT + +package aspire + +import ( + "fmt" + "os" +) + +// ============================================================================ +// Enums +// ============================================================================ + +// ContainerLifetime represents ContainerLifetime. +type ContainerLifetime string + +const ( + ContainerLifetimeSession ContainerLifetime = "Session" + ContainerLifetimePersistent ContainerLifetime = "Persistent" +) + +// ImagePullPolicy represents ImagePullPolicy. +type ImagePullPolicy string + +const ( + ImagePullPolicyDefault ImagePullPolicy = "Default" + ImagePullPolicyAlways ImagePullPolicy = "Always" + ImagePullPolicyMissing ImagePullPolicy = "Missing" + ImagePullPolicyNever ImagePullPolicy = "Never" +) + +// DistributedApplicationOperation represents DistributedApplicationOperation. +type DistributedApplicationOperation string + +const ( + DistributedApplicationOperationRun DistributedApplicationOperation = "Run" + DistributedApplicationOperationPublish DistributedApplicationOperation = "Publish" +) + +// ProtocolType represents ProtocolType. +type ProtocolType string + +const ( + ProtocolTypeIP ProtocolType = "IP" + ProtocolTypeIPv6HopByHopOptions ProtocolType = "IPv6HopByHopOptions" + ProtocolTypeUnspecified ProtocolType = "Unspecified" + ProtocolTypeIcmp ProtocolType = "Icmp" + ProtocolTypeIgmp ProtocolType = "Igmp" + ProtocolTypeGgp ProtocolType = "Ggp" + ProtocolTypeIPv4 ProtocolType = "IPv4" + ProtocolTypeTcp ProtocolType = "Tcp" + ProtocolTypePup ProtocolType = "Pup" + ProtocolTypeUdp ProtocolType = "Udp" + ProtocolTypeIdp ProtocolType = "Idp" + ProtocolTypeIPv6 ProtocolType = "IPv6" + ProtocolTypeIPv6RoutingHeader ProtocolType = "IPv6RoutingHeader" + ProtocolTypeIPv6FragmentHeader ProtocolType = "IPv6FragmentHeader" + ProtocolTypeIPSecEncapsulatingSecurityPayload ProtocolType = "IPSecEncapsulatingSecurityPayload" + ProtocolTypeIPSecAuthenticationHeader ProtocolType = "IPSecAuthenticationHeader" + ProtocolTypeIcmpV6 ProtocolType = "IcmpV6" + ProtocolTypeIPv6NoNextHeader ProtocolType = "IPv6NoNextHeader" + ProtocolTypeIPv6DestinationOptions ProtocolType = "IPv6DestinationOptions" + ProtocolTypeND ProtocolType = "ND" + ProtocolTypeRaw ProtocolType = "Raw" + ProtocolTypeIpx ProtocolType = "Ipx" + ProtocolTypeSpx ProtocolType = "Spx" + ProtocolTypeSpxII ProtocolType = "SpxII" + ProtocolTypeUnknown ProtocolType = "Unknown" +) + +// EndpointProperty represents EndpointProperty. +type EndpointProperty string + +const ( + EndpointPropertyUrl EndpointProperty = "Url" + EndpointPropertyHost EndpointProperty = "Host" + EndpointPropertyIPV4Host EndpointProperty = "IPV4Host" + EndpointPropertyPort EndpointProperty = "Port" + EndpointPropertyScheme EndpointProperty = "Scheme" + EndpointPropertyTargetPort EndpointProperty = "TargetPort" + EndpointPropertyHostAndPort EndpointProperty = "HostAndPort" +) + +// IconVariant represents IconVariant. +type IconVariant string + +const ( + IconVariantRegular IconVariant = "Regular" + IconVariantFilled IconVariant = "Filled" +) + +// UrlDisplayLocation represents UrlDisplayLocation. +type UrlDisplayLocation string + +const ( + UrlDisplayLocationSummaryAndDetails UrlDisplayLocation = "SummaryAndDetails" + UrlDisplayLocationDetailsOnly UrlDisplayLocation = "DetailsOnly" +) + +// TestPersistenceMode represents TestPersistenceMode. +type TestPersistenceMode string + +const ( + TestPersistenceModeNone TestPersistenceMode = "None" + TestPersistenceModeVolume TestPersistenceMode = "Volume" + TestPersistenceModeBind TestPersistenceMode = "Bind" +) + +// TestResourceStatus represents TestResourceStatus. +type TestResourceStatus string + +const ( + TestResourceStatusPending TestResourceStatus = "Pending" + TestResourceStatusRunning TestResourceStatus = "Running" + TestResourceStatusStopped TestResourceStatus = "Stopped" + TestResourceStatusFailed TestResourceStatus = "Failed" +) + +// ============================================================================ +// DTOs +// ============================================================================ + +// CreateBuilderOptions represents CreateBuilderOptions. +type CreateBuilderOptions struct { + Args []string `json:"Args,omitempty"` + ProjectDirectory string `json:"ProjectDirectory,omitempty"` + AppHostFilePath string `json:"AppHostFilePath,omitempty"` + ContainerRegistryOverride string `json:"ContainerRegistryOverride,omitempty"` + DisableDashboard bool `json:"DisableDashboard,omitempty"` + DashboardApplicationName string `json:"DashboardApplicationName,omitempty"` + AllowUnsecuredTransport bool `json:"AllowUnsecuredTransport,omitempty"` + EnableResourceLogging bool `json:"EnableResourceLogging,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *CreateBuilderOptions) ToMap() map[string]any { + return map[string]any{ + "Args": SerializeValue(d.Args), + "ProjectDirectory": SerializeValue(d.ProjectDirectory), + "AppHostFilePath": SerializeValue(d.AppHostFilePath), + "ContainerRegistryOverride": SerializeValue(d.ContainerRegistryOverride), + "DisableDashboard": SerializeValue(d.DisableDashboard), + "DashboardApplicationName": SerializeValue(d.DashboardApplicationName), + "AllowUnsecuredTransport": SerializeValue(d.AllowUnsecuredTransport), + "EnableResourceLogging": SerializeValue(d.EnableResourceLogging), + } +} + +// ResourceEventDto represents ResourceEventDto. +type ResourceEventDto struct { + ResourceName string `json:"ResourceName,omitempty"` + ResourceId string `json:"ResourceId,omitempty"` + State string `json:"State,omitempty"` + StateStyle string `json:"StateStyle,omitempty"` + HealthStatus string `json:"HealthStatus,omitempty"` + ExitCode float64 `json:"ExitCode,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *ResourceEventDto) ToMap() map[string]any { + return map[string]any{ + "ResourceName": SerializeValue(d.ResourceName), + "ResourceId": SerializeValue(d.ResourceId), + "State": SerializeValue(d.State), + "StateStyle": SerializeValue(d.StateStyle), + "HealthStatus": SerializeValue(d.HealthStatus), + "ExitCode": SerializeValue(d.ExitCode), + } +} + +// CommandOptions represents CommandOptions. +type CommandOptions struct { + Description string `json:"Description,omitempty"` + Parameter any `json:"Parameter,omitempty"` + ConfirmationMessage string `json:"ConfirmationMessage,omitempty"` + IconName string `json:"IconName,omitempty"` + IconVariant IconVariant `json:"IconVariant,omitempty"` + IsHighlighted bool `json:"IsHighlighted,omitempty"` + UpdateState any `json:"UpdateState,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *CommandOptions) ToMap() map[string]any { + return map[string]any{ + "Description": SerializeValue(d.Description), + "Parameter": SerializeValue(d.Parameter), + "ConfirmationMessage": SerializeValue(d.ConfirmationMessage), + "IconName": SerializeValue(d.IconName), + "IconVariant": SerializeValue(d.IconVariant), + "IsHighlighted": SerializeValue(d.IsHighlighted), + "UpdateState": SerializeValue(d.UpdateState), + } +} + +// ExecuteCommandResult represents ExecuteCommandResult. +type ExecuteCommandResult struct { + Success bool `json:"Success,omitempty"` + Canceled bool `json:"Canceled,omitempty"` + ErrorMessage string `json:"ErrorMessage,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *ExecuteCommandResult) ToMap() map[string]any { + return map[string]any{ + "Success": SerializeValue(d.Success), + "Canceled": SerializeValue(d.Canceled), + "ErrorMessage": SerializeValue(d.ErrorMessage), + } +} + +// ResourceUrlAnnotation represents ResourceUrlAnnotation. +type ResourceUrlAnnotation struct { + Url string `json:"Url,omitempty"` + DisplayText string `json:"DisplayText,omitempty"` + Endpoint *EndpointReference `json:"Endpoint,omitempty"` + DisplayLocation UrlDisplayLocation `json:"DisplayLocation,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *ResourceUrlAnnotation) ToMap() map[string]any { + return map[string]any{ + "Url": SerializeValue(d.Url), + "DisplayText": SerializeValue(d.DisplayText), + "Endpoint": SerializeValue(d.Endpoint), + "DisplayLocation": SerializeValue(d.DisplayLocation), + } +} + +// TestConfigDto represents TestConfigDto. +type TestConfigDto struct { + Name string `json:"Name,omitempty"` + Port float64 `json:"Port,omitempty"` + Enabled bool `json:"Enabled,omitempty"` + OptionalField string `json:"OptionalField,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *TestConfigDto) ToMap() map[string]any { + return map[string]any{ + "Name": SerializeValue(d.Name), + "Port": SerializeValue(d.Port), + "Enabled": SerializeValue(d.Enabled), + "OptionalField": SerializeValue(d.OptionalField), + } +} + +// TestNestedDto represents TestNestedDto. +type TestNestedDto struct { + Id string `json:"Id,omitempty"` + Config *TestConfigDto `json:"Config,omitempty"` + Tags *AspireList[string] `json:"Tags,omitempty"` + Counts *AspireDict[string, float64] `json:"Counts,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *TestNestedDto) ToMap() map[string]any { + return map[string]any{ + "Id": SerializeValue(d.Id), + "Config": SerializeValue(d.Config), + "Tags": SerializeValue(d.Tags), + "Counts": SerializeValue(d.Counts), + } +} + +// TestDeeplyNestedDto represents TestDeeplyNestedDto. +type TestDeeplyNestedDto struct { + NestedData *AspireDict[string, *AspireList[*TestConfigDto]] `json:"NestedData,omitempty"` + MetadataArray []*AspireDict[string, string] `json:"MetadataArray,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *TestDeeplyNestedDto) ToMap() map[string]any { + return map[string]any{ + "NestedData": SerializeValue(d.NestedData), + "MetadataArray": SerializeValue(d.MetadataArray), + } +} + +// ============================================================================ +// Handle Wrappers +// ============================================================================ + +// CommandLineArgsCallbackContext wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.CommandLineArgsCallbackContext. +type CommandLineArgsCallbackContext struct { + HandleWrapperBase + args *AspireList[any] +} + +// NewCommandLineArgsCallbackContext creates a new CommandLineArgsCallbackContext. +func NewCommandLineArgsCallbackContext(handle *Handle, client *AspireClient) *CommandLineArgsCallbackContext { + return &CommandLineArgsCallbackContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Args gets the Args property +func (s *CommandLineArgsCallbackContext) Args() *AspireList[any] { + if s.args == nil { + s.args = NewAspireListWithGetter[any](s.Handle(), s.Client(), "Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.args") + } + return s.args +} + +// CancellationToken gets the CancellationToken property +func (s *CommandLineArgsCallbackContext) CancellationToken() (*CancellationToken, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.cancellationToken", reqArgs) + if err != nil { + return nil, err + } + return result.(*CancellationToken), nil +} + +// ExecutionContext gets the ExecutionContext property +func (s *CommandLineArgsCallbackContext) ExecutionContext() (*DistributedApplicationExecutionContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.executionContext", reqArgs) + if err != nil { + return nil, err + } + return result.(*DistributedApplicationExecutionContext), nil +} + +// SetExecutionContext sets the ExecutionContext property +func (s *CommandLineArgsCallbackContext) SetExecutionContext(value *DistributedApplicationExecutionContext) (*CommandLineArgsCallbackContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.setExecutionContext", reqArgs) + if err != nil { + return nil, err + } + return result.(*CommandLineArgsCallbackContext), nil +} + +// ContainerResource wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource. +type ContainerResource struct { + ResourceBuilderBase +} + +// NewContainerResource creates a new ContainerResource. +func NewContainerResource(handle *Handle, client *AspireClient) *ContainerResource { + return &ContainerResource{ + ResourceBuilderBase: NewResourceBuilderBase(handle, client), + } +} + +// WithEnvironment sets an environment variable +func (s *ContainerResource) WithEnvironment(name string, value string) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironment", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentExpression adds an environment variable with a reference expression +func (s *ContainerResource) WithEnvironmentExpression(name string, value *ReferenceExpression) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentExpression", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentCallback sets environment variables via callback +func (s *ContainerResource) WithEnvironmentCallback(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentCallbackAsync sets environment variables via async callback +func (s *ContainerResource) WithEnvironmentCallbackAsync(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithArgs adds arguments +func (s *ContainerResource) WithArgs(args []string) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["args"] = SerializeValue(args) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgs", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithArgsCallback sets command-line arguments via callback +func (s *ContainerResource) WithArgsCallback(callback func(...any) any) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgsCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithArgsCallbackAsync sets command-line arguments via async callback +func (s *ContainerResource) WithArgsCallbackAsync(callback func(...any) any) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgsCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithReference adds a reference to another resource +func (s *ContainerResource) WithReference(source *IResourceWithConnectionString, connectionName string, optional bool) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["source"] = SerializeValue(source) + reqArgs["connectionName"] = SerializeValue(connectionName) + reqArgs["optional"] = SerializeValue(optional) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withReference", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithServiceReference adds a service discovery reference to another resource +func (s *ContainerResource) WithServiceReference(source *IResourceWithServiceDiscovery) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["source"] = SerializeValue(source) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withServiceReference", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEndpoint adds a network endpoint +func (s *ContainerResource) WithEndpoint(port float64, targetPort float64, scheme string, name string, env string, isProxied bool, isExternal bool, protocol ProtocolType) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["scheme"] = SerializeValue(scheme) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + reqArgs["isExternal"] = SerializeValue(isExternal) + reqArgs["protocol"] = SerializeValue(protocol) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithHttpEndpoint adds an HTTP endpoint +func (s *ContainerResource) WithHttpEndpoint(port float64, targetPort float64, name string, env string, isProxied bool) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithHttpsEndpoint adds an HTTPS endpoint +func (s *ContainerResource) WithHttpsEndpoint(port float64, targetPort float64, name string, env string, isProxied bool) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpsEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithExternalHttpEndpoints makes HTTP endpoints externally accessible +func (s *ContainerResource) WithExternalHttpEndpoints() (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withExternalHttpEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// GetEndpoint gets an endpoint reference +func (s *ContainerResource) GetEndpoint(name string) (*EndpointReference, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + result, err := s.Client().InvokeCapability("Aspire.Hosting/getEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*EndpointReference), nil +} + +// AsHttp2Service configures resource for HTTP/2 +func (s *ContainerResource) AsHttp2Service() (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/asHttp2Service", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithUrlsCallback customizes displayed URLs via callback +func (s *ContainerResource) WithUrlsCallback(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlsCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlsCallbackAsync customizes displayed URLs via async callback +func (s *ContainerResource) WithUrlsCallbackAsync(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlsCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrl adds or modifies displayed URLs +func (s *ContainerResource) WithUrl(url string, displayText string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["url"] = SerializeValue(url) + reqArgs["displayText"] = SerializeValue(displayText) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrl", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlExpression adds a URL using a reference expression +func (s *ContainerResource) WithUrlExpression(url *ReferenceExpression, displayText string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["url"] = SerializeValue(url) + reqArgs["displayText"] = SerializeValue(displayText) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlExpression", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlForEndpoint customizes the URL for a specific endpoint via callback +func (s *ContainerResource) WithUrlForEndpoint(endpointName string, callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpointName"] = SerializeValue(endpointName) + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlForEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlForEndpointFactory adds a URL for a specific endpoint via factory callback +func (s *ContainerResource) WithUrlForEndpointFactory(endpointName string, callback func(...any) any) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpointName"] = SerializeValue(endpointName) + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlForEndpointFactory", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WaitFor waits for another resource to be ready +func (s *ContainerResource) WaitFor(dependency *IResource) (*IResourceWithWaitSupport, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting/waitFor", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithWaitSupport), nil +} + +// WithExplicitStart prevents resource from starting automatically +func (s *ContainerResource) WithExplicitStart() (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withExplicitStart", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WaitForCompletion waits for resource completion +func (s *ContainerResource) WaitForCompletion(dependency *IResource, exitCode float64) (*IResourceWithWaitSupport, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + reqArgs["exitCode"] = SerializeValue(exitCode) + result, err := s.Client().InvokeCapability("Aspire.Hosting/waitForCompletion", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithWaitSupport), nil +} + +// WithHealthCheck adds a health check by key +func (s *ContainerResource) WithHealthCheck(key string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["key"] = SerializeValue(key) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHealthCheck", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithHttpHealthCheck adds an HTTP health check +func (s *ContainerResource) WithHttpHealthCheck(path string, statusCode float64, endpointName string) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["path"] = SerializeValue(path) + reqArgs["statusCode"] = SerializeValue(statusCode) + reqArgs["endpointName"] = SerializeValue(endpointName) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpHealthCheck", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithCommand adds a resource command +func (s *ContainerResource) WithCommand(name string, displayName string, executeCommand func(...any) any, commandOptions *CommandOptions) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["displayName"] = SerializeValue(displayName) + if executeCommand != nil { + reqArgs["executeCommand"] = RegisterCallback(executeCommand) + } + if commandOptions != nil { + reqArgs["commandOptions"] = SerializeValue(commandOptions) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withCommand", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithParentRelationship sets the parent relationship +func (s *ContainerResource) WithParentRelationship(parent *IResource) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["parent"] = SerializeValue(parent) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withParentRelationship", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// GetResourceName gets the resource name +func (s *ContainerResource) GetResourceName() (*string, error) { + reqArgs := map[string]any{ + "resource": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/getResourceName", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// WithOptionalString adds an optional string parameter +func (s *ContainerResource) WithOptionalString(value string, enabled bool) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + reqArgs["enabled"] = SerializeValue(enabled) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalString", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithConfig configures the resource with a DTO +func (s *ContainerResource) WithConfig(config *TestConfigDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// TestWithEnvironmentCallback configures environment with callback (test version) +func (s *ContainerResource) TestWithEnvironmentCallback(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/testWithEnvironmentCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithCreatedAt sets the created timestamp +func (s *ContainerResource) WithCreatedAt(createdAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["createdAt"] = SerializeValue(createdAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCreatedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithModifiedAt sets the modified timestamp +func (s *ContainerResource) WithModifiedAt(modifiedAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["modifiedAt"] = SerializeValue(modifiedAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withModifiedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithCorrelationId sets the correlation ID +func (s *ContainerResource) WithCorrelationId(correlationId string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["correlationId"] = SerializeValue(correlationId) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCorrelationId", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithOptionalCallback configures with optional callback +func (s *ContainerResource) WithOptionalCallback(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithStatus sets the resource status +func (s *ContainerResource) WithStatus(status TestResourceStatus) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["status"] = SerializeValue(status) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withStatus", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithNestedConfig configures with nested DTO +func (s *ContainerResource) WithNestedConfig(config *TestNestedDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withNestedConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithValidator adds validation callback +func (s *ContainerResource) WithValidator(validator func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if validator != nil { + reqArgs["validator"] = RegisterCallback(validator) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withValidator", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// TestWaitFor waits for another resource (test version) +func (s *ContainerResource) TestWaitFor(dependency *IResource) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/testWaitFor", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithDependency adds a dependency on another resource +func (s *ContainerResource) WithDependency(dependency *IResourceWithConnectionString) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withDependency", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithEndpoints sets the endpoints +func (s *ContainerResource) WithEndpoints(endpoints []string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpoints"] = SerializeValue(endpoints) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithEnvironmentVariables sets environment variables +func (s *ContainerResource) WithEnvironmentVariables(variables map[string]string) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["variables"] = SerializeValue(variables) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withEnvironmentVariables", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithCancellableOperation performs a cancellable operation +func (s *ContainerResource) WithCancellableOperation(operation func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if operation != nil { + reqArgs["operation"] = RegisterCallback(operation) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCancellableOperation", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// DistributedApplication wraps a handle for Aspire.Hosting/Aspire.Hosting.DistributedApplication. +type DistributedApplication struct { + HandleWrapperBase +} + +// NewDistributedApplication creates a new DistributedApplication. +func NewDistributedApplication(handle *Handle, client *AspireClient) *DistributedApplication { + return &DistributedApplication{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Run runs the distributed application +func (s *DistributedApplication) Run(cancellationToken *CancellationToken) error { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + if cancellationToken != nil { + reqArgs["cancellationToken"] = RegisterCancellation(cancellationToken, s.Client()) + } + _, err := s.Client().InvokeCapability("Aspire.Hosting/run", reqArgs) + return err +} + +// DistributedApplicationEventSubscription wraps a handle for Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription. +type DistributedApplicationEventSubscription struct { + HandleWrapperBase +} + +// NewDistributedApplicationEventSubscription creates a new DistributedApplicationEventSubscription. +func NewDistributedApplicationEventSubscription(handle *Handle, client *AspireClient) *DistributedApplicationEventSubscription { + return &DistributedApplicationEventSubscription{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// DistributedApplicationExecutionContext wraps a handle for Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext. +type DistributedApplicationExecutionContext struct { + HandleWrapperBase +} + +// NewDistributedApplicationExecutionContext creates a new DistributedApplicationExecutionContext. +func NewDistributedApplicationExecutionContext(handle *Handle, client *AspireClient) *DistributedApplicationExecutionContext { + return &DistributedApplicationExecutionContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// PublisherName gets the PublisherName property +func (s *DistributedApplicationExecutionContext) PublisherName() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/DistributedApplicationExecutionContext.publisherName", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetPublisherName sets the PublisherName property +func (s *DistributedApplicationExecutionContext) SetPublisherName(value string) (*DistributedApplicationExecutionContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting/DistributedApplicationExecutionContext.setPublisherName", reqArgs) + if err != nil { + return nil, err + } + return result.(*DistributedApplicationExecutionContext), nil +} + +// Operation gets the Operation property +func (s *DistributedApplicationExecutionContext) Operation() (*DistributedApplicationOperation, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/DistributedApplicationExecutionContext.operation", reqArgs) + if err != nil { + return nil, err + } + return result.(*DistributedApplicationOperation), nil +} + +// IsPublishMode gets the IsPublishMode property +func (s *DistributedApplicationExecutionContext) IsPublishMode() (*bool, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/DistributedApplicationExecutionContext.isPublishMode", reqArgs) + if err != nil { + return nil, err + } + return result.(*bool), nil +} + +// IsRunMode gets the IsRunMode property +func (s *DistributedApplicationExecutionContext) IsRunMode() (*bool, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/DistributedApplicationExecutionContext.isRunMode", reqArgs) + if err != nil { + return nil, err + } + return result.(*bool), nil +} + +// DistributedApplicationExecutionContextOptions wraps a handle for Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContextOptions. +type DistributedApplicationExecutionContextOptions struct { + HandleWrapperBase +} + +// NewDistributedApplicationExecutionContextOptions creates a new DistributedApplicationExecutionContextOptions. +func NewDistributedApplicationExecutionContextOptions(handle *Handle, client *AspireClient) *DistributedApplicationExecutionContextOptions { + return &DistributedApplicationExecutionContextOptions{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// DistributedApplicationResourceEventSubscription wraps a handle for Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationResourceEventSubscription. +type DistributedApplicationResourceEventSubscription struct { + HandleWrapperBase +} + +// NewDistributedApplicationResourceEventSubscription creates a new DistributedApplicationResourceEventSubscription. +func NewDistributedApplicationResourceEventSubscription(handle *Handle, client *AspireClient) *DistributedApplicationResourceEventSubscription { + return &DistributedApplicationResourceEventSubscription{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// EndpointReference wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReference. +type EndpointReference struct { + HandleWrapperBase +} + +// NewEndpointReference creates a new EndpointReference. +func NewEndpointReference(handle *Handle, client *AspireClient) *EndpointReference { + return &EndpointReference{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// EndpointName gets the EndpointName property +func (s *EndpointReference) EndpointName() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.endpointName", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// ErrorMessage gets the ErrorMessage property +func (s *EndpointReference) ErrorMessage() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.errorMessage", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetErrorMessage sets the ErrorMessage property +func (s *EndpointReference) SetErrorMessage(value string) (*EndpointReference, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.setErrorMessage", reqArgs) + if err != nil { + return nil, err + } + return result.(*EndpointReference), nil +} + +// IsAllocated gets the IsAllocated property +func (s *EndpointReference) IsAllocated() (*bool, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.isAllocated", reqArgs) + if err != nil { + return nil, err + } + return result.(*bool), nil +} + +// Exists gets the Exists property +func (s *EndpointReference) Exists() (*bool, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.exists", reqArgs) + if err != nil { + return nil, err + } + return result.(*bool), nil +} + +// IsHttp gets the IsHttp property +func (s *EndpointReference) IsHttp() (*bool, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.isHttp", reqArgs) + if err != nil { + return nil, err + } + return result.(*bool), nil +} + +// IsHttps gets the IsHttps property +func (s *EndpointReference) IsHttps() (*bool, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.isHttps", reqArgs) + if err != nil { + return nil, err + } + return result.(*bool), nil +} + +// Port gets the Port property +func (s *EndpointReference) Port() (*float64, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.port", reqArgs) + if err != nil { + return nil, err + } + return result.(*float64), nil +} + +// TargetPort gets the TargetPort property +func (s *EndpointReference) TargetPort() (*float64, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.targetPort", reqArgs) + if err != nil { + return nil, err + } + return result.(*float64), nil +} + +// Host gets the Host property +func (s *EndpointReference) Host() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.host", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// Scheme gets the Scheme property +func (s *EndpointReference) Scheme() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.scheme", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// Url gets the Url property +func (s *EndpointReference) Url() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.url", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// GetValueAsync gets the URL of the endpoint asynchronously +func (s *EndpointReference) GetValueAsync(cancellationToken *CancellationToken) (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + if cancellationToken != nil { + reqArgs["cancellationToken"] = RegisterCancellation(cancellationToken, s.Client()) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/getValueAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// EndpointReferenceExpression wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReferenceExpression. +type EndpointReferenceExpression struct { + HandleWrapperBase +} + +// NewEndpointReferenceExpression creates a new EndpointReferenceExpression. +func NewEndpointReferenceExpression(handle *Handle, client *AspireClient) *EndpointReferenceExpression { + return &EndpointReferenceExpression{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Endpoint gets the Endpoint property +func (s *EndpointReferenceExpression) Endpoint() (*EndpointReference, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.endpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*EndpointReference), nil +} + +// Property gets the Property property +func (s *EndpointReferenceExpression) Property() (*EndpointProperty, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.property", reqArgs) + if err != nil { + return nil, err + } + return result.(*EndpointProperty), nil +} + +// ValueExpression gets the ValueExpression property +func (s *EndpointReferenceExpression) ValueExpression() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.valueExpression", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// EnvironmentCallbackContext wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.EnvironmentCallbackContext. +type EnvironmentCallbackContext struct { + HandleWrapperBase + environmentVariables *AspireDict[string, any] +} + +// NewEnvironmentCallbackContext creates a new EnvironmentCallbackContext. +func NewEnvironmentCallbackContext(handle *Handle, client *AspireClient) *EnvironmentCallbackContext { + return &EnvironmentCallbackContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// EnvironmentVariables gets the EnvironmentVariables property +func (s *EnvironmentCallbackContext) EnvironmentVariables() *AspireDict[string, any] { + if s.environmentVariables == nil { + s.environmentVariables = NewAspireDictWithGetter[string, any](s.Handle(), s.Client(), "Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.environmentVariables") + } + return s.environmentVariables +} + +// CancellationToken gets the CancellationToken property +func (s *EnvironmentCallbackContext) CancellationToken() (*CancellationToken, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.cancellationToken", reqArgs) + if err != nil { + return nil, err + } + return result.(*CancellationToken), nil +} + +// ExecutionContext gets the ExecutionContext property +func (s *EnvironmentCallbackContext) ExecutionContext() (*DistributedApplicationExecutionContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.executionContext", reqArgs) + if err != nil { + return nil, err + } + return result.(*DistributedApplicationExecutionContext), nil +} + +// ExecutableResource wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecutableResource. +type ExecutableResource struct { + ResourceBuilderBase +} + +// NewExecutableResource creates a new ExecutableResource. +func NewExecutableResource(handle *Handle, client *AspireClient) *ExecutableResource { + return &ExecutableResource{ + ResourceBuilderBase: NewResourceBuilderBase(handle, client), + } +} + +// WithExecutableCommand sets the executable command +func (s *ExecutableResource) WithExecutableCommand(command string) (*ExecutableResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["command"] = SerializeValue(command) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withExecutableCommand", reqArgs) + if err != nil { + return nil, err + } + return result.(*ExecutableResource), nil +} + +// WithWorkingDirectory sets the executable working directory +func (s *ExecutableResource) WithWorkingDirectory(workingDirectory string) (*ExecutableResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["workingDirectory"] = SerializeValue(workingDirectory) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withWorkingDirectory", reqArgs) + if err != nil { + return nil, err + } + return result.(*ExecutableResource), nil +} + +// WithEnvironment sets an environment variable +func (s *ExecutableResource) WithEnvironment(name string, value string) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironment", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentExpression adds an environment variable with a reference expression +func (s *ExecutableResource) WithEnvironmentExpression(name string, value *ReferenceExpression) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentExpression", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentCallback sets environment variables via callback +func (s *ExecutableResource) WithEnvironmentCallback(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentCallbackAsync sets environment variables via async callback +func (s *ExecutableResource) WithEnvironmentCallbackAsync(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithArgs adds arguments +func (s *ExecutableResource) WithArgs(args []string) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["args"] = SerializeValue(args) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgs", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithArgsCallback sets command-line arguments via callback +func (s *ExecutableResource) WithArgsCallback(callback func(...any) any) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgsCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithArgsCallbackAsync sets command-line arguments via async callback +func (s *ExecutableResource) WithArgsCallbackAsync(callback func(...any) any) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgsCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithReference adds a reference to another resource +func (s *ExecutableResource) WithReference(source *IResourceWithConnectionString, connectionName string, optional bool) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["source"] = SerializeValue(source) + reqArgs["connectionName"] = SerializeValue(connectionName) + reqArgs["optional"] = SerializeValue(optional) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withReference", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithServiceReference adds a service discovery reference to another resource +func (s *ExecutableResource) WithServiceReference(source *IResourceWithServiceDiscovery) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["source"] = SerializeValue(source) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withServiceReference", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEndpoint adds a network endpoint +func (s *ExecutableResource) WithEndpoint(port float64, targetPort float64, scheme string, name string, env string, isProxied bool, isExternal bool, protocol ProtocolType) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["scheme"] = SerializeValue(scheme) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + reqArgs["isExternal"] = SerializeValue(isExternal) + reqArgs["protocol"] = SerializeValue(protocol) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithHttpEndpoint adds an HTTP endpoint +func (s *ExecutableResource) WithHttpEndpoint(port float64, targetPort float64, name string, env string, isProxied bool) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithHttpsEndpoint adds an HTTPS endpoint +func (s *ExecutableResource) WithHttpsEndpoint(port float64, targetPort float64, name string, env string, isProxied bool) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpsEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithExternalHttpEndpoints makes HTTP endpoints externally accessible +func (s *ExecutableResource) WithExternalHttpEndpoints() (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withExternalHttpEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// GetEndpoint gets an endpoint reference +func (s *ExecutableResource) GetEndpoint(name string) (*EndpointReference, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + result, err := s.Client().InvokeCapability("Aspire.Hosting/getEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*EndpointReference), nil +} + +// AsHttp2Service configures resource for HTTP/2 +func (s *ExecutableResource) AsHttp2Service() (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/asHttp2Service", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithUrlsCallback customizes displayed URLs via callback +func (s *ExecutableResource) WithUrlsCallback(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlsCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlsCallbackAsync customizes displayed URLs via async callback +func (s *ExecutableResource) WithUrlsCallbackAsync(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlsCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrl adds or modifies displayed URLs +func (s *ExecutableResource) WithUrl(url string, displayText string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["url"] = SerializeValue(url) + reqArgs["displayText"] = SerializeValue(displayText) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrl", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlExpression adds a URL using a reference expression +func (s *ExecutableResource) WithUrlExpression(url *ReferenceExpression, displayText string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["url"] = SerializeValue(url) + reqArgs["displayText"] = SerializeValue(displayText) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlExpression", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlForEndpoint customizes the URL for a specific endpoint via callback +func (s *ExecutableResource) WithUrlForEndpoint(endpointName string, callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpointName"] = SerializeValue(endpointName) + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlForEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlForEndpointFactory adds a URL for a specific endpoint via factory callback +func (s *ExecutableResource) WithUrlForEndpointFactory(endpointName string, callback func(...any) any) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpointName"] = SerializeValue(endpointName) + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlForEndpointFactory", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WaitFor waits for another resource to be ready +func (s *ExecutableResource) WaitFor(dependency *IResource) (*IResourceWithWaitSupport, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting/waitFor", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithWaitSupport), nil +} + +// WithExplicitStart prevents resource from starting automatically +func (s *ExecutableResource) WithExplicitStart() (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withExplicitStart", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WaitForCompletion waits for resource completion +func (s *ExecutableResource) WaitForCompletion(dependency *IResource, exitCode float64) (*IResourceWithWaitSupport, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + reqArgs["exitCode"] = SerializeValue(exitCode) + result, err := s.Client().InvokeCapability("Aspire.Hosting/waitForCompletion", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithWaitSupport), nil +} + +// WithHealthCheck adds a health check by key +func (s *ExecutableResource) WithHealthCheck(key string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["key"] = SerializeValue(key) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHealthCheck", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithHttpHealthCheck adds an HTTP health check +func (s *ExecutableResource) WithHttpHealthCheck(path string, statusCode float64, endpointName string) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["path"] = SerializeValue(path) + reqArgs["statusCode"] = SerializeValue(statusCode) + reqArgs["endpointName"] = SerializeValue(endpointName) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpHealthCheck", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithCommand adds a resource command +func (s *ExecutableResource) WithCommand(name string, displayName string, executeCommand func(...any) any, commandOptions *CommandOptions) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["displayName"] = SerializeValue(displayName) + if executeCommand != nil { + reqArgs["executeCommand"] = RegisterCallback(executeCommand) + } + if commandOptions != nil { + reqArgs["commandOptions"] = SerializeValue(commandOptions) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withCommand", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithParentRelationship sets the parent relationship +func (s *ExecutableResource) WithParentRelationship(parent *IResource) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["parent"] = SerializeValue(parent) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withParentRelationship", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// GetResourceName gets the resource name +func (s *ExecutableResource) GetResourceName() (*string, error) { + reqArgs := map[string]any{ + "resource": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/getResourceName", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// WithOptionalString adds an optional string parameter +func (s *ExecutableResource) WithOptionalString(value string, enabled bool) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + reqArgs["enabled"] = SerializeValue(enabled) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalString", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithConfig configures the resource with a DTO +func (s *ExecutableResource) WithConfig(config *TestConfigDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// TestWithEnvironmentCallback configures environment with callback (test version) +func (s *ExecutableResource) TestWithEnvironmentCallback(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/testWithEnvironmentCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithCreatedAt sets the created timestamp +func (s *ExecutableResource) WithCreatedAt(createdAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["createdAt"] = SerializeValue(createdAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCreatedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithModifiedAt sets the modified timestamp +func (s *ExecutableResource) WithModifiedAt(modifiedAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["modifiedAt"] = SerializeValue(modifiedAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withModifiedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithCorrelationId sets the correlation ID +func (s *ExecutableResource) WithCorrelationId(correlationId string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["correlationId"] = SerializeValue(correlationId) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCorrelationId", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithOptionalCallback configures with optional callback +func (s *ExecutableResource) WithOptionalCallback(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithStatus sets the resource status +func (s *ExecutableResource) WithStatus(status TestResourceStatus) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["status"] = SerializeValue(status) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withStatus", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithNestedConfig configures with nested DTO +func (s *ExecutableResource) WithNestedConfig(config *TestNestedDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withNestedConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithValidator adds validation callback +func (s *ExecutableResource) WithValidator(validator func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if validator != nil { + reqArgs["validator"] = RegisterCallback(validator) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withValidator", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// TestWaitFor waits for another resource (test version) +func (s *ExecutableResource) TestWaitFor(dependency *IResource) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/testWaitFor", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithDependency adds a dependency on another resource +func (s *ExecutableResource) WithDependency(dependency *IResourceWithConnectionString) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withDependency", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithEndpoints sets the endpoints +func (s *ExecutableResource) WithEndpoints(endpoints []string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpoints"] = SerializeValue(endpoints) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithEnvironmentVariables sets environment variables +func (s *ExecutableResource) WithEnvironmentVariables(variables map[string]string) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["variables"] = SerializeValue(variables) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withEnvironmentVariables", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithCancellableOperation performs a cancellable operation +func (s *ExecutableResource) WithCancellableOperation(operation func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if operation != nil { + reqArgs["operation"] = RegisterCallback(operation) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCancellableOperation", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// ExecuteCommandContext wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCommandContext. +type ExecuteCommandContext struct { + HandleWrapperBase +} + +// NewExecuteCommandContext creates a new ExecuteCommandContext. +func NewExecuteCommandContext(handle *Handle, client *AspireClient) *ExecuteCommandContext { + return &ExecuteCommandContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// ResourceName gets the ResourceName property +func (s *ExecuteCommandContext) ResourceName() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.resourceName", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetResourceName sets the ResourceName property +func (s *ExecuteCommandContext) SetResourceName(value string) (*ExecuteCommandContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.setResourceName", reqArgs) + if err != nil { + return nil, err + } + return result.(*ExecuteCommandContext), nil +} + +// CancellationToken gets the CancellationToken property +func (s *ExecuteCommandContext) CancellationToken() (*CancellationToken, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.cancellationToken", reqArgs) + if err != nil { + return nil, err + } + return result.(*CancellationToken), nil +} + +// SetCancellationToken sets the CancellationToken property +func (s *ExecuteCommandContext) SetCancellationToken(value *CancellationToken) (*ExecuteCommandContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + if value != nil { + reqArgs["value"] = RegisterCancellation(value, s.Client()) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.setCancellationToken", reqArgs) + if err != nil { + return nil, err + } + return result.(*ExecuteCommandContext), nil +} + +// IDistributedApplicationBuilder wraps a handle for Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder. +type IDistributedApplicationBuilder struct { + HandleWrapperBase +} + +// NewIDistributedApplicationBuilder creates a new IDistributedApplicationBuilder. +func NewIDistributedApplicationBuilder(handle *Handle, client *AspireClient) *IDistributedApplicationBuilder { + return &IDistributedApplicationBuilder{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// AddContainer adds a container resource +func (s *IDistributedApplicationBuilder) AddContainer(name string, image string) (*ContainerResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["image"] = SerializeValue(image) + result, err := s.Client().InvokeCapability("Aspire.Hosting/addContainer", reqArgs) + if err != nil { + return nil, err + } + return result.(*ContainerResource), nil +} + +// AddExecutable adds an executable resource +func (s *IDistributedApplicationBuilder) AddExecutable(name string, command string, workingDirectory string, args []string) (*ExecutableResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["command"] = SerializeValue(command) + reqArgs["workingDirectory"] = SerializeValue(workingDirectory) + reqArgs["args"] = SerializeValue(args) + result, err := s.Client().InvokeCapability("Aspire.Hosting/addExecutable", reqArgs) + if err != nil { + return nil, err + } + return result.(*ExecutableResource), nil +} + +// AppHostDirectory gets the AppHostDirectory property +func (s *IDistributedApplicationBuilder) AppHostDirectory() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/IDistributedApplicationBuilder.appHostDirectory", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// Eventing gets the Eventing property +func (s *IDistributedApplicationBuilder) Eventing() (*IDistributedApplicationEventing, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/IDistributedApplicationBuilder.eventing", reqArgs) + if err != nil { + return nil, err + } + return result.(*IDistributedApplicationEventing), nil +} + +// ExecutionContext gets the ExecutionContext property +func (s *IDistributedApplicationBuilder) ExecutionContext() (*DistributedApplicationExecutionContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/IDistributedApplicationBuilder.executionContext", reqArgs) + if err != nil { + return nil, err + } + return result.(*DistributedApplicationExecutionContext), nil +} + +// Build builds the distributed application +func (s *IDistributedApplicationBuilder) Build() (*DistributedApplication, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/build", reqArgs) + if err != nil { + return nil, err + } + return result.(*DistributedApplication), nil +} + +// AddParameter adds a parameter resource +func (s *IDistributedApplicationBuilder) AddParameter(name string, secret bool) (*ParameterResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["secret"] = SerializeValue(secret) + result, err := s.Client().InvokeCapability("Aspire.Hosting/addParameter", reqArgs) + if err != nil { + return nil, err + } + return result.(*ParameterResource), nil +} + +// AddConnectionString adds a connection string resource +func (s *IDistributedApplicationBuilder) AddConnectionString(name string, environmentVariableName string) (*IResourceWithConnectionString, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["environmentVariableName"] = SerializeValue(environmentVariableName) + result, err := s.Client().InvokeCapability("Aspire.Hosting/addConnectionString", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithConnectionString), nil +} + +// AddProject adds a .NET project resource +func (s *IDistributedApplicationBuilder) AddProject(name string, projectPath string, launchProfileName string) (*ProjectResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["projectPath"] = SerializeValue(projectPath) + reqArgs["launchProfileName"] = SerializeValue(launchProfileName) + result, err := s.Client().InvokeCapability("Aspire.Hosting/addProject", reqArgs) + if err != nil { + return nil, err + } + return result.(*ProjectResource), nil +} + +// AddTestRedis adds a test Redis resource +func (s *IDistributedApplicationBuilder) AddTestRedis(name string, port float64) (*TestRedisResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["port"] = SerializeValue(port) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/addTestRedis", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestRedisResource), nil +} + +// IDistributedApplicationEvent wraps a handle for Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEvent. +type IDistributedApplicationEvent struct { + HandleWrapperBase +} + +// NewIDistributedApplicationEvent creates a new IDistributedApplicationEvent. +func NewIDistributedApplicationEvent(handle *Handle, client *AspireClient) *IDistributedApplicationEvent { + return &IDistributedApplicationEvent{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// IDistributedApplicationEventing wraps a handle for Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEventing. +type IDistributedApplicationEventing struct { + HandleWrapperBase +} + +// NewIDistributedApplicationEventing creates a new IDistributedApplicationEventing. +func NewIDistributedApplicationEventing(handle *Handle, client *AspireClient) *IDistributedApplicationEventing { + return &IDistributedApplicationEventing{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Unsubscribe invokes the Unsubscribe method +func (s *IDistributedApplicationEventing) Unsubscribe(subscription *DistributedApplicationEventSubscription) error { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["subscription"] = SerializeValue(subscription) + _, err := s.Client().InvokeCapability("Aspire.Hosting.Eventing/IDistributedApplicationEventing.unsubscribe", reqArgs) + return err +} + +// IDistributedApplicationResourceEvent wraps a handle for Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationResourceEvent. +type IDistributedApplicationResourceEvent struct { + HandleWrapperBase +} + +// NewIDistributedApplicationResourceEvent creates a new IDistributedApplicationResourceEvent. +func NewIDistributedApplicationResourceEvent(handle *Handle, client *AspireClient) *IDistributedApplicationResourceEvent { + return &IDistributedApplicationResourceEvent{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// IResource wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource. +type IResource struct { + ResourceBuilderBase +} + +// NewIResource creates a new IResource. +func NewIResource(handle *Handle, client *AspireClient) *IResource { + return &IResource{ + ResourceBuilderBase: NewResourceBuilderBase(handle, client), + } +} + +// IResourceWithArgs wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithArgs. +type IResourceWithArgs struct { + HandleWrapperBase +} + +// NewIResourceWithArgs creates a new IResourceWithArgs. +func NewIResourceWithArgs(handle *Handle, client *AspireClient) *IResourceWithArgs { + return &IResourceWithArgs{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// IResourceWithConnectionString wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString. +type IResourceWithConnectionString struct { + ResourceBuilderBase +} + +// NewIResourceWithConnectionString creates a new IResourceWithConnectionString. +func NewIResourceWithConnectionString(handle *Handle, client *AspireClient) *IResourceWithConnectionString { + return &IResourceWithConnectionString{ + ResourceBuilderBase: NewResourceBuilderBase(handle, client), + } +} + +// IResourceWithEndpoints wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints. +type IResourceWithEndpoints struct { + HandleWrapperBase +} + +// NewIResourceWithEndpoints creates a new IResourceWithEndpoints. +func NewIResourceWithEndpoints(handle *Handle, client *AspireClient) *IResourceWithEndpoints { + return &IResourceWithEndpoints{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// IResourceWithEnvironment wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment. +type IResourceWithEnvironment struct { + HandleWrapperBase +} + +// NewIResourceWithEnvironment creates a new IResourceWithEnvironment. +func NewIResourceWithEnvironment(handle *Handle, client *AspireClient) *IResourceWithEnvironment { + return &IResourceWithEnvironment{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// IResourceWithServiceDiscovery wraps a handle for Aspire.Hosting/Aspire.Hosting.IResourceWithServiceDiscovery. +type IResourceWithServiceDiscovery struct { + ResourceBuilderBase +} + +// NewIResourceWithServiceDiscovery creates a new IResourceWithServiceDiscovery. +func NewIResourceWithServiceDiscovery(handle *Handle, client *AspireClient) *IResourceWithServiceDiscovery { + return &IResourceWithServiceDiscovery{ + ResourceBuilderBase: NewResourceBuilderBase(handle, client), + } +} + +// IResourceWithWaitSupport wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithWaitSupport. +type IResourceWithWaitSupport struct { + HandleWrapperBase +} + +// NewIResourceWithWaitSupport creates a new IResourceWithWaitSupport. +func NewIResourceWithWaitSupport(handle *Handle, client *AspireClient) *IResourceWithWaitSupport { + return &IResourceWithWaitSupport{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// ParameterResource wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ParameterResource. +type ParameterResource struct { + ResourceBuilderBase +} + +// NewParameterResource creates a new ParameterResource. +func NewParameterResource(handle *Handle, client *AspireClient) *ParameterResource { + return &ParameterResource{ + ResourceBuilderBase: NewResourceBuilderBase(handle, client), + } +} + +// WithDescription sets a parameter description +func (s *ParameterResource) WithDescription(description string, enableMarkdown bool) (*ParameterResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["description"] = SerializeValue(description) + reqArgs["enableMarkdown"] = SerializeValue(enableMarkdown) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withDescription", reqArgs) + if err != nil { + return nil, err + } + return result.(*ParameterResource), nil +} + +// WithUrlsCallback customizes displayed URLs via callback +func (s *ParameterResource) WithUrlsCallback(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlsCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlsCallbackAsync customizes displayed URLs via async callback +func (s *ParameterResource) WithUrlsCallbackAsync(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlsCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrl adds or modifies displayed URLs +func (s *ParameterResource) WithUrl(url string, displayText string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["url"] = SerializeValue(url) + reqArgs["displayText"] = SerializeValue(displayText) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrl", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlExpression adds a URL using a reference expression +func (s *ParameterResource) WithUrlExpression(url *ReferenceExpression, displayText string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["url"] = SerializeValue(url) + reqArgs["displayText"] = SerializeValue(displayText) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlExpression", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlForEndpoint customizes the URL for a specific endpoint via callback +func (s *ParameterResource) WithUrlForEndpoint(endpointName string, callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpointName"] = SerializeValue(endpointName) + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlForEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithExplicitStart prevents resource from starting automatically +func (s *ParameterResource) WithExplicitStart() (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withExplicitStart", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithHealthCheck adds a health check by key +func (s *ParameterResource) WithHealthCheck(key string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["key"] = SerializeValue(key) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHealthCheck", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithCommand adds a resource command +func (s *ParameterResource) WithCommand(name string, displayName string, executeCommand func(...any) any, commandOptions *CommandOptions) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["displayName"] = SerializeValue(displayName) + if executeCommand != nil { + reqArgs["executeCommand"] = RegisterCallback(executeCommand) + } + if commandOptions != nil { + reqArgs["commandOptions"] = SerializeValue(commandOptions) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withCommand", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithParentRelationship sets the parent relationship +func (s *ParameterResource) WithParentRelationship(parent *IResource) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["parent"] = SerializeValue(parent) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withParentRelationship", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// GetResourceName gets the resource name +func (s *ParameterResource) GetResourceName() (*string, error) { + reqArgs := map[string]any{ + "resource": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/getResourceName", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// WithOptionalString adds an optional string parameter +func (s *ParameterResource) WithOptionalString(value string, enabled bool) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + reqArgs["enabled"] = SerializeValue(enabled) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalString", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithConfig configures the resource with a DTO +func (s *ParameterResource) WithConfig(config *TestConfigDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithCreatedAt sets the created timestamp +func (s *ParameterResource) WithCreatedAt(createdAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["createdAt"] = SerializeValue(createdAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCreatedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithModifiedAt sets the modified timestamp +func (s *ParameterResource) WithModifiedAt(modifiedAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["modifiedAt"] = SerializeValue(modifiedAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withModifiedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithCorrelationId sets the correlation ID +func (s *ParameterResource) WithCorrelationId(correlationId string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["correlationId"] = SerializeValue(correlationId) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCorrelationId", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithOptionalCallback configures with optional callback +func (s *ParameterResource) WithOptionalCallback(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithStatus sets the resource status +func (s *ParameterResource) WithStatus(status TestResourceStatus) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["status"] = SerializeValue(status) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withStatus", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithNestedConfig configures with nested DTO +func (s *ParameterResource) WithNestedConfig(config *TestNestedDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withNestedConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithValidator adds validation callback +func (s *ParameterResource) WithValidator(validator func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if validator != nil { + reqArgs["validator"] = RegisterCallback(validator) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withValidator", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// TestWaitFor waits for another resource (test version) +func (s *ParameterResource) TestWaitFor(dependency *IResource) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/testWaitFor", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithDependency adds a dependency on another resource +func (s *ParameterResource) WithDependency(dependency *IResourceWithConnectionString) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withDependency", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithEndpoints sets the endpoints +func (s *ParameterResource) WithEndpoints(endpoints []string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpoints"] = SerializeValue(endpoints) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithCancellableOperation performs a cancellable operation +func (s *ParameterResource) WithCancellableOperation(operation func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if operation != nil { + reqArgs["operation"] = RegisterCallback(operation) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCancellableOperation", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// ProjectResource wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ProjectResource. +type ProjectResource struct { + ResourceBuilderBase +} + +// NewProjectResource creates a new ProjectResource. +func NewProjectResource(handle *Handle, client *AspireClient) *ProjectResource { + return &ProjectResource{ + ResourceBuilderBase: NewResourceBuilderBase(handle, client), + } +} + +// WithReplicas sets the number of replicas +func (s *ProjectResource) WithReplicas(replicas float64) (*ProjectResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["replicas"] = SerializeValue(replicas) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withReplicas", reqArgs) + if err != nil { + return nil, err + } + return result.(*ProjectResource), nil +} + +// WithEnvironment sets an environment variable +func (s *ProjectResource) WithEnvironment(name string, value string) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironment", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentExpression adds an environment variable with a reference expression +func (s *ProjectResource) WithEnvironmentExpression(name string, value *ReferenceExpression) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentExpression", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentCallback sets environment variables via callback +func (s *ProjectResource) WithEnvironmentCallback(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentCallbackAsync sets environment variables via async callback +func (s *ProjectResource) WithEnvironmentCallbackAsync(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithArgs adds arguments +func (s *ProjectResource) WithArgs(args []string) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["args"] = SerializeValue(args) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgs", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithArgsCallback sets command-line arguments via callback +func (s *ProjectResource) WithArgsCallback(callback func(...any) any) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgsCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithArgsCallbackAsync sets command-line arguments via async callback +func (s *ProjectResource) WithArgsCallbackAsync(callback func(...any) any) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgsCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithReference adds a reference to another resource +func (s *ProjectResource) WithReference(source *IResourceWithConnectionString, connectionName string, optional bool) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["source"] = SerializeValue(source) + reqArgs["connectionName"] = SerializeValue(connectionName) + reqArgs["optional"] = SerializeValue(optional) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withReference", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithServiceReference adds a service discovery reference to another resource +func (s *ProjectResource) WithServiceReference(source *IResourceWithServiceDiscovery) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["source"] = SerializeValue(source) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withServiceReference", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEndpoint adds a network endpoint +func (s *ProjectResource) WithEndpoint(port float64, targetPort float64, scheme string, name string, env string, isProxied bool, isExternal bool, protocol ProtocolType) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["scheme"] = SerializeValue(scheme) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + reqArgs["isExternal"] = SerializeValue(isExternal) + reqArgs["protocol"] = SerializeValue(protocol) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithHttpEndpoint adds an HTTP endpoint +func (s *ProjectResource) WithHttpEndpoint(port float64, targetPort float64, name string, env string, isProxied bool) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithHttpsEndpoint adds an HTTPS endpoint +func (s *ProjectResource) WithHttpsEndpoint(port float64, targetPort float64, name string, env string, isProxied bool) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpsEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithExternalHttpEndpoints makes HTTP endpoints externally accessible +func (s *ProjectResource) WithExternalHttpEndpoints() (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withExternalHttpEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// GetEndpoint gets an endpoint reference +func (s *ProjectResource) GetEndpoint(name string) (*EndpointReference, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + result, err := s.Client().InvokeCapability("Aspire.Hosting/getEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*EndpointReference), nil +} + +// AsHttp2Service configures resource for HTTP/2 +func (s *ProjectResource) AsHttp2Service() (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/asHttp2Service", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithUrlsCallback customizes displayed URLs via callback +func (s *ProjectResource) WithUrlsCallback(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlsCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlsCallbackAsync customizes displayed URLs via async callback +func (s *ProjectResource) WithUrlsCallbackAsync(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlsCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrl adds or modifies displayed URLs +func (s *ProjectResource) WithUrl(url string, displayText string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["url"] = SerializeValue(url) + reqArgs["displayText"] = SerializeValue(displayText) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrl", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlExpression adds a URL using a reference expression +func (s *ProjectResource) WithUrlExpression(url *ReferenceExpression, displayText string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["url"] = SerializeValue(url) + reqArgs["displayText"] = SerializeValue(displayText) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlExpression", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlForEndpoint customizes the URL for a specific endpoint via callback +func (s *ProjectResource) WithUrlForEndpoint(endpointName string, callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpointName"] = SerializeValue(endpointName) + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlForEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlForEndpointFactory adds a URL for a specific endpoint via factory callback +func (s *ProjectResource) WithUrlForEndpointFactory(endpointName string, callback func(...any) any) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpointName"] = SerializeValue(endpointName) + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlForEndpointFactory", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WaitFor waits for another resource to be ready +func (s *ProjectResource) WaitFor(dependency *IResource) (*IResourceWithWaitSupport, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting/waitFor", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithWaitSupport), nil +} + +// WithExplicitStart prevents resource from starting automatically +func (s *ProjectResource) WithExplicitStart() (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withExplicitStart", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WaitForCompletion waits for resource completion +func (s *ProjectResource) WaitForCompletion(dependency *IResource, exitCode float64) (*IResourceWithWaitSupport, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + reqArgs["exitCode"] = SerializeValue(exitCode) + result, err := s.Client().InvokeCapability("Aspire.Hosting/waitForCompletion", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithWaitSupport), nil +} + +// WithHealthCheck adds a health check by key +func (s *ProjectResource) WithHealthCheck(key string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["key"] = SerializeValue(key) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHealthCheck", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithHttpHealthCheck adds an HTTP health check +func (s *ProjectResource) WithHttpHealthCheck(path string, statusCode float64, endpointName string) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["path"] = SerializeValue(path) + reqArgs["statusCode"] = SerializeValue(statusCode) + reqArgs["endpointName"] = SerializeValue(endpointName) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpHealthCheck", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithCommand adds a resource command +func (s *ProjectResource) WithCommand(name string, displayName string, executeCommand func(...any) any, commandOptions *CommandOptions) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["displayName"] = SerializeValue(displayName) + if executeCommand != nil { + reqArgs["executeCommand"] = RegisterCallback(executeCommand) + } + if commandOptions != nil { + reqArgs["commandOptions"] = SerializeValue(commandOptions) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withCommand", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithParentRelationship sets the parent relationship +func (s *ProjectResource) WithParentRelationship(parent *IResource) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["parent"] = SerializeValue(parent) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withParentRelationship", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// GetResourceName gets the resource name +func (s *ProjectResource) GetResourceName() (*string, error) { + reqArgs := map[string]any{ + "resource": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/getResourceName", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// WithOptionalString adds an optional string parameter +func (s *ProjectResource) WithOptionalString(value string, enabled bool) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + reqArgs["enabled"] = SerializeValue(enabled) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalString", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithConfig configures the resource with a DTO +func (s *ProjectResource) WithConfig(config *TestConfigDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// TestWithEnvironmentCallback configures environment with callback (test version) +func (s *ProjectResource) TestWithEnvironmentCallback(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/testWithEnvironmentCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithCreatedAt sets the created timestamp +func (s *ProjectResource) WithCreatedAt(createdAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["createdAt"] = SerializeValue(createdAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCreatedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithModifiedAt sets the modified timestamp +func (s *ProjectResource) WithModifiedAt(modifiedAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["modifiedAt"] = SerializeValue(modifiedAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withModifiedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithCorrelationId sets the correlation ID +func (s *ProjectResource) WithCorrelationId(correlationId string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["correlationId"] = SerializeValue(correlationId) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCorrelationId", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithOptionalCallback configures with optional callback +func (s *ProjectResource) WithOptionalCallback(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithStatus sets the resource status +func (s *ProjectResource) WithStatus(status TestResourceStatus) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["status"] = SerializeValue(status) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withStatus", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithNestedConfig configures with nested DTO +func (s *ProjectResource) WithNestedConfig(config *TestNestedDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withNestedConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithValidator adds validation callback +func (s *ProjectResource) WithValidator(validator func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if validator != nil { + reqArgs["validator"] = RegisterCallback(validator) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withValidator", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// TestWaitFor waits for another resource (test version) +func (s *ProjectResource) TestWaitFor(dependency *IResource) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/testWaitFor", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithDependency adds a dependency on another resource +func (s *ProjectResource) WithDependency(dependency *IResourceWithConnectionString) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withDependency", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithEndpoints sets the endpoints +func (s *ProjectResource) WithEndpoints(endpoints []string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpoints"] = SerializeValue(endpoints) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithEnvironmentVariables sets environment variables +func (s *ProjectResource) WithEnvironmentVariables(variables map[string]string) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["variables"] = SerializeValue(variables) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withEnvironmentVariables", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithCancellableOperation performs a cancellable operation +func (s *ProjectResource) WithCancellableOperation(operation func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if operation != nil { + reqArgs["operation"] = RegisterCallback(operation) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCancellableOperation", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// ResourceUrlsCallbackContext wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceUrlsCallbackContext. +type ResourceUrlsCallbackContext struct { + HandleWrapperBase + urls *AspireList[*ResourceUrlAnnotation] +} + +// NewResourceUrlsCallbackContext creates a new ResourceUrlsCallbackContext. +func NewResourceUrlsCallbackContext(handle *Handle, client *AspireClient) *ResourceUrlsCallbackContext { + return &ResourceUrlsCallbackContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Urls gets the Urls property +func (s *ResourceUrlsCallbackContext) Urls() *AspireList[*ResourceUrlAnnotation] { + if s.urls == nil { + s.urls = NewAspireListWithGetter[*ResourceUrlAnnotation](s.Handle(), s.Client(), "Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.urls") + } + return s.urls +} + +// CancellationToken gets the CancellationToken property +func (s *ResourceUrlsCallbackContext) CancellationToken() (*CancellationToken, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.cancellationToken", reqArgs) + if err != nil { + return nil, err + } + return result.(*CancellationToken), nil +} + +// ExecutionContext gets the ExecutionContext property +func (s *ResourceUrlsCallbackContext) ExecutionContext() (*DistributedApplicationExecutionContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.executionContext", reqArgs) + if err != nil { + return nil, err + } + return result.(*DistributedApplicationExecutionContext), nil +} + +// TestCallbackContext wraps a handle for Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext. +type TestCallbackContext struct { + HandleWrapperBase +} + +// NewTestCallbackContext creates a new TestCallbackContext. +func NewTestCallbackContext(handle *Handle, client *AspireClient) *TestCallbackContext { + return &TestCallbackContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Name gets the Name property +func (s *TestCallbackContext) Name() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.name", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetName sets the Name property +func (s *TestCallbackContext) SetName(value string) (*TestCallbackContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setName", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestCallbackContext), nil +} + +// Value gets the Value property +func (s *TestCallbackContext) Value() (*float64, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.value", reqArgs) + if err != nil { + return nil, err + } + return result.(*float64), nil +} + +// SetValue sets the Value property +func (s *TestCallbackContext) SetValue(value float64) (*TestCallbackContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setValue", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestCallbackContext), nil +} + +// CancellationToken gets the CancellationToken property +func (s *TestCallbackContext) CancellationToken() (*CancellationToken, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.cancellationToken", reqArgs) + if err != nil { + return nil, err + } + return result.(*CancellationToken), nil +} + +// SetCancellationToken sets the CancellationToken property +func (s *TestCallbackContext) SetCancellationToken(value *CancellationToken) (*TestCallbackContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + if value != nil { + reqArgs["value"] = RegisterCancellation(value, s.Client()) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setCancellationToken", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestCallbackContext), nil +} + +// TestCollectionContext wraps a handle for Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext. +type TestCollectionContext struct { + HandleWrapperBase + items *AspireList[string] + metadata *AspireDict[string, string] +} + +// NewTestCollectionContext creates a new TestCollectionContext. +func NewTestCollectionContext(handle *Handle, client *AspireClient) *TestCollectionContext { + return &TestCollectionContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Items gets the Items property +func (s *TestCollectionContext) Items() *AspireList[string] { + if s.items == nil { + s.items = NewAspireListWithGetter[string](s.Handle(), s.Client(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.items") + } + return s.items +} + +// Metadata gets the Metadata property +func (s *TestCollectionContext) Metadata() *AspireDict[string, string] { + if s.metadata == nil { + s.metadata = NewAspireDictWithGetter[string, string](s.Handle(), s.Client(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.metadata") + } + return s.metadata +} + +// TestEnvironmentContext wraps a handle for Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext. +type TestEnvironmentContext struct { + HandleWrapperBase +} + +// NewTestEnvironmentContext creates a new TestEnvironmentContext. +func NewTestEnvironmentContext(handle *Handle, client *AspireClient) *TestEnvironmentContext { + return &TestEnvironmentContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Name gets the Name property +func (s *TestEnvironmentContext) Name() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.name", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetName sets the Name property +func (s *TestEnvironmentContext) SetName(value string) (*TestEnvironmentContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setName", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestEnvironmentContext), nil +} + +// Description gets the Description property +func (s *TestEnvironmentContext) Description() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.description", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetDescription sets the Description property +func (s *TestEnvironmentContext) SetDescription(value string) (*TestEnvironmentContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setDescription", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestEnvironmentContext), nil +} + +// Priority gets the Priority property +func (s *TestEnvironmentContext) Priority() (*float64, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.priority", reqArgs) + if err != nil { + return nil, err + } + return result.(*float64), nil +} + +// SetPriority sets the Priority property +func (s *TestEnvironmentContext) SetPriority(value float64) (*TestEnvironmentContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setPriority", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestEnvironmentContext), nil +} + +// TestRedisResource wraps a handle for Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource. +type TestRedisResource struct { + ResourceBuilderBase + getTags *AspireList[string] + getMetadata *AspireDict[string, string] +} + +// NewTestRedisResource creates a new TestRedisResource. +func NewTestRedisResource(handle *Handle, client *AspireClient) *TestRedisResource { + return &TestRedisResource{ + ResourceBuilderBase: NewResourceBuilderBase(handle, client), + } +} + +// WithBindMount adds a bind mount +func (s *TestRedisResource) WithBindMount(source string, target string, isReadOnly bool) (*ContainerResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["source"] = SerializeValue(source) + reqArgs["target"] = SerializeValue(target) + reqArgs["isReadOnly"] = SerializeValue(isReadOnly) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withBindMount", reqArgs) + if err != nil { + return nil, err + } + return result.(*ContainerResource), nil +} + +// WithEntrypoint sets the container entrypoint +func (s *TestRedisResource) WithEntrypoint(entrypoint string) (*ContainerResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["entrypoint"] = SerializeValue(entrypoint) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEntrypoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*ContainerResource), nil +} + +// WithImageTag sets the container image tag +func (s *TestRedisResource) WithImageTag(tag string) (*ContainerResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["tag"] = SerializeValue(tag) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withImageTag", reqArgs) + if err != nil { + return nil, err + } + return result.(*ContainerResource), nil +} + +// WithImageRegistry sets the container image registry +func (s *TestRedisResource) WithImageRegistry(registry string) (*ContainerResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["registry"] = SerializeValue(registry) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withImageRegistry", reqArgs) + if err != nil { + return nil, err + } + return result.(*ContainerResource), nil +} + +// WithImage sets the container image +func (s *TestRedisResource) WithImage(image string, tag string) (*ContainerResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["image"] = SerializeValue(image) + reqArgs["tag"] = SerializeValue(tag) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withImage", reqArgs) + if err != nil { + return nil, err + } + return result.(*ContainerResource), nil +} + +// WithContainerRuntimeArgs adds runtime arguments for the container +func (s *TestRedisResource) WithContainerRuntimeArgs(args []string) (*ContainerResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["args"] = SerializeValue(args) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withContainerRuntimeArgs", reqArgs) + if err != nil { + return nil, err + } + return result.(*ContainerResource), nil +} + +// WithLifetime sets the lifetime behavior of the container resource +func (s *TestRedisResource) WithLifetime(lifetime ContainerLifetime) (*ContainerResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["lifetime"] = SerializeValue(lifetime) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withLifetime", reqArgs) + if err != nil { + return nil, err + } + return result.(*ContainerResource), nil +} + +// WithImagePullPolicy sets the container image pull policy +func (s *TestRedisResource) WithImagePullPolicy(pullPolicy ImagePullPolicy) (*ContainerResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["pullPolicy"] = SerializeValue(pullPolicy) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withImagePullPolicy", reqArgs) + if err != nil { + return nil, err + } + return result.(*ContainerResource), nil +} + +// WithContainerName sets the container name +func (s *TestRedisResource) WithContainerName(name string) (*ContainerResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withContainerName", reqArgs) + if err != nil { + return nil, err + } + return result.(*ContainerResource), nil +} + +// WithEnvironment sets an environment variable +func (s *TestRedisResource) WithEnvironment(name string, value string) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironment", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentExpression adds an environment variable with a reference expression +func (s *TestRedisResource) WithEnvironmentExpression(name string, value *ReferenceExpression) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentExpression", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentCallback sets environment variables via callback +func (s *TestRedisResource) WithEnvironmentCallback(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEnvironmentCallbackAsync sets environment variables via async callback +func (s *TestRedisResource) WithEnvironmentCallbackAsync(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEnvironmentCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithArgs adds arguments +func (s *TestRedisResource) WithArgs(args []string) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["args"] = SerializeValue(args) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgs", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithArgsCallback sets command-line arguments via callback +func (s *TestRedisResource) WithArgsCallback(callback func(...any) any) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgsCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithArgsCallbackAsync sets command-line arguments via async callback +func (s *TestRedisResource) WithArgsCallbackAsync(callback func(...any) any) (*IResourceWithArgs, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withArgsCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithArgs), nil +} + +// WithReference adds a reference to another resource +func (s *TestRedisResource) WithReference(source *IResourceWithConnectionString, connectionName string, optional bool) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["source"] = SerializeValue(source) + reqArgs["connectionName"] = SerializeValue(connectionName) + reqArgs["optional"] = SerializeValue(optional) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withReference", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithServiceReference adds a service discovery reference to another resource +func (s *TestRedisResource) WithServiceReference(source *IResourceWithServiceDiscovery) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["source"] = SerializeValue(source) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withServiceReference", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithEndpoint adds a network endpoint +func (s *TestRedisResource) WithEndpoint(port float64, targetPort float64, scheme string, name string, env string, isProxied bool, isExternal bool, protocol ProtocolType) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["scheme"] = SerializeValue(scheme) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + reqArgs["isExternal"] = SerializeValue(isExternal) + reqArgs["protocol"] = SerializeValue(protocol) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithHttpEndpoint adds an HTTP endpoint +func (s *TestRedisResource) WithHttpEndpoint(port float64, targetPort float64, name string, env string, isProxied bool) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithHttpsEndpoint adds an HTTPS endpoint +func (s *TestRedisResource) WithHttpsEndpoint(port float64, targetPort float64, name string, env string, isProxied bool) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["port"] = SerializeValue(port) + reqArgs["targetPort"] = SerializeValue(targetPort) + reqArgs["name"] = SerializeValue(name) + reqArgs["env"] = SerializeValue(env) + reqArgs["isProxied"] = SerializeValue(isProxied) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpsEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithExternalHttpEndpoints makes HTTP endpoints externally accessible +func (s *TestRedisResource) WithExternalHttpEndpoints() (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withExternalHttpEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// GetEndpoint gets an endpoint reference +func (s *TestRedisResource) GetEndpoint(name string) (*EndpointReference, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + result, err := s.Client().InvokeCapability("Aspire.Hosting/getEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*EndpointReference), nil +} + +// AsHttp2Service configures resource for HTTP/2 +func (s *TestRedisResource) AsHttp2Service() (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/asHttp2Service", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithUrlsCallback customizes displayed URLs via callback +func (s *TestRedisResource) WithUrlsCallback(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlsCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlsCallbackAsync customizes displayed URLs via async callback +func (s *TestRedisResource) WithUrlsCallbackAsync(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlsCallbackAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrl adds or modifies displayed URLs +func (s *TestRedisResource) WithUrl(url string, displayText string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["url"] = SerializeValue(url) + reqArgs["displayText"] = SerializeValue(displayText) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrl", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlExpression adds a URL using a reference expression +func (s *TestRedisResource) WithUrlExpression(url *ReferenceExpression, displayText string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["url"] = SerializeValue(url) + reqArgs["displayText"] = SerializeValue(displayText) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlExpression", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlForEndpoint customizes the URL for a specific endpoint via callback +func (s *TestRedisResource) WithUrlForEndpoint(endpointName string, callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpointName"] = SerializeValue(endpointName) + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlForEndpoint", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithUrlForEndpointFactory adds a URL for a specific endpoint via factory callback +func (s *TestRedisResource) WithUrlForEndpointFactory(endpointName string, callback func(...any) any) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpointName"] = SerializeValue(endpointName) + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withUrlForEndpointFactory", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WaitFor waits for another resource to be ready +func (s *TestRedisResource) WaitFor(dependency *IResource) (*IResourceWithWaitSupport, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting/waitFor", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithWaitSupport), nil +} + +// WithExplicitStart prevents resource from starting automatically +func (s *TestRedisResource) WithExplicitStart() (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withExplicitStart", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WaitForCompletion waits for resource completion +func (s *TestRedisResource) WaitForCompletion(dependency *IResource, exitCode float64) (*IResourceWithWaitSupport, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + reqArgs["exitCode"] = SerializeValue(exitCode) + result, err := s.Client().InvokeCapability("Aspire.Hosting/waitForCompletion", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithWaitSupport), nil +} + +// WithHealthCheck adds a health check by key +func (s *TestRedisResource) WithHealthCheck(key string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["key"] = SerializeValue(key) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHealthCheck", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithHttpHealthCheck adds an HTTP health check +func (s *TestRedisResource) WithHttpHealthCheck(path string, statusCode float64, endpointName string) (*IResourceWithEndpoints, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["path"] = SerializeValue(path) + reqArgs["statusCode"] = SerializeValue(statusCode) + reqArgs["endpointName"] = SerializeValue(endpointName) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withHttpHealthCheck", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEndpoints), nil +} + +// WithCommand adds a resource command +func (s *TestRedisResource) WithCommand(name string, displayName string, executeCommand func(...any) any, commandOptions *CommandOptions) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["name"] = SerializeValue(name) + reqArgs["displayName"] = SerializeValue(displayName) + if executeCommand != nil { + reqArgs["executeCommand"] = RegisterCallback(executeCommand) + } + if commandOptions != nil { + reqArgs["commandOptions"] = SerializeValue(commandOptions) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/withCommand", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithParentRelationship sets the parent relationship +func (s *TestRedisResource) WithParentRelationship(parent *IResource) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["parent"] = SerializeValue(parent) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withParentRelationship", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithVolume adds a volume +func (s *TestRedisResource) WithVolume(target string, name string, isReadOnly bool) (*ContainerResource, error) { + reqArgs := map[string]any{ + "resource": SerializeValue(s.Handle()), + } + reqArgs["target"] = SerializeValue(target) + reqArgs["name"] = SerializeValue(name) + reqArgs["isReadOnly"] = SerializeValue(isReadOnly) + result, err := s.Client().InvokeCapability("Aspire.Hosting/withVolume", reqArgs) + if err != nil { + return nil, err + } + return result.(*ContainerResource), nil +} + +// GetResourceName gets the resource name +func (s *TestRedisResource) GetResourceName() (*string, error) { + reqArgs := map[string]any{ + "resource": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting/getResourceName", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// WithPersistence configures the Redis resource with persistence +func (s *TestRedisResource) WithPersistence(mode TestPersistenceMode) (*TestRedisResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["mode"] = SerializeValue(mode) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withPersistence", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestRedisResource), nil +} + +// WithOptionalString adds an optional string parameter +func (s *TestRedisResource) WithOptionalString(value string, enabled bool) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + reqArgs["enabled"] = SerializeValue(enabled) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalString", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithConfig configures the resource with a DTO +func (s *TestRedisResource) WithConfig(config *TestConfigDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// GetTags gets the tags for the resource +func (s *TestRedisResource) GetTags() *AspireList[string] { + if s.getTags == nil { + s.getTags = NewAspireListWithGetter[string](s.Handle(), s.Client(), "Aspire.Hosting.CodeGeneration.Go.Tests/getTags") + } + return s.getTags +} + +// GetMetadata gets the metadata for the resource +func (s *TestRedisResource) GetMetadata() *AspireDict[string, string] { + if s.getMetadata == nil { + s.getMetadata = NewAspireDictWithGetter[string, string](s.Handle(), s.Client(), "Aspire.Hosting.CodeGeneration.Go.Tests/getMetadata") + } + return s.getMetadata +} + +// WithConnectionString sets the connection string using a reference expression +func (s *TestRedisResource) WithConnectionString(connectionString *ReferenceExpression) (*IResourceWithConnectionString, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["connectionString"] = SerializeValue(connectionString) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withConnectionString", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithConnectionString), nil +} + +// TestWithEnvironmentCallback configures environment with callback (test version) +func (s *TestRedisResource) TestWithEnvironmentCallback(callback func(...any) any) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/testWithEnvironmentCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// WithCreatedAt sets the created timestamp +func (s *TestRedisResource) WithCreatedAt(createdAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["createdAt"] = SerializeValue(createdAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCreatedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithModifiedAt sets the modified timestamp +func (s *TestRedisResource) WithModifiedAt(modifiedAt string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["modifiedAt"] = SerializeValue(modifiedAt) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withModifiedAt", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithCorrelationId sets the correlation ID +func (s *TestRedisResource) WithCorrelationId(correlationId string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["correlationId"] = SerializeValue(correlationId) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCorrelationId", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithOptionalCallback configures with optional callback +func (s *TestRedisResource) WithOptionalCallback(callback func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if callback != nil { + reqArgs["callback"] = RegisterCallback(callback) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalCallback", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithStatus sets the resource status +func (s *TestRedisResource) WithStatus(status TestResourceStatus) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["status"] = SerializeValue(status) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withStatus", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithNestedConfig configures with nested DTO +func (s *TestRedisResource) WithNestedConfig(config *TestNestedDto) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["config"] = SerializeValue(config) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withNestedConfig", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithValidator adds validation callback +func (s *TestRedisResource) WithValidator(validator func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if validator != nil { + reqArgs["validator"] = RegisterCallback(validator) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withValidator", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// TestWaitFor waits for another resource (test version) +func (s *TestRedisResource) TestWaitFor(dependency *IResource) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/testWaitFor", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// GetEndpoints gets the endpoints +func (s *TestRedisResource) GetEndpoints() (*[]string, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/getEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*[]string), nil +} + +// WithConnectionStringDirect sets connection string using direct interface target +func (s *TestRedisResource) WithConnectionStringDirect(connectionString string) (*IResourceWithConnectionString, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["connectionString"] = SerializeValue(connectionString) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withConnectionStringDirect", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithConnectionString), nil +} + +// WithRedisSpecific redis-specific configuration +func (s *TestRedisResource) WithRedisSpecific(option string) (*TestRedisResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["option"] = SerializeValue(option) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withRedisSpecific", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestRedisResource), nil +} + +// WithDependency adds a dependency on another resource +func (s *TestRedisResource) WithDependency(dependency *IResourceWithConnectionString) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["dependency"] = SerializeValue(dependency) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withDependency", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithEndpoints sets the endpoints +func (s *TestRedisResource) WithEndpoints(endpoints []string) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["endpoints"] = SerializeValue(endpoints) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withEndpoints", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WithEnvironmentVariables sets environment variables +func (s *TestRedisResource) WithEnvironmentVariables(variables map[string]string) (*IResourceWithEnvironment, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["variables"] = SerializeValue(variables) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withEnvironmentVariables", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResourceWithEnvironment), nil +} + +// GetStatusAsync gets the status of the resource asynchronously +func (s *TestRedisResource) GetStatusAsync(cancellationToken *CancellationToken) (*string, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if cancellationToken != nil { + reqArgs["cancellationToken"] = RegisterCancellation(cancellationToken, s.Client()) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/getStatusAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// WithCancellableOperation performs a cancellable operation +func (s *TestRedisResource) WithCancellableOperation(operation func(...any) any) (*IResource, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + if operation != nil { + reqArgs["operation"] = RegisterCallback(operation) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/withCancellableOperation", reqArgs) + if err != nil { + return nil, err + } + return result.(*IResource), nil +} + +// WaitForReadyAsync waits for the resource to be ready +func (s *TestRedisResource) WaitForReadyAsync(timeout float64, cancellationToken *CancellationToken) (*bool, error) { + reqArgs := map[string]any{ + "builder": SerializeValue(s.Handle()), + } + reqArgs["timeout"] = SerializeValue(timeout) + if cancellationToken != nil { + reqArgs["cancellationToken"] = RegisterCancellation(cancellationToken, s.Client()) + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.Go.Tests/waitForReadyAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*bool), nil +} + +// TestResourceContext wraps a handle for Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext. +type TestResourceContext struct { + HandleWrapperBase +} + +// NewTestResourceContext creates a new TestResourceContext. +func NewTestResourceContext(handle *Handle, client *AspireClient) *TestResourceContext { + return &TestResourceContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// Name gets the Name property +func (s *TestResourceContext) Name() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.name", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetName sets the Name property +func (s *TestResourceContext) SetName(value string) (*TestResourceContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setName", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestResourceContext), nil +} + +// Value gets the Value property +func (s *TestResourceContext) Value() (*float64, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.value", reqArgs) + if err != nil { + return nil, err + } + return result.(*float64), nil +} + +// SetValue sets the Value property +func (s *TestResourceContext) SetValue(value float64) (*TestResourceContext, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValue", reqArgs) + if err != nil { + return nil, err + } + return result.(*TestResourceContext), nil +} + +// GetValueAsync invokes the GetValueAsync method +func (s *TestResourceContext) GetValueAsync() (*string, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.getValueAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*string), nil +} + +// SetValueAsync invokes the SetValueAsync method +func (s *TestResourceContext) SetValueAsync(value string) error { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + reqArgs["value"] = SerializeValue(value) + _, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValueAsync", reqArgs) + return err +} + +// ValidateAsync invokes the ValidateAsync method +func (s *TestResourceContext) ValidateAsync() (*bool, error) { + reqArgs := map[string]any{ + "context": SerializeValue(s.Handle()), + } + result, err := s.Client().InvokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.validateAsync", reqArgs) + if err != nil { + return nil, err + } + return result.(*bool), nil +} + +// UpdateCommandStateContext wraps a handle for Aspire.Hosting/Aspire.Hosting.ApplicationModel.UpdateCommandStateContext. +type UpdateCommandStateContext struct { + HandleWrapperBase +} + +// NewUpdateCommandStateContext creates a new UpdateCommandStateContext. +func NewUpdateCommandStateContext(handle *Handle, client *AspireClient) *UpdateCommandStateContext { + return &UpdateCommandStateContext{ + HandleWrapperBase: NewHandleWrapperBase(handle, client), + } +} + +// ============================================================================ +// Handle wrapper registrations +// ============================================================================ + +func init() { + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.DistributedApplication", func(h *Handle, c *AspireClient) any { + return NewDistributedApplication(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext", func(h *Handle, c *AspireClient) any { + return NewDistributedApplicationExecutionContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContextOptions", func(h *Handle, c *AspireClient) any { + return NewDistributedApplicationExecutionContextOptions(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder", func(h *Handle, c *AspireClient) any { + return NewIDistributedApplicationBuilder(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription", func(h *Handle, c *AspireClient) any { + return NewDistributedApplicationEventSubscription(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationResourceEventSubscription", func(h *Handle, c *AspireClient) any { + return NewDistributedApplicationResourceEventSubscription(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEvent", func(h *Handle, c *AspireClient) any { + return NewIDistributedApplicationEvent(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationResourceEvent", func(h *Handle, c *AspireClient) any { + return NewIDistributedApplicationResourceEvent(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEventing", func(h *Handle, c *AspireClient) any { + return NewIDistributedApplicationEventing(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.CommandLineArgsCallbackContext", func(h *Handle, c *AspireClient) any { + return NewCommandLineArgsCallbackContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReference", func(h *Handle, c *AspireClient) any { + return NewEndpointReference(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReferenceExpression", func(h *Handle, c *AspireClient) any { + return NewEndpointReferenceExpression(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.EnvironmentCallbackContext", func(h *Handle, c *AspireClient) any { + return NewEnvironmentCallbackContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.UpdateCommandStateContext", func(h *Handle, c *AspireClient) any { + return NewUpdateCommandStateContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCommandContext", func(h *Handle, c *AspireClient) any { + return NewExecuteCommandContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceUrlsCallbackContext", func(h *Handle, c *AspireClient) any { + return NewResourceUrlsCallbackContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource", func(h *Handle, c *AspireClient) any { + return NewContainerResource(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecutableResource", func(h *Handle, c *AspireClient) any { + return NewExecutableResource(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ParameterResource", func(h *Handle, c *AspireClient) any { + return NewParameterResource(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString", func(h *Handle, c *AspireClient) any { + return NewIResourceWithConnectionString(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ProjectResource", func(h *Handle, c *AspireClient) any { + return NewProjectResource(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.IResourceWithServiceDiscovery", func(h *Handle, c *AspireClient) any { + return NewIResourceWithServiceDiscovery(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource", func(h *Handle, c *AspireClient) any { + return NewIResource(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext", func(h *Handle, c *AspireClient) any { + return NewTestCallbackContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext", func(h *Handle, c *AspireClient) any { + return NewTestResourceContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext", func(h *Handle, c *AspireClient) any { + return NewTestEnvironmentContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext", func(h *Handle, c *AspireClient) any { + return NewTestCollectionContext(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource", func(h *Handle, c *AspireClient) any { + return NewTestRedisResource(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment", func(h *Handle, c *AspireClient) any { + return NewIResourceWithEnvironment(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithArgs", func(h *Handle, c *AspireClient) any { + return NewIResourceWithArgs(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints", func(h *Handle, c *AspireClient) any { + return NewIResourceWithEndpoints(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithWaitSupport", func(h *Handle, c *AspireClient) any { + return NewIResourceWithWaitSupport(h, c) + }) + RegisterHandleWrapper("Aspire.Hosting/Dict", func(h *Handle, c *AspireClient) any { + return &AspireDict[any, any]{HandleWrapperBase: NewHandleWrapperBase(h, c)} + }) + RegisterHandleWrapper("Aspire.Hosting/List", func(h *Handle, c *AspireClient) any { + return &AspireList[any]{HandleWrapperBase: NewHandleWrapperBase(h, c)} + }) + RegisterHandleWrapper("Aspire.Hosting/Dict", func(h *Handle, c *AspireClient) any { + return &AspireDict[any, any]{HandleWrapperBase: NewHandleWrapperBase(h, c)} + }) + RegisterHandleWrapper("Aspire.Hosting/List", func(h *Handle, c *AspireClient) any { + return &AspireList[any]{HandleWrapperBase: NewHandleWrapperBase(h, c)} + }) + RegisterHandleWrapper("Aspire.Hosting/List", func(h *Handle, c *AspireClient) any { + return &AspireList[any]{HandleWrapperBase: NewHandleWrapperBase(h, c)} + }) + RegisterHandleWrapper("Aspire.Hosting/Dict", func(h *Handle, c *AspireClient) any { + return &AspireDict[any, any]{HandleWrapperBase: NewHandleWrapperBase(h, c)} + }) +} + +// ============================================================================ +// Connection Helpers +// ============================================================================ + +// Connect establishes a connection to the AppHost server. +func Connect() (*AspireClient, error) { + socketPath := os.Getenv("REMOTE_APP_HOST_SOCKET_PATH") + if socketPath == "" { + return nil, fmt.Errorf("REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`") + } + client := NewAspireClient(socketPath) + if err := client.Connect(); err != nil { + return nil, err + } + client.OnDisconnect(func() { os.Exit(1) }) + return client, nil +} + +// CreateBuilder creates a new distributed application builder. +func CreateBuilder(options *CreateBuilderOptions) (*IDistributedApplicationBuilder, error) { + client, err := Connect() + if err != nil { + return nil, err + } + resolvedOptions := make(map[string]any) + if options != nil { + for k, v := range options.ToMap() { + resolvedOptions[k] = v + } + } + if _, ok := resolvedOptions["Args"]; !ok { + resolvedOptions["Args"] = os.Args[1:] + } + if _, ok := resolvedOptions["ProjectDirectory"]; !ok { + if pwd, err := os.Getwd(); err == nil { + resolvedOptions["ProjectDirectory"] = pwd + } + } + result, err := client.InvokeCapability("Aspire.Hosting/createBuilderWithOptions", map[string]any{"options": resolvedOptions}) + if err != nil { + return nil, err + } + return result.(*IDistributedApplicationBuilder), nil +} + diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/WithOptionalStringCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/WithOptionalStringCapability.verified.txt new file mode 100644 index 00000000000..3ce17af2c65 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/WithOptionalStringCapability.verified.txt @@ -0,0 +1,75 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Go.Tests/withOptionalString, + MethodName: withOptionalString, + QualifiedMethodName: withOptionalString, + Description: Adds an optional string parameter, + Parameters: [ + { + Name: value, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false + }, + { + Name: enabled, + Type: { + TypeId: boolean, + ClrType: bool, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false, + DefaultValue: true + } + ], + ReturnType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + ClrType: IResource, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + ClrType: IResource, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.WithOptionalString +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/WithPersistenceCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/WithPersistenceCapability.verified.txt new file mode 100644 index 00000000000..247a84592a4 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/WithPersistenceCapability.verified.txt @@ -0,0 +1,61 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Go.Tests/withPersistence, + MethodName: withPersistence, + QualifiedMethodName: withPersistence, + Description: Configures the Redis resource with persistence, + Parameters: [ + { + Name: mode, + Type: { + TypeId: enum:Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestPersistenceMode, + ClrType: TestPersistenceMode, + Category: Enum, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false, + DefaultValue: Volume + } + ], + ReturnType: { + TypeId: Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + TargetType: { + TypeId: Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting.CodeGeneration.Go.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.WithPersistence +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.Java.Tests.csproj b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.Java.Tests.csproj new file mode 100644 index 00000000000..5958deadea8 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.Java.Tests.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + + + + + + + + + + + + + + + + + + + diff --git a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/AtsJavaCodeGeneratorTests.cs b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/AtsJavaCodeGeneratorTests.cs new file mode 100644 index 00000000000..e3c61a6a40c --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/AtsJavaCodeGeneratorTests.cs @@ -0,0 +1,316 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Ats; +using Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes; + +namespace Aspire.Hosting.CodeGeneration.Java.Tests; + +public class AtsJavaCodeGeneratorTests +{ + private readonly AtsJavaCodeGenerator _generator = new(); + + // The test types are compiled into this assembly via Compile Include + private const string TestTypesAssemblyName = "Aspire.Hosting.CodeGeneration.Java.Tests"; + + [Fact] + public void Language_ReturnsJava() + { + Assert.Equal("Java", _generator.Language); + } + + [Fact] + public async Task GenerateDistributedApplication_WithTestTypes_GeneratesCorrectOutput() + { + // Arrange + var atsContext = CreateContextFromTestAssembly(); + + // Act + var files = _generator.GenerateDistributedApplication(atsContext); + + // Assert + Assert.Contains("Aspire.java", files.Keys); + Assert.Contains("Transport.java", files.Keys); + Assert.Contains("Base.java", files.Keys); + + await Verify(files["Aspire.java"], extension: "java") + .UseFileName("AtsGeneratedAspire"); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_IncludesCapabilities() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert that capabilities are discovered + Assert.NotEmpty(capabilities); + + // Check for specific capabilities (uses AssemblyName/methodName format) + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/withOptionalString"); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_DeriveCorrectMethodNames() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert method names are derived correctly + var addTestRedis = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Equal("addTestRedis", addTestRedis.MethodName); + + var withPersistence = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.Equal("withPersistence", withPersistence.MethodName); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_CapturesParameters() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert parameters are captured + var addTestRedis = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Equal(2, addTestRedis.Parameters.Count); + Assert.Equal("Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder", addTestRedis.TargetTypeId); + Assert.Contains(addTestRedis.Parameters, p => p.Name == "name" && p.Type?.TypeId == "string"); + Assert.Contains(addTestRedis.Parameters, p => p.Name == "port" && p.IsOptional); + } + + [Fact] + public void Scanner_ReturnsBuilder_TrueForResourceBuilderReturnTypes() + { + // Verify that ReturnsBuilder is correctly set to true for methods + // that return IResourceBuilder + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // addTestRedis returns IResourceBuilder - should have ReturnsBuilder = true + var addTestRedis = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.NotNull(addTestRedis); + Assert.True(addTestRedis.ReturnsBuilder, + "addTestRedis returns IResourceBuilder but ReturnsBuilder is false - fluent chaining won't work"); + + // withPersistence also returns IResourceBuilder + var withPersistence = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.NotNull(withPersistence); + Assert.True(withPersistence.ReturnsBuilder, + "withPersistence returns IResourceBuilder but ReturnsBuilder is false - fluent chaining won't work"); + } + + [Fact] + public async Task Scanner_AddTestRedis_HasCorrectTypeMetadata() + { + // Verify the entire capability object for addTestRedis + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var addTestRedis = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.NotNull(addTestRedis); + + await Verify(addTestRedis).UseFileName("AddTestRedisCapability"); + } + + [Fact] + public async Task Scanner_WithPersistence_HasCorrectExpandedTargets() + { + // Verify the entire capability object for withPersistence + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var withPersistence = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.NotNull(withPersistence); + + await Verify(withPersistence).UseFileName("WithPersistenceCapability"); + } + + [Fact] + public async Task Scanner_WithOptionalString_HasCorrectExpandedTargets() + { + // Verify withOptionalString (targets IResource, should expand to TestRedisResource) + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var withOptionalString = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withOptionalString"); + Assert.NotNull(withOptionalString); + + await Verify(withOptionalString).UseFileName("WithOptionalStringCapability"); + } + + [Fact] + public async Task Scanner_HostingAssembly_AddContainerCapability() + { + // Verify the addContainer capability from the real Aspire.Hosting assembly + var capabilities = ScanCapabilitiesFromHostingAssembly(); + + var addContainer = capabilities.FirstOrDefault(c => c.CapabilityId == "Aspire.Hosting/addContainer"); + Assert.NotNull(addContainer); + + await Verify(addContainer).UseFileName("HostingAddContainerCapability"); + } + + [Fact] + public void RuntimeType_ContainerResource_IsNotInterface() + { + // Verify that ContainerResource.IsInterface returns false using runtime reflection + var containerResourceType = typeof(ContainerResource); + + Assert.NotNull(containerResourceType); + Assert.False(containerResourceType.IsInterface, "ContainerResource should NOT be an interface"); + } + + [Fact] + public void TwoPassScanning_DeduplicatesCapabilities() + { + // Verify that when the same capability appears in multiple assemblies, + // ScanAssemblies deduplicates by CapabilityId. + var capabilities = ScanCapabilitiesFromBothAssemblies(); + + // Each capability ID should appear only once + var duplicates = capabilities + .GroupBy(c => c.CapabilityId) + .Where(g => g.Count() > 1) + .Select(g => g.Key) + .ToList(); + + Assert.Empty(duplicates); + } + + [Fact] + public void TwoPassScanning_MergesHandleTypesFromAllAssemblies() + { + // Verify that ScanAssemblies collects handle types from all assemblies + var result = CreateContextFromBothAssemblies(); + + // Should have types from Aspire.Hosting (ContainerResource, etc.) + var containerResourceType = result.HandleTypes + .FirstOrDefault(t => t.AtsTypeId.Contains("ContainerResource") && !t.AtsTypeId.Contains("IContainer")); + Assert.NotNull(containerResourceType); + + // Should have types from test assembly (TestRedisResource) + var testRedisType = result.HandleTypes + .FirstOrDefault(t => t.AtsTypeId.Contains("TestRedisResource")); + Assert.NotNull(testRedisType); + + // TestRedisResource should have IResourceWithEnvironment in its interfaces + // (inherited via ContainerResource) + var hasEnvironmentInterface = testRedisType.ImplementedInterfaces + .Any(i => i.TypeId.Contains("IResourceWithEnvironment")); + Assert.True(hasEnvironmentInterface, + "TestRedisResource should implement IResourceWithEnvironment via ContainerResource"); + } + + [Fact] + public async Task TwoPassScanning_GeneratesWithEnvironmentOnTestRedisBuilder() + { + // End-to-end test: verify that withEnvironment appears on TestRedisResource + // in the generated Java when using 2-pass scanning. + var atsContext = CreateContextFromBothAssemblies(); + + // Generate Java + var files = _generator.GenerateDistributedApplication(atsContext); + var aspireJava = files["Aspire.java"]; + + // Verify withEnvironment appears (method should exist for resources that support it) + Assert.Contains("withEnvironment", aspireJava); + + // Snapshot for detailed verification + await Verify(aspireJava, extension: "java") + .UseFileName("TwoPassScanningGeneratedAspire"); + } + + [Fact] + public void GeneratedCode_UsesCamelCaseMethodNames() + { + // Verify that the generated Java code uses camelCase for method names + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + var aspireJava = files["Aspire.java"]; + + // Java uses camelCase for methods + Assert.Contains("addContainer", aspireJava); + Assert.Contains("withEnvironment", aspireJava); + } + + [Fact] + public void GeneratedCode_HasCreateBuilderMethod() + { + // Verify that the generated Java code has a createBuilder method + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + var aspireJava = files["Aspire.java"]; + + Assert.Contains("createBuilder", aspireJava); + } + + [Fact] + public void GeneratedCode_HasPublicAspireClass() + { + // Verify that a public Aspire class is generated + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + var aspireJava = files["Aspire.java"]; + + Assert.Contains("public class Aspire", aspireJava); + } + + private static List ScanCapabilitiesFromTestAssembly() + { + var testAssembly = LoadTestAssembly(); + + // Scan capabilities from the test assembly + var result = AtsCapabilityScanner.ScanAssembly(testAssembly); + return result.Capabilities; + } + + private static AtsContext CreateContextFromTestAssembly() + { + var testAssembly = LoadTestAssembly(); + + // Scan capabilities from the test assembly + var result = AtsCapabilityScanner.ScanAssembly(testAssembly); + return result.ToAtsContext(); + } + + private static Assembly LoadTestAssembly() + { + // Get the test assembly at runtime (TypeScript tests assembly has the TestTypes) + return typeof(TestRedisResource).Assembly; + } + + private static List ScanCapabilitiesFromHostingAssembly() + { + var hostingAssembly = typeof(DistributedApplication).Assembly; + var result = AtsCapabilityScanner.ScanAssembly(hostingAssembly); + return result.Capabilities; + } + + private static List ScanCapabilitiesFromBothAssemblies() + { + var (testAssembly, hostingAssembly) = LoadBothAssemblies(); + + // Use ScanAssemblies for proper cross-assembly expansion + var result = AtsCapabilityScanner.ScanAssemblies([hostingAssembly, testAssembly]); + return result.Capabilities; + } + + private static AtsContext CreateContextFromBothAssemblies() + { + var (testAssembly, hostingAssembly) = LoadBothAssemblies(); + + // Use ScanAssemblies for proper cross-assembly expansion and enum collection + var result = AtsCapabilityScanner.ScanAssemblies([hostingAssembly, testAssembly]); + return result.ToAtsContext(); + } + + private static (Assembly testAssembly, Assembly hostingAssembly) LoadBothAssemblies() + { + var testAssembly = typeof(TestRedisResource).Assembly; + var hostingAssembly = typeof(DistributedApplication).Assembly; + return (testAssembly, hostingAssembly); + } +} diff --git a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AddTestRedisCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AddTestRedisCapability.verified.txt new file mode 100644 index 00000000000..80c8a0158a1 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AddTestRedisCapability.verified.txt @@ -0,0 +1,74 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Java.Tests/addTestRedis, + MethodName: addTestRedis, + QualifiedMethodName: addTestRedis, + Description: Adds a test Redis resource, + Parameters: [ + { + Name: name, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + }, + { + Name: port, + Type: { + TypeId: number, + ClrType: int, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: true, + IsCallback: false + } + ], + ReturnType: { + TypeId: Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.AddTestRedis +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AtsGeneratedAspire.verified.java b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AtsGeneratedAspire.verified.java new file mode 100644 index 00000000000..eca0a237d4d --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AtsGeneratedAspire.verified.java @@ -0,0 +1,657 @@ +// Aspire.java - Capability-based Aspire SDK +// GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +// ============================================================================ +// Enums +// ============================================================================ + +/** TestPersistenceMode enum. */ +enum TestPersistenceMode { + NONE("None"), + VOLUME("Volume"), + BIND("Bind"); + + private final String value; + + TestPersistenceMode(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static TestPersistenceMode fromValue(String value) { + for (TestPersistenceMode e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +/** TestResourceStatus enum. */ +enum TestResourceStatus { + PENDING("Pending"), + RUNNING("Running"), + STOPPED("Stopped"), + FAILED("Failed"); + + private final String value; + + TestResourceStatus(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static TestResourceStatus fromValue(String value) { + for (TestResourceStatus e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +// ============================================================================ +// DTOs +// ============================================================================ + +/** TestConfigDto DTO. */ +class TestConfigDto { + private String name; + private double port; + private boolean enabled; + private String optionalField; + + public String getName() { return name; } + public void setName(String value) { this.name = value; } + public double getPort() { return port; } + public void setPort(double value) { this.port = value; } + public boolean getEnabled() { return enabled; } + public void setEnabled(boolean value) { this.enabled = value; } + public String getOptionalField() { return optionalField; } + public void setOptionalField(String value) { this.optionalField = value; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Name", AspireClient.serializeValue(name)); + map.put("Port", AspireClient.serializeValue(port)); + map.put("Enabled", AspireClient.serializeValue(enabled)); + map.put("OptionalField", AspireClient.serializeValue(optionalField)); + return map; + } +} + +/** TestNestedDto DTO. */ +class TestNestedDto { + private String id; + private TestConfigDto config; + private AspireList tags; + private AspireDict counts; + + public String getId() { return id; } + public void setId(String value) { this.id = value; } + public TestConfigDto getConfig() { return config; } + public void setConfig(TestConfigDto value) { this.config = value; } + public AspireList getTags() { return tags; } + public void setTags(AspireList value) { this.tags = value; } + public AspireDict getCounts() { return counts; } + public void setCounts(AspireDict value) { this.counts = value; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Id", AspireClient.serializeValue(id)); + map.put("Config", AspireClient.serializeValue(config)); + map.put("Tags", AspireClient.serializeValue(tags)); + map.put("Counts", AspireClient.serializeValue(counts)); + return map; + } +} + +/** TestDeeplyNestedDto DTO. */ +class TestDeeplyNestedDto { + private AspireDict> nestedData; + private AspireDict[] metadataArray; + + public AspireDict> getNestedData() { return nestedData; } + public void setNestedData(AspireDict> value) { this.nestedData = value; } + public AspireDict[] getMetadataArray() { return metadataArray; } + public void setMetadataArray(AspireDict[] value) { this.metadataArray = value; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("NestedData", AspireClient.serializeValue(nestedData)); + map.put("MetadataArray", AspireClient.serializeValue(metadataArray)); + return map; + } +} + +// ============================================================================ +// Handle Wrappers +// ============================================================================ + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder. */ +class IDistributedApplicationBuilder extends HandleWrapperBase { + IDistributedApplicationBuilder(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Adds a test Redis resource */ + public TestRedisResource addTestRedis(String name, Double port) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + return (TestRedisResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/addTestRedis", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource. */ +class IResource extends ResourceBuilderBase { + IResource(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString. */ +class IResourceWithConnectionString extends ResourceBuilderBase { + IResourceWithConnectionString(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment. */ +class IResourceWithEnvironment extends HandleWrapperBase { + IResourceWithEnvironment(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext. */ +class TestCallbackContext extends HandleWrapperBase { + TestCallbackContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Name property */ + public String name() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.name", reqArgs); + } + + /** Sets the Name property */ + public TestCallbackContext setName(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestCallbackContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setName", reqArgs); + } + + /** Gets the Value property */ + public double value() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (double) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.value", reqArgs); + } + + /** Sets the Value property */ + public TestCallbackContext setValue(double value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestCallbackContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setValue", reqArgs); + } + + /** Gets the CancellationToken property */ + public CancellationToken cancellationToken() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (CancellationToken) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.cancellationToken", reqArgs); + } + + /** Sets the CancellationToken property */ + public TestCallbackContext setCancellationToken(CancellationToken value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + if (value != null) { + reqArgs.put("value", getClient().registerCancellation(value)); + } + return (TestCallbackContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setCancellationToken", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext. */ +class TestCollectionContext extends HandleWrapperBase { + TestCollectionContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Items property */ + private AspireList itemsField; + public AspireList items() { + if (itemsField == null) { + itemsField = new AspireList<>(getHandle(), getClient(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.items"); + } + return itemsField; + } + + /** Gets the Metadata property */ + private AspireDict metadataField; + public AspireDict metadata() { + if (metadataField == null) { + metadataField = new AspireDict<>(getHandle(), getClient(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.metadata"); + } + return metadataField; + } + +} + +/** Wrapper for Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext. */ +class TestEnvironmentContext extends HandleWrapperBase { + TestEnvironmentContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Name property */ + public String name() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.name", reqArgs); + } + + /** Sets the Name property */ + public TestEnvironmentContext setName(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestEnvironmentContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setName", reqArgs); + } + + /** Gets the Description property */ + public String description() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.description", reqArgs); + } + + /** Sets the Description property */ + public TestEnvironmentContext setDescription(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestEnvironmentContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setDescription", reqArgs); + } + + /** Gets the Priority property */ + public double priority() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (double) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.priority", reqArgs); + } + + /** Sets the Priority property */ + public TestEnvironmentContext setPriority(double value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestEnvironmentContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setPriority", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource. */ +class TestRedisResource extends ResourceBuilderBase { + TestRedisResource(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Configures the Redis resource with persistence */ + public TestRedisResource withPersistence(TestPersistenceMode mode) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (mode != null) { + reqArgs.put("mode", AspireClient.serializeValue(mode)); + } + return (TestRedisResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withPersistence", reqArgs); + } + + /** Adds an optional string parameter */ + public IResource withOptionalString(String value, Boolean enabled) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (value != null) { + reqArgs.put("value", AspireClient.serializeValue(value)); + } + if (enabled != null) { + reqArgs.put("enabled", AspireClient.serializeValue(enabled)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalString", reqArgs); + } + + /** Configures the resource with a DTO */ + public IResource withConfig(TestConfigDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withConfig", reqArgs); + } + + /** Gets the tags for the resource */ + private AspireList getTagsField; + public AspireList getTags() { + if (getTagsField == null) { + getTagsField = new AspireList<>(getHandle(), getClient(), "Aspire.Hosting.CodeGeneration.Java.Tests/getTags"); + } + return getTagsField; + } + + /** Gets the metadata for the resource */ + private AspireDict getMetadataField; + public AspireDict getMetadata() { + if (getMetadataField == null) { + getMetadataField = new AspireDict<>(getHandle(), getClient(), "Aspire.Hosting.CodeGeneration.Java.Tests/getMetadata"); + } + return getMetadataField; + } + + /** Sets the connection string using a reference expression */ + public IResourceWithConnectionString withConnectionString(ReferenceExpression connectionString) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("connectionString", AspireClient.serializeValue(connectionString)); + return (IResourceWithConnectionString) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withConnectionString", reqArgs); + } + + /** Configures environment with callback (test version) */ + public IResourceWithEnvironment testWithEnvironmentCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/testWithEnvironmentCallback", reqArgs); + } + + /** Sets the created timestamp */ + public IResource withCreatedAt(String createdAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("createdAt", AspireClient.serializeValue(createdAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCreatedAt", reqArgs); + } + + /** Sets the modified timestamp */ + public IResource withModifiedAt(String modifiedAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("modifiedAt", AspireClient.serializeValue(modifiedAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withModifiedAt", reqArgs); + } + + /** Sets the correlation ID */ + public IResource withCorrelationId(String correlationId) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("correlationId", AspireClient.serializeValue(correlationId)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCorrelationId", reqArgs); + } + + /** Configures with optional callback */ + public IResource withOptionalCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalCallback", reqArgs); + } + + /** Sets the resource status */ + public IResource withStatus(TestResourceStatus status) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("status", AspireClient.serializeValue(status)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withStatus", reqArgs); + } + + /** Configures with nested DTO */ + public IResource withNestedConfig(TestNestedDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withNestedConfig", reqArgs); + } + + /** Adds validation callback */ + public IResource withValidator(Function validator) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (validator != null) { + reqArgs.put("validator", getClient().registerCallback(validator)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withValidator", reqArgs); + } + + /** Waits for another resource (test version) */ + public IResource testWaitFor(IResource dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/testWaitFor", reqArgs); + } + + /** Gets the endpoints */ + public String[] getEndpoints() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (String[]) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/getEndpoints", reqArgs); + } + + /** Sets connection string using direct interface target */ + public IResourceWithConnectionString withConnectionStringDirect(String connectionString) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("connectionString", AspireClient.serializeValue(connectionString)); + return (IResourceWithConnectionString) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withConnectionStringDirect", reqArgs); + } + + /** Redis-specific configuration */ + public TestRedisResource withRedisSpecific(String option) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("option", AspireClient.serializeValue(option)); + return (TestRedisResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withRedisSpecific", reqArgs); + } + + /** Adds a dependency on another resource */ + public IResource withDependency(IResourceWithConnectionString dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withDependency", reqArgs); + } + + /** Sets the endpoints */ + public IResource withEndpoints(String[] endpoints) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpoints", AspireClient.serializeValue(endpoints)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withEndpoints", reqArgs); + } + + /** Sets environment variables */ + public IResourceWithEnvironment withEnvironmentVariables(Map variables) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("variables", AspireClient.serializeValue(variables)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withEnvironmentVariables", reqArgs); + } + + /** Gets the status of the resource asynchronously */ + public String getStatusAsync(CancellationToken cancellationToken) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (cancellationToken != null) { + reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); + } + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/getStatusAsync", reqArgs); + } + + /** Performs a cancellable operation */ + public IResource withCancellableOperation(Function operation) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (operation != null) { + reqArgs.put("operation", getClient().registerCallback(operation)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCancellableOperation", reqArgs); + } + + /** Waits for the resource to be ready */ + public boolean waitForReadyAsync(double timeout, CancellationToken cancellationToken) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("timeout", AspireClient.serializeValue(timeout)); + if (cancellationToken != null) { + reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); + } + return (boolean) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/waitForReadyAsync", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext. */ +class TestResourceContext extends HandleWrapperBase { + TestResourceContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Name property */ + public String name() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.name", reqArgs); + } + + /** Sets the Name property */ + public TestResourceContext setName(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestResourceContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setName", reqArgs); + } + + /** Gets the Value property */ + public double value() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (double) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.value", reqArgs); + } + + /** Sets the Value property */ + public TestResourceContext setValue(double value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestResourceContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValue", reqArgs); + } + + /** Invokes the GetValueAsync method */ + public String getValueAsync() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.getValueAsync", reqArgs); + } + + /** Invokes the SetValueAsync method */ + public void setValueAsync(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValueAsync", reqArgs); + } + + /** Invokes the ValidateAsync method */ + public boolean validateAsync() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (boolean) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.validateAsync", reqArgs); + } + +} + +// ============================================================================ +// Handle wrapper registrations +// ============================================================================ + +/** Static initializer to register handle wrappers. */ +class AspireRegistrations { + static { + AspireClient.registerHandleWrapper("Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext", (h, c) -> new TestCallbackContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext", (h, c) -> new TestResourceContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext", (h, c) -> new TestEnvironmentContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext", (h, c) -> new TestCollectionContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource", (h, c) -> new TestRedisResource(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource", (h, c) -> new IResource(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString", (h, c) -> new IResourceWithConnectionString(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder", (h, c) -> new IDistributedApplicationBuilder(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment", (h, c) -> new IResourceWithEnvironment(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/List", (h, c) -> new AspireList(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Dict", (h, c) -> new AspireDict(h, c)); + } + + static void ensureRegistered() { + // Called to trigger static initializer + } +} + +// ============================================================================ +// Connection Helpers +// ============================================================================ + +/** Main entry point for Aspire SDK. */ +public class Aspire { + /** Connect to the AppHost server. */ + public static AspireClient connect() throws Exception { + AspireRegistrations.ensureRegistered(); + String socketPath = System.getenv("REMOTE_APP_HOST_SOCKET_PATH"); + if (socketPath == null || socketPath.isEmpty()) { + throw new RuntimeException("REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`."); + } + AspireClient client = new AspireClient(socketPath); + client.connect(); + client.onDisconnect(() -> System.exit(1)); + return client; + } + + /** Create a new distributed application builder. */ + public static IDistributedApplicationBuilder createBuilder(CreateBuilderOptions options) throws Exception { + AspireClient client = connect(); + Map resolvedOptions = new HashMap<>(); + if (options != null) { + resolvedOptions.putAll(options.toMap()); + } + if (!resolvedOptions.containsKey("Args")) { + // Note: Java doesn't have easy access to command line args from here + resolvedOptions.put("Args", new String[0]); + } + if (!resolvedOptions.containsKey("ProjectDirectory")) { + resolvedOptions.put("ProjectDirectory", System.getProperty("user.dir")); + } + Map args = new HashMap<>(); + args.put("options", resolvedOptions); + return (IDistributedApplicationBuilder) client.invokeCapability("Aspire.Hosting/createBuilderWithOptions", args); + } +} + diff --git a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/HostingAddContainerCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/HostingAddContainerCapability.verified.txt new file mode 100644 index 00000000000..ba9342ec73c --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/HostingAddContainerCapability.verified.txt @@ -0,0 +1,74 @@ +{ + CapabilityId: Aspire.Hosting/addContainer, + MethodName: addContainer, + QualifiedMethodName: addContainer, + Description: Adds a container resource, + Parameters: [ + { + Name: name, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + }, + { + Name: image, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + } + ], + ReturnType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + ClrType: ContainerResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.ContainerResourceBuilderExtensions.AddContainer +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java new file mode 100644 index 00000000000..b52d228294d --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java @@ -0,0 +1,3622 @@ +// Aspire.java - Capability-based Aspire SDK +// GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +// ============================================================================ +// Enums +// ============================================================================ + +/** ContainerLifetime enum. */ +enum ContainerLifetime { + SESSION("Session"), + PERSISTENT("Persistent"); + + private final String value; + + ContainerLifetime(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static ContainerLifetime fromValue(String value) { + for (ContainerLifetime e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +/** ImagePullPolicy enum. */ +enum ImagePullPolicy { + DEFAULT("Default"), + ALWAYS("Always"), + MISSING("Missing"), + NEVER("Never"); + + private final String value; + + ImagePullPolicy(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static ImagePullPolicy fromValue(String value) { + for (ImagePullPolicy e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +/** DistributedApplicationOperation enum. */ +enum DistributedApplicationOperation { + RUN("Run"), + PUBLISH("Publish"); + + private final String value; + + DistributedApplicationOperation(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static DistributedApplicationOperation fromValue(String value) { + for (DistributedApplicationOperation e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +/** ProtocolType enum. */ +enum ProtocolType { + IP("IP"), + IPV6_HOP_BY_HOP_OPTIONS("IPv6HopByHopOptions"), + UNSPECIFIED("Unspecified"), + ICMP("Icmp"), + IGMP("Igmp"), + GGP("Ggp"), + IPV4("IPv4"), + TCP("Tcp"), + PUP("Pup"), + UDP("Udp"), + IDP("Idp"), + IPV6("IPv6"), + IPV6_ROUTING_HEADER("IPv6RoutingHeader"), + IPV6_FRAGMENT_HEADER("IPv6FragmentHeader"), + IPSEC_ENCAPSULATING_SECURITY_PAYLOAD("IPSecEncapsulatingSecurityPayload"), + IPSEC_AUTHENTICATION_HEADER("IPSecAuthenticationHeader"), + ICMP_V6("IcmpV6"), + IPV6_NO_NEXT_HEADER("IPv6NoNextHeader"), + IPV6_DESTINATION_OPTIONS("IPv6DestinationOptions"), + ND("ND"), + RAW("Raw"), + IPX("Ipx"), + SPX("Spx"), + SPX_II("SpxII"), + UNKNOWN("Unknown"); + + private final String value; + + ProtocolType(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static ProtocolType fromValue(String value) { + for (ProtocolType e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +/** EndpointProperty enum. */ +enum EndpointProperty { + URL("Url"), + HOST("Host"), + IPV4_HOST("IPV4Host"), + PORT("Port"), + SCHEME("Scheme"), + TARGET_PORT("TargetPort"), + HOST_AND_PORT("HostAndPort"); + + private final String value; + + EndpointProperty(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static EndpointProperty fromValue(String value) { + for (EndpointProperty e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +/** IconVariant enum. */ +enum IconVariant { + REGULAR("Regular"), + FILLED("Filled"); + + private final String value; + + IconVariant(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static IconVariant fromValue(String value) { + for (IconVariant e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +/** UrlDisplayLocation enum. */ +enum UrlDisplayLocation { + SUMMARY_AND_DETAILS("SummaryAndDetails"), + DETAILS_ONLY("DetailsOnly"); + + private final String value; + + UrlDisplayLocation(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static UrlDisplayLocation fromValue(String value) { + for (UrlDisplayLocation e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +/** TestPersistenceMode enum. */ +enum TestPersistenceMode { + NONE("None"), + VOLUME("Volume"), + BIND("Bind"); + + private final String value; + + TestPersistenceMode(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static TestPersistenceMode fromValue(String value) { + for (TestPersistenceMode e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +/** TestResourceStatus enum. */ +enum TestResourceStatus { + PENDING("Pending"), + RUNNING("Running"), + STOPPED("Stopped"), + FAILED("Failed"); + + private final String value; + + TestResourceStatus(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static TestResourceStatus fromValue(String value) { + for (TestResourceStatus e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +// ============================================================================ +// DTOs +// ============================================================================ + +/** CreateBuilderOptions DTO. */ +class CreateBuilderOptions { + private String[] args; + private String projectDirectory; + private String appHostFilePath; + private String containerRegistryOverride; + private boolean disableDashboard; + private String dashboardApplicationName; + private boolean allowUnsecuredTransport; + private boolean enableResourceLogging; + + public String[] getArgs() { return args; } + public void setArgs(String[] value) { this.args = value; } + public String getProjectDirectory() { return projectDirectory; } + public void setProjectDirectory(String value) { this.projectDirectory = value; } + public String getAppHostFilePath() { return appHostFilePath; } + public void setAppHostFilePath(String value) { this.appHostFilePath = value; } + public String getContainerRegistryOverride() { return containerRegistryOverride; } + public void setContainerRegistryOverride(String value) { this.containerRegistryOverride = value; } + public boolean getDisableDashboard() { return disableDashboard; } + public void setDisableDashboard(boolean value) { this.disableDashboard = value; } + public String getDashboardApplicationName() { return dashboardApplicationName; } + public void setDashboardApplicationName(String value) { this.dashboardApplicationName = value; } + public boolean getAllowUnsecuredTransport() { return allowUnsecuredTransport; } + public void setAllowUnsecuredTransport(boolean value) { this.allowUnsecuredTransport = value; } + public boolean getEnableResourceLogging() { return enableResourceLogging; } + public void setEnableResourceLogging(boolean value) { this.enableResourceLogging = value; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Args", AspireClient.serializeValue(args)); + map.put("ProjectDirectory", AspireClient.serializeValue(projectDirectory)); + map.put("AppHostFilePath", AspireClient.serializeValue(appHostFilePath)); + map.put("ContainerRegistryOverride", AspireClient.serializeValue(containerRegistryOverride)); + map.put("DisableDashboard", AspireClient.serializeValue(disableDashboard)); + map.put("DashboardApplicationName", AspireClient.serializeValue(dashboardApplicationName)); + map.put("AllowUnsecuredTransport", AspireClient.serializeValue(allowUnsecuredTransport)); + map.put("EnableResourceLogging", AspireClient.serializeValue(enableResourceLogging)); + return map; + } +} + +/** ResourceEventDto DTO. */ +class ResourceEventDto { + private String resourceName; + private String resourceId; + private String state; + private String stateStyle; + private String healthStatus; + private double exitCode; + + public String getResourceName() { return resourceName; } + public void setResourceName(String value) { this.resourceName = value; } + public String getResourceId() { return resourceId; } + public void setResourceId(String value) { this.resourceId = value; } + public String getState() { return state; } + public void setState(String value) { this.state = value; } + public String getStateStyle() { return stateStyle; } + public void setStateStyle(String value) { this.stateStyle = value; } + public String getHealthStatus() { return healthStatus; } + public void setHealthStatus(String value) { this.healthStatus = value; } + public double getExitCode() { return exitCode; } + public void setExitCode(double value) { this.exitCode = value; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("ResourceName", AspireClient.serializeValue(resourceName)); + map.put("ResourceId", AspireClient.serializeValue(resourceId)); + map.put("State", AspireClient.serializeValue(state)); + map.put("StateStyle", AspireClient.serializeValue(stateStyle)); + map.put("HealthStatus", AspireClient.serializeValue(healthStatus)); + map.put("ExitCode", AspireClient.serializeValue(exitCode)); + return map; + } +} + +/** CommandOptions DTO. */ +class CommandOptions { + private String description; + private Object parameter; + private String confirmationMessage; + private String iconName; + private IconVariant iconVariant; + private boolean isHighlighted; + private Object updateState; + + public String getDescription() { return description; } + public void setDescription(String value) { this.description = value; } + public Object getParameter() { return parameter; } + public void setParameter(Object value) { this.parameter = value; } + public String getConfirmationMessage() { return confirmationMessage; } + public void setConfirmationMessage(String value) { this.confirmationMessage = value; } + public String getIconName() { return iconName; } + public void setIconName(String value) { this.iconName = value; } + public IconVariant getIconVariant() { return iconVariant; } + public void setIconVariant(IconVariant value) { this.iconVariant = value; } + public boolean getIsHighlighted() { return isHighlighted; } + public void setIsHighlighted(boolean value) { this.isHighlighted = value; } + public Object getUpdateState() { return updateState; } + public void setUpdateState(Object value) { this.updateState = value; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Description", AspireClient.serializeValue(description)); + map.put("Parameter", AspireClient.serializeValue(parameter)); + map.put("ConfirmationMessage", AspireClient.serializeValue(confirmationMessage)); + map.put("IconName", AspireClient.serializeValue(iconName)); + map.put("IconVariant", AspireClient.serializeValue(iconVariant)); + map.put("IsHighlighted", AspireClient.serializeValue(isHighlighted)); + map.put("UpdateState", AspireClient.serializeValue(updateState)); + return map; + } +} + +/** ExecuteCommandResult DTO. */ +class ExecuteCommandResult { + private boolean success; + private boolean canceled; + private String errorMessage; + + public boolean getSuccess() { return success; } + public void setSuccess(boolean value) { this.success = value; } + public boolean getCanceled() { return canceled; } + public void setCanceled(boolean value) { this.canceled = value; } + public String getErrorMessage() { return errorMessage; } + public void setErrorMessage(String value) { this.errorMessage = value; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Success", AspireClient.serializeValue(success)); + map.put("Canceled", AspireClient.serializeValue(canceled)); + map.put("ErrorMessage", AspireClient.serializeValue(errorMessage)); + return map; + } +} + +/** ResourceUrlAnnotation DTO. */ +class ResourceUrlAnnotation { + private String url; + private String displayText; + private EndpointReference endpoint; + private UrlDisplayLocation displayLocation; + + public String getUrl() { return url; } + public void setUrl(String value) { this.url = value; } + public String getDisplayText() { return displayText; } + public void setDisplayText(String value) { this.displayText = value; } + public EndpointReference getEndpoint() { return endpoint; } + public void setEndpoint(EndpointReference value) { this.endpoint = value; } + public UrlDisplayLocation getDisplayLocation() { return displayLocation; } + public void setDisplayLocation(UrlDisplayLocation value) { this.displayLocation = value; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Url", AspireClient.serializeValue(url)); + map.put("DisplayText", AspireClient.serializeValue(displayText)); + map.put("Endpoint", AspireClient.serializeValue(endpoint)); + map.put("DisplayLocation", AspireClient.serializeValue(displayLocation)); + return map; + } +} + +/** TestConfigDto DTO. */ +class TestConfigDto { + private String name; + private double port; + private boolean enabled; + private String optionalField; + + public String getName() { return name; } + public void setName(String value) { this.name = value; } + public double getPort() { return port; } + public void setPort(double value) { this.port = value; } + public boolean getEnabled() { return enabled; } + public void setEnabled(boolean value) { this.enabled = value; } + public String getOptionalField() { return optionalField; } + public void setOptionalField(String value) { this.optionalField = value; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Name", AspireClient.serializeValue(name)); + map.put("Port", AspireClient.serializeValue(port)); + map.put("Enabled", AspireClient.serializeValue(enabled)); + map.put("OptionalField", AspireClient.serializeValue(optionalField)); + return map; + } +} + +/** TestNestedDto DTO. */ +class TestNestedDto { + private String id; + private TestConfigDto config; + private AspireList tags; + private AspireDict counts; + + public String getId() { return id; } + public void setId(String value) { this.id = value; } + public TestConfigDto getConfig() { return config; } + public void setConfig(TestConfigDto value) { this.config = value; } + public AspireList getTags() { return tags; } + public void setTags(AspireList value) { this.tags = value; } + public AspireDict getCounts() { return counts; } + public void setCounts(AspireDict value) { this.counts = value; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Id", AspireClient.serializeValue(id)); + map.put("Config", AspireClient.serializeValue(config)); + map.put("Tags", AspireClient.serializeValue(tags)); + map.put("Counts", AspireClient.serializeValue(counts)); + return map; + } +} + +/** TestDeeplyNestedDto DTO. */ +class TestDeeplyNestedDto { + private AspireDict> nestedData; + private AspireDict[] metadataArray; + + public AspireDict> getNestedData() { return nestedData; } + public void setNestedData(AspireDict> value) { this.nestedData = value; } + public AspireDict[] getMetadataArray() { return metadataArray; } + public void setMetadataArray(AspireDict[] value) { this.metadataArray = value; } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("NestedData", AspireClient.serializeValue(nestedData)); + map.put("MetadataArray", AspireClient.serializeValue(metadataArray)); + return map; + } +} + +// ============================================================================ +// Handle Wrappers +// ============================================================================ + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.CommandLineArgsCallbackContext. */ +class CommandLineArgsCallbackContext extends HandleWrapperBase { + CommandLineArgsCallbackContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Args property */ + private AspireList argsField; + public AspireList args() { + if (argsField == null) { + argsField = new AspireList<>(getHandle(), getClient(), "Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.args"); + } + return argsField; + } + + /** Gets the CancellationToken property */ + public CancellationToken cancellationToken() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (CancellationToken) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.cancellationToken", reqArgs); + } + + /** Gets the ExecutionContext property */ + public DistributedApplicationExecutionContext executionContext() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (DistributedApplicationExecutionContext) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.executionContext", reqArgs); + } + + /** Sets the ExecutionContext property */ + public CommandLineArgsCallbackContext setExecutionContext(DistributedApplicationExecutionContext value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (CommandLineArgsCallbackContext) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.setExecutionContext", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource. */ +class ContainerResource extends ResourceBuilderBase { + ContainerResource(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Sets an environment variable */ + public IResourceWithEnvironment withEnvironment(String name, String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironment", reqArgs); + } + + /** Adds an environment variable with a reference expression */ + public IResourceWithEnvironment withEnvironmentExpression(String name, ReferenceExpression value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentExpression", reqArgs); + } + + /** Sets environment variables via callback */ + public IResourceWithEnvironment withEnvironmentCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentCallback", reqArgs); + } + + /** Sets environment variables via async callback */ + public IResourceWithEnvironment withEnvironmentCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentCallbackAsync", reqArgs); + } + + /** Adds arguments */ + public IResourceWithArgs withArgs(String[] args) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("args", AspireClient.serializeValue(args)); + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgs", reqArgs); + } + + /** Sets command-line arguments via callback */ + public IResourceWithArgs withArgsCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgsCallback", reqArgs); + } + + /** Sets command-line arguments via async callback */ + public IResourceWithArgs withArgsCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgsCallbackAsync", reqArgs); + } + + /** Adds a reference to another resource */ + public IResourceWithEnvironment withReference(IResourceWithConnectionString source, String connectionName, Boolean optional) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("source", AspireClient.serializeValue(source)); + if (connectionName != null) { + reqArgs.put("connectionName", AspireClient.serializeValue(connectionName)); + } + if (optional != null) { + reqArgs.put("optional", AspireClient.serializeValue(optional)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withReference", reqArgs); + } + + /** Adds a service discovery reference to another resource */ + public IResourceWithEnvironment withServiceReference(IResourceWithServiceDiscovery source) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("source", AspireClient.serializeValue(source)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withServiceReference", reqArgs); + } + + /** Adds a network endpoint */ + public IResourceWithEndpoints withEndpoint(Double port, Double targetPort, String scheme, String name, String env, Boolean isProxied, Boolean isExternal, ProtocolType protocol) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (scheme != null) { + reqArgs.put("scheme", AspireClient.serializeValue(scheme)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + if (isExternal != null) { + reqArgs.put("isExternal", AspireClient.serializeValue(isExternal)); + } + if (protocol != null) { + reqArgs.put("protocol", AspireClient.serializeValue(protocol)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withEndpoint", reqArgs); + } + + /** Adds an HTTP endpoint */ + public IResourceWithEndpoints withHttpEndpoint(Double port, Double targetPort, String name, String env, Boolean isProxied) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpEndpoint", reqArgs); + } + + /** Adds an HTTPS endpoint */ + public IResourceWithEndpoints withHttpsEndpoint(Double port, Double targetPort, String name, String env, Boolean isProxied) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpsEndpoint", reqArgs); + } + + /** Makes HTTP endpoints externally accessible */ + public IResourceWithEndpoints withExternalHttpEndpoints() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withExternalHttpEndpoints", reqArgs); + } + + /** Gets an endpoint reference */ + public EndpointReference getEndpoint(String name) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + return (EndpointReference) getClient().invokeCapability("Aspire.Hosting/getEndpoint", reqArgs); + } + + /** Configures resource for HTTP/2 */ + public IResourceWithEndpoints asHttp2Service() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/asHttp2Service", reqArgs); + } + + /** Customizes displayed URLs via callback */ + public IResource withUrlsCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlsCallback", reqArgs); + } + + /** Customizes displayed URLs via async callback */ + public IResource withUrlsCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlsCallbackAsync", reqArgs); + } + + /** Adds or modifies displayed URLs */ + public IResource withUrl(String url, String displayText) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("url", AspireClient.serializeValue(url)); + if (displayText != null) { + reqArgs.put("displayText", AspireClient.serializeValue(displayText)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrl", reqArgs); + } + + /** Adds a URL using a reference expression */ + public IResource withUrlExpression(ReferenceExpression url, String displayText) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("url", AspireClient.serializeValue(url)); + if (displayText != null) { + reqArgs.put("displayText", AspireClient.serializeValue(displayText)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlExpression", reqArgs); + } + + /** Customizes the URL for a specific endpoint via callback */ + public IResource withUrlForEndpoint(String endpointName, Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlForEndpoint", reqArgs); + } + + /** Adds a URL for a specific endpoint via factory callback */ + public IResourceWithEndpoints withUrlForEndpointFactory(String endpointName, Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withUrlForEndpointFactory", reqArgs); + } + + /** Waits for another resource to be ready */ + public IResourceWithWaitSupport waitFor(IResource dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResourceWithWaitSupport) getClient().invokeCapability("Aspire.Hosting/waitFor", reqArgs); + } + + /** Prevents resource from starting automatically */ + public IResource withExplicitStart() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withExplicitStart", reqArgs); + } + + /** Waits for resource completion */ + public IResourceWithWaitSupport waitForCompletion(IResource dependency, Double exitCode) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + if (exitCode != null) { + reqArgs.put("exitCode", AspireClient.serializeValue(exitCode)); + } + return (IResourceWithWaitSupport) getClient().invokeCapability("Aspire.Hosting/waitForCompletion", reqArgs); + } + + /** Adds a health check by key */ + public IResource withHealthCheck(String key) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("key", AspireClient.serializeValue(key)); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withHealthCheck", reqArgs); + } + + /** Adds an HTTP health check */ + public IResourceWithEndpoints withHttpHealthCheck(String path, Double statusCode, String endpointName) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (path != null) { + reqArgs.put("path", AspireClient.serializeValue(path)); + } + if (statusCode != null) { + reqArgs.put("statusCode", AspireClient.serializeValue(statusCode)); + } + if (endpointName != null) { + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpHealthCheck", reqArgs); + } + + /** Adds a resource command */ + public IResource withCommand(String name, String displayName, Function executeCommand, CommandOptions commandOptions) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("displayName", AspireClient.serializeValue(displayName)); + if (executeCommand != null) { + reqArgs.put("executeCommand", getClient().registerCallback(executeCommand)); + } + if (commandOptions != null) { + reqArgs.put("commandOptions", AspireClient.serializeValue(commandOptions)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withCommand", reqArgs); + } + + /** Sets the parent relationship */ + public IResource withParentRelationship(IResource parent) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("parent", AspireClient.serializeValue(parent)); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withParentRelationship", reqArgs); + } + + /** Gets the resource name */ + public String getResourceName() { + Map reqArgs = new HashMap<>(); + reqArgs.put("resource", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting/getResourceName", reqArgs); + } + + /** Adds an optional string parameter */ + public IResource withOptionalString(String value, Boolean enabled) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (value != null) { + reqArgs.put("value", AspireClient.serializeValue(value)); + } + if (enabled != null) { + reqArgs.put("enabled", AspireClient.serializeValue(enabled)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalString", reqArgs); + } + + /** Configures the resource with a DTO */ + public IResource withConfig(TestConfigDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withConfig", reqArgs); + } + + /** Configures environment with callback (test version) */ + public IResourceWithEnvironment testWithEnvironmentCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/testWithEnvironmentCallback", reqArgs); + } + + /** Sets the created timestamp */ + public IResource withCreatedAt(String createdAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("createdAt", AspireClient.serializeValue(createdAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCreatedAt", reqArgs); + } + + /** Sets the modified timestamp */ + public IResource withModifiedAt(String modifiedAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("modifiedAt", AspireClient.serializeValue(modifiedAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withModifiedAt", reqArgs); + } + + /** Sets the correlation ID */ + public IResource withCorrelationId(String correlationId) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("correlationId", AspireClient.serializeValue(correlationId)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCorrelationId", reqArgs); + } + + /** Configures with optional callback */ + public IResource withOptionalCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalCallback", reqArgs); + } + + /** Sets the resource status */ + public IResource withStatus(TestResourceStatus status) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("status", AspireClient.serializeValue(status)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withStatus", reqArgs); + } + + /** Configures with nested DTO */ + public IResource withNestedConfig(TestNestedDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withNestedConfig", reqArgs); + } + + /** Adds validation callback */ + public IResource withValidator(Function validator) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (validator != null) { + reqArgs.put("validator", getClient().registerCallback(validator)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withValidator", reqArgs); + } + + /** Waits for another resource (test version) */ + public IResource testWaitFor(IResource dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/testWaitFor", reqArgs); + } + + /** Adds a dependency on another resource */ + public IResource withDependency(IResourceWithConnectionString dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withDependency", reqArgs); + } + + /** Sets the endpoints */ + public IResource withEndpoints(String[] endpoints) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpoints", AspireClient.serializeValue(endpoints)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withEndpoints", reqArgs); + } + + /** Sets environment variables */ + public IResourceWithEnvironment withEnvironmentVariables(Map variables) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("variables", AspireClient.serializeValue(variables)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withEnvironmentVariables", reqArgs); + } + + /** Performs a cancellable operation */ + public IResource withCancellableOperation(Function operation) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (operation != null) { + reqArgs.put("operation", getClient().registerCallback(operation)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCancellableOperation", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.DistributedApplication. */ +class DistributedApplication extends HandleWrapperBase { + DistributedApplication(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Runs the distributed application */ + public void run(CancellationToken cancellationToken) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + if (cancellationToken != null) { + reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); + } + getClient().invokeCapability("Aspire.Hosting/run", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription. */ +class DistributedApplicationEventSubscription extends HandleWrapperBase { + DistributedApplicationEventSubscription(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext. */ +class DistributedApplicationExecutionContext extends HandleWrapperBase { + DistributedApplicationExecutionContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the PublisherName property */ + public String publisherName() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting/DistributedApplicationExecutionContext.publisherName", reqArgs); + } + + /** Sets the PublisherName property */ + public DistributedApplicationExecutionContext setPublisherName(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (DistributedApplicationExecutionContext) getClient().invokeCapability("Aspire.Hosting/DistributedApplicationExecutionContext.setPublisherName", reqArgs); + } + + /** Gets the Operation property */ + public DistributedApplicationOperation operation() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (DistributedApplicationOperation) getClient().invokeCapability("Aspire.Hosting/DistributedApplicationExecutionContext.operation", reqArgs); + } + + /** Gets the IsPublishMode property */ + public boolean isPublishMode() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (boolean) getClient().invokeCapability("Aspire.Hosting/DistributedApplicationExecutionContext.isPublishMode", reqArgs); + } + + /** Gets the IsRunMode property */ + public boolean isRunMode() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (boolean) getClient().invokeCapability("Aspire.Hosting/DistributedApplicationExecutionContext.isRunMode", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContextOptions. */ +class DistributedApplicationExecutionContextOptions extends HandleWrapperBase { + DistributedApplicationExecutionContextOptions(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationResourceEventSubscription. */ +class DistributedApplicationResourceEventSubscription extends HandleWrapperBase { + DistributedApplicationResourceEventSubscription(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReference. */ +class EndpointReference extends HandleWrapperBase { + EndpointReference(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the EndpointName property */ + public String endpointName() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.endpointName", reqArgs); + } + + /** Gets the ErrorMessage property */ + public String errorMessage() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.errorMessage", reqArgs); + } + + /** Sets the ErrorMessage property */ + public EndpointReference setErrorMessage(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (EndpointReference) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.setErrorMessage", reqArgs); + } + + /** Gets the IsAllocated property */ + public boolean isAllocated() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (boolean) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.isAllocated", reqArgs); + } + + /** Gets the Exists property */ + public boolean exists() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (boolean) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.exists", reqArgs); + } + + /** Gets the IsHttp property */ + public boolean isHttp() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (boolean) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.isHttp", reqArgs); + } + + /** Gets the IsHttps property */ + public boolean isHttps() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (boolean) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.isHttps", reqArgs); + } + + /** Gets the Port property */ + public double port() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (double) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.port", reqArgs); + } + + /** Gets the TargetPort property */ + public double targetPort() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (double) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.targetPort", reqArgs); + } + + /** Gets the Host property */ + public String host() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.host", reqArgs); + } + + /** Gets the Scheme property */ + public String scheme() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.scheme", reqArgs); + } + + /** Gets the Url property */ + public String url() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReference.url", reqArgs); + } + + /** Gets the URL of the endpoint asynchronously */ + public String getValueAsync(CancellationToken cancellationToken) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + if (cancellationToken != null) { + reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); + } + return (String) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/getValueAsync", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReferenceExpression. */ +class EndpointReferenceExpression extends HandleWrapperBase { + EndpointReferenceExpression(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Endpoint property */ + public EndpointReference endpoint() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (EndpointReference) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.endpoint", reqArgs); + } + + /** Gets the Property property */ + public EndpointProperty property() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (EndpointProperty) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.property", reqArgs); + } + + /** Gets the ValueExpression property */ + public String valueExpression() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.valueExpression", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.EnvironmentCallbackContext. */ +class EnvironmentCallbackContext extends HandleWrapperBase { + EnvironmentCallbackContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the EnvironmentVariables property */ + private AspireDict environmentVariablesField; + public AspireDict environmentVariables() { + if (environmentVariablesField == null) { + environmentVariablesField = new AspireDict<>(getHandle(), getClient(), "Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.environmentVariables"); + } + return environmentVariablesField; + } + + /** Gets the CancellationToken property */ + public CancellationToken cancellationToken() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (CancellationToken) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.cancellationToken", reqArgs); + } + + /** Gets the ExecutionContext property */ + public DistributedApplicationExecutionContext executionContext() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (DistributedApplicationExecutionContext) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.executionContext", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecutableResource. */ +class ExecutableResource extends ResourceBuilderBase { + ExecutableResource(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Sets the executable command */ + public ExecutableResource withExecutableCommand(String command) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("command", AspireClient.serializeValue(command)); + return (ExecutableResource) getClient().invokeCapability("Aspire.Hosting/withExecutableCommand", reqArgs); + } + + /** Sets the executable working directory */ + public ExecutableResource withWorkingDirectory(String workingDirectory) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("workingDirectory", AspireClient.serializeValue(workingDirectory)); + return (ExecutableResource) getClient().invokeCapability("Aspire.Hosting/withWorkingDirectory", reqArgs); + } + + /** Sets an environment variable */ + public IResourceWithEnvironment withEnvironment(String name, String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironment", reqArgs); + } + + /** Adds an environment variable with a reference expression */ + public IResourceWithEnvironment withEnvironmentExpression(String name, ReferenceExpression value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentExpression", reqArgs); + } + + /** Sets environment variables via callback */ + public IResourceWithEnvironment withEnvironmentCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentCallback", reqArgs); + } + + /** Sets environment variables via async callback */ + public IResourceWithEnvironment withEnvironmentCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentCallbackAsync", reqArgs); + } + + /** Adds arguments */ + public IResourceWithArgs withArgs(String[] args) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("args", AspireClient.serializeValue(args)); + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgs", reqArgs); + } + + /** Sets command-line arguments via callback */ + public IResourceWithArgs withArgsCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgsCallback", reqArgs); + } + + /** Sets command-line arguments via async callback */ + public IResourceWithArgs withArgsCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgsCallbackAsync", reqArgs); + } + + /** Adds a reference to another resource */ + public IResourceWithEnvironment withReference(IResourceWithConnectionString source, String connectionName, Boolean optional) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("source", AspireClient.serializeValue(source)); + if (connectionName != null) { + reqArgs.put("connectionName", AspireClient.serializeValue(connectionName)); + } + if (optional != null) { + reqArgs.put("optional", AspireClient.serializeValue(optional)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withReference", reqArgs); + } + + /** Adds a service discovery reference to another resource */ + public IResourceWithEnvironment withServiceReference(IResourceWithServiceDiscovery source) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("source", AspireClient.serializeValue(source)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withServiceReference", reqArgs); + } + + /** Adds a network endpoint */ + public IResourceWithEndpoints withEndpoint(Double port, Double targetPort, String scheme, String name, String env, Boolean isProxied, Boolean isExternal, ProtocolType protocol) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (scheme != null) { + reqArgs.put("scheme", AspireClient.serializeValue(scheme)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + if (isExternal != null) { + reqArgs.put("isExternal", AspireClient.serializeValue(isExternal)); + } + if (protocol != null) { + reqArgs.put("protocol", AspireClient.serializeValue(protocol)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withEndpoint", reqArgs); + } + + /** Adds an HTTP endpoint */ + public IResourceWithEndpoints withHttpEndpoint(Double port, Double targetPort, String name, String env, Boolean isProxied) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpEndpoint", reqArgs); + } + + /** Adds an HTTPS endpoint */ + public IResourceWithEndpoints withHttpsEndpoint(Double port, Double targetPort, String name, String env, Boolean isProxied) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpsEndpoint", reqArgs); + } + + /** Makes HTTP endpoints externally accessible */ + public IResourceWithEndpoints withExternalHttpEndpoints() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withExternalHttpEndpoints", reqArgs); + } + + /** Gets an endpoint reference */ + public EndpointReference getEndpoint(String name) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + return (EndpointReference) getClient().invokeCapability("Aspire.Hosting/getEndpoint", reqArgs); + } + + /** Configures resource for HTTP/2 */ + public IResourceWithEndpoints asHttp2Service() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/asHttp2Service", reqArgs); + } + + /** Customizes displayed URLs via callback */ + public IResource withUrlsCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlsCallback", reqArgs); + } + + /** Customizes displayed URLs via async callback */ + public IResource withUrlsCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlsCallbackAsync", reqArgs); + } + + /** Adds or modifies displayed URLs */ + public IResource withUrl(String url, String displayText) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("url", AspireClient.serializeValue(url)); + if (displayText != null) { + reqArgs.put("displayText", AspireClient.serializeValue(displayText)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrl", reqArgs); + } + + /** Adds a URL using a reference expression */ + public IResource withUrlExpression(ReferenceExpression url, String displayText) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("url", AspireClient.serializeValue(url)); + if (displayText != null) { + reqArgs.put("displayText", AspireClient.serializeValue(displayText)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlExpression", reqArgs); + } + + /** Customizes the URL for a specific endpoint via callback */ + public IResource withUrlForEndpoint(String endpointName, Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlForEndpoint", reqArgs); + } + + /** Adds a URL for a specific endpoint via factory callback */ + public IResourceWithEndpoints withUrlForEndpointFactory(String endpointName, Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withUrlForEndpointFactory", reqArgs); + } + + /** Waits for another resource to be ready */ + public IResourceWithWaitSupport waitFor(IResource dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResourceWithWaitSupport) getClient().invokeCapability("Aspire.Hosting/waitFor", reqArgs); + } + + /** Prevents resource from starting automatically */ + public IResource withExplicitStart() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withExplicitStart", reqArgs); + } + + /** Waits for resource completion */ + public IResourceWithWaitSupport waitForCompletion(IResource dependency, Double exitCode) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + if (exitCode != null) { + reqArgs.put("exitCode", AspireClient.serializeValue(exitCode)); + } + return (IResourceWithWaitSupport) getClient().invokeCapability("Aspire.Hosting/waitForCompletion", reqArgs); + } + + /** Adds a health check by key */ + public IResource withHealthCheck(String key) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("key", AspireClient.serializeValue(key)); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withHealthCheck", reqArgs); + } + + /** Adds an HTTP health check */ + public IResourceWithEndpoints withHttpHealthCheck(String path, Double statusCode, String endpointName) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (path != null) { + reqArgs.put("path", AspireClient.serializeValue(path)); + } + if (statusCode != null) { + reqArgs.put("statusCode", AspireClient.serializeValue(statusCode)); + } + if (endpointName != null) { + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpHealthCheck", reqArgs); + } + + /** Adds a resource command */ + public IResource withCommand(String name, String displayName, Function executeCommand, CommandOptions commandOptions) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("displayName", AspireClient.serializeValue(displayName)); + if (executeCommand != null) { + reqArgs.put("executeCommand", getClient().registerCallback(executeCommand)); + } + if (commandOptions != null) { + reqArgs.put("commandOptions", AspireClient.serializeValue(commandOptions)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withCommand", reqArgs); + } + + /** Sets the parent relationship */ + public IResource withParentRelationship(IResource parent) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("parent", AspireClient.serializeValue(parent)); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withParentRelationship", reqArgs); + } + + /** Gets the resource name */ + public String getResourceName() { + Map reqArgs = new HashMap<>(); + reqArgs.put("resource", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting/getResourceName", reqArgs); + } + + /** Adds an optional string parameter */ + public IResource withOptionalString(String value, Boolean enabled) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (value != null) { + reqArgs.put("value", AspireClient.serializeValue(value)); + } + if (enabled != null) { + reqArgs.put("enabled", AspireClient.serializeValue(enabled)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalString", reqArgs); + } + + /** Configures the resource with a DTO */ + public IResource withConfig(TestConfigDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withConfig", reqArgs); + } + + /** Configures environment with callback (test version) */ + public IResourceWithEnvironment testWithEnvironmentCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/testWithEnvironmentCallback", reqArgs); + } + + /** Sets the created timestamp */ + public IResource withCreatedAt(String createdAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("createdAt", AspireClient.serializeValue(createdAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCreatedAt", reqArgs); + } + + /** Sets the modified timestamp */ + public IResource withModifiedAt(String modifiedAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("modifiedAt", AspireClient.serializeValue(modifiedAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withModifiedAt", reqArgs); + } + + /** Sets the correlation ID */ + public IResource withCorrelationId(String correlationId) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("correlationId", AspireClient.serializeValue(correlationId)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCorrelationId", reqArgs); + } + + /** Configures with optional callback */ + public IResource withOptionalCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalCallback", reqArgs); + } + + /** Sets the resource status */ + public IResource withStatus(TestResourceStatus status) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("status", AspireClient.serializeValue(status)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withStatus", reqArgs); + } + + /** Configures with nested DTO */ + public IResource withNestedConfig(TestNestedDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withNestedConfig", reqArgs); + } + + /** Adds validation callback */ + public IResource withValidator(Function validator) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (validator != null) { + reqArgs.put("validator", getClient().registerCallback(validator)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withValidator", reqArgs); + } + + /** Waits for another resource (test version) */ + public IResource testWaitFor(IResource dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/testWaitFor", reqArgs); + } + + /** Adds a dependency on another resource */ + public IResource withDependency(IResourceWithConnectionString dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withDependency", reqArgs); + } + + /** Sets the endpoints */ + public IResource withEndpoints(String[] endpoints) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpoints", AspireClient.serializeValue(endpoints)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withEndpoints", reqArgs); + } + + /** Sets environment variables */ + public IResourceWithEnvironment withEnvironmentVariables(Map variables) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("variables", AspireClient.serializeValue(variables)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withEnvironmentVariables", reqArgs); + } + + /** Performs a cancellable operation */ + public IResource withCancellableOperation(Function operation) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (operation != null) { + reqArgs.put("operation", getClient().registerCallback(operation)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCancellableOperation", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCommandContext. */ +class ExecuteCommandContext extends HandleWrapperBase { + ExecuteCommandContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the ResourceName property */ + public String resourceName() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.resourceName", reqArgs); + } + + /** Sets the ResourceName property */ + public ExecuteCommandContext setResourceName(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (ExecuteCommandContext) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.setResourceName", reqArgs); + } + + /** Gets the CancellationToken property */ + public CancellationToken cancellationToken() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (CancellationToken) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.cancellationToken", reqArgs); + } + + /** Sets the CancellationToken property */ + public ExecuteCommandContext setCancellationToken(CancellationToken value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + if (value != null) { + reqArgs.put("value", getClient().registerCancellation(value)); + } + return (ExecuteCommandContext) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.setCancellationToken", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder. */ +class IDistributedApplicationBuilder extends HandleWrapperBase { + IDistributedApplicationBuilder(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Adds a container resource */ + public ContainerResource addContainer(String name, String image) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("image", AspireClient.serializeValue(image)); + return (ContainerResource) getClient().invokeCapability("Aspire.Hosting/addContainer", reqArgs); + } + + /** Adds an executable resource */ + public ExecutableResource addExecutable(String name, String command, String workingDirectory, String[] args) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("command", AspireClient.serializeValue(command)); + reqArgs.put("workingDirectory", AspireClient.serializeValue(workingDirectory)); + reqArgs.put("args", AspireClient.serializeValue(args)); + return (ExecutableResource) getClient().invokeCapability("Aspire.Hosting/addExecutable", reqArgs); + } + + /** Gets the AppHostDirectory property */ + public String appHostDirectory() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting/IDistributedApplicationBuilder.appHostDirectory", reqArgs); + } + + /** Gets the Eventing property */ + public IDistributedApplicationEventing eventing() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (IDistributedApplicationEventing) getClient().invokeCapability("Aspire.Hosting/IDistributedApplicationBuilder.eventing", reqArgs); + } + + /** Gets the ExecutionContext property */ + public DistributedApplicationExecutionContext executionContext() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (DistributedApplicationExecutionContext) getClient().invokeCapability("Aspire.Hosting/IDistributedApplicationBuilder.executionContext", reqArgs); + } + + /** Builds the distributed application */ + public DistributedApplication build() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (DistributedApplication) getClient().invokeCapability("Aspire.Hosting/build", reqArgs); + } + + /** Adds a parameter resource */ + public ParameterResource addParameter(String name, Boolean secret) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + if (secret != null) { + reqArgs.put("secret", AspireClient.serializeValue(secret)); + } + return (ParameterResource) getClient().invokeCapability("Aspire.Hosting/addParameter", reqArgs); + } + + /** Adds a connection string resource */ + public IResourceWithConnectionString addConnectionString(String name, String environmentVariableName) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + if (environmentVariableName != null) { + reqArgs.put("environmentVariableName", AspireClient.serializeValue(environmentVariableName)); + } + return (IResourceWithConnectionString) getClient().invokeCapability("Aspire.Hosting/addConnectionString", reqArgs); + } + + /** Adds a .NET project resource */ + public ProjectResource addProject(String name, String projectPath, String launchProfileName) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("projectPath", AspireClient.serializeValue(projectPath)); + reqArgs.put("launchProfileName", AspireClient.serializeValue(launchProfileName)); + return (ProjectResource) getClient().invokeCapability("Aspire.Hosting/addProject", reqArgs); + } + + /** Adds a test Redis resource */ + public TestRedisResource addTestRedis(String name, Double port) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + return (TestRedisResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/addTestRedis", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEvent. */ +class IDistributedApplicationEvent extends HandleWrapperBase { + IDistributedApplicationEvent(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEventing. */ +class IDistributedApplicationEventing extends HandleWrapperBase { + IDistributedApplicationEventing(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Invokes the Unsubscribe method */ + public void unsubscribe(DistributedApplicationEventSubscription subscription) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("subscription", AspireClient.serializeValue(subscription)); + getClient().invokeCapability("Aspire.Hosting.Eventing/IDistributedApplicationEventing.unsubscribe", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationResourceEvent. */ +class IDistributedApplicationResourceEvent extends HandleWrapperBase { + IDistributedApplicationResourceEvent(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource. */ +class IResource extends ResourceBuilderBase { + IResource(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithArgs. */ +class IResourceWithArgs extends HandleWrapperBase { + IResourceWithArgs(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString. */ +class IResourceWithConnectionString extends ResourceBuilderBase { + IResourceWithConnectionString(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints. */ +class IResourceWithEndpoints extends HandleWrapperBase { + IResourceWithEndpoints(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment. */ +class IResourceWithEnvironment extends HandleWrapperBase { + IResourceWithEnvironment(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.IResourceWithServiceDiscovery. */ +class IResourceWithServiceDiscovery extends ResourceBuilderBase { + IResourceWithServiceDiscovery(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithWaitSupport. */ +class IResourceWithWaitSupport extends HandleWrapperBase { + IResourceWithWaitSupport(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ParameterResource. */ +class ParameterResource extends ResourceBuilderBase { + ParameterResource(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Sets a parameter description */ + public ParameterResource withDescription(String description, Boolean enableMarkdown) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("description", AspireClient.serializeValue(description)); + if (enableMarkdown != null) { + reqArgs.put("enableMarkdown", AspireClient.serializeValue(enableMarkdown)); + } + return (ParameterResource) getClient().invokeCapability("Aspire.Hosting/withDescription", reqArgs); + } + + /** Customizes displayed URLs via callback */ + public IResource withUrlsCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlsCallback", reqArgs); + } + + /** Customizes displayed URLs via async callback */ + public IResource withUrlsCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlsCallbackAsync", reqArgs); + } + + /** Adds or modifies displayed URLs */ + public IResource withUrl(String url, String displayText) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("url", AspireClient.serializeValue(url)); + if (displayText != null) { + reqArgs.put("displayText", AspireClient.serializeValue(displayText)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrl", reqArgs); + } + + /** Adds a URL using a reference expression */ + public IResource withUrlExpression(ReferenceExpression url, String displayText) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("url", AspireClient.serializeValue(url)); + if (displayText != null) { + reqArgs.put("displayText", AspireClient.serializeValue(displayText)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlExpression", reqArgs); + } + + /** Customizes the URL for a specific endpoint via callback */ + public IResource withUrlForEndpoint(String endpointName, Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlForEndpoint", reqArgs); + } + + /** Prevents resource from starting automatically */ + public IResource withExplicitStart() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withExplicitStart", reqArgs); + } + + /** Adds a health check by key */ + public IResource withHealthCheck(String key) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("key", AspireClient.serializeValue(key)); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withHealthCheck", reqArgs); + } + + /** Adds a resource command */ + public IResource withCommand(String name, String displayName, Function executeCommand, CommandOptions commandOptions) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("displayName", AspireClient.serializeValue(displayName)); + if (executeCommand != null) { + reqArgs.put("executeCommand", getClient().registerCallback(executeCommand)); + } + if (commandOptions != null) { + reqArgs.put("commandOptions", AspireClient.serializeValue(commandOptions)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withCommand", reqArgs); + } + + /** Sets the parent relationship */ + public IResource withParentRelationship(IResource parent) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("parent", AspireClient.serializeValue(parent)); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withParentRelationship", reqArgs); + } + + /** Gets the resource name */ + public String getResourceName() { + Map reqArgs = new HashMap<>(); + reqArgs.put("resource", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting/getResourceName", reqArgs); + } + + /** Adds an optional string parameter */ + public IResource withOptionalString(String value, Boolean enabled) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (value != null) { + reqArgs.put("value", AspireClient.serializeValue(value)); + } + if (enabled != null) { + reqArgs.put("enabled", AspireClient.serializeValue(enabled)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalString", reqArgs); + } + + /** Configures the resource with a DTO */ + public IResource withConfig(TestConfigDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withConfig", reqArgs); + } + + /** Sets the created timestamp */ + public IResource withCreatedAt(String createdAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("createdAt", AspireClient.serializeValue(createdAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCreatedAt", reqArgs); + } + + /** Sets the modified timestamp */ + public IResource withModifiedAt(String modifiedAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("modifiedAt", AspireClient.serializeValue(modifiedAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withModifiedAt", reqArgs); + } + + /** Sets the correlation ID */ + public IResource withCorrelationId(String correlationId) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("correlationId", AspireClient.serializeValue(correlationId)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCorrelationId", reqArgs); + } + + /** Configures with optional callback */ + public IResource withOptionalCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalCallback", reqArgs); + } + + /** Sets the resource status */ + public IResource withStatus(TestResourceStatus status) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("status", AspireClient.serializeValue(status)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withStatus", reqArgs); + } + + /** Configures with nested DTO */ + public IResource withNestedConfig(TestNestedDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withNestedConfig", reqArgs); + } + + /** Adds validation callback */ + public IResource withValidator(Function validator) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (validator != null) { + reqArgs.put("validator", getClient().registerCallback(validator)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withValidator", reqArgs); + } + + /** Waits for another resource (test version) */ + public IResource testWaitFor(IResource dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/testWaitFor", reqArgs); + } + + /** Adds a dependency on another resource */ + public IResource withDependency(IResourceWithConnectionString dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withDependency", reqArgs); + } + + /** Sets the endpoints */ + public IResource withEndpoints(String[] endpoints) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpoints", AspireClient.serializeValue(endpoints)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withEndpoints", reqArgs); + } + + /** Performs a cancellable operation */ + public IResource withCancellableOperation(Function operation) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (operation != null) { + reqArgs.put("operation", getClient().registerCallback(operation)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCancellableOperation", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ProjectResource. */ +class ProjectResource extends ResourceBuilderBase { + ProjectResource(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Sets the number of replicas */ + public ProjectResource withReplicas(double replicas) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("replicas", AspireClient.serializeValue(replicas)); + return (ProjectResource) getClient().invokeCapability("Aspire.Hosting/withReplicas", reqArgs); + } + + /** Sets an environment variable */ + public IResourceWithEnvironment withEnvironment(String name, String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironment", reqArgs); + } + + /** Adds an environment variable with a reference expression */ + public IResourceWithEnvironment withEnvironmentExpression(String name, ReferenceExpression value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentExpression", reqArgs); + } + + /** Sets environment variables via callback */ + public IResourceWithEnvironment withEnvironmentCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentCallback", reqArgs); + } + + /** Sets environment variables via async callback */ + public IResourceWithEnvironment withEnvironmentCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentCallbackAsync", reqArgs); + } + + /** Adds arguments */ + public IResourceWithArgs withArgs(String[] args) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("args", AspireClient.serializeValue(args)); + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgs", reqArgs); + } + + /** Sets command-line arguments via callback */ + public IResourceWithArgs withArgsCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgsCallback", reqArgs); + } + + /** Sets command-line arguments via async callback */ + public IResourceWithArgs withArgsCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgsCallbackAsync", reqArgs); + } + + /** Adds a reference to another resource */ + public IResourceWithEnvironment withReference(IResourceWithConnectionString source, String connectionName, Boolean optional) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("source", AspireClient.serializeValue(source)); + if (connectionName != null) { + reqArgs.put("connectionName", AspireClient.serializeValue(connectionName)); + } + if (optional != null) { + reqArgs.put("optional", AspireClient.serializeValue(optional)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withReference", reqArgs); + } + + /** Adds a service discovery reference to another resource */ + public IResourceWithEnvironment withServiceReference(IResourceWithServiceDiscovery source) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("source", AspireClient.serializeValue(source)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withServiceReference", reqArgs); + } + + /** Adds a network endpoint */ + public IResourceWithEndpoints withEndpoint(Double port, Double targetPort, String scheme, String name, String env, Boolean isProxied, Boolean isExternal, ProtocolType protocol) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (scheme != null) { + reqArgs.put("scheme", AspireClient.serializeValue(scheme)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + if (isExternal != null) { + reqArgs.put("isExternal", AspireClient.serializeValue(isExternal)); + } + if (protocol != null) { + reqArgs.put("protocol", AspireClient.serializeValue(protocol)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withEndpoint", reqArgs); + } + + /** Adds an HTTP endpoint */ + public IResourceWithEndpoints withHttpEndpoint(Double port, Double targetPort, String name, String env, Boolean isProxied) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpEndpoint", reqArgs); + } + + /** Adds an HTTPS endpoint */ + public IResourceWithEndpoints withHttpsEndpoint(Double port, Double targetPort, String name, String env, Boolean isProxied) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpsEndpoint", reqArgs); + } + + /** Makes HTTP endpoints externally accessible */ + public IResourceWithEndpoints withExternalHttpEndpoints() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withExternalHttpEndpoints", reqArgs); + } + + /** Gets an endpoint reference */ + public EndpointReference getEndpoint(String name) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + return (EndpointReference) getClient().invokeCapability("Aspire.Hosting/getEndpoint", reqArgs); + } + + /** Configures resource for HTTP/2 */ + public IResourceWithEndpoints asHttp2Service() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/asHttp2Service", reqArgs); + } + + /** Customizes displayed URLs via callback */ + public IResource withUrlsCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlsCallback", reqArgs); + } + + /** Customizes displayed URLs via async callback */ + public IResource withUrlsCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlsCallbackAsync", reqArgs); + } + + /** Adds or modifies displayed URLs */ + public IResource withUrl(String url, String displayText) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("url", AspireClient.serializeValue(url)); + if (displayText != null) { + reqArgs.put("displayText", AspireClient.serializeValue(displayText)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrl", reqArgs); + } + + /** Adds a URL using a reference expression */ + public IResource withUrlExpression(ReferenceExpression url, String displayText) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("url", AspireClient.serializeValue(url)); + if (displayText != null) { + reqArgs.put("displayText", AspireClient.serializeValue(displayText)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlExpression", reqArgs); + } + + /** Customizes the URL for a specific endpoint via callback */ + public IResource withUrlForEndpoint(String endpointName, Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlForEndpoint", reqArgs); + } + + /** Adds a URL for a specific endpoint via factory callback */ + public IResourceWithEndpoints withUrlForEndpointFactory(String endpointName, Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withUrlForEndpointFactory", reqArgs); + } + + /** Waits for another resource to be ready */ + public IResourceWithWaitSupport waitFor(IResource dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResourceWithWaitSupport) getClient().invokeCapability("Aspire.Hosting/waitFor", reqArgs); + } + + /** Prevents resource from starting automatically */ + public IResource withExplicitStart() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withExplicitStart", reqArgs); + } + + /** Waits for resource completion */ + public IResourceWithWaitSupport waitForCompletion(IResource dependency, Double exitCode) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + if (exitCode != null) { + reqArgs.put("exitCode", AspireClient.serializeValue(exitCode)); + } + return (IResourceWithWaitSupport) getClient().invokeCapability("Aspire.Hosting/waitForCompletion", reqArgs); + } + + /** Adds a health check by key */ + public IResource withHealthCheck(String key) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("key", AspireClient.serializeValue(key)); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withHealthCheck", reqArgs); + } + + /** Adds an HTTP health check */ + public IResourceWithEndpoints withHttpHealthCheck(String path, Double statusCode, String endpointName) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (path != null) { + reqArgs.put("path", AspireClient.serializeValue(path)); + } + if (statusCode != null) { + reqArgs.put("statusCode", AspireClient.serializeValue(statusCode)); + } + if (endpointName != null) { + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpHealthCheck", reqArgs); + } + + /** Adds a resource command */ + public IResource withCommand(String name, String displayName, Function executeCommand, CommandOptions commandOptions) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("displayName", AspireClient.serializeValue(displayName)); + if (executeCommand != null) { + reqArgs.put("executeCommand", getClient().registerCallback(executeCommand)); + } + if (commandOptions != null) { + reqArgs.put("commandOptions", AspireClient.serializeValue(commandOptions)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withCommand", reqArgs); + } + + /** Sets the parent relationship */ + public IResource withParentRelationship(IResource parent) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("parent", AspireClient.serializeValue(parent)); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withParentRelationship", reqArgs); + } + + /** Gets the resource name */ + public String getResourceName() { + Map reqArgs = new HashMap<>(); + reqArgs.put("resource", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting/getResourceName", reqArgs); + } + + /** Adds an optional string parameter */ + public IResource withOptionalString(String value, Boolean enabled) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (value != null) { + reqArgs.put("value", AspireClient.serializeValue(value)); + } + if (enabled != null) { + reqArgs.put("enabled", AspireClient.serializeValue(enabled)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalString", reqArgs); + } + + /** Configures the resource with a DTO */ + public IResource withConfig(TestConfigDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withConfig", reqArgs); + } + + /** Configures environment with callback (test version) */ + public IResourceWithEnvironment testWithEnvironmentCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/testWithEnvironmentCallback", reqArgs); + } + + /** Sets the created timestamp */ + public IResource withCreatedAt(String createdAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("createdAt", AspireClient.serializeValue(createdAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCreatedAt", reqArgs); + } + + /** Sets the modified timestamp */ + public IResource withModifiedAt(String modifiedAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("modifiedAt", AspireClient.serializeValue(modifiedAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withModifiedAt", reqArgs); + } + + /** Sets the correlation ID */ + public IResource withCorrelationId(String correlationId) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("correlationId", AspireClient.serializeValue(correlationId)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCorrelationId", reqArgs); + } + + /** Configures with optional callback */ + public IResource withOptionalCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalCallback", reqArgs); + } + + /** Sets the resource status */ + public IResource withStatus(TestResourceStatus status) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("status", AspireClient.serializeValue(status)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withStatus", reqArgs); + } + + /** Configures with nested DTO */ + public IResource withNestedConfig(TestNestedDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withNestedConfig", reqArgs); + } + + /** Adds validation callback */ + public IResource withValidator(Function validator) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (validator != null) { + reqArgs.put("validator", getClient().registerCallback(validator)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withValidator", reqArgs); + } + + /** Waits for another resource (test version) */ + public IResource testWaitFor(IResource dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/testWaitFor", reqArgs); + } + + /** Adds a dependency on another resource */ + public IResource withDependency(IResourceWithConnectionString dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withDependency", reqArgs); + } + + /** Sets the endpoints */ + public IResource withEndpoints(String[] endpoints) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpoints", AspireClient.serializeValue(endpoints)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withEndpoints", reqArgs); + } + + /** Sets environment variables */ + public IResourceWithEnvironment withEnvironmentVariables(Map variables) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("variables", AspireClient.serializeValue(variables)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withEnvironmentVariables", reqArgs); + } + + /** Performs a cancellable operation */ + public IResource withCancellableOperation(Function operation) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (operation != null) { + reqArgs.put("operation", getClient().registerCallback(operation)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCancellableOperation", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceUrlsCallbackContext. */ +class ResourceUrlsCallbackContext extends HandleWrapperBase { + ResourceUrlsCallbackContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Urls property */ + private AspireList urlsField; + public AspireList urls() { + if (urlsField == null) { + urlsField = new AspireList<>(getHandle(), getClient(), "Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.urls"); + } + return urlsField; + } + + /** Gets the CancellationToken property */ + public CancellationToken cancellationToken() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (CancellationToken) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.cancellationToken", reqArgs); + } + + /** Gets the ExecutionContext property */ + public DistributedApplicationExecutionContext executionContext() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (DistributedApplicationExecutionContext) getClient().invokeCapability("Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.executionContext", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext. */ +class TestCallbackContext extends HandleWrapperBase { + TestCallbackContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Name property */ + public String name() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.name", reqArgs); + } + + /** Sets the Name property */ + public TestCallbackContext setName(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestCallbackContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setName", reqArgs); + } + + /** Gets the Value property */ + public double value() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (double) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.value", reqArgs); + } + + /** Sets the Value property */ + public TestCallbackContext setValue(double value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestCallbackContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setValue", reqArgs); + } + + /** Gets the CancellationToken property */ + public CancellationToken cancellationToken() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (CancellationToken) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.cancellationToken", reqArgs); + } + + /** Sets the CancellationToken property */ + public TestCallbackContext setCancellationToken(CancellationToken value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + if (value != null) { + reqArgs.put("value", getClient().registerCancellation(value)); + } + return (TestCallbackContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setCancellationToken", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext. */ +class TestCollectionContext extends HandleWrapperBase { + TestCollectionContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Items property */ + private AspireList itemsField; + public AspireList items() { + if (itemsField == null) { + itemsField = new AspireList<>(getHandle(), getClient(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.items"); + } + return itemsField; + } + + /** Gets the Metadata property */ + private AspireDict metadataField; + public AspireDict metadata() { + if (metadataField == null) { + metadataField = new AspireDict<>(getHandle(), getClient(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.metadata"); + } + return metadataField; + } + +} + +/** Wrapper for Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext. */ +class TestEnvironmentContext extends HandleWrapperBase { + TestEnvironmentContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Name property */ + public String name() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.name", reqArgs); + } + + /** Sets the Name property */ + public TestEnvironmentContext setName(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestEnvironmentContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setName", reqArgs); + } + + /** Gets the Description property */ + public String description() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.description", reqArgs); + } + + /** Sets the Description property */ + public TestEnvironmentContext setDescription(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestEnvironmentContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setDescription", reqArgs); + } + + /** Gets the Priority property */ + public double priority() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (double) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.priority", reqArgs); + } + + /** Sets the Priority property */ + public TestEnvironmentContext setPriority(double value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestEnvironmentContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setPriority", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource. */ +class TestRedisResource extends ResourceBuilderBase { + TestRedisResource(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Adds a bind mount */ + public ContainerResource withBindMount(String source, String target, Boolean isReadOnly) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("source", AspireClient.serializeValue(source)); + reqArgs.put("target", AspireClient.serializeValue(target)); + if (isReadOnly != null) { + reqArgs.put("isReadOnly", AspireClient.serializeValue(isReadOnly)); + } + return (ContainerResource) getClient().invokeCapability("Aspire.Hosting/withBindMount", reqArgs); + } + + /** Sets the container entrypoint */ + public ContainerResource withEntrypoint(String entrypoint) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("entrypoint", AspireClient.serializeValue(entrypoint)); + return (ContainerResource) getClient().invokeCapability("Aspire.Hosting/withEntrypoint", reqArgs); + } + + /** Sets the container image tag */ + public ContainerResource withImageTag(String tag) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("tag", AspireClient.serializeValue(tag)); + return (ContainerResource) getClient().invokeCapability("Aspire.Hosting/withImageTag", reqArgs); + } + + /** Sets the container image registry */ + public ContainerResource withImageRegistry(String registry) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("registry", AspireClient.serializeValue(registry)); + return (ContainerResource) getClient().invokeCapability("Aspire.Hosting/withImageRegistry", reqArgs); + } + + /** Sets the container image */ + public ContainerResource withImage(String image, String tag) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("image", AspireClient.serializeValue(image)); + if (tag != null) { + reqArgs.put("tag", AspireClient.serializeValue(tag)); + } + return (ContainerResource) getClient().invokeCapability("Aspire.Hosting/withImage", reqArgs); + } + + /** Adds runtime arguments for the container */ + public ContainerResource withContainerRuntimeArgs(String[] args) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("args", AspireClient.serializeValue(args)); + return (ContainerResource) getClient().invokeCapability("Aspire.Hosting/withContainerRuntimeArgs", reqArgs); + } + + /** Sets the lifetime behavior of the container resource */ + public ContainerResource withLifetime(ContainerLifetime lifetime) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("lifetime", AspireClient.serializeValue(lifetime)); + return (ContainerResource) getClient().invokeCapability("Aspire.Hosting/withLifetime", reqArgs); + } + + /** Sets the container image pull policy */ + public ContainerResource withImagePullPolicy(ImagePullPolicy pullPolicy) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("pullPolicy", AspireClient.serializeValue(pullPolicy)); + return (ContainerResource) getClient().invokeCapability("Aspire.Hosting/withImagePullPolicy", reqArgs); + } + + /** Sets the container name */ + public ContainerResource withContainerName(String name) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + return (ContainerResource) getClient().invokeCapability("Aspire.Hosting/withContainerName", reqArgs); + } + + /** Sets an environment variable */ + public IResourceWithEnvironment withEnvironment(String name, String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironment", reqArgs); + } + + /** Adds an environment variable with a reference expression */ + public IResourceWithEnvironment withEnvironmentExpression(String name, ReferenceExpression value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentExpression", reqArgs); + } + + /** Sets environment variables via callback */ + public IResourceWithEnvironment withEnvironmentCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentCallback", reqArgs); + } + + /** Sets environment variables via async callback */ + public IResourceWithEnvironment withEnvironmentCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withEnvironmentCallbackAsync", reqArgs); + } + + /** Adds arguments */ + public IResourceWithArgs withArgs(String[] args) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("args", AspireClient.serializeValue(args)); + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgs", reqArgs); + } + + /** Sets command-line arguments via callback */ + public IResourceWithArgs withArgsCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgsCallback", reqArgs); + } + + /** Sets command-line arguments via async callback */ + public IResourceWithArgs withArgsCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithArgs) getClient().invokeCapability("Aspire.Hosting/withArgsCallbackAsync", reqArgs); + } + + /** Adds a reference to another resource */ + public IResourceWithEnvironment withReference(IResourceWithConnectionString source, String connectionName, Boolean optional) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("source", AspireClient.serializeValue(source)); + if (connectionName != null) { + reqArgs.put("connectionName", AspireClient.serializeValue(connectionName)); + } + if (optional != null) { + reqArgs.put("optional", AspireClient.serializeValue(optional)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withReference", reqArgs); + } + + /** Adds a service discovery reference to another resource */ + public IResourceWithEnvironment withServiceReference(IResourceWithServiceDiscovery source) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("source", AspireClient.serializeValue(source)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting/withServiceReference", reqArgs); + } + + /** Adds a network endpoint */ + public IResourceWithEndpoints withEndpoint(Double port, Double targetPort, String scheme, String name, String env, Boolean isProxied, Boolean isExternal, ProtocolType protocol) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (scheme != null) { + reqArgs.put("scheme", AspireClient.serializeValue(scheme)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + if (isExternal != null) { + reqArgs.put("isExternal", AspireClient.serializeValue(isExternal)); + } + if (protocol != null) { + reqArgs.put("protocol", AspireClient.serializeValue(protocol)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withEndpoint", reqArgs); + } + + /** Adds an HTTP endpoint */ + public IResourceWithEndpoints withHttpEndpoint(Double port, Double targetPort, String name, String env, Boolean isProxied) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpEndpoint", reqArgs); + } + + /** Adds an HTTPS endpoint */ + public IResourceWithEndpoints withHttpsEndpoint(Double port, Double targetPort, String name, String env, Boolean isProxied) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (port != null) { + reqArgs.put("port", AspireClient.serializeValue(port)); + } + if (targetPort != null) { + reqArgs.put("targetPort", AspireClient.serializeValue(targetPort)); + } + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (env != null) { + reqArgs.put("env", AspireClient.serializeValue(env)); + } + if (isProxied != null) { + reqArgs.put("isProxied", AspireClient.serializeValue(isProxied)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpsEndpoint", reqArgs); + } + + /** Makes HTTP endpoints externally accessible */ + public IResourceWithEndpoints withExternalHttpEndpoints() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withExternalHttpEndpoints", reqArgs); + } + + /** Gets an endpoint reference */ + public EndpointReference getEndpoint(String name) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + return (EndpointReference) getClient().invokeCapability("Aspire.Hosting/getEndpoint", reqArgs); + } + + /** Configures resource for HTTP/2 */ + public IResourceWithEndpoints asHttp2Service() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/asHttp2Service", reqArgs); + } + + /** Customizes displayed URLs via callback */ + public IResource withUrlsCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlsCallback", reqArgs); + } + + /** Customizes displayed URLs via async callback */ + public IResource withUrlsCallbackAsync(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlsCallbackAsync", reqArgs); + } + + /** Adds or modifies displayed URLs */ + public IResource withUrl(String url, String displayText) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("url", AspireClient.serializeValue(url)); + if (displayText != null) { + reqArgs.put("displayText", AspireClient.serializeValue(displayText)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrl", reqArgs); + } + + /** Adds a URL using a reference expression */ + public IResource withUrlExpression(ReferenceExpression url, String displayText) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("url", AspireClient.serializeValue(url)); + if (displayText != null) { + reqArgs.put("displayText", AspireClient.serializeValue(displayText)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlExpression", reqArgs); + } + + /** Customizes the URL for a specific endpoint via callback */ + public IResource withUrlForEndpoint(String endpointName, Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withUrlForEndpoint", reqArgs); + } + + /** Adds a URL for a specific endpoint via factory callback */ + public IResourceWithEndpoints withUrlForEndpointFactory(String endpointName, Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withUrlForEndpointFactory", reqArgs); + } + + /** Waits for another resource to be ready */ + public IResourceWithWaitSupport waitFor(IResource dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResourceWithWaitSupport) getClient().invokeCapability("Aspire.Hosting/waitFor", reqArgs); + } + + /** Prevents resource from starting automatically */ + public IResource withExplicitStart() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withExplicitStart", reqArgs); + } + + /** Waits for resource completion */ + public IResourceWithWaitSupport waitForCompletion(IResource dependency, Double exitCode) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + if (exitCode != null) { + reqArgs.put("exitCode", AspireClient.serializeValue(exitCode)); + } + return (IResourceWithWaitSupport) getClient().invokeCapability("Aspire.Hosting/waitForCompletion", reqArgs); + } + + /** Adds a health check by key */ + public IResource withHealthCheck(String key) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("key", AspireClient.serializeValue(key)); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withHealthCheck", reqArgs); + } + + /** Adds an HTTP health check */ + public IResourceWithEndpoints withHttpHealthCheck(String path, Double statusCode, String endpointName) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (path != null) { + reqArgs.put("path", AspireClient.serializeValue(path)); + } + if (statusCode != null) { + reqArgs.put("statusCode", AspireClient.serializeValue(statusCode)); + } + if (endpointName != null) { + reqArgs.put("endpointName", AspireClient.serializeValue(endpointName)); + } + return (IResourceWithEndpoints) getClient().invokeCapability("Aspire.Hosting/withHttpHealthCheck", reqArgs); + } + + /** Adds a resource command */ + public IResource withCommand(String name, String displayName, Function executeCommand, CommandOptions commandOptions) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + reqArgs.put("displayName", AspireClient.serializeValue(displayName)); + if (executeCommand != null) { + reqArgs.put("executeCommand", getClient().registerCallback(executeCommand)); + } + if (commandOptions != null) { + reqArgs.put("commandOptions", AspireClient.serializeValue(commandOptions)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting/withCommand", reqArgs); + } + + /** Sets the parent relationship */ + public IResource withParentRelationship(IResource parent) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("parent", AspireClient.serializeValue(parent)); + return (IResource) getClient().invokeCapability("Aspire.Hosting/withParentRelationship", reqArgs); + } + + /** Adds a volume */ + public ContainerResource withVolume(String target, String name, Boolean isReadOnly) { + Map reqArgs = new HashMap<>(); + reqArgs.put("resource", AspireClient.serializeValue(getHandle())); + reqArgs.put("target", AspireClient.serializeValue(target)); + if (name != null) { + reqArgs.put("name", AspireClient.serializeValue(name)); + } + if (isReadOnly != null) { + reqArgs.put("isReadOnly", AspireClient.serializeValue(isReadOnly)); + } + return (ContainerResource) getClient().invokeCapability("Aspire.Hosting/withVolume", reqArgs); + } + + /** Gets the resource name */ + public String getResourceName() { + Map reqArgs = new HashMap<>(); + reqArgs.put("resource", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting/getResourceName", reqArgs); + } + + /** Configures the Redis resource with persistence */ + public TestRedisResource withPersistence(TestPersistenceMode mode) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (mode != null) { + reqArgs.put("mode", AspireClient.serializeValue(mode)); + } + return (TestRedisResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withPersistence", reqArgs); + } + + /** Adds an optional string parameter */ + public IResource withOptionalString(String value, Boolean enabled) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (value != null) { + reqArgs.put("value", AspireClient.serializeValue(value)); + } + if (enabled != null) { + reqArgs.put("enabled", AspireClient.serializeValue(enabled)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalString", reqArgs); + } + + /** Configures the resource with a DTO */ + public IResource withConfig(TestConfigDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withConfig", reqArgs); + } + + /** Gets the tags for the resource */ + private AspireList getTagsField; + public AspireList getTags() { + if (getTagsField == null) { + getTagsField = new AspireList<>(getHandle(), getClient(), "Aspire.Hosting.CodeGeneration.Java.Tests/getTags"); + } + return getTagsField; + } + + /** Gets the metadata for the resource */ + private AspireDict getMetadataField; + public AspireDict getMetadata() { + if (getMetadataField == null) { + getMetadataField = new AspireDict<>(getHandle(), getClient(), "Aspire.Hosting.CodeGeneration.Java.Tests/getMetadata"); + } + return getMetadataField; + } + + /** Sets the connection string using a reference expression */ + public IResourceWithConnectionString withConnectionString(ReferenceExpression connectionString) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("connectionString", AspireClient.serializeValue(connectionString)); + return (IResourceWithConnectionString) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withConnectionString", reqArgs); + } + + /** Configures environment with callback (test version) */ + public IResourceWithEnvironment testWithEnvironmentCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/testWithEnvironmentCallback", reqArgs); + } + + /** Sets the created timestamp */ + public IResource withCreatedAt(String createdAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("createdAt", AspireClient.serializeValue(createdAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCreatedAt", reqArgs); + } + + /** Sets the modified timestamp */ + public IResource withModifiedAt(String modifiedAt) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("modifiedAt", AspireClient.serializeValue(modifiedAt)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withModifiedAt", reqArgs); + } + + /** Sets the correlation ID */ + public IResource withCorrelationId(String correlationId) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("correlationId", AspireClient.serializeValue(correlationId)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCorrelationId", reqArgs); + } + + /** Configures with optional callback */ + public IResource withOptionalCallback(Function callback) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (callback != null) { + reqArgs.put("callback", getClient().registerCallback(callback)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalCallback", reqArgs); + } + + /** Sets the resource status */ + public IResource withStatus(TestResourceStatus status) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("status", AspireClient.serializeValue(status)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withStatus", reqArgs); + } + + /** Configures with nested DTO */ + public IResource withNestedConfig(TestNestedDto config) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("config", AspireClient.serializeValue(config)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withNestedConfig", reqArgs); + } + + /** Adds validation callback */ + public IResource withValidator(Function validator) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (validator != null) { + reqArgs.put("validator", getClient().registerCallback(validator)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withValidator", reqArgs); + } + + /** Waits for another resource (test version) */ + public IResource testWaitFor(IResource dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/testWaitFor", reqArgs); + } + + /** Gets the endpoints */ + public String[] getEndpoints() { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + return (String[]) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/getEndpoints", reqArgs); + } + + /** Sets connection string using direct interface target */ + public IResourceWithConnectionString withConnectionStringDirect(String connectionString) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("connectionString", AspireClient.serializeValue(connectionString)); + return (IResourceWithConnectionString) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withConnectionStringDirect", reqArgs); + } + + /** Redis-specific configuration */ + public TestRedisResource withRedisSpecific(String option) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("option", AspireClient.serializeValue(option)); + return (TestRedisResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withRedisSpecific", reqArgs); + } + + /** Adds a dependency on another resource */ + public IResource withDependency(IResourceWithConnectionString dependency) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("dependency", AspireClient.serializeValue(dependency)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withDependency", reqArgs); + } + + /** Sets the endpoints */ + public IResource withEndpoints(String[] endpoints) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("endpoints", AspireClient.serializeValue(endpoints)); + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withEndpoints", reqArgs); + } + + /** Sets environment variables */ + public IResourceWithEnvironment withEnvironmentVariables(Map variables) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("variables", AspireClient.serializeValue(variables)); + return (IResourceWithEnvironment) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withEnvironmentVariables", reqArgs); + } + + /** Gets the status of the resource asynchronously */ + public String getStatusAsync(CancellationToken cancellationToken) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (cancellationToken != null) { + reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); + } + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/getStatusAsync", reqArgs); + } + + /** Performs a cancellable operation */ + public IResource withCancellableOperation(Function operation) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + if (operation != null) { + reqArgs.put("operation", getClient().registerCallback(operation)); + } + return (IResource) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/withCancellableOperation", reqArgs); + } + + /** Waits for the resource to be ready */ + public boolean waitForReadyAsync(double timeout, CancellationToken cancellationToken) { + Map reqArgs = new HashMap<>(); + reqArgs.put("builder", AspireClient.serializeValue(getHandle())); + reqArgs.put("timeout", AspireClient.serializeValue(timeout)); + if (cancellationToken != null) { + reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); + } + return (boolean) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.Java.Tests/waitForReadyAsync", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext. */ +class TestResourceContext extends HandleWrapperBase { + TestResourceContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the Name property */ + public String name() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.name", reqArgs); + } + + /** Sets the Name property */ + public TestResourceContext setName(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestResourceContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setName", reqArgs); + } + + /** Gets the Value property */ + public double value() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (double) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.value", reqArgs); + } + + /** Sets the Value property */ + public TestResourceContext setValue(double value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + return (TestResourceContext) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValue", reqArgs); + } + + /** Invokes the GetValueAsync method */ + public String getValueAsync() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (String) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.getValueAsync", reqArgs); + } + + /** Invokes the SetValueAsync method */ + public void setValueAsync(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValueAsync", reqArgs); + } + + /** Invokes the ValidateAsync method */ + public boolean validateAsync() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + return (boolean) getClient().invokeCapability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.validateAsync", reqArgs); + } + +} + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.UpdateCommandStateContext. */ +class UpdateCommandStateContext extends HandleWrapperBase { + UpdateCommandStateContext(Handle handle, AspireClient client) { + super(handle, client); + } + +} + +// ============================================================================ +// Handle wrapper registrations +// ============================================================================ + +/** Static initializer to register handle wrappers. */ +class AspireRegistrations { + static { + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.DistributedApplication", (h, c) -> new DistributedApplication(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext", (h, c) -> new DistributedApplicationExecutionContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContextOptions", (h, c) -> new DistributedApplicationExecutionContextOptions(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder", (h, c) -> new IDistributedApplicationBuilder(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription", (h, c) -> new DistributedApplicationEventSubscription(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationResourceEventSubscription", (h, c) -> new DistributedApplicationResourceEventSubscription(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEvent", (h, c) -> new IDistributedApplicationEvent(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationResourceEvent", (h, c) -> new IDistributedApplicationResourceEvent(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEventing", (h, c) -> new IDistributedApplicationEventing(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.CommandLineArgsCallbackContext", (h, c) -> new CommandLineArgsCallbackContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReference", (h, c) -> new EndpointReference(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReferenceExpression", (h, c) -> new EndpointReferenceExpression(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.EnvironmentCallbackContext", (h, c) -> new EnvironmentCallbackContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.UpdateCommandStateContext", (h, c) -> new UpdateCommandStateContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCommandContext", (h, c) -> new ExecuteCommandContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceUrlsCallbackContext", (h, c) -> new ResourceUrlsCallbackContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource", (h, c) -> new ContainerResource(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecutableResource", (h, c) -> new ExecutableResource(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ParameterResource", (h, c) -> new ParameterResource(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString", (h, c) -> new IResourceWithConnectionString(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ProjectResource", (h, c) -> new ProjectResource(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.IResourceWithServiceDiscovery", (h, c) -> new IResourceWithServiceDiscovery(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource", (h, c) -> new IResource(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext", (h, c) -> new TestCallbackContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext", (h, c) -> new TestResourceContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext", (h, c) -> new TestEnvironmentContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext", (h, c) -> new TestCollectionContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource", (h, c) -> new TestRedisResource(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment", (h, c) -> new IResourceWithEnvironment(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithArgs", (h, c) -> new IResourceWithArgs(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints", (h, c) -> new IResourceWithEndpoints(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithWaitSupport", (h, c) -> new IResourceWithWaitSupport(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Dict", (h, c) -> new AspireDict(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/List", (h, c) -> new AspireList(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Dict", (h, c) -> new AspireDict(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/List", (h, c) -> new AspireList(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/List", (h, c) -> new AspireList(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Dict", (h, c) -> new AspireDict(h, c)); + } + + static void ensureRegistered() { + // Called to trigger static initializer + } +} + +// ============================================================================ +// Connection Helpers +// ============================================================================ + +/** Main entry point for Aspire SDK. */ +public class Aspire { + /** Connect to the AppHost server. */ + public static AspireClient connect() throws Exception { + AspireRegistrations.ensureRegistered(); + String socketPath = System.getenv("REMOTE_APP_HOST_SOCKET_PATH"); + if (socketPath == null || socketPath.isEmpty()) { + throw new RuntimeException("REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`."); + } + AspireClient client = new AspireClient(socketPath); + client.connect(); + client.onDisconnect(() -> System.exit(1)); + return client; + } + + /** Create a new distributed application builder. */ + public static IDistributedApplicationBuilder createBuilder(CreateBuilderOptions options) throws Exception { + AspireClient client = connect(); + Map resolvedOptions = new HashMap<>(); + if (options != null) { + resolvedOptions.putAll(options.toMap()); + } + if (!resolvedOptions.containsKey("Args")) { + // Note: Java doesn't have easy access to command line args from here + resolvedOptions.put("Args", new String[0]); + } + if (!resolvedOptions.containsKey("ProjectDirectory")) { + resolvedOptions.put("ProjectDirectory", System.getProperty("user.dir")); + } + Map args = new HashMap<>(); + args.put("options", resolvedOptions); + return (IDistributedApplicationBuilder) client.invokeCapability("Aspire.Hosting/createBuilderWithOptions", args); + } +} + diff --git a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/WithOptionalStringCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/WithOptionalStringCapability.verified.txt new file mode 100644 index 00000000000..56fc51f2121 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/WithOptionalStringCapability.verified.txt @@ -0,0 +1,75 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Java.Tests/withOptionalString, + MethodName: withOptionalString, + QualifiedMethodName: withOptionalString, + Description: Adds an optional string parameter, + Parameters: [ + { + Name: value, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false + }, + { + Name: enabled, + Type: { + TypeId: boolean, + ClrType: bool, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false, + DefaultValue: true + } + ], + ReturnType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + ClrType: IResource, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + ClrType: IResource, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.WithOptionalString +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/WithPersistenceCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/WithPersistenceCapability.verified.txt new file mode 100644 index 00000000000..d1b81858018 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/WithPersistenceCapability.verified.txt @@ -0,0 +1,61 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Java.Tests/withPersistence, + MethodName: withPersistence, + QualifiedMethodName: withPersistence, + Description: Configures the Redis resource with persistence, + Parameters: [ + { + Name: mode, + Type: { + TypeId: enum:Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestPersistenceMode, + ClrType: TestPersistenceMode, + Category: Enum, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false, + DefaultValue: Volume + } + ], + ReturnType: { + TypeId: Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + TargetType: { + TypeId: Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting.CodeGeneration.Java.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.WithPersistence +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.Python.Tests.csproj b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.Python.Tests.csproj new file mode 100644 index 00000000000..6d743886a31 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.Python.Tests.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + + + + + + + + + + + + + + + + + + + diff --git a/tests/Aspire.Hosting.CodeGeneration.Python.Tests/AtsPythonCodeGeneratorTests.cs b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/AtsPythonCodeGeneratorTests.cs new file mode 100644 index 00000000000..5295ce0abca --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/AtsPythonCodeGeneratorTests.cs @@ -0,0 +1,321 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Ats; +using Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes; + +namespace Aspire.Hosting.CodeGeneration.Python.Tests; + +public class AtsPythonCodeGeneratorTests +{ + private readonly AtsPythonCodeGenerator _generator = new(); + + // The test types are compiled into this assembly via Compile Include + private const string TestTypesAssemblyName = "Aspire.Hosting.CodeGeneration.Python.Tests"; + + [Fact] + public void Language_ReturnsPython() + { + Assert.Equal("Python", _generator.Language); + } + + [Fact] + public async Task GenerateDistributedApplication_WithTestTypes_GeneratesCorrectOutput() + { + // Arrange + var atsContext = CreateContextFromTestAssembly(); + + // Act + var files = _generator.GenerateDistributedApplication(atsContext); + + // Assert + Assert.Contains("aspire.py", files.Keys); + Assert.Contains("transport.py", files.Keys); + Assert.Contains("base.py", files.Keys); + + await Verify(files["aspire.py"], extension: "py") + .UseFileName("AtsGeneratedAspire"); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_IncludesCapabilities() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert that capabilities are discovered + Assert.NotEmpty(capabilities); + + // Check for specific capabilities (uses AssemblyName/methodName format) + // The test types are in TypeScript.Tests assembly + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/withOptionalString"); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_DeriveCorrectMethodNames() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert method names are derived correctly + var addTestRedis = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Equal("addTestRedis", addTestRedis.MethodName); + + var withPersistence = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.Equal("withPersistence", withPersistence.MethodName); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_CapturesParameters() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert parameters are captured + var addTestRedis = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Equal(2, addTestRedis.Parameters.Count); + Assert.Equal("Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder", addTestRedis.TargetTypeId); + Assert.Contains(addTestRedis.Parameters, p => p.Name == "name" && p.Type?.TypeId == "string"); + Assert.Contains(addTestRedis.Parameters, p => p.Name == "port" && p.IsOptional); + } + + [Fact] + public void Scanner_ReturnsBuilder_TrueForResourceBuilderReturnTypes() + { + // Verify that ReturnsBuilder is correctly set to true for methods + // that return IResourceBuilder + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // addTestRedis returns IResourceBuilder - should have ReturnsBuilder = true + var addTestRedis = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.NotNull(addTestRedis); + Assert.True(addTestRedis.ReturnsBuilder, + "addTestRedis returns IResourceBuilder but ReturnsBuilder is false - fluent chaining won't work"); + + // withPersistence also returns IResourceBuilder + var withPersistence = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.NotNull(withPersistence); + Assert.True(withPersistence.ReturnsBuilder, + "withPersistence returns IResourceBuilder but ReturnsBuilder is false - fluent chaining won't work"); + } + + [Fact] + public async Task Scanner_AddTestRedis_HasCorrectTypeMetadata() + { + // Verify the entire capability object for addTestRedis + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var addTestRedis = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.NotNull(addTestRedis); + + await Verify(addTestRedis).UseFileName("AddTestRedisCapability"); + } + + [Fact] + public async Task Scanner_WithPersistence_HasCorrectExpandedTargets() + { + // Verify the entire capability object for withPersistence + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var withPersistence = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.NotNull(withPersistence); + + await Verify(withPersistence).UseFileName("WithPersistenceCapability"); + } + + [Fact] + public async Task Scanner_WithOptionalString_HasCorrectExpandedTargets() + { + // Verify withOptionalString (targets IResource, should expand to TestRedisResource) + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var withOptionalString = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withOptionalString"); + Assert.NotNull(withOptionalString); + + await Verify(withOptionalString).UseFileName("WithOptionalStringCapability"); + } + + [Fact] + public async Task Scanner_HostingAssembly_AddContainerCapability() + { + // Verify the addContainer capability from the real Aspire.Hosting assembly + var capabilities = ScanCapabilitiesFromHostingAssembly(); + + var addContainer = capabilities.FirstOrDefault(c => c.CapabilityId == "Aspire.Hosting/addContainer"); + Assert.NotNull(addContainer); + + await Verify(addContainer).UseFileName("HostingAddContainerCapability"); + } + + [Fact] + public void RuntimeType_ContainerResource_IsNotInterface() + { + // Verify that ContainerResource.IsInterface returns false using runtime reflection + var containerResourceType = typeof(ContainerResource); + + Assert.NotNull(containerResourceType); + Assert.False(containerResourceType.IsInterface, "ContainerResource should NOT be an interface"); + } + + [Fact] + public void TwoPassScanning_DeduplicatesCapabilities() + { + // Verify that when the same capability appears in multiple assemblies, + // ScanAssemblies deduplicates by CapabilityId. + var capabilities = ScanCapabilitiesFromBothAssemblies(); + + // Each capability ID should appear only once + var duplicates = capabilities + .GroupBy(c => c.CapabilityId) + .Where(g => g.Count() > 1) + .Select(g => g.Key) + .ToList(); + + Assert.Empty(duplicates); + } + + [Fact] + public void TwoPassScanning_MergesHandleTypesFromAllAssemblies() + { + // Verify that ScanAssemblies collects handle types from all assemblies + var result = CreateContextFromBothAssemblies(); + + // Should have types from Aspire.Hosting (ContainerResource, etc.) + var containerResourceType = result.HandleTypes + .FirstOrDefault(t => t.AtsTypeId.Contains("ContainerResource") && !t.AtsTypeId.Contains("IContainer")); + Assert.NotNull(containerResourceType); + + // Should have types from test assembly (TestRedisResource) + var testRedisType = result.HandleTypes + .FirstOrDefault(t => t.AtsTypeId.Contains("TestRedisResource")); + Assert.NotNull(testRedisType); + + // TestRedisResource should have IResourceWithEnvironment in its interfaces + // (inherited via ContainerResource) + var hasEnvironmentInterface = testRedisType.ImplementedInterfaces + .Any(i => i.TypeId.Contains("IResourceWithEnvironment")); + Assert.True(hasEnvironmentInterface, + "TestRedisResource should implement IResourceWithEnvironment via ContainerResource"); + } + + [Fact] + public async Task TwoPassScanning_GeneratesWithEnvironmentOnTestRedisBuilder() + { + // End-to-end test: verify that with_environment appears on TestRedisResource + // in the generated Python when using 2-pass scanning. + var atsContext = CreateContextFromBothAssemblies(); + + // Generate Python + var files = _generator.GenerateDistributedApplication(atsContext); + var aspirePy = files["aspire.py"]; + + // Verify with_environment appears (method should exist for resources that support it) + Assert.Contains("with_environment", aspirePy); + + // Snapshot for detailed verification + await Verify(aspirePy, extension: "py") + .UseFileName("TwoPassScanningGeneratedAspire"); + } + + [Fact] + public void GeneratedCode_UsesSnakeCaseMethodNames() + { + // Verify that the generated Python code uses snake_case for method names + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + var aspirePy = files["aspire.py"]; + + // Python should use snake_case, not camelCase + Assert.Contains("add_container", aspirePy); + Assert.Contains("with_environment", aspirePy); + Assert.DoesNotContain("addContainer(", aspirePy); + Assert.DoesNotContain("withEnvironment(", aspirePy); + } + + [Fact] + public void GeneratedCode_HasCreateBuilderFunction() + { + // Verify that the generated Python code has a create_builder function + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + var aspirePy = files["aspire.py"]; + + Assert.Contains("def create_builder", aspirePy); + } + + [Fact] + public void GeneratedCode_UsesTypeHints() + { + // Verify that the generated Python code uses type hints + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + var aspirePy = files["aspire.py"]; + + // Python type hints use -> for return types and : for parameters + Assert.Contains("->", aspirePy); + Assert.Contains(": str", aspirePy); + } + + private static List ScanCapabilitiesFromTestAssembly() + { + var testAssembly = LoadTestAssembly(); + + // Scan capabilities from the test assembly + var result = AtsCapabilityScanner.ScanAssembly(testAssembly); + return result.Capabilities; + } + + private static AtsContext CreateContextFromTestAssembly() + { + var testAssembly = LoadTestAssembly(); + + // Scan capabilities from the test assembly + var result = AtsCapabilityScanner.ScanAssembly(testAssembly); + return result.ToAtsContext(); + } + + private static Assembly LoadTestAssembly() + { + // Get the test assembly at runtime (TypeScript tests assembly has the TestTypes) + return typeof(TestRedisResource).Assembly; + } + + private static List ScanCapabilitiesFromHostingAssembly() + { + var hostingAssembly = typeof(DistributedApplication).Assembly; + var result = AtsCapabilityScanner.ScanAssembly(hostingAssembly); + return result.Capabilities; + } + + private static List ScanCapabilitiesFromBothAssemblies() + { + var (testAssembly, hostingAssembly) = LoadBothAssemblies(); + + // Use ScanAssemblies for proper cross-assembly expansion + var result = AtsCapabilityScanner.ScanAssemblies([hostingAssembly, testAssembly]); + return result.Capabilities; + } + + private static AtsContext CreateContextFromBothAssemblies() + { + var (testAssembly, hostingAssembly) = LoadBothAssemblies(); + + // Use ScanAssemblies for proper cross-assembly expansion and enum collection + var result = AtsCapabilityScanner.ScanAssemblies([hostingAssembly, testAssembly]); + return result.ToAtsContext(); + } + + private static (Assembly testAssembly, Assembly hostingAssembly) LoadBothAssemblies() + { + var testAssembly = typeof(TestRedisResource).Assembly; + var hostingAssembly = typeof(DistributedApplication).Assembly; + return (testAssembly, hostingAssembly); + } +} diff --git a/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/AddTestRedisCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/AddTestRedisCapability.verified.txt new file mode 100644 index 00000000000..4d80d51a061 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/AddTestRedisCapability.verified.txt @@ -0,0 +1,74 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Python.Tests/addTestRedis, + MethodName: addTestRedis, + QualifiedMethodName: addTestRedis, + Description: Adds a test Redis resource, + Parameters: [ + { + Name: name, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + }, + { + Name: port, + Type: { + TypeId: number, + ClrType: int, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: true, + IsCallback: false + } + ], + ReturnType: { + TypeId: Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.AddTestRedis +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/AtsGeneratedAspire.verified.py b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/AtsGeneratedAspire.verified.py new file mode 100644 index 00000000000..f5c1186ded4 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/AtsGeneratedAspire.verified.py @@ -0,0 +1,486 @@ +# aspire.py - Capability-based Aspire SDK +# GENERATED CODE - DO NOT EDIT + +from __future__ import annotations + +import os +import sys +from dataclasses import dataclass +from enum import Enum +from typing import Any, Callable, Dict, List + +from transport import AspireClient, Handle, CapabilityError, register_callback, register_handle_wrapper, register_cancellation +from base import AspireDict, AspireList, ReferenceExpression, ref_expr, HandleWrapperBase, ResourceBuilderBase, serialize_value + +# ============================================================================ +# Enums +# ============================================================================ + +class TestPersistenceMode(str, Enum): + NONE_ = "None" + VOLUME = "Volume" + BIND = "Bind" + +class TestResourceStatus(str, Enum): + PENDING = "Pending" + RUNNING = "Running" + STOPPED = "Stopped" + FAILED = "Failed" + +# ============================================================================ +# DTOs +# ============================================================================ + +@dataclass +class TestConfigDto: + name: str + port: float + enabled: bool + optional_field: str + + def to_dict(self) -> Dict[str, Any]: + return { + "Name": serialize_value(self.name), + "Port": serialize_value(self.port), + "Enabled": serialize_value(self.enabled), + "OptionalField": serialize_value(self.optional_field), + } + +@dataclass +class TestNestedDto: + id: str + config: TestConfigDto + tags: AspireList[str] + counts: AspireDict[str, float] + + def to_dict(self) -> Dict[str, Any]: + return { + "Id": serialize_value(self.id), + "Config": serialize_value(self.config), + "Tags": serialize_value(self.tags), + "Counts": serialize_value(self.counts), + } + +@dataclass +class TestDeeplyNestedDto: + nested_data: AspireDict[str, AspireList[TestConfigDto]] + metadata_array: list[AspireDict[str, str]] + + def to_dict(self) -> Dict[str, Any]: + return { + "NestedData": serialize_value(self.nested_data), + "MetadataArray": serialize_value(self.metadata_array), + } + +# ============================================================================ +# Handle Wrappers +# ============================================================================ + +class IDistributedApplicationBuilder(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def add_test_redis(self, name: str, port: float | None = None) -> TestRedisResource: + """Adds a test Redis resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + if port is not None: + args["port"] = serialize_value(port) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/addTestRedis", args) + + +class IResource(ResourceBuilderBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class IResourceWithConnectionString(ResourceBuilderBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class IResourceWithEnvironment(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class ReferenceExpression(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class TestCallbackContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def name(self) -> str: + """Gets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.name", args) + + def set_name(self, value: str) -> TestCallbackContext: + """Sets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setName", args) + + def value(self) -> float: + """Gets the Value property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.value", args) + + def set_value(self, value: float) -> TestCallbackContext: + """Sets the Value property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setValue", args) + + def cancellation_token(self) -> CancellationToken: + """Gets the CancellationToken property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.cancellationToken", args) + + def set_cancellation_token(self, value: CancellationToken) -> TestCallbackContext: + """Sets the CancellationToken property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + value_id = register_cancellation(value, self._client) if value is not None else None + if value_id is not None: + args["value"] = value_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setCancellationToken", args) + + +class TestCollectionContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + @property + def items(self) -> AspireList[str]: + """Gets the Items property""" + if not hasattr(self, '_items'): + self._items = AspireList( + self._handle, + self._client, + "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.items" + ) + return self._items + + @property + def metadata(self) -> AspireDict[str, str]: + """Gets the Metadata property""" + if not hasattr(self, '_metadata'): + self._metadata = AspireDict( + self._handle, + self._client, + "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.metadata" + ) + return self._metadata + + +class TestEnvironmentContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def name(self) -> str: + """Gets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.name", args) + + def set_name(self, value: str) -> TestEnvironmentContext: + """Sets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setName", args) + + def description(self) -> str: + """Gets the Description property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.description", args) + + def set_description(self, value: str) -> TestEnvironmentContext: + """Sets the Description property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setDescription", args) + + def priority(self) -> float: + """Gets the Priority property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.priority", args) + + def set_priority(self, value: float) -> TestEnvironmentContext: + """Sets the Priority property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setPriority", args) + + +class TestRedisResource(ResourceBuilderBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def with_persistence(self, mode: TestPersistenceMode = None) -> TestRedisResource: + """Configures the Redis resource with persistence""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["mode"] = serialize_value(mode) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withPersistence", args) + + def with_optional_string(self, value: str | None = None, enabled: bool = True) -> IResource: + """Adds an optional string parameter""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if value is not None: + args["value"] = serialize_value(value) + args["enabled"] = serialize_value(enabled) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalString", args) + + def with_config(self, config: TestConfigDto) -> IResource: + """Configures the resource with a DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withConfig", args) + + @property + def get_tags(self) -> AspireList[str]: + """Gets the tags for the resource""" + if not hasattr(self, '_get_tags'): + self._get_tags = AspireList( + self._handle, + self._client, + "Aspire.Hosting.CodeGeneration.Python.Tests/getTags" + ) + return self._get_tags + + @property + def get_metadata(self) -> AspireDict[str, str]: + """Gets the metadata for the resource""" + if not hasattr(self, '_get_metadata'): + self._get_metadata = AspireDict( + self._handle, + self._client, + "Aspire.Hosting.CodeGeneration.Python.Tests/getMetadata" + ) + return self._get_metadata + + def with_connection_string(self, connection_string: ReferenceExpression) -> IResourceWithConnectionString: + """Sets the connection string using a reference expression""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["connectionString"] = serialize_value(connection_string) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withConnectionString", args) + + def test_with_environment_callback(self, callback: Callable[[TestEnvironmentContext], None]) -> IResourceWithEnvironment: + """Configures environment with callback (test version)""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/testWithEnvironmentCallback", args) + + def with_created_at(self, created_at: str) -> IResource: + """Sets the created timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["createdAt"] = serialize_value(created_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCreatedAt", args) + + def with_modified_at(self, modified_at: str) -> IResource: + """Sets the modified timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["modifiedAt"] = serialize_value(modified_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withModifiedAt", args) + + def with_correlation_id(self, correlation_id: str) -> IResource: + """Sets the correlation ID""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["correlationId"] = serialize_value(correlation_id) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCorrelationId", args) + + def with_optional_callback(self, callback: Callable[[TestCallbackContext], None] | None = None) -> IResource: + """Configures with optional callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalCallback", args) + + def with_status(self, status: TestResourceStatus) -> IResource: + """Sets the resource status""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["status"] = serialize_value(status) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withStatus", args) + + def with_nested_config(self, config: TestNestedDto) -> IResource: + """Configures with nested DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withNestedConfig", args) + + def with_validator(self, validator: Callable[[TestResourceContext], bool]) -> IResource: + """Adds validation callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + validator_id = register_callback(validator) if validator is not None else None + if validator_id is not None: + args["validator"] = validator_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withValidator", args) + + def test_wait_for(self, dependency: IResource) -> IResource: + """Waits for another resource (test version)""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/testWaitFor", args) + + def get_endpoints(self) -> list[str]: + """Gets the endpoints""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/getEndpoints", args) + + def with_connection_string_direct(self, connection_string: str) -> IResourceWithConnectionString: + """Sets connection string using direct interface target""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["connectionString"] = serialize_value(connection_string) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withConnectionStringDirect", args) + + def with_redis_specific(self, option: str) -> TestRedisResource: + """Redis-specific configuration""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["option"] = serialize_value(option) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withRedisSpecific", args) + + def with_dependency(self, dependency: IResourceWithConnectionString) -> IResource: + """Adds a dependency on another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withDependency", args) + + def with_endpoints(self, endpoints: list[str]) -> IResource: + """Sets the endpoints""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpoints"] = serialize_value(endpoints) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withEndpoints", args) + + def with_environment_variables(self, variables: dict[str, str]) -> IResourceWithEnvironment: + """Sets environment variables""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["variables"] = serialize_value(variables) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withEnvironmentVariables", args) + + def get_status_async(self, cancellation_token: CancellationToken | None = None) -> str: + """Gets the status of the resource asynchronously""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + cancellation_token_id = register_cancellation(cancellation_token, self._client) if cancellation_token is not None else None + if cancellation_token_id is not None: + args["cancellationToken"] = cancellation_token_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/getStatusAsync", args) + + def with_cancellable_operation(self, operation: Callable[[CancellationToken], None]) -> IResource: + """Performs a cancellable operation""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + operation_id = register_callback(operation) if operation is not None else None + if operation_id is not None: + args["operation"] = operation_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCancellableOperation", args) + + def wait_for_ready_async(self, timeout: float, cancellation_token: CancellationToken | None = None) -> bool: + """Waits for the resource to be ready""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["timeout"] = serialize_value(timeout) + cancellation_token_id = register_cancellation(cancellation_token, self._client) if cancellation_token is not None else None + if cancellation_token_id is not None: + args["cancellationToken"] = cancellation_token_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/waitForReadyAsync", args) + + +class TestResourceContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def name(self) -> str: + """Gets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.name", args) + + def set_name(self, value: str) -> TestResourceContext: + """Sets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setName", args) + + def value(self) -> float: + """Gets the Value property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.value", args) + + def set_value(self, value: float) -> TestResourceContext: + """Sets the Value property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValue", args) + + def get_value_async(self) -> str: + """Invokes the GetValueAsync method""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.getValueAsync", args) + + def set_value_async(self, value: str) -> None: + """Invokes the SetValueAsync method""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValueAsync", args) + return None + + def validate_async(self) -> bool: + """Invokes the ValidateAsync method""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.validateAsync", args) + + +# ============================================================================ +# Handle wrapper registrations +# ============================================================================ + +register_handle_wrapper("Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext", lambda handle, client: TestCallbackContext(handle, client)) +register_handle_wrapper("Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext", lambda handle, client: TestResourceContext(handle, client)) +register_handle_wrapper("Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext", lambda handle, client: TestEnvironmentContext(handle, client)) +register_handle_wrapper("Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext", lambda handle, client: TestCollectionContext(handle, client)) +register_handle_wrapper("Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource", lambda handle, client: TestRedisResource(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource", lambda handle, client: IResource(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString", lambda handle, client: IResourceWithConnectionString(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder", lambda handle, client: IDistributedApplicationBuilder(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ReferenceExpression", lambda handle, client: ReferenceExpression(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment", lambda handle, client: IResourceWithEnvironment(handle, client)) +register_handle_wrapper("Aspire.Hosting/List", lambda handle, client: AspireList(handle, client)) +register_handle_wrapper("Aspire.Hosting/Dict", lambda handle, client: AspireDict(handle, client)) + +# ============================================================================ +# Connection Helpers +# ============================================================================ + +def connect() -> AspireClient: + socket_path = os.environ.get("REMOTE_APP_HOST_SOCKET_PATH") + if not socket_path: + raise RuntimeError("REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`.") + client = AspireClient(socket_path) + client.connect() + client.on_disconnect(lambda: sys.exit(1)) + return client + +def create_builder(options: Any | None = None) -> IDistributedApplicationBuilder: + client = connect() + resolved_options: Dict[str, Any] = {} + if options is not None: + if hasattr(options, "to_dict"): + resolved_options.update(options.to_dict()) + elif isinstance(options, dict): + resolved_options.update(options) + resolved_options.setdefault("Args", sys.argv[1:]) + resolved_options.setdefault("ProjectDirectory", os.environ.get("ASPIRE_PROJECT_DIRECTORY", os.getcwd())) + result = client.invoke_capability("Aspire.Hosting/createBuilderWithOptions", {"options": resolved_options}) + return result + +# Re-export commonly used types +CapabilityError = CapabilityError +Handle = Handle +ReferenceExpression = ReferenceExpression +ref_expr = ref_expr + diff --git a/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/HostingAddContainerCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/HostingAddContainerCapability.verified.txt new file mode 100644 index 00000000000..ba9342ec73c --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/HostingAddContainerCapability.verified.txt @@ -0,0 +1,74 @@ +{ + CapabilityId: Aspire.Hosting/addContainer, + MethodName: addContainer, + QualifiedMethodName: addContainer, + Description: Adds a container resource, + Parameters: [ + { + Name: name, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + }, + { + Name: image, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + } + ], + ReturnType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + ClrType: ContainerResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.ContainerResourceBuilderExtensions.AddContainer +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/HostingContainerResourceCapabilities.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/HostingContainerResourceCapabilities.verified.txt new file mode 100644 index 00000000000..c3626a99364 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/HostingContainerResourceCapabilities.verified.txt @@ -0,0 +1,548 @@ +[ + { + CapabilityId: Aspire.Hosting/asHttp2Service, + MethodName: asHttp2Service, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/getEndpoint, + MethodName: getEndpoint, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/getResourceName, + MethodName: getResourceName, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/waitFor, + MethodName: waitFor, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithWaitSupport, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/waitForCompletion, + MethodName: waitForCompletion, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithWaitSupport, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withArgs, + MethodName: withArgs, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithArgs, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withArgsCallback, + MethodName: withArgsCallback, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithArgs, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withArgsCallbackAsync, + MethodName: withArgsCallbackAsync, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithArgs, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withBindMount, + MethodName: withBindMount, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withCommand, + MethodName: withCommand, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withContainerName, + MethodName: withContainerName, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withContainerRuntimeArgs, + MethodName: withContainerRuntimeArgs, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withEndpoint, + MethodName: withEndpoint, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withEntrypoint, + MethodName: withEntrypoint, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withEnvironment, + MethodName: withEnvironment, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withEnvironmentCallback, + MethodName: withEnvironmentCallback, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withEnvironmentCallbackAsync, + MethodName: withEnvironmentCallbackAsync, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withEnvironmentExpression, + MethodName: withEnvironmentExpression, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withExplicitStart, + MethodName: withExplicitStart, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withExternalHttpEndpoints, + MethodName: withExternalHttpEndpoints, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withHealthCheck, + MethodName: withHealthCheck, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withHttpEndpoint, + MethodName: withHttpEndpoint, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withHttpHealthCheck, + MethodName: withHttpHealthCheck, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withHttpsEndpoint, + MethodName: withHttpsEndpoint, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withImage, + MethodName: withImage, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withImagePullPolicy, + MethodName: withImagePullPolicy, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withImageRegistry, + MethodName: withImageRegistry, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withImageTag, + MethodName: withImageTag, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withLifetime, + MethodName: withLifetime, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withParentRelationship, + MethodName: withParentRelationship, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withReference, + MethodName: withReference, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withServiceReference, + MethodName: withServiceReference, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withUrl, + MethodName: withUrl, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withUrlExpression, + MethodName: withUrlExpression, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withUrlForEndpoint, + MethodName: withUrlForEndpoint, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withUrlForEndpointFactory, + MethodName: withUrlForEndpointFactory, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withUrlsCallback, + MethodName: withUrlsCallback, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withUrlsCallbackAsync, + MethodName: withUrlsCallbackAsync, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + IsInterface: true + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + }, + { + CapabilityId: Aspire.Hosting/withVolume, + MethodName: withVolume, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + }, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + IsInterface: false + } + ] + } +] \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py new file mode 100644 index 00000000000..1bc2962a5e1 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py @@ -0,0 +1,2574 @@ +# aspire.py - Capability-based Aspire SDK +# GENERATED CODE - DO NOT EDIT + +from __future__ import annotations + +import os +import sys +from dataclasses import dataclass +from enum import Enum +from typing import Any, Callable, Dict, List + +from transport import AspireClient, Handle, CapabilityError, register_callback, register_handle_wrapper, register_cancellation +from base import AspireDict, AspireList, ReferenceExpression, ref_expr, HandleWrapperBase, ResourceBuilderBase, serialize_value + +# ============================================================================ +# Enums +# ============================================================================ + +class ContainerLifetime(str, Enum): + SESSION = "Session" + PERSISTENT = "Persistent" + +class ImagePullPolicy(str, Enum): + DEFAULT = "Default" + ALWAYS = "Always" + MISSING = "Missing" + NEVER = "Never" + +class DistributedApplicationOperation(str, Enum): + RUN = "Run" + PUBLISH = "Publish" + +class ProtocolType(str, Enum): + IP = "IP" + I_PV6_HOP_BY_HOP_OPTIONS = "IPv6HopByHopOptions" + UNSPECIFIED = "Unspecified" + ICMP = "Icmp" + IGMP = "Igmp" + GGP = "Ggp" + I_PV4 = "IPv4" + TCP = "Tcp" + PUP = "Pup" + UDP = "Udp" + IDP = "Idp" + I_PV6 = "IPv6" + I_PV6_ROUTING_HEADER = "IPv6RoutingHeader" + I_PV6_FRAGMENT_HEADER = "IPv6FragmentHeader" + IP_SEC_ENCAPSULATING_SECURITY_PAYLOAD = "IPSecEncapsulatingSecurityPayload" + IP_SEC_AUTHENTICATION_HEADER = "IPSecAuthenticationHeader" + ICMP_V6 = "IcmpV6" + I_PV6_NO_NEXT_HEADER = "IPv6NoNextHeader" + I_PV6_DESTINATION_OPTIONS = "IPv6DestinationOptions" + ND = "ND" + RAW = "Raw" + IPX = "Ipx" + SPX = "Spx" + SPX_II = "SpxII" + UNKNOWN = "Unknown" + +class EndpointProperty(str, Enum): + URL = "Url" + HOST = "Host" + IPV4_HOST = "IPV4Host" + PORT = "Port" + SCHEME = "Scheme" + TARGET_PORT = "TargetPort" + HOST_AND_PORT = "HostAndPort" + +class IconVariant(str, Enum): + REGULAR = "Regular" + FILLED = "Filled" + +class UrlDisplayLocation(str, Enum): + SUMMARY_AND_DETAILS = "SummaryAndDetails" + DETAILS_ONLY = "DetailsOnly" + +class TestPersistenceMode(str, Enum): + NONE_ = "None" + VOLUME = "Volume" + BIND = "Bind" + +class TestResourceStatus(str, Enum): + PENDING = "Pending" + RUNNING = "Running" + STOPPED = "Stopped" + FAILED = "Failed" + +# ============================================================================ +# DTOs +# ============================================================================ + +@dataclass +class CreateBuilderOptions: + args: list[str] + project_directory: str + app_host_file_path: str + container_registry_override: str + disable_dashboard: bool + dashboard_application_name: str + allow_unsecured_transport: bool + enable_resource_logging: bool + + def to_dict(self) -> Dict[str, Any]: + return { + "Args": serialize_value(self.args), + "ProjectDirectory": serialize_value(self.project_directory), + "AppHostFilePath": serialize_value(self.app_host_file_path), + "ContainerRegistryOverride": serialize_value(self.container_registry_override), + "DisableDashboard": serialize_value(self.disable_dashboard), + "DashboardApplicationName": serialize_value(self.dashboard_application_name), + "AllowUnsecuredTransport": serialize_value(self.allow_unsecured_transport), + "EnableResourceLogging": serialize_value(self.enable_resource_logging), + } + +@dataclass +class ResourceEventDto: + resource_name: str + resource_id: str + state: str + state_style: str + health_status: str + exit_code: float + + def to_dict(self) -> Dict[str, Any]: + return { + "ResourceName": serialize_value(self.resource_name), + "ResourceId": serialize_value(self.resource_id), + "State": serialize_value(self.state), + "StateStyle": serialize_value(self.state_style), + "HealthStatus": serialize_value(self.health_status), + "ExitCode": serialize_value(self.exit_code), + } + +@dataclass +class CommandOptions: + description: str + parameter: Any + confirmation_message: str + icon_name: str + icon_variant: IconVariant + is_highlighted: bool + update_state: Any + + def to_dict(self) -> Dict[str, Any]: + return { + "Description": serialize_value(self.description), + "Parameter": serialize_value(self.parameter), + "ConfirmationMessage": serialize_value(self.confirmation_message), + "IconName": serialize_value(self.icon_name), + "IconVariant": serialize_value(self.icon_variant), + "IsHighlighted": serialize_value(self.is_highlighted), + "UpdateState": serialize_value(self.update_state), + } + +@dataclass +class ExecuteCommandResult: + success: bool + canceled: bool + error_message: str + + def to_dict(self) -> Dict[str, Any]: + return { + "Success": serialize_value(self.success), + "Canceled": serialize_value(self.canceled), + "ErrorMessage": serialize_value(self.error_message), + } + +@dataclass +class ResourceUrlAnnotation: + url: str + display_text: str + endpoint: EndpointReference + display_location: UrlDisplayLocation + + def to_dict(self) -> Dict[str, Any]: + return { + "Url": serialize_value(self.url), + "DisplayText": serialize_value(self.display_text), + "Endpoint": serialize_value(self.endpoint), + "DisplayLocation": serialize_value(self.display_location), + } + +@dataclass +class TestConfigDto: + name: str + port: float + enabled: bool + optional_field: str + + def to_dict(self) -> Dict[str, Any]: + return { + "Name": serialize_value(self.name), + "Port": serialize_value(self.port), + "Enabled": serialize_value(self.enabled), + "OptionalField": serialize_value(self.optional_field), + } + +@dataclass +class TestNestedDto: + id: str + config: TestConfigDto + tags: AspireList[str] + counts: AspireDict[str, float] + + def to_dict(self) -> Dict[str, Any]: + return { + "Id": serialize_value(self.id), + "Config": serialize_value(self.config), + "Tags": serialize_value(self.tags), + "Counts": serialize_value(self.counts), + } + +@dataclass +class TestDeeplyNestedDto: + nested_data: AspireDict[str, AspireList[TestConfigDto]] + metadata_array: list[AspireDict[str, str]] + + def to_dict(self) -> Dict[str, Any]: + return { + "NestedData": serialize_value(self.nested_data), + "MetadataArray": serialize_value(self.metadata_array), + } + +# ============================================================================ +# Handle Wrappers +# ============================================================================ + +class CommandLineArgsCallbackContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + @property + def args(self) -> AspireList[Any]: + """Gets the Args property""" + if not hasattr(self, '_args'): + self._args = AspireList( + self._handle, + self._client, + "Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.args" + ) + return self._args + + def cancellation_token(self) -> CancellationToken: + """Gets the CancellationToken property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.cancellationToken", args) + + def execution_context(self) -> DistributedApplicationExecutionContext: + """Gets the ExecutionContext property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.executionContext", args) + + def set_execution_context(self, value: DistributedApplicationExecutionContext) -> CommandLineArgsCallbackContext: + """Sets the ExecutionContext property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.setExecutionContext", args) + + +class ContainerResource(ResourceBuilderBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def with_environment(self, name: str, value: str) -> IResourceWithEnvironment: + """Sets an environment variable""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting/withEnvironment", args) + + def with_environment_expression(self, name: str, value: ReferenceExpression) -> IResourceWithEnvironment: + """Adds an environment variable with a reference expression""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentExpression", args) + + def with_environment_callback(self, callback: Callable[[EnvironmentCallbackContext], None]) -> IResourceWithEnvironment: + """Sets environment variables via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentCallback", args) + + def with_environment_callback_async(self, callback: Callable[[EnvironmentCallbackContext], None]) -> IResourceWithEnvironment: + """Sets environment variables via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentCallbackAsync", args) + + def with_args(self, args: list[str]) -> IResourceWithArgs: + """Adds arguments""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["args"] = serialize_value(args) + return self._client.invoke_capability("Aspire.Hosting/withArgs", args) + + def with_args_callback(self, callback: Callable[[CommandLineArgsCallbackContext], None]) -> IResourceWithArgs: + """Sets command-line arguments via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withArgsCallback", args) + + def with_args_callback_async(self, callback: Callable[[CommandLineArgsCallbackContext], None]) -> IResourceWithArgs: + """Sets command-line arguments via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withArgsCallbackAsync", args) + + def with_reference(self, source: IResourceWithConnectionString, connection_name: str | None = None, optional: bool = False) -> IResourceWithEnvironment: + """Adds a reference to another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["source"] = serialize_value(source) + if connection_name is not None: + args["connectionName"] = serialize_value(connection_name) + args["optional"] = serialize_value(optional) + return self._client.invoke_capability("Aspire.Hosting/withReference", args) + + def with_service_reference(self, source: IResourceWithServiceDiscovery) -> IResourceWithEnvironment: + """Adds a service discovery reference to another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["source"] = serialize_value(source) + return self._client.invoke_capability("Aspire.Hosting/withServiceReference", args) + + def with_endpoint(self, port: float | None = None, target_port: float | None = None, scheme: str | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True, is_external: bool | None = None, protocol: ProtocolType | None = None) -> IResourceWithEndpoints: + """Adds a network endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if scheme is not None: + args["scheme"] = serialize_value(scheme) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + if is_external is not None: + args["isExternal"] = serialize_value(is_external) + if protocol is not None: + args["protocol"] = serialize_value(protocol) + return self._client.invoke_capability("Aspire.Hosting/withEndpoint", args) + + def with_http_endpoint(self, port: float | None = None, target_port: float | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True) -> IResourceWithEndpoints: + """Adds an HTTP endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + return self._client.invoke_capability("Aspire.Hosting/withHttpEndpoint", args) + + def with_https_endpoint(self, port: float | None = None, target_port: float | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True) -> IResourceWithEndpoints: + """Adds an HTTPS endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + return self._client.invoke_capability("Aspire.Hosting/withHttpsEndpoint", args) + + def with_external_http_endpoints(self) -> IResourceWithEndpoints: + """Makes HTTP endpoints externally accessible""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/withExternalHttpEndpoints", args) + + def get_endpoint(self, name: str) -> EndpointReference: + """Gets an endpoint reference""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + return self._client.invoke_capability("Aspire.Hosting/getEndpoint", args) + + def as_http2_service(self) -> IResourceWithEndpoints: + """Configures resource for HTTP/2""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/asHttp2Service", args) + + def with_urls_callback(self, callback: Callable[[ResourceUrlsCallbackContext], None]) -> IResource: + """Customizes displayed URLs via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlsCallback", args) + + def with_urls_callback_async(self, callback: Callable[[ResourceUrlsCallbackContext], None]) -> IResource: + """Customizes displayed URLs via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlsCallbackAsync", args) + + def with_url(self, url: str, display_text: str | None = None) -> IResource: + """Adds or modifies displayed URLs""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["url"] = serialize_value(url) + if display_text is not None: + args["displayText"] = serialize_value(display_text) + return self._client.invoke_capability("Aspire.Hosting/withUrl", args) + + def with_url_expression(self, url: ReferenceExpression, display_text: str | None = None) -> IResource: + """Adds a URL using a reference expression""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["url"] = serialize_value(url) + if display_text is not None: + args["displayText"] = serialize_value(display_text) + return self._client.invoke_capability("Aspire.Hosting/withUrlExpression", args) + + def with_url_for_endpoint(self, endpoint_name: str, callback: Callable[[ResourceUrlAnnotation], None]) -> IResource: + """Customizes the URL for a specific endpoint via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpointName"] = serialize_value(endpoint_name) + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlForEndpoint", args) + + def with_url_for_endpoint_factory(self, endpoint_name: str, callback: Callable[[EndpointReference], ResourceUrlAnnotation]) -> IResourceWithEndpoints: + """Adds a URL for a specific endpoint via factory callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpointName"] = serialize_value(endpoint_name) + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlForEndpointFactory", args) + + def wait_for(self, dependency: IResource) -> IResourceWithWaitSupport: + """Waits for another resource to be ready""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting/waitFor", args) + + def with_explicit_start(self) -> IResource: + """Prevents resource from starting automatically""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/withExplicitStart", args) + + def wait_for_completion(self, dependency: IResource, exit_code: float = 0) -> IResourceWithWaitSupport: + """Waits for resource completion""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + args["exitCode"] = serialize_value(exit_code) + return self._client.invoke_capability("Aspire.Hosting/waitForCompletion", args) + + def with_health_check(self, key: str) -> IResource: + """Adds a health check by key""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["key"] = serialize_value(key) + return self._client.invoke_capability("Aspire.Hosting/withHealthCheck", args) + + def with_http_health_check(self, path: str | None = None, status_code: float | None = None, endpoint_name: str | None = None) -> IResourceWithEndpoints: + """Adds an HTTP health check""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if path is not None: + args["path"] = serialize_value(path) + if status_code is not None: + args["statusCode"] = serialize_value(status_code) + if endpoint_name is not None: + args["endpointName"] = serialize_value(endpoint_name) + return self._client.invoke_capability("Aspire.Hosting/withHttpHealthCheck", args) + + def with_command(self, name: str, display_name: str, execute_command: Callable[[ExecuteCommandContext], ExecuteCommandResult], command_options: CommandOptions | None = None) -> IResource: + """Adds a resource command""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["displayName"] = serialize_value(display_name) + execute_command_id = register_callback(execute_command) if execute_command is not None else None + if execute_command_id is not None: + args["executeCommand"] = execute_command_id + if command_options is not None: + args["commandOptions"] = serialize_value(command_options) + return self._client.invoke_capability("Aspire.Hosting/withCommand", args) + + def with_parent_relationship(self, parent: IResource) -> IResource: + """Sets the parent relationship""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["parent"] = serialize_value(parent) + return self._client.invoke_capability("Aspire.Hosting/withParentRelationship", args) + + def get_resource_name(self) -> str: + """Gets the resource name""" + args: Dict[str, Any] = { "resource": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/getResourceName", args) + + def with_optional_string(self, value: str | None = None, enabled: bool = True) -> IResource: + """Adds an optional string parameter""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if value is not None: + args["value"] = serialize_value(value) + args["enabled"] = serialize_value(enabled) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalString", args) + + def with_config(self, config: TestConfigDto) -> IResource: + """Configures the resource with a DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withConfig", args) + + def test_with_environment_callback(self, callback: Callable[[TestEnvironmentContext], None]) -> IResourceWithEnvironment: + """Configures environment with callback (test version)""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/testWithEnvironmentCallback", args) + + def with_created_at(self, created_at: str) -> IResource: + """Sets the created timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["createdAt"] = serialize_value(created_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCreatedAt", args) + + def with_modified_at(self, modified_at: str) -> IResource: + """Sets the modified timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["modifiedAt"] = serialize_value(modified_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withModifiedAt", args) + + def with_correlation_id(self, correlation_id: str) -> IResource: + """Sets the correlation ID""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["correlationId"] = serialize_value(correlation_id) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCorrelationId", args) + + def with_optional_callback(self, callback: Callable[[TestCallbackContext], None] | None = None) -> IResource: + """Configures with optional callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalCallback", args) + + def with_status(self, status: TestResourceStatus) -> IResource: + """Sets the resource status""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["status"] = serialize_value(status) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withStatus", args) + + def with_nested_config(self, config: TestNestedDto) -> IResource: + """Configures with nested DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withNestedConfig", args) + + def with_validator(self, validator: Callable[[TestResourceContext], bool]) -> IResource: + """Adds validation callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + validator_id = register_callback(validator) if validator is not None else None + if validator_id is not None: + args["validator"] = validator_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withValidator", args) + + def test_wait_for(self, dependency: IResource) -> IResource: + """Waits for another resource (test version)""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/testWaitFor", args) + + def with_dependency(self, dependency: IResourceWithConnectionString) -> IResource: + """Adds a dependency on another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withDependency", args) + + def with_endpoints(self, endpoints: list[str]) -> IResource: + """Sets the endpoints""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpoints"] = serialize_value(endpoints) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withEndpoints", args) + + def with_environment_variables(self, variables: dict[str, str]) -> IResourceWithEnvironment: + """Sets environment variables""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["variables"] = serialize_value(variables) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withEnvironmentVariables", args) + + def with_cancellable_operation(self, operation: Callable[[CancellationToken], None]) -> IResource: + """Performs a cancellable operation""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + operation_id = register_callback(operation) if operation is not None else None + if operation_id is not None: + args["operation"] = operation_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCancellableOperation", args) + + +class DistributedApplication(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def run(self, cancellation_token: CancellationToken | None = None) -> None: + """Runs the distributed application""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + cancellation_token_id = register_cancellation(cancellation_token, self._client) if cancellation_token is not None else None + if cancellation_token_id is not None: + args["cancellationToken"] = cancellation_token_id + self._client.invoke_capability("Aspire.Hosting/run", args) + return None + + +class DistributedApplicationEventSubscription(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class DistributedApplicationExecutionContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def publisher_name(self) -> str: + """Gets the PublisherName property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/DistributedApplicationExecutionContext.publisherName", args) + + def set_publisher_name(self, value: str) -> DistributedApplicationExecutionContext: + """Sets the PublisherName property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting/DistributedApplicationExecutionContext.setPublisherName", args) + + def operation(self) -> DistributedApplicationOperation: + """Gets the Operation property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/DistributedApplicationExecutionContext.operation", args) + + def is_publish_mode(self) -> bool: + """Gets the IsPublishMode property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/DistributedApplicationExecutionContext.isPublishMode", args) + + def is_run_mode(self) -> bool: + """Gets the IsRunMode property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/DistributedApplicationExecutionContext.isRunMode", args) + + +class DistributedApplicationExecutionContextOptions(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class DistributedApplicationResourceEventSubscription(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class EndpointReference(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def endpoint_name(self) -> str: + """Gets the EndpointName property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.endpointName", args) + + def error_message(self) -> str: + """Gets the ErrorMessage property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.errorMessage", args) + + def set_error_message(self, value: str) -> EndpointReference: + """Sets the ErrorMessage property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.setErrorMessage", args) + + def is_allocated(self) -> bool: + """Gets the IsAllocated property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.isAllocated", args) + + def exists(self) -> bool: + """Gets the Exists property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.exists", args) + + def is_http(self) -> bool: + """Gets the IsHttp property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.isHttp", args) + + def is_https(self) -> bool: + """Gets the IsHttps property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.isHttps", args) + + def port(self) -> float: + """Gets the Port property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.port", args) + + def target_port(self) -> float: + """Gets the TargetPort property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.targetPort", args) + + def host(self) -> str: + """Gets the Host property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.host", args) + + def scheme(self) -> str: + """Gets the Scheme property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.scheme", args) + + def url(self) -> str: + """Gets the Url property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.url", args) + + def get_value_async(self, cancellation_token: CancellationToken | None = None) -> str: + """Gets the URL of the endpoint asynchronously""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + cancellation_token_id = register_cancellation(cancellation_token, self._client) if cancellation_token is not None else None + if cancellation_token_id is not None: + args["cancellationToken"] = cancellation_token_id + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/getValueAsync", args) + + +class EndpointReferenceExpression(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def endpoint(self) -> EndpointReference: + """Gets the Endpoint property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.endpoint", args) + + def property(self) -> EndpointProperty: + """Gets the Property property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.property", args) + + def value_expression(self) -> str: + """Gets the ValueExpression property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.valueExpression", args) + + +class EnvironmentCallbackContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + @property + def environment_variables(self) -> AspireDict[str, str | ReferenceExpression]: + """Gets the EnvironmentVariables property""" + if not hasattr(self, '_environment_variables'): + self._environment_variables = AspireDict( + self._handle, + self._client, + "Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.environmentVariables" + ) + return self._environment_variables + + def cancellation_token(self) -> CancellationToken: + """Gets the CancellationToken property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.cancellationToken", args) + + def execution_context(self) -> DistributedApplicationExecutionContext: + """Gets the ExecutionContext property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.executionContext", args) + + +class ExecutableResource(ResourceBuilderBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def with_executable_command(self, command: str) -> ExecutableResource: + """Sets the executable command""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["command"] = serialize_value(command) + return self._client.invoke_capability("Aspire.Hosting/withExecutableCommand", args) + + def with_working_directory(self, working_directory: str) -> ExecutableResource: + """Sets the executable working directory""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["workingDirectory"] = serialize_value(working_directory) + return self._client.invoke_capability("Aspire.Hosting/withWorkingDirectory", args) + + def with_environment(self, name: str, value: str) -> IResourceWithEnvironment: + """Sets an environment variable""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting/withEnvironment", args) + + def with_environment_expression(self, name: str, value: ReferenceExpression) -> IResourceWithEnvironment: + """Adds an environment variable with a reference expression""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentExpression", args) + + def with_environment_callback(self, callback: Callable[[EnvironmentCallbackContext], None]) -> IResourceWithEnvironment: + """Sets environment variables via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentCallback", args) + + def with_environment_callback_async(self, callback: Callable[[EnvironmentCallbackContext], None]) -> IResourceWithEnvironment: + """Sets environment variables via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentCallbackAsync", args) + + def with_args(self, args: list[str]) -> IResourceWithArgs: + """Adds arguments""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["args"] = serialize_value(args) + return self._client.invoke_capability("Aspire.Hosting/withArgs", args) + + def with_args_callback(self, callback: Callable[[CommandLineArgsCallbackContext], None]) -> IResourceWithArgs: + """Sets command-line arguments via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withArgsCallback", args) + + def with_args_callback_async(self, callback: Callable[[CommandLineArgsCallbackContext], None]) -> IResourceWithArgs: + """Sets command-line arguments via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withArgsCallbackAsync", args) + + def with_reference(self, source: IResourceWithConnectionString, connection_name: str | None = None, optional: bool = False) -> IResourceWithEnvironment: + """Adds a reference to another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["source"] = serialize_value(source) + if connection_name is not None: + args["connectionName"] = serialize_value(connection_name) + args["optional"] = serialize_value(optional) + return self._client.invoke_capability("Aspire.Hosting/withReference", args) + + def with_service_reference(self, source: IResourceWithServiceDiscovery) -> IResourceWithEnvironment: + """Adds a service discovery reference to another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["source"] = serialize_value(source) + return self._client.invoke_capability("Aspire.Hosting/withServiceReference", args) + + def with_endpoint(self, port: float | None = None, target_port: float | None = None, scheme: str | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True, is_external: bool | None = None, protocol: ProtocolType | None = None) -> IResourceWithEndpoints: + """Adds a network endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if scheme is not None: + args["scheme"] = serialize_value(scheme) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + if is_external is not None: + args["isExternal"] = serialize_value(is_external) + if protocol is not None: + args["protocol"] = serialize_value(protocol) + return self._client.invoke_capability("Aspire.Hosting/withEndpoint", args) + + def with_http_endpoint(self, port: float | None = None, target_port: float | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True) -> IResourceWithEndpoints: + """Adds an HTTP endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + return self._client.invoke_capability("Aspire.Hosting/withHttpEndpoint", args) + + def with_https_endpoint(self, port: float | None = None, target_port: float | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True) -> IResourceWithEndpoints: + """Adds an HTTPS endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + return self._client.invoke_capability("Aspire.Hosting/withHttpsEndpoint", args) + + def with_external_http_endpoints(self) -> IResourceWithEndpoints: + """Makes HTTP endpoints externally accessible""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/withExternalHttpEndpoints", args) + + def get_endpoint(self, name: str) -> EndpointReference: + """Gets an endpoint reference""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + return self._client.invoke_capability("Aspire.Hosting/getEndpoint", args) + + def as_http2_service(self) -> IResourceWithEndpoints: + """Configures resource for HTTP/2""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/asHttp2Service", args) + + def with_urls_callback(self, callback: Callable[[ResourceUrlsCallbackContext], None]) -> IResource: + """Customizes displayed URLs via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlsCallback", args) + + def with_urls_callback_async(self, callback: Callable[[ResourceUrlsCallbackContext], None]) -> IResource: + """Customizes displayed URLs via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlsCallbackAsync", args) + + def with_url(self, url: str, display_text: str | None = None) -> IResource: + """Adds or modifies displayed URLs""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["url"] = serialize_value(url) + if display_text is not None: + args["displayText"] = serialize_value(display_text) + return self._client.invoke_capability("Aspire.Hosting/withUrl", args) + + def with_url_expression(self, url: ReferenceExpression, display_text: str | None = None) -> IResource: + """Adds a URL using a reference expression""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["url"] = serialize_value(url) + if display_text is not None: + args["displayText"] = serialize_value(display_text) + return self._client.invoke_capability("Aspire.Hosting/withUrlExpression", args) + + def with_url_for_endpoint(self, endpoint_name: str, callback: Callable[[ResourceUrlAnnotation], None]) -> IResource: + """Customizes the URL for a specific endpoint via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpointName"] = serialize_value(endpoint_name) + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlForEndpoint", args) + + def with_url_for_endpoint_factory(self, endpoint_name: str, callback: Callable[[EndpointReference], ResourceUrlAnnotation]) -> IResourceWithEndpoints: + """Adds a URL for a specific endpoint via factory callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpointName"] = serialize_value(endpoint_name) + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlForEndpointFactory", args) + + def wait_for(self, dependency: IResource) -> IResourceWithWaitSupport: + """Waits for another resource to be ready""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting/waitFor", args) + + def with_explicit_start(self) -> IResource: + """Prevents resource from starting automatically""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/withExplicitStart", args) + + def wait_for_completion(self, dependency: IResource, exit_code: float = 0) -> IResourceWithWaitSupport: + """Waits for resource completion""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + args["exitCode"] = serialize_value(exit_code) + return self._client.invoke_capability("Aspire.Hosting/waitForCompletion", args) + + def with_health_check(self, key: str) -> IResource: + """Adds a health check by key""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["key"] = serialize_value(key) + return self._client.invoke_capability("Aspire.Hosting/withHealthCheck", args) + + def with_http_health_check(self, path: str | None = None, status_code: float | None = None, endpoint_name: str | None = None) -> IResourceWithEndpoints: + """Adds an HTTP health check""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if path is not None: + args["path"] = serialize_value(path) + if status_code is not None: + args["statusCode"] = serialize_value(status_code) + if endpoint_name is not None: + args["endpointName"] = serialize_value(endpoint_name) + return self._client.invoke_capability("Aspire.Hosting/withHttpHealthCheck", args) + + def with_command(self, name: str, display_name: str, execute_command: Callable[[ExecuteCommandContext], ExecuteCommandResult], command_options: CommandOptions | None = None) -> IResource: + """Adds a resource command""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["displayName"] = serialize_value(display_name) + execute_command_id = register_callback(execute_command) if execute_command is not None else None + if execute_command_id is not None: + args["executeCommand"] = execute_command_id + if command_options is not None: + args["commandOptions"] = serialize_value(command_options) + return self._client.invoke_capability("Aspire.Hosting/withCommand", args) + + def with_parent_relationship(self, parent: IResource) -> IResource: + """Sets the parent relationship""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["parent"] = serialize_value(parent) + return self._client.invoke_capability("Aspire.Hosting/withParentRelationship", args) + + def get_resource_name(self) -> str: + """Gets the resource name""" + args: Dict[str, Any] = { "resource": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/getResourceName", args) + + def with_optional_string(self, value: str | None = None, enabled: bool = True) -> IResource: + """Adds an optional string parameter""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if value is not None: + args["value"] = serialize_value(value) + args["enabled"] = serialize_value(enabled) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalString", args) + + def with_config(self, config: TestConfigDto) -> IResource: + """Configures the resource with a DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withConfig", args) + + def test_with_environment_callback(self, callback: Callable[[TestEnvironmentContext], None]) -> IResourceWithEnvironment: + """Configures environment with callback (test version)""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/testWithEnvironmentCallback", args) + + def with_created_at(self, created_at: str) -> IResource: + """Sets the created timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["createdAt"] = serialize_value(created_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCreatedAt", args) + + def with_modified_at(self, modified_at: str) -> IResource: + """Sets the modified timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["modifiedAt"] = serialize_value(modified_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withModifiedAt", args) + + def with_correlation_id(self, correlation_id: str) -> IResource: + """Sets the correlation ID""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["correlationId"] = serialize_value(correlation_id) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCorrelationId", args) + + def with_optional_callback(self, callback: Callable[[TestCallbackContext], None] | None = None) -> IResource: + """Configures with optional callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalCallback", args) + + def with_status(self, status: TestResourceStatus) -> IResource: + """Sets the resource status""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["status"] = serialize_value(status) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withStatus", args) + + def with_nested_config(self, config: TestNestedDto) -> IResource: + """Configures with nested DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withNestedConfig", args) + + def with_validator(self, validator: Callable[[TestResourceContext], bool]) -> IResource: + """Adds validation callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + validator_id = register_callback(validator) if validator is not None else None + if validator_id is not None: + args["validator"] = validator_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withValidator", args) + + def test_wait_for(self, dependency: IResource) -> IResource: + """Waits for another resource (test version)""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/testWaitFor", args) + + def with_dependency(self, dependency: IResourceWithConnectionString) -> IResource: + """Adds a dependency on another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withDependency", args) + + def with_endpoints(self, endpoints: list[str]) -> IResource: + """Sets the endpoints""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpoints"] = serialize_value(endpoints) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withEndpoints", args) + + def with_environment_variables(self, variables: dict[str, str]) -> IResourceWithEnvironment: + """Sets environment variables""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["variables"] = serialize_value(variables) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withEnvironmentVariables", args) + + def with_cancellable_operation(self, operation: Callable[[CancellationToken], None]) -> IResource: + """Performs a cancellable operation""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + operation_id = register_callback(operation) if operation is not None else None + if operation_id is not None: + args["operation"] = operation_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCancellableOperation", args) + + +class ExecuteCommandContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def resource_name(self) -> str: + """Gets the ResourceName property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.resourceName", args) + + def set_resource_name(self, value: str) -> ExecuteCommandContext: + """Sets the ResourceName property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.setResourceName", args) + + def cancellation_token(self) -> CancellationToken: + """Gets the CancellationToken property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.cancellationToken", args) + + def set_cancellation_token(self, value: CancellationToken) -> ExecuteCommandContext: + """Sets the CancellationToken property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + value_id = register_cancellation(value, self._client) if value is not None else None + if value_id is not None: + args["value"] = value_id + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.setCancellationToken", args) + + +class IDistributedApplicationBuilder(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def add_container(self, name: str, image: str) -> ContainerResource: + """Adds a container resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["image"] = serialize_value(image) + return self._client.invoke_capability("Aspire.Hosting/addContainer", args) + + def add_executable(self, name: str, command: str, working_directory: str, args: list[str]) -> ExecutableResource: + """Adds an executable resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["command"] = serialize_value(command) + args["workingDirectory"] = serialize_value(working_directory) + args["args"] = serialize_value(args) + return self._client.invoke_capability("Aspire.Hosting/addExecutable", args) + + def app_host_directory(self) -> str: + """Gets the AppHostDirectory property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/IDistributedApplicationBuilder.appHostDirectory", args) + + def eventing(self) -> IDistributedApplicationEventing: + """Gets the Eventing property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/IDistributedApplicationBuilder.eventing", args) + + def execution_context(self) -> DistributedApplicationExecutionContext: + """Gets the ExecutionContext property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/IDistributedApplicationBuilder.executionContext", args) + + def build(self) -> DistributedApplication: + """Builds the distributed application""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/build", args) + + def add_parameter(self, name: str, secret: bool = False) -> ParameterResource: + """Adds a parameter resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["secret"] = serialize_value(secret) + return self._client.invoke_capability("Aspire.Hosting/addParameter", args) + + def add_connection_string(self, name: str, environment_variable_name: str | None = None) -> IResourceWithConnectionString: + """Adds a connection string resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + if environment_variable_name is not None: + args["environmentVariableName"] = serialize_value(environment_variable_name) + return self._client.invoke_capability("Aspire.Hosting/addConnectionString", args) + + def add_project(self, name: str, project_path: str, launch_profile_name: str) -> ProjectResource: + """Adds a .NET project resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["projectPath"] = serialize_value(project_path) + args["launchProfileName"] = serialize_value(launch_profile_name) + return self._client.invoke_capability("Aspire.Hosting/addProject", args) + + def add_test_redis(self, name: str, port: float | None = None) -> TestRedisResource: + """Adds a test Redis resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + if port is not None: + args["port"] = serialize_value(port) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/addTestRedis", args) + + +class IDistributedApplicationEvent(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class IDistributedApplicationEventing(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def unsubscribe(self, subscription: DistributedApplicationEventSubscription) -> None: + """Invokes the Unsubscribe method""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["subscription"] = serialize_value(subscription) + self._client.invoke_capability("Aspire.Hosting.Eventing/IDistributedApplicationEventing.unsubscribe", args) + return None + + +class IDistributedApplicationResourceEvent(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class IResource(ResourceBuilderBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class IResourceWithArgs(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class IResourceWithConnectionString(ResourceBuilderBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class IResourceWithEndpoints(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class IResourceWithEnvironment(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class IResourceWithServiceDiscovery(ResourceBuilderBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class IResourceWithWaitSupport(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class ParameterResource(ResourceBuilderBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def with_description(self, description: str, enable_markdown: bool = False) -> ParameterResource: + """Sets a parameter description""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["description"] = serialize_value(description) + args["enableMarkdown"] = serialize_value(enable_markdown) + return self._client.invoke_capability("Aspire.Hosting/withDescription", args) + + def with_urls_callback(self, callback: Callable[[ResourceUrlsCallbackContext], None]) -> IResource: + """Customizes displayed URLs via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlsCallback", args) + + def with_urls_callback_async(self, callback: Callable[[ResourceUrlsCallbackContext], None]) -> IResource: + """Customizes displayed URLs via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlsCallbackAsync", args) + + def with_url(self, url: str, display_text: str | None = None) -> IResource: + """Adds or modifies displayed URLs""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["url"] = serialize_value(url) + if display_text is not None: + args["displayText"] = serialize_value(display_text) + return self._client.invoke_capability("Aspire.Hosting/withUrl", args) + + def with_url_expression(self, url: ReferenceExpression, display_text: str | None = None) -> IResource: + """Adds a URL using a reference expression""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["url"] = serialize_value(url) + if display_text is not None: + args["displayText"] = serialize_value(display_text) + return self._client.invoke_capability("Aspire.Hosting/withUrlExpression", args) + + def with_url_for_endpoint(self, endpoint_name: str, callback: Callable[[ResourceUrlAnnotation], None]) -> IResource: + """Customizes the URL for a specific endpoint via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpointName"] = serialize_value(endpoint_name) + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlForEndpoint", args) + + def with_explicit_start(self) -> IResource: + """Prevents resource from starting automatically""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/withExplicitStart", args) + + def with_health_check(self, key: str) -> IResource: + """Adds a health check by key""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["key"] = serialize_value(key) + return self._client.invoke_capability("Aspire.Hosting/withHealthCheck", args) + + def with_command(self, name: str, display_name: str, execute_command: Callable[[ExecuteCommandContext], ExecuteCommandResult], command_options: CommandOptions | None = None) -> IResource: + """Adds a resource command""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["displayName"] = serialize_value(display_name) + execute_command_id = register_callback(execute_command) if execute_command is not None else None + if execute_command_id is not None: + args["executeCommand"] = execute_command_id + if command_options is not None: + args["commandOptions"] = serialize_value(command_options) + return self._client.invoke_capability("Aspire.Hosting/withCommand", args) + + def with_parent_relationship(self, parent: IResource) -> IResource: + """Sets the parent relationship""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["parent"] = serialize_value(parent) + return self._client.invoke_capability("Aspire.Hosting/withParentRelationship", args) + + def get_resource_name(self) -> str: + """Gets the resource name""" + args: Dict[str, Any] = { "resource": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/getResourceName", args) + + def with_optional_string(self, value: str | None = None, enabled: bool = True) -> IResource: + """Adds an optional string parameter""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if value is not None: + args["value"] = serialize_value(value) + args["enabled"] = serialize_value(enabled) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalString", args) + + def with_config(self, config: TestConfigDto) -> IResource: + """Configures the resource with a DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withConfig", args) + + def with_created_at(self, created_at: str) -> IResource: + """Sets the created timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["createdAt"] = serialize_value(created_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCreatedAt", args) + + def with_modified_at(self, modified_at: str) -> IResource: + """Sets the modified timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["modifiedAt"] = serialize_value(modified_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withModifiedAt", args) + + def with_correlation_id(self, correlation_id: str) -> IResource: + """Sets the correlation ID""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["correlationId"] = serialize_value(correlation_id) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCorrelationId", args) + + def with_optional_callback(self, callback: Callable[[TestCallbackContext], None] | None = None) -> IResource: + """Configures with optional callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalCallback", args) + + def with_status(self, status: TestResourceStatus) -> IResource: + """Sets the resource status""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["status"] = serialize_value(status) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withStatus", args) + + def with_nested_config(self, config: TestNestedDto) -> IResource: + """Configures with nested DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withNestedConfig", args) + + def with_validator(self, validator: Callable[[TestResourceContext], bool]) -> IResource: + """Adds validation callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + validator_id = register_callback(validator) if validator is not None else None + if validator_id is not None: + args["validator"] = validator_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withValidator", args) + + def test_wait_for(self, dependency: IResource) -> IResource: + """Waits for another resource (test version)""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/testWaitFor", args) + + def with_dependency(self, dependency: IResourceWithConnectionString) -> IResource: + """Adds a dependency on another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withDependency", args) + + def with_endpoints(self, endpoints: list[str]) -> IResource: + """Sets the endpoints""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpoints"] = serialize_value(endpoints) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withEndpoints", args) + + def with_cancellable_operation(self, operation: Callable[[CancellationToken], None]) -> IResource: + """Performs a cancellable operation""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + operation_id = register_callback(operation) if operation is not None else None + if operation_id is not None: + args["operation"] = operation_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCancellableOperation", args) + + +class ProjectResource(ResourceBuilderBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def with_replicas(self, replicas: float) -> ProjectResource: + """Sets the number of replicas""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["replicas"] = serialize_value(replicas) + return self._client.invoke_capability("Aspire.Hosting/withReplicas", args) + + def with_environment(self, name: str, value: str) -> IResourceWithEnvironment: + """Sets an environment variable""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting/withEnvironment", args) + + def with_environment_expression(self, name: str, value: ReferenceExpression) -> IResourceWithEnvironment: + """Adds an environment variable with a reference expression""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentExpression", args) + + def with_environment_callback(self, callback: Callable[[EnvironmentCallbackContext], None]) -> IResourceWithEnvironment: + """Sets environment variables via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentCallback", args) + + def with_environment_callback_async(self, callback: Callable[[EnvironmentCallbackContext], None]) -> IResourceWithEnvironment: + """Sets environment variables via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentCallbackAsync", args) + + def with_args(self, args: list[str]) -> IResourceWithArgs: + """Adds arguments""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["args"] = serialize_value(args) + return self._client.invoke_capability("Aspire.Hosting/withArgs", args) + + def with_args_callback(self, callback: Callable[[CommandLineArgsCallbackContext], None]) -> IResourceWithArgs: + """Sets command-line arguments via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withArgsCallback", args) + + def with_args_callback_async(self, callback: Callable[[CommandLineArgsCallbackContext], None]) -> IResourceWithArgs: + """Sets command-line arguments via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withArgsCallbackAsync", args) + + def with_reference(self, source: IResourceWithConnectionString, connection_name: str | None = None, optional: bool = False) -> IResourceWithEnvironment: + """Adds a reference to another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["source"] = serialize_value(source) + if connection_name is not None: + args["connectionName"] = serialize_value(connection_name) + args["optional"] = serialize_value(optional) + return self._client.invoke_capability("Aspire.Hosting/withReference", args) + + def with_service_reference(self, source: IResourceWithServiceDiscovery) -> IResourceWithEnvironment: + """Adds a service discovery reference to another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["source"] = serialize_value(source) + return self._client.invoke_capability("Aspire.Hosting/withServiceReference", args) + + def with_endpoint(self, port: float | None = None, target_port: float | None = None, scheme: str | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True, is_external: bool | None = None, protocol: ProtocolType | None = None) -> IResourceWithEndpoints: + """Adds a network endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if scheme is not None: + args["scheme"] = serialize_value(scheme) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + if is_external is not None: + args["isExternal"] = serialize_value(is_external) + if protocol is not None: + args["protocol"] = serialize_value(protocol) + return self._client.invoke_capability("Aspire.Hosting/withEndpoint", args) + + def with_http_endpoint(self, port: float | None = None, target_port: float | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True) -> IResourceWithEndpoints: + """Adds an HTTP endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + return self._client.invoke_capability("Aspire.Hosting/withHttpEndpoint", args) + + def with_https_endpoint(self, port: float | None = None, target_port: float | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True) -> IResourceWithEndpoints: + """Adds an HTTPS endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + return self._client.invoke_capability("Aspire.Hosting/withHttpsEndpoint", args) + + def with_external_http_endpoints(self) -> IResourceWithEndpoints: + """Makes HTTP endpoints externally accessible""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/withExternalHttpEndpoints", args) + + def get_endpoint(self, name: str) -> EndpointReference: + """Gets an endpoint reference""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + return self._client.invoke_capability("Aspire.Hosting/getEndpoint", args) + + def as_http2_service(self) -> IResourceWithEndpoints: + """Configures resource for HTTP/2""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/asHttp2Service", args) + + def with_urls_callback(self, callback: Callable[[ResourceUrlsCallbackContext], None]) -> IResource: + """Customizes displayed URLs via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlsCallback", args) + + def with_urls_callback_async(self, callback: Callable[[ResourceUrlsCallbackContext], None]) -> IResource: + """Customizes displayed URLs via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlsCallbackAsync", args) + + def with_url(self, url: str, display_text: str | None = None) -> IResource: + """Adds or modifies displayed URLs""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["url"] = serialize_value(url) + if display_text is not None: + args["displayText"] = serialize_value(display_text) + return self._client.invoke_capability("Aspire.Hosting/withUrl", args) + + def with_url_expression(self, url: ReferenceExpression, display_text: str | None = None) -> IResource: + """Adds a URL using a reference expression""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["url"] = serialize_value(url) + if display_text is not None: + args["displayText"] = serialize_value(display_text) + return self._client.invoke_capability("Aspire.Hosting/withUrlExpression", args) + + def with_url_for_endpoint(self, endpoint_name: str, callback: Callable[[ResourceUrlAnnotation], None]) -> IResource: + """Customizes the URL for a specific endpoint via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpointName"] = serialize_value(endpoint_name) + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlForEndpoint", args) + + def with_url_for_endpoint_factory(self, endpoint_name: str, callback: Callable[[EndpointReference], ResourceUrlAnnotation]) -> IResourceWithEndpoints: + """Adds a URL for a specific endpoint via factory callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpointName"] = serialize_value(endpoint_name) + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlForEndpointFactory", args) + + def wait_for(self, dependency: IResource) -> IResourceWithWaitSupport: + """Waits for another resource to be ready""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting/waitFor", args) + + def with_explicit_start(self) -> IResource: + """Prevents resource from starting automatically""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/withExplicitStart", args) + + def wait_for_completion(self, dependency: IResource, exit_code: float = 0) -> IResourceWithWaitSupport: + """Waits for resource completion""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + args["exitCode"] = serialize_value(exit_code) + return self._client.invoke_capability("Aspire.Hosting/waitForCompletion", args) + + def with_health_check(self, key: str) -> IResource: + """Adds a health check by key""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["key"] = serialize_value(key) + return self._client.invoke_capability("Aspire.Hosting/withHealthCheck", args) + + def with_http_health_check(self, path: str | None = None, status_code: float | None = None, endpoint_name: str | None = None) -> IResourceWithEndpoints: + """Adds an HTTP health check""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if path is not None: + args["path"] = serialize_value(path) + if status_code is not None: + args["statusCode"] = serialize_value(status_code) + if endpoint_name is not None: + args["endpointName"] = serialize_value(endpoint_name) + return self._client.invoke_capability("Aspire.Hosting/withHttpHealthCheck", args) + + def with_command(self, name: str, display_name: str, execute_command: Callable[[ExecuteCommandContext], ExecuteCommandResult], command_options: CommandOptions | None = None) -> IResource: + """Adds a resource command""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["displayName"] = serialize_value(display_name) + execute_command_id = register_callback(execute_command) if execute_command is not None else None + if execute_command_id is not None: + args["executeCommand"] = execute_command_id + if command_options is not None: + args["commandOptions"] = serialize_value(command_options) + return self._client.invoke_capability("Aspire.Hosting/withCommand", args) + + def with_parent_relationship(self, parent: IResource) -> IResource: + """Sets the parent relationship""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["parent"] = serialize_value(parent) + return self._client.invoke_capability("Aspire.Hosting/withParentRelationship", args) + + def get_resource_name(self) -> str: + """Gets the resource name""" + args: Dict[str, Any] = { "resource": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/getResourceName", args) + + def with_optional_string(self, value: str | None = None, enabled: bool = True) -> IResource: + """Adds an optional string parameter""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if value is not None: + args["value"] = serialize_value(value) + args["enabled"] = serialize_value(enabled) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalString", args) + + def with_config(self, config: TestConfigDto) -> IResource: + """Configures the resource with a DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withConfig", args) + + def test_with_environment_callback(self, callback: Callable[[TestEnvironmentContext], None]) -> IResourceWithEnvironment: + """Configures environment with callback (test version)""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/testWithEnvironmentCallback", args) + + def with_created_at(self, created_at: str) -> IResource: + """Sets the created timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["createdAt"] = serialize_value(created_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCreatedAt", args) + + def with_modified_at(self, modified_at: str) -> IResource: + """Sets the modified timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["modifiedAt"] = serialize_value(modified_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withModifiedAt", args) + + def with_correlation_id(self, correlation_id: str) -> IResource: + """Sets the correlation ID""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["correlationId"] = serialize_value(correlation_id) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCorrelationId", args) + + def with_optional_callback(self, callback: Callable[[TestCallbackContext], None] | None = None) -> IResource: + """Configures with optional callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalCallback", args) + + def with_status(self, status: TestResourceStatus) -> IResource: + """Sets the resource status""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["status"] = serialize_value(status) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withStatus", args) + + def with_nested_config(self, config: TestNestedDto) -> IResource: + """Configures with nested DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withNestedConfig", args) + + def with_validator(self, validator: Callable[[TestResourceContext], bool]) -> IResource: + """Adds validation callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + validator_id = register_callback(validator) if validator is not None else None + if validator_id is not None: + args["validator"] = validator_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withValidator", args) + + def test_wait_for(self, dependency: IResource) -> IResource: + """Waits for another resource (test version)""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/testWaitFor", args) + + def with_dependency(self, dependency: IResourceWithConnectionString) -> IResource: + """Adds a dependency on another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withDependency", args) + + def with_endpoints(self, endpoints: list[str]) -> IResource: + """Sets the endpoints""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpoints"] = serialize_value(endpoints) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withEndpoints", args) + + def with_environment_variables(self, variables: dict[str, str]) -> IResourceWithEnvironment: + """Sets environment variables""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["variables"] = serialize_value(variables) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withEnvironmentVariables", args) + + def with_cancellable_operation(self, operation: Callable[[CancellationToken], None]) -> IResource: + """Performs a cancellable operation""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + operation_id = register_callback(operation) if operation is not None else None + if operation_id is not None: + args["operation"] = operation_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCancellableOperation", args) + + +class ReferenceExpression(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +class ResourceUrlsCallbackContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + @property + def urls(self) -> AspireList[ResourceUrlAnnotation]: + """Gets the Urls property""" + if not hasattr(self, '_urls'): + self._urls = AspireList( + self._handle, + self._client, + "Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.urls" + ) + return self._urls + + def cancellation_token(self) -> CancellationToken: + """Gets the CancellationToken property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.cancellationToken", args) + + def execution_context(self) -> DistributedApplicationExecutionContext: + """Gets the ExecutionContext property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.executionContext", args) + + +class TestCallbackContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def name(self) -> str: + """Gets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.name", args) + + def set_name(self, value: str) -> TestCallbackContext: + """Sets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setName", args) + + def value(self) -> float: + """Gets the Value property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.value", args) + + def set_value(self, value: float) -> TestCallbackContext: + """Sets the Value property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setValue", args) + + def cancellation_token(self) -> CancellationToken: + """Gets the CancellationToken property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.cancellationToken", args) + + def set_cancellation_token(self, value: CancellationToken) -> TestCallbackContext: + """Sets the CancellationToken property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + value_id = register_cancellation(value, self._client) if value is not None else None + if value_id is not None: + args["value"] = value_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setCancellationToken", args) + + +class TestCollectionContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + @property + def items(self) -> AspireList[str]: + """Gets the Items property""" + if not hasattr(self, '_items'): + self._items = AspireList( + self._handle, + self._client, + "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.items" + ) + return self._items + + @property + def metadata(self) -> AspireDict[str, str]: + """Gets the Metadata property""" + if not hasattr(self, '_metadata'): + self._metadata = AspireDict( + self._handle, + self._client, + "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.metadata" + ) + return self._metadata + + +class TestEnvironmentContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def name(self) -> str: + """Gets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.name", args) + + def set_name(self, value: str) -> TestEnvironmentContext: + """Sets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setName", args) + + def description(self) -> str: + """Gets the Description property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.description", args) + + def set_description(self, value: str) -> TestEnvironmentContext: + """Sets the Description property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setDescription", args) + + def priority(self) -> float: + """Gets the Priority property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.priority", args) + + def set_priority(self, value: float) -> TestEnvironmentContext: + """Sets the Priority property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setPriority", args) + + +class TestRedisResource(ResourceBuilderBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def with_bind_mount(self, source: str, target: str, is_read_only: bool = False) -> ContainerResource: + """Adds a bind mount""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["source"] = serialize_value(source) + args["target"] = serialize_value(target) + args["isReadOnly"] = serialize_value(is_read_only) + return self._client.invoke_capability("Aspire.Hosting/withBindMount", args) + + def with_entrypoint(self, entrypoint: str) -> ContainerResource: + """Sets the container entrypoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["entrypoint"] = serialize_value(entrypoint) + return self._client.invoke_capability("Aspire.Hosting/withEntrypoint", args) + + def with_image_tag(self, tag: str) -> ContainerResource: + """Sets the container image tag""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["tag"] = serialize_value(tag) + return self._client.invoke_capability("Aspire.Hosting/withImageTag", args) + + def with_image_registry(self, registry: str) -> ContainerResource: + """Sets the container image registry""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["registry"] = serialize_value(registry) + return self._client.invoke_capability("Aspire.Hosting/withImageRegistry", args) + + def with_image(self, image: str, tag: str | None = None) -> ContainerResource: + """Sets the container image""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["image"] = serialize_value(image) + if tag is not None: + args["tag"] = serialize_value(tag) + return self._client.invoke_capability("Aspire.Hosting/withImage", args) + + def with_container_runtime_args(self, args: list[str]) -> ContainerResource: + """Adds runtime arguments for the container""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["args"] = serialize_value(args) + return self._client.invoke_capability("Aspire.Hosting/withContainerRuntimeArgs", args) + + def with_lifetime(self, lifetime: ContainerLifetime) -> ContainerResource: + """Sets the lifetime behavior of the container resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["lifetime"] = serialize_value(lifetime) + return self._client.invoke_capability("Aspire.Hosting/withLifetime", args) + + def with_image_pull_policy(self, pull_policy: ImagePullPolicy) -> ContainerResource: + """Sets the container image pull policy""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["pullPolicy"] = serialize_value(pull_policy) + return self._client.invoke_capability("Aspire.Hosting/withImagePullPolicy", args) + + def with_container_name(self, name: str) -> ContainerResource: + """Sets the container name""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + return self._client.invoke_capability("Aspire.Hosting/withContainerName", args) + + def with_environment(self, name: str, value: str) -> IResourceWithEnvironment: + """Sets an environment variable""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting/withEnvironment", args) + + def with_environment_expression(self, name: str, value: ReferenceExpression) -> IResourceWithEnvironment: + """Adds an environment variable with a reference expression""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentExpression", args) + + def with_environment_callback(self, callback: Callable[[EnvironmentCallbackContext], None]) -> IResourceWithEnvironment: + """Sets environment variables via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentCallback", args) + + def with_environment_callback_async(self, callback: Callable[[EnvironmentCallbackContext], None]) -> IResourceWithEnvironment: + """Sets environment variables via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withEnvironmentCallbackAsync", args) + + def with_args(self, args: list[str]) -> IResourceWithArgs: + """Adds arguments""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["args"] = serialize_value(args) + return self._client.invoke_capability("Aspire.Hosting/withArgs", args) + + def with_args_callback(self, callback: Callable[[CommandLineArgsCallbackContext], None]) -> IResourceWithArgs: + """Sets command-line arguments via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withArgsCallback", args) + + def with_args_callback_async(self, callback: Callable[[CommandLineArgsCallbackContext], None]) -> IResourceWithArgs: + """Sets command-line arguments via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withArgsCallbackAsync", args) + + def with_reference(self, source: IResourceWithConnectionString, connection_name: str | None = None, optional: bool = False) -> IResourceWithEnvironment: + """Adds a reference to another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["source"] = serialize_value(source) + if connection_name is not None: + args["connectionName"] = serialize_value(connection_name) + args["optional"] = serialize_value(optional) + return self._client.invoke_capability("Aspire.Hosting/withReference", args) + + def with_service_reference(self, source: IResourceWithServiceDiscovery) -> IResourceWithEnvironment: + """Adds a service discovery reference to another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["source"] = serialize_value(source) + return self._client.invoke_capability("Aspire.Hosting/withServiceReference", args) + + def with_endpoint(self, port: float | None = None, target_port: float | None = None, scheme: str | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True, is_external: bool | None = None, protocol: ProtocolType | None = None) -> IResourceWithEndpoints: + """Adds a network endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if scheme is not None: + args["scheme"] = serialize_value(scheme) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + if is_external is not None: + args["isExternal"] = serialize_value(is_external) + if protocol is not None: + args["protocol"] = serialize_value(protocol) + return self._client.invoke_capability("Aspire.Hosting/withEndpoint", args) + + def with_http_endpoint(self, port: float | None = None, target_port: float | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True) -> IResourceWithEndpoints: + """Adds an HTTP endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + return self._client.invoke_capability("Aspire.Hosting/withHttpEndpoint", args) + + def with_https_endpoint(self, port: float | None = None, target_port: float | None = None, name: str | None = None, env: str | None = None, is_proxied: bool = True) -> IResourceWithEndpoints: + """Adds an HTTPS endpoint""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if port is not None: + args["port"] = serialize_value(port) + if target_port is not None: + args["targetPort"] = serialize_value(target_port) + if name is not None: + args["name"] = serialize_value(name) + if env is not None: + args["env"] = serialize_value(env) + args["isProxied"] = serialize_value(is_proxied) + return self._client.invoke_capability("Aspire.Hosting/withHttpsEndpoint", args) + + def with_external_http_endpoints(self) -> IResourceWithEndpoints: + """Makes HTTP endpoints externally accessible""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/withExternalHttpEndpoints", args) + + def get_endpoint(self, name: str) -> EndpointReference: + """Gets an endpoint reference""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + return self._client.invoke_capability("Aspire.Hosting/getEndpoint", args) + + def as_http2_service(self) -> IResourceWithEndpoints: + """Configures resource for HTTP/2""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/asHttp2Service", args) + + def with_urls_callback(self, callback: Callable[[ResourceUrlsCallbackContext], None]) -> IResource: + """Customizes displayed URLs via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlsCallback", args) + + def with_urls_callback_async(self, callback: Callable[[ResourceUrlsCallbackContext], None]) -> IResource: + """Customizes displayed URLs via async callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlsCallbackAsync", args) + + def with_url(self, url: str, display_text: str | None = None) -> IResource: + """Adds or modifies displayed URLs""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["url"] = serialize_value(url) + if display_text is not None: + args["displayText"] = serialize_value(display_text) + return self._client.invoke_capability("Aspire.Hosting/withUrl", args) + + def with_url_expression(self, url: ReferenceExpression, display_text: str | None = None) -> IResource: + """Adds a URL using a reference expression""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["url"] = serialize_value(url) + if display_text is not None: + args["displayText"] = serialize_value(display_text) + return self._client.invoke_capability("Aspire.Hosting/withUrlExpression", args) + + def with_url_for_endpoint(self, endpoint_name: str, callback: Callable[[ResourceUrlAnnotation], None]) -> IResource: + """Customizes the URL for a specific endpoint via callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpointName"] = serialize_value(endpoint_name) + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlForEndpoint", args) + + def with_url_for_endpoint_factory(self, endpoint_name: str, callback: Callable[[EndpointReference], ResourceUrlAnnotation]) -> IResourceWithEndpoints: + """Adds a URL for a specific endpoint via factory callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpointName"] = serialize_value(endpoint_name) + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting/withUrlForEndpointFactory", args) + + def wait_for(self, dependency: IResource) -> IResourceWithWaitSupport: + """Waits for another resource to be ready""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting/waitFor", args) + + def with_explicit_start(self) -> IResource: + """Prevents resource from starting automatically""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/withExplicitStart", args) + + def wait_for_completion(self, dependency: IResource, exit_code: float = 0) -> IResourceWithWaitSupport: + """Waits for resource completion""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + args["exitCode"] = serialize_value(exit_code) + return self._client.invoke_capability("Aspire.Hosting/waitForCompletion", args) + + def with_health_check(self, key: str) -> IResource: + """Adds a health check by key""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["key"] = serialize_value(key) + return self._client.invoke_capability("Aspire.Hosting/withHealthCheck", args) + + def with_http_health_check(self, path: str | None = None, status_code: float | None = None, endpoint_name: str | None = None) -> IResourceWithEndpoints: + """Adds an HTTP health check""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if path is not None: + args["path"] = serialize_value(path) + if status_code is not None: + args["statusCode"] = serialize_value(status_code) + if endpoint_name is not None: + args["endpointName"] = serialize_value(endpoint_name) + return self._client.invoke_capability("Aspire.Hosting/withHttpHealthCheck", args) + + def with_command(self, name: str, display_name: str, execute_command: Callable[[ExecuteCommandContext], ExecuteCommandResult], command_options: CommandOptions | None = None) -> IResource: + """Adds a resource command""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["name"] = serialize_value(name) + args["displayName"] = serialize_value(display_name) + execute_command_id = register_callback(execute_command) if execute_command is not None else None + if execute_command_id is not None: + args["executeCommand"] = execute_command_id + if command_options is not None: + args["commandOptions"] = serialize_value(command_options) + return self._client.invoke_capability("Aspire.Hosting/withCommand", args) + + def with_parent_relationship(self, parent: IResource) -> IResource: + """Sets the parent relationship""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["parent"] = serialize_value(parent) + return self._client.invoke_capability("Aspire.Hosting/withParentRelationship", args) + + def with_volume(self, target: str, name: str | None = None, is_read_only: bool = False) -> ContainerResource: + """Adds a volume""" + args: Dict[str, Any] = { "resource": serialize_value(self._handle) } + args["target"] = serialize_value(target) + if name is not None: + args["name"] = serialize_value(name) + args["isReadOnly"] = serialize_value(is_read_only) + return self._client.invoke_capability("Aspire.Hosting/withVolume", args) + + def get_resource_name(self) -> str: + """Gets the resource name""" + args: Dict[str, Any] = { "resource": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting/getResourceName", args) + + def with_persistence(self, mode: TestPersistenceMode = None) -> TestRedisResource: + """Configures the Redis resource with persistence""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["mode"] = serialize_value(mode) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withPersistence", args) + + def with_optional_string(self, value: str | None = None, enabled: bool = True) -> IResource: + """Adds an optional string parameter""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + if value is not None: + args["value"] = serialize_value(value) + args["enabled"] = serialize_value(enabled) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalString", args) + + def with_config(self, config: TestConfigDto) -> IResource: + """Configures the resource with a DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withConfig", args) + + @property + def get_tags(self) -> AspireList[str]: + """Gets the tags for the resource""" + if not hasattr(self, '_get_tags'): + self._get_tags = AspireList( + self._handle, + self._client, + "Aspire.Hosting.CodeGeneration.Python.Tests/getTags" + ) + return self._get_tags + + @property + def get_metadata(self) -> AspireDict[str, str]: + """Gets the metadata for the resource""" + if not hasattr(self, '_get_metadata'): + self._get_metadata = AspireDict( + self._handle, + self._client, + "Aspire.Hosting.CodeGeneration.Python.Tests/getMetadata" + ) + return self._get_metadata + + def with_connection_string(self, connection_string: ReferenceExpression) -> IResourceWithConnectionString: + """Sets the connection string using a reference expression""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["connectionString"] = serialize_value(connection_string) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withConnectionString", args) + + def test_with_environment_callback(self, callback: Callable[[TestEnvironmentContext], None]) -> IResourceWithEnvironment: + """Configures environment with callback (test version)""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/testWithEnvironmentCallback", args) + + def with_created_at(self, created_at: str) -> IResource: + """Sets the created timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["createdAt"] = serialize_value(created_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCreatedAt", args) + + def with_modified_at(self, modified_at: str) -> IResource: + """Sets the modified timestamp""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["modifiedAt"] = serialize_value(modified_at) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withModifiedAt", args) + + def with_correlation_id(self, correlation_id: str) -> IResource: + """Sets the correlation ID""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["correlationId"] = serialize_value(correlation_id) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCorrelationId", args) + + def with_optional_callback(self, callback: Callable[[TestCallbackContext], None] | None = None) -> IResource: + """Configures with optional callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + callback_id = register_callback(callback) if callback is not None else None + if callback_id is not None: + args["callback"] = callback_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalCallback", args) + + def with_status(self, status: TestResourceStatus) -> IResource: + """Sets the resource status""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["status"] = serialize_value(status) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withStatus", args) + + def with_nested_config(self, config: TestNestedDto) -> IResource: + """Configures with nested DTO""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["config"] = serialize_value(config) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withNestedConfig", args) + + def with_validator(self, validator: Callable[[TestResourceContext], bool]) -> IResource: + """Adds validation callback""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + validator_id = register_callback(validator) if validator is not None else None + if validator_id is not None: + args["validator"] = validator_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withValidator", args) + + def test_wait_for(self, dependency: IResource) -> IResource: + """Waits for another resource (test version)""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/testWaitFor", args) + + def get_endpoints(self) -> list[str]: + """Gets the endpoints""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/getEndpoints", args) + + def with_connection_string_direct(self, connection_string: str) -> IResourceWithConnectionString: + """Sets connection string using direct interface target""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["connectionString"] = serialize_value(connection_string) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withConnectionStringDirect", args) + + def with_redis_specific(self, option: str) -> TestRedisResource: + """Redis-specific configuration""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["option"] = serialize_value(option) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withRedisSpecific", args) + + def with_dependency(self, dependency: IResourceWithConnectionString) -> IResource: + """Adds a dependency on another resource""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["dependency"] = serialize_value(dependency) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withDependency", args) + + def with_endpoints(self, endpoints: list[str]) -> IResource: + """Sets the endpoints""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["endpoints"] = serialize_value(endpoints) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withEndpoints", args) + + def with_environment_variables(self, variables: dict[str, str]) -> IResourceWithEnvironment: + """Sets environment variables""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["variables"] = serialize_value(variables) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withEnvironmentVariables", args) + + def get_status_async(self, cancellation_token: CancellationToken | None = None) -> str: + """Gets the status of the resource asynchronously""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + cancellation_token_id = register_cancellation(cancellation_token, self._client) if cancellation_token is not None else None + if cancellation_token_id is not None: + args["cancellationToken"] = cancellation_token_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/getStatusAsync", args) + + def with_cancellable_operation(self, operation: Callable[[CancellationToken], None]) -> IResource: + """Performs a cancellable operation""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + operation_id = register_callback(operation) if operation is not None else None + if operation_id is not None: + args["operation"] = operation_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/withCancellableOperation", args) + + def wait_for_ready_async(self, timeout: float, cancellation_token: CancellationToken | None = None) -> bool: + """Waits for the resource to be ready""" + args: Dict[str, Any] = { "builder": serialize_value(self._handle) } + args["timeout"] = serialize_value(timeout) + cancellation_token_id = register_cancellation(cancellation_token, self._client) if cancellation_token is not None else None + if cancellation_token_id is not None: + args["cancellationToken"] = cancellation_token_id + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.Python.Tests/waitForReadyAsync", args) + + +class TestResourceContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + def name(self) -> str: + """Gets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.name", args) + + def set_name(self, value: str) -> TestResourceContext: + """Sets the Name property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setName", args) + + def value(self) -> float: + """Gets the Value property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.value", args) + + def set_value(self, value: float) -> TestResourceContext: + """Sets the Value property""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValue", args) + + def get_value_async(self) -> str: + """Invokes the GetValueAsync method""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.getValueAsync", args) + + def set_value_async(self, value: str) -> None: + """Invokes the SetValueAsync method""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + args["value"] = serialize_value(value) + self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValueAsync", args) + return None + + def validate_async(self) -> bool: + """Invokes the ValidateAsync method""" + args: Dict[str, Any] = { "context": serialize_value(self._handle) } + return self._client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.validateAsync", args) + + +class UpdateCommandStateContext(HandleWrapperBase): + def __init__(self, handle: Handle, client: AspireClient): + super().__init__(handle, client) + + pass + +# ============================================================================ +# Handle wrapper registrations +# ============================================================================ + +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.DistributedApplication", lambda handle, client: DistributedApplication(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext", lambda handle, client: DistributedApplicationExecutionContext(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContextOptions", lambda handle, client: DistributedApplicationExecutionContextOptions(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder", lambda handle, client: IDistributedApplicationBuilder(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription", lambda handle, client: DistributedApplicationEventSubscription(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationResourceEventSubscription", lambda handle, client: DistributedApplicationResourceEventSubscription(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEvent", lambda handle, client: IDistributedApplicationEvent(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationResourceEvent", lambda handle, client: IDistributedApplicationResourceEvent(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEventing", lambda handle, client: IDistributedApplicationEventing(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.CommandLineArgsCallbackContext", lambda handle, client: CommandLineArgsCallbackContext(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReference", lambda handle, client: EndpointReference(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReferenceExpression", lambda handle, client: EndpointReferenceExpression(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.EnvironmentCallbackContext", lambda handle, client: EnvironmentCallbackContext(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ReferenceExpression", lambda handle, client: ReferenceExpression(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.UpdateCommandStateContext", lambda handle, client: UpdateCommandStateContext(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCommandContext", lambda handle, client: ExecuteCommandContext(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceUrlsCallbackContext", lambda handle, client: ResourceUrlsCallbackContext(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource", lambda handle, client: ContainerResource(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecutableResource", lambda handle, client: ExecutableResource(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ParameterResource", lambda handle, client: ParameterResource(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString", lambda handle, client: IResourceWithConnectionString(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ProjectResource", lambda handle, client: ProjectResource(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.IResourceWithServiceDiscovery", lambda handle, client: IResourceWithServiceDiscovery(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource", lambda handle, client: IResource(handle, client)) +register_handle_wrapper("Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext", lambda handle, client: TestCallbackContext(handle, client)) +register_handle_wrapper("Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext", lambda handle, client: TestResourceContext(handle, client)) +register_handle_wrapper("Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext", lambda handle, client: TestEnvironmentContext(handle, client)) +register_handle_wrapper("Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext", lambda handle, client: TestCollectionContext(handle, client)) +register_handle_wrapper("Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource", lambda handle, client: TestRedisResource(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment", lambda handle, client: IResourceWithEnvironment(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithArgs", lambda handle, client: IResourceWithArgs(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints", lambda handle, client: IResourceWithEndpoints(handle, client)) +register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithWaitSupport", lambda handle, client: IResourceWithWaitSupport(handle, client)) +register_handle_wrapper("Aspire.Hosting/Dict", lambda handle, client: AspireDict(handle, client)) +register_handle_wrapper("Aspire.Hosting/List", lambda handle, client: AspireList(handle, client)) +register_handle_wrapper("Aspire.Hosting/Dict", lambda handle, client: AspireDict(handle, client)) +register_handle_wrapper("Aspire.Hosting/List", lambda handle, client: AspireList(handle, client)) +register_handle_wrapper("Aspire.Hosting/List", lambda handle, client: AspireList(handle, client)) +register_handle_wrapper("Aspire.Hosting/Dict", lambda handle, client: AspireDict(handle, client)) + +# ============================================================================ +# Connection Helpers +# ============================================================================ + +def connect() -> AspireClient: + socket_path = os.environ.get("REMOTE_APP_HOST_SOCKET_PATH") + if not socket_path: + raise RuntimeError("REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`.") + client = AspireClient(socket_path) + client.connect() + client.on_disconnect(lambda: sys.exit(1)) + return client + +def create_builder(options: Any | None = None) -> IDistributedApplicationBuilder: + client = connect() + resolved_options: Dict[str, Any] = {} + if options is not None: + if hasattr(options, "to_dict"): + resolved_options.update(options.to_dict()) + elif isinstance(options, dict): + resolved_options.update(options) + resolved_options.setdefault("Args", sys.argv[1:]) + resolved_options.setdefault("ProjectDirectory", os.environ.get("ASPIRE_PROJECT_DIRECTORY", os.getcwd())) + result = client.invoke_capability("Aspire.Hosting/createBuilderWithOptions", {"options": resolved_options}) + return result + +# Re-export commonly used types +CapabilityError = CapabilityError +Handle = Handle +ReferenceExpression = ReferenceExpression +ref_expr = ref_expr + diff --git a/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/WithOptionalStringCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/WithOptionalStringCapability.verified.txt new file mode 100644 index 00000000000..6b1c1fcc13a --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/WithOptionalStringCapability.verified.txt @@ -0,0 +1,75 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Python.Tests/withOptionalString, + MethodName: withOptionalString, + QualifiedMethodName: withOptionalString, + Description: Adds an optional string parameter, + Parameters: [ + { + Name: value, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false + }, + { + Name: enabled, + Type: { + TypeId: boolean, + ClrType: bool, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false, + DefaultValue: true + } + ], + ReturnType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + ClrType: IResource, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + ClrType: IResource, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.WithOptionalString +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/WithPersistenceCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/WithPersistenceCapability.verified.txt new file mode 100644 index 00000000000..0cfd8a47637 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/WithPersistenceCapability.verified.txt @@ -0,0 +1,61 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Python.Tests/withPersistence, + MethodName: withPersistence, + QualifiedMethodName: withPersistence, + Description: Configures the Redis resource with persistence, + Parameters: [ + { + Name: mode, + Type: { + TypeId: enum:Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestPersistenceMode, + ClrType: TestPersistenceMode, + Category: Enum, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false, + DefaultValue: Volume + } + ], + ReturnType: { + TypeId: Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + TargetType: { + TypeId: Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting.CodeGeneration.Python.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.WithPersistence +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.Rust.Tests.csproj b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.Rust.Tests.csproj new file mode 100644 index 00000000000..be2665b7db5 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.Rust.Tests.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + + + + + + + + + + + + + + + + + + + diff --git a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/AtsRustCodeGeneratorTests.cs b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/AtsRustCodeGeneratorTests.cs new file mode 100644 index 00000000000..58329388063 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/AtsRustCodeGeneratorTests.cs @@ -0,0 +1,319 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Ats; +using Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes; + +namespace Aspire.Hosting.CodeGeneration.Rust.Tests; + +public class AtsRustCodeGeneratorTests +{ + private readonly AtsRustCodeGenerator _generator = new(); + + // The test types are compiled into this assembly via Compile Include + private const string TestTypesAssemblyName = "Aspire.Hosting.CodeGeneration.Rust.Tests"; + + [Fact] + public void Language_ReturnsRust() + { + Assert.Equal("Rust", _generator.Language); + } + + [Fact] + public async Task GenerateDistributedApplication_WithTestTypes_GeneratesCorrectOutput() + { + // Arrange + var atsContext = CreateContextFromTestAssembly(); + + // Act + var files = _generator.GenerateDistributedApplication(atsContext); + + // Assert + Assert.Contains("aspire.rs", files.Keys); + Assert.Contains("transport.rs", files.Keys); + Assert.Contains("base.rs", files.Keys); + Assert.Contains("mod.rs", files.Keys); + + await Verify(files["aspire.rs"], extension: "rs") + .UseFileName("AtsGeneratedAspire"); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_IncludesCapabilities() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert that capabilities are discovered + Assert.NotEmpty(capabilities); + + // Check for specific capabilities (uses AssemblyName/methodName format) + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.Contains(capabilities, c => c.CapabilityId == $"{TestTypesAssemblyName}/withOptionalString"); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_DeriveCorrectMethodNames() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert method names are derived correctly + var addTestRedis = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Equal("addTestRedis", addTestRedis.MethodName); + + var withPersistence = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.Equal("withPersistence", withPersistence.MethodName); + } + + [Fact] + public void GenerateDistributedApplication_WithTestTypes_CapturesParameters() + { + // Arrange + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // Assert parameters are captured + var addTestRedis = capabilities.First(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.Equal(2, addTestRedis.Parameters.Count); + Assert.Equal("Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder", addTestRedis.TargetTypeId); + Assert.Contains(addTestRedis.Parameters, p => p.Name == "name" && p.Type?.TypeId == "string"); + Assert.Contains(addTestRedis.Parameters, p => p.Name == "port" && p.IsOptional); + } + + [Fact] + public void Scanner_ReturnsBuilder_TrueForResourceBuilderReturnTypes() + { + // Verify that ReturnsBuilder is correctly set to true for methods + // that return IResourceBuilder + var capabilities = ScanCapabilitiesFromTestAssembly(); + + // addTestRedis returns IResourceBuilder - should have ReturnsBuilder = true + var addTestRedis = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.NotNull(addTestRedis); + Assert.True(addTestRedis.ReturnsBuilder, + "addTestRedis returns IResourceBuilder but ReturnsBuilder is false - fluent chaining won't work"); + + // withPersistence also returns IResourceBuilder + var withPersistence = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.NotNull(withPersistence); + Assert.True(withPersistence.ReturnsBuilder, + "withPersistence returns IResourceBuilder but ReturnsBuilder is false - fluent chaining won't work"); + } + + [Fact] + public async Task Scanner_AddTestRedis_HasCorrectTypeMetadata() + { + // Verify the entire capability object for addTestRedis + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var addTestRedis = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/addTestRedis"); + Assert.NotNull(addTestRedis); + + await Verify(addTestRedis).UseFileName("AddTestRedisCapability"); + } + + [Fact] + public async Task Scanner_WithPersistence_HasCorrectExpandedTargets() + { + // Verify the entire capability object for withPersistence + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var withPersistence = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withPersistence"); + Assert.NotNull(withPersistence); + + await Verify(withPersistence).UseFileName("WithPersistenceCapability"); + } + + [Fact] + public async Task Scanner_WithOptionalString_HasCorrectExpandedTargets() + { + // Verify withOptionalString (targets IResource, should expand to TestRedisResource) + var capabilities = ScanCapabilitiesFromTestAssembly(); + + var withOptionalString = capabilities.FirstOrDefault(c => c.CapabilityId == $"{TestTypesAssemblyName}/withOptionalString"); + Assert.NotNull(withOptionalString); + + await Verify(withOptionalString).UseFileName("WithOptionalStringCapability"); + } + + [Fact] + public async Task Scanner_HostingAssembly_AddContainerCapability() + { + // Verify the addContainer capability from the real Aspire.Hosting assembly + var capabilities = ScanCapabilitiesFromHostingAssembly(); + + var addContainer = capabilities.FirstOrDefault(c => c.CapabilityId == "Aspire.Hosting/addContainer"); + Assert.NotNull(addContainer); + + await Verify(addContainer).UseFileName("HostingAddContainerCapability"); + } + + [Fact] + public void RuntimeType_ContainerResource_IsNotInterface() + { + // Verify that ContainerResource.IsInterface returns false using runtime reflection + var containerResourceType = typeof(ContainerResource); + + Assert.NotNull(containerResourceType); + Assert.False(containerResourceType.IsInterface, "ContainerResource should NOT be an interface"); + } + + [Fact] + public void TwoPassScanning_DeduplicatesCapabilities() + { + // Verify that when the same capability appears in multiple assemblies, + // ScanAssemblies deduplicates by CapabilityId. + var capabilities = ScanCapabilitiesFromBothAssemblies(); + + // Each capability ID should appear only once + var duplicates = capabilities + .GroupBy(c => c.CapabilityId) + .Where(g => g.Count() > 1) + .Select(g => g.Key) + .ToList(); + + Assert.Empty(duplicates); + } + + [Fact] + public void TwoPassScanning_MergesHandleTypesFromAllAssemblies() + { + // Verify that ScanAssemblies collects handle types from all assemblies + var result = CreateContextFromBothAssemblies(); + + // Should have types from Aspire.Hosting (ContainerResource, etc.) + var containerResourceType = result.HandleTypes + .FirstOrDefault(t => t.AtsTypeId.Contains("ContainerResource") && !t.AtsTypeId.Contains("IContainer")); + Assert.NotNull(containerResourceType); + + // Should have types from test assembly (TestRedisResource) + var testRedisType = result.HandleTypes + .FirstOrDefault(t => t.AtsTypeId.Contains("TestRedisResource")); + Assert.NotNull(testRedisType); + + // TestRedisResource should have IResourceWithEnvironment in its interfaces + // (inherited via ContainerResource) + var hasEnvironmentInterface = testRedisType.ImplementedInterfaces + .Any(i => i.TypeId.Contains("IResourceWithEnvironment")); + Assert.True(hasEnvironmentInterface, + "TestRedisResource should implement IResourceWithEnvironment via ContainerResource"); + } + + [Fact] + public async Task TwoPassScanning_GeneratesWithEnvironmentOnTestRedisBuilder() + { + // End-to-end test: verify that with_environment appears on TestRedisResource + // in the generated Rust when using 2-pass scanning. + var atsContext = CreateContextFromBothAssemblies(); + + // Generate Rust + var files = _generator.GenerateDistributedApplication(atsContext); + var aspireRs = files["aspire.rs"]; + + // Verify with_environment appears (method should exist for resources that support it) + Assert.Contains("with_environment", aspireRs); + + // Snapshot for detailed verification + await Verify(aspireRs, extension: "rs") + .UseFileName("TwoPassScanningGeneratedAspire"); + } + + [Fact] + public void GeneratedCode_UsesSnakeCaseMethodNames() + { + // Verify that the generated Rust code uses snake_case for method names + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + var aspireRs = files["aspire.rs"]; + + // Rust uses snake_case for methods + Assert.Contains("add_container", aspireRs); + Assert.Contains("with_environment", aspireRs); + Assert.DoesNotContain("addContainer(", aspireRs); + Assert.DoesNotContain("withEnvironment(", aspireRs); + } + + [Fact] + public void GeneratedCode_HasCreateBuilderFunction() + { + // Verify that the generated Rust code has a create_builder function + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + var aspireRs = files["aspire.rs"]; + + Assert.Contains("pub fn create_builder", aspireRs); + } + + [Fact] + public void GeneratedCode_HasModRsFile() + { + // Verify that mod.rs file is generated + var atsContext = CreateContextFromBothAssemblies(); + + var files = _generator.GenerateDistributedApplication(atsContext); + + Assert.Contains("mod.rs", files.Keys); + Assert.Contains("pub mod aspire", files["mod.rs"]); + } + + private static List ScanCapabilitiesFromTestAssembly() + { + var testAssembly = LoadTestAssembly(); + + // Scan capabilities from the test assembly + var result = AtsCapabilityScanner.ScanAssembly(testAssembly); + return result.Capabilities; + } + + private static AtsContext CreateContextFromTestAssembly() + { + var testAssembly = LoadTestAssembly(); + + // Scan capabilities from the test assembly + var result = AtsCapabilityScanner.ScanAssembly(testAssembly); + return result.ToAtsContext(); + } + + private static Assembly LoadTestAssembly() + { + // Get the test assembly at runtime (TypeScript tests assembly has the TestTypes) + return typeof(TestRedisResource).Assembly; + } + + private static List ScanCapabilitiesFromHostingAssembly() + { + var hostingAssembly = typeof(DistributedApplication).Assembly; + var result = AtsCapabilityScanner.ScanAssembly(hostingAssembly); + return result.Capabilities; + } + + private static List ScanCapabilitiesFromBothAssemblies() + { + var (testAssembly, hostingAssembly) = LoadBothAssemblies(); + + // Use ScanAssemblies for proper cross-assembly expansion + var result = AtsCapabilityScanner.ScanAssemblies([hostingAssembly, testAssembly]); + return result.Capabilities; + } + + private static AtsContext CreateContextFromBothAssemblies() + { + var (testAssembly, hostingAssembly) = LoadBothAssemblies(); + + // Use ScanAssemblies for proper cross-assembly expansion and enum collection + var result = AtsCapabilityScanner.ScanAssemblies([hostingAssembly, testAssembly]); + return result.ToAtsContext(); + } + + private static (Assembly testAssembly, Assembly hostingAssembly) LoadBothAssemblies() + { + var testAssembly = typeof(TestRedisResource).Assembly; + var hostingAssembly = typeof(DistributedApplication).Assembly; + return (testAssembly, hostingAssembly); + } +} diff --git a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/AddTestRedisCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/AddTestRedisCapability.verified.txt new file mode 100644 index 00000000000..e64f0ce64f0 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/AddTestRedisCapability.verified.txt @@ -0,0 +1,74 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Rust.Tests/addTestRedis, + MethodName: addTestRedis, + QualifiedMethodName: addTestRedis, + Description: Adds a test Redis resource, + Parameters: [ + { + Name: name, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + }, + { + Name: port, + Type: { + TypeId: number, + ClrType: int, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: true, + IsCallback: false + } + ], + ReturnType: { + TypeId: Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.AddTestRedis +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/AtsGeneratedAspire.verified.rs b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/AtsGeneratedAspire.verified.rs new file mode 100644 index 00000000000..2114fa33ff7 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/AtsGeneratedAspire.verified.rs @@ -0,0 +1,864 @@ +//! aspire.rs - Capability-based Aspire SDK +//! GENERATED CODE - DO NOT EDIT + +use std::collections::HashMap; +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; + +use crate::transport::{ + AspireClient, CancellationToken, Handle, + register_callback, register_cancellation, serialize_value, +}; +use crate::base::{ + HandleWrapperBase, ResourceBuilderBase, ReferenceExpression, + AspireList, AspireDict, serialize_handle, HasHandle, +}; + +// ============================================================================ +// Enums +// ============================================================================ + +/// TestPersistenceMode +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum TestPersistenceMode { + #[default] + #[serde(rename = "None")] + None, + #[serde(rename = "Volume")] + Volume, + #[serde(rename = "Bind")] + Bind, +} + +impl std::fmt::Display for TestPersistenceMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::None => write!(f, "None"), + Self::Volume => write!(f, "Volume"), + Self::Bind => write!(f, "Bind"), + } + } +} + +/// TestResourceStatus +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum TestResourceStatus { + #[default] + #[serde(rename = "Pending")] + Pending, + #[serde(rename = "Running")] + Running, + #[serde(rename = "Stopped")] + Stopped, + #[serde(rename = "Failed")] + Failed, +} + +impl std::fmt::Display for TestResourceStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Pending => write!(f, "Pending"), + Self::Running => write!(f, "Running"), + Self::Stopped => write!(f, "Stopped"), + Self::Failed => write!(f, "Failed"), + } + } +} + +// ============================================================================ +// DTOs +// ============================================================================ + +/// TestConfigDto +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct TestConfigDto { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "Port")] + pub port: f64, + #[serde(rename = "Enabled")] + pub enabled: bool, + #[serde(rename = "OptionalField")] + pub optional_field: String, +} + +impl TestConfigDto { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("Name".to_string(), serde_json::to_value(&self.name).unwrap_or(Value::Null)); + map.insert("Port".to_string(), serde_json::to_value(&self.port).unwrap_or(Value::Null)); + map.insert("Enabled".to_string(), serde_json::to_value(&self.enabled).unwrap_or(Value::Null)); + map.insert("OptionalField".to_string(), serde_json::to_value(&self.optional_field).unwrap_or(Value::Null)); + map + } +} + +/// TestNestedDto +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct TestNestedDto { + #[serde(rename = "Id")] + pub id: String, + #[serde(rename = "Config")] + pub config: TestConfigDto, + #[serde(rename = "Tags")] + pub tags: Vec, + #[serde(rename = "Counts")] + pub counts: HashMap, +} + +impl TestNestedDto { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("Id".to_string(), serde_json::to_value(&self.id).unwrap_or(Value::Null)); + map.insert("Config".to_string(), serde_json::to_value(&self.config).unwrap_or(Value::Null)); + map.insert("Tags".to_string(), serde_json::to_value(&self.tags).unwrap_or(Value::Null)); + map.insert("Counts".to_string(), serde_json::to_value(&self.counts).unwrap_or(Value::Null)); + map + } +} + +/// TestDeeplyNestedDto +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct TestDeeplyNestedDto { + #[serde(rename = "NestedData")] + pub nested_data: HashMap>, + #[serde(rename = "MetadataArray")] + pub metadata_array: Vec>, +} + +impl TestDeeplyNestedDto { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("NestedData".to_string(), serde_json::to_value(&self.nested_data).unwrap_or(Value::Null)); + map.insert("MetadataArray".to_string(), serde_json::to_value(&self.metadata_array).unwrap_or(Value::Null)); + map + } +} + +// ============================================================================ +// Handle Wrappers +// ============================================================================ + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder +pub struct IDistributedApplicationBuilder { + handle: Handle, + client: Arc, +} + +impl HasHandle for IDistributedApplicationBuilder { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IDistributedApplicationBuilder { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Adds a test Redis resource + pub fn add_test_redis(&self, name: &str, port: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/addTestRedis", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestRedisResource::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource +pub struct IResource { + handle: Handle, + client: Arc, +} + +impl HasHandle for IResource { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IResource { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString +pub struct IResourceWithConnectionString { + handle: Handle, + client: Arc, +} + +impl HasHandle for IResourceWithConnectionString { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IResourceWithConnectionString { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment +pub struct IResourceWithEnvironment { + handle: Handle, + client: Arc, +} + +impl HasHandle for IResourceWithEnvironment { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IResourceWithEnvironment { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext +pub struct TestCallbackContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for TestCallbackContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl TestCallbackContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Name property + pub fn name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.name", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Name property + pub fn set_name(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setName", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestCallbackContext::new(handle, self.client.clone())) + } + + /// Gets the Value property + pub fn value(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.value", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Value property + pub fn set_value(&self, value: f64) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setValue", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestCallbackContext::new(handle, self.client.clone())) + } + + /// Gets the CancellationToken property + pub fn cancellation_token(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.cancellationToken", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(CancellationToken::new(handle, self.client.clone())) + } + + /// Sets the CancellationToken property + pub fn set_cancellation_token(&self, value: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + if let Some(token) = value { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("value".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setCancellationToken", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestCallbackContext::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext +pub struct TestCollectionContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for TestCollectionContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl TestCollectionContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Items property + pub fn items(&self) -> AspireList { + AspireList::with_getter(self.handle.clone(), self.client.clone(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.items") + } + + /// Gets the Metadata property + pub fn metadata(&self) -> AspireDict { + AspireDict::with_getter(self.handle.clone(), self.client.clone(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.metadata") + } +} + +/// Wrapper for Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext +pub struct TestEnvironmentContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for TestEnvironmentContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl TestEnvironmentContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Name property + pub fn name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.name", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Name property + pub fn set_name(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setName", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestEnvironmentContext::new(handle, self.client.clone())) + } + + /// Gets the Description property + pub fn description(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.description", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Description property + pub fn set_description(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setDescription", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestEnvironmentContext::new(handle, self.client.clone())) + } + + /// Gets the Priority property + pub fn priority(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.priority", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Priority property + pub fn set_priority(&self, value: f64) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setPriority", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestEnvironmentContext::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource +pub struct TestRedisResource { + handle: Handle, + client: Arc, +} + +impl HasHandle for TestRedisResource { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl TestRedisResource { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Configures the Redis resource with persistence + pub fn with_persistence(&self, mode: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = mode { + args.insert("mode".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withPersistence", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestRedisResource::new(handle, self.client.clone())) + } + + /// Adds an optional string parameter + pub fn with_optional_string(&self, value: Option<&str>, enabled: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = value { + args.insert("value".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = enabled { + args.insert("enabled".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalString", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures the resource with a DTO + pub fn with_config(&self, config: TestConfigDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Gets the tags for the resource + pub fn get_tags(&self) -> AspireList { + AspireList::with_getter(self.handle.clone(), self.client.clone(), "Aspire.Hosting.CodeGeneration.Rust.Tests/getTags") + } + + /// Gets the metadata for the resource + pub fn get_metadata(&self) -> AspireDict { + AspireDict::with_getter(self.handle.clone(), self.client.clone(), "Aspire.Hosting.CodeGeneration.Rust.Tests/getMetadata") + } + + /// Sets the connection string using a reference expression + pub fn with_connection_string(&self, connection_string: ReferenceExpression) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("connectionString".to_string(), serde_json::to_value(&connection_string).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withConnectionString", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithConnectionString::new(handle, self.client.clone())) + } + + /// Configures environment with callback (test version) + pub fn test_with_environment_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/testWithEnvironmentCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets the created timestamp + pub fn with_created_at(&self, created_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("createdAt".to_string(), serde_json::to_value(&created_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCreatedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the modified timestamp + pub fn with_modified_at(&self, modified_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("modifiedAt".to_string(), serde_json::to_value(&modified_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withModifiedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the correlation ID + pub fn with_correlation_id(&self, correlation_id: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("correlationId".to_string(), serde_json::to_value(&correlation_id).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCorrelationId", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with optional callback + pub fn with_optional_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the resource status + pub fn with_status(&self, status: TestResourceStatus) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("status".to_string(), serde_json::to_value(&status).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withStatus", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with nested DTO + pub fn with_nested_config(&self, config: TestNestedDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withNestedConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds validation callback + pub fn with_validator(&self, validator: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(validator); + args.insert("validator".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withValidator", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for another resource (test version) + pub fn test_wait_for(&self, dependency: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/testWaitFor", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Gets the endpoints + pub fn get_endpoints(&self) -> Result, Box> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/getEndpoints", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets connection string using direct interface target + pub fn with_connection_string_direct(&self, connection_string: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("connectionString".to_string(), serde_json::to_value(&connection_string).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withConnectionStringDirect", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithConnectionString::new(handle, self.client.clone())) + } + + /// Redis-specific configuration + pub fn with_redis_specific(&self, option: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("option".to_string(), serde_json::to_value(&option).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withRedisSpecific", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestRedisResource::new(handle, self.client.clone())) + } + + /// Adds a dependency on another resource + pub fn with_dependency(&self, dependency: &IResourceWithConnectionString) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withDependency", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the endpoints + pub fn with_endpoints(&self, endpoints: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpoints".to_string(), serde_json::to_value(&endpoints).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withEndpoints", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets environment variables + pub fn with_environment_variables(&self, variables: HashMap) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("variables".to_string(), serde_json::to_value(&variables).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withEnvironmentVariables", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Gets the status of the resource asynchronously + pub fn get_status_async(&self, cancellation_token: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(token) = cancellation_token { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("cancellationToken".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/getStatusAsync", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Performs a cancellable operation + pub fn with_cancellable_operation(&self, operation: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(operation); + args.insert("operation".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCancellableOperation", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for the resource to be ready + pub fn wait_for_ready_async(&self, timeout: f64, cancellation_token: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("timeout".to_string(), serde_json::to_value(&timeout).unwrap_or(Value::Null)); + if let Some(token) = cancellation_token { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("cancellationToken".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/waitForReadyAsync", args)?; + Ok(serde_json::from_value(result)?) + } +} + +/// Wrapper for Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext +pub struct TestResourceContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for TestResourceContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl TestResourceContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Name property + pub fn name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.name", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Name property + pub fn set_name(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setName", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestResourceContext::new(handle, self.client.clone())) + } + + /// Gets the Value property + pub fn value(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.value", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Value property + pub fn set_value(&self, value: f64) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValue", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestResourceContext::new(handle, self.client.clone())) + } + + /// Invokes the GetValueAsync method + pub fn get_value_async(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.getValueAsync", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Invokes the SetValueAsync method + pub fn set_value_async(&self, value: &str) -> Result<(), Box> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValueAsync", args)?; + Ok(()) + } + + /// Invokes the ValidateAsync method + pub fn validate_async(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.validateAsync", args)?; + Ok(serde_json::from_value(result)?) + } +} + +// ============================================================================ +// Handle wrapper registrations +// ============================================================================ + +pub fn register_all_wrappers() { + // Handle wrappers are created inline in generated code + // This function is provided for API compatibility +} + +// ============================================================================ +// Connection Helpers +// ============================================================================ + +/// Establishes a connection to the AppHost server. +pub fn connect() -> Result, Box> { + let socket_path = std::env::var("REMOTE_APP_HOST_SOCKET_PATH") + .map_err(|_| "REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`")?; + let client = Arc::new(AspireClient::new(&socket_path)); + client.connect()?; + Ok(client) +} + +/// Creates a new distributed application builder. +pub fn create_builder(options: Option) -> Result> { + let client = connect()?; + let mut resolved_options: HashMap = HashMap::new(); + if let Some(opts) = options { + for (k, v) in opts.to_map() { + resolved_options.insert(k, v); + } + } + if !resolved_options.contains_key("Args") { + let args: Vec = std::env::args().skip(1).collect(); + resolved_options.insert("Args".to_string(), serde_json::to_value(args).unwrap_or(Value::Null)); + } + if !resolved_options.contains_key("ProjectDirectory") { + if let Ok(pwd) = std::env::current_dir() { + resolved_options.insert("ProjectDirectory".to_string(), Value::String(pwd.to_string_lossy().to_string())); + } + } + let mut args: HashMap = HashMap::new(); + args.insert("options".to_string(), serde_json::to_value(resolved_options).unwrap_or(Value::Null)); + let result = client.invoke_capability("Aspire.Hosting/createBuilderWithOptions", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IDistributedApplicationBuilder::new(handle, client)) +} + diff --git a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/HostingAddContainerCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/HostingAddContainerCapability.verified.txt new file mode 100644 index 00000000000..ba9342ec73c --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/HostingAddContainerCapability.verified.txt @@ -0,0 +1,74 @@ +{ + CapabilityId: Aspire.Hosting/addContainer, + MethodName: addContainer, + QualifiedMethodName: addContainer, + Description: Adds a container resource, + Parameters: [ + { + Name: name, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + }, + { + Name: image, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: false, + IsNullable: false, + IsCallback: false + } + ], + ReturnType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource, + ClrType: ContainerResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder, + ClrType: IDistributedApplicationBuilder, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: true, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.ContainerResourceBuilderExtensions.AddContainer +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs new file mode 100644 index 00000000000..13a268d1dd0 --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs @@ -0,0 +1,4635 @@ +//! aspire.rs - Capability-based Aspire SDK +//! GENERATED CODE - DO NOT EDIT + +use std::collections::HashMap; +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; + +use crate::transport::{ + AspireClient, CancellationToken, Handle, + register_callback, register_cancellation, serialize_value, +}; +use crate::base::{ + HandleWrapperBase, ResourceBuilderBase, ReferenceExpression, + AspireList, AspireDict, serialize_handle, HasHandle, +}; + +// ============================================================================ +// Enums +// ============================================================================ + +/// ContainerLifetime +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ContainerLifetime { + #[default] + #[serde(rename = "Session")] + Session, + #[serde(rename = "Persistent")] + Persistent, +} + +impl std::fmt::Display for ContainerLifetime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Session => write!(f, "Session"), + Self::Persistent => write!(f, "Persistent"), + } + } +} + +/// ImagePullPolicy +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ImagePullPolicy { + #[default] + #[serde(rename = "Default")] + Default, + #[serde(rename = "Always")] + Always, + #[serde(rename = "Missing")] + Missing, + #[serde(rename = "Never")] + Never, +} + +impl std::fmt::Display for ImagePullPolicy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Default => write!(f, "Default"), + Self::Always => write!(f, "Always"), + Self::Missing => write!(f, "Missing"), + Self::Never => write!(f, "Never"), + } + } +} + +/// DistributedApplicationOperation +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum DistributedApplicationOperation { + #[default] + #[serde(rename = "Run")] + Run, + #[serde(rename = "Publish")] + Publish, +} + +impl std::fmt::Display for DistributedApplicationOperation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Run => write!(f, "Run"), + Self::Publish => write!(f, "Publish"), + } + } +} + +/// ProtocolType +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ProtocolType { + #[default] + #[serde(rename = "IP")] + IP, + #[serde(rename = "IPv6HopByHopOptions")] + IPv6HopByHopOptions, + #[serde(rename = "Unspecified")] + Unspecified, + #[serde(rename = "Icmp")] + Icmp, + #[serde(rename = "Igmp")] + Igmp, + #[serde(rename = "Ggp")] + Ggp, + #[serde(rename = "IPv4")] + IPv4, + #[serde(rename = "Tcp")] + Tcp, + #[serde(rename = "Pup")] + Pup, + #[serde(rename = "Udp")] + Udp, + #[serde(rename = "Idp")] + Idp, + #[serde(rename = "IPv6")] + IPv6, + #[serde(rename = "IPv6RoutingHeader")] + IPv6RoutingHeader, + #[serde(rename = "IPv6FragmentHeader")] + IPv6FragmentHeader, + #[serde(rename = "IPSecEncapsulatingSecurityPayload")] + IPSecEncapsulatingSecurityPayload, + #[serde(rename = "IPSecAuthenticationHeader")] + IPSecAuthenticationHeader, + #[serde(rename = "IcmpV6")] + IcmpV6, + #[serde(rename = "IPv6NoNextHeader")] + IPv6NoNextHeader, + #[serde(rename = "IPv6DestinationOptions")] + IPv6DestinationOptions, + #[serde(rename = "ND")] + ND, + #[serde(rename = "Raw")] + Raw, + #[serde(rename = "Ipx")] + Ipx, + #[serde(rename = "Spx")] + Spx, + #[serde(rename = "SpxII")] + SpxII, + #[serde(rename = "Unknown")] + Unknown, +} + +impl std::fmt::Display for ProtocolType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::IP => write!(f, "IP"), + Self::IPv6HopByHopOptions => write!(f, "IPv6HopByHopOptions"), + Self::Unspecified => write!(f, "Unspecified"), + Self::Icmp => write!(f, "Icmp"), + Self::Igmp => write!(f, "Igmp"), + Self::Ggp => write!(f, "Ggp"), + Self::IPv4 => write!(f, "IPv4"), + Self::Tcp => write!(f, "Tcp"), + Self::Pup => write!(f, "Pup"), + Self::Udp => write!(f, "Udp"), + Self::Idp => write!(f, "Idp"), + Self::IPv6 => write!(f, "IPv6"), + Self::IPv6RoutingHeader => write!(f, "IPv6RoutingHeader"), + Self::IPv6FragmentHeader => write!(f, "IPv6FragmentHeader"), + Self::IPSecEncapsulatingSecurityPayload => write!(f, "IPSecEncapsulatingSecurityPayload"), + Self::IPSecAuthenticationHeader => write!(f, "IPSecAuthenticationHeader"), + Self::IcmpV6 => write!(f, "IcmpV6"), + Self::IPv6NoNextHeader => write!(f, "IPv6NoNextHeader"), + Self::IPv6DestinationOptions => write!(f, "IPv6DestinationOptions"), + Self::ND => write!(f, "ND"), + Self::Raw => write!(f, "Raw"), + Self::Ipx => write!(f, "Ipx"), + Self::Spx => write!(f, "Spx"), + Self::SpxII => write!(f, "SpxII"), + Self::Unknown => write!(f, "Unknown"), + } + } +} + +/// EndpointProperty +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum EndpointProperty { + #[default] + #[serde(rename = "Url")] + Url, + #[serde(rename = "Host")] + Host, + #[serde(rename = "IPV4Host")] + IPV4Host, + #[serde(rename = "Port")] + Port, + #[serde(rename = "Scheme")] + Scheme, + #[serde(rename = "TargetPort")] + TargetPort, + #[serde(rename = "HostAndPort")] + HostAndPort, +} + +impl std::fmt::Display for EndpointProperty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Url => write!(f, "Url"), + Self::Host => write!(f, "Host"), + Self::IPV4Host => write!(f, "IPV4Host"), + Self::Port => write!(f, "Port"), + Self::Scheme => write!(f, "Scheme"), + Self::TargetPort => write!(f, "TargetPort"), + Self::HostAndPort => write!(f, "HostAndPort"), + } + } +} + +/// IconVariant +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum IconVariant { + #[default] + #[serde(rename = "Regular")] + Regular, + #[serde(rename = "Filled")] + Filled, +} + +impl std::fmt::Display for IconVariant { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Regular => write!(f, "Regular"), + Self::Filled => write!(f, "Filled"), + } + } +} + +/// UrlDisplayLocation +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum UrlDisplayLocation { + #[default] + #[serde(rename = "SummaryAndDetails")] + SummaryAndDetails, + #[serde(rename = "DetailsOnly")] + DetailsOnly, +} + +impl std::fmt::Display for UrlDisplayLocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SummaryAndDetails => write!(f, "SummaryAndDetails"), + Self::DetailsOnly => write!(f, "DetailsOnly"), + } + } +} + +/// TestPersistenceMode +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum TestPersistenceMode { + #[default] + #[serde(rename = "None")] + None, + #[serde(rename = "Volume")] + Volume, + #[serde(rename = "Bind")] + Bind, +} + +impl std::fmt::Display for TestPersistenceMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::None => write!(f, "None"), + Self::Volume => write!(f, "Volume"), + Self::Bind => write!(f, "Bind"), + } + } +} + +/// TestResourceStatus +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum TestResourceStatus { + #[default] + #[serde(rename = "Pending")] + Pending, + #[serde(rename = "Running")] + Running, + #[serde(rename = "Stopped")] + Stopped, + #[serde(rename = "Failed")] + Failed, +} + +impl std::fmt::Display for TestResourceStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Pending => write!(f, "Pending"), + Self::Running => write!(f, "Running"), + Self::Stopped => write!(f, "Stopped"), + Self::Failed => write!(f, "Failed"), + } + } +} + +// ============================================================================ +// DTOs +// ============================================================================ + +/// CreateBuilderOptions +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CreateBuilderOptions { + #[serde(rename = "Args")] + pub args: Vec, + #[serde(rename = "ProjectDirectory")] + pub project_directory: String, + #[serde(rename = "AppHostFilePath")] + pub app_host_file_path: String, + #[serde(rename = "ContainerRegistryOverride")] + pub container_registry_override: String, + #[serde(rename = "DisableDashboard")] + pub disable_dashboard: bool, + #[serde(rename = "DashboardApplicationName")] + pub dashboard_application_name: String, + #[serde(rename = "AllowUnsecuredTransport")] + pub allow_unsecured_transport: bool, + #[serde(rename = "EnableResourceLogging")] + pub enable_resource_logging: bool, +} + +impl CreateBuilderOptions { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("Args".to_string(), serde_json::to_value(&self.args).unwrap_or(Value::Null)); + map.insert("ProjectDirectory".to_string(), serde_json::to_value(&self.project_directory).unwrap_or(Value::Null)); + map.insert("AppHostFilePath".to_string(), serde_json::to_value(&self.app_host_file_path).unwrap_or(Value::Null)); + map.insert("ContainerRegistryOverride".to_string(), serde_json::to_value(&self.container_registry_override).unwrap_or(Value::Null)); + map.insert("DisableDashboard".to_string(), serde_json::to_value(&self.disable_dashboard).unwrap_or(Value::Null)); + map.insert("DashboardApplicationName".to_string(), serde_json::to_value(&self.dashboard_application_name).unwrap_or(Value::Null)); + map.insert("AllowUnsecuredTransport".to_string(), serde_json::to_value(&self.allow_unsecured_transport).unwrap_or(Value::Null)); + map.insert("EnableResourceLogging".to_string(), serde_json::to_value(&self.enable_resource_logging).unwrap_or(Value::Null)); + map + } +} + +/// ResourceEventDto +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct ResourceEventDto { + #[serde(rename = "ResourceName")] + pub resource_name: String, + #[serde(rename = "ResourceId")] + pub resource_id: String, + #[serde(rename = "State")] + pub state: String, + #[serde(rename = "StateStyle")] + pub state_style: String, + #[serde(rename = "HealthStatus")] + pub health_status: String, + #[serde(rename = "ExitCode")] + pub exit_code: f64, +} + +impl ResourceEventDto { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("ResourceName".to_string(), serde_json::to_value(&self.resource_name).unwrap_or(Value::Null)); + map.insert("ResourceId".to_string(), serde_json::to_value(&self.resource_id).unwrap_or(Value::Null)); + map.insert("State".to_string(), serde_json::to_value(&self.state).unwrap_or(Value::Null)); + map.insert("StateStyle".to_string(), serde_json::to_value(&self.state_style).unwrap_or(Value::Null)); + map.insert("HealthStatus".to_string(), serde_json::to_value(&self.health_status).unwrap_or(Value::Null)); + map.insert("ExitCode".to_string(), serde_json::to_value(&self.exit_code).unwrap_or(Value::Null)); + map + } +} + +/// CommandOptions +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CommandOptions { + #[serde(rename = "Description")] + pub description: String, + #[serde(rename = "Parameter")] + pub parameter: Value, + #[serde(rename = "ConfirmationMessage")] + pub confirmation_message: String, + #[serde(rename = "IconName")] + pub icon_name: String, + #[serde(rename = "IconVariant")] + pub icon_variant: IconVariant, + #[serde(rename = "IsHighlighted")] + pub is_highlighted: bool, + #[serde(rename = "UpdateState")] + pub update_state: Value, +} + +impl CommandOptions { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("Description".to_string(), serde_json::to_value(&self.description).unwrap_or(Value::Null)); + map.insert("Parameter".to_string(), serde_json::to_value(&self.parameter).unwrap_or(Value::Null)); + map.insert("ConfirmationMessage".to_string(), serde_json::to_value(&self.confirmation_message).unwrap_or(Value::Null)); + map.insert("IconName".to_string(), serde_json::to_value(&self.icon_name).unwrap_or(Value::Null)); + map.insert("IconVariant".to_string(), serde_json::to_value(&self.icon_variant).unwrap_or(Value::Null)); + map.insert("IsHighlighted".to_string(), serde_json::to_value(&self.is_highlighted).unwrap_or(Value::Null)); + map.insert("UpdateState".to_string(), serde_json::to_value(&self.update_state).unwrap_or(Value::Null)); + map + } +} + +/// ExecuteCommandResult +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct ExecuteCommandResult { + #[serde(rename = "Success")] + pub success: bool, + #[serde(rename = "Canceled")] + pub canceled: bool, + #[serde(rename = "ErrorMessage")] + pub error_message: String, +} + +impl ExecuteCommandResult { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("Success".to_string(), serde_json::to_value(&self.success).unwrap_or(Value::Null)); + map.insert("Canceled".to_string(), serde_json::to_value(&self.canceled).unwrap_or(Value::Null)); + map.insert("ErrorMessage".to_string(), serde_json::to_value(&self.error_message).unwrap_or(Value::Null)); + map + } +} + +/// ResourceUrlAnnotation +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct ResourceUrlAnnotation { + #[serde(rename = "Url")] + pub url: String, + #[serde(rename = "DisplayText")] + pub display_text: String, + #[serde(rename = "Endpoint")] + pub endpoint: Handle, + #[serde(rename = "DisplayLocation")] + pub display_location: UrlDisplayLocation, +} + +impl ResourceUrlAnnotation { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("Url".to_string(), serde_json::to_value(&self.url).unwrap_or(Value::Null)); + map.insert("DisplayText".to_string(), serde_json::to_value(&self.display_text).unwrap_or(Value::Null)); + map.insert("Endpoint".to_string(), serde_json::to_value(&self.endpoint).unwrap_or(Value::Null)); + map.insert("DisplayLocation".to_string(), serde_json::to_value(&self.display_location).unwrap_or(Value::Null)); + map + } +} + +/// TestConfigDto +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct TestConfigDto { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "Port")] + pub port: f64, + #[serde(rename = "Enabled")] + pub enabled: bool, + #[serde(rename = "OptionalField")] + pub optional_field: String, +} + +impl TestConfigDto { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("Name".to_string(), serde_json::to_value(&self.name).unwrap_or(Value::Null)); + map.insert("Port".to_string(), serde_json::to_value(&self.port).unwrap_or(Value::Null)); + map.insert("Enabled".to_string(), serde_json::to_value(&self.enabled).unwrap_or(Value::Null)); + map.insert("OptionalField".to_string(), serde_json::to_value(&self.optional_field).unwrap_or(Value::Null)); + map + } +} + +/// TestNestedDto +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct TestNestedDto { + #[serde(rename = "Id")] + pub id: String, + #[serde(rename = "Config")] + pub config: TestConfigDto, + #[serde(rename = "Tags")] + pub tags: Vec, + #[serde(rename = "Counts")] + pub counts: HashMap, +} + +impl TestNestedDto { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("Id".to_string(), serde_json::to_value(&self.id).unwrap_or(Value::Null)); + map.insert("Config".to_string(), serde_json::to_value(&self.config).unwrap_or(Value::Null)); + map.insert("Tags".to_string(), serde_json::to_value(&self.tags).unwrap_or(Value::Null)); + map.insert("Counts".to_string(), serde_json::to_value(&self.counts).unwrap_or(Value::Null)); + map + } +} + +/// TestDeeplyNestedDto +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct TestDeeplyNestedDto { + #[serde(rename = "NestedData")] + pub nested_data: HashMap>, + #[serde(rename = "MetadataArray")] + pub metadata_array: Vec>, +} + +impl TestDeeplyNestedDto { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("NestedData".to_string(), serde_json::to_value(&self.nested_data).unwrap_or(Value::Null)); + map.insert("MetadataArray".to_string(), serde_json::to_value(&self.metadata_array).unwrap_or(Value::Null)); + map + } +} + +// ============================================================================ +// Handle Wrappers +// ============================================================================ + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.CommandLineArgsCallbackContext +pub struct CommandLineArgsCallbackContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for CommandLineArgsCallbackContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl CommandLineArgsCallbackContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Args property + pub fn args(&self) -> AspireList { + AspireList::with_getter(self.handle.clone(), self.client.clone(), "Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.args") + } + + /// Gets the CancellationToken property + pub fn cancellation_token(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.cancellationToken", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(CancellationToken::new(handle, self.client.clone())) + } + + /// Gets the ExecutionContext property + pub fn execution_context(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.executionContext", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplicationExecutionContext::new(handle, self.client.clone())) + } + + /// Sets the ExecutionContext property + pub fn set_execution_context(&self, value: &DistributedApplicationExecutionContext) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), value.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/CommandLineArgsCallbackContext.setExecutionContext", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(CommandLineArgsCallbackContext::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource +pub struct ContainerResource { + handle: Handle, + client: Arc, +} + +impl HasHandle for ContainerResource { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl ContainerResource { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Sets an environment variable + pub fn with_environment(&self, name: &str, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironment", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds an environment variable with a reference expression + pub fn with_environment_expression(&self, name: &str, value: ReferenceExpression) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentExpression", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets environment variables via callback + pub fn with_environment_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets environment variables via async callback + pub fn with_environment_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds arguments + pub fn with_args(&self, args: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("args".to_string(), serde_json::to_value(&args).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgs", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Sets command-line arguments via callback + pub fn with_args_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgsCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Sets command-line arguments via async callback + pub fn with_args_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgsCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Adds a reference to another resource + pub fn with_reference(&self, source: &IResourceWithConnectionString, connection_name: Option<&str>, optional: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("source".to_string(), source.handle().to_json()); + if let Some(ref v) = connection_name { + args.insert("connectionName".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = optional { + args.insert("optional".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withReference", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds a service discovery reference to another resource + pub fn with_service_reference(&self, source: &IResourceWithServiceDiscovery) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("source".to_string(), source.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withServiceReference", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds a network endpoint + pub fn with_endpoint(&self, port: Option, target_port: Option, scheme: Option<&str>, name: Option<&str>, env: Option<&str>, is_proxied: Option, is_external: Option, protocol: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = scheme { + args.insert("scheme".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_external { + args.insert("isExternal".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = protocol { + args.insert("protocol".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds an HTTP endpoint + pub fn with_http_endpoint(&self, port: Option, target_port: Option, name: Option<&str>, env: Option<&str>, is_proxied: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds an HTTPS endpoint + pub fn with_https_endpoint(&self, port: Option, target_port: Option, name: Option<&str>, env: Option<&str>, is_proxied: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpsEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Makes HTTP endpoints externally accessible + pub fn with_external_http_endpoints(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withExternalHttpEndpoints", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Gets an endpoint reference + pub fn get_endpoint(&self, name: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/getEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(EndpointReference::new(handle, self.client.clone())) + } + + /// Configures resource for HTTP/2 + pub fn as_http2_service(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/asHttp2Service", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Customizes displayed URLs via callback + pub fn with_urls_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlsCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Customizes displayed URLs via async callback + pub fn with_urls_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlsCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds or modifies displayed URLs + pub fn with_url(&self, url: &str, display_text: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("url".to_string(), serde_json::to_value(&url).unwrap_or(Value::Null)); + if let Some(ref v) = display_text { + args.insert("displayText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withUrl", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a URL using a reference expression + pub fn with_url_expression(&self, url: ReferenceExpression, display_text: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("url".to_string(), serde_json::to_value(&url).unwrap_or(Value::Null)); + if let Some(ref v) = display_text { + args.insert("displayText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withUrlExpression", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Customizes the URL for a specific endpoint via callback + pub fn with_url_for_endpoint(&self, endpoint_name: &str, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpointName".to_string(), serde_json::to_value(&endpoint_name).unwrap_or(Value::Null)); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlForEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a URL for a specific endpoint via factory callback + pub fn with_url_for_endpoint_factory(&self, endpoint_name: &str, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpointName".to_string(), serde_json::to_value(&endpoint_name).unwrap_or(Value::Null)); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlForEndpointFactory", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Waits for another resource to be ready + pub fn wait_for(&self, dependency: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/waitFor", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithWaitSupport::new(handle, self.client.clone())) + } + + /// Prevents resource from starting automatically + pub fn with_explicit_start(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withExplicitStart", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for resource completion + pub fn wait_for_completion(&self, dependency: &IResource, exit_code: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + if let Some(ref v) = exit_code { + args.insert("exitCode".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/waitForCompletion", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithWaitSupport::new(handle, self.client.clone())) + } + + /// Adds a health check by key + pub fn with_health_check(&self, key: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("key".to_string(), serde_json::to_value(&key).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withHealthCheck", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds an HTTP health check + pub fn with_http_health_check(&self, path: Option<&str>, status_code: Option, endpoint_name: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = path { + args.insert("path".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = status_code { + args.insert("statusCode".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = endpoint_name { + args.insert("endpointName".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpHealthCheck", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds a resource command + pub fn with_command(&self, name: &str, display_name: &str, execute_command: impl Fn(Vec) -> Value + Send + Sync + 'static, command_options: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("displayName".to_string(), serde_json::to_value(&display_name).unwrap_or(Value::Null)); + let callback_id = register_callback(execute_command); + args.insert("executeCommand".to_string(), Value::String(callback_id)); + if let Some(ref v) = command_options { + args.insert("commandOptions".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withCommand", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the parent relationship + pub fn with_parent_relationship(&self, parent: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("parent".to_string(), parent.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withParentRelationship", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Gets the resource name + pub fn get_resource_name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("resource".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/getResourceName", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Adds an optional string parameter + pub fn with_optional_string(&self, value: Option<&str>, enabled: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = value { + args.insert("value".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = enabled { + args.insert("enabled".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalString", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures the resource with a DTO + pub fn with_config(&self, config: TestConfigDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures environment with callback (test version) + pub fn test_with_environment_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/testWithEnvironmentCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets the created timestamp + pub fn with_created_at(&self, created_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("createdAt".to_string(), serde_json::to_value(&created_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCreatedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the modified timestamp + pub fn with_modified_at(&self, modified_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("modifiedAt".to_string(), serde_json::to_value(&modified_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withModifiedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the correlation ID + pub fn with_correlation_id(&self, correlation_id: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("correlationId".to_string(), serde_json::to_value(&correlation_id).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCorrelationId", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with optional callback + pub fn with_optional_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the resource status + pub fn with_status(&self, status: TestResourceStatus) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("status".to_string(), serde_json::to_value(&status).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withStatus", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with nested DTO + pub fn with_nested_config(&self, config: TestNestedDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withNestedConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds validation callback + pub fn with_validator(&self, validator: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(validator); + args.insert("validator".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withValidator", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for another resource (test version) + pub fn test_wait_for(&self, dependency: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/testWaitFor", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a dependency on another resource + pub fn with_dependency(&self, dependency: &IResourceWithConnectionString) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withDependency", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the endpoints + pub fn with_endpoints(&self, endpoints: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpoints".to_string(), serde_json::to_value(&endpoints).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withEndpoints", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets environment variables + pub fn with_environment_variables(&self, variables: HashMap) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("variables".to_string(), serde_json::to_value(&variables).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withEnvironmentVariables", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Performs a cancellable operation + pub fn with_cancellable_operation(&self, operation: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(operation); + args.insert("operation".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCancellableOperation", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.DistributedApplication +pub struct DistributedApplication { + handle: Handle, + client: Arc, +} + +impl HasHandle for DistributedApplication { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl DistributedApplication { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Runs the distributed application + pub fn run(&self, cancellation_token: Option<&CancellationToken>) -> Result<(), Box> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + if let Some(token) = cancellation_token { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("cancellationToken".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting/run", args)?; + Ok(()) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription +pub struct DistributedApplicationEventSubscription { + handle: Handle, + client: Arc, +} + +impl HasHandle for DistributedApplicationEventSubscription { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl DistributedApplicationEventSubscription { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext +pub struct DistributedApplicationExecutionContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for DistributedApplicationExecutionContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl DistributedApplicationExecutionContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the PublisherName property + pub fn publisher_name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/DistributedApplicationExecutionContext.publisherName", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the PublisherName property + pub fn set_publisher_name(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/DistributedApplicationExecutionContext.setPublisherName", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplicationExecutionContext::new(handle, self.client.clone())) + } + + /// Gets the Operation property + pub fn operation(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/DistributedApplicationExecutionContext.operation", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the IsPublishMode property + pub fn is_publish_mode(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/DistributedApplicationExecutionContext.isPublishMode", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the IsRunMode property + pub fn is_run_mode(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/DistributedApplicationExecutionContext.isRunMode", args)?; + Ok(serde_json::from_value(result)?) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContextOptions +pub struct DistributedApplicationExecutionContextOptions { + handle: Handle, + client: Arc, +} + +impl HasHandle for DistributedApplicationExecutionContextOptions { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl DistributedApplicationExecutionContextOptions { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationResourceEventSubscription +pub struct DistributedApplicationResourceEventSubscription { + handle: Handle, + client: Arc, +} + +impl HasHandle for DistributedApplicationResourceEventSubscription { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl DistributedApplicationResourceEventSubscription { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReference +pub struct EndpointReference { + handle: Handle, + client: Arc, +} + +impl HasHandle for EndpointReference { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl EndpointReference { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the EndpointName property + pub fn endpoint_name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.endpointName", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the ErrorMessage property + pub fn error_message(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.errorMessage", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the ErrorMessage property + pub fn set_error_message(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.setErrorMessage", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(EndpointReference::new(handle, self.client.clone())) + } + + /// Gets the IsAllocated property + pub fn is_allocated(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.isAllocated", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the Exists property + pub fn exists(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.exists", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the IsHttp property + pub fn is_http(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.isHttp", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the IsHttps property + pub fn is_https(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.isHttps", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the Port property + pub fn port(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.port", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the TargetPort property + pub fn target_port(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.targetPort", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the Host property + pub fn host(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.host", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the Scheme property + pub fn scheme(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.scheme", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the Url property + pub fn url(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReference.url", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the URL of the endpoint asynchronously + pub fn get_value_async(&self, cancellation_token: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + if let Some(token) = cancellation_token { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("cancellationToken".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/getValueAsync", args)?; + Ok(serde_json::from_value(result)?) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReferenceExpression +pub struct EndpointReferenceExpression { + handle: Handle, + client: Arc, +} + +impl HasHandle for EndpointReferenceExpression { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl EndpointReferenceExpression { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Endpoint property + pub fn endpoint(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.endpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(EndpointReference::new(handle, self.client.clone())) + } + + /// Gets the Property property + pub fn property(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.property", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the ValueExpression property + pub fn value_expression(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EndpointReferenceExpression.valueExpression", args)?; + Ok(serde_json::from_value(result)?) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.EnvironmentCallbackContext +pub struct EnvironmentCallbackContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for EnvironmentCallbackContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl EnvironmentCallbackContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the EnvironmentVariables property + pub fn environment_variables(&self) -> AspireDict { + AspireDict::with_getter(self.handle.clone(), self.client.clone(), "Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.environmentVariables") + } + + /// Gets the CancellationToken property + pub fn cancellation_token(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.cancellationToken", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(CancellationToken::new(handle, self.client.clone())) + } + + /// Gets the ExecutionContext property + pub fn execution_context(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/EnvironmentCallbackContext.executionContext", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplicationExecutionContext::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecutableResource +pub struct ExecutableResource { + handle: Handle, + client: Arc, +} + +impl HasHandle for ExecutableResource { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl ExecutableResource { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Sets the executable command + pub fn with_executable_command(&self, command: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("command".to_string(), serde_json::to_value(&command).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withExecutableCommand", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ExecutableResource::new(handle, self.client.clone())) + } + + /// Sets the executable working directory + pub fn with_working_directory(&self, working_directory: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("workingDirectory".to_string(), serde_json::to_value(&working_directory).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withWorkingDirectory", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ExecutableResource::new(handle, self.client.clone())) + } + + /// Sets an environment variable + pub fn with_environment(&self, name: &str, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironment", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds an environment variable with a reference expression + pub fn with_environment_expression(&self, name: &str, value: ReferenceExpression) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentExpression", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets environment variables via callback + pub fn with_environment_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets environment variables via async callback + pub fn with_environment_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds arguments + pub fn with_args(&self, args: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("args".to_string(), serde_json::to_value(&args).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgs", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Sets command-line arguments via callback + pub fn with_args_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgsCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Sets command-line arguments via async callback + pub fn with_args_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgsCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Adds a reference to another resource + pub fn with_reference(&self, source: &IResourceWithConnectionString, connection_name: Option<&str>, optional: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("source".to_string(), source.handle().to_json()); + if let Some(ref v) = connection_name { + args.insert("connectionName".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = optional { + args.insert("optional".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withReference", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds a service discovery reference to another resource + pub fn with_service_reference(&self, source: &IResourceWithServiceDiscovery) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("source".to_string(), source.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withServiceReference", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds a network endpoint + pub fn with_endpoint(&self, port: Option, target_port: Option, scheme: Option<&str>, name: Option<&str>, env: Option<&str>, is_proxied: Option, is_external: Option, protocol: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = scheme { + args.insert("scheme".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_external { + args.insert("isExternal".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = protocol { + args.insert("protocol".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds an HTTP endpoint + pub fn with_http_endpoint(&self, port: Option, target_port: Option, name: Option<&str>, env: Option<&str>, is_proxied: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds an HTTPS endpoint + pub fn with_https_endpoint(&self, port: Option, target_port: Option, name: Option<&str>, env: Option<&str>, is_proxied: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpsEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Makes HTTP endpoints externally accessible + pub fn with_external_http_endpoints(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withExternalHttpEndpoints", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Gets an endpoint reference + pub fn get_endpoint(&self, name: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/getEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(EndpointReference::new(handle, self.client.clone())) + } + + /// Configures resource for HTTP/2 + pub fn as_http2_service(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/asHttp2Service", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Customizes displayed URLs via callback + pub fn with_urls_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlsCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Customizes displayed URLs via async callback + pub fn with_urls_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlsCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds or modifies displayed URLs + pub fn with_url(&self, url: &str, display_text: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("url".to_string(), serde_json::to_value(&url).unwrap_or(Value::Null)); + if let Some(ref v) = display_text { + args.insert("displayText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withUrl", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a URL using a reference expression + pub fn with_url_expression(&self, url: ReferenceExpression, display_text: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("url".to_string(), serde_json::to_value(&url).unwrap_or(Value::Null)); + if let Some(ref v) = display_text { + args.insert("displayText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withUrlExpression", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Customizes the URL for a specific endpoint via callback + pub fn with_url_for_endpoint(&self, endpoint_name: &str, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpointName".to_string(), serde_json::to_value(&endpoint_name).unwrap_or(Value::Null)); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlForEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a URL for a specific endpoint via factory callback + pub fn with_url_for_endpoint_factory(&self, endpoint_name: &str, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpointName".to_string(), serde_json::to_value(&endpoint_name).unwrap_or(Value::Null)); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlForEndpointFactory", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Waits for another resource to be ready + pub fn wait_for(&self, dependency: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/waitFor", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithWaitSupport::new(handle, self.client.clone())) + } + + /// Prevents resource from starting automatically + pub fn with_explicit_start(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withExplicitStart", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for resource completion + pub fn wait_for_completion(&self, dependency: &IResource, exit_code: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + if let Some(ref v) = exit_code { + args.insert("exitCode".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/waitForCompletion", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithWaitSupport::new(handle, self.client.clone())) + } + + /// Adds a health check by key + pub fn with_health_check(&self, key: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("key".to_string(), serde_json::to_value(&key).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withHealthCheck", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds an HTTP health check + pub fn with_http_health_check(&self, path: Option<&str>, status_code: Option, endpoint_name: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = path { + args.insert("path".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = status_code { + args.insert("statusCode".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = endpoint_name { + args.insert("endpointName".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpHealthCheck", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds a resource command + pub fn with_command(&self, name: &str, display_name: &str, execute_command: impl Fn(Vec) -> Value + Send + Sync + 'static, command_options: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("displayName".to_string(), serde_json::to_value(&display_name).unwrap_or(Value::Null)); + let callback_id = register_callback(execute_command); + args.insert("executeCommand".to_string(), Value::String(callback_id)); + if let Some(ref v) = command_options { + args.insert("commandOptions".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withCommand", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the parent relationship + pub fn with_parent_relationship(&self, parent: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("parent".to_string(), parent.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withParentRelationship", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Gets the resource name + pub fn get_resource_name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("resource".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/getResourceName", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Adds an optional string parameter + pub fn with_optional_string(&self, value: Option<&str>, enabled: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = value { + args.insert("value".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = enabled { + args.insert("enabled".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalString", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures the resource with a DTO + pub fn with_config(&self, config: TestConfigDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures environment with callback (test version) + pub fn test_with_environment_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/testWithEnvironmentCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets the created timestamp + pub fn with_created_at(&self, created_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("createdAt".to_string(), serde_json::to_value(&created_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCreatedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the modified timestamp + pub fn with_modified_at(&self, modified_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("modifiedAt".to_string(), serde_json::to_value(&modified_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withModifiedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the correlation ID + pub fn with_correlation_id(&self, correlation_id: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("correlationId".to_string(), serde_json::to_value(&correlation_id).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCorrelationId", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with optional callback + pub fn with_optional_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the resource status + pub fn with_status(&self, status: TestResourceStatus) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("status".to_string(), serde_json::to_value(&status).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withStatus", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with nested DTO + pub fn with_nested_config(&self, config: TestNestedDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withNestedConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds validation callback + pub fn with_validator(&self, validator: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(validator); + args.insert("validator".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withValidator", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for another resource (test version) + pub fn test_wait_for(&self, dependency: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/testWaitFor", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a dependency on another resource + pub fn with_dependency(&self, dependency: &IResourceWithConnectionString) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withDependency", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the endpoints + pub fn with_endpoints(&self, endpoints: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpoints".to_string(), serde_json::to_value(&endpoints).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withEndpoints", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets environment variables + pub fn with_environment_variables(&self, variables: HashMap) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("variables".to_string(), serde_json::to_value(&variables).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withEnvironmentVariables", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Performs a cancellable operation + pub fn with_cancellable_operation(&self, operation: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(operation); + args.insert("operation".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCancellableOperation", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCommandContext +pub struct ExecuteCommandContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for ExecuteCommandContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl ExecuteCommandContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the ResourceName property + pub fn resource_name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.resourceName", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the ResourceName property + pub fn set_resource_name(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.setResourceName", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ExecuteCommandContext::new(handle, self.client.clone())) + } + + /// Gets the CancellationToken property + pub fn cancellation_token(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.cancellationToken", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(CancellationToken::new(handle, self.client.clone())) + } + + /// Sets the CancellationToken property + pub fn set_cancellation_token(&self, value: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + if let Some(token) = value { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("value".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.setCancellationToken", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ExecuteCommandContext::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder +pub struct IDistributedApplicationBuilder { + handle: Handle, + client: Arc, +} + +impl HasHandle for IDistributedApplicationBuilder { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IDistributedApplicationBuilder { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Adds a container resource + pub fn add_container(&self, name: &str, image: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("image".to_string(), serde_json::to_value(&image).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/addContainer", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ContainerResource::new(handle, self.client.clone())) + } + + /// Adds an executable resource + pub fn add_executable(&self, name: &str, command: &str, working_directory: &str, args: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("command".to_string(), serde_json::to_value(&command).unwrap_or(Value::Null)); + args.insert("workingDirectory".to_string(), serde_json::to_value(&working_directory).unwrap_or(Value::Null)); + args.insert("args".to_string(), serde_json::to_value(&args).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/addExecutable", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ExecutableResource::new(handle, self.client.clone())) + } + + /// Gets the AppHostDirectory property + pub fn app_host_directory(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/IDistributedApplicationBuilder.appHostDirectory", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the Eventing property + pub fn eventing(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/IDistributedApplicationBuilder.eventing", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IDistributedApplicationEventing::new(handle, self.client.clone())) + } + + /// Gets the ExecutionContext property + pub fn execution_context(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/IDistributedApplicationBuilder.executionContext", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplicationExecutionContext::new(handle, self.client.clone())) + } + + /// Builds the distributed application + pub fn build(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/build", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplication::new(handle, self.client.clone())) + } + + /// Adds a parameter resource + pub fn add_parameter(&self, name: &str, secret: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + if let Some(ref v) = secret { + args.insert("secret".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/addParameter", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ParameterResource::new(handle, self.client.clone())) + } + + /// Adds a connection string resource + pub fn add_connection_string(&self, name: &str, environment_variable_name: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + if let Some(ref v) = environment_variable_name { + args.insert("environmentVariableName".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/addConnectionString", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithConnectionString::new(handle, self.client.clone())) + } + + /// Adds a .NET project resource + pub fn add_project(&self, name: &str, project_path: &str, launch_profile_name: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("projectPath".to_string(), serde_json::to_value(&project_path).unwrap_or(Value::Null)); + args.insert("launchProfileName".to_string(), serde_json::to_value(&launch_profile_name).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/addProject", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ProjectResource::new(handle, self.client.clone())) + } + + /// Adds a test Redis resource + pub fn add_test_redis(&self, name: &str, port: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/addTestRedis", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestRedisResource::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEvent +pub struct IDistributedApplicationEvent { + handle: Handle, + client: Arc, +} + +impl HasHandle for IDistributedApplicationEvent { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IDistributedApplicationEvent { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEventing +pub struct IDistributedApplicationEventing { + handle: Handle, + client: Arc, +} + +impl HasHandle for IDistributedApplicationEventing { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IDistributedApplicationEventing { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Invokes the Unsubscribe method + pub fn unsubscribe(&self, subscription: &DistributedApplicationEventSubscription) -> Result<(), Box> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("subscription".to_string(), subscription.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.Eventing/IDistributedApplicationEventing.unsubscribe", args)?; + Ok(()) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationResourceEvent +pub struct IDistributedApplicationResourceEvent { + handle: Handle, + client: Arc, +} + +impl HasHandle for IDistributedApplicationResourceEvent { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IDistributedApplicationResourceEvent { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource +pub struct IResource { + handle: Handle, + client: Arc, +} + +impl HasHandle for IResource { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IResource { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithArgs +pub struct IResourceWithArgs { + handle: Handle, + client: Arc, +} + +impl HasHandle for IResourceWithArgs { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IResourceWithArgs { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithConnectionString +pub struct IResourceWithConnectionString { + handle: Handle, + client: Arc, +} + +impl HasHandle for IResourceWithConnectionString { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IResourceWithConnectionString { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints +pub struct IResourceWithEndpoints { + handle: Handle, + client: Arc, +} + +impl HasHandle for IResourceWithEndpoints { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IResourceWithEndpoints { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEnvironment +pub struct IResourceWithEnvironment { + handle: Handle, + client: Arc, +} + +impl HasHandle for IResourceWithEnvironment { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IResourceWithEnvironment { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.IResourceWithServiceDiscovery +pub struct IResourceWithServiceDiscovery { + handle: Handle, + client: Arc, +} + +impl HasHandle for IResourceWithServiceDiscovery { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IResourceWithServiceDiscovery { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithWaitSupport +pub struct IResourceWithWaitSupport { + handle: Handle, + client: Arc, +} + +impl HasHandle for IResourceWithWaitSupport { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IResourceWithWaitSupport { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ParameterResource +pub struct ParameterResource { + handle: Handle, + client: Arc, +} + +impl HasHandle for ParameterResource { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl ParameterResource { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Sets a parameter description + pub fn with_description(&self, description: &str, enable_markdown: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("description".to_string(), serde_json::to_value(&description).unwrap_or(Value::Null)); + if let Some(ref v) = enable_markdown { + args.insert("enableMarkdown".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withDescription", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ParameterResource::new(handle, self.client.clone())) + } + + /// Customizes displayed URLs via callback + pub fn with_urls_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlsCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Customizes displayed URLs via async callback + pub fn with_urls_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlsCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds or modifies displayed URLs + pub fn with_url(&self, url: &str, display_text: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("url".to_string(), serde_json::to_value(&url).unwrap_or(Value::Null)); + if let Some(ref v) = display_text { + args.insert("displayText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withUrl", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a URL using a reference expression + pub fn with_url_expression(&self, url: ReferenceExpression, display_text: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("url".to_string(), serde_json::to_value(&url).unwrap_or(Value::Null)); + if let Some(ref v) = display_text { + args.insert("displayText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withUrlExpression", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Customizes the URL for a specific endpoint via callback + pub fn with_url_for_endpoint(&self, endpoint_name: &str, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpointName".to_string(), serde_json::to_value(&endpoint_name).unwrap_or(Value::Null)); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlForEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Prevents resource from starting automatically + pub fn with_explicit_start(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withExplicitStart", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a health check by key + pub fn with_health_check(&self, key: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("key".to_string(), serde_json::to_value(&key).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withHealthCheck", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a resource command + pub fn with_command(&self, name: &str, display_name: &str, execute_command: impl Fn(Vec) -> Value + Send + Sync + 'static, command_options: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("displayName".to_string(), serde_json::to_value(&display_name).unwrap_or(Value::Null)); + let callback_id = register_callback(execute_command); + args.insert("executeCommand".to_string(), Value::String(callback_id)); + if let Some(ref v) = command_options { + args.insert("commandOptions".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withCommand", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the parent relationship + pub fn with_parent_relationship(&self, parent: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("parent".to_string(), parent.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withParentRelationship", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Gets the resource name + pub fn get_resource_name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("resource".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/getResourceName", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Adds an optional string parameter + pub fn with_optional_string(&self, value: Option<&str>, enabled: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = value { + args.insert("value".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = enabled { + args.insert("enabled".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalString", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures the resource with a DTO + pub fn with_config(&self, config: TestConfigDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the created timestamp + pub fn with_created_at(&self, created_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("createdAt".to_string(), serde_json::to_value(&created_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCreatedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the modified timestamp + pub fn with_modified_at(&self, modified_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("modifiedAt".to_string(), serde_json::to_value(&modified_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withModifiedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the correlation ID + pub fn with_correlation_id(&self, correlation_id: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("correlationId".to_string(), serde_json::to_value(&correlation_id).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCorrelationId", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with optional callback + pub fn with_optional_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the resource status + pub fn with_status(&self, status: TestResourceStatus) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("status".to_string(), serde_json::to_value(&status).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withStatus", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with nested DTO + pub fn with_nested_config(&self, config: TestNestedDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withNestedConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds validation callback + pub fn with_validator(&self, validator: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(validator); + args.insert("validator".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withValidator", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for another resource (test version) + pub fn test_wait_for(&self, dependency: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/testWaitFor", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a dependency on another resource + pub fn with_dependency(&self, dependency: &IResourceWithConnectionString) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withDependency", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the endpoints + pub fn with_endpoints(&self, endpoints: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpoints".to_string(), serde_json::to_value(&endpoints).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withEndpoints", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Performs a cancellable operation + pub fn with_cancellable_operation(&self, operation: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(operation); + args.insert("operation".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCancellableOperation", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ProjectResource +pub struct ProjectResource { + handle: Handle, + client: Arc, +} + +impl HasHandle for ProjectResource { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl ProjectResource { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Sets the number of replicas + pub fn with_replicas(&self, replicas: f64) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("replicas".to_string(), serde_json::to_value(&replicas).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withReplicas", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ProjectResource::new(handle, self.client.clone())) + } + + /// Sets an environment variable + pub fn with_environment(&self, name: &str, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironment", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds an environment variable with a reference expression + pub fn with_environment_expression(&self, name: &str, value: ReferenceExpression) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentExpression", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets environment variables via callback + pub fn with_environment_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets environment variables via async callback + pub fn with_environment_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds arguments + pub fn with_args(&self, args: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("args".to_string(), serde_json::to_value(&args).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgs", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Sets command-line arguments via callback + pub fn with_args_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgsCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Sets command-line arguments via async callback + pub fn with_args_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgsCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Adds a reference to another resource + pub fn with_reference(&self, source: &IResourceWithConnectionString, connection_name: Option<&str>, optional: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("source".to_string(), source.handle().to_json()); + if let Some(ref v) = connection_name { + args.insert("connectionName".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = optional { + args.insert("optional".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withReference", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds a service discovery reference to another resource + pub fn with_service_reference(&self, source: &IResourceWithServiceDiscovery) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("source".to_string(), source.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withServiceReference", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds a network endpoint + pub fn with_endpoint(&self, port: Option, target_port: Option, scheme: Option<&str>, name: Option<&str>, env: Option<&str>, is_proxied: Option, is_external: Option, protocol: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = scheme { + args.insert("scheme".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_external { + args.insert("isExternal".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = protocol { + args.insert("protocol".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds an HTTP endpoint + pub fn with_http_endpoint(&self, port: Option, target_port: Option, name: Option<&str>, env: Option<&str>, is_proxied: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds an HTTPS endpoint + pub fn with_https_endpoint(&self, port: Option, target_port: Option, name: Option<&str>, env: Option<&str>, is_proxied: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpsEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Makes HTTP endpoints externally accessible + pub fn with_external_http_endpoints(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withExternalHttpEndpoints", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Gets an endpoint reference + pub fn get_endpoint(&self, name: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/getEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(EndpointReference::new(handle, self.client.clone())) + } + + /// Configures resource for HTTP/2 + pub fn as_http2_service(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/asHttp2Service", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Customizes displayed URLs via callback + pub fn with_urls_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlsCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Customizes displayed URLs via async callback + pub fn with_urls_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlsCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds or modifies displayed URLs + pub fn with_url(&self, url: &str, display_text: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("url".to_string(), serde_json::to_value(&url).unwrap_or(Value::Null)); + if let Some(ref v) = display_text { + args.insert("displayText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withUrl", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a URL using a reference expression + pub fn with_url_expression(&self, url: ReferenceExpression, display_text: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("url".to_string(), serde_json::to_value(&url).unwrap_or(Value::Null)); + if let Some(ref v) = display_text { + args.insert("displayText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withUrlExpression", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Customizes the URL for a specific endpoint via callback + pub fn with_url_for_endpoint(&self, endpoint_name: &str, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpointName".to_string(), serde_json::to_value(&endpoint_name).unwrap_or(Value::Null)); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlForEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a URL for a specific endpoint via factory callback + pub fn with_url_for_endpoint_factory(&self, endpoint_name: &str, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpointName".to_string(), serde_json::to_value(&endpoint_name).unwrap_or(Value::Null)); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlForEndpointFactory", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Waits for another resource to be ready + pub fn wait_for(&self, dependency: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/waitFor", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithWaitSupport::new(handle, self.client.clone())) + } + + /// Prevents resource from starting automatically + pub fn with_explicit_start(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withExplicitStart", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for resource completion + pub fn wait_for_completion(&self, dependency: &IResource, exit_code: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + if let Some(ref v) = exit_code { + args.insert("exitCode".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/waitForCompletion", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithWaitSupport::new(handle, self.client.clone())) + } + + /// Adds a health check by key + pub fn with_health_check(&self, key: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("key".to_string(), serde_json::to_value(&key).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withHealthCheck", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds an HTTP health check + pub fn with_http_health_check(&self, path: Option<&str>, status_code: Option, endpoint_name: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = path { + args.insert("path".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = status_code { + args.insert("statusCode".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = endpoint_name { + args.insert("endpointName".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpHealthCheck", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds a resource command + pub fn with_command(&self, name: &str, display_name: &str, execute_command: impl Fn(Vec) -> Value + Send + Sync + 'static, command_options: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("displayName".to_string(), serde_json::to_value(&display_name).unwrap_or(Value::Null)); + let callback_id = register_callback(execute_command); + args.insert("executeCommand".to_string(), Value::String(callback_id)); + if let Some(ref v) = command_options { + args.insert("commandOptions".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withCommand", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the parent relationship + pub fn with_parent_relationship(&self, parent: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("parent".to_string(), parent.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withParentRelationship", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Gets the resource name + pub fn get_resource_name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("resource".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/getResourceName", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Adds an optional string parameter + pub fn with_optional_string(&self, value: Option<&str>, enabled: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = value { + args.insert("value".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = enabled { + args.insert("enabled".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalString", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures the resource with a DTO + pub fn with_config(&self, config: TestConfigDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures environment with callback (test version) + pub fn test_with_environment_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/testWithEnvironmentCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets the created timestamp + pub fn with_created_at(&self, created_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("createdAt".to_string(), serde_json::to_value(&created_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCreatedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the modified timestamp + pub fn with_modified_at(&self, modified_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("modifiedAt".to_string(), serde_json::to_value(&modified_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withModifiedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the correlation ID + pub fn with_correlation_id(&self, correlation_id: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("correlationId".to_string(), serde_json::to_value(&correlation_id).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCorrelationId", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with optional callback + pub fn with_optional_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the resource status + pub fn with_status(&self, status: TestResourceStatus) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("status".to_string(), serde_json::to_value(&status).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withStatus", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with nested DTO + pub fn with_nested_config(&self, config: TestNestedDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withNestedConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds validation callback + pub fn with_validator(&self, validator: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(validator); + args.insert("validator".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withValidator", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for another resource (test version) + pub fn test_wait_for(&self, dependency: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/testWaitFor", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a dependency on another resource + pub fn with_dependency(&self, dependency: &IResourceWithConnectionString) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withDependency", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the endpoints + pub fn with_endpoints(&self, endpoints: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpoints".to_string(), serde_json::to_value(&endpoints).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withEndpoints", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets environment variables + pub fn with_environment_variables(&self, variables: HashMap) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("variables".to_string(), serde_json::to_value(&variables).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withEnvironmentVariables", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Performs a cancellable operation + pub fn with_cancellable_operation(&self, operation: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(operation); + args.insert("operation".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCancellableOperation", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceUrlsCallbackContext +pub struct ResourceUrlsCallbackContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for ResourceUrlsCallbackContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl ResourceUrlsCallbackContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Urls property + pub fn urls(&self) -> AspireList { + AspireList::with_getter(self.handle.clone(), self.client.clone(), "Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.urls") + } + + /// Gets the CancellationToken property + pub fn cancellation_token(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.cancellationToken", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(CancellationToken::new(handle, self.client.clone())) + } + + /// Gets the ExecutionContext property + pub fn execution_context(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/ResourceUrlsCallbackContext.executionContext", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(DistributedApplicationExecutionContext::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCallbackContext +pub struct TestCallbackContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for TestCallbackContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl TestCallbackContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Name property + pub fn name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.name", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Name property + pub fn set_name(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setName", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestCallbackContext::new(handle, self.client.clone())) + } + + /// Gets the Value property + pub fn value(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.value", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Value property + pub fn set_value(&self, value: f64) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setValue", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestCallbackContext::new(handle, self.client.clone())) + } + + /// Gets the CancellationToken property + pub fn cancellation_token(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.cancellationToken", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(CancellationToken::new(handle, self.client.clone())) + } + + /// Sets the CancellationToken property + pub fn set_cancellation_token(&self, value: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + if let Some(token) = value { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("value".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCallbackContext.setCancellationToken", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestCallbackContext::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestCollectionContext +pub struct TestCollectionContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for TestCollectionContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl TestCollectionContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Items property + pub fn items(&self) -> AspireList { + AspireList::with_getter(self.handle.clone(), self.client.clone(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.items") + } + + /// Gets the Metadata property + pub fn metadata(&self) -> AspireDict { + AspireDict::with_getter(self.handle.clone(), self.client.clone(), "Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestCollectionContext.metadata") + } +} + +/// Wrapper for Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestEnvironmentContext +pub struct TestEnvironmentContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for TestEnvironmentContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl TestEnvironmentContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Name property + pub fn name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.name", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Name property + pub fn set_name(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setName", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestEnvironmentContext::new(handle, self.client.clone())) + } + + /// Gets the Description property + pub fn description(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.description", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Description property + pub fn set_description(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setDescription", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestEnvironmentContext::new(handle, self.client.clone())) + } + + /// Gets the Priority property + pub fn priority(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.priority", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Priority property + pub fn set_priority(&self, value: f64) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestEnvironmentContext.setPriority", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestEnvironmentContext::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource +pub struct TestRedisResource { + handle: Handle, + client: Arc, +} + +impl HasHandle for TestRedisResource { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl TestRedisResource { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Adds a bind mount + pub fn with_bind_mount(&self, source: &str, target: &str, is_read_only: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("source".to_string(), serde_json::to_value(&source).unwrap_or(Value::Null)); + args.insert("target".to_string(), serde_json::to_value(&target).unwrap_or(Value::Null)); + if let Some(ref v) = is_read_only { + args.insert("isReadOnly".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withBindMount", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ContainerResource::new(handle, self.client.clone())) + } + + /// Sets the container entrypoint + pub fn with_entrypoint(&self, entrypoint: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("entrypoint".to_string(), serde_json::to_value(&entrypoint).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withEntrypoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ContainerResource::new(handle, self.client.clone())) + } + + /// Sets the container image tag + pub fn with_image_tag(&self, tag: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("tag".to_string(), serde_json::to_value(&tag).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withImageTag", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ContainerResource::new(handle, self.client.clone())) + } + + /// Sets the container image registry + pub fn with_image_registry(&self, registry: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("registry".to_string(), serde_json::to_value(®istry).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withImageRegistry", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ContainerResource::new(handle, self.client.clone())) + } + + /// Sets the container image + pub fn with_image(&self, image: &str, tag: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("image".to_string(), serde_json::to_value(&image).unwrap_or(Value::Null)); + if let Some(ref v) = tag { + args.insert("tag".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withImage", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ContainerResource::new(handle, self.client.clone())) + } + + /// Adds runtime arguments for the container + pub fn with_container_runtime_args(&self, args: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("args".to_string(), serde_json::to_value(&args).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withContainerRuntimeArgs", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ContainerResource::new(handle, self.client.clone())) + } + + /// Sets the lifetime behavior of the container resource + pub fn with_lifetime(&self, lifetime: ContainerLifetime) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("lifetime".to_string(), serde_json::to_value(&lifetime).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withLifetime", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ContainerResource::new(handle, self.client.clone())) + } + + /// Sets the container image pull policy + pub fn with_image_pull_policy(&self, pull_policy: ImagePullPolicy) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("pullPolicy".to_string(), serde_json::to_value(&pull_policy).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withImagePullPolicy", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ContainerResource::new(handle, self.client.clone())) + } + + /// Sets the container name + pub fn with_container_name(&self, name: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withContainerName", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ContainerResource::new(handle, self.client.clone())) + } + + /// Sets an environment variable + pub fn with_environment(&self, name: &str, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironment", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds an environment variable with a reference expression + pub fn with_environment_expression(&self, name: &str, value: ReferenceExpression) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentExpression", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets environment variables via callback + pub fn with_environment_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets environment variables via async callback + pub fn with_environment_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withEnvironmentCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds arguments + pub fn with_args(&self, args: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("args".to_string(), serde_json::to_value(&args).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgs", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Sets command-line arguments via callback + pub fn with_args_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgsCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Sets command-line arguments via async callback + pub fn with_args_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withArgsCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithArgs::new(handle, self.client.clone())) + } + + /// Adds a reference to another resource + pub fn with_reference(&self, source: &IResourceWithConnectionString, connection_name: Option<&str>, optional: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("source".to_string(), source.handle().to_json()); + if let Some(ref v) = connection_name { + args.insert("connectionName".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = optional { + args.insert("optional".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withReference", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds a service discovery reference to another resource + pub fn with_service_reference(&self, source: &IResourceWithServiceDiscovery) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("source".to_string(), source.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withServiceReference", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Adds a network endpoint + pub fn with_endpoint(&self, port: Option, target_port: Option, scheme: Option<&str>, name: Option<&str>, env: Option<&str>, is_proxied: Option, is_external: Option, protocol: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = scheme { + args.insert("scheme".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_external { + args.insert("isExternal".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = protocol { + args.insert("protocol".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds an HTTP endpoint + pub fn with_http_endpoint(&self, port: Option, target_port: Option, name: Option<&str>, env: Option<&str>, is_proxied: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds an HTTPS endpoint + pub fn with_https_endpoint(&self, port: Option, target_port: Option, name: Option<&str>, env: Option<&str>, is_proxied: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = port { + args.insert("port".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = target_port { + args.insert("targetPort".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = env { + args.insert("env".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_proxied { + args.insert("isProxied".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpsEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Makes HTTP endpoints externally accessible + pub fn with_external_http_endpoints(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withExternalHttpEndpoints", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Gets an endpoint reference + pub fn get_endpoint(&self, name: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/getEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(EndpointReference::new(handle, self.client.clone())) + } + + /// Configures resource for HTTP/2 + pub fn as_http2_service(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/asHttp2Service", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Customizes displayed URLs via callback + pub fn with_urls_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlsCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Customizes displayed URLs via async callback + pub fn with_urls_callback_async(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlsCallbackAsync", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds or modifies displayed URLs + pub fn with_url(&self, url: &str, display_text: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("url".to_string(), serde_json::to_value(&url).unwrap_or(Value::Null)); + if let Some(ref v) = display_text { + args.insert("displayText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withUrl", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a URL using a reference expression + pub fn with_url_expression(&self, url: ReferenceExpression, display_text: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("url".to_string(), serde_json::to_value(&url).unwrap_or(Value::Null)); + if let Some(ref v) = display_text { + args.insert("displayText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withUrlExpression", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Customizes the URL for a specific endpoint via callback + pub fn with_url_for_endpoint(&self, endpoint_name: &str, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpointName".to_string(), serde_json::to_value(&endpoint_name).unwrap_or(Value::Null)); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlForEndpoint", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a URL for a specific endpoint via factory callback + pub fn with_url_for_endpoint_factory(&self, endpoint_name: &str, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpointName".to_string(), serde_json::to_value(&endpoint_name).unwrap_or(Value::Null)); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting/withUrlForEndpointFactory", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Waits for another resource to be ready + pub fn wait_for(&self, dependency: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/waitFor", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithWaitSupport::new(handle, self.client.clone())) + } + + /// Prevents resource from starting automatically + pub fn with_explicit_start(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withExplicitStart", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for resource completion + pub fn wait_for_completion(&self, dependency: &IResource, exit_code: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + if let Some(ref v) = exit_code { + args.insert("exitCode".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/waitForCompletion", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithWaitSupport::new(handle, self.client.clone())) + } + + /// Adds a health check by key + pub fn with_health_check(&self, key: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("key".to_string(), serde_json::to_value(&key).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting/withHealthCheck", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds an HTTP health check + pub fn with_http_health_check(&self, path: Option<&str>, status_code: Option, endpoint_name: Option<&str>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = path { + args.insert("path".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = status_code { + args.insert("statusCode".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = endpoint_name { + args.insert("endpointName".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withHttpHealthCheck", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEndpoints::new(handle, self.client.clone())) + } + + /// Adds a resource command + pub fn with_command(&self, name: &str, display_name: &str, execute_command: impl Fn(Vec) -> Value + Send + Sync + 'static, command_options: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + args.insert("displayName".to_string(), serde_json::to_value(&display_name).unwrap_or(Value::Null)); + let callback_id = register_callback(execute_command); + args.insert("executeCommand".to_string(), Value::String(callback_id)); + if let Some(ref v) = command_options { + args.insert("commandOptions".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withCommand", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the parent relationship + pub fn with_parent_relationship(&self, parent: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("parent".to_string(), parent.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/withParentRelationship", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds a volume + pub fn with_volume(&self, target: &str, name: Option<&str>, is_read_only: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("resource".to_string(), self.handle.to_json()); + args.insert("target".to_string(), serde_json::to_value(&target).unwrap_or(Value::Null)); + if let Some(ref v) = name { + args.insert("name".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = is_read_only { + args.insert("isReadOnly".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/withVolume", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(ContainerResource::new(handle, self.client.clone())) + } + + /// Gets the resource name + pub fn get_resource_name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("resource".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/getResourceName", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Configures the Redis resource with persistence + pub fn with_persistence(&self, mode: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = mode { + args.insert("mode".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withPersistence", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestRedisResource::new(handle, self.client.clone())) + } + + /// Adds an optional string parameter + pub fn with_optional_string(&self, value: Option<&str>, enabled: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(ref v) = value { + args.insert("value".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = enabled { + args.insert("enabled".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalString", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures the resource with a DTO + pub fn with_config(&self, config: TestConfigDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Gets the tags for the resource + pub fn get_tags(&self) -> AspireList { + AspireList::with_getter(self.handle.clone(), self.client.clone(), "Aspire.Hosting.CodeGeneration.Rust.Tests/getTags") + } + + /// Gets the metadata for the resource + pub fn get_metadata(&self) -> AspireDict { + AspireDict::with_getter(self.handle.clone(), self.client.clone(), "Aspire.Hosting.CodeGeneration.Rust.Tests/getMetadata") + } + + /// Sets the connection string using a reference expression + pub fn with_connection_string(&self, connection_string: ReferenceExpression) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("connectionString".to_string(), serde_json::to_value(&connection_string).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withConnectionString", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithConnectionString::new(handle, self.client.clone())) + } + + /// Configures environment with callback (test version) + pub fn test_with_environment_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/testWithEnvironmentCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Sets the created timestamp + pub fn with_created_at(&self, created_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("createdAt".to_string(), serde_json::to_value(&created_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCreatedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the modified timestamp + pub fn with_modified_at(&self, modified_at: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("modifiedAt".to_string(), serde_json::to_value(&modified_at).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withModifiedAt", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the correlation ID + pub fn with_correlation_id(&self, correlation_id: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("correlationId".to_string(), serde_json::to_value(&correlation_id).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCorrelationId", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with optional callback + pub fn with_optional_callback(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalCallback", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the resource status + pub fn with_status(&self, status: TestResourceStatus) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("status".to_string(), serde_json::to_value(&status).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withStatus", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Configures with nested DTO + pub fn with_nested_config(&self, config: TestNestedDto) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("config".to_string(), serde_json::to_value(&config).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withNestedConfig", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Adds validation callback + pub fn with_validator(&self, validator: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(validator); + args.insert("validator".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withValidator", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for another resource (test version) + pub fn test_wait_for(&self, dependency: &IResource) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/testWaitFor", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Gets the endpoints + pub fn get_endpoints(&self) -> Result, Box> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/getEndpoints", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets connection string using direct interface target + pub fn with_connection_string_direct(&self, connection_string: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("connectionString".to_string(), serde_json::to_value(&connection_string).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withConnectionStringDirect", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithConnectionString::new(handle, self.client.clone())) + } + + /// Redis-specific configuration + pub fn with_redis_specific(&self, option: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("option".to_string(), serde_json::to_value(&option).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withRedisSpecific", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestRedisResource::new(handle, self.client.clone())) + } + + /// Adds a dependency on another resource + pub fn with_dependency(&self, dependency: &IResourceWithConnectionString) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("dependency".to_string(), dependency.handle().to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withDependency", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets the endpoints + pub fn with_endpoints(&self, endpoints: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("endpoints".to_string(), serde_json::to_value(&endpoints).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withEndpoints", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Sets environment variables + pub fn with_environment_variables(&self, variables: HashMap) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("variables".to_string(), serde_json::to_value(&variables).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withEnvironmentVariables", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResourceWithEnvironment::new(handle, self.client.clone())) + } + + /// Gets the status of the resource asynchronously + pub fn get_status_async(&self, cancellation_token: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + if let Some(token) = cancellation_token { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("cancellationToken".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/getStatusAsync", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Performs a cancellable operation + pub fn with_cancellable_operation(&self, operation: impl Fn(Vec) -> Value + Send + Sync + 'static) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + let callback_id = register_callback(operation); + args.insert("operation".to_string(), Value::String(callback_id)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/withCancellableOperation", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IResource::new(handle, self.client.clone())) + } + + /// Waits for the resource to be ready + pub fn wait_for_ready_async(&self, timeout: f64, cancellation_token: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("builder".to_string(), self.handle.to_json()); + args.insert("timeout".to_string(), serde_json::to_value(&timeout).unwrap_or(Value::Null)); + if let Some(token) = cancellation_token { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("cancellationToken".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.Rust.Tests/waitForReadyAsync", args)?; + Ok(serde_json::from_value(result)?) + } +} + +/// Wrapper for Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestResourceContext +pub struct TestResourceContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for TestResourceContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl TestResourceContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the Name property + pub fn name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.name", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Name property + pub fn set_name(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setName", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestResourceContext::new(handle, self.client.clone())) + } + + /// Gets the Value property + pub fn value(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.value", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the Value property + pub fn set_value(&self, value: f64) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValue", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(TestResourceContext::new(handle, self.client.clone())) + } + + /// Invokes the GetValueAsync method + pub fn get_value_async(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.getValueAsync", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Invokes the SetValueAsync method + pub fn set_value_async(&self, value: &str) -> Result<(), Box> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.setValueAsync", args)?; + Ok(()) + } + + /// Invokes the ValidateAsync method + pub fn validate_async(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes/TestResourceContext.validateAsync", args)?; + Ok(serde_json::from_value(result)?) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.UpdateCommandStateContext +pub struct UpdateCommandStateContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for UpdateCommandStateContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl UpdateCommandStateContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } +} + +// ============================================================================ +// Handle wrapper registrations +// ============================================================================ + +pub fn register_all_wrappers() { + // Handle wrappers are created inline in generated code + // This function is provided for API compatibility +} + +// ============================================================================ +// Connection Helpers +// ============================================================================ + +/// Establishes a connection to the AppHost server. +pub fn connect() -> Result, Box> { + let socket_path = std::env::var("REMOTE_APP_HOST_SOCKET_PATH") + .map_err(|_| "REMOTE_APP_HOST_SOCKET_PATH environment variable not set. Run this application using `aspire run`")?; + let client = Arc::new(AspireClient::new(&socket_path)); + client.connect()?; + Ok(client) +} + +/// Creates a new distributed application builder. +pub fn create_builder(options: Option) -> Result> { + let client = connect()?; + let mut resolved_options: HashMap = HashMap::new(); + if let Some(opts) = options { + for (k, v) in opts.to_map() { + resolved_options.insert(k, v); + } + } + if !resolved_options.contains_key("Args") { + let args: Vec = std::env::args().skip(1).collect(); + resolved_options.insert("Args".to_string(), serde_json::to_value(args).unwrap_or(Value::Null)); + } + if !resolved_options.contains_key("ProjectDirectory") { + if let Ok(pwd) = std::env::current_dir() { + resolved_options.insert("ProjectDirectory".to_string(), Value::String(pwd.to_string_lossy().to_string())); + } + } + let mut args: HashMap = HashMap::new(); + args.insert("options".to_string(), serde_json::to_value(resolved_options).unwrap_or(Value::Null)); + let result = client.invoke_capability("Aspire.Hosting/createBuilderWithOptions", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IDistributedApplicationBuilder::new(handle, client)) +} + diff --git a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/WithOptionalStringCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/WithOptionalStringCapability.verified.txt new file mode 100644 index 00000000000..771ce4719da --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/WithOptionalStringCapability.verified.txt @@ -0,0 +1,75 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Rust.Tests/withOptionalString, + MethodName: withOptionalString, + QualifiedMethodName: withOptionalString, + Description: Adds an optional string parameter, + Parameters: [ + { + Name: value, + Type: { + TypeId: string, + ClrType: string, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false + }, + { + Name: enabled, + Type: { + TypeId: boolean, + ClrType: bool, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false, + DefaultValue: true + } + ], + ReturnType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + ClrType: IResource, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + TargetType: { + TypeId: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, + ClrType: IResource, + Category: Handle, + IsInterface: true, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.WithOptionalString +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/WithPersistenceCapability.verified.txt b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/WithPersistenceCapability.verified.txt new file mode 100644 index 00000000000..627c2d56f5a --- /dev/null +++ b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/WithPersistenceCapability.verified.txt @@ -0,0 +1,61 @@ +{ + CapabilityId: Aspire.Hosting.CodeGeneration.Rust.Tests/withPersistence, + MethodName: withPersistence, + QualifiedMethodName: withPersistence, + Description: Configures the Redis resource with persistence, + Parameters: [ + { + Name: mode, + Type: { + TypeId: enum:Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestPersistenceMode, + ClrType: TestPersistenceMode, + Category: Enum, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: false, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + IsOptional: true, + IsNullable: false, + IsCallback: false, + DefaultValue: Volume + } + ], + ReturnType: { + TypeId: Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetTypeId: Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + TargetType: { + TypeId: Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + }, + TargetParameterName: builder, + ExpandedTargetTypes: [ + { + TypeId: Aspire.Hosting.CodeGeneration.Rust.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestRedisResource, + ClrType: TestRedisResource, + Category: Handle, + IsInterface: false, + IsReadOnly: false, + IsResourceBuilder: true, + IsDistributedApplicationBuilder: false, + IsDistributedApplication: false + } + ], + ReturnsBuilder: true, + SourceLocation: Aspire.Hosting.CodeGeneration.TypeScript.Tests.TestTypes.TestExtensions.WithPersistence +} \ No newline at end of file diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs index c50dcebf6f5..1120a7082f5 100644 --- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs +++ b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs @@ -18,34 +18,6 @@ public void Language_ReturnsTypeScript() Assert.Equal("TypeScript", _generator.Language); } - [Fact] - public async Task EmbeddedResource_TransportTs_MatchesSnapshot() - { - var assembly = typeof(AtsTypeScriptCodeGenerator).Assembly; - var resourceName = "Aspire.Hosting.CodeGeneration.TypeScript.Resources.transport.ts"; - - using var stream = assembly.GetManifestResourceStream(resourceName)!; - using var reader = new StreamReader(stream); - var content = await reader.ReadToEndAsync(); - - await Verify(content, extension: "ts") - .UseFileName("transport"); - } - - [Fact] - public async Task EmbeddedResource_BaseTs_MatchesSnapshot() - { - var assembly = typeof(AtsTypeScriptCodeGenerator).Assembly; - var resourceName = "Aspire.Hosting.CodeGeneration.TypeScript.Resources.base.ts"; - - using var stream = assembly.GetManifestResourceStream(resourceName)!; - using var reader = new StreamReader(stream); - var content = await reader.ReadToEndAsync(); - - await Verify(content, extension: "ts") - .UseFileName("base"); - } - [Fact] public async Task EmbeddedResource_PackageJson_MatchesSnapshot() { diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/base.verified.ts b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/base.verified.ts deleted file mode 100644 index 475f9a97a8d..00000000000 --- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/base.verified.ts +++ /dev/null @@ -1,453 +0,0 @@ -// aspire.ts - Core Aspire types: base classes, ReferenceExpression -import { Handle, AspireClient, MarshalledHandle } from './transport.js'; - -// Re-export transport types for convenience -export { Handle, AspireClient, CapabilityError, registerCallback, unregisterCallback, registerCancellation, unregisterCancellation } from './transport.js'; -export type { MarshalledHandle, AtsError, AtsErrorDetails, CallbackFunction } from './transport.js'; -export { AtsErrorCodes, isMarshalledHandle, isAtsError, wrapIfHandle } from './transport.js'; - -// ============================================================================ -// Reference Expression -// ============================================================================ - -/** - * Represents a reference expression that can be passed to capabilities. - * - * Reference expressions are serialized in the protocol as: - * ```json - * { - * "$expr": { - * "format": "redis://{0}:{1}", - * "valueProviders": [ - * { "$handle": "Aspire.Hosting.ApplicationModel/EndpointReference:1" }, - * { "$handle": "Aspire.Hosting.ApplicationModel/EndpointReference:2" } - * ] - * } - * } - * ``` - * - * @example - * ```typescript - * const redis = await builder.addRedis("cache"); - * const endpoint = await redis.getEndpoint("tcp"); - * - * // Create a reference expression - * const expr = refExpr`redis://${endpoint}:6379`; - * - * // Use it in an environment variable - * await api.withEnvironment("REDIS_URL", expr); - * ``` - */ -export class ReferenceExpression { - private readonly _format: string; - private readonly _valueProviders: unknown[]; - - private constructor(format: string, valueProviders: unknown[]) { - this._format = format; - this._valueProviders = valueProviders; - } - - /** - * Creates a reference expression from a tagged template literal. - * - * @param strings - The template literal string parts - * @param values - The interpolated values (handles to value providers) - * @returns A ReferenceExpression instance - */ - static create(strings: TemplateStringsArray, ...values: unknown[]): ReferenceExpression { - // Build the format string with {0}, {1}, etc. placeholders - let format = ''; - for (let i = 0; i < strings.length; i++) { - format += strings[i]; - if (i < values.length) { - format += `{${i}}`; - } - } - - // Extract handles from values - const valueProviders = values.map(extractHandleForExpr); - - return new ReferenceExpression(format, valueProviders); - } - - /** - * Serializes the reference expression for JSON-RPC transport. - * Uses the $expr format recognized by the server. - */ - toJSON(): { $expr: { format: string; valueProviders?: unknown[] } } { - return { - $expr: { - format: this._format, - valueProviders: this._valueProviders.length > 0 ? this._valueProviders : undefined - } - }; - } - - /** - * String representation for debugging. - */ - toString(): string { - return `ReferenceExpression(${this._format})`; - } -} - -/** - * Extracts a value for use in reference expressions. - * Supports handles (objects) and string literals. - * @internal - */ -function extractHandleForExpr(value: unknown): unknown { - if (value === null || value === undefined) { - throw new Error('Cannot use null or undefined in reference expression'); - } - - // String literals - include directly in the expression - if (typeof value === 'string') { - return value; - } - - // Number literals - convert to string - if (typeof value === 'number') { - return String(value); - } - - // Handle objects - get their JSON representation - if (value instanceof Handle) { - return value.toJSON(); - } - - // Objects with $handle property (already in handle format) - if (typeof value === 'object' && value !== null && '$handle' in value) { - return value; - } - - // Objects with toJSON that returns a handle - if (typeof value === 'object' && value !== null && 'toJSON' in value && typeof value.toJSON === 'function') { - const json = value.toJSON(); - if (json && typeof json === 'object' && '$handle' in json) { - return json; - } - } - - throw new Error( - `Cannot use value of type ${typeof value} in reference expression. ` + - `Expected a Handle, string, or number.` - ); -} - -/** - * Tagged template function for creating reference expressions. - * - * Use this to create dynamic expressions that reference endpoints, parameters, and other - * value providers. The expression is evaluated at runtime by Aspire. - * - * @example - * ```typescript - * const redis = await builder.addRedis("cache"); - * const endpoint = await redis.getEndpoint("tcp"); - * - * // Create a reference expression using the tagged template - * const expr = refExpr`redis://${endpoint}:6379`; - * - * // Use it in an environment variable - * await api.withEnvironment("REDIS_URL", expr); - * ``` - */ -export function refExpr(strings: TemplateStringsArray, ...values: unknown[]): ReferenceExpression { - return ReferenceExpression.create(strings, ...values); -} - -// ============================================================================ -// ResourceBuilderBase -// ============================================================================ - -/** - * Base class for resource builders (e.g., RedisBuilder, ContainerBuilder). - * Provides handle management and JSON serialization. - */ -export class ResourceBuilderBase { - constructor(protected _handle: THandle, protected _client: AspireClient) {} - - toJSON(): MarshalledHandle { return this._handle.toJSON(); } -} - -// ============================================================================ -// AspireList - Mutable List Wrapper -// ============================================================================ - -/** - * Wrapper for a mutable .NET List. - * Provides array-like methods that invoke capabilities on the underlying collection. - * - * @example - * ```typescript - * const items = await resource.getItems(); // Returns AspireList - * const count = await items.count(); - * const first = await items.get(0); - * await items.add(newItem); - * ``` - */ -export class AspireList { - private _resolvedHandle?: Handle; - private _resolvePromise?: Promise; - - constructor( - private readonly _handleOrContext: Handle, - private readonly _client: AspireClient, - private readonly _typeId: string, - private readonly _getterCapabilityId?: string - ) { - // If no getter capability, the handle is already the list handle - if (!_getterCapabilityId) { - this._resolvedHandle = _handleOrContext; - } - } - - /** - * Ensures we have the actual list handle by calling the getter if needed. - */ - private async _ensureHandle(): Promise { - if (this._resolvedHandle) { - return this._resolvedHandle; - } - if (this._resolvePromise) { - return this._resolvePromise; - } - // Call the getter capability to get the actual list handle - this._resolvePromise = (async () => { - const result = await this._client.invokeCapability(this._getterCapabilityId!, { - context: this._handleOrContext - }); - this._resolvedHandle = result as Handle; - return this._resolvedHandle; - })(); - return this._resolvePromise; - } - - /** - * Gets the number of elements in the list. - */ - async count(): Promise { - const handle = await this._ensureHandle(); - return await this._client.invokeCapability('Aspire.Hosting/List.length', { - list: handle - }) as number; - } - - /** - * Gets the element at the specified index. - */ - async get(index: number): Promise { - const handle = await this._ensureHandle(); - return await this._client.invokeCapability('Aspire.Hosting/List.get', { - list: handle, - index - }) as T; - } - - /** - * Adds an element to the end of the list. - */ - async add(item: T): Promise { - const handle = await this._ensureHandle(); - await this._client.invokeCapability('Aspire.Hosting/List.add', { - list: handle, - item - }); - } - - /** - * Removes the element at the specified index. - */ - async removeAt(index: number): Promise { - const handle = await this._ensureHandle(); - await this._client.invokeCapability('Aspire.Hosting/List.removeAt', { - list: handle, - index - }); - } - - /** - * Clears all elements from the list. - */ - async clear(): Promise { - const handle = await this._ensureHandle(); - await this._client.invokeCapability('Aspire.Hosting/List.clear', { - list: handle - }); - } - - /** - * Converts the list to an array (creates a copy). - */ - async toArray(): Promise { - const handle = await this._ensureHandle(); - return await this._client.invokeCapability('Aspire.Hosting/List.toArray', { - list: handle - }) as T[]; - } - - toJSON(): MarshalledHandle { - if (this._resolvedHandle) { - return this._resolvedHandle.toJSON(); - } - return this._handleOrContext.toJSON(); - } -} - -// ============================================================================ -// AspireDict - Mutable Dictionary Wrapper -// ============================================================================ - -/** - * Wrapper for a mutable .NET Dictionary. - * Provides object-like methods that invoke capabilities on the underlying collection. - * - * @example - * ```typescript - * const config = await resource.getConfig(); // Returns AspireDict - * const value = await config.get("key"); - * await config.set("key", "value"); - * const hasKey = await config.containsKey("key"); - * ``` - */ -export class AspireDict { - private _resolvedHandle?: Handle; - private _resolvePromise?: Promise; - - constructor( - private readonly _handleOrContext: Handle, - private readonly _client: AspireClient, - private readonly _typeId: string, - private readonly _getterCapabilityId?: string - ) { - // If no getter capability, the handle is already the dictionary handle - if (!_getterCapabilityId) { - this._resolvedHandle = _handleOrContext; - } - } - - /** - * Ensures we have the actual dictionary handle by calling the getter if needed. - */ - private async _ensureHandle(): Promise { - if (this._resolvedHandle) { - return this._resolvedHandle; - } - if (this._resolvePromise) { - return this._resolvePromise; - } - // Call the getter capability to get the actual dictionary handle - this._resolvePromise = (async () => { - const result = await this._client.invokeCapability(this._getterCapabilityId!, { - context: this._handleOrContext - }); - this._resolvedHandle = result as Handle; - return this._resolvedHandle; - })(); - return this._resolvePromise; - } - - /** - * Gets the number of key-value pairs in the dictionary. - */ - async count(): Promise { - const handle = await this._ensureHandle(); - return await this._client.invokeCapability('Aspire.Hosting/Dict.count', { - dict: handle - }) as number; - } - - /** - * Gets the value associated with the specified key. - * @throws If the key is not found. - */ - async get(key: K): Promise { - const handle = await this._ensureHandle(); - return await this._client.invokeCapability('Aspire.Hosting/Dict.get', { - dict: handle, - key - }) as V; - } - - /** - * Sets the value for the specified key. - */ - async set(key: K, value: V): Promise { - const handle = await this._ensureHandle(); - await this._client.invokeCapability('Aspire.Hosting/Dict.set', { - dict: handle, - key, - value - }); - } - - /** - * Determines whether the dictionary contains the specified key. - */ - async containsKey(key: K): Promise { - const handle = await this._ensureHandle(); - return await this._client.invokeCapability('Aspire.Hosting/Dict.has', { - dict: handle, - key - }) as boolean; - } - - /** - * Removes the value with the specified key. - * @returns True if the element was removed; false if the key was not found. - */ - async remove(key: K): Promise { - const handle = await this._ensureHandle(); - return await this._client.invokeCapability('Aspire.Hosting/Dict.remove', { - dict: handle, - key - }) as boolean; - } - - /** - * Clears all key-value pairs from the dictionary. - */ - async clear(): Promise { - const handle = await this._ensureHandle(); - await this._client.invokeCapability('Aspire.Hosting/Dict.clear', { - dict: handle - }); - } - - /** - * Gets all keys in the dictionary. - */ - async keys(): Promise { - const handle = await this._ensureHandle(); - return await this._client.invokeCapability('Aspire.Hosting/Dict.keys', { - dict: handle - }) as K[]; - } - - /** - * Gets all values in the dictionary. - */ - async values(): Promise { - const handle = await this._ensureHandle(); - return await this._client.invokeCapability('Aspire.Hosting/Dict.values', { - dict: handle - }) as V[]; - } - - /** - * Converts the dictionary to a plain object (creates a copy). - * Only works when K is string. - */ - async toObject(): Promise> { - const handle = await this._ensureHandle(); - return await this._client.invokeCapability('Aspire.Hosting/Dict.toObject', { - dict: handle - }) as Record; - } - - async toJSON(): Promise { - const handle = await this._ensureHandle(); - return handle.toJSON(); - } -} diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/transport.verified.ts b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/transport.verified.ts deleted file mode 100644 index 50d6aeeaf5f..00000000000 --- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/transport.verified.ts +++ /dev/null @@ -1,557 +0,0 @@ -// transport.ts - ATS transport layer: RPC, Handle, errors, callbacks -import * as net from 'net'; -import * as rpc from 'vscode-jsonrpc/node.js'; - -// ============================================================================ -// Base Types -// ============================================================================ - -/** - * Type for callback functions that can be registered and invoked from .NET. - * Internal: receives args and client for handle wrapping. - */ -export type CallbackFunction = (args: unknown, client: AspireClient) => unknown | Promise; - -/** - * Represents a handle to a .NET object in the ATS system. - * Handles are typed references that can be passed between capabilities. - */ -export interface MarshalledHandle { - /** The handle ID (instance number) */ - $handle: string; - /** The ATS type ID */ - $type: string; -} - -/** - * Error details for ATS errors. - */ -export interface AtsErrorDetails { - /** The parameter that caused the error */ - parameter?: string; - /** The expected type or value */ - expected?: string; - /** The actual type or value */ - actual?: string; -} - -/** - * Structured error from ATS capability invocation. - */ -export interface AtsError { - /** Machine-readable error code */ - code: string; - /** Human-readable error message */ - message: string; - /** The capability that failed (if applicable) */ - capability?: string; - /** Additional error details */ - details?: AtsErrorDetails; -} - -/** - * ATS error codes returned by the server. - */ -export const AtsErrorCodes = { - /** Unknown capability ID */ - CapabilityNotFound: 'CAPABILITY_NOT_FOUND', - /** Handle ID doesn't exist or was disposed */ - HandleNotFound: 'HANDLE_NOT_FOUND', - /** Handle type doesn't satisfy capability's type constraint */ - TypeMismatch: 'TYPE_MISMATCH', - /** Missing required argument or wrong type */ - InvalidArgument: 'INVALID_ARGUMENT', - /** Argument value outside valid range */ - ArgumentOutOfRange: 'ARGUMENT_OUT_OF_RANGE', - /** Error occurred during callback invocation */ - CallbackError: 'CALLBACK_ERROR', - /** Unexpected error in capability execution */ - InternalError: 'INTERNAL_ERROR', -} as const; - -/** - * Type guard to check if a value is an ATS error response. - */ -export function isAtsError(value: unknown): value is { $error: AtsError } { - return ( - value !== null && - typeof value === 'object' && - '$error' in value && - typeof (value as { $error: unknown }).$error === 'object' - ); -} - -/** - * Type guard to check if a value is a marshalled handle. - */ -export function isMarshalledHandle(value: unknown): value is MarshalledHandle { - return ( - value !== null && - typeof value === 'object' && - '$handle' in value && - '$type' in value - ); -} - -// ============================================================================ -// Handle -// ============================================================================ - -/** - * A typed handle to a .NET object in the ATS system. - * Handles are opaque references that can be passed to capabilities. - * - * @typeParam T - The ATS type ID (e.g., "Aspire.Hosting/IDistributedApplicationBuilder") - */ -export class Handle { - private readonly _handleId: string; - private readonly _typeId: T; - - constructor(marshalled: MarshalledHandle) { - this._handleId = marshalled.$handle; - this._typeId = marshalled.$type as T; - } - - /** The handle ID (instance number) */ - get $handle(): string { - return this._handleId; - } - - /** The ATS type ID */ - get $type(): T { - return this._typeId; - } - - /** Serialize for JSON-RPC transport */ - toJSON(): MarshalledHandle { - return { - $handle: this._handleId, - $type: this._typeId - }; - } - - /** String representation for debugging */ - toString(): string { - return `Handle<${this._typeId}>(${this._handleId})`; - } -} - -// ============================================================================ -// Handle Wrapper Registry -// ============================================================================ - -/** - * Factory function for creating typed wrapper instances from handles. - */ -export type HandleWrapperFactory = (handle: Handle, client: AspireClient) => unknown; - -/** - * Registry of handle wrapper factories by type ID. - * Generated code registers wrapper classes here so callback handles can be properly typed. - */ -const handleWrapperRegistry = new Map(); - -/** - * Register a wrapper factory for a type ID. - * Called by generated code to register wrapper classes. - */ -export function registerHandleWrapper(typeId: string, factory: HandleWrapperFactory): void { - handleWrapperRegistry.set(typeId, factory); -} - -/** - * Checks if a value is a marshalled handle and wraps it appropriately. - * Uses the wrapper registry to create typed wrapper instances when available. - * - * @param value - The value to potentially wrap - * @param client - Optional client for creating typed wrapper instances - */ -export function wrapIfHandle(value: unknown, client?: AspireClient): unknown { - if (value && typeof value === 'object') { - if (isMarshalledHandle(value)) { - const handle = new Handle(value); - const typeId = value.$type; - - // Try to find a registered wrapper factory for this type - if (typeId && client) { - const factory = handleWrapperRegistry.get(typeId); - if (factory) { - return factory(handle, client); - } - } - - return handle; - } - } - return value; -} - -// ============================================================================ -// Capability Error -// ============================================================================ - -/** - * Error thrown when an ATS capability invocation fails. - */ -export class CapabilityError extends Error { - constructor( - /** The structured error from the server */ - public readonly error: AtsError - ) { - super(error.message); - this.name = 'CapabilityError'; - } - - /** Machine-readable error code */ - get code(): string { - return this.error.code; - } - - /** The capability that failed (if applicable) */ - get capability(): string | undefined { - return this.error.capability; - } -} - -// ============================================================================ -// Callback Registry -// ============================================================================ - -const callbackRegistry = new Map(); -let callbackIdCounter = 0; - -/** - * Register a callback function that can be invoked from the .NET side. - * Returns a callback ID that should be passed to methods accepting callbacks. - * - * .NET passes arguments as an object with positional keys: `{ p0: value0, p1: value1, ... }` - * This function automatically extracts positional parameters and wraps handles. - * - * @example - * // Single parameter callback - * const id = registerCallback((ctx) => console.log(ctx)); - * // .NET sends: { p0: { $handle: "...", $type: "..." } } - * // Callback receives: Handle instance - * - * @example - * // Multi-parameter callback - * const id = registerCallback((a, b) => console.log(a, b)); - * // .NET sends: { p0: "hello", p1: 42 } - * // Callback receives: "hello", 42 - */ -export function registerCallback( - callback: (...args: any[]) => TResult | Promise -): string { - const callbackId = `callback_${++callbackIdCounter}_${Date.now()}`; - - // Wrap the callback to handle .NET's positional argument format - const wrapper: CallbackFunction = async (args: unknown, client: AspireClient) => { - // .NET sends args as object { p0: value0, p1: value1, ... } - if (args && typeof args === 'object' && !Array.isArray(args)) { - const argObj = args as Record; - const argArray: unknown[] = []; - - // Extract positional parameters (p0, p1, p2, ...) - for (let i = 0; ; i++) { - const key = `p${i}`; - if (key in argObj) { - argArray.push(wrapIfHandle(argObj[key], client)); - } else { - break; - } - } - - if (argArray.length > 0) { - // Spread positional arguments to callback - return await callback(...argArray); - } - - // No positional params found - call with no args - return await callback(); - } - - // Null/undefined - call with no args - if (args === null || args === undefined) { - return await callback(); - } - - // Primitive value - pass as single arg (shouldn't happen with current protocol) - return await callback(wrapIfHandle(args, client)); - }; - - callbackRegistry.set(callbackId, wrapper); - return callbackId; -} - -/** - * Unregister a callback by its ID. - */ -export function unregisterCallback(callbackId: string): boolean { - return callbackRegistry.delete(callbackId); -} - -/** - * Get the number of registered callbacks. - */ -export function getCallbackCount(): number { - return callbackRegistry.size; -} - -// ============================================================================ -// Cancellation Token Registry -// ============================================================================ - -/** - * Registry for cancellation tokens. - * Maps cancellation IDs to cleanup functions. - */ -const cancellationRegistry = new Map void>(); -let cancellationIdCounter = 0; - -/** - * A reference to the current AspireClient for sending cancel requests. - * Set by AspireClient.connect(). - */ -let currentClient: AspireClient | null = null; - -/** - * Register an AbortSignal for cancellation support. - * Returns a cancellation ID that should be passed to methods accepting CancellationToken. - * - * When the AbortSignal is aborted, sends a cancelToken request to the host. - * - * @param signal - The AbortSignal to register (optional) - * @returns The cancellation ID, or undefined if no signal provided - * - * @example - * const controller = new AbortController(); - * const id = registerCancellation(controller.signal); - * // Pass id to capability invocation - * // Later: controller.abort() will cancel the operation - */ -export function registerCancellation(signal?: AbortSignal): string | undefined { - if (!signal) { - return undefined; - } - - // Already aborted? Don't register - if (signal.aborted) { - return undefined; - } - - const cancellationId = `ct_${++cancellationIdCounter}_${Date.now()}`; - - // Set up the abort listener - const onAbort = () => { - // Send cancel request to host - if (currentClient?.connected) { - currentClient.cancelToken(cancellationId).catch(() => { - // Ignore errors - the operation may have already completed - }); - } - // Clean up the listener - cancellationRegistry.delete(cancellationId); - }; - - // Listen for abort - signal.addEventListener('abort', onAbort, { once: true }); - - // Store cleanup function - cancellationRegistry.set(cancellationId, () => { - signal.removeEventListener('abort', onAbort); - }); - - return cancellationId; -} - -/** - * Unregister a cancellation token by its ID. - * Call this when the operation completes to clean up resources. - * - * @param cancellationId - The cancellation ID to unregister - */ -export function unregisterCancellation(cancellationId: string | undefined): void { - if (!cancellationId) { - return; - } - - const cleanup = cancellationRegistry.get(cancellationId); - if (cleanup) { - cleanup(); - cancellationRegistry.delete(cancellationId); - } -} - -// ============================================================================ -// AspireClient (JSON-RPC Connection) -// ============================================================================ - -/** - * Client for connecting to the Aspire AppHost via socket/named pipe. - */ -export class AspireClient { - private connection: rpc.MessageConnection | null = null; - private socket: net.Socket | null = null; - private disconnectCallbacks: (() => void)[] = []; - private _pendingCalls = 0; - - constructor(private socketPath: string) { } - - /** - * Register a callback to be called when the connection is lost - */ - onDisconnect(callback: () => void): void { - this.disconnectCallbacks.push(callback); - } - - private notifyDisconnect(): void { - for (const callback of this.disconnectCallbacks) { - try { - callback(); - } catch { - // Ignore callback errors - } - } - } - - connect(timeoutMs: number = 5000): Promise { - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(new Error('Connection timeout')), timeoutMs); - - // On Windows, use named pipes; on Unix, use Unix domain sockets - const isWindows = process.platform === 'win32'; - const pipePath = isWindows ? `\\\\.\\pipe\\${this.socketPath}` : this.socketPath; - - this.socket = net.createConnection(pipePath); - - this.socket.once('error', (error: Error) => { - clearTimeout(timeout); - reject(error); - }); - - this.socket.once('connect', () => { - clearTimeout(timeout); - try { - const reader = new rpc.SocketMessageReader(this.socket!); - const writer = new rpc.SocketMessageWriter(this.socket!); - this.connection = rpc.createMessageConnection(reader, writer); - - this.connection.onClose(() => { - this.connection = null; - this.notifyDisconnect(); - }); - this.connection.onError((err: any) => console.error('JsonRpc connection error:', err)); - - // Handle callback invocations from the .NET side - this.connection.onRequest('invokeCallback', async (callbackId: string, args: unknown) => { - const callback = callbackRegistry.get(callbackId); - if (!callback) { - throw new Error(`Callback not found: ${callbackId}`); - } - try { - // The registered wrapper handles arg unpacking and handle wrapping - // Pass this client so handles can be wrapped with typed wrapper classes - return await Promise.resolve(callback(args, this)); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - throw new Error(`Callback execution failed: ${message}`); - } - }); - - this.connection.listen(); - - // Set the current client for cancellation registry - currentClient = this; - - resolve(); - } catch (e) { - reject(e); - } - }); - - this.socket.on('close', () => { - this.connection?.dispose(); - this.connection = null; - if (currentClient === this) { - currentClient = null; - } - this.notifyDisconnect(); - }); - }); - } - - ping(): Promise { - if (!this.connection) return Promise.reject(new Error('Not connected to AppHost')); - return this.connection.sendRequest('ping'); - } - - /** - * Cancel a CancellationToken by its ID. - * Called when an AbortSignal is aborted. - * - * @param tokenId - The token ID to cancel - * @returns True if the token was found and cancelled, false otherwise - */ - cancelToken(tokenId: string): Promise { - if (!this.connection) return Promise.reject(new Error('Not connected to AppHost')); - return this.connection.sendRequest('cancelToken', tokenId); - } - - /** - * Invoke an ATS capability by ID. - * - * Capabilities are operations exposed by [AspireExport] attributes. - * Results are automatically wrapped in Handle objects when applicable. - * - * @param capabilityId - The capability ID (e.g., "Aspire.Hosting/createBuilder") - * @param args - Arguments to pass to the capability - * @returns The capability result, wrapped as Handle if it's a handle type - * @throws CapabilityError if the capability fails - */ - async invokeCapability( - capabilityId: string, - args?: Record - ): Promise { - if (!this.connection) { - throw new Error('Not connected to AppHost'); - } - - // Ref counting: The vscode-jsonrpc socket keeps Node's event loop alive. - // We ref() during RPC calls so the process doesn't exit mid-call, and - // unref() when idle so the process can exit naturally after all work completes. - if (this._pendingCalls === 0) { - this.socket?.ref(); - } - this._pendingCalls++; - - try { - const result = await this.connection.sendRequest( - 'invokeCapability', - capabilityId, - args ?? null - ); - - // Check for structured error response - if (isAtsError(result)) { - throw new CapabilityError(result.$error); - } - - // Wrap handles automatically - return wrapIfHandle(result, this) as T; - } finally { - this._pendingCalls--; - if (this._pendingCalls === 0) { - this.socket?.unref(); - } - } - } - - disconnect(): void { - try { this.connection?.dispose(); } finally { this.connection = null; } - try { this.socket?.end(); } finally { this.socket = null; } - } - - get connected(): boolean { - return this.connection !== null && this.socket !== null; - } -}