From 2308ac1a04bfdfce824dd84feb4239ee032f8208 Mon Sep 17 00:00:00 2001 From: aecsocket <43144841+aecsocket@users.noreply.github.com> Date: Sat, 1 Aug 2026 05:02:37 +0100 Subject: [PATCH] inline loader enum metadata fields --- .../database/models/legacy_loader_fields.rs | 12 +-- .../src/database/models/loader_fields.rs | 96 ++++++++++--------- .../src/database/models/product_item.rs | 2 +- .../src/database/models/project_item.rs | 11 ++- .../src/database/models/version_item.rs | 10 +- apps/labrinth/src/database/redis.rs | 89 ++++++++--------- apps/labrinth/src/models/exp/project.rs | 3 +- apps/labrinth/src/routes/v2/tags.rs | 13 +-- apps/labrinth/src/search/indexing.rs | 10 +- apps/labrinth/tests/loader_fields.rs | 4 +- 10 files changed, 117 insertions(+), 133 deletions(-) diff --git a/apps/labrinth/src/database/models/legacy_loader_fields.rs b/apps/labrinth/src/database/models/legacy_loader_fields.rs index 4449068f50..f24291c25e 100644 --- a/apps/labrinth/src/database/models/legacy_loader_fields.rs +++ b/apps/labrinth/src/database/models/legacy_loader_fields.rs @@ -117,16 +117,8 @@ impl MinecraftGameVersion { id: loader_field_enum_value.id, version: loader_field_enum_value.value, created: loader_field_enum_value.created, - type_: loader_field_enum_value - .metadata - .as_ref() - .map(|metadata| metadata.type_.clone()) - .unwrap_or_default(), - major: loader_field_enum_value - .metadata - .as_ref() - .map(|metadata| metadata.major) - .unwrap_or_default(), + type_: loader_field_enum_value.ty.unwrap_or_default(), + major: loader_field_enum_value.major.unwrap_or_default(), } } } diff --git a/apps/labrinth/src/database/models/loader_fields.rs b/apps/labrinth/src/database/models/loader_fields.rs index 706be3cfc4..a54f60c996 100644 --- a/apps/labrinth/src/database/models/loader_fields.rs +++ b/apps/labrinth/src/database/models/loader_fields.rs @@ -278,13 +278,6 @@ pub struct LoaderFieldEnum { pub hidable: bool, } -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -pub struct LoaderFieldEnumValueMetadata { - #[serde(rename = "type")] - pub type_: String, - pub major: bool, -} - #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct LoaderFieldEnumValue { pub id: LoaderFieldEnumValueId, @@ -292,8 +285,9 @@ pub struct LoaderFieldEnumValue { pub value: String, pub ordering: Option, pub created: DateTime, - #[serde(flatten)] - pub metadata: Option, + #[serde(rename = "type")] + pub ty: Option, + pub major: Option, } impl std::hash::Hash for LoaderFieldEnumValue { @@ -372,7 +366,8 @@ pub struct QueryLoaderFieldEnumValue { pub value: String, pub ordering: Option, pub created: DateTime, - pub metadata: Option, + pub ty: Option, + pub major: Option, } impl LoaderField { @@ -627,44 +622,50 @@ impl LoaderFieldEnumValue { where E: crate::database::Executor<'a, Database = sqlx::Postgres>, { - let val = redis.get_cached_keys_raw( - LOADER_FIELD_ENUM_VALUES_NAMESPACE, - &loader_field_enum_ids.iter().map(|x| x.0).collect::>(), - |loader_field_enum_ids| async move { - let values = sqlx::query!( - r#" + let val = redis + .get_cached_keys_raw( + LOADER_FIELD_ENUM_VALUES_NAMESPACE, + &loader_field_enum_ids + .iter() + .map(|x| x.0) + .collect::>(), + |loader_field_enum_ids| async move { + let values = sqlx::query!( + r#" SELECT id, enum_id, value, ordering, - metadata AS "metadata?: sqlx::types::Json", + metadata->>'type' AS "ty?", + (metadata->>'major')::boolean AS "major?", created FROM loader_field_enum_values WHERE enum_id = ANY($1) ORDER BY enum_id, ordering, created DESC "#, - &loader_field_enum_ids - ) + &loader_field_enum_ids + ) .fetch(exec) - .try_fold(DashMap::new(), |acc: DashMap>, c| { - let value = LoaderFieldEnumValue { - id: LoaderFieldEnumValueId(c.id), - enum_id: LoaderFieldEnumId(c.enum_id), - value: c.value, - ordering: c.ordering, - created: c.created, - metadata: c.metadata.map(|metadata| metadata.0), - }; + .try_fold( + DashMap::new(), + |acc: DashMap>, c| { + let value = LoaderFieldEnumValue { + id: LoaderFieldEnumValueId(c.id), + enum_id: LoaderFieldEnumId(c.enum_id), + value: c.value, + ordering: c.ordering, + created: c.created, + ty: c.ty, + major: c.major, + }; - acc.entry(c.enum_id) - .or_default() - .push(value); + acc.entry(c.enum_id).or_default().push(value); - async move { - Ok(acc) - } - }) + async move { Ok(acc) } + }, + ) .await?; - Ok::<_, DatabaseError>(values) - }, - ).await?; + Ok::<_, DatabaseError>(values) + }, + ) + .await?; Ok(val .into_iter() @@ -687,12 +688,13 @@ impl LoaderFieldEnumValue { .into_iter() .filter(|x| { filter.iter().all(|(key, value)| match key.as_str() { - "type" => x.metadata.as_ref().is_some_and(|metadata| { - value.as_str() == Some(metadata.type_.as_str()) - }), - "major" => x.metadata.as_ref().is_some_and(|metadata| { - value.as_bool() == Some(metadata.major) - }), + "type" => { + x.ty.as_deref() + .is_some_and(|type_| value.as_str() == Some(type_)) + } + "major" => x + .major + .is_some_and(|major| value.as_bool() == Some(major)), _ => false, }) }) @@ -1187,7 +1189,8 @@ impl VersionFieldValue { value: lfev.value.clone(), ordering: lfev.ordering, created: lfev.created, - metadata: lfev.metadata.clone(), + ty: lfev.ty.clone(), + major: lfev.major, } }), )) @@ -1263,7 +1266,8 @@ impl VersionFieldValue { value: lfev.value.clone(), ordering: lfev.ordering, created: lfev.created, - metadata: lfev.metadata.clone(), + ty: lfev.ty.clone(), + major: lfev.major, }) }) .collect::>()?, diff --git a/apps/labrinth/src/database/models/product_item.rs b/apps/labrinth/src/database/models/product_item.rs index 266eafa773..c239472a41 100644 --- a/apps/labrinth/src/database/models/product_item.rs +++ b/apps/labrinth/src/database/models/product_item.rs @@ -136,7 +136,7 @@ pub struct QueryProductWithPrices { pub id: DBProductId, pub metadata: ProductMetadata, pub unitary: bool, - #[serde(skip_serializing_if = "Option::is_none", default)] + #[serde(default)] pub name: Option, pub prices: Vec, } diff --git a/apps/labrinth/src/database/models/project_item.rs b/apps/labrinth/src/database/models/project_item.rs index b756743d79..e106afb58c 100644 --- a/apps/labrinth/src/database/models/project_item.rs +++ b/apps/labrinth/src/database/models/project_item.rs @@ -1,6 +1,6 @@ use super::loader_fields::{ - LoaderFieldEnumValueMetadata, QueryLoaderField, QueryLoaderFieldEnumValue, - QueryVersionField, VersionField, + QueryLoaderField, QueryLoaderFieldEnumValue, QueryVersionField, + VersionField, }; use super::{DBUser, ids::*}; use crate::database::models::DatabaseError; @@ -659,7 +659,8 @@ impl DBProject { let loader_field_enum_values: Vec = sqlx::query!( r#" SELECT DISTINCT id, enum_id, value, ordering, created, - metadata AS "metadata?: sqlx::types::Json" + metadata->>'type' AS "ty?", + (metadata->>'major')::boolean AS "major?" FROM loader_field_enum_values lfev WHERE id = ANY($1) ORDER BY enum_id, ordering, created DESC @@ -676,7 +677,8 @@ impl DBProject { value: m.value, ordering: m.ordering, created: m.created, - metadata: m.metadata.map(|metadata| metadata.0), + ty: m.ty, + major: m.major, }) .try_collect() .await?; @@ -1054,6 +1056,5 @@ pub struct ProjectQueryResult { pub gallery_items: Vec, pub thread_id: DBThreadId, pub aggregate_version_fields: Vec, - #[serde(flatten)] pub components: exp::ProjectQuery, } diff --git a/apps/labrinth/src/database/models/version_item.rs b/apps/labrinth/src/database/models/version_item.rs index 6d587d8cad..24838e0ff1 100644 --- a/apps/labrinth/src/database/models/version_item.rs +++ b/apps/labrinth/src/database/models/version_item.rs @@ -3,8 +3,7 @@ use super::ids::*; use super::loader_fields::VersionField; use crate::database::PgTransaction; use crate::database::models::loader_fields::{ - LoaderFieldEnumValueMetadata, QueryLoaderField, QueryLoaderFieldEnumValue, - QueryVersionField, + QueryLoaderField, QueryLoaderFieldEnumValue, QueryVersionField, }; use crate::file_hosting::FileHost; use crate::models::exp; @@ -707,7 +706,8 @@ impl DBVersion { let loader_field_enum_values: Vec = sqlx::query!( r#" SELECT DISTINCT id, enum_id, value, ordering, created, - metadata AS "metadata?: sqlx::types::Json" + metadata->>'type' AS "ty?", + (metadata->>'major')::boolean AS "major?" FROM loader_field_enum_values lfev WHERE id = ANY($1) ORDER BY enum_id, ordering, created ASC @@ -724,7 +724,8 @@ impl DBVersion { value: m.value, ordering: m.ordering, created: m.created, - metadata: m.metadata.map(|metadata| metadata.0), + ty: m.ty, + major: m.major, }) .try_collect() .await?; @@ -1092,7 +1093,6 @@ pub struct VersionQueryResult { pub project_types: Vec, pub games: Vec, pub dependencies: Vec, - #[serde(flatten)] pub components: exp::VersionQuery, } diff --git a/apps/labrinth/src/database/redis.rs b/apps/labrinth/src/database/redis.rs index 352a6f130b..4a6c36a4b1 100644 --- a/apps/labrinth/src/database/redis.rs +++ b/apps/labrinth/src/database/redis.rs @@ -69,18 +69,15 @@ mod tests { use crate::database::models::flow_item::DBFlow; use crate::database::models::ids::{ - DBNotificationId, DBProductId, DBProductPriceId, DBProjectId, DBTeamId, - DBThreadId, DBUserId, DBVersionId, LoaderFieldEnumId, - LoaderFieldEnumValueId, LoaderFieldId, LoaderId, + DBNotificationId, DBProjectId, DBTeamId, DBThreadId, DBUserId, + DBVersionId, LoaderFieldEnumId, LoaderFieldEnumValueId, LoaderFieldId, + LoaderId, }; use crate::database::models::loader_fields::{ - Loader, LoaderFieldEnumValue, LoaderFieldEnumValueMetadata, - LoaderMetadata, VersionField, VersionFieldValue, + Loader, LoaderFieldEnumValue, LoaderMetadata, VersionField, + VersionFieldValue, }; use crate::database::models::notification_item::DBNotification; - use crate::database::models::product_item::{ - DBProductPrice, QueryProductWithPrices, - }; use crate::database::models::project_item::{ DBProject, ProjectQueryResult, }; @@ -104,20 +101,15 @@ mod tests { postcard::from_bytes(&serialized).expect("deserializing with postcard") } - fn loader_field_enum_value( - type_: &str, - major: bool, - ) -> LoaderFieldEnumValue { + fn loader_field_enum_value(ty: &str, major: bool) -> LoaderFieldEnumValue { LoaderFieldEnumValue { id: LoaderFieldEnumValueId(1), enum_id: LoaderFieldEnumId(2), value: "1.21.8".to_string(), ordering: None, created: Utc::now(), - metadata: Some(LoaderFieldEnumValueMetadata { - type_: type_.to_string(), - major, - }), + ty: Some(ty.to_string()), + major: Some(major), } } @@ -211,31 +203,48 @@ mod tests { } #[test] - fn loader_field_enum_value_metadata_round_trips_with_postcard() { + fn loader_field_enum_values_round_trip_with_postcard() { let metadata_values = [ - ("snapshot", false), - ("alpha", false), - ("beta", true), - ("release", true), - ("beta", false), - ("release", false), + (None, None), + (Some("snapshot"), Some(false)), + (Some("alpha"), Some(false)), + (Some("beta"), Some(true)), + (Some("release"), Some(true)), + (Some("beta"), Some(false)), + (Some("release"), Some(false)), ]; - assert_eq!( - postcard_round_trip(&None::), - None - ); - - for (type_, major) in metadata_values { - let metadata = Some(LoaderFieldEnumValueMetadata { - type_: type_.to_string(), + for (ty, major) in metadata_values { + let enum_value = LoaderFieldEnumValue { + id: LoaderFieldEnumValueId(1), + enum_id: LoaderFieldEnumId(2), + value: "1.21.8".to_string(), + ordering: None, + created: Utc::now(), + ty: ty.map(str::to_string), major, - }); + }; - assert_eq!(postcard_round_trip(&metadata), metadata); + assert_eq!(postcard_round_trip(&enum_value), enum_value); } } + #[test] + fn loader_field_enum_value_keeps_flattened_json_layout() { + let enum_value = loader_field_enum_value("release", true); + let json = serde_json::to_value(&enum_value) + .expect("serializing loader field enum value as JSON"); + + assert_eq!(json.get("type"), Some(&serde_json::json!("release"))); + assert_eq!(json.get("major"), Some(&serde_json::json!(true))); + assert!(json.get("metadata").is_none()); + assert_eq!( + serde_json::from_value::(json) + .expect("deserializing loader field enum value from JSON"), + enum_value + ); + } + #[test] fn non_enum_version_fields_round_trip_with_postcard() { let values = [ @@ -305,20 +314,6 @@ mod tests { postcard_round_trip(&exp::ProjectSerial::default()); postcard_round_trip(&exp::ProjectQuery::default()); postcard_round_trip(&db_project()); - - let product = QueryProductWithPrices { - id: DBProductId(1), - metadata: ProductMetadata::Midas, - unitary: false, - name: None, - prices: vec![DBProductPrice { - id: DBProductPriceId(2), - product_id: DBProductId(1), - prices: Price::OneTime { price: 500 }, - currency_code: "USD".to_string(), - }], - }; - postcard_round_trip(&product); } #[test] diff --git a/apps/labrinth/src/models/exp/project.rs b/apps/labrinth/src/models/exp/project.rs index 46694b2ec8..4dcc06fcab 100644 --- a/apps/labrinth/src/models/exp/project.rs +++ b/apps/labrinth/src/models/exp/project.rs @@ -54,7 +54,7 @@ macro_rules! define_project_components { pub struct ProjectSerial { $( #[validate(nested)] - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(default)] pub $field_name: Option<$ty>, )* } @@ -114,7 +114,6 @@ macro_rules! define_project_components { #[derive(Debug, Clone, Default, Serialize, Deserialize, utoipa::ToSchema)] pub struct ProjectQuery { $( - #[serde(skip_serializing_if = "Option::is_none")] pub $field_name: Option>, )* } diff --git a/apps/labrinth/src/routes/v2/tags.rs b/apps/labrinth/src/routes/v2/tags.rs index d589e02d1a..56a86ca46d 100644 --- a/apps/labrinth/src/routes/v2/tags.rs +++ b/apps/labrinth/src/routes/v2/tags.rs @@ -210,18 +210,9 @@ pub async fn game_version_list( .into_iter() .map(|f| GameVersionQueryData { version: f.value, - version_type: f - .metadata - .as_ref() - .map(|metadata| metadata.type_.as_str()) - .unwrap_or_default() - .to_string(), + version_type: f.ty.unwrap_or_default(), date: f.created, - major: f - .metadata - .as_ref() - .map(|metadata| metadata.major) - .unwrap_or_default(), + major: f.major.unwrap_or_default(), }) .collect::>(); HttpResponse::Ok().json(fields) diff --git a/apps/labrinth/src/search/indexing.rs b/apps/labrinth/src/search/indexing.rs index 7e7347395c..26ca2ba53b 100644 --- a/apps/labrinth/src/search/indexing.rs +++ b/apps/labrinth/src/search/indexing.rs @@ -11,8 +11,8 @@ use tracing::{info, warn}; use crate::database::PgPool; use crate::database::models::loader_fields::{ - LoaderFieldEnumValueMetadata, QueryLoaderField, QueryLoaderFieldEnumValue, - QueryVersionField, VersionField, + QueryLoaderField, QueryLoaderFieldEnumValue, QueryVersionField, + VersionField, }; use crate::database::models::{ DBOrganizationId, DBProjectId, DBUserId, DBVersionId, LoaderFieldEnumId, @@ -396,7 +396,8 @@ async fn build_search_documents( sqlx::query!( r#" SELECT DISTINCT id, enum_id, value, ordering, created, - metadata AS "metadata?: sqlx::types::Json" + metadata->>'type' AS "ty?", + (metadata->>'major')::boolean AS "major?" FROM loader_field_enum_values lfev ORDER BY enum_id, ordering, created DESC "# @@ -408,7 +409,8 @@ async fn build_search_documents( value: m.value, ordering: m.ordering, created: m.created, - metadata: m.metadata.map(|metadata| metadata.0), + ty: m.ty, + major: m.major, }) .try_collect() .await?; diff --git a/apps/labrinth/tests/loader_fields.rs b/apps/labrinth/tests/loader_fields.rs index 4ca24116f5..750fd18e91 100644 --- a/apps/labrinth/tests/loader_fields.rs +++ b/apps/labrinth/tests/loader_fields.rs @@ -574,7 +574,7 @@ async fn minecraft_game_version_update() { // A couple specific checks- in the dummy data, all game versions are marked as major=false except 1.20.5 let name_to_major = game_versions .iter() - .map(|x| (x.value.clone(), x.metadata.as_ref().unwrap().major)) + .map(|x| (x.value.clone(), x.major.unwrap())) .collect::>(); for (name, major) in name_to_major { if name == "1.20.5" { @@ -607,7 +607,7 @@ async fn minecraft_game_version_update() { let name_to_major = game_versions .iter() - .map(|x| (x.value.clone(), x.metadata.as_ref().unwrap().major)) + .map(|x| (x.value.clone(), x.major.unwrap())) .collect::>(); // Confirm that the new version is there assert!(name_to_major.contains_key("1.20.6"));