Skip to main content

objectiveai_sdk/cli/command/
path_ref.rs

1//! Typed paths shared across cli leaves.
2//!
3//! Every cli leaf that takes a docker-style `key=value,key=value` ref
4//! parses it at `TryFrom<Args>` time so each leaf's `Request` carries
5//! the pre-parsed form: [`FromStr for crate::RemotePathCommitOptional`]
6//! accepts `remote=<github|client|mock>` plus the matching
7//! owner/repository/name/commit fields. Any other key is an
8//! `unknown key` error.
9
10use std::str::FromStr;
11
12/// Wire-level source enum: which remote backend a path references.
13/// Same shape as `crate::Remote` but lives under `cli::command` so it
14/// can be `clap::ValueEnum` without dragging clap into the SDK's
15/// non-cli surface.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
17pub enum Remote {
18    Github,
19    Client,
20    Mock,
21}
22
23impl Remote {
24    /// Combine `(remote, owner?, repository?, name?, commit?)` into a
25    /// concrete `RemotePathCommitOptional`. Returns `None` if a
26    /// required field is missing for the chosen backend.
27    pub fn into_path(
28        self,
29        owner: Option<String>,
30        repository: Option<String>,
31        name: Option<String>,
32        commit: Option<String>,
33    ) -> Option<crate::RemotePathCommitOptional> {
34        match self {
35            Remote::Github => Some(crate::RemotePathCommitOptional::Github {
36                owner: owner?,
37                repository: repository?,
38                commit,
39            }),
40            Remote::Client => Some(crate::RemotePathCommitOptional::Client {
41                owner: owner?,
42                repository: repository?,
43                commit,
44            }),
45            Remote::Mock => Some(crate::RemotePathCommitOptional::Mock { name: name? }),
46        }
47    }
48
49    fn parse_keyword(s: &str) -> Result<Self, String> {
50        match s {
51            "github" => Ok(Self::Github),
52            "client" => Ok(Self::Client),
53            "mock" => Ok(Self::Mock),
54            other => Err(format!(
55                "unknown remote: {other} (expected github, client, or mock)"
56            )),
57        }
58    }
59}
60
61/// Tokenize a `key=value,key=value` string into a `Vec<(key, value)>`,
62/// trimming whitespace around each token. Returns an error if any
63/// pair lacks an `=`.
64pub(crate) fn tokenize(s: &str) -> Result<Vec<(&str, &str)>, String> {
65    s.split(',')
66        .map(|pair| {
67            pair.split_once('=')
68                .map(|(k, v)| (k.trim(), v.trim()))
69                .ok_or_else(|| format!("expected key=value, got: {pair}"))
70        })
71        .collect()
72}
73
74impl FromStr for crate::RemotePathCommitOptional {
75    type Err = String;
76
77    /// Parse a remote-path ref. Accepted keys: `remote`, `owner`,
78    /// `repository`, `name`, `commit`. Anything else is an
79    /// unknown-key error.
80    fn from_str(s: &str) -> Result<Self, Self::Err> {
81        let mut remote: Option<Remote> = None;
82        let mut owner: Option<String> = None;
83        let mut repository: Option<String> = None;
84        let mut name: Option<String> = None;
85        let mut commit: Option<String> = None;
86        for (k, v) in tokenize(s)? {
87            match k {
88                "remote" => remote = Some(Remote::parse_keyword(v)?),
89                "owner" => owner = Some(v.to_string()),
90                "repository" => repository = Some(v.to_string()),
91                "name" => name = Some(v.to_string()),
92                "commit" => commit = Some(v.to_string()),
93                other => return Err(format!("unknown key: {other}")),
94            }
95        }
96        let remote = remote.ok_or_else(|| "remote is required".to_string())?;
97        remote
98            .into_path(owner, repository, name, commit)
99            .ok_or_else(|| {
100                "owner and repository are required for github/client, name for mock"
101                    .to_string()
102            })
103    }
104}
105
106/// Serialize a `RemotePathCommitOptional` back into its docker-style
107/// `key=value,...` form for round-tripping through argv.
108pub fn remote_path_to_arg_string(path: &crate::RemotePathCommitOptional) -> String {
109    match path {
110        crate::RemotePathCommitOptional::Github {
111            owner,
112            repository,
113            commit,
114        } => {
115            let mut s = format!("remote=github,owner={owner},repository={repository}");
116            if let Some(c) = commit {
117                s.push_str(&format!(",commit={c}"));
118            }
119            s
120        }
121        crate::RemotePathCommitOptional::Client {
122            owner,
123            repository,
124            commit,
125        } => {
126            let mut s = format!("remote=client,owner={owner},repository={repository}");
127            if let Some(c) = commit {
128                s.push_str(&format!(",commit={c}"));
129            }
130            s
131        }
132        crate::RemotePathCommitOptional::Mock { name } => format!("remote=mock,name={name}"),
133    }
134}