diff --git a/openapi-internal.yaml b/openapi-internal.yaml index c47f030..6a74bd4 100644 --- a/openapi-internal.yaml +++ b/openapi-internal.yaml @@ -9,6 +9,31 @@ tags: - name: Submissions description: Submission operations paths: + /mapfixes: + post: + summary: Create a mapfix + operationId: createMapfix + tags: + - Mapfixes + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MapfixCreate' + responses: + "201": + description: Successful response + content: + application/json: + schema: + $ref: "#/components/schemas/Id" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /mapfixes/{MapfixID}/validated-model: post: summary: Update validated model @@ -96,6 +121,55 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /operations/{OperationID}/failed: + post: + summary: (Internal endpoint) Fail an operation and write a StatusMessage + operationId: actionOperationFailed + tags: + - Operations + parameters: + - $ref: '#/components/parameters/OperationID' + - name: StatusMessage + in: query + required: true + schema: + type: string + minLength: 0 + maxLength: 4096 + responses: + "204": + description: Successful response + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /submissions: + post: + summary: Create a new submission + operationId: createSubmission + tags: + - Submissions + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SubmissionCreate' + responses: + "201": + description: Successful response + content: + application/json: + schema: + $ref: "#/components/schemas/Id" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /submissions/{SubmissionID}/validated-model: post: summary: Update validated model @@ -358,6 +432,14 @@ components: schema: type: integer format: int64 + OperationID: + name: OperationID + in: path + required: true + description: The unique identifier for a long-running operation. + schema: + type: integer + format: int32 SubmissionID: name: SubmissionID in: path @@ -400,6 +482,74 @@ components: ID: type: integer format: int64 + MapfixCreate: + required: + - OperationID + - AssetOwner + - DisplayName + - Creator + - GameID + - AssetID + - AssetVersion + - TargetAssetID + type: object + properties: + OperationID: + type: integer + format: int32 + AssetOwner: + type: integer + format: int64 + DisplayName: + type: string + maxLength: 128 + Creator: + type: string + maxLength: 128 + GameID: + type: integer + format: int32 + AssetID: + type: integer + format: int64 + AssetVersion: + type: integer + format: int64 + TargetAssetID: + type: integer + format: int64 + SubmissionCreate: + required: + - OperationID + - AssetOwner + - DisplayName + - Creator + - GameID + - AssetID + - AssetVersion + type: object + properties: + OperationID: + type: integer + format: int32 + AssetOwner: + type: integer + format: int64 + DisplayName: + type: string + maxLength: 128 + Creator: + type: string + maxLength: 128 + GameID: + type: integer + format: int32 + AssetID: + type: integer + format: int64 + AssetVersion: + type: integer + format: int64 Script: required: - ID diff --git a/openapi.yaml b/openapi.yaml index bb8bebc..3970378 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -10,6 +10,8 @@ tags: description: Mapfix operations - name: Maps description: Map queries + - name: Operations + description: Long-running operations - name: Session description: Session queries - name: Submissions @@ -191,7 +193,7 @@ paths: schema: $ref: "#/components/schemas/Error" post: - summary: Create new mapfix + summary: Trigger the validator to create a mapfix operationId: createMapfix tags: - Mapfixes @@ -200,14 +202,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/MapfixCreate' + $ref: '#/components/schemas/MapfixTriggerCreate' responses: "201": description: Successful response content: application/json: schema: - $ref: "#/components/schemas/Id" + $ref: "#/components/schemas/OperationID" default: description: General Error content: @@ -435,6 +437,27 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /operations/{OperationID}: + get: + summary: Retrieve operation with ID + operationId: getOperation + tags: + - Operations + parameters: + - $ref: '#/components/parameters/OperationID' + responses: + "200": + description: Successful response + content: + application/json: + schema: + $ref: "#/components/schemas/Operation" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /submissions: get: summary: Get list of submissions @@ -481,7 +504,7 @@ paths: schema: $ref: "#/components/schemas/Error" post: - summary: Create new submission + summary: Trigger the validator to create a new submission operationId: createSubmission tags: - Submissions @@ -490,14 +513,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SubmissionCreate' + $ref: '#/components/schemas/SubmissionTriggerCreate' responses: "201": description: Successful response content: application/json: schema: - $ref: "#/components/schemas/Id" + $ref: "#/components/schemas/OperationID" default: description: General Error content: @@ -1033,6 +1056,14 @@ components: schema: type: integer format: int64 + OperationID: + name: OperationID + in: path + required: true + description: The unique identifier for a long-running operation. + schema: + type: integer + format: int32 SubmissionID: name: SubmissionID in: path @@ -1083,6 +1114,14 @@ components: ID: type: integer format: int64 + OperationID: + required: + - OperationID + type: object + properties: + OperationID: + type: integer + format: int32 Roles: required: - Roles @@ -1186,34 +1225,46 @@ components: StatusMessage: type: string maxLength: 256 - MapfixCreate: + MapfixTriggerCreate: required: - - DisplayName - - Creator - - GameID - AssetID - - AssetVersion - TargetAssetID type: object properties: - DisplayName: - type: string - maxLength: 128 - Creator: - type: string - maxLength: 128 - GameID: - type: integer - format: int32 AssetID: type: integer format: int64 - AssetVersion: - type: integer - format: int64 TargetAssetID: type: integer format: int64 + Operation: + required: + - OperationID + - Date + - Owner + - Status + - StatusMessage + - Path + type: object + properties: + OperationID: + type: integer + format: int32 + Date: + type: integer + format: int64 + Owner: + type: integer + format: int64 + Status: + type: integer + format: int32 + StatusMessage: + type: string + maxLength: 256 + Path: + type: string + maxLength: 128 Submission: required: - ID @@ -1277,30 +1328,14 @@ components: StatusMessage: type: string maxLength: 256 - SubmissionCreate: + SubmissionTriggerCreate: required: - - DisplayName - - Creator - - GameID - AssetID - - AssetVersion type: object properties: - DisplayName: - type: string - maxLength: 128 - Creator: - type: string - maxLength: 128 - GameID: - type: integer - format: int32 AssetID: type: integer format: int64 - AssetVersion: - type: integer - format: int64 ReleaseInfo: required: - SubmissionID diff --git a/pkg/api/oas_client_gen.go b/pkg/api/oas_client_gen.go index 714161e..3fac351 100644 --- a/pkg/api/oas_client_gen.go +++ b/pkg/api/oas_client_gen.go @@ -139,10 +139,10 @@ type Invoker interface { ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error // CreateMapfix invokes createMapfix operation. // - // Create new mapfix. + // Trigger the validator to create a mapfix. // // POST /mapfixes - CreateMapfix(ctx context.Context, request *MapfixCreate) (*ID, error) + CreateMapfix(ctx context.Context, request *MapfixTriggerCreate) (*OperationID, error) // CreateScript invokes createScript operation. // // Create a new script. @@ -157,10 +157,10 @@ type Invoker interface { CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ID, error) // CreateSubmission invokes createSubmission operation. // - // Create new submission. + // Trigger the validator to create a new submission. // // POST /submissions - CreateSubmission(ctx context.Context, request *SubmissionCreate) (*ID, error) + CreateSubmission(ctx context.Context, request *SubmissionTriggerCreate) (*OperationID, error) // DeleteScript invokes deleteScript operation. // // Delete the specified script by ID. @@ -185,6 +185,12 @@ type Invoker interface { // // GET /mapfixes/{MapfixID} GetMapfix(ctx context.Context, params GetMapfixParams) (*Mapfix, error) + // GetOperation invokes getOperation operation. + // + // Retrieve operation with ID. + // + // GET /operations/{OperationID} + GetOperation(ctx context.Context, params GetOperationParams) (*Operation, error) // GetScript invokes getScript operation. // // Get the specified script by ID. @@ -2578,15 +2584,15 @@ func (c *Client) sendActionSubmissionValidated(ctx context.Context, params Actio // CreateMapfix invokes createMapfix operation. // -// Create new mapfix. +// Trigger the validator to create a mapfix. // // POST /mapfixes -func (c *Client) CreateMapfix(ctx context.Context, request *MapfixCreate) (*ID, error) { +func (c *Client) CreateMapfix(ctx context.Context, request *MapfixTriggerCreate) (*OperationID, error) { res, err := c.sendCreateMapfix(ctx, request) return res, err } -func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixCreate) (res *ID, err error) { +func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixTriggerCreate) (res *OperationID, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("createMapfix"), semconv.HTTPRequestMethodKey.String("POST"), @@ -2902,15 +2908,15 @@ func (c *Client) sendCreateScriptPolicy(ctx context.Context, request *ScriptPoli // CreateSubmission invokes createSubmission operation. // -// Create new submission. +// Trigger the validator to create a new submission. // // POST /submissions -func (c *Client) CreateSubmission(ctx context.Context, request *SubmissionCreate) (*ID, error) { +func (c *Client) CreateSubmission(ctx context.Context, request *SubmissionTriggerCreate) (*OperationID, error) { res, err := c.sendCreateSubmission(ctx, request) return res, err } -func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionCreate) (res *ID, err error) { +func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionTriggerCreate) (res *OperationID, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("createSubmission"), semconv.HTTPRequestMethodKey.String("POST"), @@ -3434,6 +3440,129 @@ func (c *Client) sendGetMapfix(ctx context.Context, params GetMapfixParams) (res return result, nil } +// GetOperation invokes getOperation operation. +// +// Retrieve operation with ID. +// +// GET /operations/{OperationID} +func (c *Client) GetOperation(ctx context.Context, params GetOperationParams) (*Operation, error) { + res, err := c.sendGetOperation(ctx, params) + return res, err +} + +func (c *Client) sendGetOperation(ctx context.Context, params GetOperationParams) (res *Operation, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getOperation"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/operations/{OperationID}"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, GetOperationOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [2]string + pathParts[0] = "/operations/" + { + // Encode "OperationID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "OperationID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int32ToString(params.OperationID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:CookieAuth" + switch err := c.securityCookieAuth(ctx, GetOperationOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"CookieAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeGetOperationResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // GetScript invokes getScript operation. // // Get the specified script by ID. diff --git a/pkg/api/oas_handlers_gen.go b/pkg/api/oas_handlers_gen.go index 95e1799..d667263 100644 --- a/pkg/api/oas_handlers_gen.go +++ b/pkg/api/oas_handlers_gen.go @@ -3542,7 +3542,7 @@ func (s *Server) handleActionSubmissionValidatedRequest(args [1]string, argsEsca // handleCreateMapfixRequest handles createMapfix operation. // -// Create new mapfix. +// Trigger the validator to create a mapfix. // // POST /mapfixes func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { @@ -3676,12 +3676,12 @@ func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w h } }() - var response *ID + var response *OperationID if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, OperationName: CreateMapfixOperation, - OperationSummary: "Create new mapfix", + OperationSummary: "Trigger the validator to create a mapfix", OperationID: "createMapfix", Body: request, Params: middleware.Parameters{}, @@ -3689,9 +3689,9 @@ func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w h } type ( - Request = *MapfixCreate + Request = *MapfixTriggerCreate Params = struct{} - Response = *ID + Response = *OperationID ) response, err = middleware.HookMiddleware[ Request, @@ -4127,7 +4127,7 @@ func (s *Server) handleCreateScriptPolicyRequest(args [0]string, argsEscaped boo // handleCreateSubmissionRequest handles createSubmission operation. // -// Create new submission. +// Trigger the validator to create a new submission. // // POST /submissions func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { @@ -4261,12 +4261,12 @@ func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool, } }() - var response *ID + var response *OperationID if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, OperationName: CreateSubmissionOperation, - OperationSummary: "Create new submission", + OperationSummary: "Trigger the validator to create a new submission", OperationID: "createSubmission", Body: request, Params: middleware.Parameters{}, @@ -4274,9 +4274,9 @@ func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool, } type ( - Request = *SubmissionCreate + Request = *SubmissionTriggerCreate Params = struct{} - Response = *ID + Response = *OperationID ) response, err = middleware.HookMiddleware[ Request, @@ -5008,6 +5008,201 @@ func (s *Server) handleGetMapfixRequest(args [1]string, argsEscaped bool, w http } } +// handleGetOperationRequest handles getOperation operation. +// +// Retrieve operation with ID. +// +// GET /operations/{OperationID} +func (s *Server) handleGetOperationRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getOperation"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/operations/{OperationID}"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), GetOperationOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: GetOperationOperation, + ID: "getOperation", + } + ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securityCookieAuth(ctx, GetOperationOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "CookieAuth", + Err: err, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security:CookieAuth", err) + } + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security", err) + } + return + } + } + params, err := decodeGetOperationParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *Operation + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: GetOperationOperation, + OperationSummary: "Retrieve operation with ID", + OperationID: "getOperation", + Body: nil, + Params: middleware.Parameters{ + { + Name: "OperationID", + In: "path", + }: params.OperationID, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = GetOperationParams + Response = *Operation + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackGetOperationParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetOperation(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.GetOperation(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeGetOperationResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleGetScriptRequest handles getScript operation. // // Get the specified script by ID. diff --git a/pkg/api/oas_json_gen.go b/pkg/api/oas_json_gen.go index 9061475..6833f15 100644 --- a/pkg/api/oas_json_gen.go +++ b/pkg/api/oas_json_gen.go @@ -688,96 +688,40 @@ func (s *Mapfix) UnmarshalJSON(data []byte) error { } // Encode implements json.Marshaler. -func (s *MapfixCreate) Encode(e *jx.Encoder) { +func (s *MapfixTriggerCreate) Encode(e *jx.Encoder) { e.ObjStart() s.encodeFields(e) e.ObjEnd() } // encodeFields encodes fields. -func (s *MapfixCreate) encodeFields(e *jx.Encoder) { - { - e.FieldStart("DisplayName") - e.Str(s.DisplayName) - } - { - e.FieldStart("Creator") - e.Str(s.Creator) - } - { - e.FieldStart("GameID") - e.Int32(s.GameID) - } +func (s *MapfixTriggerCreate) encodeFields(e *jx.Encoder) { { e.FieldStart("AssetID") e.Int64(s.AssetID) } - { - e.FieldStart("AssetVersion") - e.Int64(s.AssetVersion) - } { e.FieldStart("TargetAssetID") e.Int64(s.TargetAssetID) } } -var jsonFieldsNameOfMapfixCreate = [6]string{ - 0: "DisplayName", - 1: "Creator", - 2: "GameID", - 3: "AssetID", - 4: "AssetVersion", - 5: "TargetAssetID", +var jsonFieldsNameOfMapfixTriggerCreate = [2]string{ + 0: "AssetID", + 1: "TargetAssetID", } -// Decode decodes MapfixCreate from json. -func (s *MapfixCreate) Decode(d *jx.Decoder) error { +// Decode decodes MapfixTriggerCreate from json. +func (s *MapfixTriggerCreate) Decode(d *jx.Decoder) error { if s == nil { - return errors.New("invalid: unable to decode MapfixCreate to nil") + return errors.New("invalid: unable to decode MapfixTriggerCreate to nil") } var requiredBitSet [1]uint8 if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { switch string(k) { - case "DisplayName": - requiredBitSet[0] |= 1 << 0 - if err := func() error { - v, err := d.Str() - s.DisplayName = string(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"DisplayName\"") - } - case "Creator": - requiredBitSet[0] |= 1 << 1 - if err := func() error { - v, err := d.Str() - s.Creator = string(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"Creator\"") - } - case "GameID": - requiredBitSet[0] |= 1 << 2 - if err := func() error { - v, err := d.Int32() - s.GameID = int32(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"GameID\"") - } case "AssetID": - requiredBitSet[0] |= 1 << 3 + requiredBitSet[0] |= 1 << 0 if err := func() error { v, err := d.Int64() s.AssetID = int64(v) @@ -788,20 +732,8 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"AssetID\"") } - case "AssetVersion": - requiredBitSet[0] |= 1 << 4 - if err := func() error { - v, err := d.Int64() - s.AssetVersion = int64(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"AssetVersion\"") - } case "TargetAssetID": - requiredBitSet[0] |= 1 << 5 + requiredBitSet[0] |= 1 << 1 if err := func() error { v, err := d.Int64() s.TargetAssetID = int64(v) @@ -817,12 +749,12 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error { } return nil }); err != nil { - return errors.Wrap(err, "decode MapfixCreate") + return errors.Wrap(err, "decode MapfixTriggerCreate") } // Validate required fields. var failures []validate.FieldError for i, mask := range [1]uint8{ - 0b00111111, + 0b00000011, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. @@ -834,8 +766,8 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error { bitIdx := bits.TrailingZeros8(result) fieldIdx := i*8 + bitIdx var name string - if fieldIdx < len(jsonFieldsNameOfMapfixCreate) { - name = jsonFieldsNameOfMapfixCreate[fieldIdx] + if fieldIdx < len(jsonFieldsNameOfMapfixTriggerCreate) { + name = jsonFieldsNameOfMapfixTriggerCreate[fieldIdx] } else { name = strconv.Itoa(fieldIdx) } @@ -856,14 +788,291 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error { } // MarshalJSON implements stdjson.Marshaler. -func (s *MapfixCreate) MarshalJSON() ([]byte, error) { +func (s *MapfixTriggerCreate) MarshalJSON() ([]byte, error) { e := jx.Encoder{} s.Encode(&e) return e.Bytes(), nil } // UnmarshalJSON implements stdjson.Unmarshaler. -func (s *MapfixCreate) UnmarshalJSON(data []byte) error { +func (s *MapfixTriggerCreate) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *Operation) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Operation) encodeFields(e *jx.Encoder) { + { + e.FieldStart("OperationID") + e.Int32(s.OperationID) + } + { + e.FieldStart("Date") + e.Int64(s.Date) + } + { + e.FieldStart("Owner") + e.Int64(s.Owner) + } + { + e.FieldStart("Status") + e.Int32(s.Status) + } + { + e.FieldStart("StatusMessage") + e.Str(s.StatusMessage) + } + { + e.FieldStart("Path") + e.Str(s.Path) + } +} + +var jsonFieldsNameOfOperation = [6]string{ + 0: "OperationID", + 1: "Date", + 2: "Owner", + 3: "Status", + 4: "StatusMessage", + 5: "Path", +} + +// Decode decodes Operation from json. +func (s *Operation) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Operation to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "OperationID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int32() + s.OperationID = int32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"OperationID\"") + } + case "Date": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Int64() + s.Date = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Date\"") + } + case "Owner": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Int64() + s.Owner = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Owner\"") + } + case "Status": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + v, err := d.Int32() + s.Status = int32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Status\"") + } + case "StatusMessage": + requiredBitSet[0] |= 1 << 4 + if err := func() error { + v, err := d.Str() + s.StatusMessage = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"StatusMessage\"") + } + case "Path": + requiredBitSet[0] |= 1 << 5 + if err := func() error { + v, err := d.Str() + s.Path = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Path\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Operation") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00111111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfOperation) { + name = jsonFieldsNameOfOperation[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *Operation) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Operation) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *OperationID) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *OperationID) encodeFields(e *jx.Encoder) { + { + e.FieldStart("OperationID") + e.Int32(s.OperationID) + } +} + +var jsonFieldsNameOfOperationID = [1]string{ + 0: "OperationID", +} + +// Decode decodes OperationID from json. +func (s *OperationID) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode OperationID to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "OperationID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int32() + s.OperationID = int32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"OperationID\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode OperationID") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfOperationID) { + name = jsonFieldsNameOfOperationID[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *OperationID) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OperationID) UnmarshalJSON(data []byte) error { d := jx.DecodeBytes(data) return s.Decode(d) } @@ -2434,91 +2643,35 @@ func (s *Submission) UnmarshalJSON(data []byte) error { } // Encode implements json.Marshaler. -func (s *SubmissionCreate) Encode(e *jx.Encoder) { +func (s *SubmissionTriggerCreate) Encode(e *jx.Encoder) { e.ObjStart() s.encodeFields(e) e.ObjEnd() } // encodeFields encodes fields. -func (s *SubmissionCreate) encodeFields(e *jx.Encoder) { - { - e.FieldStart("DisplayName") - e.Str(s.DisplayName) - } - { - e.FieldStart("Creator") - e.Str(s.Creator) - } - { - e.FieldStart("GameID") - e.Int32(s.GameID) - } +func (s *SubmissionTriggerCreate) encodeFields(e *jx.Encoder) { { e.FieldStart("AssetID") e.Int64(s.AssetID) } - { - e.FieldStart("AssetVersion") - e.Int64(s.AssetVersion) - } } -var jsonFieldsNameOfSubmissionCreate = [5]string{ - 0: "DisplayName", - 1: "Creator", - 2: "GameID", - 3: "AssetID", - 4: "AssetVersion", +var jsonFieldsNameOfSubmissionTriggerCreate = [1]string{ + 0: "AssetID", } -// Decode decodes SubmissionCreate from json. -func (s *SubmissionCreate) Decode(d *jx.Decoder) error { +// Decode decodes SubmissionTriggerCreate from json. +func (s *SubmissionTriggerCreate) Decode(d *jx.Decoder) error { if s == nil { - return errors.New("invalid: unable to decode SubmissionCreate to nil") + return errors.New("invalid: unable to decode SubmissionTriggerCreate to nil") } var requiredBitSet [1]uint8 if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { switch string(k) { - case "DisplayName": - requiredBitSet[0] |= 1 << 0 - if err := func() error { - v, err := d.Str() - s.DisplayName = string(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"DisplayName\"") - } - case "Creator": - requiredBitSet[0] |= 1 << 1 - if err := func() error { - v, err := d.Str() - s.Creator = string(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"Creator\"") - } - case "GameID": - requiredBitSet[0] |= 1 << 2 - if err := func() error { - v, err := d.Int32() - s.GameID = int32(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"GameID\"") - } case "AssetID": - requiredBitSet[0] |= 1 << 3 + requiredBitSet[0] |= 1 << 0 if err := func() error { v, err := d.Int64() s.AssetID = int64(v) @@ -2529,29 +2682,17 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"AssetID\"") } - case "AssetVersion": - requiredBitSet[0] |= 1 << 4 - if err := func() error { - v, err := d.Int64() - s.AssetVersion = int64(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"AssetVersion\"") - } default: return d.Skip() } return nil }); err != nil { - return errors.Wrap(err, "decode SubmissionCreate") + return errors.Wrap(err, "decode SubmissionTriggerCreate") } // Validate required fields. var failures []validate.FieldError for i, mask := range [1]uint8{ - 0b00011111, + 0b00000001, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. @@ -2563,8 +2704,8 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error { bitIdx := bits.TrailingZeros8(result) fieldIdx := i*8 + bitIdx var name string - if fieldIdx < len(jsonFieldsNameOfSubmissionCreate) { - name = jsonFieldsNameOfSubmissionCreate[fieldIdx] + if fieldIdx < len(jsonFieldsNameOfSubmissionTriggerCreate) { + name = jsonFieldsNameOfSubmissionTriggerCreate[fieldIdx] } else { name = strconv.Itoa(fieldIdx) } @@ -2585,14 +2726,14 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error { } // MarshalJSON implements stdjson.Marshaler. -func (s *SubmissionCreate) MarshalJSON() ([]byte, error) { +func (s *SubmissionTriggerCreate) MarshalJSON() ([]byte, error) { e := jx.Encoder{} s.Encode(&e) return e.Bytes(), nil } // UnmarshalJSON implements stdjson.Unmarshaler. -func (s *SubmissionCreate) UnmarshalJSON(data []byte) error { +func (s *SubmissionTriggerCreate) UnmarshalJSON(data []byte) error { d := jx.DecodeBytes(data) return s.Decode(d) } diff --git a/pkg/api/oas_operations_gen.go b/pkg/api/oas_operations_gen.go index f2235e9..9806eb1 100644 --- a/pkg/api/oas_operations_gen.go +++ b/pkg/api/oas_operations_gen.go @@ -32,6 +32,7 @@ const ( DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy" GetMapOperation OperationName = "GetMap" GetMapfixOperation OperationName = "GetMapfix" + GetOperationOperation OperationName = "GetOperation" GetScriptOperation OperationName = "GetScript" GetScriptPolicyOperation OperationName = "GetScriptPolicy" GetSubmissionOperation OperationName = "GetSubmission" diff --git a/pkg/api/oas_parameters_gen.go b/pkg/api/oas_parameters_gen.go index 6ea7cb4..b2cb6ee 100644 --- a/pkg/api/oas_parameters_gen.go +++ b/pkg/api/oas_parameters_gen.go @@ -1467,6 +1467,72 @@ func decodeGetMapfixParams(args [1]string, argsEscaped bool, r *http.Request) (p return params, nil } +// GetOperationParams is parameters of getOperation operation. +type GetOperationParams struct { + // The unique identifier for a long-running operation. + OperationID int32 +} + +func unpackGetOperationParams(packed middleware.Parameters) (params GetOperationParams) { + { + key := middleware.ParameterKey{ + Name: "OperationID", + In: "path", + } + params.OperationID = packed[key].(int32) + } + return params +} + +func decodeGetOperationParams(args [1]string, argsEscaped bool, r *http.Request) (params GetOperationParams, _ error) { + // Decode path: OperationID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "OperationID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.OperationID = c + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "OperationID", + In: "path", + Err: err, + } + } + return params, nil +} + // GetScriptParams is parameters of getScript operation. type GetScriptParams struct { // The unique identifier for a script. diff --git a/pkg/api/oas_request_decoders_gen.go b/pkg/api/oas_request_decoders_gen.go index da76dc0..1fa2247 100644 --- a/pkg/api/oas_request_decoders_gen.go +++ b/pkg/api/oas_request_decoders_gen.go @@ -16,7 +16,7 @@ import ( ) func (s *Server) decodeCreateMapfixRequest(r *http.Request) ( - req *MapfixCreate, + req *MapfixTriggerCreate, close func() error, rerr error, ) { @@ -55,7 +55,7 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) ( d := jx.DecodeBytes(buf) - var request MapfixCreate + var request MapfixTriggerCreate if err := func() error { if err := request.Decode(d); err != nil { return err @@ -72,14 +72,6 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) ( } return req, close, err } - if err := func() error { - if err := request.Validate(); err != nil { - return err - } - return nil - }(); err != nil { - return req, close, errors.Wrap(err, "validate") - } return &request, close, nil default: return req, close, validate.InvalidContentType(ct) @@ -221,7 +213,7 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) ( } func (s *Server) decodeCreateSubmissionRequest(r *http.Request) ( - req *SubmissionCreate, + req *SubmissionTriggerCreate, close func() error, rerr error, ) { @@ -260,7 +252,7 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) ( d := jx.DecodeBytes(buf) - var request SubmissionCreate + var request SubmissionTriggerCreate if err := func() error { if err := request.Decode(d); err != nil { return err @@ -277,14 +269,6 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) ( } return req, close, err } - if err := func() error { - if err := request.Validate(); err != nil { - return err - } - return nil - }(); err != nil { - return req, close, errors.Wrap(err, "validate") - } return &request, close, nil default: return req, close, validate.InvalidContentType(ct) diff --git a/pkg/api/oas_request_encoders_gen.go b/pkg/api/oas_request_encoders_gen.go index b8686b0..52f1046 100644 --- a/pkg/api/oas_request_encoders_gen.go +++ b/pkg/api/oas_request_encoders_gen.go @@ -12,7 +12,7 @@ import ( ) func encodeCreateMapfixRequest( - req *MapfixCreate, + req *MapfixTriggerCreate, r *http.Request, ) error { const contentType = "application/json" @@ -54,7 +54,7 @@ func encodeCreateScriptPolicyRequest( } func encodeCreateSubmissionRequest( - req *SubmissionCreate, + req *SubmissionTriggerCreate, r *http.Request, ) error { const contentType = "application/json" diff --git a/pkg/api/oas_response_decoders_gen.go b/pkg/api/oas_response_decoders_gen.go index 6a6b258..197eacd 100644 --- a/pkg/api/oas_response_decoders_gen.go +++ b/pkg/api/oas_response_decoders_gen.go @@ -933,7 +933,7 @@ func decodeActionSubmissionValidatedResponse(resp *http.Response) (res *ActionSu return res, errors.Wrap(defRes, "error") } -func decodeCreateMapfixResponse(resp *http.Response) (res *ID, _ error) { +func decodeCreateMapfixResponse(resp *http.Response) (res *OperationID, _ error) { switch resp.StatusCode { case 201: // Code 201. @@ -949,7 +949,7 @@ func decodeCreateMapfixResponse(resp *http.Response) (res *ID, _ error) { } d := jx.DecodeBytes(buf) - var response ID + var response OperationID if err := func() error { if err := response.Decode(d); err != nil { return err @@ -1182,7 +1182,7 @@ func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ID, _ error) { return res, errors.Wrap(defRes, "error") } -func decodeCreateSubmissionResponse(resp *http.Response) (res *ID, _ error) { +func decodeCreateSubmissionResponse(resp *http.Response) (res *OperationID, _ error) { switch resp.StatusCode { case 201: // Code 201. @@ -1198,7 +1198,7 @@ func decodeCreateSubmissionResponse(resp *http.Response) (res *ID, _ error) { } d := jx.DecodeBytes(buf) - var response ID + var response OperationID if err := func() error { if err := response.Decode(d); err != nil { return err @@ -1551,6 +1551,98 @@ func decodeGetMapfixResponse(resp *http.Response) (res *Mapfix, _ error) { return res, errors.Wrap(defRes, "error") } +func decodeGetOperationResponse(resp *http.Response) (res *Operation, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Operation + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeGetScriptResponse(resp *http.Response) (res *Script, _ error) { switch resp.StatusCode { case 200: diff --git a/pkg/api/oas_response_encoders_gen.go b/pkg/api/oas_response_encoders_gen.go index ba6c549..58f61b3 100644 --- a/pkg/api/oas_response_encoders_gen.go +++ b/pkg/api/oas_response_encoders_gen.go @@ -139,7 +139,7 @@ func encodeActionSubmissionValidatedResponse(response *ActionSubmissionValidated return nil } -func encodeCreateMapfixResponse(response *ID, w http.ResponseWriter, span trace.Span) error { +func encodeCreateMapfixResponse(response *OperationID, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(201) span.SetStatus(codes.Ok, http.StatusText(201)) @@ -181,7 +181,7 @@ func encodeCreateScriptPolicyResponse(response *ID, w http.ResponseWriter, span return nil } -func encodeCreateSubmissionResponse(response *ID, w http.ResponseWriter, span trace.Span) error { +func encodeCreateSubmissionResponse(response *OperationID, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(201) span.SetStatus(codes.Ok, http.StatusText(201)) @@ -237,6 +237,20 @@ func encodeGetMapfixResponse(response *Mapfix, w http.ResponseWriter, span trace return nil } +func encodeGetOperationResponse(response *Operation, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + func encodeGetScriptResponse(response *Script, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) diff --git a/pkg/api/oas_router_gen.go b/pkg/api/oas_router_gen.go index 6ebc1ef..414d7e4 100644 --- a/pkg/api/oas_router_gen.go +++ b/pkg/api/oas_router_gen.go @@ -494,6 +494,37 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } + case 'o': // Prefix: "operations/" + + if l := len("operations/"); len(elem) >= l && elem[0:l] == "operations/" { + elem = elem[l:] + } else { + break + } + + // Param: "OperationID" + // Leaf parameter, slashes are prohibited + idx := strings.IndexByte(elem, '/') + if idx >= 0 { + break + } + args[0] = elem + elem = "" + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleGetOperationRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + case 'r': // Prefix: "release-submissions" if l := len("release-submissions"); len(elem) >= l && elem[0:l] == "release-submissions" { @@ -1233,7 +1264,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { return r, true case "POST": r.name = CreateMapfixOperation - r.summary = "Create new mapfix" + r.summary = "Trigger the validator to create a mapfix" r.operationID = "createMapfix" r.pathPattern = "/mapfixes" r.args = args @@ -1674,6 +1705,39 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } + case 'o': // Prefix: "operations/" + + if l := len("operations/"); len(elem) >= l && elem[0:l] == "operations/" { + elem = elem[l:] + } else { + break + } + + // Param: "OperationID" + // Leaf parameter, slashes are prohibited + idx := strings.IndexByte(elem, '/') + if idx >= 0 { + break + } + args[0] = elem + elem = "" + + if len(elem) == 0 { + // Leaf node. + switch method { + case "GET": + r.name = GetOperationOperation + r.summary = "Retrieve operation with ID" + r.operationID = "getOperation" + r.pathPattern = "/operations/{OperationID}" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + case 'r': // Prefix: "release-submissions" if l := len("release-submissions"); len(elem) >= l && elem[0:l] == "release-submissions" { @@ -1994,7 +2058,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { return r, true case "POST": r.name = CreateSubmissionOperation - r.summary = "Create new submission" + r.summary = "Trigger the validator to create a new submission" r.operationID = "createSubmission" r.pathPattern = "/submissions" r.args = args diff --git a/pkg/api/oas_schemas_gen.go b/pkg/api/oas_schemas_gen.go index 4c368c2..5141d86 100644 --- a/pkg/api/oas_schemas_gen.go +++ b/pkg/api/oas_schemas_gen.go @@ -359,74 +359,115 @@ func (s *Mapfix) SetStatusMessage(val string) { s.StatusMessage = val } -// Ref: #/components/schemas/MapfixCreate -type MapfixCreate struct { - DisplayName string `json:"DisplayName"` - Creator string `json:"Creator"` - GameID int32 `json:"GameID"` - AssetID int64 `json:"AssetID"` - AssetVersion int64 `json:"AssetVersion"` - TargetAssetID int64 `json:"TargetAssetID"` -} - -// GetDisplayName returns the value of DisplayName. -func (s *MapfixCreate) GetDisplayName() string { - return s.DisplayName -} - -// GetCreator returns the value of Creator. -func (s *MapfixCreate) GetCreator() string { - return s.Creator -} - -// GetGameID returns the value of GameID. -func (s *MapfixCreate) GetGameID() int32 { - return s.GameID +// Ref: #/components/schemas/MapfixTriggerCreate +type MapfixTriggerCreate struct { + AssetID int64 `json:"AssetID"` + TargetAssetID int64 `json:"TargetAssetID"` } // GetAssetID returns the value of AssetID. -func (s *MapfixCreate) GetAssetID() int64 { +func (s *MapfixTriggerCreate) GetAssetID() int64 { return s.AssetID } -// GetAssetVersion returns the value of AssetVersion. -func (s *MapfixCreate) GetAssetVersion() int64 { - return s.AssetVersion -} - // GetTargetAssetID returns the value of TargetAssetID. -func (s *MapfixCreate) GetTargetAssetID() int64 { +func (s *MapfixTriggerCreate) GetTargetAssetID() int64 { return s.TargetAssetID } -// SetDisplayName sets the value of DisplayName. -func (s *MapfixCreate) SetDisplayName(val string) { - s.DisplayName = val -} - -// SetCreator sets the value of Creator. -func (s *MapfixCreate) SetCreator(val string) { - s.Creator = val -} - -// SetGameID sets the value of GameID. -func (s *MapfixCreate) SetGameID(val int32) { - s.GameID = val -} - // SetAssetID sets the value of AssetID. -func (s *MapfixCreate) SetAssetID(val int64) { +func (s *MapfixTriggerCreate) SetAssetID(val int64) { s.AssetID = val } -// SetAssetVersion sets the value of AssetVersion. -func (s *MapfixCreate) SetAssetVersion(val int64) { - s.AssetVersion = val +// SetTargetAssetID sets the value of TargetAssetID. +func (s *MapfixTriggerCreate) SetTargetAssetID(val int64) { + s.TargetAssetID = val } -// SetTargetAssetID sets the value of TargetAssetID. -func (s *MapfixCreate) SetTargetAssetID(val int64) { - s.TargetAssetID = val +// Ref: #/components/schemas/Operation +type Operation struct { + OperationID int32 `json:"OperationID"` + Date int64 `json:"Date"` + Owner int64 `json:"Owner"` + Status int32 `json:"Status"` + StatusMessage string `json:"StatusMessage"` + Path string `json:"Path"` +} + +// GetOperationID returns the value of OperationID. +func (s *Operation) GetOperationID() int32 { + return s.OperationID +} + +// GetDate returns the value of Date. +func (s *Operation) GetDate() int64 { + return s.Date +} + +// GetOwner returns the value of Owner. +func (s *Operation) GetOwner() int64 { + return s.Owner +} + +// GetStatus returns the value of Status. +func (s *Operation) GetStatus() int32 { + return s.Status +} + +// GetStatusMessage returns the value of StatusMessage. +func (s *Operation) GetStatusMessage() string { + return s.StatusMessage +} + +// GetPath returns the value of Path. +func (s *Operation) GetPath() string { + return s.Path +} + +// SetOperationID sets the value of OperationID. +func (s *Operation) SetOperationID(val int32) { + s.OperationID = val +} + +// SetDate sets the value of Date. +func (s *Operation) SetDate(val int64) { + s.Date = val +} + +// SetOwner sets the value of Owner. +func (s *Operation) SetOwner(val int64) { + s.Owner = val +} + +// SetStatus sets the value of Status. +func (s *Operation) SetStatus(val int32) { + s.Status = val +} + +// SetStatusMessage sets the value of StatusMessage. +func (s *Operation) SetStatusMessage(val string) { + s.StatusMessage = val +} + +// SetPath sets the value of Path. +func (s *Operation) SetPath(val string) { + s.Path = val +} + +// Ref: #/components/schemas/OperationID +type OperationID struct { + OperationID int32 `json:"OperationID"` +} + +// GetOperationID returns the value of OperationID. +func (s *OperationID) GetOperationID() int32 { + return s.OperationID +} + +// SetOperationID sets the value of OperationID. +func (s *OperationID) SetOperationID(val int32) { + s.OperationID = val } // NewOptInt32 returns new OptInt32 with value set to v. @@ -1096,65 +1137,21 @@ func (s *Submission) SetStatusMessage(val string) { s.StatusMessage = val } -// Ref: #/components/schemas/SubmissionCreate -type SubmissionCreate struct { - DisplayName string `json:"DisplayName"` - Creator string `json:"Creator"` - GameID int32 `json:"GameID"` - AssetID int64 `json:"AssetID"` - AssetVersion int64 `json:"AssetVersion"` -} - -// GetDisplayName returns the value of DisplayName. -func (s *SubmissionCreate) GetDisplayName() string { - return s.DisplayName -} - -// GetCreator returns the value of Creator. -func (s *SubmissionCreate) GetCreator() string { - return s.Creator -} - -// GetGameID returns the value of GameID. -func (s *SubmissionCreate) GetGameID() int32 { - return s.GameID +// Ref: #/components/schemas/SubmissionTriggerCreate +type SubmissionTriggerCreate struct { + AssetID int64 `json:"AssetID"` } // GetAssetID returns the value of AssetID. -func (s *SubmissionCreate) GetAssetID() int64 { +func (s *SubmissionTriggerCreate) GetAssetID() int64 { return s.AssetID } -// GetAssetVersion returns the value of AssetVersion. -func (s *SubmissionCreate) GetAssetVersion() int64 { - return s.AssetVersion -} - -// SetDisplayName sets the value of DisplayName. -func (s *SubmissionCreate) SetDisplayName(val string) { - s.DisplayName = val -} - -// SetCreator sets the value of Creator. -func (s *SubmissionCreate) SetCreator(val string) { - s.Creator = val -} - -// SetGameID sets the value of GameID. -func (s *SubmissionCreate) SetGameID(val int32) { - s.GameID = val -} - // SetAssetID sets the value of AssetID. -func (s *SubmissionCreate) SetAssetID(val int64) { +func (s *SubmissionTriggerCreate) SetAssetID(val int64) { s.AssetID = val } -// SetAssetVersion sets the value of AssetVersion. -func (s *SubmissionCreate) SetAssetVersion(val int64) { - s.AssetVersion = val -} - // UpdateMapfixModelNoContent is response for UpdateMapfixModel operation. type UpdateMapfixModelNoContent struct{} diff --git a/pkg/api/oas_server_gen.go b/pkg/api/oas_server_gen.go index 9f294eb..b06f259 100644 --- a/pkg/api/oas_server_gen.go +++ b/pkg/api/oas_server_gen.go @@ -118,10 +118,10 @@ type Handler interface { ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error // CreateMapfix implements createMapfix operation. // - // Create new mapfix. + // Trigger the validator to create a mapfix. // // POST /mapfixes - CreateMapfix(ctx context.Context, req *MapfixCreate) (*ID, error) + CreateMapfix(ctx context.Context, req *MapfixTriggerCreate) (*OperationID, error) // CreateScript implements createScript operation. // // Create a new script. @@ -136,10 +136,10 @@ type Handler interface { CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (*ID, error) // CreateSubmission implements createSubmission operation. // - // Create new submission. + // Trigger the validator to create a new submission. // // POST /submissions - CreateSubmission(ctx context.Context, req *SubmissionCreate) (*ID, error) + CreateSubmission(ctx context.Context, req *SubmissionTriggerCreate) (*OperationID, error) // DeleteScript implements deleteScript operation. // // Delete the specified script by ID. @@ -164,6 +164,12 @@ type Handler interface { // // GET /mapfixes/{MapfixID} GetMapfix(ctx context.Context, params GetMapfixParams) (*Mapfix, error) + // GetOperation implements getOperation operation. + // + // Retrieve operation with ID. + // + // GET /operations/{OperationID} + GetOperation(ctx context.Context, params GetOperationParams) (*Operation, error) // GetScript implements getScript operation. // // Get the specified script by ID. diff --git a/pkg/api/oas_unimplemented_gen.go b/pkg/api/oas_unimplemented_gen.go index 738f398..3e92782 100644 --- a/pkg/api/oas_unimplemented_gen.go +++ b/pkg/api/oas_unimplemented_gen.go @@ -177,10 +177,10 @@ func (UnimplementedHandler) ActionSubmissionValidated(ctx context.Context, param // CreateMapfix implements createMapfix operation. // -// Create new mapfix. +// Trigger the validator to create a mapfix. // // POST /mapfixes -func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixCreate) (r *ID, _ error) { +func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixTriggerCreate) (r *OperationID, _ error) { return r, ht.ErrNotImplemented } @@ -204,10 +204,10 @@ func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptP // CreateSubmission implements createSubmission operation. // -// Create new submission. +// Trigger the validator to create a new submission. // // POST /submissions -func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *SubmissionCreate) (r *ID, _ error) { +func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *SubmissionTriggerCreate) (r *OperationID, _ error) { return r, ht.ErrNotImplemented } @@ -247,6 +247,15 @@ func (UnimplementedHandler) GetMapfix(ctx context.Context, params GetMapfixParam return r, ht.ErrNotImplemented } +// GetOperation implements getOperation operation. +// +// Retrieve operation with ID. +// +// GET /operations/{OperationID} +func (UnimplementedHandler) GetOperation(ctx context.Context, params GetOperationParams) (r *Operation, _ error) { + return r, ht.ErrNotImplemented +} + // GetScript implements getScript operation. // // Get the specified script by ID. diff --git a/pkg/api/oas_validators_gen.go b/pkg/api/oas_validators_gen.go index 4dc04e6..006fce8 100644 --- a/pkg/api/oas_validators_gen.go +++ b/pkg/api/oas_validators_gen.go @@ -127,7 +127,7 @@ func (s *Mapfix) Validate() error { return nil } -func (s *MapfixCreate) Validate() error { +func (s *Operation) Validate() error { if s == nil { return validate.ErrNilPointer } @@ -137,18 +137,18 @@ func (s *MapfixCreate) Validate() error { if err := (validate.String{ MinLength: 0, MinLengthSet: false, - MaxLength: 128, + MaxLength: 256, MaxLengthSet: true, Email: false, Hostname: false, Regex: nil, - }).Validate(string(s.DisplayName)); err != nil { + }).Validate(string(s.StatusMessage)); err != nil { return errors.Wrap(err, "string") } return nil }(); err != nil { failures = append(failures, validate.FieldError{ - Name: "DisplayName", + Name: "StatusMessage", Error: err, }) } @@ -161,13 +161,13 @@ func (s *MapfixCreate) Validate() error { Email: false, Hostname: false, Regex: nil, - }).Validate(string(s.Creator)); err != nil { + }).Validate(string(s.Path)); err != nil { return errors.Wrap(err, "string") } return nil }(); err != nil { failures = append(failures, validate.FieldError{ - Name: "Creator", + Name: "Path", Error: err, }) } @@ -460,56 +460,6 @@ func (s *Submission) Validate() error { return nil } -func (s *SubmissionCreate) Validate() error { - if s == nil { - return validate.ErrNilPointer - } - - var failures []validate.FieldError - if err := func() error { - if err := (validate.String{ - MinLength: 0, - MinLengthSet: false, - MaxLength: 128, - MaxLengthSet: true, - Email: false, - Hostname: false, - Regex: nil, - }).Validate(string(s.DisplayName)); err != nil { - return errors.Wrap(err, "string") - } - return nil - }(); err != nil { - failures = append(failures, validate.FieldError{ - Name: "DisplayName", - Error: 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(s.Creator)); err != nil { - return errors.Wrap(err, "string") - } - return nil - }(); err != nil { - failures = append(failures, validate.FieldError{ - Name: "Creator", - Error: err, - }) - } - if len(failures) > 0 { - return &validate.Error{Fields: failures} - } - return nil -} - func (s *User) Validate() error { if s == nil { return validate.ErrNilPointer diff --git a/pkg/datastore/datastore.go b/pkg/datastore/datastore.go index 1cc3f48..82eda8f 100644 --- a/pkg/datastore/datastore.go +++ b/pkg/datastore/datastore.go @@ -23,6 +23,7 @@ const ( type Datastore interface { Mapfixes() Mapfixes + Operations() Operations Submissions() Submissions Scripts() Scripts ScriptPolicy() ScriptPolicy @@ -39,6 +40,13 @@ type Mapfixes interface { List(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) ([]model.Mapfix, error) } +type Operations interface { + Get(ctx context.Context, id int32) (model.Operation, error) + Create(ctx context.Context, smap model.Operation) (model.Operation, error) + Update(ctx context.Context, id int32, values OptionalMap) error + Delete(ctx context.Context, id int32) error +} + type Submissions interface { Get(ctx context.Context, id int64) (model.Submission, error) GetList(ctx context.Context, id []int64) ([]model.Submission, error) diff --git a/pkg/datastore/gormstore/db.go b/pkg/datastore/gormstore/db.go index 01d001c..e4d0ed3 100644 --- a/pkg/datastore/gormstore/db.go +++ b/pkg/datastore/gormstore/db.go @@ -32,6 +32,7 @@ func New(ctx *cli.Context) (datastore.Datastore, error) { if ctx.Bool("migrate") { if err := db.AutoMigrate( &model.Mapfix{}, + &model.Operation{}, &model.Submission{}, &model.Script{}, &model.ScriptPolicy{}, diff --git a/pkg/datastore/gormstore/gormstore.go b/pkg/datastore/gormstore/gormstore.go index da16895..7d88b12 100644 --- a/pkg/datastore/gormstore/gormstore.go +++ b/pkg/datastore/gormstore/gormstore.go @@ -13,6 +13,10 @@ func (g Gormstore) Mapfixes() datastore.Mapfixes { return &Mapfixes{db: g.db} } +func (g Gormstore) Operations() datastore.Operations { + return &Operations{db: g.db} +} + func (g Gormstore) Submissions() datastore.Submissions { return &Submissions{db: g.db} } diff --git a/pkg/datastore/gormstore/operations.go b/pkg/datastore/gormstore/operations.go new file mode 100644 index 0000000..bf49c86 --- /dev/null +++ b/pkg/datastore/gormstore/operations.go @@ -0,0 +1,55 @@ +package gormstore + +import ( + "context" + "errors" + + "git.itzana.me/strafesnet/maps-service/pkg/datastore" + "git.itzana.me/strafesnet/maps-service/pkg/model" + "gorm.io/gorm" +) + +type Operations struct { + db *gorm.DB +} + +func (env *Operations) Get(ctx context.Context, id int32) (model.Operation, error) { + var operation model.Operation + if err := env.db.First(&operation, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return operation, datastore.ErrNotExist + } + return operation, err + } + return operation, nil +} + +func (env *Operations) Create(ctx context.Context, smap model.Operation) (model.Operation, error) { + if err := env.db.Create(&smap).Error; err != nil { + return smap, err + } + + return smap, nil +} + +func (env *Operations) Update(ctx context.Context, id int32, values datastore.OptionalMap) error { + if err := env.db.Model(&model.Operation{}).Where("id = ?", id).Updates(values.Map()).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return datastore.ErrNotExist + } + return err + } + + return nil +} + +func (env *Operations) Delete(ctx context.Context, id int32) error { + if err := env.db.Delete(&model.Operation{}, id).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return datastore.ErrNotExist + } + return err + } + + return nil +} diff --git a/pkg/internal/oas_client_gen.go b/pkg/internal/oas_client_gen.go index 8c64dc5..21152b2 100644 --- a/pkg/internal/oas_client_gen.go +++ b/pkg/internal/oas_client_gen.go @@ -46,6 +46,12 @@ type Invoker interface { // // POST /mapfixes/{MapfixID}/status/validator-validated ActionMapfixValidated(ctx context.Context, params ActionMapfixValidatedParams) error + // ActionOperationFailed invokes actionOperationFailed operation. + // + // (Internal endpoint) Fail an operation and write a StatusMessage. + // + // POST /operations/{OperationID}/failed + ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error // ActionSubmissionAccepted invokes actionSubmissionAccepted operation. // // (Internal endpoint) Role Validator changes status from Validating -> Accepted. @@ -64,6 +70,12 @@ type Invoker interface { // // POST /submissions/{SubmissionID}/status/validator-validated ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error + // CreateMapfix invokes createMapfix operation. + // + // Create a mapfix. + // + // POST /mapfixes + CreateMapfix(ctx context.Context, request *MapfixCreate) (*ID, error) // CreateScript invokes createScript operation. // // Create a new script. @@ -76,6 +88,12 @@ type Invoker interface { // // POST /script-policy CreateScriptPolicy(ctx context.Context, request *ScriptPolicyCreate) (*ID, error) + // CreateSubmission invokes createSubmission operation. + // + // Create a new submission. + // + // POST /submissions + CreateSubmission(ctx context.Context, request *SubmissionCreate) (*ID, error) // GetScript invokes getScript operation. // // Get the specified script by ID. @@ -446,6 +464,115 @@ func (c *Client) sendActionMapfixValidated(ctx context.Context, params ActionMap return result, nil } +// ActionOperationFailed invokes actionOperationFailed operation. +// +// (Internal endpoint) Fail an operation and write a StatusMessage. +// +// POST /operations/{OperationID}/failed +func (c *Client) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error { + _, err := c.sendActionOperationFailed(ctx, params) + return err +} + +func (c *Client) sendActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) (res *ActionOperationFailedNoContent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionOperationFailed"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/operations/{OperationID}/failed"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ActionOperationFailedOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/operations/" + { + // Encode "OperationID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "OperationID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int32ToString(params.OperationID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/failed" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "StatusMessage" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "StatusMessage", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.StringToString(params.StatusMessage)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeActionOperationFailedResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // ActionSubmissionAccepted invokes actionSubmissionAccepted operation. // // (Internal endpoint) Role Validator changes status from Validating -> Accepted. @@ -755,6 +882,81 @@ func (c *Client) sendActionSubmissionValidated(ctx context.Context, params Actio return result, nil } +// CreateMapfix invokes createMapfix operation. +// +// Create a mapfix. +// +// POST /mapfixes +func (c *Client) CreateMapfix(ctx context.Context, request *MapfixCreate) (*ID, error) { + res, err := c.sendCreateMapfix(ctx, request) + return res, err +} + +func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixCreate) (res *ID, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createMapfix"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, CreateMapfixOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/mapfixes" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeCreateMapfixRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeCreateMapfixResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // CreateScript invokes createScript operation. // // Create a new script. @@ -905,6 +1107,81 @@ func (c *Client) sendCreateScriptPolicy(ctx context.Context, request *ScriptPoli return result, nil } +// CreateSubmission invokes createSubmission operation. +// +// Create a new submission. +// +// POST /submissions +func (c *Client) CreateSubmission(ctx context.Context, request *SubmissionCreate) (*ID, error) { + res, err := c.sendCreateSubmission(ctx, request) + return res, err +} + +func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionCreate) (res *ID, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createSubmission"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, CreateSubmissionOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/submissions" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeCreateSubmissionRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeCreateSubmissionResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // GetScript invokes getScript operation. // // Get the specified script by ID. diff --git a/pkg/internal/oas_handlers_gen.go b/pkg/internal/oas_handlers_gen.go index 346fefb..99977b4 100644 --- a/pkg/internal/oas_handlers_gen.go +++ b/pkg/internal/oas_handlers_gen.go @@ -481,6 +481,159 @@ func (s *Server) handleActionMapfixValidatedRequest(args [1]string, argsEscaped } } +// handleActionOperationFailedRequest handles actionOperationFailed operation. +// +// (Internal endpoint) Fail an operation and write a StatusMessage. +// +// POST /operations/{OperationID}/failed +func (s *Server) handleActionOperationFailedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("actionOperationFailed"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/operations/{OperationID}/failed"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ActionOperationFailedOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ActionOperationFailedOperation, + ID: "actionOperationFailed", + } + ) + params, err := decodeActionOperationFailedParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *ActionOperationFailedNoContent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ActionOperationFailedOperation, + OperationSummary: "(Internal endpoint) Fail an operation and write a StatusMessage", + OperationID: "actionOperationFailed", + Body: nil, + Params: middleware.Parameters{ + { + Name: "OperationID", + In: "path", + }: params.OperationID, + { + Name: "StatusMessage", + In: "query", + }: params.StatusMessage, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ActionOperationFailedParams + Response = *ActionOperationFailedNoContent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackActionOperationFailedParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.ActionOperationFailed(ctx, params) + return response, err + }, + ) + } else { + err = s.h.ActionOperationFailed(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeActionOperationFailedResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleActionSubmissionAcceptedRequest handles actionSubmissionAccepted operation. // // (Internal endpoint) Role Validator changes status from Validating -> Accepted. @@ -936,6 +1089,155 @@ func (s *Server) handleActionSubmissionValidatedRequest(args [1]string, argsEsca } } +// handleCreateMapfixRequest handles createMapfix operation. +// +// Create a mapfix. +// +// POST /mapfixes +func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createMapfix"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), CreateMapfixOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: CreateMapfixOperation, + ID: "createMapfix", + } + ) + request, close, err := s.decodeCreateMapfixRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response *ID + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: CreateMapfixOperation, + OperationSummary: "Create a mapfix", + OperationID: "createMapfix", + Body: request, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = *MapfixCreate + Params = struct{} + Response = *ID + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.CreateMapfix(ctx, request) + return response, err + }, + ) + } else { + response, err = s.h.CreateMapfix(ctx, request) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeCreateMapfixResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleCreateScriptRequest handles createScript operation. // // Create a new script. @@ -1234,6 +1536,155 @@ func (s *Server) handleCreateScriptPolicyRequest(args [0]string, argsEscaped boo } } +// handleCreateSubmissionRequest handles createSubmission operation. +// +// Create a new submission. +// +// POST /submissions +func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createSubmission"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), CreateSubmissionOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: CreateSubmissionOperation, + ID: "createSubmission", + } + ) + request, close, err := s.decodeCreateSubmissionRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response *ID + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: CreateSubmissionOperation, + OperationSummary: "Create a new submission", + OperationID: "createSubmission", + Body: request, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = *SubmissionCreate + Params = struct{} + Response = *ID + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.CreateSubmission(ctx, request) + return response, err + }, + ) + } else { + response, err = s.h.CreateSubmission(ctx, request) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeCreateSubmissionResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleGetScriptRequest handles getScript operation. // // Get the specified script by ID. diff --git a/pkg/internal/oas_json_gen.go b/pkg/internal/oas_json_gen.go index 7b03e7b..7ad30b5 100644 --- a/pkg/internal/oas_json_gen.go +++ b/pkg/internal/oas_json_gen.go @@ -221,6 +221,221 @@ func (s *ID) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *MapfixCreate) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *MapfixCreate) encodeFields(e *jx.Encoder) { + { + e.FieldStart("OperationID") + e.Int32(s.OperationID) + } + { + e.FieldStart("AssetOwner") + e.Int64(s.AssetOwner) + } + { + e.FieldStart("DisplayName") + e.Str(s.DisplayName) + } + { + e.FieldStart("Creator") + e.Str(s.Creator) + } + { + e.FieldStart("GameID") + e.Int32(s.GameID) + } + { + e.FieldStart("AssetID") + e.Int64(s.AssetID) + } + { + e.FieldStart("AssetVersion") + e.Int64(s.AssetVersion) + } + { + e.FieldStart("TargetAssetID") + e.Int64(s.TargetAssetID) + } +} + +var jsonFieldsNameOfMapfixCreate = [8]string{ + 0: "OperationID", + 1: "AssetOwner", + 2: "DisplayName", + 3: "Creator", + 4: "GameID", + 5: "AssetID", + 6: "AssetVersion", + 7: "TargetAssetID", +} + +// Decode decodes MapfixCreate from json. +func (s *MapfixCreate) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode MapfixCreate to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "OperationID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int32() + s.OperationID = int32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"OperationID\"") + } + case "AssetOwner": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Int64() + s.AssetOwner = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"AssetOwner\"") + } + case "DisplayName": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Str() + s.DisplayName = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"DisplayName\"") + } + case "Creator": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + v, err := d.Str() + s.Creator = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Creator\"") + } + case "GameID": + requiredBitSet[0] |= 1 << 4 + if err := func() error { + v, err := d.Int32() + s.GameID = int32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"GameID\"") + } + case "AssetID": + requiredBitSet[0] |= 1 << 5 + if err := func() error { + v, err := d.Int64() + s.AssetID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"AssetID\"") + } + case "AssetVersion": + requiredBitSet[0] |= 1 << 6 + if err := func() error { + v, err := d.Int64() + s.AssetVersion = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"AssetVersion\"") + } + case "TargetAssetID": + requiredBitSet[0] |= 1 << 7 + if err := func() error { + v, err := d.Int64() + s.TargetAssetID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"TargetAssetID\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode MapfixCreate") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b11111111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfMapfixCreate) { + name = jsonFieldsNameOfMapfixCreate[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *MapfixCreate) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *MapfixCreate) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode encodes int64 as json. func (o OptInt64) Encode(e *jx.Encoder) { if !o.Set { @@ -860,3 +1075,201 @@ func (s *ScriptPolicyCreate) UnmarshalJSON(data []byte) error { d := jx.DecodeBytes(data) return s.Decode(d) } + +// Encode implements json.Marshaler. +func (s *SubmissionCreate) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *SubmissionCreate) encodeFields(e *jx.Encoder) { + { + e.FieldStart("OperationID") + e.Int32(s.OperationID) + } + { + e.FieldStart("AssetOwner") + e.Int64(s.AssetOwner) + } + { + e.FieldStart("DisplayName") + e.Str(s.DisplayName) + } + { + e.FieldStart("Creator") + e.Str(s.Creator) + } + { + e.FieldStart("GameID") + e.Int32(s.GameID) + } + { + e.FieldStart("AssetID") + e.Int64(s.AssetID) + } + { + e.FieldStart("AssetVersion") + e.Int64(s.AssetVersion) + } +} + +var jsonFieldsNameOfSubmissionCreate = [7]string{ + 0: "OperationID", + 1: "AssetOwner", + 2: "DisplayName", + 3: "Creator", + 4: "GameID", + 5: "AssetID", + 6: "AssetVersion", +} + +// Decode decodes SubmissionCreate from json. +func (s *SubmissionCreate) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode SubmissionCreate to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "OperationID": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int32() + s.OperationID = int32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"OperationID\"") + } + case "AssetOwner": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Int64() + s.AssetOwner = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"AssetOwner\"") + } + case "DisplayName": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Str() + s.DisplayName = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"DisplayName\"") + } + case "Creator": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + v, err := d.Str() + s.Creator = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Creator\"") + } + case "GameID": + requiredBitSet[0] |= 1 << 4 + if err := func() error { + v, err := d.Int32() + s.GameID = int32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"GameID\"") + } + case "AssetID": + requiredBitSet[0] |= 1 << 5 + if err := func() error { + v, err := d.Int64() + s.AssetID = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"AssetID\"") + } + case "AssetVersion": + requiredBitSet[0] |= 1 << 6 + if err := func() error { + v, err := d.Int64() + s.AssetVersion = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"AssetVersion\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode SubmissionCreate") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b01111111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfSubmissionCreate) { + name = jsonFieldsNameOfSubmissionCreate[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *SubmissionCreate) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *SubmissionCreate) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} diff --git a/pkg/internal/oas_operations_gen.go b/pkg/internal/oas_operations_gen.go index d472077..9e4ecd3 100644 --- a/pkg/internal/oas_operations_gen.go +++ b/pkg/internal/oas_operations_gen.go @@ -9,11 +9,14 @@ const ( ActionMapfixAcceptedOperation OperationName = "ActionMapfixAccepted" ActionMapfixUploadedOperation OperationName = "ActionMapfixUploaded" ActionMapfixValidatedOperation OperationName = "ActionMapfixValidated" + ActionOperationFailedOperation OperationName = "ActionOperationFailed" ActionSubmissionAcceptedOperation OperationName = "ActionSubmissionAccepted" ActionSubmissionUploadedOperation OperationName = "ActionSubmissionUploaded" ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated" + CreateMapfixOperation OperationName = "CreateMapfix" CreateScriptOperation OperationName = "CreateScript" CreateScriptPolicyOperation OperationName = "CreateScriptPolicy" + CreateSubmissionOperation OperationName = "CreateSubmission" GetScriptOperation OperationName = "GetScript" ListScriptPolicyOperation OperationName = "ListScriptPolicy" ListScriptsOperation OperationName = "ListScripts" diff --git a/pkg/internal/oas_parameters_gen.go b/pkg/internal/oas_parameters_gen.go index 5ce0a6b..9d171fc 100644 --- a/pkg/internal/oas_parameters_gen.go +++ b/pkg/internal/oas_parameters_gen.go @@ -274,6 +274,133 @@ func decodeActionMapfixValidatedParams(args [1]string, argsEscaped bool, r *http return params, nil } +// ActionOperationFailedParams is parameters of actionOperationFailed operation. +type ActionOperationFailedParams struct { + // The unique identifier for a long-running operation. + OperationID int32 + StatusMessage string +} + +func unpackActionOperationFailedParams(packed middleware.Parameters) (params ActionOperationFailedParams) { + { + key := middleware.ParameterKey{ + Name: "OperationID", + In: "path", + } + params.OperationID = packed[key].(int32) + } + { + key := middleware.ParameterKey{ + Name: "StatusMessage", + In: "query", + } + params.StatusMessage = packed[key].(string) + } + return params +} + +func decodeActionOperationFailedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionOperationFailedParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + // Decode path: OperationID. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "OperationID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.OperationID = c + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "OperationID", + In: "path", + Err: err, + } + } + // Decode query: StatusMessage. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "StatusMessage", + 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.StatusMessage = c + return nil + }); err != nil { + return err + } + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: true, + MaxLength: 4096, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(params.StatusMessage)); 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: "StatusMessage", + In: "query", + Err: err, + } + } + return params, nil +} + // ActionSubmissionAcceptedParams is parameters of actionSubmissionAccepted operation. type ActionSubmissionAcceptedParams struct { // The unique identifier for a submission. diff --git a/pkg/internal/oas_request_decoders_gen.go b/pkg/internal/oas_request_decoders_gen.go index db79c40..5be039e 100644 --- a/pkg/internal/oas_request_decoders_gen.go +++ b/pkg/internal/oas_request_decoders_gen.go @@ -15,6 +15,77 @@ import ( "github.com/ogen-go/ogen/validate" ) +func (s *Server) decodeCreateMapfixRequest(r *http.Request) ( + req *MapfixCreate, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = multierr.Append(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = multierr.Append(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + if r.ContentLength == 0 { + return req, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + if err != nil { + return req, close, err + } + + if len(buf) == 0 { + return req, close, validate.ErrBodyRequired + } + + d := jx.DecodeBytes(buf) + + var request MapfixCreate + if err := func() error { + if err := request.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return req, close, err + } + if err := func() error { + if err := request.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return req, close, errors.Wrap(err, "validate") + } + return &request, close, nil + default: + return req, close, validate.InvalidContentType(ct) + } +} + func (s *Server) decodeCreateScriptRequest(r *http.Request) ( req *ScriptCreate, close func() error, @@ -148,3 +219,74 @@ func (s *Server) decodeCreateScriptPolicyRequest(r *http.Request) ( return req, close, validate.InvalidContentType(ct) } } + +func (s *Server) decodeCreateSubmissionRequest(r *http.Request) ( + req *SubmissionCreate, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = multierr.Append(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = multierr.Append(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + if r.ContentLength == 0 { + return req, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + if err != nil { + return req, close, err + } + + if len(buf) == 0 { + return req, close, validate.ErrBodyRequired + } + + d := jx.DecodeBytes(buf) + + var request SubmissionCreate + if err := func() error { + if err := request.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return req, close, err + } + if err := func() error { + if err := request.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return req, close, errors.Wrap(err, "validate") + } + return &request, close, nil + default: + return req, close, validate.InvalidContentType(ct) + } +} diff --git a/pkg/internal/oas_request_encoders_gen.go b/pkg/internal/oas_request_encoders_gen.go index a0e7305..1e38200 100644 --- a/pkg/internal/oas_request_encoders_gen.go +++ b/pkg/internal/oas_request_encoders_gen.go @@ -11,6 +11,20 @@ import ( ht "github.com/ogen-go/ogen/http" ) +func encodeCreateMapfixRequest( + req *MapfixCreate, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} + func encodeCreateScriptRequest( req *ScriptCreate, r *http.Request, @@ -38,3 +52,17 @@ func encodeCreateScriptPolicyRequest( ht.SetBody(r, bytes.NewReader(encoded), contentType) return nil } + +func encodeCreateSubmissionRequest( + req *SubmissionCreate, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} diff --git a/pkg/internal/oas_response_decoders_gen.go b/pkg/internal/oas_response_decoders_gen.go index 226883f..1c492e1 100644 --- a/pkg/internal/oas_response_decoders_gen.go +++ b/pkg/internal/oas_response_decoders_gen.go @@ -168,6 +168,57 @@ func decodeActionMapfixValidatedResponse(resp *http.Response) (res *ActionMapfix return res, errors.Wrap(defRes, "error") } +func decodeActionOperationFailedResponse(resp *http.Response) (res *ActionOperationFailedNoContent, _ error) { + switch resp.StatusCode { + case 204: + // Code 204. + return &ActionOperationFailedNoContent{}, nil + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeActionSubmissionAcceptedResponse(resp *http.Response) (res *ActionSubmissionAcceptedNoContent, _ error) { switch resp.StatusCode { case 204: @@ -321,6 +372,89 @@ func decodeActionSubmissionValidatedResponse(resp *http.Response) (res *ActionSu return res, errors.Wrap(defRes, "error") } +func decodeCreateMapfixResponse(resp *http.Response) (res *ID, _ error) { + switch resp.StatusCode { + case 201: + // Code 201. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response ID + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeCreateScriptResponse(resp *http.Response) (res *ID, _ error) { switch resp.StatusCode { case 201: @@ -487,6 +621,89 @@ func decodeCreateScriptPolicyResponse(resp *http.Response) (res *ID, _ error) { return res, errors.Wrap(defRes, "error") } +func decodeCreateSubmissionResponse(resp *http.Response) (res *ID, _ error) { + switch resp.StatusCode { + case 201: + // Code 201. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response ID + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + func decodeGetScriptResponse(resp *http.Response) (res *Script, _ error) { switch resp.StatusCode { case 200: diff --git a/pkg/internal/oas_response_encoders_gen.go b/pkg/internal/oas_response_encoders_gen.go index c7d2b1f..ab1b12d 100644 --- a/pkg/internal/oas_response_encoders_gen.go +++ b/pkg/internal/oas_response_encoders_gen.go @@ -34,6 +34,13 @@ func encodeActionMapfixValidatedResponse(response *ActionMapfixValidatedNoConten return nil } +func encodeActionOperationFailedResponse(response *ActionOperationFailedNoContent, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(204) + span.SetStatus(codes.Ok, http.StatusText(204)) + + return nil +} + func encodeActionSubmissionAcceptedResponse(response *ActionSubmissionAcceptedNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) @@ -55,6 +62,20 @@ func encodeActionSubmissionValidatedResponse(response *ActionSubmissionValidated return nil } +func encodeCreateMapfixResponse(response *ID, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(201) + span.SetStatus(codes.Ok, http.StatusText(201)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + func encodeCreateScriptResponse(response *ID, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(201) @@ -83,6 +104,20 @@ func encodeCreateScriptPolicyResponse(response *ID, w http.ResponseWriter, span return nil } +func encodeCreateSubmissionResponse(response *ID, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(201) + span.SetStatus(codes.Ok, http.StatusText(201)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + func encodeGetScriptResponse(response *Script, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) diff --git a/pkg/internal/oas_router_gen.go b/pkg/internal/oas_router_gen.go index 77e8063..0531e26 100644 --- a/pkg/internal/oas_router_gen.go +++ b/pkg/internal/oas_router_gen.go @@ -61,15 +61,175 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { - case 'm': // Prefix: "mapfixes/" + case 'm': // Prefix: "mapfixes" - if l := len("mapfixes/"); len(elem) >= l && elem[0:l] == "mapfixes/" { + if l := len("mapfixes"); len(elem) >= l && elem[0:l] == "mapfixes" { elem = elem[l:] } else { break } - // Param: "MapfixID" + if len(elem) == 0 { + switch r.Method { + case "POST": + s.handleCreateMapfixRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + switch elem[0] { + case '/': // Prefix: "/" + + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + elem = elem[l:] + } else { + break + } + + // Param: "MapfixID" + // Match until "/" + idx := strings.IndexByte(elem, '/') + if idx < 0 { + idx = len(elem) + } + args[0] = elem[:idx] + elem = elem[idx:] + + if len(elem) == 0 { + break + } + switch elem[0] { + case '/': // Prefix: "/" + + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 's': // Prefix: "status/validator-" + + if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'f': // Prefix: "failed" + + if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixAcceptedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 'u': // Prefix: "uploaded" + + if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixUploadedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 'v': // Prefix: "validated" + + if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionMapfixValidatedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + } + + case 'v': // Prefix: "validated-model" + + if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleUpdateMapfixValidatedModelRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + } + + } + + } + + case 'o': // Prefix: "operations/" + + if l := len("operations/"); len(elem) >= l && elem[0:l] == "operations/" { + elem = elem[l:] + } else { + break + } + + // Param: "OperationID" // Match until "/" idx := strings.IndexByte(elem, '/') if idx < 0 { @@ -82,120 +242,26 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { - case '/': // Prefix: "/" + case '/': // Prefix: "/failed" - if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + if l := len("/failed"); len(elem) >= l && elem[0:l] == "/failed" { elem = elem[l:] } else { break } if len(elem) == 0 { - break - } - switch elem[0] { - case 's': // Prefix: "status/validator-" - - if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - break - } - switch elem[0] { - case 'f': // Prefix: "failed" - - if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixAcceptedRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - case 'u': // Prefix: "uploaded" - - if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixUploadedRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - case 'v': // Prefix: "validated" - - if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionMapfixValidatedRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - } - - case 'v': // Prefix: "validated-model" - - if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleUpdateMapfixValidatedModelRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return + // Leaf node. + switch r.Method { + case "POST": + s.handleActionOperationFailedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") } + return } } @@ -302,25 +368,23 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } - case 'u': // Prefix: "ubmissions/" + case 'u': // Prefix: "ubmissions" - if l := len("ubmissions/"); len(elem) >= l && elem[0:l] == "ubmissions/" { + if l := len("ubmissions"); len(elem) >= l && elem[0:l] == "ubmissions" { elem = elem[l:] } else { break } - // Param: "SubmissionID" - // Match until "/" - idx := strings.IndexByte(elem, '/') - if idx < 0 { - idx = len(elem) - } - args[0] = elem[:idx] - elem = elem[idx:] - if len(elem) == 0 { - break + switch r.Method { + case "POST": + s.handleCreateSubmissionRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return } switch elem[0] { case '/': // Prefix: "/" @@ -331,13 +395,22 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } + // Param: "SubmissionID" + // Match until "/" + idx := strings.IndexByte(elem, '/') + if idx < 0 { + idx = len(elem) + } + args[0] = elem[:idx] + elem = elem[idx:] + if len(elem) == 0 { break } switch elem[0] { - case 's': // Prefix: "status/validator-" + case '/': // Prefix: "/" - if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" { + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { elem = elem[l:] } else { break @@ -347,9 +420,89 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { - case 'f': // Prefix: "failed" + case 's': // Prefix: "status/validator-" - if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" { + if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'f': // Prefix: "failed" + + if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionSubmissionAcceptedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 'u': // Prefix: "uploaded" + + if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionSubmissionUploadedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + case 'v': // Prefix: "validated" + + if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleActionSubmissionValidatedRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + } + + case 'v': // Prefix: "validated-model" + + if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" { elem = elem[l:] } else { break @@ -359,7 +512,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Leaf node. switch r.Method { case "POST": - s.handleActionSubmissionAcceptedRequest([1]string{ + s.handleUpdateSubmissionValidatedModelRequest([1]string{ args[0], }, elemIsEscaped, w, r) default: @@ -369,72 +522,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - case 'u': // Prefix: "uploaded" - - if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionSubmissionUploadedRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - case 'v': // Prefix: "validated" - - if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleActionSubmissionValidatedRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return - } - - } - - case 'v': // Prefix: "validated-model" - - if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "POST": - s.handleUpdateSubmissionValidatedModelRequest([1]string{ - args[0], - }, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "POST") - } - - return } } @@ -537,15 +624,187 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { - case 'm': // Prefix: "mapfixes/" + case 'm': // Prefix: "mapfixes" - if l := len("mapfixes/"); len(elem) >= l && elem[0:l] == "mapfixes/" { + if l := len("mapfixes"); len(elem) >= l && elem[0:l] == "mapfixes" { elem = elem[l:] } else { break } - // Param: "MapfixID" + if len(elem) == 0 { + switch method { + case "POST": + r.name = CreateMapfixOperation + r.summary = "Create a mapfix" + r.operationID = "createMapfix" + r.pathPattern = "/mapfixes" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + switch elem[0] { + case '/': // Prefix: "/" + + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + elem = elem[l:] + } else { + break + } + + // Param: "MapfixID" + // Match until "/" + idx := strings.IndexByte(elem, '/') + if idx < 0 { + idx = len(elem) + } + args[0] = elem[:idx] + elem = elem[idx:] + + if len(elem) == 0 { + break + } + switch elem[0] { + case '/': // Prefix: "/" + + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 's': // Prefix: "status/validator-" + + if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'f': // Prefix: "failed" + + if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixAcceptedOperation + r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Accepted" + r.operationID = "actionMapfixAccepted" + r.pathPattern = "/mapfixes/{MapfixID}/status/validator-failed" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 'u': // Prefix: "uploaded" + + if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixUploadedOperation + r.summary = "(Internal endpoint) Role Validator changes status from Uploading -> Uploaded" + r.operationID = "actionMapfixUploaded" + r.pathPattern = "/mapfixes/{MapfixID}/status/validator-uploaded" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 'v': // Prefix: "validated" + + if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionMapfixValidatedOperation + r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Validated" + r.operationID = "actionMapfixValidated" + r.pathPattern = "/mapfixes/{MapfixID}/status/validator-validated" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + } + + case 'v': // Prefix: "validated-model" + + if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = UpdateMapfixValidatedModelOperation + r.summary = "Update validated model" + r.operationID = "updateMapfixValidatedModel" + r.pathPattern = "/mapfixes/{MapfixID}/validated-model" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + } + + } + + } + + case 'o': // Prefix: "operations/" + + if l := len("operations/"); len(elem) >= l && elem[0:l] == "operations/" { + elem = elem[l:] + } else { + break + } + + // Param: "OperationID" // Match until "/" idx := strings.IndexByte(elem, '/') if idx < 0 { @@ -558,128 +817,28 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { - case '/': // Prefix: "/" + case '/': // Prefix: "/failed" - if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + if l := len("/failed"); len(elem) >= l && elem[0:l] == "/failed" { elem = elem[l:] } else { break } if len(elem) == 0 { - break - } - switch elem[0] { - case 's': // Prefix: "status/validator-" - - if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" { - elem = elem[l:] - } else { - break + // Leaf node. + switch method { + case "POST": + r.name = ActionOperationFailedOperation + r.summary = "(Internal endpoint) Fail an operation and write a StatusMessage" + r.operationID = "actionOperationFailed" + r.pathPattern = "/operations/{OperationID}/failed" + r.args = args + r.count = 1 + return r, true + default: + return } - - if len(elem) == 0 { - break - } - switch elem[0] { - case 'f': // Prefix: "failed" - - if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionMapfixAcceptedOperation - r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Accepted" - r.operationID = "actionMapfixAccepted" - r.pathPattern = "/mapfixes/{MapfixID}/status/validator-failed" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - case 'u': // Prefix: "uploaded" - - if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionMapfixUploadedOperation - r.summary = "(Internal endpoint) Role Validator changes status from Uploading -> Uploaded" - r.operationID = "actionMapfixUploaded" - r.pathPattern = "/mapfixes/{MapfixID}/status/validator-uploaded" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - case 'v': // Prefix: "validated" - - if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionMapfixValidatedOperation - r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Validated" - r.operationID = "actionMapfixValidated" - r.pathPattern = "/mapfixes/{MapfixID}/status/validator-validated" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - } - - case 'v': // Prefix: "validated-model" - - if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = UpdateMapfixValidatedModelOperation - r.summary = "Update validated model" - r.operationID = "updateMapfixValidatedModel" - r.pathPattern = "/mapfixes/{MapfixID}/validated-model" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - } } @@ -808,25 +967,27 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } - case 'u': // Prefix: "ubmissions/" + case 'u': // Prefix: "ubmissions" - if l := len("ubmissions/"); len(elem) >= l && elem[0:l] == "ubmissions/" { + if l := len("ubmissions"); len(elem) >= l && elem[0:l] == "ubmissions" { elem = elem[l:] } else { break } - // Param: "SubmissionID" - // Match until "/" - idx := strings.IndexByte(elem, '/') - if idx < 0 { - idx = len(elem) - } - args[0] = elem[:idx] - elem = elem[idx:] - if len(elem) == 0 { - break + switch method { + case "POST": + r.name = CreateSubmissionOperation + r.summary = "Create a new submission" + r.operationID = "createSubmission" + r.pathPattern = "/submissions" + r.args = args + r.count = 0 + return r, true + default: + return + } } switch elem[0] { case '/': // Prefix: "/" @@ -837,13 +998,22 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } + // Param: "SubmissionID" + // Match until "/" + idx := strings.IndexByte(elem, '/') + if idx < 0 { + idx = len(elem) + } + args[0] = elem[:idx] + elem = elem[idx:] + if len(elem) == 0 { break } switch elem[0] { - case 's': // Prefix: "status/validator-" + case '/': // Prefix: "/" - if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" { + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { elem = elem[l:] } else { break @@ -853,9 +1023,95 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { - case 'f': // Prefix: "failed" + case 's': // Prefix: "status/validator-" - if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" { + if l := len("status/validator-"); len(elem) >= l && elem[0:l] == "status/validator-" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'f': // Prefix: "failed" + + if l := len("failed"); len(elem) >= l && elem[0:l] == "failed" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionSubmissionAcceptedOperation + r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Accepted" + r.operationID = "actionSubmissionAccepted" + r.pathPattern = "/submissions/{SubmissionID}/status/validator-failed" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 'u': // Prefix: "uploaded" + + if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionSubmissionUploadedOperation + r.summary = "(Internal endpoint) Role Validator changes status from Uploading -> Uploaded" + r.operationID = "actionSubmissionUploaded" + r.pathPattern = "/submissions/{SubmissionID}/status/validator-uploaded" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 'v': // Prefix: "validated" + + if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ActionSubmissionValidatedOperation + r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Validated" + r.operationID = "actionSubmissionValidated" + r.pathPattern = "/submissions/{SubmissionID}/status/validator-validated" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + } + + case 'v': // Prefix: "validated-model" + + if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" { elem = elem[l:] } else { break @@ -865,10 +1121,10 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { // Leaf node. switch method { case "POST": - r.name = ActionSubmissionAcceptedOperation - r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Accepted" - r.operationID = "actionSubmissionAccepted" - r.pathPattern = "/submissions/{SubmissionID}/status/validator-failed" + r.name = UpdateSubmissionValidatedModelOperation + r.summary = "Update validated model" + r.operationID = "updateSubmissionValidatedModel" + r.pathPattern = "/submissions/{SubmissionID}/validated-model" r.args = args r.count = 1 return r, true @@ -877,78 +1133,6 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } - case 'u': // Prefix: "uploaded" - - if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionSubmissionUploadedOperation - r.summary = "(Internal endpoint) Role Validator changes status from Uploading -> Uploaded" - r.operationID = "actionSubmissionUploaded" - r.pathPattern = "/submissions/{SubmissionID}/status/validator-uploaded" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - case 'v': // Prefix: "validated" - - if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = ActionSubmissionValidatedOperation - r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Validated" - r.operationID = "actionSubmissionValidated" - r.pathPattern = "/submissions/{SubmissionID}/status/validator-validated" - r.args = args - r.count = 1 - return r, true - default: - return - } - } - - } - - case 'v': // Prefix: "validated-model" - - if l := len("validated-model"); len(elem) >= l && elem[0:l] == "validated-model" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch method { - case "POST": - r.name = UpdateSubmissionValidatedModelOperation - r.summary = "Update validated model" - r.operationID = "updateSubmissionValidatedModel" - r.pathPattern = "/submissions/{SubmissionID}/validated-model" - r.args = args - r.count = 1 - return r, true - default: - return - } } } diff --git a/pkg/internal/oas_schemas_gen.go b/pkg/internal/oas_schemas_gen.go index 2f06835..cc5c4ed 100644 --- a/pkg/internal/oas_schemas_gen.go +++ b/pkg/internal/oas_schemas_gen.go @@ -19,6 +19,9 @@ type ActionMapfixUploadedNoContent struct{} // ActionMapfixValidatedNoContent is response for ActionMapfixValidated operation. type ActionMapfixValidatedNoContent struct{} +// ActionOperationFailedNoContent is response for ActionOperationFailed operation. +type ActionOperationFailedNoContent struct{} + // ActionSubmissionAcceptedNoContent is response for ActionSubmissionAccepted operation. type ActionSubmissionAcceptedNoContent struct{} @@ -96,6 +99,98 @@ func (s *ID) SetID(val int64) { s.ID = val } +// Ref: #/components/schemas/MapfixCreate +type MapfixCreate struct { + OperationID int32 `json:"OperationID"` + AssetOwner int64 `json:"AssetOwner"` + DisplayName string `json:"DisplayName"` + Creator string `json:"Creator"` + GameID int32 `json:"GameID"` + AssetID int64 `json:"AssetID"` + AssetVersion int64 `json:"AssetVersion"` + TargetAssetID int64 `json:"TargetAssetID"` +} + +// GetOperationID returns the value of OperationID. +func (s *MapfixCreate) GetOperationID() int32 { + return s.OperationID +} + +// GetAssetOwner returns the value of AssetOwner. +func (s *MapfixCreate) GetAssetOwner() int64 { + return s.AssetOwner +} + +// GetDisplayName returns the value of DisplayName. +func (s *MapfixCreate) GetDisplayName() string { + return s.DisplayName +} + +// GetCreator returns the value of Creator. +func (s *MapfixCreate) GetCreator() string { + return s.Creator +} + +// GetGameID returns the value of GameID. +func (s *MapfixCreate) GetGameID() int32 { + return s.GameID +} + +// GetAssetID returns the value of AssetID. +func (s *MapfixCreate) GetAssetID() int64 { + return s.AssetID +} + +// GetAssetVersion returns the value of AssetVersion. +func (s *MapfixCreate) GetAssetVersion() int64 { + return s.AssetVersion +} + +// GetTargetAssetID returns the value of TargetAssetID. +func (s *MapfixCreate) GetTargetAssetID() int64 { + return s.TargetAssetID +} + +// SetOperationID sets the value of OperationID. +func (s *MapfixCreate) SetOperationID(val int32) { + s.OperationID = val +} + +// SetAssetOwner sets the value of AssetOwner. +func (s *MapfixCreate) SetAssetOwner(val int64) { + s.AssetOwner = val +} + +// SetDisplayName sets the value of DisplayName. +func (s *MapfixCreate) SetDisplayName(val string) { + s.DisplayName = val +} + +// SetCreator sets the value of Creator. +func (s *MapfixCreate) SetCreator(val string) { + s.Creator = val +} + +// SetGameID sets the value of GameID. +func (s *MapfixCreate) SetGameID(val int32) { + s.GameID = val +} + +// SetAssetID sets the value of AssetID. +func (s *MapfixCreate) SetAssetID(val int64) { + s.AssetID = val +} + +// SetAssetVersion sets the value of AssetVersion. +func (s *MapfixCreate) SetAssetVersion(val int64) { + s.AssetVersion = val +} + +// SetTargetAssetID sets the value of TargetAssetID. +func (s *MapfixCreate) SetTargetAssetID(val int64) { + s.TargetAssetID = val +} + // NewOptInt32 returns new OptInt32 with value set to v. func NewOptInt32(v int32) OptInt32 { return OptInt32{ @@ -437,6 +532,87 @@ func (s *ScriptPolicyCreate) SetPolicy(val int32) { s.Policy = val } +// Ref: #/components/schemas/SubmissionCreate +type SubmissionCreate struct { + OperationID int32 `json:"OperationID"` + AssetOwner int64 `json:"AssetOwner"` + DisplayName string `json:"DisplayName"` + Creator string `json:"Creator"` + GameID int32 `json:"GameID"` + AssetID int64 `json:"AssetID"` + AssetVersion int64 `json:"AssetVersion"` +} + +// GetOperationID returns the value of OperationID. +func (s *SubmissionCreate) GetOperationID() int32 { + return s.OperationID +} + +// GetAssetOwner returns the value of AssetOwner. +func (s *SubmissionCreate) GetAssetOwner() int64 { + return s.AssetOwner +} + +// GetDisplayName returns the value of DisplayName. +func (s *SubmissionCreate) GetDisplayName() string { + return s.DisplayName +} + +// GetCreator returns the value of Creator. +func (s *SubmissionCreate) GetCreator() string { + return s.Creator +} + +// GetGameID returns the value of GameID. +func (s *SubmissionCreate) GetGameID() int32 { + return s.GameID +} + +// GetAssetID returns the value of AssetID. +func (s *SubmissionCreate) GetAssetID() int64 { + return s.AssetID +} + +// GetAssetVersion returns the value of AssetVersion. +func (s *SubmissionCreate) GetAssetVersion() int64 { + return s.AssetVersion +} + +// SetOperationID sets the value of OperationID. +func (s *SubmissionCreate) SetOperationID(val int32) { + s.OperationID = val +} + +// SetAssetOwner sets the value of AssetOwner. +func (s *SubmissionCreate) SetAssetOwner(val int64) { + s.AssetOwner = val +} + +// SetDisplayName sets the value of DisplayName. +func (s *SubmissionCreate) SetDisplayName(val string) { + s.DisplayName = val +} + +// SetCreator sets the value of Creator. +func (s *SubmissionCreate) SetCreator(val string) { + s.Creator = val +} + +// SetGameID sets the value of GameID. +func (s *SubmissionCreate) SetGameID(val int32) { + s.GameID = val +} + +// SetAssetID sets the value of AssetID. +func (s *SubmissionCreate) SetAssetID(val int64) { + s.AssetID = val +} + +// SetAssetVersion sets the value of AssetVersion. +func (s *SubmissionCreate) SetAssetVersion(val int64) { + s.AssetVersion = val +} + // UpdateMapfixValidatedModelNoContent is response for UpdateMapfixValidatedModel operation. type UpdateMapfixValidatedModelNoContent struct{} diff --git a/pkg/internal/oas_server_gen.go b/pkg/internal/oas_server_gen.go index 846550e..0423592 100644 --- a/pkg/internal/oas_server_gen.go +++ b/pkg/internal/oas_server_gen.go @@ -26,6 +26,12 @@ type Handler interface { // // POST /mapfixes/{MapfixID}/status/validator-validated ActionMapfixValidated(ctx context.Context, params ActionMapfixValidatedParams) error + // ActionOperationFailed implements actionOperationFailed operation. + // + // (Internal endpoint) Fail an operation and write a StatusMessage. + // + // POST /operations/{OperationID}/failed + ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error // ActionSubmissionAccepted implements actionSubmissionAccepted operation. // // (Internal endpoint) Role Validator changes status from Validating -> Accepted. @@ -44,6 +50,12 @@ type Handler interface { // // POST /submissions/{SubmissionID}/status/validator-validated ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error + // CreateMapfix implements createMapfix operation. + // + // Create a mapfix. + // + // POST /mapfixes + CreateMapfix(ctx context.Context, req *MapfixCreate) (*ID, error) // CreateScript implements createScript operation. // // Create a new script. @@ -56,6 +68,12 @@ type Handler interface { // // POST /script-policy CreateScriptPolicy(ctx context.Context, req *ScriptPolicyCreate) (*ID, error) + // CreateSubmission implements createSubmission operation. + // + // Create a new submission. + // + // POST /submissions + CreateSubmission(ctx context.Context, req *SubmissionCreate) (*ID, error) // GetScript implements getScript operation. // // Get the specified script by ID. diff --git a/pkg/internal/oas_unimplemented_gen.go b/pkg/internal/oas_unimplemented_gen.go index 1a08c02..0f56dad 100644 --- a/pkg/internal/oas_unimplemented_gen.go +++ b/pkg/internal/oas_unimplemented_gen.go @@ -40,6 +40,15 @@ func (UnimplementedHandler) ActionMapfixValidated(ctx context.Context, params Ac return ht.ErrNotImplemented } +// ActionOperationFailed implements actionOperationFailed operation. +// +// (Internal endpoint) Fail an operation and write a StatusMessage. +// +// POST /operations/{OperationID}/failed +func (UnimplementedHandler) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error { + return ht.ErrNotImplemented +} + // ActionSubmissionAccepted implements actionSubmissionAccepted operation. // // (Internal endpoint) Role Validator changes status from Validating -> Accepted. @@ -67,6 +76,15 @@ func (UnimplementedHandler) ActionSubmissionValidated(ctx context.Context, param return ht.ErrNotImplemented } +// CreateMapfix implements createMapfix operation. +// +// Create a mapfix. +// +// POST /mapfixes +func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixCreate) (r *ID, _ error) { + return r, ht.ErrNotImplemented +} + // CreateScript implements createScript operation. // // Create a new script. @@ -85,6 +103,15 @@ func (UnimplementedHandler) CreateScriptPolicy(ctx context.Context, req *ScriptP return r, ht.ErrNotImplemented } +// CreateSubmission implements createSubmission operation. +// +// Create a new submission. +// +// POST /submissions +func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *SubmissionCreate) (r *ID, _ error) { + return r, ht.ErrNotImplemented +} + // GetScript implements getScript operation. // // Get the specified script by ID. diff --git a/pkg/internal/oas_validators_gen.go b/pkg/internal/oas_validators_gen.go index 973f07b..733e7f4 100644 --- a/pkg/internal/oas_validators_gen.go +++ b/pkg/internal/oas_validators_gen.go @@ -8,6 +8,56 @@ import ( "github.com/ogen-go/ogen/validate" ) +func (s *MapfixCreate) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 128, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(s.DisplayName)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "DisplayName", + Error: 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(s.Creator)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Creator", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + func (s *Script) Validate() error { if s == nil { return validate.ErrNilPointer @@ -157,3 +207,53 @@ func (s *ScriptPolicy) Validate() error { } return nil } + +func (s *SubmissionCreate) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 128, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(s.DisplayName)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "DisplayName", + Error: 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(s.Creator)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Creator", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} diff --git a/pkg/model/nats.go b/pkg/model/nats.go index 6add04f..832c340 100644 --- a/pkg/model/nats.go +++ b/pkg/model/nats.go @@ -5,6 +5,18 @@ package model // Requests are sent from maps-service to validator +type CreateSubmissionRequest struct { + // operation_id is passed back in the response message + OperationID int32 + ModelID int64 +} + +type CreateMapfixRequest struct { + OperationID int32 + ModelID int64 + TargetAssetID int64 +} + type ValidateSubmissionRequest struct { // submission_id is passed back in the response message SubmissionID int64 diff --git a/pkg/model/operation.go b/pkg/model/operation.go new file mode 100644 index 0000000..4949a92 --- /dev/null +++ b/pkg/model/operation.go @@ -0,0 +1,19 @@ +package model + +import "time" + +type OperationStatus int32 +const ( + OperationStatusCreated OperationStatus = 0 + OperationStatusCompleted OperationStatus = 1 + OperationStatusFailed OperationStatus = 2 +) + +type Operation struct { + ID int32 `gorm:"primaryKey"` + CreatedAt time.Time + Owner int64 // UserID + StatusID OperationStatus + StatusMessage string + Path string // redirect to view completed operation e.g. "/mapfixes/4" +} diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go index ba38707..a0e9e02 100644 --- a/pkg/service/mapfixes.go +++ b/pkg/service/mapfixes.go @@ -7,6 +7,7 @@ import ( "fmt" "time" + "git.itzana.me/strafesnet/go-grpc/maps" "git.itzana.me/strafesnet/maps-service/pkg/api" "git.itzana.me/strafesnet/maps-service/pkg/datastore" "git.itzana.me/strafesnet/maps-service/pkg/model" @@ -19,16 +20,6 @@ var( model.MapfixStatusSubmitted, model.MapfixStatusUnderConstruction, } - // prevent two mapfixes with same asset id - ActiveMapfixStatuses = []model.MapfixStatus{ - model.MapfixStatusUploading, - model.MapfixStatusValidated, - model.MapfixStatusValidating, - model.MapfixStatusAccepted, - model.MapfixStatusChangesRequested, - model.MapfixStatusSubmitted, - model.MapfixStatusUnderConstruction, - } // limit mapfixes in the pipeline to one per target map ActiveAcceptedMapfixStatuses = []model.MapfixStatus{ model.MapfixStatusUploading, @@ -40,13 +31,12 @@ var( var ( ErrCreationPhaseMapfixesLimit = errors.New("Active mapfixes limited to 20") - ErrActiveMapfixSameAssetID = errors.New("There is an active mapfix with the same AssetID") ErrActiveMapfixSameTargetAssetID = errors.New("There is an active mapfix with the same TargetAssetID") ErrAcceptOwnMapfix = fmt.Errorf("%w: You cannot accept your own mapfix as the submitter", ErrPermissionDenied) ) // POST /mapfixes -func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixCreate) (*api.ID, error) { +func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTriggerCreate) (*api.OperationID, error) { userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) if !ok { return nil, ErrUserInfo @@ -75,41 +65,40 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixCreate) } } - // Check if an active mapfix with the same asset id exists + // Check if TargetAssetID actually exists { - filter := datastore.Optional() - filter.Add("asset_id", request.AssetID) - filter.Add("asset_version", request.AssetVersion) - filter.Add("status_id", ActiveMapfixStatuses) - active_mapfixes, err := svc.DB.Mapfixes().List(ctx, filter, model.Page{ - Number: 1, - Size: 1, - },datastore.ListSortDisabled) + _, err := svc.Client.Get(ctx, &maps.IdMessage{ + ID: request.TargetAssetID, + }) if err != nil { + // TODO: match specific does not exist grpc error return nil, err } - if len(active_mapfixes) != 0{ - return nil, ErrActiveMapfixSameAssetID - } } - mapfix, err := svc.DB.Mapfixes().Create(ctx, model.Mapfix{ - ID: 0, - DisplayName: request.DisplayName, - Creator: request.Creator, - GameID: request.GameID, - Submitter: int64(userId), - AssetID: request.AssetID, - AssetVersion: request.AssetVersion, - Completed: false, - TargetAssetID: request.TargetAssetID, - StatusID: model.MapfixStatusUnderConstruction, + operation, err := svc.DB.Operations().Create(ctx, model.Operation{ + Owner: int64(userId), + StatusID: model.OperationStatusCreated, }) if err != nil { return nil, err } - return &api.ID{ - ID: mapfix.ID, + + create_request := model.CreateMapfixRequest{ + OperationID: operation.ID, + ModelID: request.AssetID, + TargetAssetID: request.TargetAssetID, + } + + j, err := json.Marshal(create_request) + if err != nil { + return nil, err + } + + svc.Nats.Publish("maptest.mapfixes.create", []byte(j)) + + return &api.OperationID{ + OperationID: operation.ID, }, nil } diff --git a/pkg/service/operations.go b/pkg/service/operations.go new file mode 100644 index 0000000..eb48adc --- /dev/null +++ b/pkg/service/operations.go @@ -0,0 +1,44 @@ +package service + +import ( + "context" + + "git.itzana.me/strafesnet/maps-service/pkg/api" +) + +// GetOperation implements getOperation operation. +// +// Get the specified operation by ID. +// +// GET /operations/{OperationID} +func (svc *Service) GetOperation(ctx context.Context, params api.GetOperationParams) (*api.Operation, error) { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return nil, ErrUserInfo + } + + // You must be the operation owner to read it + + operation, err := svc.DB.Operations().Get(ctx, params.OperationID) + if err != nil { + return nil, err + } + + has_role, err := userInfo.IsSubmitter(uint64(operation.Owner)) + if err != nil { + return nil, err + } + // check if caller is operation owner + if !has_role { + return nil, ErrPermissionDeniedNotSubmitter + } + + return &api.Operation{ + OperationID: operation.ID, + Date: operation.CreatedAt.Unix(), + Owner: operation.Owner, + Status: int32(operation.StatusID), + StatusMessage: operation.StatusMessage, + Path: operation.Path, + }, nil +} diff --git a/pkg/service/submissions.go b/pkg/service/submissions.go index 5944eff..f36a18a 100644 --- a/pkg/service/submissions.go +++ b/pkg/service/submissions.go @@ -20,16 +20,6 @@ var( model.SubmissionStatusSubmitted, model.SubmissionStatusUnderConstruction, } - // prevent two mapfixes with same asset id - ActiveSubmissionStatuses = []model.SubmissionStatus{ - model.SubmissionStatusUploading, - model.SubmissionStatusValidated, - model.SubmissionStatusValidating, - model.SubmissionStatusAccepted, - model.SubmissionStatusChangesRequested, - model.SubmissionStatusSubmitted, - model.SubmissionStatusUnderConstruction, - } // limit mapfixes in the pipeline to one per target map ActiveAcceptedSubmissionStatuses = []model.SubmissionStatus{ model.SubmissionStatusUploading, @@ -41,7 +31,6 @@ var( var ( ErrCreationPhaseSubmissionsLimit = errors.New("Active submissions limited to 20") - ErrActiveSubmissionSameAssetID = errors.New("There is an active submission with the same AssetID") ErrUploadedAssetIDAlreadyExists = errors.New("The submission UploadedAssetID is already set") ErrReleaseInvalidStatus = errors.New("Only submissions with Uploaded status can be released") ErrReleaseNoUploadedAssetID = errors.New("Only submissions with a UploadedAssetID can be released") @@ -49,7 +38,7 @@ var ( ) // POST /submissions -func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionCreate) (*api.ID, error) { +func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionTriggerCreate) (*api.OperationID, error) { userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) if !ok { return nil, ErrUserInfo @@ -77,41 +66,28 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio return nil, ErrCreationPhaseSubmissionsLimit } } - - // Check if an active submission with the same asset id exists - { - filter := datastore.Optional() - filter.Add("asset_id", request.AssetID) - filter.Add("asset_version", request.AssetVersion) - filter.Add("status_id", ActiveSubmissionStatuses) - active_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{ - Number: 1, - Size: 1, - },datastore.ListSortDisabled) - if err != nil { - return nil, err - } - if len(active_submissions) != 0{ - return nil, ErrActiveSubmissionSameAssetID - } - } - - submission, err := svc.DB.Submissions().Create(ctx, model.Submission{ - ID: 0, - DisplayName: request.DisplayName, - Creator: request.Creator, - GameID: request.GameID, - Submitter: int64(userId), - AssetID: request.AssetID, - AssetVersion: request.AssetVersion, - Completed: false, - StatusID: model.SubmissionStatusUnderConstruction, + operation, err := svc.DB.Operations().Create(ctx, model.Operation{ + Owner: int64(userId), + StatusID: model.OperationStatusCreated, }) if err != nil { return nil, err } - return &api.ID{ - ID: submission.ID, + + create_request := model.CreateSubmissionRequest{ + OperationID: operation.ID, + ModelID: request.AssetID, + } + + j, err := json.Marshal(create_request) + if err != nil { + return nil, err + } + + svc.Nats.Publish("maptest.submissions.create", []byte(j)) + + return &api.OperationID{ + OperationID: operation.ID, }, nil } diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index cfdf31a..c8a8c79 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -2,12 +2,31 @@ package service_internal import ( "context" + "errors" - internal "git.itzana.me/strafesnet/maps-service/pkg/internal" "git.itzana.me/strafesnet/maps-service/pkg/datastore" + internal "git.itzana.me/strafesnet/maps-service/pkg/internal" "git.itzana.me/strafesnet/maps-service/pkg/model" ) +var( + // prevent two mapfixes with same asset id + ActiveMapfixStatuses = []model.MapfixStatus{ + model.MapfixStatusUploading, + model.MapfixStatusValidated, + model.MapfixStatusValidating, + model.MapfixStatusAccepted, + model.MapfixStatusChangesRequested, + model.MapfixStatusSubmitted, + model.MapfixStatusUnderConstruction, + } +) + +var( + ErrActiveMapfixSameAssetID = errors.New("There is an active mapfix with the same AssetID") + ErrNotAssetOwner = errors.New("You can only submit an asset you own") +) + // UpdateMapfixValidatedModel implements patchMapfixModel operation. // // Update model following role restrictions. @@ -59,3 +78,54 @@ func (svc *Service) ActionMapfixUploaded(ctx context.Context, params internal.Ac smap.Add("status_id", model.MapfixStatusUploaded) return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap) } + +// POST /mapfixes +func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCreate) (*internal.ID, error) { + // Check if an active mapfix with the same asset id exists + { + filter := datastore.Optional() + filter.Add("asset_id", request.AssetID) + filter.Add("asset_version", request.AssetVersion) + filter.Add("status_id", ActiveMapfixStatuses) + active_mapfixes, err := svc.DB.Mapfixes().List(ctx, filter, model.Page{ + Number: 1, + Size: 1, + },datastore.ListSortDisabled) + if err != nil { + return nil, err + } + if len(active_mapfixes) != 0{ + return nil, ErrActiveMapfixSameAssetID + } + } + + operation, err := svc.DB.Operations().Get(ctx, request.OperationID) + if err != nil { + return nil, err + } + + // check if user owns asset + // TODO: allow bypass by admin + if operation.Owner != request.AssetOwner { + return nil, ErrNotAssetOwner + } + + mapfix, err := svc.DB.Mapfixes().Create(ctx, model.Mapfix{ + ID: 0, + DisplayName: request.DisplayName, + Creator: request.Creator, + GameID: request.GameID, + Submitter: request.AssetOwner, + AssetID: request.AssetID, + AssetVersion: request.AssetVersion, + Completed: false, + TargetAssetID: request.TargetAssetID, + StatusID: model.MapfixStatusUnderConstruction, + }) + if err != nil { + return nil, err + } + return &internal.ID{ + ID: mapfix.ID, + }, nil +} diff --git a/pkg/service_internal/operations.go b/pkg/service_internal/operations.go new file mode 100644 index 0000000..2deeffd --- /dev/null +++ b/pkg/service_internal/operations.go @@ -0,0 +1,21 @@ +package service_internal + +import ( + "context" + + "git.itzana.me/strafesnet/maps-service/pkg/datastore" + internal "git.itzana.me/strafesnet/maps-service/pkg/internal" + "git.itzana.me/strafesnet/maps-service/pkg/model" +) + +// ActionOperationFailed implements actionOperationFailed operation. +// +// Fail the specified OperationID with a StatusMessage. +// +// POST /operations/{OperationID}/failed +func (svc *Service) ActionOperationFailed(ctx context.Context, params internal.ActionOperationFailedParams) (error) { + pmap := datastore.Optional() + pmap.Add("status_id", model.OperationStatusFailed) + pmap.Add("status_message", params.StatusMessage) + return svc.DB.Operations().Update(ctx, params.OperationID, pmap) +} diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index c9846d1..02a5533 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -2,12 +2,30 @@ package service_internal import ( "context" + "errors" - internal "git.itzana.me/strafesnet/maps-service/pkg/internal" "git.itzana.me/strafesnet/maps-service/pkg/datastore" + internal "git.itzana.me/strafesnet/maps-service/pkg/internal" "git.itzana.me/strafesnet/maps-service/pkg/model" ) +var( + // prevent two mapfixes with same asset id + ActiveSubmissionStatuses = []model.SubmissionStatus{ + model.SubmissionStatusUploading, + model.SubmissionStatusValidated, + model.SubmissionStatusValidating, + model.SubmissionStatusAccepted, + model.SubmissionStatusChangesRequested, + model.SubmissionStatusSubmitted, + model.SubmissionStatusUnderConstruction, + } +) + +var( + ErrActiveSubmissionSameAssetID = errors.New("There is an active submission with the same AssetID") +) + // UpdateSubmissionValidatedModel implements patchSubmissionModel operation. // // Update model following role restrictions. @@ -60,3 +78,53 @@ func (svc *Service) ActionSubmissionUploaded(ctx context.Context, params interna smap.Add("uploaded_asset_id", params.UploadedAssetID) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap) } + +// POST /submissions +func (svc *Service) CreateSubmission(ctx context.Context, request *internal.SubmissionCreate) (*internal.ID, error) { + // Check if an active submission with the same asset id exists + { + filter := datastore.Optional() + filter.Add("asset_id", request.AssetID) + filter.Add("asset_version", request.AssetVersion) + filter.Add("status_id", ActiveSubmissionStatuses) + active_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{ + Number: 1, + Size: 1, + },datastore.ListSortDisabled) + if err != nil { + return nil, err + } + if len(active_submissions) != 0{ + return nil, ErrActiveSubmissionSameAssetID + } + } + + operation, err := svc.DB.Operations().Get(ctx, request.OperationID) + if err != nil { + return nil, err + } + + // check if user owns asset + // TODO: allow bypass by admin + if operation.Owner != request.AssetOwner { + return nil, ErrNotAssetOwner + } + + submission, err := svc.DB.Submissions().Create(ctx, model.Submission{ + ID: 0, + DisplayName: request.DisplayName, + Creator: request.Creator, + GameID: request.GameID, + Submitter: request.AssetOwner, + AssetID: request.AssetID, + AssetVersion: request.AssetVersion, + Completed: false, + StatusID: model.SubmissionStatusUnderConstruction, + }) + if err != nil { + return nil, err + } + return &internal.ID{ + ID: submission.ID, + }, nil +} diff --git a/validation/api/src/internal.rs b/validation/api/src/internal.rs index 9897f9f..ed0e85c 100644 --- a/validation/api/src/internal.rs +++ b/validation/api/src/internal.rs @@ -150,6 +150,17 @@ impl Context{ ).await.map_err(Error::Response)? .json().await.map_err(Error::ReqwestJson) } + pub async fn create_submission<'a>(&self,config:CreateSubmissionRequest<'a>)->Result<SubmissionIDResponse,Error>{ + let url_raw=format!("{}/submissions",self.0.base_url); + let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; + + let body=serde_json::to_string(&config).map_err(Error::JSON)?; + + response_ok( + self.0.post(url,body).await.map_err(Error::Reqwest)? + ).await.map_err(Error::Response)? + .json().await.map_err(Error::ReqwestJson) + } // simple submission endpoints action!("submissions",action_submission_validated,config,SubmissionID,"validator-validated",config.0,); action!("submissions",update_submission_validated_model,config,UpdateSubmissionModelRequest,"validated-model",config.SubmissionID, @@ -162,6 +173,17 @@ impl Context{ action!("submissions",action_submission_accepted,config,ActionSubmissionAcceptedRequest,"validator-failed",config.SubmissionID, ("StatusMessage",config.StatusMessage.as_str()) ); + pub async fn create_mapfix<'a>(&self,config:CreateMapfixRequest<'a>)->Result<MapfixIDResponse,Error>{ + let url_raw=format!("{}/mapfixes",self.0.base_url); + let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; + + let body=serde_json::to_string(&config).map_err(Error::JSON)?; + + response_ok( + self.0.post(url,body).await.map_err(Error::Reqwest)? + ).await.map_err(Error::Response)? + .json().await.map_err(Error::ReqwestJson) + } // simple mapfixes endpoints action!("mapfixes",action_mapfix_validated,config,MapfixID,"validator-validated",config.0,); action!("mapfixes",update_mapfix_validated_model,config,UpdateMapfixModelRequest,"validated-model",config.MapfixID, @@ -172,4 +194,8 @@ impl Context{ action!("mapfixes",action_mapfix_accepted,config,ActionMapfixAcceptedRequest,"validator-failed",config.MapfixID, ("StatusMessage",config.StatusMessage.as_str()) ); + // simple operation endpoint + action!("operations",action_operation_failed,config,ActionOperationFailedRequest,"operation-failed",config.OperationID, + ("StatusMessage",config.StatusMessage.as_str()) + ); } diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs index 6cdd357..56743b2 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -61,6 +61,42 @@ pub async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,R } } + +#[allow(nonstandard_style)] +#[derive(Clone,Debug,serde::Serialize)] +pub struct CreateMapfixRequest<'a>{ + pub OperationID:i32, + pub AssetOwner:i64, + pub DisplayName:&'a str, + pub Creator:&'a str, + pub GameID:i32, + pub AssetID:u64, + pub AssetVersion:u64, + pub TargetAssetID:u64, +} +#[allow(nonstandard_style)] +#[derive(Clone,Debug,serde::Deserialize)] +pub struct MapfixIDResponse{ + pub ID:MapfixID, +} + +#[allow(nonstandard_style)] +#[derive(Clone,Debug,serde::Serialize)] +pub struct CreateSubmissionRequest<'a>{ + pub OperationID:i32, + pub AssetOwner:i64, + pub DisplayName:&'a str, + pub Creator:&'a str, + pub GameID:i32, + pub AssetID:u64, + pub AssetVersion:u64, +} +#[allow(nonstandard_style)] +#[derive(Clone,Debug,serde::Deserialize)] +pub struct SubmissionIDResponse{ + pub ID:SubmissionID, +} + #[derive(Clone,Copy,Debug,PartialEq,Eq,serde::Serialize,serde::Deserialize)] pub struct ScriptID(pub(crate)i64); #[derive(Clone,Copy,Debug,serde::Serialize,serde::Deserialize)] @@ -200,7 +236,7 @@ pub struct ActionSubmissionAcceptedRequest{ pub StatusMessage:String, } -#[derive(Clone,Copy,Debug)] +#[derive(Clone,Copy,Debug,serde::Deserialize)] pub struct SubmissionID(pub i64); #[allow(nonstandard_style)] @@ -224,5 +260,12 @@ pub struct ActionMapfixAcceptedRequest{ pub StatusMessage:String, } -#[derive(Clone,Copy,Debug)] +#[derive(Clone,Copy,Debug,serde::Deserialize)] pub struct MapfixID(pub i64); + +#[allow(nonstandard_style)] +#[derive(Clone,Debug)] +pub struct ActionOperationFailedRequest{ + pub OperationID:i32, + pub StatusMessage:String, +} diff --git a/validation/src/create.rs b/validation/src/create.rs new file mode 100644 index 0000000..1513904 --- /dev/null +++ b/validation/src/create.rs @@ -0,0 +1,77 @@ +use crate::rbx_util::{get_mapinfo,read_dom,MapInfo,ReadDomError,GetMapInfoError,ParseGameIDError}; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error{ + ModelVersionsPage(rbx_asset::cookie::PageError), + EmptyVersionsPage, + WrongCreatorType, + ModelFileDownload(rbx_asset::cookie::GetError), + ModelFileDecode(ReadDomError), + GetMapInfo(GetMapInfoError), + ParseGameID(ParseGameIDError), +} +impl std::fmt::Display for Error{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for Error{} + +#[allow(nonstandard_style)] +pub struct CreateRequest{ + pub ModelID:u64, +} +#[allow(nonstandard_style)] +pub struct CreateResult{ + pub AssetOwner:i64, + pub DisplayName:String, + pub Creator:String, + pub GameID:i32, + pub AssetVersion:u64, +} +impl crate::message_handler::MessageHandler{ + pub async fn create_inner(&self,create_info:CreateRequest)->Result<CreateResult,Error>{ + // discover the latest asset version + let asset_versions_page=self.cookie_context.get_asset_versions_page(rbx_asset::cookie::AssetVersionsPageRequest{ + asset_id:create_info.ModelID, + cursor:None + }).await.map_err(Error::ModelVersionsPage)?; + + // grab version info + let first_version=asset_versions_page.data.first().ok_or(Error::EmptyVersionsPage)?; + + if first_version.creatorType!="User"{ + return Err(Error::WrongCreatorType); + } + + let asset_creator_id=first_version.creatorTargetId; + let asset_version=first_version.assetVersionNumber; + + // download the map model version + let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ + asset_id:create_info.ModelID, + version:Some(asset_version), + }).await.map_err(Error::ModelFileDownload)?; + + // decode dom (slow!) + let dom=read_dom(&mut std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; + + // parse create fields out of asset + let MapInfo{ + display_name, + creator, + game_id, + }=get_mapinfo(&dom).map_err(Error::GetMapInfo)?; + + let game_id=game_id.map_err(Error::ParseGameID)?; + + Ok(CreateResult{ + AssetOwner:asset_creator_id as i64, + DisplayName:display_name.unwrap_or_default().to_owned(), + Creator:creator.unwrap_or_default().to_owned(), + GameID:game_id as i32, + AssetVersion:asset_version, + }) + } +} diff --git a/validation/src/create_mapfix.rs b/validation/src/create_mapfix.rs new file mode 100644 index 0000000..6dbe905 --- /dev/null +++ b/validation/src/create_mapfix.rs @@ -0,0 +1,47 @@ +use crate::nats_types::CreateMapfixRequest; +use crate::create::CreateRequest; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error{ + ApiActionMapfixCreate(submissions_api::Error), + ApiActionOperationFailed(submissions_api::Error), +} +impl std::fmt::Display for Error{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for Error{} + +impl crate::message_handler::MessageHandler{ + pub async fn create_mapfix(&self,create_info:CreateMapfixRequest)->Result<(),Error>{ + let create_result=self.create_inner(CreateRequest{ + ModelID:create_info.ModelID, + }).await; + + match create_result{ + Ok(create_request)=>{ + // call create on api + self.api.create_mapfix(submissions_api::types::CreateMapfixRequest{ + OperationID:create_info.OperationID, + AssetOwner:create_request.AssetOwner, + DisplayName:create_request.DisplayName.as_str(), + Creator:create_request.Creator.as_str(), + GameID:create_request.GameID, + AssetID:create_info.ModelID, + AssetVersion:create_request.AssetVersion, + TargetAssetID:create_info.TargetAssetID, + }).await.map_err(Error::ApiActionMapfixCreate)?; + }, + Err(e)=>{ + self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{ + OperationID:create_info.OperationID, + StatusMessage:format!("{e}"), + }).await.map_err(Error::ApiActionOperationFailed)?; + }, + } + + Ok(()) + } +} diff --git a/validation/src/create_submission.rs b/validation/src/create_submission.rs new file mode 100644 index 0000000..31f2a0e --- /dev/null +++ b/validation/src/create_submission.rs @@ -0,0 +1,46 @@ +use crate::nats_types::CreateSubmissionRequest; +use crate::create::CreateRequest; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error{ + ApiActionSubmissionCreate(submissions_api::Error), + ApiActionOperationFailed(submissions_api::Error), +} +impl std::fmt::Display for Error{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for Error{} + +impl crate::message_handler::MessageHandler{ + pub async fn create_submission(&self,create_info:CreateSubmissionRequest)->Result<(),Error>{ + let create_result=self.create_inner(CreateRequest{ + ModelID:create_info.ModelID, + }).await; + + match create_result{ + Ok(create_request)=>{ + // call create on api + self.api.create_submission(submissions_api::types::CreateSubmissionRequest{ + OperationID:create_info.OperationID, + AssetOwner:create_request.AssetOwner, + DisplayName:create_request.DisplayName.as_str(), + Creator:create_request.Creator.as_str(), + GameID:create_request.GameID, + AssetID:create_info.ModelID, + AssetVersion:create_request.AssetVersion, + }).await.map_err(Error::ApiActionSubmissionCreate)?; + }, + Err(e)=>{ + self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{ + OperationID:create_info.OperationID, + StatusMessage:format!("{e}"), + }).await.map_err(Error::ApiActionOperationFailed)?; + }, + } + + Ok(()) + } +} diff --git a/validation/src/main.rs b/validation/src/main.rs index 9234aa8..30b443d 100644 --- a/validation/src/main.rs +++ b/validation/src/main.rs @@ -1,9 +1,12 @@ use futures::StreamExt; +mod rbx_util; mod message_handler; mod nats_types; mod types; -mod uploader; +mod create; +mod create_mapfix; +mod create_submission; mod upload_mapfix; mod upload_submission; mod validator; @@ -84,7 +87,7 @@ async fn main()->Result<(),StartupError>{ Err(e)=>println!("[Validation] There was an error, oopsie! {e}"), } // explicitly call drop to make the move semantics and permit release more obvious - core::mem::drop(permit); + drop(permit); }); } }; diff --git a/validation/src/message_handler.rs b/validation/src/message_handler.rs index c6cd200..bff0f10 100644 --- a/validation/src/message_handler.rs +++ b/validation/src/message_handler.rs @@ -5,10 +5,12 @@ pub enum HandleMessageError{ DoubleAck(async_nats::Error), Json(serde_json::Error), UnknownSubject(String), - UploadMapfix(crate::upload_mapfix::UploadError), - UploadSubmission(crate::upload_submission::UploadError), - ValidateMapfix(crate::validate_mapfix::ValidateMapfixError), - ValidateSubmission(crate::validate_submission::ValidateSubmissionError), + CreateMapfix(crate::create_mapfix::Error), + CreateSubmission(crate::create_submission::Error), + UploadMapfix(crate::upload_mapfix::Error), + UploadSubmission(crate::upload_submission::Error), + ValidateMapfix(crate::validate_mapfix::Error), + ValidateSubmission(crate::validate_submission::Error), } impl std::fmt::Display for HandleMessageError{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ @@ -24,10 +26,9 @@ fn from_slice<'a,T:serde::de::Deserialize<'a>>(slice:&'a [u8])->Result<T,HandleM } pub struct MessageHandler{ - upload_mapfix:crate::upload_mapfix::Uploader, - upload_submission:crate::upload_submission::Uploader, - validate_mapfix:crate::validate_mapfix::Validator, - validate_submission:crate::validate_submission::Validator, + pub(crate) cookie_context:rbx_asset::cookie::CookieContext, + pub(crate) group_id:Option<u64>, + pub(crate) api:submissions_api::internal::Context, } impl MessageHandler{ @@ -37,20 +38,21 @@ impl MessageHandler{ api:submissions_api::internal::Context, )->Self{ Self{ - upload_mapfix:crate::upload_mapfix::Uploader::new(crate::uploader::Uploader::new(cookie_context.clone(),group_id,api.clone())), - upload_submission:crate::upload_submission::Uploader::new(crate::uploader::Uploader::new(cookie_context.clone(),group_id,api.clone())), - validate_mapfix:crate::validate_mapfix::Validator::new(crate::validator::Validator::new(cookie_context.clone(),api.clone())), - validate_submission:crate::validate_submission::Validator::new(crate::validator::Validator::new(cookie_context,api)), + cookie_context, + group_id, + api, } } pub async fn handle_message_result(&self,message_result:MessageResult)->Result<(),HandleMessageError>{ let message=message_result.map_err(HandleMessageError::Messages)?; message.double_ack().await.map_err(HandleMessageError::DoubleAck)?; match message.subject.as_str(){ - "maptest.mapfixes.upload"=>self.upload_mapfix.upload(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadMapfix), - "maptest.submissions.upload"=>self.upload_submission.upload(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadSubmission), - "maptest.mapfixes.validate"=>self.validate_mapfix.validate(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateMapfix), - "maptest.submissions.validate"=>self.validate_submission.validate(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateSubmission), + "maptest.mapfixes.create"=>self.create_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::CreateMapfix), + "maptest.submissions.create"=>self.create_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::CreateSubmission), + "maptest.mapfixes.upload"=>self.upload_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadMapfix), + "maptest.submissions.upload"=>self.upload_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadSubmission), + "maptest.mapfixes.validate"=>self.validate_mapfix(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateMapfix), + "maptest.submissions.validate"=>self.validate_submission(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateSubmission), other=>Err(HandleMessageError::UnknownSubject(other.to_owned())) } } diff --git a/validation/src/nats_types.rs b/validation/src/nats_types.rs index 15f0924..1401f83 100644 --- a/validation/src/nats_types.rs +++ b/validation/src/nats_types.rs @@ -4,6 +4,22 @@ // Requests are sent from maps-service to validator // Validation invokes the REST api to update the submissions +#[allow(nonstandard_style)] +#[derive(serde::Deserialize)] +pub struct CreateSubmissionRequest{ + // operation_id is passed back in the response message + pub OperationID:i32, + pub ModelID:u64, +} + +#[allow(nonstandard_style)] +#[derive(serde::Deserialize)] +pub struct CreateMapfixRequest{ + pub OperationID:i32, + pub ModelID:u64, + pub TargetAssetID:u64, +} + #[allow(nonstandard_style)] #[derive(serde::Deserialize)] pub struct ValidateSubmissionRequest{ diff --git a/validation/src/rbx_util.rs b/validation/src/rbx_util.rs new file mode 100644 index 0000000..947bb9c --- /dev/null +++ b/validation/src/rbx_util.rs @@ -0,0 +1,119 @@ + +#[allow(dead_code)] +#[derive(Debug)] +pub enum ReadDomError{ + Binary(rbx_binary::DecodeError), + Xml(rbx_xml::DecodeError), + Read(std::io::Error), + Seek(std::io::Error), + UnknownFormat([u8;8]), +} +impl std::fmt::Display for ReadDomError{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for ReadDomError{} + +pub fn read_dom<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<rbx_dom_weak::WeakDom,ReadDomError>{ + let mut first_8=[0u8;8]; + std::io::Read::read_exact(input,&mut first_8).map_err(ReadDomError::Read)?; + std::io::Seek::rewind(input).map_err(ReadDomError::Seek)?; + match &first_8[0..4]{ + b"<rob"=>{ + match &first_8[4..8]{ + b"lox!"=>rbx_binary::from_reader(input).map_err(ReadDomError::Binary), + b"lox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).map_err(ReadDomError::Xml), + _=>Err(ReadDomError::UnknownFormat(first_8)), + } + }, + _=>Err(ReadDomError::UnknownFormat(first_8)), + } +} + +pub fn class_is_a(class:&str,superclass:&str)->bool{ + if class==superclass{ + return true + } + let class_descriptor=rbx_reflection_database::get().classes.get(class); + if let Some(descriptor)=&class_descriptor{ + if let Some(class_super)=&descriptor.superclass{ + return class_is_a(&class_super,superclass) + } + } + false +} + +pub fn find_first_child_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str,class:&str)->Option<&'a rbx_dom_weak::Instance>{ + for &referent in instance.children(){ + if let Some(c)=dom.get_by_ref(referent){ + if c.name==name&&class_is_a(c.class.as_str(),class) { + return Some(c); + } + } + } + None +} + +pub enum GameID{ + Bhop=1, + Surf=2, + FlyTrials=5, +} +#[derive(Debug)] +pub struct ParseGameIDError; +impl std::str::FromStr for GameID{ + type Err=ParseGameIDError; + fn from_str(s:&str)->Result<Self,Self::Err>{ + if s.starts_with("bhop_"){ + return Ok(GameID::Bhop); + } + if s.starts_with("surf_"){ + return Ok(GameID::Surf); + } + if s.starts_with("flytrials_"){ + return Ok(GameID::FlyTrials); + } + return Err(ParseGameIDError); + } +} + +pub struct MapInfo<'a>{ + pub display_name:Result<&'a str,StringValueError>, + pub creator:Result<&'a str,StringValueError>, + pub game_id:Result<GameID,ParseGameIDError>, +} + +pub enum StringValueError{ + ObjectNotFound, + ValueNotSet, + NonStringValue, +} + +fn string_value(instance:Option<&rbx_dom_weak::Instance>)->Result<&str,StringValueError>{ + let instance=instance.ok_or(StringValueError::ObjectNotFound)?; + let value=instance.properties.get("Value").ok_or(StringValueError::ValueNotSet)?; + match value{ + rbx_dom_weak::types::Variant::String(value)=>Ok(value), + _=>Err(StringValueError::NonStringValue), + } +} + +#[derive(Debug)] +pub enum GetMapInfoError{ + ModelFileRootMustHaveOneChild, + ModelFileChildRefIsNil, +} + +pub fn get_mapinfo(dom:&rbx_dom_weak::WeakDom)->Result<MapInfo,GetMapInfoError>{ + let &[map_ref]=dom.root().children()else{ + return Err(GetMapInfoError::ModelFileRootMustHaveOneChild); + }; + let model_instance=dom.get_by_ref(map_ref).ok_or(GetMapInfoError::ModelFileChildRefIsNil)?; + + Ok(MapInfo{ + display_name:string_value(find_first_child_class(dom,model_instance,"DisplayName","StringValue")), + creator:string_value(find_first_child_class(dom,model_instance,"Creator","StringValue")), + game_id:model_instance.name.parse(), + }) +} diff --git a/validation/src/upload_mapfix.rs b/validation/src/upload_mapfix.rs index 71c37fd..78108a4 100644 --- a/validation/src/upload_mapfix.rs +++ b/validation/src/upload_mapfix.rs @@ -2,48 +2,43 @@ use crate::nats_types::UploadMapfixRequest; #[allow(dead_code)] #[derive(Debug)] -pub enum UploadError{ +pub enum Error{ Get(rbx_asset::cookie::GetError), Json(serde_json::Error), Upload(rbx_asset::cookie::UploadError), ApiActionMapfixUploaded(submissions_api::Error), } -impl std::fmt::Display for UploadError{ +impl std::fmt::Display for Error{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"{self:?}") } } -impl std::error::Error for UploadError{} +impl std::error::Error for Error{} -pub struct Uploader(crate::uploader::Uploader); -impl Uploader{ - pub const fn new(inner:crate::uploader::Uploader)->Self{ - Self(inner) - } - pub async fn upload(&self,upload_info:UploadMapfixRequest)->Result<(),UploadError>{ - let Self(uploader)=self; +impl crate::message_handler::MessageHandler{ + pub async fn upload_mapfix(&self,upload_info:UploadMapfixRequest)->Result<(),Error>{ // download the map model version - let model_data=uploader.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{ + let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ asset_id:upload_info.ModelID, version:Some(upload_info.ModelVersion), - }).await.map_err(UploadError::Get)?; + }).await.map_err(Error::Get)?; // upload the map to the strafesnet group - let _upload_response=uploader.roblox_cookie.upload(rbx_asset::cookie::UploadRequest{ + let _upload_response=self.cookie_context.upload(rbx_asset::cookie::UploadRequest{ assetid:upload_info.TargetAssetID, - groupId:uploader.group_id, + groupId:self.group_id, name:None, description:None, ispublic:None, allowComments:None, - },model_data).await.map_err(UploadError::Upload)?; + },model_data).await.map_err(Error::Upload)?; // that's it, the database entry does not need to be changed. // mark mapfix as uploaded, TargetAssetID is unchanged - uploader.api.action_mapfix_uploaded(submissions_api::types::ActionMapfixUploadedRequest{ + self.api.action_mapfix_uploaded(submissions_api::types::ActionMapfixUploadedRequest{ MapfixID:upload_info.MapfixID, - }).await.map_err(UploadError::ApiActionMapfixUploaded)?; + }).await.map_err(Error::ApiActionMapfixUploaded)?; Ok(()) } diff --git a/validation/src/upload_submission.rs b/validation/src/upload_submission.rs index d5c0983..ef8618a 100644 --- a/validation/src/upload_submission.rs +++ b/validation/src/upload_submission.rs @@ -2,47 +2,42 @@ use crate::nats_types::UploadSubmissionRequest; #[allow(dead_code)] #[derive(Debug)] -pub enum UploadError{ +pub enum Error{ Get(rbx_asset::cookie::GetError), Json(serde_json::Error), Create(rbx_asset::cookie::CreateError), SystemTime(std::time::SystemTimeError), ApiActionSubmissionUploaded(submissions_api::Error), } -impl std::fmt::Display for UploadError{ +impl std::fmt::Display for Error{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"{self:?}") } } -impl std::error::Error for UploadError{} +impl std::error::Error for Error{} -pub struct Uploader(crate::uploader::Uploader); -impl Uploader{ - pub const fn new(inner:crate::uploader::Uploader)->Self{ - Self(inner) - } - pub async fn upload(&self,upload_info:UploadSubmissionRequest)->Result<(),UploadError>{ - let Self(uploader)=self; +impl crate::message_handler::MessageHandler{ + pub async fn upload_submission(&self,upload_info:UploadSubmissionRequest)->Result<(),Error>{ // download the map model version - let model_data=uploader.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{ + let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ asset_id:upload_info.ModelID, version:Some(upload_info.ModelVersion), - }).await.map_err(UploadError::Get)?; + }).await.map_err(Error::Get)?; // upload the map to the strafesnet group - let upload_response=uploader.roblox_cookie.create(rbx_asset::cookie::CreateRequest{ + let upload_response=self.cookie_context.create(rbx_asset::cookie::CreateRequest{ name:upload_info.ModelName.clone(), description:"".to_owned(), ispublic:false, allowComments:false, - groupId:uploader.group_id, - },model_data).await.map_err(UploadError::Create)?; + groupId:self.group_id, + },model_data).await.map_err(Error::Create)?; // note the asset id of the created model for later release, and mark the submission as uploaded - uploader.api.action_submission_uploaded(submissions_api::types::ActionSubmissionUploadedRequest{ + self.api.action_submission_uploaded(submissions_api::types::ActionSubmissionUploadedRequest{ SubmissionID:upload_info.SubmissionID, UploadedAssetID:upload_response.AssetId, - }).await.map_err(UploadError::ApiActionSubmissionUploaded)?; + }).await.map_err(Error::ApiActionSubmissionUploaded)?; Ok(()) } diff --git a/validation/src/uploader.rs b/validation/src/uploader.rs deleted file mode 100644 index ca1ba06..0000000 --- a/validation/src/uploader.rs +++ /dev/null @@ -1,18 +0,0 @@ -pub struct Uploader{ - pub(crate) roblox_cookie:rbx_asset::cookie::CookieContext, - pub(crate) group_id:Option<u64>, - pub(crate) api:submissions_api::internal::Context, -} -impl Uploader{ - pub const fn new( - roblox_cookie:rbx_asset::cookie::CookieContext, - group_id:Option<u64>, - api:submissions_api::internal::Context, - )->Self{ - Self{ - roblox_cookie, - group_id, - api, - } - } -} diff --git a/validation/src/validate_mapfix.rs b/validation/src/validate_mapfix.rs index f9334d5..c1607f6 100644 --- a/validation/src/validate_mapfix.rs +++ b/validation/src/validate_mapfix.rs @@ -2,41 +2,35 @@ use crate::nats_types::ValidateMapfixRequest; #[allow(dead_code)] #[derive(Debug)] -pub enum ValidateMapfixError{ +pub enum Error{ ApiActionMapfixValidate(submissions_api::Error), } -impl std::fmt::Display for ValidateMapfixError{ +impl std::fmt::Display for Error{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"{self:?}") } } -impl std::error::Error for ValidateMapfixError{} - -pub struct Validator(crate::validator::Validator); -impl Validator{ - pub const fn new(inner:crate::validator::Validator)->Self{ - Self(inner) - } - pub async fn validate(&self,validate_info:ValidateMapfixRequest)->Result<(),ValidateMapfixError>{ - let Self(validator)=self; +impl std::error::Error for Error{} +impl crate::message_handler::MessageHandler{ + pub async fn validate_mapfix(&self,validate_info:ValidateMapfixRequest)->Result<(),Error>{ let mapfix_id=validate_info.MapfixID; - let validate_result=validator.validate(validate_info.into()).await; + let validate_result=self.validate_inner(validate_info.into()).await; // update the mapfix depending on the result match &validate_result{ Ok(())=>{ // update the mapfix model status to validated - validator.api.action_mapfix_validated( + self.api.action_mapfix_validated( submissions_api::types::MapfixID(mapfix_id) - ).await.map_err(ValidateMapfixError::ApiActionMapfixValidate)?; + ).await.map_err(Error::ApiActionMapfixValidate)?; }, Err(e)=>{ // update the mapfix model status to accepted - validator.api.action_mapfix_accepted(submissions_api::types::ActionMapfixAcceptedRequest{ + self.api.action_mapfix_accepted(submissions_api::types::ActionMapfixAcceptedRequest{ MapfixID:mapfix_id, StatusMessage:format!("{e}"), - }).await.map_err(ValidateMapfixError::ApiActionMapfixValidate)?; + }).await.map_err(Error::ApiActionMapfixValidate)?; }, } diff --git a/validation/src/validate_submission.rs b/validation/src/validate_submission.rs index 51ae1f3..5859bd6 100644 --- a/validation/src/validate_submission.rs +++ b/validation/src/validate_submission.rs @@ -2,41 +2,35 @@ use crate::nats_types::ValidateSubmissionRequest; #[allow(dead_code)] #[derive(Debug)] -pub enum ValidateSubmissionError{ +pub enum Error{ ApiActionSubmissionValidate(submissions_api::Error), } -impl std::fmt::Display for ValidateSubmissionError{ +impl std::fmt::Display for Error{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"{self:?}") } } -impl std::error::Error for ValidateSubmissionError{} - -pub struct Validator(crate::validator::Validator); -impl Validator{ - pub const fn new(inner:crate::validator::Validator)->Self{ - Self(inner) - } - pub async fn validate(&self,validate_info:ValidateSubmissionRequest)->Result<(),ValidateSubmissionError>{ - let Self(validator)=self; +impl std::error::Error for Error{} +impl crate::message_handler::MessageHandler{ + pub async fn validate_submission(&self,validate_info:ValidateSubmissionRequest)->Result<(),Error>{ let submission_id=validate_info.SubmissionID; - let validate_result=validator.validate(validate_info.into()).await; + let validate_result=self.validate_inner(validate_info.into()).await; // update the submission depending on the result match &validate_result{ Ok(())=>{ // update the submission model status to validated - validator.api.action_submission_validated( + self.api.action_submission_validated( submissions_api::types::SubmissionID(submission_id) - ).await.map_err(ValidateSubmissionError::ApiActionSubmissionValidate)?; + ).await.map_err(Error::ApiActionSubmissionValidate)?; }, Err(e)=>{ // update the submission model status to accepted - validator.api.action_submission_accepted(submissions_api::types::ActionSubmissionAcceptedRequest{ + self.api.action_submission_accepted(submissions_api::types::ActionSubmissionAcceptedRequest{ SubmissionID:submission_id, StatusMessage:format!("{e}"), - }).await.map_err(ValidateSubmissionError::ApiActionSubmissionValidate)?; + }).await.map_err(Error::ApiActionSubmissionValidate)?; }, } diff --git a/validation/src/validator.rs b/validation/src/validator.rs index 5d3b1ed..c114ca3 100644 --- a/validation/src/validator.rs +++ b/validation/src/validator.rs @@ -1,6 +1,7 @@ use futures::TryStreamExt; use submissions_api::types::ResourceType; +use crate::rbx_util::{class_is_a,read_dom,ReadDomError}; use crate::types::ResourceID; const SCRIPT_CONCURRENCY:usize=16; @@ -86,24 +87,10 @@ impl From<crate::nats_types::ValidateSubmissionRequest> for ValidateRequest{ } } -pub struct Validator{ - pub(crate) roblox_cookie:rbx_asset::cookie::CookieContext, - pub(crate) api:submissions_api::internal::Context, -} - -impl Validator{ - pub const fn new( - roblox_cookie:rbx_asset::cookie::CookieContext, - api:submissions_api::internal::Context, - )->Self{ - Self{ - roblox_cookie, - api, - } - } - pub async fn validate(&self,validate_info:ValidateRequest)->Result<(),ValidateError>{ +impl crate::message_handler::MessageHandler{ + pub async fn validate_inner(&self,validate_info:ValidateRequest)->Result<(),ValidateError>{ // download map - let data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{ + let data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{ asset_id:validate_info.ModelID, version:Some(validate_info.ModelVersion), }).await.map_err(ValidateError::ModelFileDownload)?; @@ -238,7 +225,7 @@ impl Validator{ // upload a model lol let model_id=if let Some(model_id)=validate_info.ValidatedModelID{ // upload to existing id - let response=self.roblox_cookie.upload(rbx_asset::cookie::UploadRequest{ + let response=self.cookie_context.upload(rbx_asset::cookie::UploadRequest{ assetid:model_id, name:None, description:None, @@ -254,7 +241,7 @@ impl Validator{ return Err(ValidateError::ModelFileChildRefIsNil); }; // create new model - let response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{ + let response=self.cookie_context.create(rbx_asset::cookie::CreateRequest{ name:map_instance.name.clone(), description:"".to_owned(), ispublic:true, @@ -289,51 +276,6 @@ impl Validator{ } } -#[allow(dead_code)] -#[derive(Debug)] -pub enum ReadDomError{ - Binary(rbx_binary::DecodeError), - Xml(rbx_xml::DecodeError), - Read(std::io::Error), - Seek(std::io::Error), - UnknownFormat([u8;8]), -} -impl std::fmt::Display for ReadDomError{ - fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ - write!(f,"{self:?}") - } -} -impl std::error::Error for ReadDomError{} - -fn read_dom<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<rbx_dom_weak::WeakDom,ReadDomError>{ - let mut first_8=[0u8;8]; - std::io::Read::read_exact(input,&mut first_8).map_err(ReadDomError::Read)?; - std::io::Seek::rewind(input).map_err(ReadDomError::Seek)?; - match &first_8[0..4]{ - b"<rob"=>{ - match &first_8[4..8]{ - b"lox!"=>rbx_binary::from_reader(input).map_err(ReadDomError::Binary), - b"lox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).map_err(ReadDomError::Xml), - _=>Err(ReadDomError::UnknownFormat(first_8)), - } - }, - _=>Err(ReadDomError::UnknownFormat(first_8)), - } -} - -fn class_is_a(class:&str,superclass:&str)->bool{ - if class==superclass{ - return true - } - let class_descriptor=rbx_reflection_database::get().classes.get(class); - if let Some(descriptor)=&class_descriptor{ - if let Some(class_super)=&descriptor.superclass{ - return class_is_a(&class_super,superclass) - } - } - false -} - fn recursive_collect_superclass(objects:&mut std::vec::Vec<rbx_dom_weak::types::Ref>,dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,superclass:&str){ for &referent in instance.children(){ if let Some(c)=dom.get_by_ref(referent){ diff --git a/web/src/app/maps/[mapId]/fix/_game.tsx b/web/src/app/maps/[mapId]/fix/_game.tsx deleted file mode 100644 index e754601..0000000 --- a/web/src/app/maps/[mapId]/fix/_game.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { FormControl, Select, InputLabel, MenuItem } from "@mui/material"; -import { styled } from '@mui/material/styles'; -import InputBase from '@mui/material/InputBase'; -import React from "react"; -import { SelectChangeEvent } from "@mui/material"; - -// TODO: Properly style everything instead of pasting 🤚 - -type GameSelectionProps = { - game: number; - setGame: React.Dispatch<React.SetStateAction<number>>; -}; - -const BootstrapInput = styled(InputBase)(({ theme }) => ({ - 'label + &': { - marginTop: theme.spacing(3), - }, - '& .MuiInputBase-input': { - backgroundColor: '#0000', - color: '#FFF', - border: '1px solid rgba(175, 175, 175, 0.66)', - fontSize: 16, - padding: '10px 26px 10px 12px', - transition: theme.transitions.create(['border-color', 'box-shadow']), - fontFamily: [ - '-apple-system', - 'BlinkMacSystemFont', - '"Segoe UI"', - 'Roboto', - '"Helvetica Neue"', - 'Arial', - 'sans-serif', - '"Apple Color Emoji"', - '"Segoe UI Emoji"', - '"Segoe UI Symbol"', - ].join(','), - '&:focus': { - borderRadius: 4, - borderColor: '#80bdff', - boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)', - }, - }, - })); - -export default function GameSelection({ game, setGame }: GameSelectionProps) { - const handleChange = (event: SelectChangeEvent) => { - setGame(Number(event.target.value)); // TODO: Change later!! there's 100% a proper way of doing this - }; - - return ( - <FormControl> - <InputLabel sx={{ color: "#646464" }}>Game</InputLabel> - <Select - value={String(game)} - label="Game" - onChange={handleChange} - input={<BootstrapInput />} - > - <MenuItem value={1}>Bhop</MenuItem> - <MenuItem value={2}>Surf</MenuItem> - <MenuItem value={3}>Fly Trials</MenuItem> - </Select> - </FormControl> - ); -} \ No newline at end of file diff --git a/web/src/app/maps/[mapId]/fix/page.tsx b/web/src/app/maps/[mapId]/fix/page.tsx index 5e68374..7408e43 100644 --- a/web/src/app/maps/[mapId]/fix/page.tsx +++ b/web/src/app/maps/[mapId]/fix/page.tsx @@ -2,20 +2,14 @@ import { Button, TextField } from "@mui/material" -import GameSelection from "./_game"; import SendIcon from '@mui/icons-material/Send'; import Webpage from "@/app/_components/webpage"; import { useParams } from "next/navigation"; -import React, { useState } from "react"; import "./(styles)/page.scss" interface MapfixPayload { - DisplayName: string; - Creator: string; - GameID: number; AssetID: number; - AssetVersion: number; TargetAssetID: number; } interface IdResponse { @@ -23,7 +17,6 @@ interface IdResponse { } export default function MapfixInfoPage() { - const [game, setGame] = useState(1); const dynamicId = useParams<{ mapId: string }>(); const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { @@ -33,11 +26,7 @@ export default function MapfixInfoPage() { const formData = new FormData(form); const payload: MapfixPayload = { - DisplayName: (formData.get("display-name") as string) ?? "unknown", // TEMPORARY! TODO: Change - Creator: (formData.get("creator") as string) ?? "unknown", // TEMPORARY! TODO: Change - GameID: game, AssetID: Number((formData.get("asset-id") as string) ?? "0"), - AssetVersion: 0, TargetAssetID: Number(dynamicId.mapId), }; @@ -80,11 +69,7 @@ export default function MapfixInfoPage() { </header> <form onSubmit={handleSubmit}> {/* TODO: Add form data for mapfixes, such as changes they did, and any times that need to be deleted & what styles */} - <TextField className="form-field" id="display-name" name="display-name" label="Display Name" variant="outlined"/> - <TextField className="form-field" id="creator" name="creator" label="Creator" variant="outlined"/> <TextField className="form-field" id="asset-id" name="asset-id" label="Asset ID" variant="outlined"/> - {/* I think this is Quat's job to figure this one out (to be set when someone clicks review(?)) */} {/* <TextField className="form-field" id="asset-version" label="Asset Version" variant="outlined"/> */} - <GameSelection game={game} setGame={setGame} /> <span className="spacer form-spacer"></span> <Button type="submit" variant="contained" startIcon={<SendIcon/>} sx={{ width: "400px", diff --git a/web/src/app/submit/_game.tsx b/web/src/app/submit/_game.tsx deleted file mode 100644 index e754601..0000000 --- a/web/src/app/submit/_game.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { FormControl, Select, InputLabel, MenuItem } from "@mui/material"; -import { styled } from '@mui/material/styles'; -import InputBase from '@mui/material/InputBase'; -import React from "react"; -import { SelectChangeEvent } from "@mui/material"; - -// TODO: Properly style everything instead of pasting 🤚 - -type GameSelectionProps = { - game: number; - setGame: React.Dispatch<React.SetStateAction<number>>; -}; - -const BootstrapInput = styled(InputBase)(({ theme }) => ({ - 'label + &': { - marginTop: theme.spacing(3), - }, - '& .MuiInputBase-input': { - backgroundColor: '#0000', - color: '#FFF', - border: '1px solid rgba(175, 175, 175, 0.66)', - fontSize: 16, - padding: '10px 26px 10px 12px', - transition: theme.transitions.create(['border-color', 'box-shadow']), - fontFamily: [ - '-apple-system', - 'BlinkMacSystemFont', - '"Segoe UI"', - 'Roboto', - '"Helvetica Neue"', - 'Arial', - 'sans-serif', - '"Apple Color Emoji"', - '"Segoe UI Emoji"', - '"Segoe UI Symbol"', - ].join(','), - '&:focus': { - borderRadius: 4, - borderColor: '#80bdff', - boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)', - }, - }, - })); - -export default function GameSelection({ game, setGame }: GameSelectionProps) { - const handleChange = (event: SelectChangeEvent) => { - setGame(Number(event.target.value)); // TODO: Change later!! there's 100% a proper way of doing this - }; - - return ( - <FormControl> - <InputLabel sx={{ color: "#646464" }}>Game</InputLabel> - <Select - value={String(game)} - label="Game" - onChange={handleChange} - input={<BootstrapInput />} - > - <MenuItem value={1}>Bhop</MenuItem> - <MenuItem value={2}>Surf</MenuItem> - <MenuItem value={3}>Fly Trials</MenuItem> - </Select> - </FormControl> - ); -} \ No newline at end of file diff --git a/web/src/app/submit/page.tsx b/web/src/app/submit/page.tsx index b7108e9..caaef78 100644 --- a/web/src/app/submit/page.tsx +++ b/web/src/app/submit/page.tsx @@ -2,26 +2,19 @@ import { Button, TextField } from "@mui/material" -import GameSelection from "./_game"; import SendIcon from '@mui/icons-material/Send'; import Webpage from "@/app/_components/webpage" -import React, { useState } from "react"; import "./(styles)/page.scss" interface SubmissionPayload { - DisplayName: string; - Creator: string; - GameID: number; AssetID: number; - AssetVersion: number; } interface IdResponse { ID: number; } export default function SubmissionInfoPage() { - const [game, setGame] = useState(1); const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); @@ -30,11 +23,7 @@ export default function SubmissionInfoPage() { const formData = new FormData(form); const payload: SubmissionPayload = { - DisplayName: (formData.get("display-name") as string) ?? "unknown", // TEMPORARY! TODO: Change - Creator: (formData.get("creator") as string) ?? "unknown", // TEMPORARY! TODO: Change - GameID: game, AssetID: Number((formData.get("asset-id") as string) ?? "0"), - AssetVersion: 0, }; console.log(payload) @@ -75,12 +64,7 @@ export default function SubmissionInfoPage() { <span className="spacer form-spacer"></span> </header> <form onSubmit={handleSubmit}> - {/* TODO: Add form data for mapfixes, such as changes they did, and any times that need to be deleted & what styles */} - <TextField className="form-field" id="display-name" name="display-name" label="Display Name" variant="outlined"/> - <TextField className="form-field" id="creator" name="creator" label="Creator" variant="outlined"/> <TextField className="form-field" id="asset-id" name="asset-id" label="Asset ID" variant="outlined"/> - {/* I think this is Quat's job to figure this one out (to be set when someone clicks review(?)) */} {/* <TextField className="form-field" id="asset-version" label="Asset Version" variant="outlined"/> */} - <GameSelection game={game} setGame={setGame} /> <span className="spacer form-spacer"></span> <Button type="submit" variant="contained" startIcon={<SendIcon/>} sx={{ width: "400px",