use std::fmt;
use std::marker::PhantomData;
use crate::{ConfigError, Operator, SortOrder};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct FieldId(pub u16);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ValueType {
Bool,
I32,
I64,
F32,
F64,
#[cfg(feature = "decimal")]
Decimal,
Text,
Bytes,
#[cfg(feature = "chrono")]
Date,
#[cfg(feature = "chrono")]
Time,
#[cfg(feature = "chrono")]
DateTime,
#[cfg(feature = "chrono")]
Timestamp,
#[cfg(feature = "uuid")]
Uuid,
#[cfg(feature = "json")]
Json,
#[cfg(feature = "postgres-types")]
ArrayI64,
#[cfg(feature = "postgres-types")]
ArrayText,
#[cfg(feature = "postgres-types")]
Int8Range,
#[cfg(feature = "postgres-types")]
Network,
}
impl ValueType {
pub fn supports_cursor(self) -> bool {
match self {
#[cfg(feature = "json")]
Self::Json => false,
#[cfg(feature = "postgres-types")]
Self::ArrayI64 | Self::ArrayText | Self::Int8Range | Self::Network => false,
_ => true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FieldKind {
Scalar,
GroupKey,
Aggregate,
FilterOnly,
}
#[derive(Debug, Clone, Copy)]
pub struct OperatorSet(pub &'static [Operator]);
impl OperatorSet {
#[must_use]
pub fn contains(self, operator: Operator) -> bool {
self.0.contains(&operator)
}
}
#[derive(Debug)]
pub struct FieldMeta {
pub id: FieldId,
pub public_name: &'static str,
pub column: &'static str,
pub result_alias: Option<&'static str>,
pub value_type: ValueType,
pub kind: FieldKind,
pub filterable: bool,
pub selectable: bool,
pub sortable: bool,
pub operators: OperatorSet,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OrderMeta {
pub field: FieldId,
pub direction: SortOrder,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EntityLimits {
pub max_page_size: u32,
pub max_offset: u64,
pub timeout_ms: u64,
}
impl Default for EntityLimits {
fn default() -> Self {
Self {
max_page_size: 100,
max_offset: 20_000,
timeout_ms: 5_000,
}
}
}
#[derive(Debug)]
pub struct EntityMeta {
pub entity_name: &'static str,
pub from: &'static str,
pub base_where: Option<&'static str>,
pub fixed_having: Option<&'static str>,
pub distinct: bool,
pub fields: &'static [FieldMeta],
pub default_order: &'static [OrderMeta],
pub stable_order: &'static [OrderMeta],
pub limits: EntityLimits,
pub schema_fingerprint: u64,
}
impl EntityMeta {
#[must_use]
pub fn field(&'static self, id: FieldId) -> Option<&'static FieldMeta> {
self.fields.get(usize::from(id.0))
}
#[must_use]
pub fn resolve_field(&'static self, name: &str) -> Option<&'static FieldMeta> {
self.fields.iter().find(|field| field.public_name == name)
}
#[must_use]
pub fn is_grouped(&self) -> bool {
self.fields
.iter()
.any(|field| matches!(field.kind, FieldKind::GroupKey | FieldKind::Aggregate))
}
pub fn validate(&self) -> Result<(), ConfigError> {
if self.entity_name.is_empty() || self.from.trim().is_empty() {
return Err(ConfigError::Invalid(
"entity_name and from must not be empty".to_owned(),
));
}
for (index, field) in self.fields.iter().enumerate() {
if usize::from(field.id.0) != index {
return Err(ConfigError::Invalid(format!(
"field {} has non-contiguous FieldId {}",
field.public_name, field.id.0
)));
}
}
if self.is_grouped()
&& self.fields.iter().any(|field| {
field.result_alias.is_some() && matches!(field.kind, FieldKind::Scalar)
})
{
return Err(ConfigError::Invalid(
"grouped entities cannot return Scalar fields".to_owned(),
));
}
Ok(())
}
}
pub trait SearchEntity: Sized + Send + Sync + 'static {
fn condition_order(_field: FieldId) -> Option<ConditionOrder> {
None
}
fn custom_operator_names(_field: FieldId) -> &'static [&'static str] {
&[]
}
fn codec_name(_field: FieldId) -> Option<&'static str> {
None
}
fn label_bindings() -> &'static [(&'static str, &'static str)] {
&[]
}
const META: &'static EntityMeta;
fn cursor_key_non_null(_field: FieldId) -> bool {
false
}
fn cursor_value(&self, _field: FieldId) -> Option<crate::CursorValue> {
None
}
}
#[derive(Debug, Clone, Copy)]
pub struct ConditionOrder {
pub source: FieldId,
pub operator: &'static str,
pub expression: crate::CustomOperator,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProjectionField {
pub source: FieldId,
pub result_alias: &'static str,
}
pub trait SearchProjection<E: SearchEntity>: Sized + Send + Sync + 'static {
const FIELDS: &'static [ProjectionField];
fn cursor_value(&self, _source: FieldId) -> Option<crate::CursorValue> {
None
}
}
#[derive(PartialEq, Eq, Hash)]
pub struct FieldRef<E, T, K> {
id: FieldId,
_marker: PhantomData<fn() -> (E, T, K)>,
}
impl<E, T, K> FieldRef<E, T, K> {
#[doc(hidden)]
pub const fn new(id: FieldId) -> Self {
Self {
id,
_marker: PhantomData,
}
}
#[must_use]
pub const fn id(self) -> FieldId {
self.id
}
}
impl<E, T, K> Clone for FieldRef<E, T, K> {
fn clone(&self) -> Self {
*self
}
}
impl<E, T, K> Copy for FieldRef<E, T, K> {}
impl<E, T, K> fmt::Debug for FieldRef<E, T, K> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_tuple("FieldRef").field(&self.id).finish()
}
}
pub enum ScalarKind {}
pub enum GroupKeyKind {}
pub enum AggregateKind {}
pub enum FilterOnlyKind {}
pub trait RowPhaseKind {}
impl RowPhaseKind for ScalarKind {}
impl RowPhaseKind for GroupKeyKind {}
impl RowPhaseKind for FilterOnlyKind {}
pub trait SummableKind {}
impl SummableKind for ScalarKind {}
impl SummableKind for GroupKeyKind {}
impl SummableKind for AggregateKind {}
pub struct FilterOnly<T>(PhantomData<fn() -> T>);
impl<T> Default for FilterOnly<T> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<T> Clone for FilterOnly<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for FilterOnly<T> {}
impl<T> fmt::Debug for FilterOnly<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("FilterOnly")
}
}