#![warn(missing_docs)]
extern crate alloc;
pub mod api;
pub mod fake;
pub mod login;
pub mod real;
pub mod text;
mod util;
use alloc::sync::Arc;
use core::fmt;
use std::sync::Mutex;
use serde_json::Value;
pub use self::api::{
Cart, CartOp, Fee, GrabBackend, GrabClient, GrabError, GrabResult, Health, Line, LoginDriver,
Menu, MenuItem, Merchant, Op, Placed, Quote, SessionSlot, Step, Tracked, money,
};
pub use self::login::{LoggedIn, LoginError, Logins, OnDone, Snapshot, Timing, View};
pub type Log = Arc<dyn Fn(&str) + Send + Sync>;
#[derive(Clone, Default, PartialEq, Eq)]
pub struct Secret(String);
impl Secret {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn expose(&self) -> &str {
&self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn redact(&self, text: &str) -> String {
if self.0.is_empty() {
text.to_owned()
} else {
text.replace(&self.0, "<redacted>")
}
}
}
impl fmt::Debug for Secret {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<redacted>")
}
}
pub(crate) struct Redacted;
impl fmt::Debug for Redacted {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<redacted>")
}
}
#[derive(Default)]
pub struct MemorySlot {
id: String,
session: Mutex<Option<Value>>,
cart: Mutex<Option<Value>>,
}
impl fmt::Debug for MemorySlot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let held = |m: &Mutex<Option<Value>>| util::lock(m).as_ref().map(|_| Redacted);
f.debug_struct("MemorySlot")
.field("id", &self.id)
.field("session", &held(&self.session))
.field("cart", &held(&self.cart))
.finish()
}
}
impl MemorySlot {
pub fn new(id: impl Into<String>, session: Option<Value>) -> Self {
Self {
id: id.into(),
session: Mutex::new(session),
cart: Mutex::default(),
}
}
pub fn session(&self) -> Option<Value> {
util::lock(&self.session).clone()
}
pub fn cart(&self) -> Option<Value> {
util::lock(&self.cart).clone()
}
}
#[async_trait::async_trait]
impl SessionSlot for MemorySlot {
fn id(&self) -> &str {
&self.id
}
async fn load(&self) -> GrabResult<Option<Value>> {
Ok(self.session())
}
async fn save(&self, session: &Value) -> GrabResult<()> {
*util::lock(&self.session) = Some(session.clone());
Ok(())
}
async fn load_cart(&self) -> GrabResult<Option<Value>> {
Ok(self.cart())
}
async fn save_cart(&self, cart: Option<&Value>) -> GrabResult<()> {
*util::lock(&self.cart) = cart.cloned();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_secret_never_debugs() {
let s = Secret::new("+10000000000");
assert_eq!(format!("{s:?}"), "<redacted>");
assert_eq!(s.redact("got +10000000000"), "got <redacted>");
assert!(Secret::default().is_empty());
}
}
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;