From 668c5fef51a7b9c4145d0ad1052a543808d706d6 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 21:46:44 -0700 Subject: [PATCH 1/9] validator: move function call so get_model_info is infallible --- validation/src/check.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 1a59124..c6c1bbd 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -127,10 +127,7 @@ pub struct ModelInfo<'a>{ counts:Counts, } -pub fn get_model_info(dom:&rbx_dom_weak::WeakDom)->Result{ - // extract the root instance, otherwise immediately return - let model_instance=get_root_instance(&dom)?; - +pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_dom_weak::Instance)->ModelInfo<'a>{ // extract model info let map_info=get_mapinfo(&dom,model_instance); @@ -161,12 +158,12 @@ pub fn get_model_info(dom:&rbx_dom_weak::WeakDom)->Result Date: Fri, 11 Apr 2025 21:59:37 -0700 Subject: [PATCH 2/9] validator: remove newline --- validation/src/check_mapfix.rs | 6 ++---- validation/src/check_submission.rs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/validation/src/check_mapfix.rs b/validation/src/check_mapfix.rs index 2187fe3..f5aec5b 100644 --- a/validation/src/check_mapfix.rs +++ b/validation/src/check_mapfix.rs @@ -24,8 +24,7 @@ impl crate::message_handler::MessageHandler{ Ok(CheckReportAndVersion{status,version})=>{ match status{ // update the mapfix model status to submitted - Ok(map_info)=> - self.api.action_mapfix_submitted( + Ok(map_info)=>self.api.action_mapfix_submitted( submissions_api::types::ActionMapfixSubmittedRequest{ MapfixID:mapfix_id, ModelVersion:version, @@ -35,8 +34,7 @@ impl crate::message_handler::MessageHandler{ } ).await.map_err(Error::ApiActionMapfixCheck)?, // update the mapfix model status to request changes - Err(report)=> - self.api.action_mapfix_request_changes( + Err(report)=>self.api.action_mapfix_request_changes( submissions_api::types::ActionMapfixRequestChangesRequest{ MapfixID:mapfix_id, ErrorMessage:report, diff --git a/validation/src/check_submission.rs b/validation/src/check_submission.rs index d0bd80c..1440a10 100644 --- a/validation/src/check_submission.rs +++ b/validation/src/check_submission.rs @@ -24,8 +24,7 @@ impl crate::message_handler::MessageHandler{ Ok(CheckReportAndVersion{status,version})=>{ match status{ // update the submission model status to submitted - Ok(map_info)=> - self.api.action_submission_submitted( + Ok(map_info)=>self.api.action_submission_submitted( submissions_api::types::ActionSubmissionSubmittedRequest{ SubmissionID:submission_id, ModelVersion:version, @@ -35,8 +34,7 @@ impl crate::message_handler::MessageHandler{ } ).await.map_err(Error::ApiActionSubmissionCheck)?, // update the submission model status to request changes - Err(report)=> - self.api.action_submission_request_changes( + Err(report)=>self.api.action_submission_request_changes( submissions_api::types::ActionSubmissionRequestChangesRequest{ SubmissionID:submission_id, ErrorMessage:report, -- 2.49.1 From aa513a79736205c2d0c52d3168edab0a7c5622d1 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 22:20:59 -0700 Subject: [PATCH 3/9] validator: code tweaks --- validation/src/check.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index c6c1bbd..a4a1208 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -193,11 +193,21 @@ impl<'a,Str:std::fmt::Display> std::fmt::Display for StringCheckContext<'a,Str>{ // check if a string is empty pub struct StringEmpty; pub struct StringEmptyCheck(Result); +impl StringEmptyCheck{ + fn map(self,f:impl Fn(Context)->T)->StringEmptyCheck{ + StringEmptyCheck(self.0.map(f)) + } +} impl std::fmt::Display for StringEmpty{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"Empty string") } } +impl<'a> StringEmptyCheck<&'a str>{ + fn new(value:&'a str)->StringEmptyCheck<&'a str>{ + StringEmptyCheck(value.is_empty().then_some(value).ok_or(StringEmpty)) + } +} // check for duplicate objects pub struct DuplicateCheckContext(HashMap); @@ -302,24 +312,14 @@ impl<'a> ModelInfo<'a>{ // check display name let display_name=self.map_info.display_name.map(|display_name|{ - if display_name.is_empty(){ - StringEmptyCheck(Err(StringEmpty)) - }else{ - StringEmptyCheck(Ok(StringCheckContext{ - observed:display_name, - expected:display_name.to_title_case(), - }.check(display_name))) - } + StringEmptyCheck::new(display_name).map(|display_name|StringCheckContext{ + observed:display_name, + expected:display_name.to_title_case(), + }.check(display_name)) }); // check Creator - let creator=self.map_info.creator.map(|creator|{ - if creator.is_empty(){ - StringEmptyCheck(Err(StringEmpty)) - }else{ - StringEmptyCheck(Ok(creator)) - } - }); + let creator=self.map_info.creator.map(StringEmptyCheck::new); // check GameID let game_id=self.map_info.game_id; -- 2.49.1 From 50e3fb283ce16e31ff1495e32bfac28f74573f2f Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 22:25:49 -0700 Subject: [PATCH 4/9] validator: comment ModelInfo::check --- validation/src/check.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index a4a1208..1637521 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -300,17 +300,19 @@ pub struct MapCheck<'a>{ impl<'a> ModelInfo<'a>{ fn check(self)->MapCheck<'a>{ + // check class is exactly "Model" let model_class=StringCheckContext{ observed:self.model_class, expected:"Model", }.check(()); + // check model name is snake case let model_name=StringCheckContext{ observed:self.model_name, expected:self.model_name.to_snake_case(), }.check(()); - // check display name + // check display name is not empty and has title case let display_name=self.map_info.display_name.map(|display_name|{ StringEmptyCheck::new(display_name).map(|display_name|StringCheckContext{ observed:display_name, @@ -318,10 +320,10 @@ impl<'a> ModelInfo<'a>{ }.check(display_name)) }); - // check Creator + // check Creator is not empty let creator=self.map_info.creator.map(StringEmptyCheck::new); - // check GameID + // check GameID (model name was prefixed with bhop_ surf_ etc) let game_id=self.map_info.game_id; // MapStart must exist -- 2.49.1 From ea58fcedc9cfaa27b1f95a4a57eaa9f14842897b Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 22:30:55 -0700 Subject: [PATCH 5/9] validator: save some loc with default --- validation/src/check.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 1637521..f96e5c1 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -102,23 +102,13 @@ impl SpawnID{ #[derive(Debug,Hash,Eq,PartialEq)] struct WormholeOutID(u64); +#[derive(Default)] struct Counts{ mode_start_counts:HashMap, mode_finish_counts:HashMap, spawn_counts:HashMap, wormhole_out_counts:HashMap, } -impl Counts{ - fn new()->Self{ - Self{ - mode_start_counts:HashMap::new(), - mode_finish_counts:HashMap::new(), - spawn_counts:HashMap::new(), - wormhole_out_counts:HashMap::new(), - } - } -} - pub struct ModelInfo<'a>{ model_class:&'a str, @@ -131,8 +121,8 @@ pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_d // extract model info let map_info=get_mapinfo(&dom,model_instance); - // count objects - let mut counts=Counts::new(); + // count objects (default count is 0) + let mut counts=Counts::default(); for instance in dom.descendants_of(model_instance.referent()){ if class_is_a(instance.class.as_str(),"BasePart"){ // Zones -- 2.49.1 From c63997d1612e5c4a78ce59e6a7bc955fab04f0c2 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 22:38:21 -0700 Subject: [PATCH 6/9] validator: implement dangling anticheat zone check --- validation/src/check.rs | 109 +++++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 29 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index f96e5c1..d001dd3 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -43,27 +43,28 @@ impl From for CheckRequest{ enum Zone{ Start(ModeID), Finish(ModeID), + Anticheat(ModeID), } #[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)] struct ModeID(u64); +macro_rules! write_zone{ + ($fname:ident,$zone:expr)=>{ + fn $fname(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + match self{ + ModeID(0)=>write!(f,concat!("Map",$zone)), + ModeID(1)=>write!(f,concat!("Bonus",$zone)), + ModeID(other)=>write!(f,concat!("Bonus{}",$zone),other), + } + } + }; +} impl ModeID{ const MAIN:Self=Self(0); const BONUS:Self=Self(1); - fn write_start_zone(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - match self{ - ModeID(0)=>write!(f,"MapStart"), - ModeID(1)=>write!(f,"BonusStart"), - ModeID(other)=>write!(f,"Bonus{other}Start"), - } - } - fn write_finish_zone(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - match self{ - ModeID(0)=>write!(f,"MapFinish"), - ModeID(1)=>write!(f,"BonusFinish"), - ModeID(other)=>write!(f,"Bonus{other}Finish"), - } - } + write_zone!(write_start_zone,"Start"); + write_zone!(write_finish_zone,"Finish"); + write_zone!(write_anticheat_zone,"Anticheat"); } #[allow(dead_code)] pub enum ZoneParseError{ @@ -76,8 +77,10 @@ impl std::str::FromStr for Zone{ match s{ "MapStart"=>Ok(Self::Start(ModeID::MAIN)), "MapFinish"=>Ok(Self::Finish(ModeID::MAIN)), + "MapAnticheat"=>Ok(Self::Anticheat(ModeID::MAIN)), "BonusStart"=>Ok(Self::Start(ModeID::BONUS)), "BonusFinish"=>Ok(Self::Finish(ModeID::BONUS)), + "BonusAnticheat"=>Ok(Self::Anticheat(ModeID::BONUS)), other=>{ let bonus_start_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Start$|^BonusStart(\d+)$"); if let Some(captures)=bonus_start_pattern.captures(other){ @@ -87,6 +90,10 @@ impl std::str::FromStr for Zone{ if let Some(captures)=bonus_finish_pattern.captures(other){ return Ok(Self::Finish(ModeID(captures[1].parse().map_err(ZoneParseError::ParseInt)?))); } + let bonus_finish_pattern=lazy_regex::lazy_regex!(r"^Bonus(\d+)Anticheat$|^BonusAnticheat(\d+)$"); + if let Some(captures)=bonus_finish_pattern.captures(other){ + return Ok(Self::Anticheat(ModeID(captures[1].parse().map_err(ZoneParseError::ParseInt)?))); + } Err(ZoneParseError::NoCaptures) } } @@ -106,6 +113,7 @@ struct WormholeOutID(u64); struct Counts{ mode_start_counts:HashMap, mode_finish_counts:HashMap, + mode_anticheat_counts:HashMap, spawn_counts:HashMap, wormhole_out_counts:HashMap, } @@ -129,7 +137,8 @@ pub fn get_model_info<'a>(dom:&'a rbx_dom_weak::WeakDom,model_instance:&'a rbx_d match instance.name.parse(){ Ok(Zone::Start(mode_id))=>*counts.mode_start_counts.entry(mode_id).or_insert(0)+=1, Ok(Zone::Finish(mode_id))=>*counts.mode_finish_counts.entry(mode_id).or_insert(0)+=1, - _=>(), + Ok(Zone::Anticheat(mode_id))=>*counts.mode_anticheat_counts.entry(mode_id).or_insert(0)+=1, + Err(_)=>(), } // Spawns let spawn_pattern=lazy_regex::lazy_regex!(r"^Spawn(\d+)$"); @@ -217,12 +226,36 @@ impl DuplicateCheckContext{ } // check that there is at least one matching item for each item in a reference set, and no extra items -pub struct SetDifferenceCheckContext{ +pub struct SetDifferenceCheckContextAllowNone{ + extra:HashMap, +} +pub struct SetDifferenceCheckContextAtLeastOne{ extra:HashMap, missing:HashSet, } -pub struct SetDifferenceCheck(Result<(),SetDifferenceCheckContext>); -impl SetDifferenceCheckContext{ +pub struct SetDifferenceCheck(Result<(),Context>); +impl SetDifferenceCheckContextAllowNone{ + fn new(initial_set:HashMap)->Self{ + Self{ + extra:initial_set, + } + } +} +impl SetDifferenceCheckContextAllowNone{ + fn check(mut self,reference_set:&HashMap)->SetDifferenceCheck{ + // remove correct entries + for (id,_) in reference_set{ + self.extra.remove(id); + } + // if any entries remain, they are incorrect + if self.extra.is_empty(){ + SetDifferenceCheck(Ok(())) + }else{ + SetDifferenceCheck(Err(self)) + } + } +} +impl SetDifferenceCheckContextAtLeastOne{ fn new(initial_set:HashMap)->Self{ Self{ extra:initial_set, @@ -230,21 +263,20 @@ impl SetDifferenceCheckContext{ } } } -impl SetDifferenceCheckContext{ - fn check(self,reference_set:&HashMap)->SetDifferenceCheck{ - let Self{mut extra,mut missing}=self; +impl SetDifferenceCheckContextAtLeastOne{ + fn check(mut self,reference_set:&HashMap)->SetDifferenceCheck{ // remove correct entries for (id,_) in reference_set{ - if extra.remove(id).is_none(){ + if self.extra.remove(id).is_none(){ // the set did not contain a required item. This is a fail - missing.insert(*id); + self.missing.insert(*id); } } // if any entries remain, they are incorrect - if extra.is_empty()&&missing.is_empty(){ + if self.extra.is_empty()&&self.missing.is_empty(){ SetDifferenceCheck(Ok(())) }else{ - SetDifferenceCheck(Err(Self{extra,missing})) + SetDifferenceCheck(Err(self)) } } } @@ -278,8 +310,9 @@ pub struct MapCheck<'a>{ // No duplicate map starts (including bonuses) mode_start_counts:DuplicateCheck, // At least one finish zone for each start zone, and no finishes with no start - mode_finish_counts:SetDifferenceCheck, - // TODO: check for dangling MapAnticheat zones (no associated MapStart) + mode_finish_counts:SetDifferenceCheck>, + // check for dangling MapAnticheat zones (no associated MapStart) + mode_anticheat_counts:SetDifferenceCheck>, // Spawn1 must exist spawn1:Result<(),()>, // No duplicate Spawn# @@ -331,7 +364,12 @@ impl<'a> ModelInfo<'a>{ }; // check that at least one end zone exists for each start zone. - let mode_finish_counts=SetDifferenceCheckContext::new(self.counts.mode_finish_counts) + let mode_finish_counts=SetDifferenceCheckContextAtLeastOne::new(self.counts.mode_finish_counts) + .check(&self.counts.mode_start_counts); + + // check that there are no anticheat zones that have no corresponding start zone. + // modes are allowed to have 0 anticheat zones. + let mode_anticheat_counts=SetDifferenceCheckContextAllowNone::new(self.counts.mode_anticheat_counts) .check(&self.counts.mode_start_counts); // there must be exactly one start zone for every mode in the map. @@ -352,6 +390,7 @@ impl<'a> ModelInfo<'a>{ mapstart, mode_start_counts, mode_finish_counts, + mode_anticheat_counts, spawn1, spawn_counts, wormhole_out_counts, @@ -371,6 +410,7 @@ impl<'a> MapCheck<'a>{ mapstart:Ok(()), mode_start_counts:DuplicateCheck(Ok(())), mode_finish_counts:SetDifferenceCheck(Ok(())), + mode_anticheat_counts:SetDifferenceCheck(Ok(())), spawn1:Ok(()), spawn_counts:DuplicateCheck(Ok(())), wormhole_out_counts:DuplicateCheck(Ok(())), @@ -443,7 +483,8 @@ impl<'a> std::fmt::Display for MapCheck<'a>{ if !context.extra.is_empty(){ write!(f,"Extra finish zones with no matching start zone: ")?; comma_separated(f,context.extra.iter(),|f,(mode_id,_count)| - mode_id.write_finish_zone(f))?; + mode_id.write_finish_zone(f) + )?; writeln!(f,"")?; } // perhaps there are missing end zones (context.missing) @@ -455,6 +496,16 @@ impl<'a> std::fmt::Display for MapCheck<'a>{ writeln!(f,"")?; } } + if let SetDifferenceCheck(Err(context))=&self.mode_anticheat_counts{ + // perhaps there are extra end zones (context.extra) + if !context.extra.is_empty(){ + write!(f,"Extra anticheat zones with no matching start zone: ")?; + comma_separated(f,context.extra.iter(),|f,(mode_id,_count)| + mode_id.write_anticheat_zone(f) + )?; + writeln!(f,"")?; + } + } if let Err(())=&self.spawn1{ writeln!(f,"Model has no Spawn1")?; } -- 2.49.1 From 343a4011dd03077854dc2c299dfc32394c11ae52 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 23:06:10 -0700 Subject: [PATCH 7/9] validator: tweak write_zone macro --- validation/src/check.rs | 52 +++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index d001dd3..3a99b6f 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -48,23 +48,9 @@ enum Zone{ #[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)] struct ModeID(u64); -macro_rules! write_zone{ - ($fname:ident,$zone:expr)=>{ - fn $fname(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - match self{ - ModeID(0)=>write!(f,concat!("Map",$zone)), - ModeID(1)=>write!(f,concat!("Bonus",$zone)), - ModeID(other)=>write!(f,concat!("Bonus{}",$zone),other), - } - } - }; -} impl ModeID{ const MAIN:Self=Self(0); const BONUS:Self=Self(1); - write_zone!(write_start_zone,"Start"); - write_zone!(write_finish_zone,"Finish"); - write_zone!(write_anticheat_zone,"Anticheat"); } #[allow(dead_code)] pub enum ZoneParseError{ @@ -426,10 +412,11 @@ impl<'a> MapCheck<'a>{ } } -fn comma_separated(f:&mut std::fmt::Formatter<'_>,mut it:impl Iterator,custom_write:F)->std::fmt::Result -where - F:Fn(&mut std::fmt::Formatter<'_>,T)->std::fmt::Result, -{ +fn write_comma_separated( + f:&mut std::fmt::Formatter<'_>, + mut it:impl Iterator, + custom_write:impl Fn(&mut std::fmt::Formatter<'_>,T)->std::fmt::Result +)->std::fmt::Result{ if let Some(t)=it.next(){ custom_write(f,t)?; for t in it{ @@ -440,6 +427,15 @@ where Ok(()) } +macro_rules! write_zone{ + ($f:expr,$mode:expr,$zone:expr)=>{ + match $mode{ + ModeID(0)=>write!($f,concat!("Map",$zone)), + ModeID(1)=>write!($f,concat!("Bonus",$zone)), + ModeID(other)=>write!($f,concat!("Bonus{}",$zone),other), + } + }; +} impl<'a> std::fmt::Display for MapCheck<'a>{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ if let StringCheck(Err(context))=&self.model_class{ @@ -471,8 +467,8 @@ impl<'a> std::fmt::Display for MapCheck<'a>{ } if let DuplicateCheck(Err(DuplicateCheckContext(context)))=&self.mode_start_counts{ write!(f,"Duplicate start zones: ")?; - comma_separated(f,context.iter(),|f,(mode_id,count)|{ - mode_id.write_start_zone(f)?; + write_comma_separated(f,context.iter(),|f,(mode_id,count)|{ + write_zone!(f,mode_id,"Start")?; write!(f,"({count} duplicates)")?; Ok(()) })?; @@ -482,16 +478,16 @@ impl<'a> std::fmt::Display for MapCheck<'a>{ // perhaps there are extra end zones (context.extra) if !context.extra.is_empty(){ write!(f,"Extra finish zones with no matching start zone: ")?; - comma_separated(f,context.extra.iter(),|f,(mode_id,_count)| - mode_id.write_finish_zone(f) + write_comma_separated(f,context.extra.iter(),|f,(mode_id,_count)| + write_zone!(f,mode_id,"Finish") )?; writeln!(f,"")?; } // perhaps there are missing end zones (context.missing) if !context.missing.is_empty(){ write!(f,"Missing finish zones: ")?; - comma_separated(f,context.missing.iter(),|f,mode_id| - mode_id.write_finish_zone(f) + write_comma_separated(f,context.missing.iter(),|f,mode_id| + write_zone!(f,mode_id,"Finish") )?; writeln!(f,"")?; } @@ -500,8 +496,8 @@ impl<'a> std::fmt::Display for MapCheck<'a>{ // perhaps there are extra end zones (context.extra) if !context.extra.is_empty(){ write!(f,"Extra anticheat zones with no matching start zone: ")?; - comma_separated(f,context.extra.iter(),|f,(mode_id,_count)| - mode_id.write_anticheat_zone(f) + write_comma_separated(f,context.extra.iter(),|f,(mode_id,_count)| + write_zone!(f,mode_id,"Anticheat") )?; writeln!(f,"")?; } @@ -511,14 +507,14 @@ impl<'a> std::fmt::Display for MapCheck<'a>{ } if let DuplicateCheck(Err(DuplicateCheckContext(context)))=&self.spawn_counts{ write!(f,"Duplicate spawn zones: ")?; - comma_separated(f,context.iter(),|f,(SpawnID(spawn_id),count)| + write_comma_separated(f,context.iter(),|f,(SpawnID(spawn_id),count)| write!(f,"Spawn{spawn_id}({count} duplicates)") )?; writeln!(f,"")?; } if let DuplicateCheck(Err(DuplicateCheckContext(context)))=&self.wormhole_out_counts{ write!(f,"Duplicate wormhole out: ")?; - comma_separated(f,context.iter(),|f,(WormholeOutID(wormhole_out_id),count)| + write_comma_separated(f,context.iter(),|f,(WormholeOutID(wormhole_out_id),count)| write!(f,"WormholeOut{wormhole_out_id}({count} duplicates)") )?; writeln!(f,"")?; -- 2.49.1 From c4f910c1f08677c734d7f64b459c989211609cef Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 23:11:59 -0700 Subject: [PATCH 8/9] validator: comment ModelInfo::check --- validation/src/check.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 3a99b6f..e8d337f 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -309,19 +309,19 @@ pub struct MapCheck<'a>{ impl<'a> ModelInfo<'a>{ fn check(self)->MapCheck<'a>{ - // check class is exactly "Model" + // Check class is exactly "Model" let model_class=StringCheckContext{ observed:self.model_class, expected:"Model", }.check(()); - // check model name is snake case + // Check model name is snake case let model_name=StringCheckContext{ observed:self.model_name, expected:self.model_name.to_snake_case(), }.check(()); - // check display name is not empty and has title case + // Check display name is not empty and has title case let display_name=self.map_info.display_name.map(|display_name|{ StringEmptyCheck::new(display_name).map(|display_name|StringCheckContext{ observed:display_name, @@ -329,10 +329,10 @@ impl<'a> ModelInfo<'a>{ }.check(display_name)) }); - // check Creator is not empty + // Check Creator is not empty let creator=self.map_info.creator.map(StringEmptyCheck::new); - // check GameID (model name was prefixed with bhop_ surf_ etc) + // Check GameID (model name was prefixed with bhop_ surf_ etc) let game_id=self.map_info.game_id; // MapStart must exist @@ -349,22 +349,23 @@ impl<'a> ModelInfo<'a>{ Err(()) }; - // check that at least one end zone exists for each start zone. + // Check that at least one finish zone exists for each start zone. + // This also checks that there are no finish zones without a corresponding start zone. let mode_finish_counts=SetDifferenceCheckContextAtLeastOne::new(self.counts.mode_finish_counts) .check(&self.counts.mode_start_counts); - // check that there are no anticheat zones that have no corresponding start zone. - // modes are allowed to have 0 anticheat zones. + // Check that there are no anticheat zones without a corresponding start zone. + // Modes are allowed to have 0 anticheat zones. let mode_anticheat_counts=SetDifferenceCheckContextAllowNone::new(self.counts.mode_anticheat_counts) .check(&self.counts.mode_start_counts); - // there must be exactly one start zone for every mode in the map. + // There must be exactly one start zone for every mode in the map. let mode_start_counts=DuplicateCheckContext(self.counts.mode_start_counts).check(); - // there must be exactly one of any perticular spawn id in the map. + // There must be exactly one of any perticular spawn id in the map. let spawn_counts=DuplicateCheckContext(self.counts.spawn_counts).check(); - // there must be exactly one of any perticular wormhole out id in the map. + // There must be exactly one of any perticular wormhole out id in the map. let wormhole_out_counts=DuplicateCheckContext(self.counts.wormhole_out_counts).check(); MapCheck{ -- 2.49.1 From 9331f37d70a877a493a2cc3b7c536242261d3bc6 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 11 Apr 2025 23:20:48 -0700 Subject: [PATCH 9/9] validator: remove explicit StringEmptyCheck newtype --- validation/src/check.rs | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index e8d337f..1f957b4 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -177,21 +177,13 @@ impl<'a,Str:std::fmt::Display> std::fmt::Display for StringCheckContext<'a,Str>{ // check if a string is empty pub struct StringEmpty; -pub struct StringEmptyCheck(Result); -impl StringEmptyCheck{ - fn map(self,f:impl Fn(Context)->T)->StringEmptyCheck{ - StringEmptyCheck(self.0.map(f)) - } -} impl std::fmt::Display for StringEmpty{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"Empty string") } } -impl<'a> StringEmptyCheck<&'a str>{ - fn new(value:&'a str)->StringEmptyCheck<&'a str>{ - StringEmptyCheck(value.is_empty().then_some(value).ok_or(StringEmpty)) - } +fn check_empty(value:&str)->Result<&str,StringEmpty>{ + value.is_empty().then_some(value).ok_or(StringEmpty) } // check for duplicate objects @@ -282,10 +274,10 @@ pub struct MapCheck<'a>{ model_name:StringCheck<'a,(),String>, // Map must have a StringValue named DisplayName. // Value must not be empty, must be in title case. - display_name:Result>,StringValueError>, + display_name:Result,StringEmpty>,StringValueError>, // Map must have a StringValue named Creator. // Value must not be empty. - creator:Result,StringValueError>, + creator:Result,StringValueError>, // The prefix of the model's name must match the game it was submitted for. // bhop_ for bhop, and surf_ for surf game_id:Result, @@ -323,14 +315,14 @@ impl<'a> ModelInfo<'a>{ // Check display name is not empty and has title case let display_name=self.map_info.display_name.map(|display_name|{ - StringEmptyCheck::new(display_name).map(|display_name|StringCheckContext{ + check_empty(display_name).map(|display_name|StringCheckContext{ observed:display_name, expected:display_name.to_title_case(), }.check(display_name)) }); // Check Creator is not empty - let creator=self.map_info.creator.map(StringEmptyCheck::new); + let creator=self.map_info.creator.map(check_empty); // Check GameID (model name was prefixed with bhop_ surf_ etc) let game_id=self.map_info.game_id; @@ -391,8 +383,8 @@ impl<'a> MapCheck<'a>{ MapCheck{ model_class:StringCheck(Ok(())), model_name:StringCheck(Ok(())), - display_name:Ok(StringEmptyCheck(Ok(StringCheck(Ok(display_name))))), - creator:Ok(StringEmptyCheck(Ok(creator))), + display_name:Ok(Ok(StringCheck(Ok(display_name)))), + creator:Ok(Ok(creator)), game_id:Ok(game_id), mapstart:Ok(()), mode_start_counts:DuplicateCheck(Ok(())), @@ -446,16 +438,16 @@ impl<'a> std::fmt::Display for MapCheck<'a>{ writeln!(f,"Invalid Model Name: {context}")?; } match &self.display_name{ - Ok(StringEmptyCheck(Ok(StringCheck(Ok(_)))))=>(), - Ok(StringEmptyCheck(Ok(StringCheck(Err(context)))))=>writeln!(f,"Invalid DisplayName: {context}")?, - Ok(StringEmptyCheck(Err(context)))=>writeln!(f,"Invalid DisplayName: {context}")?, + Ok(Ok(StringCheck(Ok(_))))=>(), + Ok(Ok(StringCheck(Err(context))))=>writeln!(f,"Invalid DisplayName: {context}")?, + Ok(Err(context))=>writeln!(f,"Invalid DisplayName: {context}")?, Err(StringValueError::ObjectNotFound)=>writeln!(f,"Missing DisplayName StringValue")?, Err(StringValueError::ValueNotSet)=>writeln!(f,"DisplayName Value not set")?, Err(StringValueError::NonStringValue)=>writeln!(f,"DisplayName Value is not a String")?, } match &self.creator{ - Ok(StringEmptyCheck(Ok(_)))=>(), - Ok(StringEmptyCheck(Err(context)))=>writeln!(f,"Invalid Creator: {context}")?, + Ok(Ok(_))=>(), + Ok(Err(context))=>writeln!(f,"Invalid Creator: {context}")?, Err(StringValueError::ObjectNotFound)=>writeln!(f,"Missing Creator StringValue")?, Err(StringValueError::ValueNotSet)=>writeln!(f,"Creator Value not set")?, Err(StringValueError::NonStringValue)=>writeln!(f,"Creator Value is not a String")?, -- 2.49.1