submissions: add updated info to validator-submitted #129

Merged
Quaternions merged 7 commits from val into staging 2025-04-12 04:31:58 +00:00
7 changed files with 552 additions and 22 deletions

View File

@@ -80,6 +80,25 @@ paths:
type: integer
format: int64
minimum: 0
- name: DisplayName
in: query
required: true
schema:
type: string
maxLength: 128
- name: Creator
in: query
required: true
schema:
type: string
maxLength: 128
- name: GameID
in: query
required: true
schema:
type: integer
format: int32
minimum: 0
responses:
"204":
description: Successful response
@@ -266,6 +285,25 @@ paths:
type: integer
format: int64
minimum: 0
- name: DisplayName
in: query
required: true
schema:
type: string
maxLength: 128
- name: Creator
in: query
required: true
schema:
type: string
maxLength: 128
- name: GameID
in: query
required: true
schema:
type: integer
format: int32
minimum: 0
responses:
"204":
description: Successful response

View File

@@ -500,6 +500,48 @@ func (c *Client) sendActionMapfixSubmitted(ctx context.Context, params ActionMap
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "DisplayName" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "DisplayName",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.DisplayName))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "Creator" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "Creator",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.Creator))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "GameID" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "GameID",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.Int32ToString(params.GameID))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
u.RawQuery = q.Values().Encode()
stage = "EncodeRequest"
@@ -1118,6 +1160,48 @@ func (c *Client) sendActionSubmissionSubmitted(ctx context.Context, params Actio
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "DisplayName" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "DisplayName",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.DisplayName))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "Creator" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "Creator",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.StringToString(params.Creator))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "GameID" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "GameID",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return e.EncodeValue(conv.Int32ToString(params.GameID))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
u.RawQuery = q.Values().Encode()
stage = "EncodeRequest"

View File

@@ -438,6 +438,18 @@ func (s *Server) handleActionMapfixSubmittedRequest(args [1]string, argsEscaped
Name: "ModelVersion",
In: "query",
}: params.ModelVersion,
{
Name: "DisplayName",
In: "query",
}: params.DisplayName,
{
Name: "Creator",
In: "query",
}: params.Creator,
{
Name: "GameID",
In: "query",
}: params.GameID,
},
Raw: r,
}
@@ -1348,6 +1360,18 @@ func (s *Server) handleActionSubmissionSubmittedRequest(args [1]string, argsEsca
Name: "ModelVersion",
In: "query",
}: params.ModelVersion,
{
Name: "DisplayName",
In: "query",
}: params.DisplayName,
{
Name: "Creator",
In: "query",
}: params.Creator,
{
Name: "GameID",
In: "query",
}: params.GameID,
},
Raw: r,
}

View File

@@ -308,6 +308,9 @@ type ActionMapfixSubmittedParams struct {
// The unique identifier for a submission.
MapfixID int64
ModelVersion int64
DisplayName string
Creator string
GameID int32
}
func unpackActionMapfixSubmittedParams(packed middleware.Parameters) (params ActionMapfixSubmittedParams) {
@@ -325,6 +328,27 @@ func unpackActionMapfixSubmittedParams(packed middleware.Parameters) (params Act
}
params.ModelVersion = packed[key].(int64)
}
{
key := middleware.ParameterKey{
Name: "DisplayName",
In: "query",
}
params.DisplayName = packed[key].(string)
}
{
key := middleware.ParameterKey{
Name: "Creator",
In: "query",
}
params.Creator = packed[key].(string)
}
{
key := middleware.ParameterKey{
Name: "GameID",
In: "query",
}
params.GameID = packed[key].(int32)
}
return params
}
@@ -445,6 +469,163 @@ func decodeActionMapfixSubmittedParams(args [1]string, argsEscaped bool, r *http
Err: err,
}
}
// Decode query: DisplayName.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "DisplayName",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToString(val)
if err != nil {
return err
}
params.DisplayName = c
return nil
}); err != nil {
return err
}
if err := func() error {
if err := (validate.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 128,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(params.DisplayName)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
return err
}
} else {
return err
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "DisplayName",
In: "query",
Err: err,
}
}
// Decode query: Creator.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "Creator",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToString(val)
if err != nil {
return err
}
params.Creator = c
return nil
}); err != nil {
return err
}
if err := func() error {
if err := (validate.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 128,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(params.Creator)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
return err
}
} else {
return err
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "Creator",
In: "query",
Err: err,
}
}
// Decode query: GameID.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "GameID",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt32(val)
if err != nil {
return err
}
params.GameID = c
return nil
}); err != nil {
return err
}
if err := func() error {
if err := (validate.Int{
MinSet: true,
Min: 0,
MaxSet: false,
Max: 0,
MinExclusive: false,
MaxExclusive: false,
MultipleOfSet: false,
MultipleOf: 0,
}).Validate(int64(params.GameID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
return err
}
} else {
return err
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "GameID",
In: "query",
Err: err,
}
}
return params, nil
}
@@ -1051,6 +1232,9 @@ type ActionSubmissionSubmittedParams struct {
// The unique identifier for a submission.
SubmissionID int64
ModelVersion int64
DisplayName string
Creator string
GameID int32
}
func unpackActionSubmissionSubmittedParams(packed middleware.Parameters) (params ActionSubmissionSubmittedParams) {
@@ -1068,6 +1252,27 @@ func unpackActionSubmissionSubmittedParams(packed middleware.Parameters) (params
}
params.ModelVersion = packed[key].(int64)
}
{
key := middleware.ParameterKey{
Name: "DisplayName",
In: "query",
}
params.DisplayName = packed[key].(string)
}
{
key := middleware.ParameterKey{
Name: "Creator",
In: "query",
}
params.Creator = packed[key].(string)
}
{
key := middleware.ParameterKey{
Name: "GameID",
In: "query",
}
params.GameID = packed[key].(int32)
}
return params
}
@@ -1188,6 +1393,163 @@ func decodeActionSubmissionSubmittedParams(args [1]string, argsEscaped bool, r *
Err: err,
}
}
// Decode query: DisplayName.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "DisplayName",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToString(val)
if err != nil {
return err
}
params.DisplayName = c
return nil
}); err != nil {
return err
}
if err := func() error {
if err := (validate.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 128,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(params.DisplayName)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
return err
}
} else {
return err
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "DisplayName",
In: "query",
Err: err,
}
}
// Decode query: Creator.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "Creator",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToString(val)
if err != nil {
return err
}
params.Creator = c
return nil
}); err != nil {
return err
}
if err := func() error {
if err := (validate.String{
MinLength: 0,
MinLengthSet: false,
MaxLength: 128,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(params.Creator)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
return err
}
} else {
return err
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "Creator",
In: "query",
Err: err,
}
}
// Decode query: GameID.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "GameID",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt32(val)
if err != nil {
return err
}
params.GameID = c
return nil
}); err != nil {
return err
}
if err := func() error {
if err := (validate.Int{
MinSet: true,
Min: 0,
MaxSet: false,
Max: 0,
MinExclusive: false,
MaxExclusive: false,
MultipleOfSet: false,
MultipleOf: 0,
}).Validate(int64(params.GameID)); err != nil {
return errors.Wrap(err, "int")
}
return nil
}(); err != nil {
return err
}
} else {
return err
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "GameID",
In: "query",
Err: err,
}
}
return params, nil
}

View File

@@ -85,6 +85,9 @@ func (svc *Service) ActionMapfixSubmitted(ctx context.Context, params internal.A
smap := datastore.Optional()
smap.Add("status_id", target_status)
smap.Add("asset_version", params.ModelVersion)
smap.Add("display_name", params.DisplayName)
smap.Add("creator", params.Creator)
smap.Add("game_id", params.GameID)
err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap)
if err != nil {
return err

View File

@@ -84,6 +84,9 @@ func (svc *Service) ActionSubmissionSubmitted(ctx context.Context, params intern
smap := datastore.Optional()
smap.Add("status_id", target_status)
smap.Add("asset_version", params.ModelVersion)
smap.Add("display_name", params.DisplayName)
smap.Add("creator", params.Creator)
smap.Add("game_id", params.GameID)
err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap)
if err != nil {
return err

View File

@@ -169,7 +169,7 @@ pub fn get_model_info(dom:&rbx_dom_weak::WeakDom)->Result<ModelInfo,GetRootInsta
})
}
// check if an observed string matches and expected string
// check if an observed string matches an expected string
pub struct StringCheck<'a,T,Str>(Result<T,StringCheckContext<'a,Str>>);
pub struct StringCheckContext<'a,Str>{
observed:&'a str,
@@ -219,13 +219,13 @@ impl<ID> DuplicateCheckContext<ID>{
}
}
// check that there is at least one
pub struct AtLeastOneMatchingAndNoExtraCheckContext<ID>{
// check that there is at least one matching item for each item in a reference set, and no extra items
pub struct SetDifferenceCheckContext<ID>{
extra:HashMap<ID,u64>,
missing:HashSet<ID>,
}
pub struct AtLeastOneMatchingAndNoExtraCheck<ID>(Result<(),AtLeastOneMatchingAndNoExtraCheckContext<ID>>);
impl<ID> AtLeastOneMatchingAndNoExtraCheckContext<ID>{
pub struct SetDifferenceCheck<ID>(Result<(),SetDifferenceCheckContext<ID>>);
impl<ID> SetDifferenceCheckContext<ID>{
fn new(initial_set:HashMap<ID,u64>)->Self{
Self{
extra:initial_set,
@@ -233,8 +233,8 @@ impl<ID> AtLeastOneMatchingAndNoExtraCheckContext<ID>{
}
}
}
impl<ID:Copy+Eq+std::hash::Hash> AtLeastOneMatchingAndNoExtraCheckContext<ID>{
fn check<T>(self,reference_set:&HashMap<ID,T>)->AtLeastOneMatchingAndNoExtraCheck<ID>{
impl<ID:Copy+Eq+std::hash::Hash> SetDifferenceCheckContext<ID>{
fn check<T>(self,reference_set:&HashMap<ID,T>)->SetDifferenceCheck<ID>{
let Self{mut extra,mut missing}=self;
// remove correct entries
for (id,_) in reference_set{
@@ -245,9 +245,9 @@ impl<ID:Copy+Eq+std::hash::Hash> AtLeastOneMatchingAndNoExtraCheckContext<ID>{
}
// if any entries remain, they are incorrect
if extra.is_empty()&&missing.is_empty(){
AtLeastOneMatchingAndNoExtraCheck(Ok(()))
SetDifferenceCheck(Ok(()))
}else{
AtLeastOneMatchingAndNoExtraCheck(Err(Self{extra,missing}))
SetDifferenceCheck(Err(Self{extra,missing}))
}
}
}
@@ -260,16 +260,34 @@ pub struct MapInfoOwned{
// crazy!
pub struct MapCheck<'a>{
model_class:StringCheck<'a,(),&'a str>,
// === METADATA CHECKS ===
// The root must be of class Model
model_class:StringCheck<'a,(),&'static str>,
// Model's name must be in snake case
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<StringEmptyCheck<StringCheck<'a,&'a str,String>>,StringValueError>,
// Map must have a StringValue named Creator.
// Value must not be empty.
creator:Result<StringEmptyCheck<&'a str>,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<GameID,ParseGameIDError>,
// === MODE CHECKS ===
// MapStart must exist
mapstart:Result<(),()>,
// No duplicate map starts (including bonuses)
mode_start_counts:DuplicateCheck<ModeID>,
mode_finish_counts:AtLeastOneMatchingAndNoExtraCheck<ModeID>,
// At least one finish zone for each start zone, and no finishes with no start
mode_finish_counts:SetDifferenceCheck<ModeID>,
// TODO: check for dangling MapAnticheat zones (no associated MapStart)
// Spawn1 must exist
spawn1:Result<(),()>,
// No duplicate Spawn#
spawn_counts:DuplicateCheck<SpawnID>,
// No duplicate WormholeOut# (duplicate WormholeIn# ok)
wormhole_out_counts:DuplicateCheck<WormholeOutID>,
}
@@ -324,7 +342,7 @@ impl<'a> ModelInfo<'a>{
};
// check that at least one end zone exists for each start zone.
let mode_finish_counts=AtLeastOneMatchingAndNoExtraCheckContext::new(self.counts.mode_finish_counts)
let mode_finish_counts=SetDifferenceCheckContext::new(self.counts.mode_finish_counts)
.check(&self.counts.mode_start_counts);
// there must be exactly one start zone for every mode in the map.
@@ -363,7 +381,7 @@ impl<'a> MapCheck<'a>{
game_id:Ok(game_id),
mapstart:Ok(()),
mode_start_counts:DuplicateCheck(Ok(())),
mode_finish_counts:AtLeastOneMatchingAndNoExtraCheck(Ok(())),
mode_finish_counts:SetDifferenceCheck(Ok(())),
spawn1:Ok(()),
spawn_counts:DuplicateCheck(Ok(())),
wormhole_out_counts:DuplicateCheck(Ok(())),
@@ -431,7 +449,7 @@ impl<'a> std::fmt::Display for MapCheck<'a>{
})?;
writeln!(f,"")?;
}
if let AtLeastOneMatchingAndNoExtraCheck(Err(context))=&self.mode_finish_counts{
if let SetDifferenceCheck(Err(context))=&self.mode_finish_counts{
// perhaps there are extra end zones (context.extra)
if !context.extra.is_empty(){
write!(f,"Extra finish zones with no matching start zone: ")?;
@@ -453,18 +471,16 @@ 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!(f,"Spawn{spawn_id}({count} duplicates)")?;
Ok(())
})?;
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!(f,"WormholeOut{wormhole_out_id}({count} duplicates)")?;
Ok(())
})?;
comma_separated(f,context.iter(),|f,(WormholeOutID(wormhole_out_id),count)|
write!(f,"WormholeOut{wormhole_out_id}({count} duplicates)")
)?;
writeln!(f,"")?;
}
Ok(())