feat(server): expand Rust startup banner - #234
Conversation
Signed-off-by: nachiketb <nachiketb@nvidia.com>
WalkthroughThe startup banner now shows requestable URLs, configured routes, available endpoints, and a JSON ChangesStartup banner
Estimated code review effort: 2 (Simple) | ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
crates/switchyard-server/src/lib.rs (1)
1007-1024: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the non-obvious URL normalization.
request_url_for_addrmaps unspecified bind addresses to loopback.host_for_urlbrackets 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
📒 Files selected for processing (1)
crates/switchyard-server/src/lib.rs
| 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, |
There was a problem hiding this comment.
🎯 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 || trueRepository: 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'}")
PYRepository: 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'}")
PYRepository: 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.rsRepository: 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}")
PYRepository: 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}")
PYRepository: 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}")
PYRepository: 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}")
PYRepository: 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.rsRepository: 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.
What
Adds concise runtime guidance to the Rust server startup banner:
curlexample for/v1/chat/completionsusing the first configured routeThe optional
/v1/routing/session-statsendpoint 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.rsnear the existingstartup_bannerhelper. It derives the example request fromServerState::models()and uses a localhost-friendly request URL when the server binds to an unspecified address like0.0.0.0.Validation
cargo check -p switchyard-servercargo fmtandcargo clippyTests were not run per request.
Summary by CodeRabbit
curlrequest to help verify the server is running.