use eggress_cli::{
pproxy_exec::{self, PreparedAction},
EXIT_CLI_PARSE_ERROR, EXIT_CONFIG_VALIDATION, EXIT_RUNTIME_FAILURE, EXIT_SUCCESS,
EXIT_UNSUPPORTED_FEATURE,
};
use crate::cli::{PproxyAction, PproxyCheck, PproxyCommand, PproxyRun, PproxyTranslate};
pub fn handle_pproxy_command(cmd: &PproxyCommand) -> i32 {
match &cmd.action {
PproxyAction::Translate(args) => handle_pproxy_translate(args),
PproxyAction::Check(args) => handle_pproxy_check(args),
PproxyAction::Run(args) => handle_pproxy_run(args),
}
}
fn handle_pproxy_translate(args: &PproxyTranslate) -> i32 {
let pproxy_args = match eggress_pproxy_compat::PproxyArgs::parse(&args.args) {
Ok(a) => a,
Err(e) => {
eprintln!("error: {e}");
return EXIT_CLI_PARSE_ERROR;
}
};
let output = match eggress_pproxy_compat::translate_pproxy_args(&pproxy_args) {
Ok(o) => o,
Err(e) => {
eprintln!("error: {e}");
return EXIT_CONFIG_VALIDATION;
}
};
if output.has_unsupported() {
for u in &output.unsupported() {
eprintln!("warning: {u}");
}
eprintln!("\nGenerated TOML may be incomplete due to unsupported features.");
}
for w in &output.warnings() {
eprintln!("warning: {w}");
}
if args.annotate {
println!("# Generated by eggress pproxy translate");
println!("# pproxy arguments: {}", args.args.join(" "));
if !output.warnings().is_empty() || !output.unsupported().is_empty() {
println!("#");
for w in &output.warnings() {
println!("# {w}");
}
for u in &output.unsupported() {
println!("# {u}");
}
}
println!();
}
print!("{}", output.toml);
if output.has_unsupported() {
return EXIT_UNSUPPORTED_FEATURE;
}
EXIT_SUCCESS
}
fn handle_pproxy_check(args: &PproxyCheck) -> i32 {
let pproxy_args = match eggress_pproxy_compat::PproxyArgs::parse(&args.args) {
Ok(a) => a,
Err(e) => {
eprintln!("error: {e}");
return EXIT_CLI_PARSE_ERROR;
}
};
let output = match eggress_pproxy_compat::translate_pproxy_args(&pproxy_args) {
Ok(o) => o,
Err(e) => {
eprintln!("error: {e}");
return EXIT_CONFIG_VALIDATION;
}
};
let local_uris = pproxy_args.parse_local_uris();
let remote_chains = pproxy_args.parse_remote_chains();
if args.json {
let listeners = match &local_uris {
Ok(uris) => uris
.iter()
.map(|u| u.redacted_display().to_string())
.collect(),
Err(e) => vec![format!("error: {e}")],
};
let remotes = match &remote_chains {
Ok(chains) => chains.iter().map(|c| c.redacted_display()).collect(),
Err(e) => vec![format!("error: {e}")],
};
let remote_chains_info: Vec<ChainInfo> = match &remote_chains {
Ok(chains) => chains
.iter()
.map(|c| ChainInfo {
raw: c.raw.clone(),
hops: c.hops.len(),
hop_schemes: c.hops.iter().map(|h| h.scheme.clone()).collect(),
is_chain: c.hops.len() > 1,
})
.collect(),
Err(e) => vec![ChainInfo {
raw: format!("error: {e}"),
hops: 0,
hop_schemes: vec![],
is_chain: false,
}],
};
let mut diagnostics: Vec<eggress_pproxy_compat::StructuredDiagnostic> = Vec::new();
let mut features: Vec<FeatureInfo> = Vec::new();
for w in &output.warnings() {
let feature_tier = eggress_pproxy_compat::manifest_tier_for_category(w.category);
diagnostics.push(eggress_pproxy_compat::StructuredDiagnostic::from(w));
features.push(FeatureInfo {
name: w.category.to_string(),
tier: feature_tier.as_str().to_string(),
diagnostic_code: Some(w.diagnostic_code()),
});
}
for u in &output.unsupported() {
let diag = eggress_pproxy_compat::StructuredDiagnostic {
code: eggress_pproxy_compat::DiagnosticCode::UnsupportedProtocol,
feature_id: Some(u.feature.to_string()),
tier: Some(
eggress_pproxy_compat::ManifestTier::Unsupported
.as_str()
.to_string(),
),
message: u.detail.clone(),
suggestion: None,
};
diagnostics.push(diag);
features.push(FeatureInfo {
name: u.feature.to_string(),
tier: eggress_pproxy_compat::ManifestTier::Unsupported
.as_str()
.to_string(),
diagnostic_code: Some(eggress_pproxy_compat::DiagnosticCode::UnsupportedProtocol),
});
}
let tier = eggress_pproxy_compat::classify_aggregate_tier(
&output.warnings(),
&output.unsupported(),
);
let check_output = PproxyCheckOutput {
tier: tier.as_str().to_string(),
diagnostics,
features,
raw_args: args.args.clone(),
parsed_uris: ParsedUris {
listeners,
remotes,
chain_info: remote_chains_info,
},
};
match serde_json::to_string_pretty(&check_output) {
Ok(json) => println!("{json}"),
Err(e) => {
eprintln!("failed to serialize check output: {e}");
return EXIT_RUNTIME_FAILURE;
}
}
} else {
println!("pproxy compatibility check");
println!("=========================");
match local_uris {
Ok(uris) => {
for uri in &uris {
println!(
" local: {} -> scheme={}",
uri.redacted_display(),
uri.scheme
);
}
}
Err(e) => eprintln!(" local: error: {e}"),
}
match remote_chains {
Ok(chains) => {
for chain in &chains {
if chain.hops.len() > 1 {
println!(
" remote: {} -> chain ({} hops: {})",
chain.redacted_display(),
chain.hops.len(),
chain
.hops
.iter()
.map(|h| h.scheme.as_str())
.collect::<Vec<_>>()
.join(" -> ")
);
} else if let Some(hop) = chain.hops.first() {
println!(
" remote: {} -> scheme={}",
hop.redacted_display(),
hop.scheme
);
}
}
}
Err(e) => eprintln!(" remote: error: {e}"),
}
let tier = eggress_pproxy_compat::classify_aggregate_tier(
&output.warnings(),
&output.unsupported(),
);
println!("\nparity tier: {}", tier_label(&tier));
if !output.warnings().is_empty() {
println!("\nwarnings:");
for w in &output.warnings() {
println!(" {w}");
}
}
if !output.unsupported().is_empty() {
println!("\nunsupported:");
for u in &output.unsupported() {
println!(" {u}");
}
}
}
EXIT_SUCCESS
}
fn handle_pproxy_run(args: &PproxyRun) -> i32 {
match pproxy_exec::prepare(&args.args) {
Ok(PreparedAction::PrintVersion) => {
println!("{}", eggress_pproxy_compat::PproxyArgs::version_string());
EXIT_SUCCESS
}
Ok(PreparedAction::PrintHelp) => {
println!("pproxy compatibility binary (eggress-pproxy-compat)");
println!("Use -l and -r with pproxy-compatible URIs.");
println!("Run standalone `pproxy --help` for the complete option reference.");
EXIT_SUCCESS
}
Ok(PreparedAction::Run(gated)) => {
let prepared = match pproxy_exec::compile(*gated) {
Ok(prepared) => prepared,
Err(failure) => {
eprintln!("error: {}", failure.message);
return failure.code;
}
};
crate::logging::init_pproxy_logging(&prepared.args, args.log_format);
pproxy_exec::execute(prepared, "")
}
Err(failure) => {
eprintln!("error: {}", failure.message);
failure.code
}
}
}
#[derive(serde::Serialize)]
struct PproxyCheckOutput {
tier: String,
diagnostics: Vec<eggress_pproxy_compat::StructuredDiagnostic>,
features: Vec<FeatureInfo>,
raw_args: Vec<String>,
parsed_uris: ParsedUris,
}
#[derive(serde::Serialize)]
struct FeatureInfo {
name: String,
tier: String,
#[serde(skip_serializing_if = "Option::is_none")]
diagnostic_code: Option<eggress_pproxy_compat::DiagnosticCode>,
}
#[derive(serde::Serialize)]
struct ParsedUris {
listeners: Vec<String>,
remotes: Vec<String>,
chain_info: Vec<ChainInfo>,
}
#[derive(serde::Serialize)]
struct ChainInfo {
raw: String,
hops: usize,
hop_schemes: Vec<String>,
is_chain: bool,
}
fn tier_label(tier: &eggress_pproxy_compat::ManifestTier) -> &'static str {
match tier {
eggress_pproxy_compat::ManifestTier::DropIn => "Drop-in",
eggress_pproxy_compat::ManifestTier::CompatibleWithWarning => "Compatible (with warnings)",
eggress_pproxy_compat::ManifestTier::NativeEquivalent => "Native equivalent",
eggress_pproxy_compat::ManifestTier::IntentionalNonParity => "Intentional non-parity",
eggress_pproxy_compat::ManifestTier::Unsupported => "Unsupported",
}
}