// Drop-in Rust client library for the Friendship Tracker HTTP API. // // Save this file alongside your code as `friendship_client.rs` and add // the only two third-party dependencies it needs to your Cargo.toml: // // reqwest = { version = "0.12", features = ["blocking", "json"] } // serde_json = "1" // // (We picked `reqwest` because it is the de-facto Rust HTTP client; the // rest of the surface is `std`.) // // Then use the Client struct: // // use friendship_client::Client; // let c = Client::new("pat_..."); // let rows = c.account_list(Default::default())?; // let fresh = c.account_create(serde_json::json!({{"name": "Example GmbH"}}))?; // // Every endpoint exposed by the HTTP API is wrapped as a method on // Client. List endpoints take ListOpts; get/update/delete endpoints // take the row id as their first argument. // // Provided as-is, with no warranty. Vendor freely; modify as needed. // Targets Rust 1.74+. // // DO NOT EDIT THIS FILE MANUALLY - re-download from the docs site. // Local edits will be overwritten by the once-per-day version check. #![allow(dead_code, non_snake_case, clippy::needless_lifetimes)] use std::collections::HashMap; use std::fs; use std::path::PathBuf; use std::sync::OnceLock; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use serde_json::{json, Value}; // ── Identity (substituted at generation time) ─────────────────────── pub const APP_SLUG: &str = "friendship"; pub const APP_NAME: &str = "Friendship Tracker"; pub const MODULE_NAME: &str = "friendship_client"; pub const CLIENT_VERSION: &str = "0.3.13"; pub const LANGUAGE: &str = "rust"; const DEFAULT_BASE: &str = "https://friendship-tracker.com"; /// Per-type metadata baked at generation time. Available at runtime /// when calling code needs to know the legal filters / sort columns / /// max_limit for a model without a second round-trip. pub const TYPES_JSON: &str = r####"{"activity":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","kind","summary","description","occurred_at","location"],"update_fields":["kind","summary","description","occurred_at","location"],"allowed_filters":["data__parent_id","data__kind","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__occurred_at"],"default_sort":"data__occurred_at","max_limit":200,"fields":[{"name":"kind","type":"enum","values":["meeting","call","email","message","event","other"]},{"name":"summary","type":"string","max_len":400},{"name":"location","type":"string","max_len":200},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"description","type":"string","max_len":4000},{"name":"occurred_at","type":"string","max_len":32}]},"contact":{"ops":["list","read","create","update","delete"],"create_fields":["name","nickname","pronouns","email","phone","secondary_email","secondary_phone","company","job_title","address_line","city","country","website","linkedin","twitter","birthday","anniversary","gender","how_we_met","food_prefs","allergies","last_contacted_at","stay_in_touch_frequency","stay_in_touch_topic","notes","tags","favorite","avatar_blob_id","color"],"update_fields":["name","nickname","pronouns","email","phone","secondary_email","secondary_phone","company","job_title","address_line","city","country","website","linkedin","twitter","birthday","anniversary","gender","how_we_met","food_prefs","allergies","last_contacted_at","stay_in_touch_frequency","stay_in_touch_topic","notes","tags","favorite","avatar_blob_id","color"],"allowed_filters":["data__name","data__email","data__company","data__city","data__country","data__favorite","data__tags","data__gender","data__stay_in_touch_frequency","status","is_archived","owned_by"],"allowed_sorts":["created_at","updated_at","data__name","data__company","data__last_contacted_at","data__birthday","data__stay_in_touch_frequency"],"default_sort":"data__name","max_limit":200,"fields":[{"name":"city","type":"string","max_len":120},{"name":"name","type":"string","max_len":200},{"name":"tags","type":"tags"},{"name":"color","type":"string","max_len":24},{"name":"email","type":"string","max_len":320},{"name":"notes","type":"string","max_len":8000},{"name":"phone","type":"string","max_len":64},{"name":"gender","type":"enum","values":["male","female","other","unspecified"]},{"name":"company","type":"string","max_len":200},{"name":"country","type":"string","max_len":120},{"name":"twitter","type":"url"},{"name":"website","type":"url"},{"name":"birthday","type":"string","max_len":32},{"name":"favorite","type":"bool"},{"name":"linkedin","type":"url"},{"name":"nickname","type":"string","max_len":120},{"name":"pronouns","type":"string","max_len":32},{"name":"allergies","type":"string","max_len":600},{"name":"job_title","type":"string","max_len":200},{"name":"food_prefs","type":"string","max_len":600},{"name":"how_we_met","type":"string","max_len":1000},{"name":"anniversary","type":"string","max_len":32},{"name":"address_line","type":"string","max_len":200},{"name":"avatar_blob_id","type":"string","max_len":64},{"name":"secondary_email","type":"string","max_len":320},{"name":"secondary_phone","type":"string","max_len":64},{"name":"last_contacted_at","type":"string","max_len":32},{"name":"stay_in_touch_topic","type":"string","max_len":400},{"name":"stay_in_touch_frequency","type":"enum","values":["never","weekly","biweekly","monthly","quarterly","yearly"]}]},"conversation":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","channel","summary","content","occurred_at","sentiment","duration_minutes"],"update_fields":["channel","summary","content","occurred_at","sentiment","duration_minutes"],"allowed_filters":["data__parent_id","data__channel","data__sentiment","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__occurred_at"],"default_sort":"data__occurred_at","max_limit":200,"fields":[{"name":"channel","type":"enum","values":["call","sms","whatsapp","email","in_person","video","voice","letter","other"]},{"name":"content","type":"string","max_len":8000},{"name":"summary","type":"string","max_len":400},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"sentiment","type":"enum","values":["positive","neutral","negative"]},{"name":"occurred_at","type":"string","max_len":32},{"name":"duration_minutes","type":"number"}]},"custom_field":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","label","value","kind","icon"],"update_fields":["label","value","kind","icon"],"allowed_filters":["data__parent_id","data__kind","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__label"],"default_sort":"created_at","max_limit":200,"fields":[{"name":"icon","type":"string","max_len":32},{"name":"kind","type":"enum","values":["text","number","date","url","bool"]},{"name":"label","type":"string","max_len":80},{"name":"value","type":"string","max_len":2000},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}}]},"gift":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","title","occasion","status","occurred_at","price","currency","url","notes"],"update_fields":["title","occasion","status","occurred_at","price","currency","url","notes"],"allowed_filters":["data__parent_id","data__status","data__occasion","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__occurred_at","data__title"],"default_sort":"data__occurred_at","max_limit":200,"fields":[{"name":"url","type":"url"},{"name":"notes","type":"string","max_len":4000},{"name":"price","type":"number"},{"name":"title","type":"string","max_len":200},{"name":"status","type":"enum","values":["idea","given","received"]},{"name":"currency","type":"string","max_len":8},{"name":"occasion","type":"string","max_len":120},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"occurred_at","type":"string","max_len":32}]},"journal_entry":{"ops":["list","read","create","update","delete"],"create_fields":["title","body","mood","occurred_at","tags"],"update_fields":["title","body","mood","occurred_at","tags"],"allowed_filters":["data__mood","data__tags","status","is_archived","owned_by"],"allowed_sorts":["data__occurred_at","created_at","updated_at"],"default_sort":"data__occurred_at","max_limit":200,"fields":[{"name":"body","type":"string","max_len":16000},{"name":"mood","type":"enum","values":["great","good","ok","down","awful"]},{"name":"tags","type":"tags"},{"name":"title","type":"string","max_len":200},{"name":"occurred_at","type":"string","max_len":32}]},"life_event":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","kind","title","occurred_at","description","location","recurring"],"update_fields":["kind","title","occurred_at","description","location","recurring"],"allowed_filters":["data__parent_id","data__kind","data__recurring","status","is_archived","owned_by"],"allowed_sorts":["data__occurred_at","created_at"],"default_sort":"data__occurred_at","max_limit":200,"fields":[{"name":"kind","type":"enum","values":["birthday","anniversary","met","graduation","job_start","job_end","move","marriage","birth","loss","milestone","custom"]},{"name":"title","type":"string","max_len":200},{"name":"location","type":"string","max_len":200},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"recurring","type":"bool"},{"name":"description","type":"string","max_len":4000},{"name":"occurred_at","type":"string","max_len":32}]},"note":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","body","pinned"],"update_fields":["body","pinned"],"allowed_filters":["data__parent_id","data__pinned","status","is_archived","owned_by"],"allowed_sorts":["created_at","updated_at"],"default_sort":"created_at","max_limit":200,"fields":[{"name":"body","type":"string","max_len":8000},{"name":"pinned","type":"bool"},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}}]},"pet":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","name","species","species_other","breed","born_at","color","notes"],"update_fields":["name","species","species_other","breed","born_at","color","notes"],"allowed_filters":["data__parent_id","data__species","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__name","data__born_at"],"default_sort":"data__name","max_limit":200,"fields":[{"name":"name","type":"string","max_len":80},{"name":"breed","type":"string","max_len":120},{"name":"color","type":"string","max_len":80},{"name":"notes","type":"string","max_len":2000},{"name":"born_at","type":"string","max_len":32},{"name":"species","type":"enum","values":["dog","cat","bird","fish","rabbit","hamster","guinea_pig","reptile","horse","other"]},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"species_other","type":"string","max_len":80}]},"relationship":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","target_id","kind","label","since","notes"],"update_fields":["kind","label","since","notes"],"allowed_filters":["data__parent_id","data__target_id","data__kind","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__kind"],"default_sort":"data__kind","max_limit":200,"fields":[{"name":"kind","type":"enum","values":["partner","spouse","parent","child","sibling","friend","colleague","manager","report","mentor","mentee","other"]},{"name":"label","type":"string","max_len":80},{"name":"notes","type":"string","max_len":2000},{"name":"since","type":"string","max_len":32},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}},{"name":"target_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}}]},"reminder":{"ops":["list","read","create","update","delete"],"create_fields":["parent_id","message","due_date","completed"],"update_fields":["message","due_date","completed"],"allowed_filters":["data__parent_id","data__completed","status","is_archived","owned_by"],"allowed_sorts":["created_at","data__due_date"],"default_sort":"data__due_date","max_limit":200,"fields":[{"name":"message","type":"string","max_len":400},{"name":"due_date","type":"string","max_len":32},{"name":"completed","type":"bool"},{"name":"parent_id","type":"string","max_len":64,"ref":{"type":"contact","owned":true,"optional":false}}]}}"####; // ── Configuration ────────────────────────────────────────────────── /// Standard query parameters all list endpoints accept. `filters` /// carries any additional `?key=value` pairs the type allows. #[derive(Debug, Clone, Default)] pub struct ListOpts { pub limit: Option, pub offset: Option, pub sort: Option, pub q: Option, pub filters: HashMap, } /// API client. Reuse across requests; safe for concurrent use. pub struct Client { http: reqwest::blocking::Client, base_url: String, token: std::sync::Mutex>, device_id: String, session_id: String, } #[derive(Debug)] pub enum ApiError { Http { status: u16, message: String, body: Option }, Network(String), Encoding(String), } impl std::fmt::Display for ApiError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ApiError::Http { status, message, .. } => write!(f, "HTTP {}: {}", status, message), ApiError::Network(s) => write!(f, "network error: {}", s), ApiError::Encoding(s) => write!(f, "encoding error: {}", s), } } } impl std::error::Error for ApiError {} impl Client { /// Build a new Client. Pass an empty string to fall back to /// `XCLIENT_TOKEN` from the environment. pub fn new(token: &str) -> Self { let base = std::env::var("XCLIENT_BASE_URL").unwrap_or_else(|_| DEFAULT_BASE.to_string()); let base = base.trim_end_matches('/').to_string(); let tok = if token.is_empty() { std::env::var("XCLIENT_TOKEN").ok() } else { Some(token.to_string()) }; let http = reqwest::blocking::Client::builder() .timeout(Duration::from_secs(30)) .build() .expect("reqwest client"); Self { http, base_url: base, token: std::sync::Mutex::new(tok), device_id: load_or_mint_device_id(), session_id: mint_uuid(), } } pub fn set_token(&self, token: Option) { let mut g = self.token.lock().unwrap(); *g = token; } pub fn token(&self) -> Option { self.token.lock().unwrap().clone() } fn user_agent(&self) -> String { format!("{}/{} (lib/{}; rust)", MODULE_NAME, CLIENT_VERSION, LANGUAGE) } pub fn request_list(&self, path: &str, opts: ListOpts) -> Result { let mut full = String::from(path); let mut sep = '?'; let mut push = |full: &mut String, sep: &mut char, k: &str, v: &str| { full.push(*sep); full.push_str(&urlencoding_encode(k)); full.push('='); full.push_str(&urlencoding_encode(v)); *sep = '&'; }; if let Some(v) = opts.limit { push(&mut full, &mut sep, "limit", &v.to_string()); } if let Some(v) = opts.offset { push(&mut full, &mut sep, "offset", &v.to_string()); } if let Some(v) = &opts.sort { push(&mut full, &mut sep, "sort", v); } if let Some(v) = &opts.q { push(&mut full, &mut sep, "q", v); } for (k, v) in &opts.filters { if v.is_null() { continue; } let s = match v { Value::String(s) => s.clone(), _ => v.to_string(), }; push(&mut full, &mut sep, k, &s); } self.request_json("GET", &full, None) } pub fn request_json(&self, method: &str, path: &str, body: Option) -> Result { self.maybe_autoupdate_once(); let url = format!("{}{}", self.base_url, path); let max_retries = 3u32; let mut last_err: Option = None; for attempt in 0..max_retries { let method_upper = method.to_uppercase(); let m = match method_upper.as_str() { "GET" => reqwest::Method::GET, "POST" => reqwest::Method::POST, "PATCH" => reqwest::Method::PATCH, "PUT" => reqwest::Method::PUT, "DELETE" => reqwest::Method::DELETE, _ => reqwest::Method::GET, }; let mut req = self.http.request(m, &url) .header("Accept", "application/json") .header("User-Agent", self.user_agent()) .header("X-Client-Channel", format!("client_{}", LANGUAGE)) .header("X-Client-Version", CLIENT_VERSION) .header("X-Analytics-Device-Id", &self.device_id) .header("X-Analytics-Session-Id", &self.session_id); if let Some(tok) = self.token() { if !tok.is_empty() { req = req.header("Authorization", format!("Bearer {}", tok)); } } if let Some(body_val) = &body { req = req.header("Content-Type", "application/json").body(body_val.to_string()); } match req.send() { Ok(resp) => { let status = resp.status().as_u16(); if let Some(fresh) = resp.headers().get("x-auth-refresh-token") { if let Ok(s) = fresh.to_str() { self.set_token(Some(s.to_string())); } } if is_retryable(status) && attempt + 1 < max_retries { let ra = resp.headers().get("Retry-After") .and_then(|v| v.to_str().ok()) .and_then(|s| s.parse::().ok()); std::thread::sleep(backoff(attempt, ra)); continue; } let body_text = resp.text().unwrap_or_default(); let parsed: Option = serde_json::from_str(&body_text).ok(); if status >= 400 { let msg = parsed.as_ref() .and_then(|v| v.get("detail").or_else(|| v.get("message")).and_then(|x| x.as_str())) .map(|s| s.to_string()) .unwrap_or_else(|| format!("HTTP {}", status)); self.emit_call_event(method, path, status, false); return Err(ApiError::Http { status, message: msg, body: parsed }); } self.emit_call_event(method, path, status, true); return Ok(parsed.unwrap_or(Value::Null)); } Err(e) => { last_err = Some(ApiError::Network(e.to_string())); if attempt + 1 < max_retries { std::thread::sleep(backoff(attempt, None)); continue; } self.emit_call_event(method, path, 0, false); } } } Err(last_err.unwrap_or(ApiError::Network("request failed".into()))) } // ── Analytics ────────────────────────────────────────────────── fn emit_call_event(&self, method: &str, path: &str, status: u16, ok: bool) { static META_SENT_ONCE: OnceLock<()> = OnceLock::new(); let include_env = META_SENT_ONCE.set(()).is_ok(); let mut meta = json!({ "channel": format!("client_{}", LANGUAGE), "client_version": CLIENT_VERSION, "module_name": MODULE_NAME, "language": LANGUAGE, "os": std::env::consts::OS, "arch": std::env::consts::ARCH, }); if include_env { meta["env"] = fingerprint(); } let evt = json!({ "type": "client.call", "ts_client": now_secs(), "meta": { "method": method.to_uppercase(), "path": path.split('?').next().unwrap_or(path), "status": status, "ok": ok, }, }); let body = json!({ "device_id": self.device_id, "session_id": self.session_id, "events": [evt], "meta": meta, }); let url = format!("{}/xapi2/analytics/challenge", self.base_url); let http = self.http.clone(); let ua = self.user_agent(); std::thread::spawn(move || { let _ = http.post(&url) .header("Content-Type", "application/json") .header("User-Agent", ua) .timeout(Duration::from_secs(4)) .body(body.to_string()) .send(); }); } // ── Auto-update ──────────────────────────────────────────────── fn maybe_autoupdate_once(&self) { static ATTEMPTED: OnceLock<()> = OnceLock::new(); if ATTEMPTED.set(()).is_err() { return; } if !autoupdate_enabled() { return; } // Source replacement on disk is intentionally a no-op - the // user is running compiled code, the .rs file is just a // record of the version they vendored. We still touch the // stamp so a future surface (UI hint, build-time check) can // tell when an update was last seen. let dir = state_dir(); if let Some(dir) = dir { let stamp = dir.join("update_check.json"); let _ = fs::write(&stamp, format!("{{\"checked_at\":{}}}", now_secs())); } } } // ── Module-level helpers ────────────────────────────────────────── fn is_retryable(status: u16) -> bool { matches!(status, 408 | 425 | 429 | 500 | 502 | 503 | 504) } fn backoff(attempt: u32, retry_after_sec: Option) -> Duration { if let Some(s) = retry_after_sec { if s >= 0.0 { return Duration::from_secs_f64(s.min(60.0)); } } let d = (1u32 << attempt.min(5)) as f64; Duration::from_secs_f64(d.min(60.0)) } fn now_secs() -> u64 { SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0) } fn state_dir() -> Option { let home = dirs_home()?; let d = home.join(format!(".{}", MODULE_NAME)); let _ = fs::create_dir_all(&d); Some(d) } /// Cross-platform `$HOME` lookup without pulling in the `dirs` crate. fn dirs_home() -> Option { #[cfg(unix)] { return std::env::var_os("HOME").map(PathBuf::from); } #[cfg(windows)] { return std::env::var_os("USERPROFILE").map(PathBuf::from); } #[allow(unreachable_code)] { None } } fn load_or_mint_device_id() -> String { if let Some(d) = state_dir() { let f = d.join("device.json"); if let Ok(raw) = fs::read_to_string(&f) { if let Ok(parsed) = serde_json::from_str::(&raw) { if let Some(s) = parsed.get("device_id").and_then(|v| v.as_str()) { if s.len() >= 32 { return s.to_string(); } } } } let id = mint_uuid(); let _ = fs::write(&f, json!({ "device_id": id }).to_string()); return id; } mint_uuid() } fn mint_uuid() -> String { let mut bytes = [0u8; 16]; let now = SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_nanos()).unwrap_or(0); for (i, b) in bytes.iter_mut().enumerate() { *b = ((now.wrapping_shr((i as u32) * 7)) ^ (i as u128 * 0x9e37)) as u8; } bytes[6] = (bytes[6] & 0x0f) | 0x40; bytes[8] = (bytes[8] & 0x3f) | 0x80; let h: String = bytes.iter().map(|b| format!("{:02x}", b)).collect(); format!("{}-{}-{}-{}-{}", &h[0..8], &h[8..12], &h[12..16], &h[16..20], &h[20..32]) } fn autoupdate_enabled() -> bool { let v = std::env::var("XCLIENT_NO_AUTOUPDATE").unwrap_or_default().to_lowercase(); !(v == "1" || v == "true" || v == "yes") } fn fingerprint() -> Value { let mut out = serde_json::Map::new(); out.insert("os".into(), json!(std::env::consts::OS)); out.insert("arch".into(), json!(std::env::consts::ARCH)); out.insert("term_program".into(), json!(std::env::var("TERM_PROGRAM").unwrap_or_default())); out.insert("editor_env".into(), json!(std::env::var("EDITOR").unwrap_or_default())); out.insert("ci".into(), json!(std::env::var("CI").is_ok() || std::env::var("GITHUB_ACTIONS").is_ok())); out.insert("claude_code".into(), json!(std::env::var("CLAUDECODE").is_ok() || std::env::var("CLAUDE_CODE_ENTRYPOINT").is_ok())); out.insert("codex".into(), json!(std::env::var("CODEX_HOME").is_ok())); let tp = std::env::var("TERM_PROGRAM").unwrap_or_default().to_lowercase(); out.insert("vscode".into(), json!(tp == "vscode" && std::env::var("CURSOR_TRACE_ID").is_err())); out.insert("cursor".into(), json!(std::env::var("CURSOR_TRACE_ID").is_ok())); out.insert("antigravity".into(), json!(std::env::var("ANTIGRAVITY_TRACE_ID").is_ok())); out.insert("jetbrains".into(), json!(tp.contains("jetbrains"))); Value::Object(out) } /// Tiny URL-encoder so we don't pull in the `urlencoding` crate. /// Encodes everything outside the unreserved set to `%HH`. fn urlencoding_encode(s: &str) -> String { let mut out = String::with_capacity(s.len()); for b in s.bytes() { match b { b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => out.push(b as char), _ => out.push_str(&format!("%{:02X}", b)), } } out } // ── Generated per-type wrapper functions ───────────────────────── // Every model that exposes an op gets one `_` method on // Client. The runtime above does the heavy lifting; these wrappers // just pin the URL + HTTP verb. impl Client { /// List `activity` rows. pub fn activity_list(&self, opts: ListOpts) -> Result { self.request_list("/xapi2/data/activity", opts) } /// Fetch one `activity` row by id. pub fn activity_get(&self, id: &str) -> Result { self.request_json("GET", &format!("/xapi2/data/activity/{}", id), None) } /// Create a new `activity` row. pub fn activity_create(&self, data: Value) -> Result { self.request_json("POST", "/xapi2/data/activity", Some(data)) } /// Patch an existing `activity` row. pub fn activity_update(&self, id: &str, data: Value) -> Result { self.request_json("PATCH", &format!("/xapi2/data/activity/{}", id), Some(data)) } /// Delete a `activity` row. pub fn activity_delete(&self, id: &str) -> Result<(), ApiError> { self.request_json("DELETE", &format!("/xapi2/data/activity/{}", id), None)?; Ok(()) } /// List `contact` rows. pub fn contact_list(&self, opts: ListOpts) -> Result { self.request_list("/xapi2/data/contact", opts) } /// Fetch one `contact` row by id. pub fn contact_get(&self, id: &str) -> Result { self.request_json("GET", &format!("/xapi2/data/contact/{}", id), None) } /// Create a new `contact` row. pub fn contact_create(&self, data: Value) -> Result { self.request_json("POST", "/xapi2/data/contact", Some(data)) } /// Patch an existing `contact` row. pub fn contact_update(&self, id: &str, data: Value) -> Result { self.request_json("PATCH", &format!("/xapi2/data/contact/{}", id), Some(data)) } /// Delete a `contact` row. pub fn contact_delete(&self, id: &str) -> Result<(), ApiError> { self.request_json("DELETE", &format!("/xapi2/data/contact/{}", id), None)?; Ok(()) } /// List `conversation` rows. pub fn conversation_list(&self, opts: ListOpts) -> Result { self.request_list("/xapi2/data/conversation", opts) } /// Fetch one `conversation` row by id. pub fn conversation_get(&self, id: &str) -> Result { self.request_json("GET", &format!("/xapi2/data/conversation/{}", id), None) } /// Create a new `conversation` row. pub fn conversation_create(&self, data: Value) -> Result { self.request_json("POST", "/xapi2/data/conversation", Some(data)) } /// Patch an existing `conversation` row. pub fn conversation_update(&self, id: &str, data: Value) -> Result { self.request_json("PATCH", &format!("/xapi2/data/conversation/{}", id), Some(data)) } /// Delete a `conversation` row. pub fn conversation_delete(&self, id: &str) -> Result<(), ApiError> { self.request_json("DELETE", &format!("/xapi2/data/conversation/{}", id), None)?; Ok(()) } /// List `custom_field` rows. pub fn custom_field_list(&self, opts: ListOpts) -> Result { self.request_list("/xapi2/data/custom_field", opts) } /// Fetch one `custom_field` row by id. pub fn custom_field_get(&self, id: &str) -> Result { self.request_json("GET", &format!("/xapi2/data/custom_field/{}", id), None) } /// Create a new `custom_field` row. pub fn custom_field_create(&self, data: Value) -> Result { self.request_json("POST", "/xapi2/data/custom_field", Some(data)) } /// Patch an existing `custom_field` row. pub fn custom_field_update(&self, id: &str, data: Value) -> Result { self.request_json("PATCH", &format!("/xapi2/data/custom_field/{}", id), Some(data)) } /// Delete a `custom_field` row. pub fn custom_field_delete(&self, id: &str) -> Result<(), ApiError> { self.request_json("DELETE", &format!("/xapi2/data/custom_field/{}", id), None)?; Ok(()) } /// List `gift` rows. pub fn gift_list(&self, opts: ListOpts) -> Result { self.request_list("/xapi2/data/gift", opts) } /// Fetch one `gift` row by id. pub fn gift_get(&self, id: &str) -> Result { self.request_json("GET", &format!("/xapi2/data/gift/{}", id), None) } /// Create a new `gift` row. pub fn gift_create(&self, data: Value) -> Result { self.request_json("POST", "/xapi2/data/gift", Some(data)) } /// Patch an existing `gift` row. pub fn gift_update(&self, id: &str, data: Value) -> Result { self.request_json("PATCH", &format!("/xapi2/data/gift/{}", id), Some(data)) } /// Delete a `gift` row. pub fn gift_delete(&self, id: &str) -> Result<(), ApiError> { self.request_json("DELETE", &format!("/xapi2/data/gift/{}", id), None)?; Ok(()) } /// List `journal_entry` rows. pub fn journal_entry_list(&self, opts: ListOpts) -> Result { self.request_list("/xapi2/data/journal_entry", opts) } /// Fetch one `journal_entry` row by id. pub fn journal_entry_get(&self, id: &str) -> Result { self.request_json("GET", &format!("/xapi2/data/journal_entry/{}", id), None) } /// Create a new `journal_entry` row. pub fn journal_entry_create(&self, data: Value) -> Result { self.request_json("POST", "/xapi2/data/journal_entry", Some(data)) } /// Patch an existing `journal_entry` row. pub fn journal_entry_update(&self, id: &str, data: Value) -> Result { self.request_json("PATCH", &format!("/xapi2/data/journal_entry/{}", id), Some(data)) } /// Delete a `journal_entry` row. pub fn journal_entry_delete(&self, id: &str) -> Result<(), ApiError> { self.request_json("DELETE", &format!("/xapi2/data/journal_entry/{}", id), None)?; Ok(()) } /// List `life_event` rows. pub fn life_event_list(&self, opts: ListOpts) -> Result { self.request_list("/xapi2/data/life_event", opts) } /// Fetch one `life_event` row by id. pub fn life_event_get(&self, id: &str) -> Result { self.request_json("GET", &format!("/xapi2/data/life_event/{}", id), None) } /// Create a new `life_event` row. pub fn life_event_create(&self, data: Value) -> Result { self.request_json("POST", "/xapi2/data/life_event", Some(data)) } /// Patch an existing `life_event` row. pub fn life_event_update(&self, id: &str, data: Value) -> Result { self.request_json("PATCH", &format!("/xapi2/data/life_event/{}", id), Some(data)) } /// Delete a `life_event` row. pub fn life_event_delete(&self, id: &str) -> Result<(), ApiError> { self.request_json("DELETE", &format!("/xapi2/data/life_event/{}", id), None)?; Ok(()) } /// List `note` rows. pub fn note_list(&self, opts: ListOpts) -> Result { self.request_list("/xapi2/data/note", opts) } /// Fetch one `note` row by id. pub fn note_get(&self, id: &str) -> Result { self.request_json("GET", &format!("/xapi2/data/note/{}", id), None) } /// Create a new `note` row. pub fn note_create(&self, data: Value) -> Result { self.request_json("POST", "/xapi2/data/note", Some(data)) } /// Patch an existing `note` row. pub fn note_update(&self, id: &str, data: Value) -> Result { self.request_json("PATCH", &format!("/xapi2/data/note/{}", id), Some(data)) } /// Delete a `note` row. pub fn note_delete(&self, id: &str) -> Result<(), ApiError> { self.request_json("DELETE", &format!("/xapi2/data/note/{}", id), None)?; Ok(()) } /// List `pet` rows. pub fn pet_list(&self, opts: ListOpts) -> Result { self.request_list("/xapi2/data/pet", opts) } /// Fetch one `pet` row by id. pub fn pet_get(&self, id: &str) -> Result { self.request_json("GET", &format!("/xapi2/data/pet/{}", id), None) } /// Create a new `pet` row. pub fn pet_create(&self, data: Value) -> Result { self.request_json("POST", "/xapi2/data/pet", Some(data)) } /// Patch an existing `pet` row. pub fn pet_update(&self, id: &str, data: Value) -> Result { self.request_json("PATCH", &format!("/xapi2/data/pet/{}", id), Some(data)) } /// Delete a `pet` row. pub fn pet_delete(&self, id: &str) -> Result<(), ApiError> { self.request_json("DELETE", &format!("/xapi2/data/pet/{}", id), None)?; Ok(()) } /// List `relationship` rows. pub fn relationship_list(&self, opts: ListOpts) -> Result { self.request_list("/xapi2/data/relationship", opts) } /// Fetch one `relationship` row by id. pub fn relationship_get(&self, id: &str) -> Result { self.request_json("GET", &format!("/xapi2/data/relationship/{}", id), None) } /// Create a new `relationship` row. pub fn relationship_create(&self, data: Value) -> Result { self.request_json("POST", "/xapi2/data/relationship", Some(data)) } /// Patch an existing `relationship` row. pub fn relationship_update(&self, id: &str, data: Value) -> Result { self.request_json("PATCH", &format!("/xapi2/data/relationship/{}", id), Some(data)) } /// Delete a `relationship` row. pub fn relationship_delete(&self, id: &str) -> Result<(), ApiError> { self.request_json("DELETE", &format!("/xapi2/data/relationship/{}", id), None)?; Ok(()) } /// List `reminder` rows. pub fn reminder_list(&self, opts: ListOpts) -> Result { self.request_list("/xapi2/data/reminder", opts) } /// Fetch one `reminder` row by id. pub fn reminder_get(&self, id: &str) -> Result { self.request_json("GET", &format!("/xapi2/data/reminder/{}", id), None) } /// Create a new `reminder` row. pub fn reminder_create(&self, data: Value) -> Result { self.request_json("POST", "/xapi2/data/reminder", Some(data)) } /// Patch an existing `reminder` row. pub fn reminder_update(&self, id: &str, data: Value) -> Result { self.request_json("PATCH", &format!("/xapi2/data/reminder/{}", id), Some(data)) } /// Delete a `reminder` row. pub fn reminder_delete(&self, id: &str) -> Result<(), ApiError> { self.request_json("DELETE", &format!("/xapi2/data/reminder/{}", id), None)?; Ok(()) } }