use async_trait::async_trait;
use serde::de::DeserializeOwned;
use serde::Serialize;
use structfs_core_store::{AsyncReader, AsyncWriter, Codec, Error, Path, Record};
use crate::convert::{from_value, to_value};
#[async_trait]
pub trait AsyncTypedReader: AsyncReader {
async fn read_as_async<T: DeserializeOwned + Send>(
&mut self,
from: &Path,
codec: &(dyn Codec + Sync),
) -> Result<Option<T>, Error> {
let Some(record) = self.read_async(from).await? else {
return Ok(None);
};
let value = record.into_value(codec)?;
let typed = from_value(value)?;
Ok(Some(typed))
}
async fn read_json_async(
&mut self,
from: &Path,
codec: &(dyn Codec + Sync),
) -> Result<Option<serde_json::Value>, Error> {
self.read_as_async::<structfs_core_store::Value>(from, codec)
.await?
.map(crate::value_to_json)
.transpose()
}
}
#[async_trait]
impl<R: AsyncReader + ?Sized + Send> AsyncTypedReader for R {}
#[async_trait]
pub trait AsyncTypedWriter: AsyncWriter {
async fn write_as_async<T: Serialize + Sync>(
&mut self,
to: &Path,
data: &T,
) -> Result<Path, Error> {
let value = to_value(data)?;
self.write_async(to, Record::parsed(value)).await
}
async fn write_json_async(
&mut self,
to: &Path,
data: serde_json::Value,
) -> Result<Path, Error> {
self.write_as_async(to, &data).await
}
}
#[async_trait]
impl<W: AsyncWriter + ?Sized + Send> AsyncTypedWriter for W {}
#[cfg(test)]
mod tests {
use super::*;
use crate::JsonCodec;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use structfs_core_store::{path, Record};
struct TestAsyncStore {
data: HashMap<Path, Record>,
}
impl TestAsyncStore {
fn new() -> Self {
Self {
data: HashMap::new(),
}
}
}
#[async_trait]
impl AsyncReader for TestAsyncStore {
async fn read_async(&mut self, from: &Path) -> Result<Option<Record>, Error> {
Ok(self.data.get(from).cloned())
}
}
#[async_trait]
impl AsyncWriter for TestAsyncStore {
async fn write_async(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
self.data.insert(to.clone(), data);
Ok(to.clone())
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct TestUser {
name: String,
age: u32,
}
#[tokio::test]
async fn async_typed_roundtrip() {
let mut store = TestAsyncStore::new();
let codec = JsonCodec;
let user = TestUser {
name: "Alice".to_string(),
age: 30,
};
store
.write_as_async(&path!("users/alice"), &user)
.await
.unwrap();
let recovered: TestUser = store
.read_as_async(&path!("users/alice"), &codec)
.await
.unwrap()
.unwrap();
assert_eq!(user, recovered);
}
#[tokio::test]
async fn async_read_nonexistent_returns_none() {
let mut store = TestAsyncStore::new();
let codec = JsonCodec;
let result: Option<TestUser> = store
.read_as_async(&path!("nonexistent"), &codec)
.await
.unwrap();
assert!(result.is_none());
}
#[tokio::test]
async fn async_write_json_works() {
let mut store = TestAsyncStore::new();
let codec = JsonCodec;
let json = serde_json::json!({
"key": "value",
"nested": {"a": 1, "b": 2}
});
store
.write_json_async(&path!("config"), json.clone())
.await
.unwrap();
let recovered: serde_json::Value = store
.read_json_async(&path!("config"), &codec)
.await
.unwrap()
.unwrap();
assert_eq!(json, recovered);
}
}