use std::num::NonZeroUsize;
use std::sync::Arc;
use serde::Serialize;
use super::host::{Catalog, CatalogKind, CatalogPage};
use super::{Context, Lane, Operation, OperationManager};
use crate::storage::query::SchemaSelection;
use crate::storage::{IdentityKind, IdentitySelection, ReadLimits, StorageManager, StorageSchema, StoredIdentity};
use crate::{Error, Result};
pub struct Listing<C> {
storage: Arc<Option<Arc<StorageManager<C>>>>,
}
impl<C: 'static> Operation for Listing<C> {
type Declaration = Catalog;
type Resources = Option<Arc<StorageManager<C>>>;
type Error = Error;
type Context<'a> = Context<'a>;
type Endpoints = ();
async fn initialize<'ctx>(storage: Arc<Self::Resources>, _: Self::Context<'ctx>) -> Result<Self> {
Ok(Self { storage })
}
async fn run<'ctx>(
&mut self, context: Self::Context<'ctx>, request: &mut Catalog, _: &mut Self::Endpoints,
) -> Result<super::Response<CatalogPage>> {
let page = match request.kind {
CatalogKind::Operations => context.operations.discover(&context.source, request)?,
CatalogKind::Schemas => {
let schema = request.schema.clone();
stored::<C, StorageSchema>(self.storage()?, &SchemaSelection { schema: None }, request, |entry| {
schema.as_ref().is_none_or(|schema| schema == &entry.key.id)
})
.await?
}
CatalogKind::Streams | CatalogKind::Objects => {
let selection = IdentitySelection {
kind: if request.kind == CatalogKind::Streams { IdentityKind::Streams } else { IdentityKind::Objects },
schema: request.schema.clone(),
};
stored::<C, StoredIdentity>(self.storage()?, &selection, request, |_| true).await?
}
};
Ok(page.into())
}
}
impl<C> Listing<C> {
fn storage(&self) -> Result<&StorageManager<C>> {
self.storage.as_deref().ok_or(Error::NotReady { domain: "catalog storage" })
}
}
pub fn register<C: 'static>(
operations: &OperationManager, owner: impl Into<String>, lane: impl Lane, storage: Option<Arc<StorageManager<C>>>,
) -> Result<()> {
operations.register_wire_operation::<Listing<C>>(owner.into(), lane, Arc::new(storage))
}
async fn stored<C, T>(
storage: &StorageManager<C>, selection: &T::Selection, request: &Catalog, keep: impl Fn(&T) -> bool,
) -> Result<CatalogPage>
where
T: crate::storage::follow::Entry,
{
let (rows, bytes) = super::manager::limits(request)?;
let after = request
.after
.as_deref()
.map(str::parse::<u64>)
.transpose()
.map_err(|_| Error::InvalidInput { field: "catalog cursor", reason: "cursor must be a storage position" })?
.unwrap_or(0);
let limits = ReadLimits {
rows: NonZeroUsize::new(rows).expect("validated catalog rows"),
bytes: NonZeroUsize::new(bytes).expect("validated catalog bytes"),
};
let page = storage.page::<T>(selection, after, limits).await?;
let mut entries = vec![b'['];
let mut next = None;
let mut count = 0usize;
for entry in page.entries() {
let position = entry.position().sequence;
if keep(entry) {
let start = entries.len();
if count > 0 {
entries.push(b',');
}
if write(&mut entries, entry).is_err() || entries.len() + 1 > bytes {
entries.truncate(start);
if next.is_none() {
return Err(Error::ResourceExhausted { resource: "catalog entry bytes" });
}
break;
}
count += 1;
}
next = Some(position.to_string());
}
entries.push(b']');
Ok(CatalogPage { entries, next })
}
fn write(bytes: &mut Vec<u8>, entry: &impl Serialize) -> std::result::Result<(), serde_json::Error> {
serde_json::to_writer(bytes, entry)
}