use osproxy_core::{ClusterId, Epoch, FieldName, IndexName, PartitionId};
use osproxy_spi::{
BodyDoc, DocIdRule, IdTemplate, InjectedField, InjectedValue, JsonPath, PartitionKeySpec,
Placement, PlacementAt, SpiError, TenancySpi,
};
const TENANT_FIELD: &str = "_tenant";
const TENANT_HEADER: &str = "x-tenant";
#[derive(Debug)]
pub struct ReferenceTenancy {
cluster: ClusterId,
index: IndexName,
endpoint: String,
}
impl ReferenceTenancy {
#[must_use]
pub fn new(cluster: ClusterId, index: IndexName, endpoint: impl Into<String>) -> Self {
Self {
cluster,
index,
endpoint: endpoint.into(),
}
}
}
impl TenancySpi for ReferenceTenancy {
fn resolve_partition(
&self,
ctx: &osproxy_spi::RequestCtx<'_>,
body: BodyDoc<'_>,
) -> Result<osproxy_core::PartitionId, osproxy_spi::SpiError> {
let spec = PartitionKeySpec::AnyOf(vec![
PartitionKeySpec::BodyField(JsonPath::new("tenant_id")),
PartitionKeySpec::Header(TENANT_HEADER.to_owned()),
]);
osproxy_tenancy::resolve_partition_spec(&spec, ctx, body)
}
fn doc_id_rule(&self) -> Option<DocIdRule> {
Some(DocIdRule::new(IdTemplate::new("{partition}:{body.id}")).with_routing(true))
}
fn injected_fields(&self) -> Vec<InjectedField> {
vec![InjectedField::new(
FieldName::from(TENANT_FIELD),
InjectedValue::PartitionId,
)]
}
fn cluster_endpoint(&self, cluster: &ClusterId) -> Option<String> {
(cluster == &self.cluster).then(|| self.endpoint.clone())
}
async fn placement_for(&self, _partition: &PartitionId) -> Result<PlacementAt, SpiError> {
Ok(PlacementAt::new(
Placement::SharedIndex {
cluster: self.cluster.clone(),
index: self.index.clone(),
inject: self.injected_fields(),
},
Epoch::new(1),
)
.with_endpoint(self.endpoint.clone()))
}
}