summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--adenosine-cli/plan.txt4
-rw-r--r--adenosine-cli/src/bin/adenosine.rs28
-rw-r--r--adenosine-cli/src/identifiers.rs4
-rw-r--r--adenosine-cli/src/lib.rs64
4 files changed, 84 insertions, 16 deletions
diff --git a/adenosine-cli/plan.txt b/adenosine-cli/plan.txt
index 668a746..32e5070 100644
--- a/adenosine-cli/plan.txt
+++ b/adenosine-cli/plan.txt
@@ -63,7 +63,3 @@ file; '-' to read a JSON file from stdin.
profile <name>?
search-users <query>
----
-
-plan:
-- first version which uses entirely schema-less / generic XRPC client
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()))
+}