use std::str::FromStr;
use bson::{Document, doc, oid::ObjectId};
use clap::ValueEnum;
use derive_builder::Builder;
use derive_default_builder::DefaultBuilder;
use serde::{Deserialize, Serialize};
use strum::Display;
use typeshare::typeshare;
use crate::{
deserializers::string_list_deserializer,
entities::{I64, MongoId},
};
use super::{
ResourceTargetVariant, permission::PermissionLevelAndSpecifics,
};
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct Resource<Config, Info: Default = ()>
where
Config: Default,
{
#[serde(
default,
rename = "_id",
skip_serializing_if = "String::is_empty",
with = "bson::serde_helpers::hex_string_as_object_id"
)]
#[builder(setter(skip))]
#[cfg_attr(feature = "utoipa", schema(value_type = crate::entities::MongoIdObj))]
pub id: MongoId,
pub name: String,
#[serde(default)]
#[builder(default)]
pub description: String,
#[serde(default)]
#[builder(default)]
pub template: bool,
#[serde(default, deserialize_with = "string_list_deserializer")]
#[builder(default)]
pub tags: Vec<String>,
#[serde(default)]
#[builder(setter(skip))]
pub info: Info,
#[serde(default)]
#[builder(default)]
pub config: Config,
#[serde(default)]
#[builder(default)]
pub base_permission: PermissionLevelAndSpecifics,
#[serde(default)]
#[builder(setter(skip))]
pub updated_at: I64,
}
impl<C: Default, I: Default> Default for Resource<C, I> {
fn default() -> Self {
Self {
id: String::new(),
name: String::from("temp-resource"),
description: String::new(),
template: Default::default(),
tags: Vec::new(),
info: I::default(),
config: C::default(),
base_permission: Default::default(),
updated_at: 0,
}
}
}
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ResourceListItem<Info> {
pub id: String,
#[serde(rename = "type")]
pub resource_type: ResourceTargetVariant,
pub name: String,
pub template: bool,
pub tags: Vec<String>,
pub info: Info,
}
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Default, DefaultBuilder,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ResourceQuery<T: Default> {
#[serde(default)]
pub names: Vec<String>,
#[serde(default)]
pub templates: TemplatesQueryBehavior,
#[serde(default, deserialize_with = "string_list_deserializer")]
pub tags: Vec<String>,
#[serde(default)]
pub tag_behavior: TagQueryBehavior,
#[serde(default)]
pub specific: T,
}
#[typeshare]
#[derive(
Debug,
Clone,
Copy,
Default,
Serialize,
Deserialize,
ValueEnum,
Display,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[strum(serialize_all = "lowercase")]
pub enum TemplatesQueryBehavior {
#[default]
Include,
Exclude,
Only,
}
#[typeshare]
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum TagQueryBehavior {
#[default]
All,
Any,
}
pub trait AddFilters {
fn add_filters(&self, _filters: &mut Document) {}
}
impl AddFilters for () {}
impl<T: AddFilters + Default> AddFilters for ResourceQuery<T> {
fn add_filters(&self, filters: &mut Document) {
let (ids, names) = split_names(&self.names);
if !ids.is_empty() {
filters.insert("_id", doc! { "$in": ids });
}
if !names.is_empty() {
filters.insert("name", doc! { "$in": names });
}
match self.templates {
TemplatesQueryBehavior::Exclude => {
filters.insert("template", doc! { "$ne": true });
}
TemplatesQueryBehavior::Only => {
filters.insert("template", true);
}
TemplatesQueryBehavior::Include => {
}
};
if !self.tags.is_empty() {
match self.tag_behavior {
TagQueryBehavior::All => {
filters.insert("tags", doc! { "$all": &self.tags });
}
TagQueryBehavior::Any => {
let ors = self
.tags
.iter()
.map(|tag| doc! { "tags": tag })
.collect::<Vec<_>>();
filters.insert("$or", ors);
}
}
}
self.specific.add_filters(filters);
}
}
fn split_names(
names_or_ids: &[String],
) -> (Vec<ObjectId>, Vec<&String>) {
let mut ids = Vec::new();
let mut names = Vec::new();
for name in names_or_ids {
match ObjectId::from_str(name) {
Ok(id) => ids.push(id),
Err(_) => names.push(name),
}
}
(ids, names)
}