use std::time::Duration;
use clap::Args as ClapArgs;
use reqwest::Client;
use serde_json::{json, Value};
use url::Url;
#[derive(Debug, Clone, ClapArgs)]
pub struct PushArgs {
#[arg(long)]
pub r#type: String,
#[arg(long)]
pub doc: Option<String>,
#[arg(long)]
pub target: Option<String>,
#[arg(long)]
pub json: Option<String>,
}
pub async fn run(args: PushArgs, server: &Url) -> i32 {
let body: Value = match args.json {
Some(raw) => match serde_json::from_str(&raw) {
Ok(v) => v,
Err(e) => {
eprintln!("invalid --json: {e}");
return 2;
}
},
None => json!({
"v": 2,
"id": null,
"src": "domi.js",
"kind": args.r#type,
"doc": args.doc.clone().unwrap_or_else(|| "synthetic".to_string()),
"target": {
"id": args.target.clone().unwrap_or_default(),
"selector": null,
"rect": {"x": 0.0, "y": 0.0, "w": 0.0, "h": 0.0},
},
"data": {},
}),
};
let client = match Client::builder().timeout(Duration::from_secs(5)).build() {
Ok(c) => c,
Err(e) => {
eprintln!("client init: {e}");
return 1;
}
};
let url = match server.join("/api/events") {
Ok(u) => u,
Err(e) => {
eprintln!("invalid server URL: {e}");
return 2;
}
};
match client.post(url).json(&body).send().await {
Ok(resp) if resp.status().is_success() => 0,
Ok(resp) => {
eprintln!("server returned {}", resp.status());
2
}
Err(e) => {
eprintln!("request failed: {e}");
1
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
#[test]
fn synthesized_body_omits_ts_so_server_stamps_it() {
let body = json!({
"v": 2,
"id": null,
"src": "domi.js",
"kind": "click",
"doc": "synthetic",
"target": {
"id": "",
"selector": null,
"rect": {"x": 0.0, "y": 0.0, "w": 0.0, "h": 0.0},
},
"data": {},
});
assert_eq!(body["kind"], "click");
assert_eq!(body["v"], 2);
assert!(body["target"].is_object(), "target must be an object");
assert_eq!(body["target"]["selector"], serde_json::Value::Null);
assert!(
body.get("ts").is_none(),
"ts must be omitted so the server stamps it"
);
assert_eq!(body["id"], serde_json::Value::Null);
}
}