use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use certon::{CertResolver, Config, KeyInfo, Result, Storage};
use chrono::Utc;
use tokio::sync::RwLock;
struct MemoryStorage {
data: RwLock<HashMap<String, Vec<u8>>>,
locks: RwLock<HashMap<String, bool>>,
}
impl MemoryStorage {
fn new() -> Self {
Self {
data: RwLock::new(HashMap::new()),
locks: RwLock::new(HashMap::new()),
}
}
}
#[async_trait]
impl Storage for MemoryStorage {
async fn store(&self, key: &str, value: &[u8]) -> Result<()> {
let mut data = self.data.write().await;
data.insert(key.to_string(), value.to_vec());
Ok(())
}
async fn load(&self, key: &str) -> Result<Vec<u8>> {
let data = self.data.read().await;
data.get(key).cloned().ok_or_else(|| {
certon::Error::Storage(certon::error::StorageError::NotFound(format!(
"key not found: {key}"
)))
})
}
async fn delete(&self, key: &str) -> Result<()> {
let mut data = self.data.write().await;
let prefix = format!("{key}/");
data.retain(|k, _| k != key && !k.starts_with(&prefix));
Ok(())
}
async fn exists(&self, key: &str) -> Result<bool> {
let data = self.data.read().await;
let prefix = format!("{key}/");
Ok(data.contains_key(key) || data.keys().any(|k| k.starts_with(&prefix)))
}
async fn list(&self, path: &str, recursive: bool) -> Result<Vec<String>> {
let data = self.data.read().await;
let prefix = if path.is_empty() {
String::new()
} else {
format!("{path}/")
};
let mut results = Vec::new();
for key in data.keys() {
if let Some(rest) = key.strip_prefix(&prefix) {
if recursive || !rest.contains('/') {
results.push(key.clone());
}
}
}
results.sort();
Ok(results)
}
async fn stat(&self, key: &str) -> Result<KeyInfo> {
let data = self.data.read().await;
match data.get(key) {
Some(value) => Ok(KeyInfo {
key: key.to_string(),
modified: Utc::now(),
size: value.len() as u64,
is_terminal: true,
}),
None => {
let prefix = format!("{key}/");
if data.keys().any(|k| k.starts_with(&prefix)) {
Ok(KeyInfo {
key: key.to_string(),
modified: Utc::now(),
size: 0,
is_terminal: false,
})
} else {
Err(certon::Error::Storage(
certon::error::StorageError::NotFound(format!("key not found: {key}")),
))
}
}
}
}
async fn lock(&self, name: &str) -> Result<()> {
loop {
{
let mut locks = self.locks.write().await;
if !locks.contains_key(name) {
locks.insert(name.to_string(), true);
return Ok(());
}
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
}
async fn unlock(&self, name: &str) -> Result<()> {
let mut locks = self.locks.write().await;
locks.remove(name);
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let storage: Arc<dyn Storage> = Arc::new(MemoryStorage::new());
let config = Config::builder().storage(storage).build();
let domains = vec!["example.com".into()];
println!("Managing certificates with in-memory storage backend...");
config.manage_sync(&domains).await?;
let resolver = CertResolver::new(config.cache.clone());
let _tls_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_cert_resolver(Arc::new(resolver));
println!("TLS config ready with custom storage backend");
Ok(())
}