use crate::errors::FirestoreError;
use crate::{FirestoreDb, FirestoreListenerTarget, FirestoreResult};
use rvstruct::ValueStruct;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct FirestoreCacheConfiguration {
pub collections: HashMap<String, FirestoreCacheCollectionConfiguration>,
pub incomplete_collection_policy: FirestoreCacheIncompleteCollectionPolicy,
}
impl FirestoreCacheConfiguration {
#[inline]
pub fn new() -> Self {
Self {
collections: HashMap::new(),
incomplete_collection_policy: FirestoreCacheIncompleteCollectionPolicy::default(),
}
}
#[inline]
pub fn add_collection_config(
self,
db: &FirestoreDb,
config: FirestoreCacheCollectionConfiguration,
) -> Self {
self.add_collection_config_at(db.get_documents_path(), config)
}
#[inline]
pub fn with_incomplete_collection_policy(
self,
incomplete_collection_policy: FirestoreCacheIncompleteCollectionPolicy,
) -> Self {
Self {
incomplete_collection_policy,
..self
}
}
#[inline]
pub(crate) fn add_collection_config_at(
mut self,
documents_path: &str,
config: FirestoreCacheCollectionConfiguration,
) -> Self {
let collection_path = config.resolve_collection_path(documents_path);
self.collections.insert(collection_path, config);
self
}
#[inline]
pub fn target_change_action(
&self,
target_change: &gcloud_sdk::google::firestore::v1::TargetChange,
) -> crate::FirestoreCacheTargetChangeAction {
crate::cache::cache_target_change_action(self, target_change)
}
#[inline]
pub fn is_document_cached(&self, collection_path: &str, document_id: &str) -> bool {
match self.collections.get(collection_path) {
Some(config) => match config.watched_document_ids() {
Some(document_ids) => document_ids.iter().any(|id| id == document_id),
None => true,
},
None => false,
}
}
pub(crate) fn allocate_listener_target(
&self,
from: u32,
) -> FirestoreResult<FirestoreListenerTarget> {
let used: std::collections::HashSet<u32> = self
.collections
.values()
.map(|config| *config.listener_target.value())
.collect();
let mut candidate = from.max(1);
while used.contains(&candidate) {
candidate = candidate.checked_add(1).ok_or_else(|| {
FirestoreError::invalid_parameters(
"listener_target",
"Ran out of listener target IDs while assigning them automatically.",
)
})?;
}
let target = FirestoreListenerTarget::new(candidate);
target.validate()?;
Ok(target)
}
pub(crate) fn max_listener_target(&self) -> Option<u32> {
self.collections
.values()
.map(|config| *config.listener_target.value())
.max()
}
pub(crate) fn target_scope(
&self,
target: &FirestoreListenerTarget,
) -> Option<FirestoreCacheTargetScope<'_>> {
self.collections
.iter()
.find(|(_, config)| config.listener_target == *target)
.map(|(collection_path, config)| target_scope_of(collection_path, config))
}
pub(crate) fn all_target_scopes(&self) -> Vec<FirestoreCacheTargetScope<'_>> {
self.collections
.iter()
.map(|(collection_path, config)| target_scope_of(collection_path, config))
.collect()
}
#[inline]
pub fn is_collection_listable(&self, collection_path: &str) -> bool {
match self.collections.get(collection_path) {
Some(config) if config.watched_document_ids().is_some() => false,
Some(config) => match self.incomplete_collection_policy {
FirestoreCacheIncompleteCollectionPolicy::SkipCache => {
config.collection_load_mode.is_preloading()
}
FirestoreCacheIncompleteCollectionPolicy::PartialResults => true,
},
None => false,
}
}
}
impl Default for FirestoreCacheConfiguration {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum FirestoreCacheCollectionWatch {
WholeCollection,
Documents(Vec<String>),
}
impl Default for FirestoreCacheCollectionWatch {
#[inline]
fn default() -> Self {
Self::WholeCollection
}
}
#[inline]
pub(crate) fn target_scope_of<'a>(
collection_path: &'a str,
config: &'a FirestoreCacheCollectionConfiguration,
) -> FirestoreCacheTargetScope<'a> {
match config.watched_document_ids() {
Some(document_ids) => FirestoreCacheTargetScope::Documents {
collection_path,
document_ids,
},
None => FirestoreCacheTargetScope::Collection(collection_path),
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) enum FirestoreCacheTargetScope<'a> {
Collection(&'a str),
Documents {
collection_path: &'a str,
document_ids: &'a [String],
},
}
impl FirestoreCacheTargetScope<'_> {
#[inline]
pub(crate) fn collection_path(&self) -> &str {
match self {
Self::Collection(collection_path) => collection_path,
Self::Documents {
collection_path, ..
} => collection_path,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub enum FirestoreCacheIncompleteCollectionPolicy {
#[default]
SkipCache,
PartialResults,
}
#[derive(Debug, Clone)]
pub struct FirestoreCacheCollectionConfiguration {
pub collection_name: String,
pub parent: Option<String>,
pub listener_target: FirestoreListenerTarget,
pub collection_load_mode: FirestoreCacheCollectionLoadMode,
pub collection_watch: FirestoreCacheCollectionWatch,
#[doc(hidden)]
pub indices: Vec<FirestoreCacheIndexConfiguration>,
}
impl FirestoreCacheCollectionConfiguration {
#[inline]
pub fn new<S>(
collection_name: S,
listener_target: FirestoreListenerTarget,
collection_load_mode: FirestoreCacheCollectionLoadMode,
) -> Self
where
S: AsRef<str>,
{
Self {
collection_name: collection_name.as_ref().to_string(),
parent: None,
listener_target,
collection_load_mode,
collection_watch: FirestoreCacheCollectionWatch::WholeCollection,
indices: Vec::new(),
}
}
#[inline]
pub fn listener_target_params(
&self,
resume_type: Option<crate::FirestoreListenerTargetResumeType>,
) -> crate::FirestoreListenerTargetParams {
crate::cache::target_params_for_collection(self, resume_type)
}
#[inline]
pub fn listener_read_time() -> crate::FirestoreInstant {
crate::cache::cache_target_read_time()
}
#[inline]
pub fn with_documents<I>(self, document_ids: I) -> Self
where
I: IntoIterator,
I::Item: AsRef<str>,
{
Self {
collection_watch: FirestoreCacheCollectionWatch::Documents(
document_ids
.into_iter()
.map(|id| id.as_ref().to_string())
.collect(),
),
..self
}
}
#[inline]
pub fn watched_document_ids(&self) -> Option<&[String]> {
match &self.collection_watch {
FirestoreCacheCollectionWatch::WholeCollection => None,
FirestoreCacheCollectionWatch::Documents(ids) => Some(ids),
}
}
#[inline]
pub fn with_parent<S>(self, parent: S) -> Self
where
S: AsRef<str>,
{
Self {
parent: Some(parent.as_ref().to_string()),
..self
}
}
#[doc(hidden)]
#[deprecated(
since = "0.52.0",
note = "Cache indices are not implemented and have no effect. This method is scheduled for removal."
)]
#[inline]
pub fn with_index(self, index: FirestoreCacheIndexConfiguration) -> Self {
let mut indices = self.indices;
indices.push(index);
Self { indices, ..self }
}
#[inline]
pub(crate) fn resolve_collection_path(&self, documents_path: &str) -> String {
match self.parent {
Some(ref parent) => format!("{}/{}", parent, self.collection_name),
None => format!("{}/{}", documents_path, self.collection_name),
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum FirestoreCacheCollectionLoadMode {
PreloadAllDocs,
PreloadAllIfEmpty,
PreloadNone,
}
impl FirestoreCacheCollectionLoadMode {
#[inline]
pub fn is_preloading(&self) -> bool {
matches!(self, Self::PreloadAllDocs | Self::PreloadAllIfEmpty)
}
}
#[doc(hidden)]
#[derive(Debug, Clone)]
pub struct FirestoreCacheIndexConfiguration {
pub fields: Vec<String>,
pub unique: bool,
}
impl FirestoreCacheIndexConfiguration {
#[inline]
pub fn new<I>(fields: I) -> Self
where
I: IntoIterator,
I::Item: AsRef<str>,
{
Self {
fields: fields.into_iter().map(|s| s.as_ref().to_string()).collect(),
unique: false,
}
}
#[inline]
pub fn unique(self, value: bool) -> Self {
Self {
unique: value,
..self
}
}
}