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