#![warn(missing_docs)] //! Small things that couldn't fit elsewhere in Kittybox, yet may be //! useful on their own or in multiple Kittybox crates. //! //! Some things are gated behind features, namely: //! - `fs` - enables use of filesystem-related utilities use serde::{Deserialize, Serialize}; #[derive(Clone, Serialize, Deserialize)] pub struct IndiewebEndpoints { pub authorization_endpoint: String, pub token_endpoint: String, pub webmention: Option, pub microsub: Option, } /// Data structure representing a Micropub channel in the ?q=channels output. #[derive(Serialize, Deserialize, PartialEq, Debug)] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] pub struct MicropubChannel { /// The channel's UID. It is usually also a publically accessible permalink URL. pub uid: String, /// The channel's user-friendly name used to recognize it in lists. pub name: String, } /// Common errors from the IndieWeb protocols that can be reused between modules. pub mod error; pub use error::{ErrorType, MicropubError}; /// Common data-types useful in creating smart authentication systems. pub mod auth { #[derive(PartialEq, Eq, Hash, Clone, Copy)] pub enum EnrolledCredential { /// An indicator that a password is enrolled. Passwords can be /// used to recover from a lost token. Password, /// An indicator that one or more WebAuthn credentials were /// enrolled. WebAuthn } } #[cfg(feature = "fs")] /// Commonly-used operations with the file system in Kittybox's /// underlying storage mechanisms. pub mod fs { use std::io::{self, Result}; use std::path::{Path, PathBuf}; use rand::{Rng, distributions::Alphanumeric}; use tokio::fs; /// Create a temporary file named `temp.[a-zA-Z0-9]{length}` in /// the given location and immediately open it. Returns the /// filename and the corresponding file handle. It is the caller's /// responsibility to clean up the temporary file when it is no /// longer needed. /// /// Uses [`OpenOptions::create_new`][fs::OpenOptions::create_new] /// to detect filename collisions, in which case it will /// automatically retry until the operation succeeds. /// /// # Errors /// /// Returns the underlying [`io::Error`] if the operation fails /// due to reasons other than filename collision. pub async fn mktemp(dir: T, basename: B, length: usize) -> Result<(PathBuf, fs::File)> where T: AsRef, B: Into> { let dir = dir.as_ref(); let basename = basename.into().unwrap_or(""); fs::create_dir_all(dir).await?; loop { let filename = dir.join(format!( "{}{}{}", basename, if basename.is_empty() { "" } else { "." }, { let string = rand::thread_rng() .sample_iter(&Alphanumeric) .take(length) .collect::>(); String::from_utf8(string).unwrap() } )); match fs::OpenOptions::new() .create_new(true) .write(true) .open(&filename) .await { Ok(file) => return Ok((filename, file)), Err(err) => match err.kind() { io::ErrorKind::AlreadyExists => continue, _ => return Err(err) } } } } }