Refactor Validator Errors #29

Merged
Quaternions merged 5 commits from error into staging 2025-03-26 21:30:13 +00:00
3 changed files with 128 additions and 36 deletions

@ -31,6 +31,47 @@ impl Context{
).await.map_err(Error::Response)? ).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest) .json().await.map_err(Error::Reqwest)
} }
pub async fn get_scripts<'a>(&self,config:GetScriptsRequest<'a>)->Result<Vec<ScriptResponse>,Error>{
let url_raw=format!("{}/scripts",self.0.base_url);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
{
let mut query_pairs=url.query_pairs_mut();
query_pairs.append_pair("Page",config.Page.to_string().as_str());
query_pairs.append_pair("Limit",config.Limit.to_string().as_str());
if let Some(name)=config.Name{
query_pairs.append_pair("Name",name);
}
if let Some(hash)=config.Hash{
query_pairs.append_pair("Hash",hash);
}
if let Some(source)=config.Source{
query_pairs.append_pair("Source",source);
}
if let Some(submission_id)=config.SubmissionID{
query_pairs.append_pair("SubmissionID",submission_id.to_string().as_str());
}
}
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_script_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptResponse>,SingleItemError>{
let scripts=self.get_scripts(GetScriptsRequest{
Page:1,
Limit:2,
Hash:Some(config.hash),
Name:None,
Source:None,
SubmissionID:None,
}).await.map_err(SingleItemError::Other)?;
if 1<scripts.len(){
return Err(SingleItemError::DuplicateItems);
}
Ok(scripts.into_iter().next())
}
pub async fn create_script<'a>(&self,config:CreateScriptRequest<'a>)->Result<ScriptIDResponse,Error>{ pub async fn create_script<'a>(&self,config:CreateScriptRequest<'a>)->Result<ScriptIDResponse,Error>{
let url_raw=format!("{}/scripts",self.0.base_url); let url_raw=format!("{}/scripts",self.0.base_url);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;

@ -60,9 +60,9 @@ pub async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,R
} }
} }
#[derive(Clone,Copy,PartialEq,Eq,serde::Serialize,serde::Deserialize)] #[derive(Clone,Copy,Debug,PartialEq,Eq,serde::Serialize,serde::Deserialize)]
pub struct ScriptID(pub(crate)i64); pub struct ScriptID(pub(crate)i64);
#[derive(Clone,Copy,serde::Serialize,serde::Deserialize)] #[derive(Clone,Copy,Debug,serde::Serialize,serde::Deserialize)]
pub struct ScriptPolicyID(pub(crate)i64); pub struct ScriptPolicyID(pub(crate)i64);
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
@ -70,7 +70,7 @@ pub struct GetScriptRequest{
pub ScriptID:ScriptID, pub ScriptID:ScriptID,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Serialize)] #[derive(Clone,Debug,serde::Serialize)]
pub struct GetScriptsRequest<'a>{ pub struct GetScriptsRequest<'a>{
pub Page:u32, pub Page:u32,
pub Limit:u32, pub Limit:u32,
@ -83,11 +83,12 @@ pub struct GetScriptsRequest<'a>{
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if="Option::is_none")]
pub SubmissionID:Option<i64>, pub SubmissionID:Option<i64>,
} }
#[derive(Clone,Copy,Debug)]
pub struct HashRequest<'a>{ pub struct HashRequest<'a>{
pub hash:&'a str, pub hash:&'a str,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Deserialize)] #[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptResponse{ pub struct ScriptResponse{
pub ID:ScriptID, pub ID:ScriptID,
pub Name:String, pub Name:String,
@ -96,7 +97,7 @@ pub struct ScriptResponse{
pub SubmissionID:i64, pub SubmissionID:i64,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Serialize)] #[derive(Clone,Debug,serde::Serialize)]
pub struct CreateScriptRequest<'a>{ pub struct CreateScriptRequest<'a>{
pub Name:&'a str, pub Name:&'a str,
pub Source:&'a str, pub Source:&'a str,
@ -104,12 +105,12 @@ pub struct CreateScriptRequest<'a>{
pub SubmissionID:Option<i64>, pub SubmissionID:Option<i64>,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Deserialize)] #[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptIDResponse{ pub struct ScriptIDResponse{
pub ID:ScriptID, pub ID:ScriptID,
} }
#[derive(PartialEq,Eq,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)] #[derive(Clone,Copy,Debug,PartialEq,Eq,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)]
#[repr(i32)] #[repr(i32)]
pub enum Policy{ pub enum Policy{
None=0, // not yet reviewed None=0, // not yet reviewed
@ -120,7 +121,7 @@ pub enum Policy{
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Serialize)] #[derive(Clone,Debug,serde::Serialize)]
pub struct GetScriptPoliciesRequest<'a>{ pub struct GetScriptPoliciesRequest<'a>{
pub Page:u32, pub Page:u32,
pub Limit:u32, pub Limit:u32,
@ -132,7 +133,7 @@ pub struct GetScriptPoliciesRequest<'a>{
pub Policy:Option<Policy>, pub Policy:Option<Policy>,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Deserialize)] #[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptPolicyResponse{ pub struct ScriptPolicyResponse{
pub ID:ScriptPolicyID, pub ID:ScriptPolicyID,
pub FromScriptHash:String, pub FromScriptHash:String,
@ -140,20 +141,20 @@ pub struct ScriptPolicyResponse{
pub Policy:Policy pub Policy:Policy
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Serialize)] #[derive(Clone,Debug,serde::Serialize)]
pub struct CreateScriptPolicyRequest{ pub struct CreateScriptPolicyRequest{
pub FromScriptID:ScriptID, pub FromScriptID:ScriptID,
pub ToScriptID:ScriptID, pub ToScriptID:ScriptID,
pub Policy:Policy, pub Policy:Policy,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Deserialize)] #[derive(Clone,Debug,serde::Deserialize)]
pub struct ScriptPolicyIDResponse{ pub struct ScriptPolicyIDResponse{
pub ID:ScriptPolicyID, pub ID:ScriptPolicyID,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(serde::Serialize)] #[derive(Clone,Debug,serde::Serialize)]
pub struct UpdateScriptPolicyRequest{ pub struct UpdateScriptPolicyRequest{
pub ID:ScriptPolicyID, pub ID:ScriptPolicyID,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if="Option::is_none")]
@ -165,6 +166,7 @@ pub struct UpdateScriptPolicyRequest{
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct UpdateSubmissionModelRequest{ pub struct UpdateSubmissionModelRequest{
pub SubmissionID:i64, pub SubmissionID:i64,
pub ModelID:u64, pub ModelID:u64,
@ -172,15 +174,18 @@ pub struct UpdateSubmissionModelRequest{
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionSubmissionUploadedRequest{ pub struct ActionSubmissionUploadedRequest{
pub SubmissionID:i64, pub SubmissionID:i64,
pub TargetAssetID:Option<u64>, pub TargetAssetID:Option<u64>,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]
#[derive(Clone,Debug)]
pub struct ActionSubmissionAcceptedRequest{ pub struct ActionSubmissionAcceptedRequest{
pub SubmissionID:i64, pub SubmissionID:i64,
pub StatusMessage:String, pub StatusMessage:String,
} }
#[derive(Clone,Copy,Debug)]
pub struct SubmissionID(pub i64); pub struct SubmissionID(pub i64);

@ -21,23 +21,33 @@ fn source_has_illegal_keywords(source:&str)->bool{
source.find("getfenv").is_some()||source.find("require").is_some() source.find("getfenv").is_some()||source.find("require").is_some()
} }
fn hash_source(source:&str)->String{
let mut hasher=siphasher::sip::SipHasher::new();
std::hash::Hasher::write(&mut hasher,source.as_bytes());
let hash=std::hash::Hasher::finish(&hasher);
format!("{:016x}",hash)
}
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum ValidateError{ pub enum ValidateError{
Flagged, ScriptFlaggedIllegalKeyword,
Blocked, ScriptBlocked(Option<submissions_api::types::ScriptID>),
NotAllowed, ScriptNotYetReviewed(Option<submissions_api::types::ScriptID>),
Get(rbx_asset::cookie::GetError), ModelFileDownload(rbx_asset::cookie::GetError),
ReadDom(ReadDomError), ModelFileDecode(ReadDomError),
ApiGetScriptPolicy(submissions_api::types::SingleItemError), ApiGetScriptPolicyFromHash(submissions_api::types::SingleItemError),
ApiGetScript(submissions_api::Error), ApiGetScript(submissions_api::Error),
ApiCreateScript(submissions_api::Error), ApiCreateScript(submissions_api::Error),
ApiCreateScriptPolicy(submissions_api::Error), ApiCreateScriptPolicy(submissions_api::Error),
ApiGetScriptFromHash(submissions_api::types::SingleItemError),
ApiUpdateSubmissionModel(submissions_api::Error), ApiUpdateSubmissionModel(submissions_api::Error),
ApiActionSubmissionValidate(submissions_api::Error), ApiActionSubmissionValidate(submissions_api::Error),
WriteDom(rbx_binary::EncodeError), ModelFileRootMustHaveOneChild,
Upload(rbx_asset::cookie::UploadError), ModelFileChildRefIsNil,
Create(rbx_asset::cookie::CreateError), ModelFileEncode(rbx_binary::EncodeError),
AssetUpload(rbx_asset::cookie::UploadError),
AssetCreate(rbx_asset::cookie::CreateError),
} }
impl std::fmt::Display for ValidateError{ impl std::fmt::Display for ValidateError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@ -89,10 +99,10 @@ impl Validator{
let data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{ let data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id:validate_info.ModelID, asset_id:validate_info.ModelID,
version:Some(validate_info.ModelVersion), version:Some(validate_info.ModelVersion),
}).await.map_err(ValidateError::Get)?; }).await.map_err(ValidateError::ModelFileDownload)?;
// decode dom (slow!) // decode dom (slow!)
let mut dom=read_dom(&mut std::io::Cursor::new(data)).map_err(ValidateError::ReadDom)?; let mut dom=read_dom(&mut std::io::Cursor::new(data)).map_err(ValidateError::ModelFileDecode)?;
/* VALIDATE MAP */ /* VALIDATE MAP */
@ -105,12 +115,12 @@ impl Validator{
// check the source for illegal keywords // check the source for illegal keywords
if source_has_illegal_keywords(source){ if source_has_illegal_keywords(source){
// immediately abort // immediately abort
return Err(ValidateError::Flagged); return Err(ValidateError::ScriptFlaggedIllegalKeyword);
} }
// associate a name and policy with the source code // associate a name and policy with the source code
// policy will be fetched from the database to replace the default policy // policy will be fetched from the database to replace the default policy
script_map.insert(source.clone(),NamePolicy{ script_map.insert(source.clone(),NamePolicy{
name:script.name.clone(), name:get_partial_path(&dom,script),
policy:Policy::None, policy:Policy::None,
}); });
} }
@ -121,14 +131,12 @@ impl Validator{
futures::stream::iter(script_map.iter_mut().map(Ok)) futures::stream::iter(script_map.iter_mut().map(Ok))
.try_for_each_concurrent(Some(SCRIPT_CONCURRENCY),|(source,NamePolicy{policy,name})|async{ .try_for_each_concurrent(Some(SCRIPT_CONCURRENCY),|(source,NamePolicy{policy,name})|async{
// get the hash // get the hash
let mut hasher=siphasher::sip::SipHasher::new(); let hash=hash_source(source.as_str());
std::hash::Hasher::write(&mut hasher,source.as_bytes());
let hash=std::hash::Hasher::finish(&hasher);
// fetch the script policy // fetch the script policy
let script_policy=self.api.get_script_policy_from_hash(submissions_api::types::HashRequest{ let script_policy=self.api.get_script_policy_from_hash(submissions_api::types::HashRequest{
hash:format!("{:016x}",hash).as_str(), hash:hash.as_str(),
}).await.map_err(ValidateError::ApiGetScriptPolicy)?; }).await.map_err(ValidateError::ApiGetScriptPolicyFromHash)?;
// write the policy to the script_map, fetching the replacement code if necessary // write the policy to the script_map, fetching the replacement code if necessary
if let Some(script_policy)=script_policy{ if let Some(script_policy)=script_policy{
@ -170,10 +178,22 @@ impl Validator{
if let Some(script)=dom.get_by_ref_mut(script_ref){ if let Some(script)=dom.get_by_ref_mut(script_ref){
if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get_mut("Source"){ if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get_mut("Source"){
match script_map.get(source.as_str()).map(|p|&p.policy){ match script_map.get(source.as_str()).map(|p|&p.policy){
Some(Policy::Blocked)=>return Err(ValidateError::Blocked), Some(Policy::Blocked)=>{
let hash=hash_source(source.as_str());
let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{
hash:hash.as_str(),
}).await.map_err(ValidateError::ApiGetScriptFromHash)?;
return Err(ValidateError::ScriptBlocked(script.map(|s|s.ID)));
},
None None
|Some(Policy::None) |Some(Policy::None)
=>return Err(ValidateError::NotAllowed), =>{
let hash=hash_source(source.as_str());
let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{
hash:hash.as_str(),
}).await.map_err(ValidateError::ApiGetScriptFromHash)?;
return Err(ValidateError::ScriptNotYetReviewed(script.map(|s|s.ID)));
},
Some(Policy::Allowed)=>(), Some(Policy::Allowed)=>(),
Some(Policy::Delete)=>{ Some(Policy::Delete)=>{
modified=true; modified=true;
@ -195,7 +215,10 @@ impl Validator{
if modified{ if modified{
// serialize model (slow!) // serialize model (slow!)
let mut data=Vec::new(); let mut data=Vec::new();
rbx_binary::to_writer(&mut data,&dom,dom.root().children()).map_err(ValidateError::WriteDom)?; let &[map_ref]=dom.root().children()else{
return Err(ValidateError::ModelFileRootMustHaveOneChild);
};
rbx_binary::to_writer(&mut data,&dom,&[map_ref]).map_err(ValidateError::ModelFileEncode)?;
// upload a model lol // upload a model lol
let model_id=if let Some(model_id)=validate_info.ValidatedModelID{ let model_id=if let Some(model_id)=validate_info.ValidatedModelID{
@ -207,18 +230,22 @@ impl Validator{
ispublic:None, ispublic:None,
allowComments:None, allowComments:None,
groupId:None, groupId:None,
},data).await.map_err(ValidateError::Upload)?; },data).await.map_err(ValidateError::AssetUpload)?;
response.AssetId response.AssetId
}else{ }else{
// grab the map instance from the map re
let Some(map_instance)=dom.get_by_ref(map_ref)else{
return Err(ValidateError::ModelFileChildRefIsNil);
};
// create new model // create new model
let response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{ let response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{
name:dom.root().name.clone(), name:map_instance.name.clone(),
description:"".to_owned(), description:"".to_owned(),
ispublic:true, ispublic:true,
allowComments:true, allowComments:true,
groupId:None, groupId:None,
},data).await.map_err(ValidateError::Create)?; },data).await.map_err(ValidateError::AssetCreate)?;
response.AssetId response.AssetId
}; };
@ -291,6 +318,25 @@ fn recursive_collect_superclass(objects:&mut std::vec::Vec<rbx_dom_weak::types::
} }
} }
fn get_partial_path(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{
struct ParentIter<'a>{
dom:&'a rbx_dom_weak::WeakDom,
instance:Option<&'a rbx_dom_weak::Instance>,
}
impl<'a> Iterator for ParentIter<'a>{
type Item=&'a rbx_dom_weak::Instance;
fn next(&mut self)->Option<Self::Item>{
let parent=self.instance.map(|i|i.parent()).and_then(|p|self.dom.get_by_ref(p));
core::mem::replace(&mut self.instance,parent)
}
}
let mut tragic:Vec<_>=ParentIter{dom,instance:Some(instance)}.map(|i|i.name.as_str()).collect();
tragic.pop();
tragic.reverse();
tragic.join(".")
}
fn get_script_refs(dom:&rbx_dom_weak::WeakDom)->Vec<rbx_dom_weak::types::Ref>{ fn get_script_refs(dom:&rbx_dom_weak::WeakDom)->Vec<rbx_dom_weak::types::Ref>{
let mut scripts=std::vec::Vec::new(); let mut scripts=std::vec::Vec::new();
recursive_collect_superclass(&mut scripts,dom,dom.root(),"LuaSourceContainer"); recursive_collect_superclass(&mut scripts,dom,dom.root(),"LuaSourceContainer");