File size: 2,604 Bytes
afa0cbf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
use std::ffi::OsStr;

use clap::Args;
use clap::builder::TypedValueParser;
use clap::error::ErrorKind;
use url::Url;

/// Selects the code-mode host for a single app-server process.
#[derive(Args, Debug, Clone, Default, PartialEq, Eq)]
pub struct AppServerCodeModeHostArgs {
    /// Connect to a remote code-mode host instead of starting a local host.
    #[arg(
        long = "code-mode-host",
        value_name = "URL",
        value_parser = RedactedHostUrlParser
    )]
    pub code_mode_host: Option<Url>,
}

/// Process-scoped transport used to reach the code-mode host.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum CodeModeHostTransport {
    /// Start and own the default local code-mode host.
    #[default]
    Local,
    /// Share an HTTP/2 gRPC connection to the specified remote code-mode host.
    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;