| use std::ffi::OsStr; |
|
|
| use clap::Args; |
| use clap::builder::TypedValueParser; |
| use clap::error::ErrorKind; |
| use url::Url; |
|
|
| |
| #[derive(Args, Debug, Clone, Default, PartialEq, Eq)] |
| pub struct AppServerCodeModeHostArgs { |
| |
| #[arg( |
| long = "code-mode-host", |
| value_name = "URL", |
| value_parser = RedactedHostUrlParser |
| )] |
| pub code_mode_host: Option<Url>, |
| } |
|
|
| |
| #[derive(Debug, Clone, Default, PartialEq, Eq)] |
| pub enum CodeModeHostTransport { |
| |
| #[default] |
| Local, |
| |
| Grpc(Url), |
| } |
|
|
| impl From<AppServerCodeModeHostArgs> for CodeModeHostTransport { |
| fn from(args: AppServerCodeModeHostArgs) -> Self { |
| match args.code_mode_host { |
| Some(url) => Self::Grpc(url), |
| None => Self::Local, |
| } |
| } |
| } |
|
|
| #[derive(Clone)] |
| struct RedactedHostUrlParser; |
|
|
| impl TypedValueParser for RedactedHostUrlParser { |
| type Value = Url; |
|
|
| fn parse_ref( |
| &self, |
| command: &clap::Command, |
| _argument: Option<&clap::Arg>, |
| value: &OsStr, |
| ) -> Result<Self::Value, clap::Error> { |
| let value = value.to_str().ok_or_else(|| { |
| clap::Error::raw( |
| ErrorKind::InvalidUtf8, |
| "code-mode host URL must contain valid UTF-8", |
| ) |
| .with_cmd(command) |
| })?; |
|
|
| parse_host_url(value) |
| .map_err(|error| clap::Error::raw(ErrorKind::ValueValidation, error).with_cmd(command)) |
| } |
| } |
|
|
| fn parse_host_url(value: &str) -> Result<Url, String> { |
| let url = Url::parse(value).map_err(|error| format!("invalid code-mode host URL: {error}"))?; |
| if !matches!(url.scheme(), "http" | "https") || url.host_str().is_none() { |
| return Err("code-mode host URL must use http:// or https:// with a host".to_string()); |
| } |
| if url.fragment().is_some() { |
| return Err("code-mode host URL must not contain a fragment".to_string()); |
| } |
| if !url.username().is_empty() || url.password().is_some() { |
| return Err("gRPC code-mode host URL must not contain credentials".to_string()); |
| } |
| if url.path() != "/" || url.query().is_some() { |
| return Err("gRPC code-mode host URL must not contain a path or query".to_string()); |
| } |
| Ok(url) |
| } |
|
|
| #[cfg(test)] |
| #[path = "code_mode_host_tests.rs"] |
| mod tests; |
|
|