validator: rusty check v2
This commit is contained in:
parent
337cf502cf
commit
67215d82cd
@ -12,6 +12,7 @@ pub enum Error{
|
||||
CreatorTypeMustBeUser,
|
||||
Download(crate::download::Error),
|
||||
ModelFileDecode(ReadDomError),
|
||||
GetRootInstance(GetRootInstanceError),
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
@ -155,38 +156,29 @@ pub fn get_model_info(dom:&rbx_dom_weak::WeakDom)->Result<ModelInfo,GetRootInsta
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub enum Check<Context>{
|
||||
Pass,
|
||||
Fail(Context),
|
||||
}
|
||||
|
||||
// check if an observed string matches and expected string
|
||||
pub struct StringCheck<'a>(Check<StringCheckContext<'a>>);
|
||||
pub struct StringCheck<'a,T>(Result<T,StringCheckContext<'a>>);
|
||||
pub struct StringCheckContext<'a>{
|
||||
observed:&'a str,
|
||||
expected:Cow<'a,str>,
|
||||
}
|
||||
impl<'a> StringCheckContext<'a>{
|
||||
fn check(self)->StringCheck<'a>{
|
||||
fn check<T>(self,value:T)->StringCheck<'a,T>{
|
||||
if self.observed==self.expected{
|
||||
StringCheck(Check::Pass)
|
||||
StringCheck(Ok(value))
|
||||
}else{
|
||||
StringCheck(Check::Fail(self))
|
||||
StringCheck(Err(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if a string is empty
|
||||
pub enum StringEmptyCheck<Context>{
|
||||
Empty,
|
||||
Passed(Context),
|
||||
}
|
||||
pub struct StringEmpty;
|
||||
pub struct StringEmptyCheck<Context>(Result<Context,StringEmpty>);
|
||||
|
||||
// check for duplicate objects
|
||||
pub struct DuplicateCheckContext<ID>(HashMap<ID,u64>);
|
||||
pub struct DuplicateCheck<ID>(Check<DuplicateCheckContext<ID>>);
|
||||
pub struct DuplicateCheck<ID>(Result<(),DuplicateCheckContext<ID>>);
|
||||
impl<ID> DuplicateCheckContext<ID>{
|
||||
fn check(self)->DuplicateCheck<ID>{
|
||||
let Self(mut set)=self;
|
||||
@ -194,9 +186,9 @@ impl<ID> DuplicateCheckContext<ID>{
|
||||
set.retain(|_,&mut c|c!=1);
|
||||
// if any entries remain, they are incorrect
|
||||
if set.is_empty(){
|
||||
DuplicateCheck(Check::Pass)
|
||||
DuplicateCheck(Ok(()))
|
||||
}else{
|
||||
DuplicateCheck(Check::Fail(Self(set)))
|
||||
DuplicateCheck(Err(Self(set)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -206,7 +198,7 @@ pub struct AtLeastOneMatchingAndNoExtraCheckContext<ID>{
|
||||
set:HashMap<ID,u64>,
|
||||
missing:HashSet<ID>,
|
||||
}
|
||||
pub struct AtLeastOneMatchingAndNoExtraCheck<ID>(Check<AtLeastOneMatchingAndNoExtraCheckContext<ID>>);
|
||||
pub struct AtLeastOneMatchingAndNoExtraCheck<ID>(Result<(),AtLeastOneMatchingAndNoExtraCheckContext<ID>>);
|
||||
impl<ID> AtLeastOneMatchingAndNoExtraCheckContext<ID>{
|
||||
fn new(set:HashMap<ID,u64>)->Self{
|
||||
Self{
|
||||
@ -227,9 +219,9 @@ impl<ID:Copy+Eq+std::hash::Hash> AtLeastOneMatchingAndNoExtraCheckContext<ID>{
|
||||
}
|
||||
// if any entries remain, they are incorrect
|
||||
if set.is_empty()&&missing.is_empty(){
|
||||
AtLeastOneMatchingAndNoExtraCheck(Check::Pass)
|
||||
AtLeastOneMatchingAndNoExtraCheck(Ok(()))
|
||||
}else{
|
||||
AtLeastOneMatchingAndNoExtraCheck(Check::Fail(Self{set,missing}))
|
||||
AtLeastOneMatchingAndNoExtraCheck(Err(Self{set,missing}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -242,15 +234,15 @@ pub struct MapInfoOwned{
|
||||
|
||||
// crazy!
|
||||
pub struct MapCheck<'a>{
|
||||
model_class:StringCheck<'a>,
|
||||
model_name:StringCheck<'a>,
|
||||
display_name:Result<StringEmptyCheck<StringCheck<'a>>,StringValueError>,
|
||||
creator:Result<StringEmptyCheck<()>,StringValueError>,
|
||||
model_class:StringCheck<'a,()>,
|
||||
model_name:StringCheck<'a,()>,
|
||||
display_name:Result<StringEmptyCheck<StringCheck<'a,&'a str>>,StringValueError>,
|
||||
creator:Result<StringEmptyCheck<&'a str>,StringValueError>,
|
||||
game_id:Result<GameID,ParseGameIDError>,
|
||||
mapstart:Check<()>,
|
||||
mapstart:Result<(),()>,
|
||||
mode_start_counts:DuplicateCheck<ModeID>,
|
||||
mode_finish_counts:AtLeastOneMatchingAndNoExtraCheck<ModeID>,
|
||||
spawn1:Check<()>,
|
||||
spawn1:Result<(),()>,
|
||||
spawn_counts:DuplicateCheck<SpawnID>,
|
||||
wormhole_out_counts:DuplicateCheck<WormholeOutID>,
|
||||
}
|
||||
@ -260,32 +252,32 @@ impl<'a> ModelInfo<'a>{
|
||||
let model_class=StringCheckContext{
|
||||
observed:self.model_class,
|
||||
expected:Cow::Borrowed("Model"),
|
||||
}.check();
|
||||
}.check(());
|
||||
|
||||
let model_name=StringCheckContext{
|
||||
observed:self.model_name,
|
||||
expected:Cow::Owned(self.model_name.to_snake_case()),
|
||||
}.check();
|
||||
}.check(());
|
||||
|
||||
// check display name
|
||||
let display_name=self.map_info.display_name.map(|display_name|{
|
||||
if display_name.is_empty(){
|
||||
StringEmptyCheck::Empty
|
||||
StringEmptyCheck(Err(StringEmpty))
|
||||
}else{
|
||||
let display_name=StringCheckContext{
|
||||
observed:display_name,
|
||||
expected:Cow::Owned(display_name.to_title_case()),
|
||||
}.check();
|
||||
StringEmptyCheck::Passed(display_name)
|
||||
}.check(display_name);
|
||||
StringEmptyCheck(Ok(display_name))
|
||||
}
|
||||
});
|
||||
|
||||
// check Creator
|
||||
let creator=self.map_info.creator.map(|creator|{
|
||||
if creator.is_empty(){
|
||||
StringEmptyCheck::Empty
|
||||
StringEmptyCheck(Err(StringEmpty))
|
||||
}else{
|
||||
StringEmptyCheck::Passed(())
|
||||
StringEmptyCheck(Ok(creator))
|
||||
}
|
||||
});
|
||||
|
||||
@ -294,16 +286,16 @@ impl<'a> ModelInfo<'a>{
|
||||
|
||||
// MapStart must exist
|
||||
let mapstart=if self.counts.mode_start_counts.get(&ModeID::MAIN).is_some(){
|
||||
Check::Pass
|
||||
Ok(())
|
||||
}else{
|
||||
Check::Fail(())
|
||||
Err(())
|
||||
};
|
||||
|
||||
// Spawn1 must exist
|
||||
let spawn1=if self.counts.spawn_counts.get(&SpawnID::FIRST).is_some(){
|
||||
Check::Pass
|
||||
Ok(())
|
||||
}else{
|
||||
Check::Fail(())
|
||||
Err(())
|
||||
};
|
||||
|
||||
// check that at least one end zone exists for each start zone.
|
||||
@ -338,17 +330,17 @@ impl<'a> MapCheck<'a>{
|
||||
fn pass(self)->Result<MapInfoOwned,Self>{
|
||||
match self{
|
||||
MapCheck{
|
||||
model_class:StringCheck(Check::Pass),
|
||||
model_name:StringCheck(Check::Pass),
|
||||
display_name:Ok(StringEmptyCheck::Passed(StringCheck(Check::Pass))),
|
||||
creator:Ok(StringEmptyCheck::Passed(())),
|
||||
model_class:StringCheck(Ok(())),
|
||||
model_name:StringCheck(Ok(())),
|
||||
display_name:Ok(StringEmptyCheck(Ok(StringCheck(Ok(display_name))))),
|
||||
creator:Ok(StringEmptyCheck(Ok(creator))),
|
||||
game_id:Ok(game_id),
|
||||
mapstart,
|
||||
mode_start_counts,
|
||||
mode_finish_counts,
|
||||
spawn1,
|
||||
spawn_counts,
|
||||
wormhole_out_counts,
|
||||
mapstart:Ok(()),
|
||||
mode_start_counts:DuplicateCheck(Ok(())),
|
||||
mode_finish_counts:AtLeastOneMatchingAndNoExtraCheck(Ok(())),
|
||||
spawn1:Ok(()),
|
||||
spawn_counts:DuplicateCheck(Ok(())),
|
||||
wormhole_out_counts:DuplicateCheck(Ok(())),
|
||||
}=>{
|
||||
Ok(MapInfoOwned{
|
||||
display_name:display_name.to_owned(),
|
||||
@ -362,7 +354,7 @@ impl<'a> MapCheck<'a>{
|
||||
}
|
||||
|
||||
pub struct CheckReportAndVersion{
|
||||
pub status:CheckStatus,
|
||||
pub status:Result<MapInfoOwned,String>,
|
||||
pub version:u64,
|
||||
}
|
||||
|
||||
@ -389,7 +381,14 @@ impl crate::message_handler::MessageHandler{
|
||||
// decode dom (slow!)
|
||||
let dom=maybe_gzip.read_with(read_dom,read_dom).map_err(Error::ModelFileDecode)?;
|
||||
|
||||
let status=check(&dom);
|
||||
// extract information from the model
|
||||
let model_info=get_model_info(&dom).map_err(Error::GetRootInstance)?;
|
||||
|
||||
// convert the model information into a structured report
|
||||
let map_check=model_info.check();
|
||||
|
||||
// check the report, generate an error message if it fails the check
|
||||
let status=map_check.pass().map_err(|e|e.to_string());
|
||||
|
||||
Ok(CheckReportAndVersion{status,version})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user