use std::collections::HashMap; use super::ids::*; use super::DatabaseError; use crate::database::redis::RedisPool; use chrono::DateTime; use chrono::Utc; use futures::TryStreamExt; use itertools::Itertools; use serde::{Deserialize, Serialize}; const LOADER_ID: &str = "loader_id"; const LOADERS_LIST_NAMESPACE: &str = "loaders"; const LOADER_FIELDS_NAMESPACE: &str = "loader_fields"; const LOADER_FIELD_ENUMS_ID_NAMESPACE: &str = "loader_field_enums"; const LOADER_FIELD_ENUM_VALUES_NAMESPACE: &str = "loader_field_enum_values"; #[derive(Clone, Serialize, Deserialize, Debug, Copy)] pub enum Game { MinecraftJava, // MinecraftBedrock // Future games } impl Game { pub fn name(&self) -> &'static str { match self { Game::MinecraftJava => "minecraft-java", // Game::MinecraftBedrock => "minecraft-bedrock" // Future games } } pub fn from_name(name: &str) -> Option { match name { "minecraft-java" => Some(Game::MinecraftJava), // "minecraft-bedrock" => Some(Game::MinecraftBedrock) // Future games _ => None, } } } #[derive(Serialize, Deserialize, Clone)] pub struct Loader { pub id: LoaderId, pub loader: String, pub icon: String, pub supported_project_types: Vec, pub supported_games: Vec, } impl Loader { pub async fn get_id<'a, E>( name: &str, exec: E, redis: &RedisPool, ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { let cached_id: Option = redis.get_deserialized_from_json(LOADER_ID, name).await?; if let Some(cached_id) = cached_id { return Ok(Some(LoaderId(cached_id))); } let result = sqlx::query!( " SELECT id FROM loaders WHERE loader = $1 ", name ) .fetch_optional(exec) .await? .map(|r| LoaderId(r.id)); if let Some(result) = result { redis .set_serialized_to_json(LOADER_ID, name, &result.0, None) .await?; } Ok(result) } pub async fn list<'a, E>(exec: E, redis: &RedisPool) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { let cached_loaders: Option> = redis .get_deserialized_from_json(LOADERS_LIST_NAMESPACE, "all") .await?; if let Some(cached_loaders) = cached_loaders { return Ok(cached_loaders); } let result = sqlx::query!( " SELECT l.id id, l.loader loader, l.icon icon, ARRAY_AGG(DISTINCT pt.name) filter (where pt.name is not null) project_types, ARRAY_AGG(DISTINCT g.name) filter (where g.name is not null) games FROM loaders l LEFT OUTER JOIN loaders_project_types lpt ON joining_loader_id = l.id LEFT OUTER JOIN project_types pt ON lpt.joining_project_type_id = pt.id LEFT OUTER JOIN loaders_project_types_games lptg ON lptg.loader_id = lpt.joining_loader_id AND lptg.project_type_id = lpt.joining_project_type_id LEFT OUTER JOIN games g ON lptg.game_id = g.id GROUP BY l.id; ", ) .fetch_many(exec) .try_filter_map(|e| async { Ok(e.right().map(|x| Loader { id: LoaderId(x.id), loader: x.loader, icon: x.icon, supported_project_types: x .project_types .unwrap_or_default() .iter() .map(|x| x.to_string()) .collect(), supported_games: x .games .unwrap_or_default() .iter() .filter_map(|x| Game::from_name(x)) .collect(), })) }) .try_collect::>() .await?; redis .set_serialized_to_json(LOADERS_LIST_NAMESPACE, "all", &result, None) .await?; Ok(result) } } #[derive(Clone, Serialize, Deserialize, Debug)] pub struct LoaderField { pub id: LoaderFieldId, pub field: String, pub field_type: LoaderFieldType, pub optional: bool, pub min_val: Option, pub max_val: Option, } #[derive(Clone, Serialize, Deserialize, Debug)] pub enum LoaderFieldType { Integer, Text, Enum(LoaderFieldEnumId), Boolean, ArrayInteger, ArrayText, ArrayEnum(LoaderFieldEnumId), ArrayBoolean, } impl LoaderFieldType { pub fn build(field_type_name: &str, loader_field_enum: Option) -> Option { Some(match (field_type_name, loader_field_enum) { ("integer", _) => LoaderFieldType::Integer, ("text", _) => LoaderFieldType::Text, ("boolean", _) => LoaderFieldType::Boolean, ("array_integer", _) => LoaderFieldType::ArrayInteger, ("array_text", _) => LoaderFieldType::ArrayText, ("array_boolean", _) => LoaderFieldType::ArrayBoolean, ("enum", Some(id)) => LoaderFieldType::Enum(LoaderFieldEnumId(id)), ("array_enum", Some(id)) => LoaderFieldType::ArrayEnum(LoaderFieldEnumId(id)), _ => return None, }) } pub fn to_str(&self) -> &'static str { match self { LoaderFieldType::Integer => "integer", LoaderFieldType::Text => "text", LoaderFieldType::Boolean => "boolean", LoaderFieldType::ArrayInteger => "array_integer", LoaderFieldType::ArrayText => "array_text", LoaderFieldType::ArrayBoolean => "array_boolean", LoaderFieldType::Enum(_) => "enum", LoaderFieldType::ArrayEnum(_) => "array_enum", } } } #[derive(Clone, Serialize, Deserialize, Debug)] pub struct LoaderFieldEnum { pub id: LoaderFieldEnumId, pub enum_name: String, pub ordering: Option, pub hidable: bool, } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct LoaderFieldEnumValue { pub id: LoaderFieldEnumValueId, pub enum_id: LoaderFieldEnumId, pub value: String, pub ordering: Option, pub created: DateTime, #[serde(flatten)] pub metadata: serde_json::Value, } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct VersionField { pub version_id: VersionId, pub field_id: LoaderFieldId, pub field_name: String, pub value: VersionFieldValue, } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub enum VersionFieldValue { Integer(i32), Text(String), Enum(LoaderFieldEnumId, LoaderFieldEnumValue), Boolean(bool), ArrayInteger(Vec), ArrayText(Vec), ArrayEnum(LoaderFieldEnumId, Vec), ArrayBoolean(Vec), } #[derive(Clone, Serialize, Deserialize, Debug)] pub struct QueryVersionField { pub version_id: VersionId, pub field_id: LoaderFieldId, pub int_value: Option, pub enum_value: Option, pub string_value: Option, } impl QueryVersionField { pub fn with_int_value(mut self, int_value: i32) -> Self { self.int_value = Some(int_value); self } pub fn with_enum_value(mut self, enum_value: LoaderFieldEnumValue) -> Self { self.enum_value = Some(enum_value); self } pub fn with_string_value(mut self, string_value: String) -> Self { self.string_value = Some(string_value); self } } #[derive(Clone, Serialize, Deserialize, Debug)] pub struct SideType { pub id: SideTypeId, pub name: String, } impl LoaderField { pub async fn get_field<'a, E>( field: &str, exec: E, redis: &RedisPool, ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { let fields = Self::get_fields(exec, redis).await?; Ok(fields.into_iter().find(|f| f.field == field)) } // Gets all fields for a given loader // Returns all as this there are probably relatively few fields per loader // TODO: in the future, this should be to get all fields in relation to something // - e.g. get all fields for a given game? pub async fn get_fields<'a, E>( exec: E, redis: &RedisPool, ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { let cached_fields = redis .get_deserialized_from_json(LOADER_FIELDS_NAMESPACE, 0) // 0 => whatever we search for fields by .await?; if let Some(cached_fields) = cached_fields { return Ok(cached_fields); } let result = sqlx::query!( " SELECT lf.id, lf.field, lf.field_type, lf.optional, lf.min_val, lf.max_val, lf.enum_type FROM loader_fields lf ", ) .fetch_many(exec) .try_filter_map(|e| async { Ok(e.right().and_then(|r| { Some(LoaderField { id: LoaderFieldId(r.id), field_type: LoaderFieldType::build(&r.field_type, r.enum_type)?, field: r.field, optional: r.optional, min_val: r.min_val, max_val: r.max_val, }) })) }) .try_collect::>() .await?; redis .set_serialized_to_json(LOADER_FIELDS_NAMESPACE, &0, &result, None) .await?; Ok(result) } } impl LoaderFieldEnum { pub async fn get<'a, E>( enum_name: &str, // Note: NOT loader field name exec: E, redis: &RedisPool, ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { let cached_enum = redis .get_deserialized_from_json(LOADER_FIELD_ENUMS_ID_NAMESPACE, enum_name) .await?; if let Some(cached_enum) = cached_enum { return Ok(cached_enum); } let result = sqlx::query!( " SELECT lfe.id, lfe.enum_name, lfe.ordering, lfe.hidable FROM loader_field_enums lfe WHERE lfe.enum_name = $1 ", enum_name ) .fetch_optional(exec) .await? .map(|l| LoaderFieldEnum { id: LoaderFieldEnumId(l.id), enum_name: l.enum_name, ordering: l.ordering, hidable: l.hidable, }); redis .set_serialized_to_json(LOADER_FIELD_ENUMS_ID_NAMESPACE, enum_name, &result, None) .await?; Ok(result) } } impl LoaderFieldEnumValue { pub async fn list<'a, E>( loader_field_enum_id: LoaderFieldEnumId, exec: E, redis: &RedisPool, ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { Ok(Self::list_many(&[loader_field_enum_id], exec, redis) .await? .into_iter() .next() .map(|x| x.1) .unwrap_or_default()) } pub async fn list_many_loader_fields<'a, E>( loader_fields: &[LoaderField], exec: E, redis: &RedisPool, ) -> Result>, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { let get_enum_id = |x: &LoaderField| match x.field_type { LoaderFieldType::Enum(id) | LoaderFieldType::ArrayEnum(id) => Some(id), _ => None, }; let enum_ids = loader_fields .iter() .filter_map(|x| get_enum_id(x)) .collect::>(); let values = Self::list_many(&enum_ids, exec, redis) .await? .into_iter() .collect::>(); let mut res = HashMap::new(); for lf in loader_fields { if let Some(id) = get_enum_id(lf) { res.insert(lf.id, values.get(&id).unwrap_or(&Vec::new()).to_vec()); } } Ok(res) } pub async fn list_many<'a, E>( loader_field_enum_ids: &[LoaderFieldEnumId], exec: E, redis: &RedisPool, ) -> Result)>, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { let mut found_enums = Vec::new(); let mut remaining_enums: Vec = loader_field_enum_ids.to_vec(); if !remaining_enums.is_empty() { let enums = redis .multi_get::( LOADER_FIELD_ENUM_VALUES_NAMESPACE, loader_field_enum_ids.iter().map(|x| x.0), ) .await?; for lfe in enums { if let Some(lfe) = lfe.and_then(|x| { serde_json::from_str::<(LoaderFieldEnumId, Vec)>(&x).ok() }) { remaining_enums.retain(|x| lfe.0 .0 != x.0); found_enums.push(lfe.1); continue; } } } let remaining_enums = remaining_enums.iter().map(|x| x.0).collect::>(); let result = sqlx::query!( " SELECT id, enum_id, value, ordering, metadata, created FROM loader_field_enum_values WHERE enum_id = ANY($1) ", &remaining_enums ) .fetch_many(exec) .try_filter_map(|e| async { Ok(e.right().map(|c| LoaderFieldEnumValue { id: LoaderFieldEnumValueId(c.id), enum_id: LoaderFieldEnumId(c.enum_id), value: c.value, ordering: c.ordering, created: c.created, metadata: c.metadata.unwrap_or_default(), })) }) .try_collect::>() .await?; // Convert from an Vec to a Vec<(LoaderFieldEnumId, Vec)> let cachable_enum_sets: Vec<(LoaderFieldEnumId, Vec)> = result .clone() .into_iter() .group_by(|x| x.enum_id) .into_iter() .map(|(k, v)| (k, v.collect::>().to_vec())) .collect(); for (k, v) in cachable_enum_sets.iter() { redis .set_serialized_to_json(LOADER_FIELD_ENUM_VALUES_NAMESPACE, k.0, v, None) .await?; } Ok(cachable_enum_sets) } // Matches filter against metadata of enum values pub async fn list_filter<'a, E>( loader_field_enum_id: LoaderFieldEnumId, filter: HashMap, exec: E, redis: &RedisPool, ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { let result = Self::list(loader_field_enum_id, exec, redis) .await? .into_iter() .filter(|x| { let mut bool = true; for (key, value) in filter.iter() { if let Some(metadata_value) = x.metadata.get(key) { bool &= metadata_value == value; } else { bool = false; } } bool }) .collect(); Ok(result) } } impl VersionField { pub async fn insert_many( items: Vec, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result<(), DatabaseError> { let mut query_version_fields = vec![]; for item in items { let base = QueryVersionField { version_id: item.version_id, field_id: item.field_id, int_value: None, enum_value: None, string_value: None, }; match item.value { VersionFieldValue::Integer(i) => { query_version_fields.push(base.clone().with_int_value(i)) } VersionFieldValue::Text(s) => { query_version_fields.push(base.clone().with_string_value(s)) } VersionFieldValue::Boolean(b) => { query_version_fields.push(base.clone().with_int_value(if b { 1 } else { 0 })) } VersionFieldValue::ArrayInteger(v) => { for i in v { query_version_fields.push(base.clone().with_int_value(i)); } } VersionFieldValue::ArrayText(v) => { for s in v { query_version_fields.push(base.clone().with_string_value(s)); } } VersionFieldValue::ArrayBoolean(v) => { for b in v { query_version_fields.push(base.clone().with_int_value(if b { 1 } else { 0 })); } } VersionFieldValue::Enum(_, v) => { query_version_fields.push(base.clone().with_enum_value(v)) } VersionFieldValue::ArrayEnum(_, v) => { for ev in v { query_version_fields.push(base.clone().with_enum_value(ev)); } } }; } let (field_ids, version_ids, int_values, enum_values, string_values): ( Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, ) = query_version_fields .iter() .map(|l| { ( l.field_id.0, l.version_id.0, l.int_value, l.enum_value.as_ref().map(|e| e.id.0), l.string_value.clone(), ) }) .multiunzip(); sqlx::query!( " INSERT INTO version_fields (field_id, version_id, int_value, string_value, enum_value) SELECT * FROM UNNEST($1::integer[], $2::bigint[], $3::integer[], $4::text[], $5::integer[]) ", &field_ids[..], &version_ids[..], &int_values[..] as &[Option], &string_values[..] as &[Option], &enum_values[..] as &[Option] ) .execute(&mut **transaction) .await?; Ok(()) } pub fn check_parse( version_id: VersionId, loader_field: LoaderField, value: serde_json::Value, enum_variants: Vec, ) -> Result { let value = VersionFieldValue::parse(&loader_field, value, enum_variants)?; Ok(VersionField { version_id, field_id: loader_field.id, field_name: loader_field.field, value, }) } pub fn from_query_json( version_id: i64, loader_fields: Option, version_fields: Option, loader_field_enum_values: Option, ) -> Vec { #[derive(Deserialize, Debug)] struct JsonLoaderField { lf_id: i32, field: String, field_type: String, enum_type: Option, min_val: Option, max_val: Option, optional: bool, } #[derive(Deserialize, Debug)] struct JsonVersionField { field_id: i32, int_value: Option, enum_value: Option, string_value: Option, } #[derive(Deserialize, Debug)] struct JsonLoaderFieldEnumValue { id: i32, enum_id: i32, value: String, ordering: Option, created: DateTime, metadata: Option, } let query_loader_fields: Vec = loader_fields .and_then(|x| serde_json::from_value(x).ok()) .unwrap_or_default(); let query_version_field_combined: Vec = version_fields .and_then(|x| serde_json::from_value(x).ok()) .unwrap_or_default(); let query_loader_field_enum_values: Vec = loader_field_enum_values .and_then(|x| serde_json::from_value(x).ok()) .unwrap_or_default(); let version_id = VersionId(version_id); query_loader_fields .into_iter() .filter_map(|q| { let loader_field_type = match LoaderFieldType::build(&q.field_type, q.enum_type) { Some(lft) => lft, None => return None, }; let loader_field = LoaderField { id: LoaderFieldId(q.lf_id), field: q.field.clone(), field_type: loader_field_type, optional: q.optional, min_val: q.min_val, max_val: q.max_val, }; let values = query_version_field_combined .iter() .filter_map(|qvf| { if qvf.field_id == q.lf_id { let lfev = query_loader_field_enum_values .iter() .find(|x| Some(x.id) == qvf.enum_value); Some(QueryVersionField { version_id, field_id: LoaderFieldId(qvf.field_id), int_value: qvf.int_value, enum_value: lfev.map(|lfev| LoaderFieldEnumValue { id: LoaderFieldEnumValueId(lfev.id), enum_id: LoaderFieldEnumId(lfev.enum_id), value: lfev.value.clone(), ordering: lfev.ordering, created: lfev.created, metadata: lfev.metadata.clone().unwrap_or_default(), }), string_value: qvf.string_value.clone(), }) } else { None } }) .collect::>(); VersionField::build(loader_field, version_id, values).ok() }) .collect() } pub fn build( loader_field: LoaderField, version_id: VersionId, query_version_fields: Vec, ) -> Result { let value = VersionFieldValue::build(&loader_field.field_type, query_version_fields)?; Ok(VersionField { version_id, field_id: loader_field.id, field_name: loader_field.field, value, }) } } impl VersionFieldValue { // Build from user-submitted JSON data // value is the attempted value of the field, which will be tried to parse to the correct type // enum_array is the list of valid enum variants for the field, if it is an enum (see LoaderFieldEnumValue::list_many_loader_fields) pub fn parse( loader_field: &LoaderField, value: serde_json::Value, enum_array: Vec, ) -> Result { let field_name = &loader_field.field; let field_type = &loader_field.field_type; let error_value = value.clone(); let incorrect_type_error = |field_type: &str| { format!( "Provided value '{v}' for {field_name} could not be parsed to {field_type} ", v = serde_json::to_string(&error_value).unwrap_or_default() ) }; Ok(match field_type { LoaderFieldType::Integer => VersionFieldValue::Integer( serde_json::from_value(value).map_err(|_| incorrect_type_error("integer"))?, ), LoaderFieldType::Text => VersionFieldValue::Text( value .as_str() .ok_or_else(|| incorrect_type_error("string"))? .to_string(), ), LoaderFieldType::Boolean => VersionFieldValue::Boolean( value .as_bool() .ok_or_else(|| incorrect_type_error("boolean"))?, ), LoaderFieldType::ArrayInteger => VersionFieldValue::ArrayInteger({ let array_values: Vec = serde_json::from_value(value) .map_err(|_| incorrect_type_error("array of integers"))?; array_values.into_iter().collect() }), LoaderFieldType::ArrayText => VersionFieldValue::ArrayText({ let array_values: Vec = serde_json::from_value(value) .map_err(|_| incorrect_type_error("array of strings"))?; array_values.into_iter().collect() }), LoaderFieldType::ArrayBoolean => VersionFieldValue::ArrayBoolean({ let array_values: Vec = serde_json::from_value(value) .map_err(|_| incorrect_type_error("array of booleans"))?; array_values.into_iter().map(|v| v != 0).collect() }), LoaderFieldType::Enum(id) => VersionFieldValue::Enum(*id, { let enum_value = value.as_str().ok_or_else(|| incorrect_type_error("enum"))?; if let Some(ev) = enum_array.into_iter().find(|v| v.value == enum_value) { ev } else { return Err(format!( "Provided value '{enum_value}' is not a valid variant for {field_name}" )); } }), LoaderFieldType::ArrayEnum(id) => VersionFieldValue::ArrayEnum(*id, { let array_values: Vec = serde_json::from_value(value) .map_err(|_| incorrect_type_error("array of enums"))?; let mut enum_values = vec![]; for av in array_values { if let Some(ev) = enum_array.iter().find(|v| v.value == av) { enum_values.push(ev.clone()); } else { return Err(format!( "Provided value '{av}' is not a valid variant for {field_name}" )); } } enum_values }), }) } // Build from internal query data // This encapsulates reundant behavior in db querie -> object conversions pub fn build( field_type: &LoaderFieldType, qvfs: Vec, ) -> Result { let field_name = field_type.to_str(); let get_first = |qvfs: Vec| -> Result { if qvfs.len() > 1 { return Err(DatabaseError::SchemaError(format!( "Multiple fields for field {}", field_name ))); } qvfs.into_iter().next().ok_or_else(|| { DatabaseError::SchemaError(format!("No version fields for field {}", field_name)) }) }; let did_not_exist_error = |field_name: &str, desired_field: &str| { DatabaseError::SchemaError(format!( "Field name {} for field {} in does not exist", desired_field, field_name )) }; Ok(match field_type { LoaderFieldType::Integer => VersionFieldValue::Integer( get_first(qvfs)? .int_value .ok_or(did_not_exist_error(field_name, "int_value"))?, ), LoaderFieldType::Text => VersionFieldValue::Text( get_first(qvfs)? .string_value .ok_or(did_not_exist_error(field_name, "string_value"))?, ), LoaderFieldType::Boolean => VersionFieldValue::Boolean( get_first(qvfs)? .int_value .ok_or(did_not_exist_error(field_name, "int_value"))? != 0, ), LoaderFieldType::ArrayInteger => VersionFieldValue::ArrayInteger( qvfs.into_iter() .map(|qvf| { qvf.int_value .ok_or(did_not_exist_error(field_name, "int_value")) }) .collect::>()?, ), LoaderFieldType::ArrayText => VersionFieldValue::ArrayText( qvfs.into_iter() .map(|qvf| { qvf.string_value .ok_or(did_not_exist_error(field_name, "string_value")) }) .collect::>()?, ), LoaderFieldType::ArrayBoolean => VersionFieldValue::ArrayBoolean( qvfs.into_iter() .map(|qvf| { Ok::( qvf.int_value .ok_or(did_not_exist_error(field_name, "int_value"))? != 0, ) }) .collect::>()?, ), LoaderFieldType::Enum(id) => VersionFieldValue::Enum( *id, get_first(qvfs)? .enum_value .ok_or(did_not_exist_error(field_name, "enum_value"))?, ), LoaderFieldType::ArrayEnum(id) => VersionFieldValue::ArrayEnum( *id, qvfs.into_iter() .map(|qvf| { qvf.enum_value .ok_or(did_not_exist_error(field_name, "enum_value")) }) .collect::>()?, ), }) } // Serialize to internal value, such as for converting to user-facing JSON pub fn serialize_internal(&self) -> serde_json::Value { match self { VersionFieldValue::Integer(i) => serde_json::Value::Number((*i).into()), VersionFieldValue::Text(s) => serde_json::Value::String(s.clone()), VersionFieldValue::Boolean(b) => serde_json::Value::Bool(*b), VersionFieldValue::ArrayInteger(v) => serde_json::Value::Array( v.iter() .map(|i| serde_json::Value::Number((*i).into())) .collect(), ), VersionFieldValue::ArrayText(v) => serde_json::Value::Array( v.iter() .map(|s| serde_json::Value::String(s.clone())) .collect(), ), VersionFieldValue::ArrayBoolean(v) => { serde_json::Value::Array(v.iter().map(|b| serde_json::Value::Bool(*b)).collect()) } VersionFieldValue::Enum(_, v) => serde_json::Value::String(v.value.clone()), VersionFieldValue::ArrayEnum(_, v) => serde_json::Value::Array( v.iter() .map(|v| serde_json::Value::String(v.value.clone())) .collect(), ), } } // For conversion to an interanl string(s), such as for search facets, filtering, or direct hardcoding // No matter the type, it will be converted to a Vec, whre the non-array types will have a single element pub fn as_strings(&self) -> Vec { match self { VersionFieldValue::Integer(i) => vec![i.to_string()], VersionFieldValue::Text(s) => vec![s.clone()], VersionFieldValue::Boolean(b) => vec![b.to_string()], VersionFieldValue::ArrayInteger(v) => v.iter().map(|i| i.to_string()).collect(), VersionFieldValue::ArrayText(v) => v.clone(), VersionFieldValue::ArrayBoolean(v) => v.iter().map(|b| b.to_string()).collect(), VersionFieldValue::Enum(_, v) => vec![v.value.clone()], VersionFieldValue::ArrayEnum(_, v) => v.iter().map(|v| v.value.clone()).collect(), } } pub fn contains_json_value(&self, value: &serde_json::Value) -> bool { match self { VersionFieldValue::Integer(i) => value.as_i64() == Some(*i as i64), VersionFieldValue::Text(s) => value.as_str() == Some(s), VersionFieldValue::Boolean(b) => value.as_bool() == Some(*b), VersionFieldValue::ArrayInteger(v) => value .as_i64() .map(|i| v.contains(&(i as i32))) .unwrap_or(false), VersionFieldValue::ArrayText(v) => value .as_str() .map(|s| v.contains(&s.to_string())) .unwrap_or(false), VersionFieldValue::ArrayBoolean(v) => { value.as_bool().map(|b| v.contains(&b)).unwrap_or(false) } VersionFieldValue::Enum(_, v) => value.as_str() == Some(&v.value), VersionFieldValue::ArrayEnum(_, v) => value .as_str() .map(|s| v.iter().any(|v| v.value == s)) .unwrap_or(false), } } }