diff options
| author | Bryan Newbold <bnewbold@robocracy.org> | 2022-10-27 23:03:28 -0700 | 
|---|---|---|
| committer | Bryan Newbold <bnewbold@robocracy.org> | 2022-10-27 23:03:28 -0700 | 
| commit | 7ba5eec5a87c14f8cfe944131d6fb7229648d81f (patch) | |
| tree | f6def4f470ddd8242f7aa37f1c485a9b588b9038 /adenosine-cli/src | |
| parent | dc24074377875b7f500b20e4f6088930f2eef4ea (diff) | |
| download | adenosine-7ba5eec5a87c14f8cfe944131d6fb7229648d81f.tar.gz adenosine-7ba5eec5a87c14f8cfe944131d6fb7229648d81f.zip | |
cli: parse fields as body or query params from args
Diffstat (limited to 'adenosine-cli/src')
| -rw-r--r-- | adenosine-cli/src/bin/adenosine.rs | 28 | ||||
| -rw-r--r-- | adenosine-cli/src/identifiers.rs | 4 | ||||
| -rw-r--r-- | adenosine-cli/src/lib.rs | 64 | 
3 files changed, 84 insertions, 12 deletions
| diff --git a/adenosine-cli/src/bin/adenosine.rs b/adenosine-cli/src/bin/adenosine.rs index 1d23c27..33f74c9 100644 --- a/adenosine-cli/src/bin/adenosine.rs +++ b/adenosine-cli/src/bin/adenosine.rs @@ -123,11 +123,11 @@ enum Command {      Create {          collection: String, -        fields: String, +        fields: Vec<ArgField>,      },      Update {          uri: AtUri, -        fields: String, +        fields: Vec<ArgField>,      },      Delete {          uri: AtUri, @@ -144,7 +144,7 @@ enum Command {      Xrpc {          method: XrpcMethod,          nsid: String, -        fields: Option<String>, +        fields: Vec<ArgField>,      },      /// Sub-commands for managing account @@ -274,7 +274,7 @@ fn run(opt: Opt) -> Result<()> {              if let Some(c) = cid {                  params.insert("cid".to_string(), c);              } -            xrpc_client.post("com.atproto.repoGetRecord", Some(params), json!({}))? +            xrpc_client.get("com.atproto.repoGetRecord", Some(params))?          }          Command::Ls { uri } => {              // TODO: option to print fully-qualified path? @@ -303,7 +303,9 @@ fn run(opt: Opt) -> Result<()> {          }          Command::Create { collection, fields } => {              params.insert("collection".to_string(), collection); -            unimplemented!() +            update_params_from_fields(&fields, &mut params); +            let val = value_from_fields(fields); +            xrpc_client.post("com.atproto.repoCreateRecord", Some(params), val)?          }          Command::Update { uri, fields } => {              params.insert("did".to_string(), uri.repository.to_string()); @@ -315,7 +317,13 @@ fn run(opt: Opt) -> Result<()> {                  "rkey".to_string(),                  uri.record.ok_or(anyhow!("record key required"))?,              ); -            unimplemented!() +            // fetch existing, extend map with fields, put the updated value +            let mut record = xrpc_client +                .get("com.atproto.repoGetRecord", Some(params.clone()))? +                .unwrap_or(json!({})); +            update_params_from_fields(&fields, &mut params); +            update_value_from_fields(fields, &mut record); +            xrpc_client.post("com.atproto.repoPutRecord", Some(params), record)?          }          Command::Delete { uri } => {              params.insert("did".to_string(), uri.repository.to_string()); @@ -334,11 +342,11 @@ fn run(opt: Opt) -> Result<()> {              nsid,              fields,          } => { -            let body: Value = ().into(); +            update_params_from_fields(&fields, &mut params); +            let body = value_from_fields(fields);              match method { -                // XXX: parse params -                XrpcMethod::Get => xrpc_client.get(&nsid, None)?, -                XrpcMethod::Post => xrpc_client.post(&nsid, None, body)?, +                XrpcMethod::Get => xrpc_client.get(&nsid, Some(params))?, +                XrpcMethod::Post => xrpc_client.post(&nsid, Some(params), body)?,              }          }          Command::Account { diff --git a/adenosine-cli/src/identifiers.rs b/adenosine-cli/src/identifiers.rs index 7129ba5..7f88df5 100644 --- a/adenosine-cli/src/identifiers.rs +++ b/adenosine-cli/src/identifiers.rs @@ -118,6 +118,10 @@ fn test_aturi() {      assert!(AtUri::from_str("at://bob.com/io.example.song").is_ok());      assert!(AtUri::from_str("at://bob.com/io.example.song/3yI5-c1z-cc2p-1a").is_ok());      assert!(AtUri::from_str("at://bob.com/io.example.song/3yI5-c1z-cc2p-1a#/title").is_ok()); +    assert!( +        AtUri::from_str("at://did:plc:ltk4reuh7rkoy2frnueetpb5/app.bsky.follow/3jg23pbmlhc2a") +            .is_ok() +    );      let uri = AtUri {          repository: DidOrHost::Did("some".to_string(), "thing".to_string()), diff --git a/adenosine-cli/src/lib.rs b/adenosine-cli/src/lib.rs index 8f462da..6d953ca 100644 --- a/adenosine-cli/src/lib.rs +++ b/adenosine-cli/src/lib.rs @@ -1,5 +1,7 @@  use anyhow::anyhow;  pub use anyhow::Result; +use lazy_static::lazy_static; +use regex::Regex;  use reqwest::header;  use serde_json::Value;  use std::collections::HashMap; @@ -178,5 +180,63 @@ fn test_parse_jwt() {      assert!(parse_did_from_jwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9").is_err());  } -// TODO: parse at-uri -// at://did:plc:ltk4reuh7rkoy2frnueetpb5/app.bsky.follow/3jg23pbmlhc2a +/// Represents fields/content specified on the command line. +/// +/// Sort of like HTTPie. Query parameters are '==', body values (JSON) are '='. Only single-level +/// body values are allowed currently, not JSON Pointer assignment. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ArgField { +    Query(String, serde_json::Value), +    Body(String, serde_json::Value), +} + +impl FromStr for ArgField { +    type Err = anyhow::Error; + +    fn from_str(s: &str) -> Result<Self, Self::Err> { +        lazy_static! { +            static ref FIELD_RE: Regex = Regex::new(r"^([a-zA-Z_]+)=(=?)(.*)$").unwrap(); +        } +        if let Some(captures) = FIELD_RE.captures(s) { +            let key = captures[1].to_string(); +            let val = Value::from_str(&captures[3])?; +            if captures.get(2).is_some() { +                Ok(ArgField::Query(key, val)) +            } else { +                Ok(ArgField::Body(key, val)) +            } +        } else { +            Err(anyhow!("could not parse as a field assignment: {}", s)) +        } +    } +} + +// TODO: what should type signature actually be here... +pub fn update_params_from_fields(fields: &[ArgField], params: &mut HashMap<String, String>) { +    for f in fields.iter() { +        if let ArgField::Query(ref k, ref v) = f { +            params.insert(k.to_string(), v.to_string()); +        } +    } +} + +pub fn update_value_from_fields(fields: Vec<ArgField>, value: &mut Value) { +    if let Value::Object(map) = value { +        for f in fields.into_iter() { +            if let ArgField::Body(k, v) = f { +                map.insert(k, v); +            } +        } +    } +} + +/// Consumes the entire Vec of fields passed in +pub fn value_from_fields(fields: Vec<ArgField>) -> Value { +    let mut map: HashMap<String, Value> = HashMap::new(); +    for f in fields.into_iter() { +        if let ArgField::Body(k, v) = f { +            map.insert(k, v); +        } +    } +    Value::Object(serde_json::map::Map::from_iter(map.into_iter())) +} | 
