Skip to content

feat(server): expand Rust startup banner - #234

Merged
ayushag-nv merged 1 commit into
mainfrom
agent/rust-banner-endpoints
Jul 31, 2026
Merged

feat(server): expand Rust startup banner#234
ayushag-nv merged 1 commit into
mainfrom
agent/rust-banner-endpoints

Conversation

@nachiketb-nvidia

@nachiketb-nvidia nachiketb-nvidia commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

What

Adds concise runtime guidance to the Rust server startup banner:

  • the bound listening URL
  • configured route model IDs
  • the mounted endpoint list
  • a copy-pasteable curl example for /v1/chat/completions using the first configured route

The optional /v1/routing/session-stats endpoint is only shown when routing-log support is enabled, matching router registration.

Why

The existing banner only printed the server URL and route ids. On startup, users still had to know which endpoints are available and how to send a first request. This makes the Rust server self-describing without changing endpoint behavior.

How

This stays isolated to crates/switchyard-server/src/lib.rs near the existing startup_banner helper. It derives the example request from ServerState::models() and uses a localhost-friendly request URL when the server binds to an unspecified address like 0.0.0.0.

Validation

  • cargo check -p switchyard-server
  • commit hooks ran cargo fmt and cargo clippy

Tests were not run per request.

Summary by CodeRabbit

  • Enhancements
    • Improved the server startup message with clearer listening and request URLs.
    • Added visibility into configured routes and available endpoints.
    • Included a sample curl request to help verify the server is running.
    • Request URLs now display accessible loopback addresses when the server binds to an unspecified host.
    • The routing-session statistics endpoint is shown when enabled.

Signed-off-by: nachiketb <nachiketb@nvidia.com>
@nachiketb-nvidia
nachiketb-nvidia requested a review from a team as a code owner July 31, 2026 20:12

@ryan-lempka ryan-lempka left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Love this

@ayushag-nv
ayushag-nv enabled auto-merge (squash) July 31, 2026 20:14
@ayushag-nv
ayushag-nv merged commit a67536f into main Jul 31, 2026
17 of 18 checks passed
@ayushag-nv
ayushag-nv deleted the agent/rust-banner-endpoints branch July 31, 2026 20:18
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The startup banner now shows requestable URLs, configured routes, available endpoints, and a JSON curl example. New helpers map unspecified bind addresses to loopback hosts and conditionally list routing-session statistics.

Changes

Startup banner

Layer / File(s) Summary
Request URL and endpoint metadata
crates/switchyard-server/src/lib.rs
request_url_for_addr maps unspecified IPv4 and IPv6 addresses to loopback hosts. endpoint_listing includes the routing-session statistics endpoint when routing logs are enabled.
Expanded startup banner
crates/switchyard-server/src/lib.rs
startup_banner displays requestable URLs, configured routes, endpoint listings, and a JSON curl example.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

A rabbit checks the server sign,
Two loopback paths now neatly align.
Routes and endpoints hop in a row,
A curl example helps requests flow.
The startup banner shines bright—
“All set!” says Bun, and bounds from sight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: expanding the Rust server startup banner.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/switchyard-server/src/lib.rs (1)

1007-1024: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the non-obvious URL normalization.

request_url_for_addr maps unspecified bind addresses to loopback. host_for_url brackets IPv6 literals. Add concise comments so future changes preserve this behavior.

Proposed comments
+// Use loopback in the request example when the listener binds all interfaces.
 fn request_url_for_addr(scheme: &'static str, addr: SocketAddr) -> String {
...
+// Bracket IPv6 literals so the host is valid in a URL authority.
 fn host_for_url(ip: std::net::IpAddr) -> String {

As per coding guidelines, add comments for non-obvious private helpers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/switchyard-server/src/lib.rs` around lines 1007 - 1024, Add concise
comments above request_url_for_addr and host_for_url documenting their
non-obvious URL normalization: unspecified IPv4/IPv6 bind addresses map to
127.0.0.1/[::1], and IPv6 literals are bracketed for valid URLs. Preserve the
existing implementation and behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/switchyard-server/src/lib.rs`:
- Around line 965-977: Update the startup example formatting around the
generated curl command to shell-escape the serialized example_body, including
replacing embedded apostrophes with a safe shell-quoted representation, and
quote request_url so bracketed IPv6 hosts are handled safely. Add regression
tests covering apostrophes in model or message values and IPv6 request URLs.

---

Nitpick comments:
In `@crates/switchyard-server/src/lib.rs`:
- Around line 1007-1024: Add concise comments above request_url_for_addr and
host_for_url documenting their non-obvious URL normalization: unspecified
IPv4/IPv6 bind addresses map to 127.0.0.1/[::1], and IPv6 literals are bracketed
for valid URLs. Preserve the existing implementation and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7f0444f3-eec9-400e-8b11-0acc4675828c

📥 Commits

Reviewing files that changed from the base of the PR and between d676965 and b8d2614.

📒 Files selected for processing (1)
  • crates/switchyard-server/src/lib.rs

Comment on lines +965 to +977
let example_model = routes.first().copied().unwrap_or("switchyard/route");
let example_body = json!({
"model": example_model,
"messages": [{"role": "user", "content": "Hello from Switchyard"}],
});
format!(
"{}\nSwitchyard libsy server\n listening: {}\n routes: {}",
"{}\nSwitchyard libsy server\n listening: {}\n routes: {}\n\nendpoints:\n{}\n\nexample:\n curl -s {}/v1/chat/completions \\\n -H 'Content-Type: application/json' \\\n -d '{}'",
render_startup_banner_art(color),
url_for_addr(scheme, options.addr),
state.models().collect::<Vec<_>>().join(", ")
listen_url,
route_list,
endpoint_listing(state.routing_log.is_some()),
request_url,
example_body,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 6 'fn models|models\(\)|model[_ -]?id|route.*model|model.*(validate|allowed|regex)' crates || true
rg -n -C 6 'startup_banner|request_url_for_addr|endpoint_listing|chat/completions' crates/switchyard-server || true

Repository: NVIDIA-NeMo/Switchyard

Length of output: 50381


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- banner implementation ---'
sed -n '900,990p' crates/switchyard-server/src/lib.rs

printf '%s\n' '--- route construction and model validation ---'
sed -n '125,175p' crates/switchyard-server/src/lib.rs
rg -n -C 4 'ServerRunOptions|options\.addr|TcpListener|local_addr|listen_url|request_url|route model|ModelId::new' crates/switchyard-server crates/protocol crates/switchyard-components

printf '%s\n' '--- banner tests ---'
rg -n -C 8 'startup|banner|curl|example_body|request_url_for_addr|endpoint_listing' crates/switchyard-server --glob '*.rs'

Repository: NVIDIA-NeMo/Switchyard

Length of output: 50378


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import subprocess
from pathlib import Path

source = Path("crates/switchyard-server/src/lib.rs").read_text()
start = source.index('    let example_model = routes.first().copied()')
end = source.index('        request_url,', start)
template = source[start:end + len('        request_url,')]

models = ["vendor'O", "vendor;echo PWNED", 'vendor"$(echo PWNED)', "vendor model"]
urls = ["http://127.0.0.1:8080", "http://[::1]:8080"]

for model in models:
    # This reproduces the relevant formatting shape without running repository code.
    body = '{"model": "' + model.replace('\\', '\\\\').replace('"', '\\"') + '", "messages": [{"role": "user", "content": "Hello from Switchyard"}]}'
    command = f"curl -s {urls[0]}/v1/chat/completions -H 'Content-Type: application/json' -d '{body}'"
    parsed = subprocess.run(
        ["bash", "-n"],
        input=command + "\n",
        text=True,
        capture_output=True,
    )
    print(f"model={model!r} bash_n={'ok' if parsed.returncode == 0 else 'error'}")
    print(f"  command={command}")

ipv6 = "curl -s http://[::1]:8080/v1/chat/completions -H 'Content-Type: application/json' -d '{}'"
print(f"ipv6 bash_n={'ok' if subprocess.run(['bash', '-n'], input=ipv6 + '\\n', text=True).returncode == 0 else 'error'}")
PY

Repository: NVIDIA-NeMo/Switchyard

Length of output: 505


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import subprocess

models = ["vendor'O", "vendor;echo PWNED", 'vendor"$(echo PWNED)', "vendor model"]

for model in models:
    body = '{"model": "' + model.replace('\\', '\\\\').replace('"', '\\"') + '", "messages": [{"role": "user", "content": "Hello from Switchyard"}]}'
    command = f"curl -s http://127.0.0.1:8080/v1/chat/completions -H 'Content-Type: application/json' -d '{body}'"
    result = subprocess.run(["bash", "-n"], input=command + "\n", text=True)
    print(f"model={model!r} bash_n={'ok' if result.returncode == 0 else 'error'}")

for url in ["http://127.0.0.1:8080", "http://[::1]:8080"]:
    command = f"curl -s {url}/v1/chat/completions -H 'Content-Type: application/json' -d '{{}}'"
    result = subprocess.run(["bash", "-n"], input=command + "\n", text=True)
    print(f"url={url!r} bash_n={'ok' if result.returncode == 0 else 'error'}")
PY

Repository: NVIDIA-NeMo/Switchyard

Length of output: 428


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- URL helpers and binding flow ---'
sed -n '995,1035p' crates/switchyard-server/src/lib.rs
sed -n '203,255p' crates/switchyard-server/src/lib.rs

printf '%s\n' '--- ModelId definition and validation ---'
rg -n -C 12 'pub struct ModelId|struct ModelId|impl ModelId|type ModelId|enum ModelId' crates
rg -n -C 8 'invalid model|model id.*empty|ModelId.*new|models_reject|route.*empty' crates/switchyard-server crates/switchyard-components/src crates/protocol/src --glob '*.rs'

printf '%s\n' '--- relevant tests near server helpers ---'
rg -n -C 12 '#\[cfg\(test\)\]|#\[test\]|startup_banner|url_for_addr|request_url_for_addr|ServerState::new' crates/switchyard-server/src/lib.rs

Repository: NVIDIA-NeMo/Switchyard

Length of output: 3461


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import subprocess

def bash_parse(command):
    result = subprocess.run(
        ["bash", "-n"],
        input=command + "\n",
        text=True,
        capture_output=True,
    )
    return result.returncode == 0, result.stderr.strip()

def bash_words(command):
    # Use Bash's parser only; do not execute curl or repository code.
    script = f"set -x\n{command}\n"
    result = subprocess.run(
        ["bash", "-c", script],
        text=True,
        capture_output=True,
    )
    return result.returncode, result.stderr.strip().splitlines()

body = '{"model": "vendor\\'O", "messages": [{"role": "user", "content": "Hello from Switchyard"}]}'
unquoted_body = (
    "curl -s http://127.0.0.1:8080/v1/chat/completions "
    "-H 'Content-Type: application/json' "
    f"-d '{body}'"
)
quoted_body = (
    "curl -s 'http://127.0.0.1:8080/v1/chat/completions' "
    "-H 'Content-Type: application/json' "
    f"-d '{body.replace(chr(39), chr(39)+chr(92)+chr(39)+chr(39))}'"
)

for label, command in [
    ("unquoted apostrophe body", unquoted_body),
    ("shell-escaped body", quoted_body),
    ("unquoted IPv6 URL", "curl -s http://[::1]:8080/v1/chat/completions -d '{}'"),
    ("quoted IPv6 URL", "curl -s 'http://[::1]:8080/v1/chat/completions' -d '{}'"),
]:
    ok, error = bash_parse(command)
    print(f"{label}: parse={'ok' if ok else 'error'}")
    if error:
        print(f"  stderr={error}")
PY

Repository: NVIDIA-NeMo/Switchyard

Length of output: 353


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import subprocess

def parse_ok(command):
    result = subprocess.run(
        ["bash", "-n"],
        input=command + "\n",
        text=True,
        capture_output=True,
    )
    return result.returncode == 0, result.stderr.strip()

body = """{"model": "vendor'O", "messages": [{"role": "user", "content": "Hello from Switchyard"}]}"""
unquoted_body = (
    "curl -s http://127.0.0.1:8080/v1/chat/completions "
    "-H 'Content-Type: application/json' "
    "-d '" + body + "'"
)
escaped_body = body.replace("'", "'\\''")
quoted_body = (
    "curl -s 'http://127.0.0.1:8080/v1/chat/completions' "
    "-H 'Content-Type: application/json' "
    "-d '" + escaped_body + "'"
)

commands = [
    ("unquoted apostrophe body", unquoted_body),
    ("shell-escaped body", quoted_body),
    ("unquoted IPv6 URL", "curl -s http://[::1]:8080/v1/chat/completions -d '{}'"),
    ("quoted IPv6 URL", "curl -s 'http://[::1]:8080/v1/chat/completions' -d '{}'"),
]

for label, command in commands:
    ok, error = parse_ok(command)
    print(f"{label}: parse={'ok' if ok else 'error'}")
    if error:
        print(f"  stderr={error}")
PY

Repository: NVIDIA-NeMo/Switchyard

Length of output: 350


🏁 Script executed:

#!/usr/bin/env bash
set -u

printf '%s\n' '--- all ModelId definitions and constructors ---'
rg -n -C 10 'ModelId' crates --glob '*.rs' | head -240
printf 'rg_status=%s\n' "${PIPESTATUS[0]}"

printf '%s\n' '--- server route creation paths ---'
rg -n -C 10 'ServerState::new|routes:|algorithm.*route|route.*algorithm|model.*algorithm' crates/switchyard-server --glob '*.rs' | head -240
printf 'rg_status=%s\n' "${PIPESTATUS[0]}"

Repository: NVIDIA-NeMo/Switchyard

Length of output: 40275


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import json
import subprocess

model = "vendor'O"
body = json.dumps({
    "model": model,
    "messages": [{"role": "user", "content": "Hello from Switchyard"}],
}, separators=(",", ":"))

raw = "curl -s http://127.0.0.1:8080/v1/chat/completions -d '" + body + "'"
escaped = "curl -s 'http://[::1]:8080/v1/chat/completions' -d '" + body.replace(\"'\", \"'\\\\''\") + "'"

for label, command in [("raw generated command", raw), ("escaped command", escaped)]:
    result = subprocess.run(
        ["bash", "-n"],
        input=command + "\n",
        text=True,
        capture_output=True,
    )
    print(f"{label}: {'valid shell' if result.returncode == 0 else 'invalid shell'}")
    if result.stderr:
        print(result.stderr.strip())
print(f"json_model={json.loads(body)['model']!r}")
PY

Repository: NVIDIA-NeMo/Switchyard

Length of output: 451


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import json
import subprocess

model = "vendor'O"
body = json.dumps({
    "model": model,
    "messages": [{"role": "user", "content": "Hello from Switchyard"}],
}, separators=(",", ":"))

raw = "curl -s http://127.0.0.1:8080/v1/chat/completions -d '" + body + "'"
escaped = "curl -s 'http://[::1]:8080/v1/chat/completions' -d '" + body.replace("'", "'\\''") + "'"

for label, command in [("raw generated command", raw), ("escaped command", escaped)]:
    result = subprocess.run(
        ["bash", "-n"],
        input=command + "\n",
        text=True,
        capture_output=True,
    )
    print(f"{label}: {'valid shell' if result.returncode == 0 else 'invalid shell'}")
    if result.stderr:
        print(result.stderr.strip())
print(f"json_model={json.loads(body)['model']!r}")
PY

Repository: NVIDIA-NeMo/Switchyard

Length of output: 308


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 20 'fn validate_value|validate_value\(' crates/switchyard-server/src/config.rs
sed -n '1,130p' crates/switchyard-server/src/config.rs

Repository: NVIDIA-NeMo/Switchyard

Length of output: 8974


Shell-escape the generated curl arguments.

validate_value permits vendor'O, and JSON serialization leaves the apostrophe unchanged. The generated -d '...' argument is then invalid shell syntax. Escape single quotes in the body and quote the request URL to protect bracketed IPv6 hosts. Add regression tests for both cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/switchyard-server/src/lib.rs` around lines 965 - 977, Update the
startup example formatting around the generated curl command to shell-escape the
serialized example_body, including replacing embedded apostrophes with a safe
shell-quoted representation, and quote request_url so bracketed IPv6 hosts are
handled safely. Add regression tests covering apostrophes in model or message
values and IPv6 request URLs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants