use serde_json::Value;
use std::borrow::Cow;
use std::ops::Range;
#[derive(Debug, Clone, PartialEq)]
pub struct GovernedPage<'a> {
records: Cow<'a, [Value]>,
}
impl GovernedPage<'static> {
pub(in crate::governance) fn sealed(records: Vec<Value>) -> Self {
Self {
records: Cow::Owned(records),
}
}
}
impl<'a> GovernedPage<'a> {
pub fn records(&self) -> &[Value] {
&self.records
}
pub fn len(&self) -> usize {
self.records.len()
}
pub fn is_empty(&self) -> bool {
self.records.is_empty()
}
pub fn slice(&self, range: Range<usize>) -> GovernedPage<'_> {
GovernedPage {
records: Cow::Borrowed(&self.records[range]),
}
}
pub fn select(&self, indices: &[usize]) -> GovernedPage<'static> {
GovernedPage {
records: Cow::Owned(indices.iter().map(|&i| self.records[i].clone()).collect()),
}
}
pub(crate) fn restamped(&self, records: Vec<Value>) -> GovernedPage<'static> {
GovernedPage {
records: Cow::Owned(records),
}
}
pub fn into_records(self) -> Vec<Value> {
self.records.into_owned()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct GovernedEnvelopes {
envelopes: Vec<Value>,
}
impl GovernedEnvelopes {
pub(crate) fn new() -> Self {
Self::default()
}
pub(in crate::governance) fn sealed(envelopes: Vec<Value>) -> Self {
Self { envelopes }
}
pub(crate) fn push_failure(
&mut self,
page: &GovernedPage<'_>,
index: usize,
build: impl FnOnce(&Value) -> Value,
) {
self.envelopes.push(build(&page.records()[index]));
}
pub(crate) fn prepend(&mut self, earlier: GovernedEnvelopes) {
self.envelopes.splice(0..0, earlier.envelopes);
}
pub fn envelopes(&self) -> &[Value] {
&self.envelopes
}
pub fn len(&self) -> usize {
self.envelopes.len()
}
pub fn is_empty(&self) -> bool {
self.envelopes.is_empty()
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Clearance(pub(in crate::governance) ());
#[cfg(feature = "arrow")]
#[derive(Debug, Clone)]
pub(crate) struct GovernedBatch {
batch: arrow::array::RecordBatch,
}
#[cfg(feature = "arrow")]
impl GovernedBatch {
pub(crate) fn passthrough(
batch: arrow::array::RecordBatch,
clearance: Option<&Clearance>,
) -> Result<Self, crate::FaucetError> {
if clearance.is_none() && batch.num_rows() > 0 {
return Err(crate::FaucetError::Config(
"a columnar batch with a record governance pass configured must be governed \
before it is written"
.into(),
));
}
Ok(Self { batch })
}
pub(crate) fn encode(
page: &GovernedPage<'_>,
schema: arrow::datatypes::SchemaRef,
) -> Result<Self, crate::FaucetError> {
Ok(Self {
batch: crate::columnar::values_to_record_batch(page.records(), schema)?,
})
}
pub(crate) fn batch(&self) -> &arrow::array::RecordBatch {
&self.batch
}
pub(crate) fn num_rows(&self) -> usize {
self.batch.num_rows()
}
}
#[derive(Debug)]
pub(crate) struct GovernedNative {
batch: crate::native::NativeBatch,
}
impl GovernedNative {
pub(crate) fn passthrough(batch: crate::native::NativeBatch, _: &Clearance) -> Self {
Self { batch }
}
pub(crate) fn into_inner(self) -> crate::native::NativeBatch {
self.batch
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn page() -> GovernedPage<'static> {
GovernedPage::sealed(vec![json!(0), json!(1), json!(2), json!(3)])
}
#[test]
fn derivations_stay_governed_and_keep_order() {
let p = page();
assert_eq!(p.len(), 4);
assert!(!p.is_empty());
let s = p.slice(1..3);
assert_eq!(s.records(), &[json!(1), json!(2)]);
let picked = p.select(&[3, 0]);
assert_eq!(picked.into_records(), vec![json!(3), json!(0)]);
assert!(GovernedPage::sealed(vec![]).is_empty());
assert_eq!(p.clone(), p);
}
#[test]
fn envelopes_are_built_from_governed_rows_and_prepend_in_order() {
let p = page();
let mut env = GovernedEnvelopes::new();
assert!(env.is_empty());
env.push_failure(&p, 2, |r| json!({ "payload": r }));
let mut all = GovernedEnvelopes::sealed(vec![json!("q")]);
all.prepend(GovernedEnvelopes::sealed(vec![json!("first")]));
all.envelopes.extend(env.envelopes().iter().cloned());
assert_eq!(all.len(), 3);
assert_eq!(
all.envelopes(),
&[json!("first"), json!("q"), json!({"payload": 2})]
);
}
#[cfg(feature = "arrow")]
#[test]
fn a_governed_page_re_encodes_into_a_governed_batch() {
use arrow::datatypes::{DataType, Field, Schema};
let schema =
std::sync::Arc::new(Schema::new(vec![Field::new("id", DataType::Int64, true)]));
let p = GovernedPage::sealed(vec![json!({"id": 1}), json!({"id": 2})]);
let b = GovernedBatch::encode(&p, schema.clone()).unwrap();
assert_eq!(b.num_rows(), 2);
let again = GovernedBatch::passthrough(b.batch().clone(), Some(&Clearance(()))).unwrap();
assert_eq!(again.batch().schema(), schema);
let refused = GovernedBatch::passthrough(b.batch().clone(), None).unwrap_err();
assert!(
refused.to_string().contains("must be governed"),
"{refused}"
);
let empty = arrow::array::RecordBatch::new_empty(schema);
assert_eq!(
GovernedBatch::passthrough(empty, None).unwrap().num_rows(),
0
);
}
#[test]
fn a_native_batch_passes_through_unchanged() {
let n = crate::native::NativeBatch {
format: crate::native::NativeFormat::NdJson,
payload: crate::native::NativePayload::Bytes(b"{}\n".to_vec()),
csv: Default::default(),
records: Some(1),
bookmark: None,
};
let g = GovernedNative::passthrough(n, &Clearance(()));
assert_eq!(g.into_inner().records, Some(1));
}
}