From 75919b19c8fd0b7c8c2e43a19e0897874a6fb5fb Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sat, 7 Jun 2025 21:28:57 -0700 Subject: [PATCH 01/17] submissions: AuditEvent CheckReport --- pkg/model/audit_event.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/model/audit_event.go b/pkg/model/audit_event.go index 53b0632..db12c6b 100644 --- a/pkg/model/audit_event.go +++ b/pkg/model/audit_event.go @@ -48,6 +48,20 @@ type AuditEventDataError struct { Error string `json:"error"` } +type Check struct { + Name string `json:"name"` + Summary string `json:"summary"` + Passed bool `json:"passed"` + Details any `json:"details"` +} + +// Validator map checks details +const AuditEventTypeCheckReport AuditEventType = 7 + +type AuditEventDataCheckReport struct { + CheckList []Check `json:"checklist"` +} + type AuditEvent struct { ID int64 `gorm:"primaryKey"` CreatedAt time.Time -- 2.49.1 From 17d9c538400d45e74389c7e2471474d5624828d5 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sat, 7 Jun 2025 22:59:26 -0700 Subject: [PATCH 02/17] openapi: send detailed CheckList on internal changes requested --- openapi-internal.yaml | 68 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/openapi-internal.yaml b/openapi-internal.yaml index 8166333..7598b62 100644 --- a/openapi-internal.yaml +++ b/openapi-internal.yaml @@ -89,6 +89,29 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /mapfixes/{MapfixID}/checklist: + post: + summary: Validator posts a checklist to the audit log + operationId: createMapfixAuditCheckList + tags: + - Mapfixes + parameters: + - $ref: '#/components/parameters/MapfixID' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CheckList' + responses: + "204": + description: Successful response + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /mapfixes/{MapfixID}/status/validator-submitted: post: summary: (Internal endpoint) Role Validator changes status from Submitting -> Submitted @@ -304,6 +327,29 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /submissions/{SubmissionID}/checklist: + post: + summary: Validator posts a checklist to the audit log + operationId: createSubmissionAuditCheckList + tags: + - Submissions + parameters: + - $ref: '#/components/parameters/SubmissionID' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CheckList' + responses: + "204": + description: Successful response + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /submissions/{SubmissionID}/status/validator-submitted: post: summary: (Internal endpoint) Role Validator changes status from Submitting -> Submitted @@ -867,6 +913,28 @@ components: type: integer format: int32 minimum: 0 + Check: + required: + - Name + - Summary + - Passed + - Details + type: object + properties: + Name: + type: string + maxLength: 128 + Summary: + type: string + maxLength: 4096 + Passed: + type: boolean + Details: + type: object + CheckList: + type: array + items: + $ref: "#/components/schemas/Check" Error: description: Represents error object type: object -- 2.49.1 From 6d151afebf55ff3cd946f5e6b703bd808630b5f9 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sat, 7 Jun 2025 23:00:10 -0700 Subject: [PATCH 03/17] openapi: generate --- pkg/internal/oas_client_gen.go | 22 ++- pkg/internal/oas_handlers_gen.go | 46 ++++- pkg/internal/oas_json_gen.go | 239 +++++++++++++++++++++++ pkg/internal/oas_request_decoders_gen.go | 142 ++++++++++++++ pkg/internal/oas_request_encoders_gen.go | 28 +++ pkg/internal/oas_server_gen.go | 4 +- pkg/internal/oas_unimplemented_gen.go | 4 +- pkg/internal/oas_validators_gen.go | 77 ++++++++ 8 files changed, 542 insertions(+), 20 deletions(-) diff --git a/pkg/internal/oas_client_gen.go b/pkg/internal/oas_client_gen.go index 927f983..890513c 100644 --- a/pkg/internal/oas_client_gen.go +++ b/pkg/internal/oas_client_gen.go @@ -39,7 +39,7 @@ type Invoker interface { // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /mapfixes/{MapfixID}/status/validator-request-changes - ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error + ActionMapfixRequestChanges(ctx context.Context, request CheckList, params ActionMapfixRequestChangesParams) error // ActionMapfixSubmitted invokes actionMapfixSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -75,7 +75,7 @@ type Invoker interface { // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /submissions/{SubmissionID}/status/validator-request-changes - ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error + ActionSubmissionRequestChanges(ctx context.Context, request CheckList, params ActionSubmissionRequestChangesParams) error // ActionSubmissionSubmitted invokes actionSubmissionSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -305,12 +305,12 @@ func (c *Client) sendActionMapfixAccepted(ctx context.Context, params ActionMapf // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /mapfixes/{MapfixID}/status/validator-request-changes -func (c *Client) ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error { - _, err := c.sendActionMapfixRequestChanges(ctx, params) +func (c *Client) ActionMapfixRequestChanges(ctx context.Context, request CheckList, params ActionMapfixRequestChangesParams) error { + _, err := c.sendActionMapfixRequestChanges(ctx, request, params) return err } -func (c *Client) sendActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) (res *ActionMapfixRequestChangesNoContent, err error) { +func (c *Client) sendActionMapfixRequestChanges(ctx context.Context, request CheckList, params ActionMapfixRequestChangesParams) (res *ActionMapfixRequestChangesNoContent, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("actionMapfixRequestChanges"), semconv.HTTPRequestMethodKey.String("POST"), @@ -374,6 +374,9 @@ func (c *Client) sendActionMapfixRequestChanges(ctx context.Context, params Acti if err != nil { return res, errors.Wrap(err, "create request") } + if err := encodeActionMapfixRequestChangesRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } stage = "SendRequest" resp, err := c.cfg.Client.Do(r) @@ -929,12 +932,12 @@ func (c *Client) sendActionSubmissionAccepted(ctx context.Context, params Action // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /submissions/{SubmissionID}/status/validator-request-changes -func (c *Client) ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error { - _, err := c.sendActionSubmissionRequestChanges(ctx, params) +func (c *Client) ActionSubmissionRequestChanges(ctx context.Context, request CheckList, params ActionSubmissionRequestChangesParams) error { + _, err := c.sendActionSubmissionRequestChanges(ctx, request, params) return err } -func (c *Client) sendActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) (res *ActionSubmissionRequestChangesNoContent, err error) { +func (c *Client) sendActionSubmissionRequestChanges(ctx context.Context, request CheckList, params ActionSubmissionRequestChangesParams) (res *ActionSubmissionRequestChangesNoContent, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("actionSubmissionRequestChanges"), semconv.HTTPRequestMethodKey.String("POST"), @@ -998,6 +1001,9 @@ func (c *Client) sendActionSubmissionRequestChanges(ctx context.Context, params if err != nil { return res, errors.Wrap(err, "create request") } + if err := encodeActionSubmissionRequestChangesRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } stage = "SendRequest" resp, err := c.cfg.Client.Do(r) diff --git a/pkg/internal/oas_handlers_gen.go b/pkg/internal/oas_handlers_gen.go index a7c67c7..c45ec3b 100644 --- a/pkg/internal/oas_handlers_gen.go +++ b/pkg/internal/oas_handlers_gen.go @@ -263,6 +263,21 @@ func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEsc s.cfg.ErrorHandler(ctx, w, r, err) return } + request, close, err := s.decodeActionMapfixRequestChangesRequest(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 *ActionMapfixRequestChangesNoContent if m := s.cfg.Middleware; m != nil { @@ -271,7 +286,7 @@ func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEsc OperationName: ActionMapfixRequestChangesOperation, OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested", OperationID: "actionMapfixRequestChanges", - Body: nil, + Body: request, Params: middleware.Parameters{ { Name: "MapfixID", @@ -282,7 +297,7 @@ func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEsc } type ( - Request = struct{} + Request = CheckList Params = ActionMapfixRequestChangesParams Response = *ActionMapfixRequestChangesNoContent ) @@ -295,12 +310,12 @@ func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEsc mreq, unpackActionMapfixRequestChangesParams, func(ctx context.Context, request Request, params Params) (response Response, err error) { - err = s.h.ActionMapfixRequestChanges(ctx, params) + err = s.h.ActionMapfixRequestChanges(ctx, request, params) return response, err }, ) } else { - err = s.h.ActionMapfixRequestChanges(ctx, params) + err = s.h.ActionMapfixRequestChanges(ctx, request, params) } if err != nil { if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { @@ -1177,6 +1192,21 @@ func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, arg s.cfg.ErrorHandler(ctx, w, r, err) return } + request, close, err := s.decodeActionSubmissionRequestChangesRequest(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 *ActionSubmissionRequestChangesNoContent if m := s.cfg.Middleware; m != nil { @@ -1185,7 +1215,7 @@ func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, arg OperationName: ActionSubmissionRequestChangesOperation, OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested", OperationID: "actionSubmissionRequestChanges", - Body: nil, + Body: request, Params: middleware.Parameters{ { Name: "SubmissionID", @@ -1196,7 +1226,7 @@ func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, arg } type ( - Request = struct{} + Request = CheckList Params = ActionSubmissionRequestChangesParams Response = *ActionSubmissionRequestChangesNoContent ) @@ -1209,12 +1239,12 @@ func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, arg mreq, unpackActionSubmissionRequestChangesParams, func(ctx context.Context, request Request, params Params) (response Response, err error) { - err = s.h.ActionSubmissionRequestChanges(ctx, params) + err = s.h.ActionSubmissionRequestChanges(ctx, request, params) return response, err }, ) } else { - err = s.h.ActionSubmissionRequestChanges(ctx, params) + err = s.h.ActionSubmissionRequestChanges(ctx, request, params) } if err != nil { if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { diff --git a/pkg/internal/oas_json_gen.go b/pkg/internal/oas_json_gen.go index 9a74a1a..1f4f1b8 100644 --- a/pkg/internal/oas_json_gen.go +++ b/pkg/internal/oas_json_gen.go @@ -12,6 +12,245 @@ import ( "github.com/ogen-go/ogen/validate" ) +// Encode implements json.Marshaler. +func (s *Check) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Check) encodeFields(e *jx.Encoder) { + { + e.FieldStart("Name") + e.Str(s.Name) + } + { + e.FieldStart("Summary") + e.Str(s.Summary) + } + { + e.FieldStart("Passed") + e.Bool(s.Passed) + } + { + e.FieldStart("Details") + s.Details.Encode(e) + } +} + +var jsonFieldsNameOfCheck = [4]string{ + 0: "Name", + 1: "Summary", + 2: "Passed", + 3: "Details", +} + +// Decode decodes Check from json. +func (s *Check) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Check to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "Name": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Str() + s.Name = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Name\"") + } + case "Summary": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Str() + s.Summary = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Summary\"") + } + case "Passed": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Bool() + s.Passed = bool(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Passed\"") + } + case "Details": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + if err := s.Details.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Details\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Check") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00001111, + } { + 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(jsonFieldsNameOfCheck) { + name = jsonFieldsNameOfCheck[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 *Check) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Check) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *CheckDetails) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *CheckDetails) encodeFields(e *jx.Encoder) { +} + +var jsonFieldsNameOfCheckDetails = [0]string{} + +// Decode decodes CheckDetails from json. +func (s *CheckDetails) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode CheckDetails to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + default: + return d.Skip() + } + }); err != nil { + return errors.Wrap(err, "decode CheckDetails") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *CheckDetails) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *CheckDetails) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes CheckList as json. +func (s CheckList) Encode(e *jx.Encoder) { + unwrapped := []Check(s) + + e.ArrStart() + for _, elem := range unwrapped { + elem.Encode(e) + } + e.ArrEnd() +} + +// Decode decodes CheckList from json. +func (s *CheckList) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode CheckList to nil") + } + var unwrapped []Check + if err := func() error { + unwrapped = make([]Check, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem Check + if err := elem.Decode(d); err != nil { + return err + } + unwrapped = append(unwrapped, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "alias") + } + *s = CheckList(unwrapped) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s CheckList) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *CheckList) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *Error) Encode(e *jx.Encoder) { e.ObjStart() diff --git a/pkg/internal/oas_request_decoders_gen.go b/pkg/internal/oas_request_decoders_gen.go index 016e05d..f159295 100644 --- a/pkg/internal/oas_request_decoders_gen.go +++ b/pkg/internal/oas_request_decoders_gen.go @@ -14,6 +14,148 @@ import ( "github.com/ogen-go/ogen/validate" ) +func (s *Server) decodeActionMapfixRequestChangesRequest(r *http.Request) ( + req CheckList, + 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 = errors.Join(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = errors.Join(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 CheckList + 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) decodeActionSubmissionRequestChangesRequest(r *http.Request) ( + req CheckList, + 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 = errors.Join(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = errors.Join(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 CheckList + 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) decodeCreateMapfixRequest(r *http.Request) ( req *MapfixCreate, close func() error, diff --git a/pkg/internal/oas_request_encoders_gen.go b/pkg/internal/oas_request_encoders_gen.go index 1e38200..bcf0909 100644 --- a/pkg/internal/oas_request_encoders_gen.go +++ b/pkg/internal/oas_request_encoders_gen.go @@ -11,6 +11,34 @@ import ( ht "github.com/ogen-go/ogen/http" ) +func encodeActionMapfixRequestChangesRequest( + req CheckList, + 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 encodeActionSubmissionRequestChangesRequest( + req CheckList, + 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 encodeCreateMapfixRequest( req *MapfixCreate, r *http.Request, diff --git a/pkg/internal/oas_server_gen.go b/pkg/internal/oas_server_gen.go index b091f81..f2bcdfc 100644 --- a/pkg/internal/oas_server_gen.go +++ b/pkg/internal/oas_server_gen.go @@ -19,7 +19,7 @@ type Handler interface { // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /mapfixes/{MapfixID}/status/validator-request-changes - ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error + ActionMapfixRequestChanges(ctx context.Context, req CheckList, params ActionMapfixRequestChangesParams) error // ActionMapfixSubmitted implements actionMapfixSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -55,7 +55,7 @@ type Handler interface { // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /submissions/{SubmissionID}/status/validator-request-changes - ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error + ActionSubmissionRequestChanges(ctx context.Context, req CheckList, params ActionSubmissionRequestChangesParams) error // ActionSubmissionSubmitted implements actionSubmissionSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. diff --git a/pkg/internal/oas_unimplemented_gen.go b/pkg/internal/oas_unimplemented_gen.go index 666fc14..b94f66e 100644 --- a/pkg/internal/oas_unimplemented_gen.go +++ b/pkg/internal/oas_unimplemented_gen.go @@ -27,7 +27,7 @@ func (UnimplementedHandler) ActionMapfixAccepted(ctx context.Context, params Act // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /mapfixes/{MapfixID}/status/validator-request-changes -func (UnimplementedHandler) ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error { +func (UnimplementedHandler) ActionMapfixRequestChanges(ctx context.Context, req CheckList, params ActionMapfixRequestChangesParams) error { return ht.ErrNotImplemented } @@ -81,7 +81,7 @@ func (UnimplementedHandler) ActionSubmissionAccepted(ctx context.Context, params // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /submissions/{SubmissionID}/status/validator-request-changes -func (UnimplementedHandler) ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error { +func (UnimplementedHandler) ActionSubmissionRequestChanges(ctx context.Context, req CheckList, params ActionSubmissionRequestChangesParams) error { return ht.ErrNotImplemented } diff --git a/pkg/internal/oas_validators_gen.go b/pkg/internal/oas_validators_gen.go index 86e8c6a..2e5d084 100644 --- a/pkg/internal/oas_validators_gen.go +++ b/pkg/internal/oas_validators_gen.go @@ -3,11 +3,88 @@ package api import ( + "fmt" + "github.com/go-faster/errors" "github.com/ogen-go/ogen/validate" ) +func (s *Check) 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.Name)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Name", + Error: err, + }) + } + if err := func() error { + if err := (validate.String{ + MinLength: 0, + MinLengthSet: false, + MaxLength: 4096, + MaxLengthSet: true, + Email: false, + Hostname: false, + Regex: nil, + }).Validate(string(s.Summary)); err != nil { + return errors.Wrap(err, "string") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Summary", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s CheckList) Validate() error { + alias := ([]Check)(s) + if alias == nil { + return errors.New("nil is invalid value") + } + var failures []validate.FieldError + for i, elem := range alias { + if err := func() error { + if err := elem.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: fmt.Sprintf("[%d]", i), + Error: err, + }) + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + func (s *Error) Validate() error { if s == nil { return validate.ErrNilPointer -- 2.49.1 From 40ba62b15e9363ed579129459c0fd9b267d6a49e Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sat, 7 Jun 2025 23:22:05 -0700 Subject: [PATCH 04/17] submissions-api: update changes requested internal endpoint --- validation/api/src/internal.rs | 24 ++++++++++++++++++++++++ validation/api/src/types.rs | 31 +++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/validation/api/src/internal.rs b/validation/api/src/internal.rs index 28fe19c..02f1bed 100644 --- a/validation/api/src/internal.rs +++ b/validation/api/src/internal.rs @@ -161,6 +161,18 @@ impl Context{ ).await.map_err(Error::Response)? .json().await.map_err(Error::ReqwestJson) } + pub async fn create_submission_audit_check_list(&self,config:CreateSubmissionAuditCheckListRequest<'_>)->Result<(),Error>{ + let url_raw=format!("{}/submissions/{}/checklist",self.0.base_url,config.SubmissionID.0); + let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; + + let body=serde_json::to_string(&config.check_list).map_err(Error::JSON)?; + + response_ok( + self.0.post(url,body).await.map_err(Error::Reqwest)? + ).await.map_err(Error::Response)?; + + Ok(()) + } // simple submission endpoints action!("submissions",action_submission_request_changes,config,ActionSubmissionRequestChangesRequest,"status/validator-request-changes",config.SubmissionID.0,); action!("submissions",action_submission_submitted,config,ActionSubmissionSubmittedRequest,"status/validator-submitted",config.SubmissionID.0, @@ -192,6 +204,18 @@ impl Context{ ).await.map_err(Error::Response)? .json().await.map_err(Error::ReqwestJson) } + pub async fn create_mapfix_audit_check_list(&self,config:CreateMapfixCheckListRequest<'_>)->Result<(),Error>{ + let url_raw=format!("{}/mapfixes/{}/checklist",self.0.base_url,config.MapfixID.0); + let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; + + let body=serde_json::to_string(&config.check_list).map_err(Error::JSON)?; + + response_ok( + self.0.post(url,body).await.map_err(Error::Reqwest)? + ).await.map_err(Error::Response)?; + + Ok(()) + } // simple mapfixes endpoints action!("mapfixes",action_mapfix_request_changes,config,ActionMapfixRequestChangesRequest,"status/validator-request-changes",config.MapfixID.0,); action!("mapfixes",action_mapfix_submitted,config,ActionMapfixSubmittedRequest,"status/validator-submitted",config.MapfixID.0, diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs index 0ccb5d1..ddf729f 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -334,6 +334,15 @@ pub struct MapResponse{ pub Date:i64, } +#[allow(nonstandard_style)] +#[derive(Clone,Debug,serde::Serialize)] +pub struct Check{ + pub Name:&'static str, + pub Summary:String, + pub Passed:bool, + pub Details:serde_json::Value, +} + #[allow(nonstandard_style)] #[derive(Clone,Debug)] pub struct ActionSubmissionSubmittedRequest{ @@ -345,8 +354,8 @@ pub struct ActionSubmissionSubmittedRequest{ } #[allow(nonstandard_style)] -#[derive(Clone,Debug)] -pub struct ActionSubmissionRequestChangesRequest{ +#[derive(Clone,Debug,serde::Serialize)] +pub struct ActionSubmissionRequestChangesRequest<'a>{ pub SubmissionID:SubmissionID, } @@ -366,6 +375,13 @@ pub struct ActionSubmissionAcceptedRequest{ #[allow(nonstandard_style)] #[derive(Clone,Debug)] pub struct CreateSubmissionAuditErrorRequest{ + pub SubmissionID:SubmissionID, + pub CheckList:&'a [Check], +} + +#[allow(nonstandard_style)] +#[derive(Clone,Debug)] +pub struct CreateSubmissionAuditCheckListRequest{ pub SubmissionID:SubmissionID, pub ErrorMessage:String, } @@ -392,8 +408,8 @@ pub struct ActionMapfixSubmittedRequest{ } #[allow(nonstandard_style)] -#[derive(Clone,Debug)] -pub struct ActionMapfixRequestChangesRequest{ +#[derive(Clone,Debug,serde::Serialize)] +pub struct ActionMapfixRequestChangesRequest<'a>{ pub MapfixID:MapfixID, } @@ -416,6 +432,13 @@ pub struct CreateMapfixAuditErrorRequest{ pub ErrorMessage:String, } +#[allow(nonstandard_style)] +#[derive(Clone,Debug)] +pub struct CreateMapfixAuditCheckListRequest{ + pub MapfixID:MapfixID, + pub CheckList:&'a [Check], +} + #[derive(Clone,Copy,Debug,serde::Serialize,serde::Deserialize)] pub struct MapfixID(pub(crate)i64); -- 2.49.1 From 50d136de5d88012b4b5351892afa02e3426b0176 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sat, 7 Jun 2025 23:24:54 -0700 Subject: [PATCH 05/17] validation: plumb out CheckList --- validation/src/check.rs | 104 ++++++++++++----------------- validation/src/check_mapfix.rs | 6 +- validation/src/check_submission.rs | 6 +- 3 files changed, 48 insertions(+), 68 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 8c39abe..83f1193 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -3,6 +3,7 @@ use crate::download::download_asset_version; use crate::rbx_util::{get_mapinfo,get_root_instance,read_dom,ReadDomError,GameID,ParseGameIDError,MapInfo,GetRootInstanceError,StringValueError}; use heck::{ToSnakeCase,ToTitleCase}; +use submissions_api::types::Check; #[allow(dead_code)] #[derive(Debug)] @@ -591,40 +592,34 @@ impl std::fmt::Display for Duplicates{ } } -#[derive(serde::Serialize)] -struct CheckSummary{ - name:&'static str, - summary:String, - passed:bool, - details:serde_json::Value, -} -impl CheckSummary{ - const fn passed(name:&'static str)->Self{ - Self{ - name, - summary:String::new(), - passed:true, - details:serde_json::Value::Null, + +macro_rules! passed{ + ($name:literal)=>{ + Check{ + Name:$name, + Summary:String::new(), + Passed:true, + Details:serde_json::Value::Null, } } } macro_rules! summary{ ($name:literal,$summary:expr,$details:expr)=>{ - CheckSummary{ - name:$name, - summary:$summary, - passed:false, - details:serde_json::to_value($details)?, + Check{ + Name:$name, + Summary:$summary, + Passed:false, + Details:serde_json::to_value($details)?, } }; } macro_rules! summary_format{ ($name:literal,$fmt:literal,$details:expr)=>{ - CheckSummary{ - name:$name, - summary:format!($fmt), - passed:false, - details:serde_json::to_value($details)?, + Check{ + Name:$name, + Summary:format!($fmt), + Passed:false, + Details:serde_json::to_value($details)?, } }; } @@ -634,15 +629,15 @@ macro_rules! summary_format{ impl MapCheck<'_>{ fn itemize(&self)->Result{ let model_class=match &self.model_class{ - StringCheck(Ok(()))=>CheckSummary::passed("ModelClass"), + StringCheck(Ok(()))=>passed!("ModelClass"), StringCheck(Err(context))=>summary_format!("ModelClass","Invalid model class: {context}",()), }; let model_name=match &self.model_name{ - StringCheck(Ok(()))=>CheckSummary::passed("ModelName"), + StringCheck(Ok(()))=>passed!("ModelName"), StringCheck(Err(context))=>summary_format!("ModelName","Model name must have snake_case: {context}",()), }; let display_name=match &self.display_name{ - Ok(Ok(StringCheck(Ok(_))))=>CheckSummary::passed("DisplayName"), + Ok(Ok(StringCheck(Ok(_))))=>passed!("DisplayName"), Ok(Ok(StringCheck(Err(context))))=>summary_format!("DisplayName","DisplayName must have Title Case: {context}",()), Ok(Err(context))=>summary_format!("DisplayName","Invalid DisplayName: {context}",()), Err(StringValueError::ObjectNotFound)=>summary!("DisplayName","Missing DisplayName StringValue".to_owned(),()), @@ -650,22 +645,22 @@ impl MapCheck<'_>{ Err(StringValueError::NonStringValue)=>summary!("DisplayName","DisplayName Value is not a String".to_owned(),()), }; let creator=match &self.creator{ - Ok(Ok(_))=>CheckSummary::passed("Creator"), + Ok(Ok(_))=>passed!("Creator"), Ok(Err(context))=>summary_format!("Creator","Invalid Creator: {context}",()), Err(StringValueError::ObjectNotFound)=>summary!("Creator","Missing Creator StringValue".to_owned(),()), Err(StringValueError::ValueNotSet)=>summary!("Creator","Creator Value not set".to_owned(),()), Err(StringValueError::NonStringValue)=>summary!("Creator","Creator Value is not a String".to_owned(),()), }; let game_id=match &self.game_id{ - Ok(_)=>CheckSummary::passed("GameID"), + Ok(_)=>passed!("GameID"), Err(ParseGameIDError)=>summary!("GameID","Model name must be prefixed with bhop_ surf_ or flytrials_".to_owned(),()), }; let mapstart=match &self.mapstart{ - Ok(Exists)=>CheckSummary::passed("MapStart"), + Ok(Exists)=>passed!("MapStart"), Err(Absent)=>summary_format!("MapStart","Model has no MapStart",()), }; let duplicate_start=match &self.mode_start_counts{ - DuplicateCheck(Ok(()))=>CheckSummary::passed("DuplicateStart"), + DuplicateCheck(Ok(()))=>passed!("DuplicateStart"), DuplicateCheck(Err(DuplicateCheckContext(context)))=>{ let context=Separated::new(", ",||context.iter().map(|(&mode_id,names)| Duplicates::new(ModeElement{zone:Zone::Start,mode_id},names.len()) @@ -674,10 +669,10 @@ impl MapCheck<'_>{ } }; let (extra_finish,missing_finish)=match &self.mode_finish_counts{ - SetDifferenceCheck(Ok(()))=>(CheckSummary::passed("ExtraFinish"),CheckSummary::passed("MissingFinish")), + SetDifferenceCheck(Ok(()))=>(passed!("ExtraFinish"),passed!("MissingFinish")), SetDifferenceCheck(Err(context))=>( if context.extra.is_empty(){ - CheckSummary::passed("ExtraFinish") + passed!("ExtraFinish") }else{ let plural=if context.extra.len()==1{"zone"}else{"zones"}; let context=Separated::new(", ",||context.extra.iter().map(|(&mode_id,_names)| @@ -686,7 +681,7 @@ impl MapCheck<'_>{ summary_format!("ExtraFinish","No matching start zone for finish {plural}: {context}",()) }, if context.missing.is_empty(){ - CheckSummary::passed("MissingFinish") + passed!("MissingFinish") }else{ let plural=if context.missing.len()==1{"zone"}else{"zones"}; let context=Separated::new(", ",||context.missing.iter().map(|&mode_id| @@ -697,10 +692,10 @@ impl MapCheck<'_>{ ), }; let dangling_anticheat=match &self.mode_anticheat_counts{ - SetDifferenceCheck(Ok(()))=>CheckSummary::passed("DanglingAnticheat"), + SetDifferenceCheck(Ok(()))=>passed!("DanglingAnticheat"), SetDifferenceCheck(Err(context))=>{ if context.extra.is_empty(){ - CheckSummary::passed("DanglingAnticheat") + passed!("DanglingAnticheat") }else{ let plural=if context.extra.len()==1{"zone"}else{"zones"}; let context=Separated::new(", ",||context.extra.iter().map(|(&mode_id,_names)| @@ -711,11 +706,11 @@ impl MapCheck<'_>{ } }; let spawn1=match &self.spawn1{ - Ok(Exists)=>CheckSummary::passed("Spawn1"), + Ok(Exists)=>passed!("Spawn1"), Err(Absent)=>summary_format!("Spawn1","Model has no Spawn1",()), }; let dangling_teleport=match &self.teleport_counts{ - SetDifferenceCheck(Ok(()))=>CheckSummary::passed("DanglingTeleport"), + SetDifferenceCheck(Ok(()))=>passed!("DanglingTeleport"), SetDifferenceCheck(Err(context))=>{ let unique_names:HashSet<_>=context.extra.values().flat_map(|names|names.iter().copied()).collect(); let plural=if unique_names.len()==1{"object"}else{"objects"}; @@ -724,7 +719,7 @@ impl MapCheck<'_>{ } }; let duplicate_spawns=match &self.spawn_counts{ - DuplicateCheck(Ok(()))=>CheckSummary::passed("DuplicateSpawn"), + DuplicateCheck(Ok(()))=>passed!("DuplicateSpawn"), DuplicateCheck(Err(DuplicateCheckContext(context)))=>{ let context=Separated::new(", ",||context.iter().map(|(&stage_id,&names)| Duplicates::new(StageElement{behaviour:StageElementBehaviour::Spawn,stage_id},names as usize) @@ -733,10 +728,10 @@ impl MapCheck<'_>{ } }; let (extra_wormhole_in,missing_wormhole_in)=match &self.wormhole_in_counts{ - SetDifferenceCheck(Ok(()))=>(CheckSummary::passed("ExtraWormholeIn"),CheckSummary::passed("MissingWormholeIn")), + SetDifferenceCheck(Ok(()))=>(passed!("ExtraWormholeIn"),passed!("MissingWormholeIn")), SetDifferenceCheck(Err(context))=>( if context.extra.is_empty(){ - CheckSummary::passed("ExtraWormholeIn") + passed!("ExtraWormholeIn") }else{ let context=Separated::new(", ",||context.extra.iter().map(|(&wormhole_id,_names)| WormholeElement{behaviour:WormholeBehaviour::In,wormhole_id} @@ -744,7 +739,7 @@ impl MapCheck<'_>{ summary_format!("ExtraWormholeIn","WormholeIn with no matching WormholeOut: {context}",()) }, if context.missing.is_empty(){ - CheckSummary::passed("MissingWormholeIn") + passed!("MissingWormholeIn") }else{ // This counts WormholeIn objects, but // flipped logic is easier to understand @@ -756,7 +751,7 @@ impl MapCheck<'_>{ ) }; let duplicate_wormhole_out=match &self.wormhole_out_counts{ - DuplicateCheck(Ok(()))=>CheckSummary::passed("DuplicateWormholeOut"), + DuplicateCheck(Ok(()))=>passed!("DuplicateWormholeOut"), DuplicateCheck(Err(DuplicateCheckContext(context)))=>{ let context=Separated::new(", ",||context.iter().map(|(&wormhole_id,&names)| Duplicates::new(WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id},names as usize) @@ -786,24 +781,12 @@ impl MapCheck<'_>{ } #[derive(serde::Serialize)] -struct MapCheckList{ - checks:Box<[CheckSummary;16]>, -} -impl MapCheckList{ - fn summary(&self)->String{ - Separated::new("; ",||self.checks.iter().filter_map(|check| - (!check.passed).then_some(check.summary.as_str()) - )).to_string() - } -} - -pub struct Summary{ - pub summary:String, - pub json:serde_json::Value, +pub struct MapCheckList{ + pub checks:Box<[Check;16]>, } pub struct CheckReportAndVersion{ - pub status:Result, + pub status:Result, pub version:u64, } @@ -842,10 +825,7 @@ impl crate::message_handler::MessageHandler{ // check the report, generate an error message if it fails the check let status=match map_check.result(){ Ok(map_info)=>Ok(map_info), - Err(Ok(summary))=>Err(Summary{ - summary:summary.summary(), - json:serde_json::to_value(&summary).map_err(Error::ToJsonValue)?, - }), + Err(Ok(check_list))=>Err(check_list), Err(Err(e))=>return Err(Error::ToJsonValue(e)), }; diff --git a/validation/src/check_mapfix.rs b/validation/src/check_mapfix.rs index e66b323..a473fbf 100644 --- a/validation/src/check_mapfix.rs +++ b/validation/src/check_mapfix.rs @@ -36,10 +36,10 @@ impl crate::message_handler::MessageHandler{ return Ok(()); }, // update the mapfix model status to request changes - Ok(CheckReportAndVersion{status:Err(report),..})=>self.api.create_mapfix_audit_error( - submissions_api::types::CreateMapfixAuditErrorRequest{ + Ok(CheckReportAndVersion{status:Err(check_list),..})=>self.api.create_mapfix_audit_check_list( + submissions_api::types::CreateMapfixAuditCheckListRequest{ MapfixID:mapfix_id, - ErrorMessage:report.summary, + CheckList:check_list.checks.as_slice(), } ).await.map_err(Error::ApiActionMapfixCheck)?, // update the mapfix model status to request changes diff --git a/validation/src/check_submission.rs b/validation/src/check_submission.rs index 596f8e0..4e6d2b7 100644 --- a/validation/src/check_submission.rs +++ b/validation/src/check_submission.rs @@ -37,10 +37,10 @@ impl crate::message_handler::MessageHandler{ return Ok(()); }, // update the submission model status to request changes - Ok(CheckReportAndVersion{status:Err(report),..})=>self.api.create_submission_audit_error( - submissions_api::types::CreateSubmissionAuditErrorRequest{ + Ok(CheckReportAndVersion{status:Err(check_list),..})=>self.api.create_submission_audit_check_list( + submissions_api::types::CreateSubmissionAuditCheckListRequest{ SubmissionID:submission_id, - ErrorMessage:report.summary, + CheckList:check_list.checks.as_slice(), } ).await.map_err(Error::ApiActionSubmissionCheck)?, // update the submission model status to request changes -- 2.49.1 From 81cd8f758822e9f9406063acf01aeb9a7ea6fb7c Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sat, 7 Jun 2025 23:30:47 -0700 Subject: [PATCH 06/17] validation: hijack check system to report internal error This should really use the dedicated error audit event somehow --- validation/src/check_mapfix.rs | 7 ++++++- validation/src/check_submission.rs | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/validation/src/check_mapfix.rs b/validation/src/check_mapfix.rs index a473fbf..074fa09 100644 --- a/validation/src/check_mapfix.rs +++ b/validation/src/check_mapfix.rs @@ -50,7 +50,12 @@ impl crate::message_handler::MessageHandler{ self.api.create_mapfix_audit_error( submissions_api::types::CreateMapfixAuditErrorRequest{ MapfixID:mapfix_id, - ErrorMessage:e.to_string(), + CheckList:&[submissions_api::types::Check{ + Name:"InternalError", + Summary:e.to_string(), + Passed:false, + Details:serde_json::Value::Null, + }], } ).await.map_err(Error::ApiActionMapfixCheck)?; }, diff --git a/validation/src/check_submission.rs b/validation/src/check_submission.rs index 4e6d2b7..0e4e753 100644 --- a/validation/src/check_submission.rs +++ b/validation/src/check_submission.rs @@ -51,7 +51,12 @@ impl crate::message_handler::MessageHandler{ self.api.create_submission_audit_error( submissions_api::types::CreateSubmissionAuditErrorRequest{ SubmissionID:submission_id, - ErrorMessage:e.to_string(), + CheckList:&[submissions_api::types::Check{ + Name:"InternalError", + Summary:e.to_string(), + Passed:false, + Details:serde_json::Value::Null, + }], } ).await.map_err(Error::ApiActionSubmissionCheck)?; }, -- 2.49.1 From 4ce50dce9ab62659aa59e782ed7505a4661365ae Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sat, 7 Jun 2025 23:48:27 -0700 Subject: [PATCH 07/17] submissions: accept CheckList in internal changes requested --- pkg/service_internal/mapfixes.go | 33 ++++++++++++++++++++++++++++- pkg/service_internal/submissions.go | 33 ++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index 7c44a89..5975441 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -103,7 +103,7 @@ func (svc *Service) ActionMapfixSubmitted(ctx context.Context, params internal.A // (Internal endpoint) Role Validator changes status from Submitting -> RequestChanges. // // POST /mapfixes/{MapfixID}/status/validator-request-changes -func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params internal.ActionMapfixRequestChangesParams) error { +func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, check_list internal.CheckList, params internal.ActionMapfixRequestChangesParams) error { // transaction target_status := model.MapfixStatusChangesRequested smap := datastore.Optional() @@ -222,6 +222,37 @@ func (svc *Service) CreateMapfixAuditError(ctx context.Context, params internal. ) } +// CreateMapfixAuditCheckList implements createMapfixAuditCheckList operation. +// +// Post a checklist to the audit log +// +// POST /mapfixes/{MapfixID}/checklist +func (svc *Service) CreateMapfixAuditCheckList(ctx context.Context, check_list internal.CheckList, params internal.CreateMapfixAuditCheckListParams) (error) { + check_list2 := make([]model.Check, len(check_list)) + for i, check := range check_list { + check_list2[i] = model.Check{ + Name: check.Name, + Summary: check.Summary, + Passed: check.Passed, + Details: check.Details, + } + } + + event_data := model.AuditEventDataCheckList{ + CheckList: check_list2, + } + + return svc.CreateAuditEventCheckList( + ctx, + ValidtorUserID, + model.Resource{ + ID: params.MapfixID, + Type: model.ResourceMapfix, + }, + event_data, + ) +} + // POST /mapfixes func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCreate) (*internal.MapfixID, error) { // sanitization diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 910b8f8..34bc8bf 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -103,7 +103,7 @@ func (svc *Service) ActionSubmissionSubmitted(ctx context.Context, params intern // (Internal endpoint) Role Validator changes status from Submitting -> RequestChanges. // // POST /submissions/{SubmissionID}/status/validator-request-changes -func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params internal.ActionSubmissionRequestChangesParams) error { +func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, check_list internal.CheckList, params internal.ActionSubmissionRequestChangesParams) error { // transaction target_status := model.SubmissionStatusChangesRequested smap := datastore.Optional() @@ -242,6 +242,37 @@ func (svc *Service) CreateSubmissionAuditError(ctx context.Context, params inter ) } +// CreateSubmissionAuditCheckList implements createSubmissionAuditCheckList operation. +// +// Post a checklist to the audit log +// +// POST /submissions/{SubmissionID}/checklist +func (svc *Service) CreateSubmissionAuditCheckList(ctx context.Context, check_list internal.CheckList, params internal.CreateSubmissionAuditCheckListParams) (error) { + check_list2 := make([]model.Check, len(check_list)) + for i, check := range check_list { + check_list2[i] = model.Check{ + Name: check.Name, + Summary: check.Summary, + Passed: check.Passed, + Details: check.Details, + } + } + + event_data := model.AuditEventDataCheckList{ + CheckList: check_list2, + } + + return svc.CreateAuditEventCheckList( + ctx, + ValidtorUserID, + model.Resource{ + ID: params.SubmissionID, + Type: model.ResourceSubmission, + }, + event_data, + ) +} + // POST /submissions func (svc *Service) CreateSubmission(ctx context.Context, request *internal.SubmissionCreate) (*internal.SubmissionID, error) { // sanitization -- 2.49.1 From cccdeb29d00d0f15925aefca29bee6350d864785 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 8 Jun 2025 18:13:30 -0700 Subject: [PATCH 08/17] remove details --- openapi-internal.yaml | 3 -- pkg/model/audit_event.go | 1 - pkg/service_internal/mapfixes.go | 1 - pkg/service_internal/submissions.go | 1 - validation/api/src/types.rs | 1 - validation/src/check.rs | 53 ++++++++++++++--------------- validation/src/check_mapfix.rs | 1 - validation/src/check_submission.rs | 1 - 8 files changed, 25 insertions(+), 37 deletions(-) diff --git a/openapi-internal.yaml b/openapi-internal.yaml index 7598b62..a6dcaa0 100644 --- a/openapi-internal.yaml +++ b/openapi-internal.yaml @@ -918,7 +918,6 @@ components: - Name - Summary - Passed - - Details type: object properties: Name: @@ -929,8 +928,6 @@ components: maxLength: 4096 Passed: type: boolean - Details: - type: object CheckList: type: array items: diff --git a/pkg/model/audit_event.go b/pkg/model/audit_event.go index db12c6b..cd60447 100644 --- a/pkg/model/audit_event.go +++ b/pkg/model/audit_event.go @@ -52,7 +52,6 @@ type Check struct { Name string `json:"name"` Summary string `json:"summary"` Passed bool `json:"passed"` - Details any `json:"details"` } // Validator map checks details diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index 5975441..a7fb2f4 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -234,7 +234,6 @@ func (svc *Service) CreateMapfixAuditCheckList(ctx context.Context, check_list i Name: check.Name, Summary: check.Summary, Passed: check.Passed, - Details: check.Details, } } diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 34bc8bf..1f377bc 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -254,7 +254,6 @@ func (svc *Service) CreateSubmissionAuditCheckList(ctx context.Context, check_li Name: check.Name, Summary: check.Summary, Passed: check.Passed, - Details: check.Details, } } diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs index ddf729f..13070ef 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -340,7 +340,6 @@ pub struct Check{ pub Name:&'static str, pub Summary:String, pub Passed:bool, - pub Details:serde_json::Value, } #[allow(nonstandard_style)] diff --git a/validation/src/check.rs b/validation/src/check.rs index 83f1193..72bf8dd 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -599,27 +599,24 @@ macro_rules! passed{ Name:$name, Summary:String::new(), Passed:true, - Details:serde_json::Value::Null, } } } macro_rules! summary{ - ($name:literal,$summary:expr,$details:expr)=>{ + ($name:literal,$summary:expr)=>{ Check{ Name:$name, Summary:$summary, Passed:false, - Details:serde_json::to_value($details)?, } }; } macro_rules! summary_format{ - ($name:literal,$fmt:literal,$details:expr)=>{ + ($name:literal,$fmt:literal)=>{ Check{ Name:$name, Summary:format!($fmt), Passed:false, - Details:serde_json::to_value($details)?, } }; } @@ -630,34 +627,34 @@ impl MapCheck<'_>{ fn itemize(&self)->Result{ let model_class=match &self.model_class{ StringCheck(Ok(()))=>passed!("ModelClass"), - StringCheck(Err(context))=>summary_format!("ModelClass","Invalid model class: {context}",()), + StringCheck(Err(context))=>summary_format!("ModelClass","Invalid model class: {context}"), }; let model_name=match &self.model_name{ StringCheck(Ok(()))=>passed!("ModelName"), - StringCheck(Err(context))=>summary_format!("ModelName","Model name must have snake_case: {context}",()), + StringCheck(Err(context))=>summary_format!("ModelName","Model name must have snake_case: {context}"), }; let display_name=match &self.display_name{ Ok(Ok(StringCheck(Ok(_))))=>passed!("DisplayName"), - Ok(Ok(StringCheck(Err(context))))=>summary_format!("DisplayName","DisplayName must have Title Case: {context}",()), - Ok(Err(context))=>summary_format!("DisplayName","Invalid DisplayName: {context}",()), - Err(StringValueError::ObjectNotFound)=>summary!("DisplayName","Missing DisplayName StringValue".to_owned(),()), - Err(StringValueError::ValueNotSet)=>summary!("DisplayName","DisplayName Value not set".to_owned(),()), - Err(StringValueError::NonStringValue)=>summary!("DisplayName","DisplayName Value is not a String".to_owned(),()), + Ok(Ok(StringCheck(Err(context))))=>summary_format!("DisplayName","DisplayName must have Title Case: {context}"), + Ok(Err(context))=>summary_format!("DisplayName","Invalid DisplayName: {context}"), + Err(StringValueError::ObjectNotFound)=>summary!("DisplayName","Missing DisplayName StringValue".to_owned()), + Err(StringValueError::ValueNotSet)=>summary!("DisplayName","DisplayName Value not set".to_owned()), + Err(StringValueError::NonStringValue)=>summary!("DisplayName","DisplayName Value is not a String".to_owned()), }; let creator=match &self.creator{ Ok(Ok(_))=>passed!("Creator"), - Ok(Err(context))=>summary_format!("Creator","Invalid Creator: {context}",()), - Err(StringValueError::ObjectNotFound)=>summary!("Creator","Missing Creator StringValue".to_owned(),()), - Err(StringValueError::ValueNotSet)=>summary!("Creator","Creator Value not set".to_owned(),()), - Err(StringValueError::NonStringValue)=>summary!("Creator","Creator Value is not a String".to_owned(),()), + Ok(Err(context))=>summary_format!("Creator","Invalid Creator: {context}"), + Err(StringValueError::ObjectNotFound)=>summary!("Creator","Missing Creator StringValue".to_owned()), + Err(StringValueError::ValueNotSet)=>summary!("Creator","Creator Value not set".to_owned()), + Err(StringValueError::NonStringValue)=>summary!("Creator","Creator Value is not a String".to_owned()), }; let game_id=match &self.game_id{ Ok(_)=>passed!("GameID"), - Err(ParseGameIDError)=>summary!("GameID","Model name must be prefixed with bhop_ surf_ or flytrials_".to_owned(),()), + Err(ParseGameIDError)=>summary!("GameID","Model name must be prefixed with bhop_ surf_ or flytrials_".to_owned()), }; let mapstart=match &self.mapstart{ Ok(Exists)=>passed!("MapStart"), - Err(Absent)=>summary_format!("MapStart","Model has no MapStart",()), + Err(Absent)=>summary_format!("MapStart","Model has no MapStart"), }; let duplicate_start=match &self.mode_start_counts{ DuplicateCheck(Ok(()))=>passed!("DuplicateStart"), @@ -665,7 +662,7 @@ impl MapCheck<'_>{ let context=Separated::new(", ",||context.iter().map(|(&mode_id,names)| Duplicates::new(ModeElement{zone:Zone::Start,mode_id},names.len()) )); - summary_format!("DuplicateStart","Duplicate start zones: {context}",()) + summary_format!("DuplicateStart","Duplicate start zones: {context}") } }; let (extra_finish,missing_finish)=match &self.mode_finish_counts{ @@ -678,7 +675,7 @@ impl MapCheck<'_>{ let context=Separated::new(", ",||context.extra.iter().map(|(&mode_id,_names)| ModeElement{zone:Zone::Finish,mode_id} )); - summary_format!("ExtraFinish","No matching start zone for finish {plural}: {context}",()) + summary_format!("ExtraFinish","No matching start zone for finish {plural}: {context}") }, if context.missing.is_empty(){ passed!("MissingFinish") @@ -687,7 +684,7 @@ impl MapCheck<'_>{ let context=Separated::new(", ",||context.missing.iter().map(|&mode_id| ModeElement{zone:Zone::Finish,mode_id} )); - summary_format!("MissingFinish","Missing finish {plural}: {context}",()) + summary_format!("MissingFinish","Missing finish {plural}: {context}") } ), }; @@ -701,13 +698,13 @@ impl MapCheck<'_>{ let context=Separated::new(", ",||context.extra.iter().map(|(&mode_id,_names)| ModeElement{zone:Zone::Anticheat,mode_id} )); - summary_format!("DanglingAnticheat","No matching start zone for anticheat {plural}: {context}",()) + summary_format!("DanglingAnticheat","No matching start zone for anticheat {plural}: {context}") } } }; let spawn1=match &self.spawn1{ Ok(Exists)=>passed!("Spawn1"), - Err(Absent)=>summary_format!("Spawn1","Model has no Spawn1",()), + Err(Absent)=>summary_format!("Spawn1","Model has no Spawn1"), }; let dangling_teleport=match &self.teleport_counts{ SetDifferenceCheck(Ok(()))=>passed!("DanglingTeleport"), @@ -715,7 +712,7 @@ impl MapCheck<'_>{ let unique_names:HashSet<_>=context.extra.values().flat_map(|names|names.iter().copied()).collect(); let plural=if unique_names.len()==1{"object"}else{"objects"}; let context=Separated::new(", ",||&unique_names); - summary_format!("DanglingTeleport","No matching Spawn for {plural}: {context}",()) + summary_format!("DanglingTeleport","No matching Spawn for {plural}: {context}") } }; let duplicate_spawns=match &self.spawn_counts{ @@ -724,7 +721,7 @@ impl MapCheck<'_>{ let context=Separated::new(", ",||context.iter().map(|(&stage_id,&names)| Duplicates::new(StageElement{behaviour:StageElementBehaviour::Spawn,stage_id},names as usize) )); - summary_format!("DuplicateSpawn","Duplicate Spawn: {context}",()) + summary_format!("DuplicateSpawn","Duplicate Spawn: {context}") } }; let (extra_wormhole_in,missing_wormhole_in)=match &self.wormhole_in_counts{ @@ -736,7 +733,7 @@ impl MapCheck<'_>{ let context=Separated::new(", ",||context.extra.iter().map(|(&wormhole_id,_names)| WormholeElement{behaviour:WormholeBehaviour::In,wormhole_id} )); - summary_format!("ExtraWormholeIn","WormholeIn with no matching WormholeOut: {context}",()) + summary_format!("ExtraWormholeIn","WormholeIn with no matching WormholeOut: {context}") }, if context.missing.is_empty(){ passed!("MissingWormholeIn") @@ -746,7 +743,7 @@ impl MapCheck<'_>{ let context=Separated::new(", ",||context.missing.iter().map(|&wormhole_id| WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id} )); - summary_format!("MissingWormholeIn","WormholeOut with no matching WormholeIn: {context}",()) + summary_format!("MissingWormholeIn","WormholeOut with no matching WormholeIn: {context}") } ) }; @@ -756,7 +753,7 @@ impl MapCheck<'_>{ let context=Separated::new(", ",||context.iter().map(|(&wormhole_id,&names)| Duplicates::new(WormholeElement{behaviour:WormholeBehaviour::Out,wormhole_id},names as usize) )); - summary_format!("DuplicateWormholeOut","Duplicate WormholeOut: {context}",()) + summary_format!("DuplicateWormholeOut","Duplicate WormholeOut: {context}") } }; Ok(MapCheckList{checks:Box::new([ diff --git a/validation/src/check_mapfix.rs b/validation/src/check_mapfix.rs index 074fa09..e6499ed 100644 --- a/validation/src/check_mapfix.rs +++ b/validation/src/check_mapfix.rs @@ -54,7 +54,6 @@ impl crate::message_handler::MessageHandler{ Name:"InternalError", Summary:e.to_string(), Passed:false, - Details:serde_json::Value::Null, }], } ).await.map_err(Error::ApiActionMapfixCheck)?; diff --git a/validation/src/check_submission.rs b/validation/src/check_submission.rs index 0e4e753..7155bcf 100644 --- a/validation/src/check_submission.rs +++ b/validation/src/check_submission.rs @@ -55,7 +55,6 @@ impl crate::message_handler::MessageHandler{ Name:"InternalError", Summary:e.to_string(), Passed:false, - Details:serde_json::Value::Null, }], } ).await.map_err(Error::ApiActionSubmissionCheck)?; -- 2.49.1 From 19900b1a778f7220b1c2a87804cdab7f33752114 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Sun, 8 Jun 2025 18:13:44 -0700 Subject: [PATCH 09/17] openapi: generate --- pkg/internal/oas_json_gen.go | 63 ++---------------------------------- 1 file changed, 2 insertions(+), 61 deletions(-) diff --git a/pkg/internal/oas_json_gen.go b/pkg/internal/oas_json_gen.go index 1f4f1b8..4f95e1c 100644 --- a/pkg/internal/oas_json_gen.go +++ b/pkg/internal/oas_json_gen.go @@ -33,17 +33,12 @@ func (s *Check) encodeFields(e *jx.Encoder) { e.FieldStart("Passed") e.Bool(s.Passed) } - { - e.FieldStart("Details") - s.Details.Encode(e) - } } -var jsonFieldsNameOfCheck = [4]string{ +var jsonFieldsNameOfCheck = [3]string{ 0: "Name", 1: "Summary", 2: "Passed", - 3: "Details", } // Decode decodes Check from json. @@ -91,16 +86,6 @@ func (s *Check) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"Passed\"") } - case "Details": - requiredBitSet[0] |= 1 << 3 - if err := func() error { - if err := s.Details.Decode(d); err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"Details\"") - } default: return d.Skip() } @@ -111,7 +96,7 @@ func (s *Check) Decode(d *jx.Decoder) error { // Validate required fields. var failures []validate.FieldError for i, mask := range [1]uint8{ - 0b00001111, + 0b00000111, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. @@ -157,50 +142,6 @@ func (s *Check) UnmarshalJSON(data []byte) error { return s.Decode(d) } -// Encode implements json.Marshaler. -func (s *CheckDetails) Encode(e *jx.Encoder) { - e.ObjStart() - s.encodeFields(e) - e.ObjEnd() -} - -// encodeFields encodes fields. -func (s *CheckDetails) encodeFields(e *jx.Encoder) { -} - -var jsonFieldsNameOfCheckDetails = [0]string{} - -// Decode decodes CheckDetails from json. -func (s *CheckDetails) Decode(d *jx.Decoder) error { - if s == nil { - return errors.New("invalid: unable to decode CheckDetails to nil") - } - - if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { - switch string(k) { - default: - return d.Skip() - } - }); err != nil { - return errors.Wrap(err, "decode CheckDetails") - } - - return nil -} - -// MarshalJSON implements stdjson.Marshaler. -func (s *CheckDetails) MarshalJSON() ([]byte, error) { - e := jx.Encoder{} - s.Encode(&e) - return e.Bytes(), nil -} - -// UnmarshalJSON implements stdjson.Unmarshaler. -func (s *CheckDetails) UnmarshalJSON(data []byte) error { - d := jx.DecodeBytes(data) - return s.Decode(d) -} - // Encode encodes CheckList as json. func (s CheckList) Encode(e *jx.Encoder) { unwrapped := []Check(s) -- 2.49.1 From ccd43ede81e0caa26d59c3295ef80bb00a62a2fb Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 11 Jun 2025 18:14:38 -0700 Subject: [PATCH 10/17] web: display CheckList audit events --- web/src/app/ts/AuditEvent.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/web/src/app/ts/AuditEvent.ts b/web/src/app/ts/AuditEvent.ts index d7b506f..cb99149 100644 --- a/web/src/app/ts/AuditEvent.ts +++ b/web/src/app/ts/AuditEvent.ts @@ -9,6 +9,7 @@ export const enum AuditEventType { ChangeDisplayName = 4, ChangeCreator = 5, Error = 6, + CheckList = 7, } // Discriminated union types for each event @@ -19,7 +20,8 @@ export type AuditEventData = | { EventType: AuditEventType.ChangeValidatedModel; EventData: AuditEventDataChangeValidatedModel; } | { EventType: AuditEventType.ChangeDisplayName; EventData: AuditEventDataChangeName; } | { EventType: AuditEventType.ChangeCreator; EventData: AuditEventDataChangeName; } - | { EventType: AuditEventType.Error; EventData: AuditEventDataError }; + | { EventType: AuditEventType.Error; EventData: AuditEventDataError; } + | { EventType: AuditEventType.CheckList; EventData: AuditEventDataCheckList; }; // Concrete data interfaces export interface AuditEventDataAction { @@ -51,6 +53,15 @@ export interface AuditEventDataError { error: string; } +export interface AuditEventDataCheck { + name: string + summary: string + passed: boolean +} +export interface AuditEventDataCheckList { + check_list: [AuditEventDataCheck]; +} + // Full audit event type (mirroring the Go struct) export interface AuditEvent { Id: number; @@ -87,6 +98,13 @@ export function decodeAuditEvent(event: AuditEvent): string { }case AuditEventType.Error:{ const data = event.EventData as AuditEventDataError; return `Error: ${data.error}`; + }case AuditEventType.CheckList:{ + const data = event.EventData as AuditEventDataCheckList; + const failedSummaries = data.check_list + .filter(check => !check.passed) + .map(check => check.summary) + .join('; '); + return `CheckList: ${failedSummaries}`; } default: throw new Error(`Unknown EventType: ${event.EventType}`); -- 2.49.1 From ea4eaa8df34b5d2f3054417fd7708abad41c861b Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 11 Jun 2025 18:17:39 -0700 Subject: [PATCH 11/17] validation: fixup rebase mistakes --- validation/api/src/internal.rs | 6 +++--- validation/api/src/types.rs | 12 ++++++------ validation/src/check_mapfix.rs | 6 +----- validation/src/check_submission.rs | 6 +----- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/validation/api/src/internal.rs b/validation/api/src/internal.rs index 02f1bed..773c8ac 100644 --- a/validation/api/src/internal.rs +++ b/validation/api/src/internal.rs @@ -165,7 +165,7 @@ impl Context{ let url_raw=format!("{}/submissions/{}/checklist",self.0.base_url,config.SubmissionID.0); let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; - let body=serde_json::to_string(&config.check_list).map_err(Error::JSON)?; + let body=serde_json::to_string(&config.CheckList).map_err(Error::JSON)?; response_ok( self.0.post(url,body).await.map_err(Error::Reqwest)? @@ -204,11 +204,11 @@ impl Context{ ).await.map_err(Error::Response)? .json().await.map_err(Error::ReqwestJson) } - pub async fn create_mapfix_audit_check_list(&self,config:CreateMapfixCheckListRequest<'_>)->Result<(),Error>{ + pub async fn create_mapfix_audit_check_list(&self,config:CreateMapfixAuditCheckListRequest<'_>)->Result<(),Error>{ let url_raw=format!("{}/mapfixes/{}/checklist",self.0.base_url,config.MapfixID.0); let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?; - let body=serde_json::to_string(&config.check_list).map_err(Error::JSON)?; + let body=serde_json::to_string(&config.CheckList).map_err(Error::JSON)?; response_ok( self.0.post(url,body).await.map_err(Error::Reqwest)? diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs index 13070ef..e591f38 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -354,7 +354,7 @@ pub struct ActionSubmissionSubmittedRequest{ #[allow(nonstandard_style)] #[derive(Clone,Debug,serde::Serialize)] -pub struct ActionSubmissionRequestChangesRequest<'a>{ +pub struct ActionSubmissionRequestChangesRequest{ pub SubmissionID:SubmissionID, } @@ -375,14 +375,14 @@ pub struct ActionSubmissionAcceptedRequest{ #[derive(Clone,Debug)] pub struct CreateSubmissionAuditErrorRequest{ pub SubmissionID:SubmissionID, - pub CheckList:&'a [Check], + pub ErrorMessage:String, } #[allow(nonstandard_style)] #[derive(Clone,Debug)] -pub struct CreateSubmissionAuditCheckListRequest{ +pub struct CreateSubmissionAuditCheckListRequest<'a>{ pub SubmissionID:SubmissionID, - pub ErrorMessage:String, + pub CheckList:&'a [Check], } #[derive(Clone,Copy,Debug,serde::Serialize,serde::Deserialize)] @@ -408,7 +408,7 @@ pub struct ActionMapfixSubmittedRequest{ #[allow(nonstandard_style)] #[derive(Clone,Debug,serde::Serialize)] -pub struct ActionMapfixRequestChangesRequest<'a>{ +pub struct ActionMapfixRequestChangesRequest{ pub MapfixID:MapfixID, } @@ -433,7 +433,7 @@ pub struct CreateMapfixAuditErrorRequest{ #[allow(nonstandard_style)] #[derive(Clone,Debug)] -pub struct CreateMapfixAuditCheckListRequest{ +pub struct CreateMapfixAuditCheckListRequest<'a>{ pub MapfixID:MapfixID, pub CheckList:&'a [Check], } diff --git a/validation/src/check_mapfix.rs b/validation/src/check_mapfix.rs index e6499ed..a473fbf 100644 --- a/validation/src/check_mapfix.rs +++ b/validation/src/check_mapfix.rs @@ -50,11 +50,7 @@ impl crate::message_handler::MessageHandler{ self.api.create_mapfix_audit_error( submissions_api::types::CreateMapfixAuditErrorRequest{ MapfixID:mapfix_id, - CheckList:&[submissions_api::types::Check{ - Name:"InternalError", - Summary:e.to_string(), - Passed:false, - }], + ErrorMessage:e.to_string(), } ).await.map_err(Error::ApiActionMapfixCheck)?; }, diff --git a/validation/src/check_submission.rs b/validation/src/check_submission.rs index 7155bcf..4e6d2b7 100644 --- a/validation/src/check_submission.rs +++ b/validation/src/check_submission.rs @@ -51,11 +51,7 @@ impl crate::message_handler::MessageHandler{ self.api.create_submission_audit_error( submissions_api::types::CreateSubmissionAuditErrorRequest{ SubmissionID:submission_id, - CheckList:&[submissions_api::types::Check{ - Name:"InternalError", - Summary:e.to_string(), - Passed:false, - }], + ErrorMessage:e.to_string(), } ).await.map_err(Error::ApiActionSubmissionCheck)?; }, -- 2.49.1 From 3372ffd1859c21818e092d07ef44f4b7ad96adce Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 11 Jun 2025 18:18:14 -0700 Subject: [PATCH 12/17] openapi: generate --- pkg/internal/oas_client_gen.go | 222 ++++++++++++- pkg/internal/oas_handlers_gen.go | 374 +++++++++++++++++++--- pkg/internal/oas_operations_gen.go | 2 + pkg/internal/oas_parameters_gen.go | 166 ++++++++++ pkg/internal/oas_request_decoders_gen.go | 284 ++++++++-------- pkg/internal/oas_request_encoders_gen.go | 56 ++-- pkg/internal/oas_response_decoders_gen.go | 120 +++++++ pkg/internal/oas_response_encoders_gen.go | 14 + pkg/internal/oas_router_gen.go | 92 ++++++ pkg/internal/oas_schemas_gen.go | 45 +++ pkg/internal/oas_server_gen.go | 16 +- pkg/internal/oas_unimplemented_gen.go | 22 +- 12 files changed, 1187 insertions(+), 226 deletions(-) diff --git a/pkg/internal/oas_client_gen.go b/pkg/internal/oas_client_gen.go index 890513c..ba9a38c 100644 --- a/pkg/internal/oas_client_gen.go +++ b/pkg/internal/oas_client_gen.go @@ -39,7 +39,7 @@ type Invoker interface { // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /mapfixes/{MapfixID}/status/validator-request-changes - ActionMapfixRequestChanges(ctx context.Context, request CheckList, params ActionMapfixRequestChangesParams) error + ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error // ActionMapfixSubmitted invokes actionMapfixSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -75,7 +75,7 @@ type Invoker interface { // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /submissions/{SubmissionID}/status/validator-request-changes - ActionSubmissionRequestChanges(ctx context.Context, request CheckList, params ActionSubmissionRequestChangesParams) error + ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error // ActionSubmissionSubmitted invokes actionSubmissionSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -100,6 +100,12 @@ type Invoker interface { // // POST /mapfixes CreateMapfix(ctx context.Context, request *MapfixCreate) (*MapfixID, error) + // CreateMapfixAuditCheckList invokes createMapfixAuditCheckList operation. + // + // Validator posts a checklist to the audit log. + // + // POST /mapfixes/{MapfixID}/checklist + CreateMapfixAuditCheckList(ctx context.Context, request CheckList, params CreateMapfixAuditCheckListParams) error // CreateMapfixAuditError invokes createMapfixAuditError operation. // // Validator posts an error to the audit log. @@ -124,6 +130,12 @@ type Invoker interface { // // POST /submissions CreateSubmission(ctx context.Context, request *SubmissionCreate) (*SubmissionID, error) + // CreateSubmissionAuditCheckList invokes createSubmissionAuditCheckList operation. + // + // Validator posts a checklist to the audit log. + // + // POST /submissions/{SubmissionID}/checklist + CreateSubmissionAuditCheckList(ctx context.Context, request CheckList, params CreateSubmissionAuditCheckListParams) error // CreateSubmissionAuditError invokes createSubmissionAuditError operation. // // Validator posts an error to the audit log. @@ -305,12 +317,12 @@ func (c *Client) sendActionMapfixAccepted(ctx context.Context, params ActionMapf // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /mapfixes/{MapfixID}/status/validator-request-changes -func (c *Client) ActionMapfixRequestChanges(ctx context.Context, request CheckList, params ActionMapfixRequestChangesParams) error { - _, err := c.sendActionMapfixRequestChanges(ctx, request, params) +func (c *Client) ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error { + _, err := c.sendActionMapfixRequestChanges(ctx, params) return err } -func (c *Client) sendActionMapfixRequestChanges(ctx context.Context, request CheckList, params ActionMapfixRequestChangesParams) (res *ActionMapfixRequestChangesNoContent, err error) { +func (c *Client) sendActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) (res *ActionMapfixRequestChangesNoContent, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("actionMapfixRequestChanges"), semconv.HTTPRequestMethodKey.String("POST"), @@ -374,9 +386,6 @@ func (c *Client) sendActionMapfixRequestChanges(ctx context.Context, request Che if err != nil { return res, errors.Wrap(err, "create request") } - if err := encodeActionMapfixRequestChangesRequest(request, r); err != nil { - return res, errors.Wrap(err, "encode request") - } stage = "SendRequest" resp, err := c.cfg.Client.Do(r) @@ -932,12 +941,12 @@ func (c *Client) sendActionSubmissionAccepted(ctx context.Context, params Action // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /submissions/{SubmissionID}/status/validator-request-changes -func (c *Client) ActionSubmissionRequestChanges(ctx context.Context, request CheckList, params ActionSubmissionRequestChangesParams) error { - _, err := c.sendActionSubmissionRequestChanges(ctx, request, params) +func (c *Client) ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error { + _, err := c.sendActionSubmissionRequestChanges(ctx, params) return err } -func (c *Client) sendActionSubmissionRequestChanges(ctx context.Context, request CheckList, params ActionSubmissionRequestChangesParams) (res *ActionSubmissionRequestChangesNoContent, err error) { +func (c *Client) sendActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) (res *ActionSubmissionRequestChangesNoContent, err error) { otelAttrs := []attribute.KeyValue{ otelogen.OperationID("actionSubmissionRequestChanges"), semconv.HTTPRequestMethodKey.String("POST"), @@ -1001,9 +1010,6 @@ func (c *Client) sendActionSubmissionRequestChanges(ctx context.Context, request if err != nil { return res, errors.Wrap(err, "create request") } - if err := encodeActionSubmissionRequestChangesRequest(request, r); err != nil { - return res, errors.Wrap(err, "encode request") - } stage = "SendRequest" resp, err := c.cfg.Client.Do(r) @@ -1447,6 +1453,100 @@ func (c *Client) sendCreateMapfix(ctx context.Context, request *MapfixCreate) (r return result, nil } +// CreateMapfixAuditCheckList invokes createMapfixAuditCheckList operation. +// +// Validator posts a checklist to the audit log. +// +// POST /mapfixes/{MapfixID}/checklist +func (c *Client) CreateMapfixAuditCheckList(ctx context.Context, request CheckList, params CreateMapfixAuditCheckListParams) error { + _, err := c.sendCreateMapfixAuditCheckList(ctx, request, params) + return err +} + +func (c *Client) sendCreateMapfixAuditCheckList(ctx context.Context, request CheckList, params CreateMapfixAuditCheckListParams) (res *CreateMapfixAuditCheckListNoContent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createMapfixAuditCheckList"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/checklist"), + } + + // 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, CreateMapfixAuditCheckListOperation, + 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] = "/mapfixes/" + { + // Encode "MapfixID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "MapfixID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.MapfixID)) + }(); 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] = "/checklist" + 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 := encodeCreateMapfixAuditCheckListRequest(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 := decodeCreateMapfixAuditCheckListResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // CreateMapfixAuditError invokes createMapfixAuditError operation. // // Validator posts an error to the audit log. @@ -1781,6 +1881,100 @@ func (c *Client) sendCreateSubmission(ctx context.Context, request *SubmissionCr return result, nil } +// CreateSubmissionAuditCheckList invokes createSubmissionAuditCheckList operation. +// +// Validator posts a checklist to the audit log. +// +// POST /submissions/{SubmissionID}/checklist +func (c *Client) CreateSubmissionAuditCheckList(ctx context.Context, request CheckList, params CreateSubmissionAuditCheckListParams) error { + _, err := c.sendCreateSubmissionAuditCheckList(ctx, request, params) + return err +} + +func (c *Client) sendCreateSubmissionAuditCheckList(ctx context.Context, request CheckList, params CreateSubmissionAuditCheckListParams) (res *CreateSubmissionAuditCheckListNoContent, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createSubmissionAuditCheckList"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/checklist"), + } + + // 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, CreateSubmissionAuditCheckListOperation, + 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] = "/submissions/" + { + // Encode "SubmissionID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "SubmissionID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.SubmissionID)) + }(); 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] = "/checklist" + 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 := encodeCreateSubmissionAuditCheckListRequest(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 := decodeCreateSubmissionAuditCheckListResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // CreateSubmissionAuditError invokes createSubmissionAuditError operation. // // Validator posts an error to the audit log. diff --git a/pkg/internal/oas_handlers_gen.go b/pkg/internal/oas_handlers_gen.go index c45ec3b..fc18a64 100644 --- a/pkg/internal/oas_handlers_gen.go +++ b/pkg/internal/oas_handlers_gen.go @@ -263,21 +263,6 @@ func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEsc s.cfg.ErrorHandler(ctx, w, r, err) return } - request, close, err := s.decodeActionMapfixRequestChangesRequest(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 *ActionMapfixRequestChangesNoContent if m := s.cfg.Middleware; m != nil { @@ -286,7 +271,7 @@ func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEsc OperationName: ActionMapfixRequestChangesOperation, OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested", OperationID: "actionMapfixRequestChanges", - Body: request, + Body: nil, Params: middleware.Parameters{ { Name: "MapfixID", @@ -297,7 +282,7 @@ func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEsc } type ( - Request = CheckList + Request = struct{} Params = ActionMapfixRequestChangesParams Response = *ActionMapfixRequestChangesNoContent ) @@ -310,12 +295,12 @@ func (s *Server) handleActionMapfixRequestChangesRequest(args [1]string, argsEsc mreq, unpackActionMapfixRequestChangesParams, func(ctx context.Context, request Request, params Params) (response Response, err error) { - err = s.h.ActionMapfixRequestChanges(ctx, request, params) + err = s.h.ActionMapfixRequestChanges(ctx, params) return response, err }, ) } else { - err = s.h.ActionMapfixRequestChanges(ctx, request, params) + err = s.h.ActionMapfixRequestChanges(ctx, params) } if err != nil { if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { @@ -1192,21 +1177,6 @@ func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, arg s.cfg.ErrorHandler(ctx, w, r, err) return } - request, close, err := s.decodeActionSubmissionRequestChangesRequest(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 *ActionSubmissionRequestChangesNoContent if m := s.cfg.Middleware; m != nil { @@ -1215,7 +1185,7 @@ func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, arg OperationName: ActionSubmissionRequestChangesOperation, OperationSummary: "(Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested", OperationID: "actionSubmissionRequestChanges", - Body: request, + Body: nil, Params: middleware.Parameters{ { Name: "SubmissionID", @@ -1226,7 +1196,7 @@ func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, arg } type ( - Request = CheckList + Request = struct{} Params = ActionSubmissionRequestChangesParams Response = *ActionSubmissionRequestChangesNoContent ) @@ -1239,12 +1209,12 @@ func (s *Server) handleActionSubmissionRequestChangesRequest(args [1]string, arg mreq, unpackActionSubmissionRequestChangesParams, func(ctx context.Context, request Request, params Params) (response Response, err error) { - err = s.h.ActionSubmissionRequestChanges(ctx, request, params) + err = s.h.ActionSubmissionRequestChanges(ctx, params) return response, err }, ) } else { - err = s.h.ActionSubmissionRequestChanges(ctx, request, params) + err = s.h.ActionSubmissionRequestChanges(ctx, params) } if err != nil { if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { @@ -1888,6 +1858,170 @@ func (s *Server) handleCreateMapfixRequest(args [0]string, argsEscaped bool, w h } } +// handleCreateMapfixAuditCheckListRequest handles createMapfixAuditCheckList operation. +// +// Validator posts a checklist to the audit log. +// +// POST /mapfixes/{MapfixID}/checklist +func (s *Server) handleCreateMapfixAuditCheckListRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createMapfixAuditCheckList"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/mapfixes/{MapfixID}/checklist"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), CreateMapfixAuditCheckListOperation, + 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: CreateMapfixAuditCheckListOperation, + ID: "createMapfixAuditCheckList", + } + ) + params, err := decodeCreateMapfixAuditCheckListParams(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 + } + request, close, err := s.decodeCreateMapfixAuditCheckListRequest(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 *CreateMapfixAuditCheckListNoContent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: CreateMapfixAuditCheckListOperation, + OperationSummary: "Validator posts a checklist to the audit log", + OperationID: "createMapfixAuditCheckList", + Body: request, + Params: middleware.Parameters{ + { + Name: "MapfixID", + In: "path", + }: params.MapfixID, + }, + Raw: r, + } + + type ( + Request = CheckList + Params = CreateMapfixAuditCheckListParams + Response = *CreateMapfixAuditCheckListNoContent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackCreateMapfixAuditCheckListParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.CreateMapfixAuditCheckList(ctx, request, params) + return response, err + }, + ) + } else { + err = s.h.CreateMapfixAuditCheckList(ctx, request, 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 := encodeCreateMapfixAuditCheckListResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleCreateMapfixAuditErrorRequest handles createMapfixAuditError operation. // // Validator posts an error to the audit log. @@ -2488,6 +2622,170 @@ func (s *Server) handleCreateSubmissionRequest(args [0]string, argsEscaped bool, } } +// handleCreateSubmissionAuditCheckListRequest handles createSubmissionAuditCheckList operation. +// +// Validator posts a checklist to the audit log. +// +// POST /submissions/{SubmissionID}/checklist +func (s *Server) handleCreateSubmissionAuditCheckListRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createSubmissionAuditCheckList"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/checklist"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), CreateSubmissionAuditCheckListOperation, + 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: CreateSubmissionAuditCheckListOperation, + ID: "createSubmissionAuditCheckList", + } + ) + params, err := decodeCreateSubmissionAuditCheckListParams(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 + } + request, close, err := s.decodeCreateSubmissionAuditCheckListRequest(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 *CreateSubmissionAuditCheckListNoContent + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: CreateSubmissionAuditCheckListOperation, + OperationSummary: "Validator posts a checklist to the audit log", + OperationID: "createSubmissionAuditCheckList", + Body: request, + Params: middleware.Parameters{ + { + Name: "SubmissionID", + In: "path", + }: params.SubmissionID, + }, + Raw: r, + } + + type ( + Request = CheckList + Params = CreateSubmissionAuditCheckListParams + Response = *CreateSubmissionAuditCheckListNoContent + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackCreateSubmissionAuditCheckListParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.CreateSubmissionAuditCheckList(ctx, request, params) + return response, err + }, + ) + } else { + err = s.h.CreateSubmissionAuditCheckList(ctx, request, 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 := encodeCreateSubmissionAuditCheckListResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleCreateSubmissionAuditErrorRequest handles createSubmissionAuditError operation. // // Validator posts an error to the audit log. diff --git a/pkg/internal/oas_operations_gen.go b/pkg/internal/oas_operations_gen.go index 1ee0aac..a789b23 100644 --- a/pkg/internal/oas_operations_gen.go +++ b/pkg/internal/oas_operations_gen.go @@ -18,10 +18,12 @@ const ( ActionSubmissionUploadedOperation OperationName = "ActionSubmissionUploaded" ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated" CreateMapfixOperation OperationName = "CreateMapfix" + CreateMapfixAuditCheckListOperation OperationName = "CreateMapfixAuditCheckList" CreateMapfixAuditErrorOperation OperationName = "CreateMapfixAuditError" CreateScriptOperation OperationName = "CreateScript" CreateScriptPolicyOperation OperationName = "CreateScriptPolicy" CreateSubmissionOperation OperationName = "CreateSubmission" + CreateSubmissionAuditCheckListOperation OperationName = "CreateSubmissionAuditCheckList" CreateSubmissionAuditErrorOperation OperationName = "CreateSubmissionAuditError" GetScriptOperation OperationName = "GetScript" ListScriptPolicyOperation OperationName = "ListScriptPolicy" diff --git a/pkg/internal/oas_parameters_gen.go b/pkg/internal/oas_parameters_gen.go index e8f11fd..7194603 100644 --- a/pkg/internal/oas_parameters_gen.go +++ b/pkg/internal/oas_parameters_gen.go @@ -1537,6 +1537,89 @@ func decodeActionSubmissionValidatedParams(args [1]string, argsEscaped bool, r * return params, nil } +// CreateMapfixAuditCheckListParams is parameters of createMapfixAuditCheckList operation. +type CreateMapfixAuditCheckListParams struct { + // The unique identifier for a submission. + MapfixID int64 +} + +func unpackCreateMapfixAuditCheckListParams(packed middleware.Parameters) (params CreateMapfixAuditCheckListParams) { + { + key := middleware.ParameterKey{ + Name: "MapfixID", + In: "path", + } + params.MapfixID = packed[key].(int64) + } + return params +} + +func decodeCreateMapfixAuditCheckListParams(args [1]string, argsEscaped bool, r *http.Request) (params CreateMapfixAuditCheckListParams, _ error) { + // Decode path: MapfixID. + 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: "MapfixID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.MapfixID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.MapfixID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "MapfixID", + In: "path", + Err: err, + } + } + return params, nil +} + // CreateMapfixAuditErrorParams is parameters of createMapfixAuditError operation. type CreateMapfixAuditErrorParams struct { // The unique identifier for a submission. @@ -1681,6 +1764,89 @@ func decodeCreateMapfixAuditErrorParams(args [1]string, argsEscaped bool, r *htt return params, nil } +// CreateSubmissionAuditCheckListParams is parameters of createSubmissionAuditCheckList operation. +type CreateSubmissionAuditCheckListParams struct { + // The unique identifier for a submission. + SubmissionID int64 +} + +func unpackCreateSubmissionAuditCheckListParams(packed middleware.Parameters) (params CreateSubmissionAuditCheckListParams) { + { + key := middleware.ParameterKey{ + Name: "SubmissionID", + In: "path", + } + params.SubmissionID = packed[key].(int64) + } + return params +} + +func decodeCreateSubmissionAuditCheckListParams(args [1]string, argsEscaped bool, r *http.Request) (params CreateSubmissionAuditCheckListParams, _ error) { + // Decode path: SubmissionID. + 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: "SubmissionID", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + params.SubmissionID = c + return nil + }(); err != nil { + return err + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(params.SubmissionID)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "SubmissionID", + In: "path", + Err: err, + } + } + return params, nil +} + // CreateSubmissionAuditErrorParams is parameters of createSubmissionAuditError operation. type CreateSubmissionAuditErrorParams 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 f159295..3d02cd5 100644 --- a/pkg/internal/oas_request_decoders_gen.go +++ b/pkg/internal/oas_request_decoders_gen.go @@ -14,148 +14,6 @@ import ( "github.com/ogen-go/ogen/validate" ) -func (s *Server) decodeActionMapfixRequestChangesRequest(r *http.Request) ( - req CheckList, - 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 = errors.Join(merr, c()) - } - return merr - } - defer func() { - if rerr != nil { - rerr = errors.Join(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 CheckList - 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) decodeActionSubmissionRequestChangesRequest(r *http.Request) ( - req CheckList, - 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 = errors.Join(merr, c()) - } - return merr - } - defer func() { - if rerr != nil { - rerr = errors.Join(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 CheckList - 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) decodeCreateMapfixRequest(r *http.Request) ( req *MapfixCreate, close func() error, @@ -227,6 +85,77 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) ( } } +func (s *Server) decodeCreateMapfixAuditCheckListRequest(r *http.Request) ( + req CheckList, + 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 = errors.Join(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = errors.Join(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 CheckList + 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, @@ -439,3 +368,74 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) ( return req, close, validate.InvalidContentType(ct) } } + +func (s *Server) decodeCreateSubmissionAuditCheckListRequest(r *http.Request) ( + req CheckList, + 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 = errors.Join(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = errors.Join(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 CheckList + 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 bcf0909..a819e46 100644 --- a/pkg/internal/oas_request_encoders_gen.go +++ b/pkg/internal/oas_request_encoders_gen.go @@ -11,34 +11,6 @@ import ( ht "github.com/ogen-go/ogen/http" ) -func encodeActionMapfixRequestChangesRequest( - req CheckList, - 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 encodeActionSubmissionRequestChangesRequest( - req CheckList, - 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 encodeCreateMapfixRequest( req *MapfixCreate, r *http.Request, @@ -53,6 +25,20 @@ func encodeCreateMapfixRequest( return nil } +func encodeCreateMapfixAuditCheckListRequest( + req CheckList, + 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, @@ -94,3 +80,17 @@ func encodeCreateSubmissionRequest( ht.SetBody(r, bytes.NewReader(encoded), contentType) return nil } + +func encodeCreateSubmissionAuditCheckListRequest( + req CheckList, + 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 28f2f29..c68aff2 100644 --- a/pkg/internal/oas_response_decoders_gen.go +++ b/pkg/internal/oas_response_decoders_gen.go @@ -776,6 +776,66 @@ func decodeCreateMapfixResponse(resp *http.Response) (res *MapfixID, _ error) { return res, errors.Wrap(defRes, "error") } +func decodeCreateMapfixAuditCheckListResponse(resp *http.Response) (res *CreateMapfixAuditCheckListNoContent, _ error) { + switch resp.StatusCode { + case 204: + // Code 204. + return &CreateMapfixAuditCheckListNoContent{}, 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 + } + // 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 &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 decodeCreateMapfixAuditErrorResponse(resp *http.Response) (res *CreateMapfixAuditErrorNoContent, _ error) { switch resp.StatusCode { case 204: @@ -1139,6 +1199,66 @@ func decodeCreateSubmissionResponse(resp *http.Response) (res *SubmissionID, _ e return res, errors.Wrap(defRes, "error") } +func decodeCreateSubmissionAuditCheckListResponse(resp *http.Response) (res *CreateSubmissionAuditCheckListNoContent, _ error) { + switch resp.StatusCode { + case 204: + // Code 204. + return &CreateSubmissionAuditCheckListNoContent{}, 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 + } + // 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 &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 decodeCreateSubmissionAuditErrorResponse(resp *http.Response) (res *CreateSubmissionAuditErrorNoContent, _ error) { switch resp.StatusCode { case 204: diff --git a/pkg/internal/oas_response_encoders_gen.go b/pkg/internal/oas_response_encoders_gen.go index 5cde190..00268a8 100644 --- a/pkg/internal/oas_response_encoders_gen.go +++ b/pkg/internal/oas_response_encoders_gen.go @@ -104,6 +104,13 @@ func encodeCreateMapfixResponse(response *MapfixID, w http.ResponseWriter, span return nil } +func encodeCreateMapfixAuditCheckListResponse(response *CreateMapfixAuditCheckListNoContent, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(204) + span.SetStatus(codes.Ok, http.StatusText(204)) + + return nil +} + func encodeCreateMapfixAuditErrorResponse(response *CreateMapfixAuditErrorNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) @@ -153,6 +160,13 @@ func encodeCreateSubmissionResponse(response *SubmissionID, w http.ResponseWrite return nil } +func encodeCreateSubmissionAuditCheckListResponse(response *CreateSubmissionAuditCheckListNoContent, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(204) + span.SetStatus(codes.Ok, http.StatusText(204)) + + return nil +} + func encodeCreateSubmissionAuditErrorResponse(response *CreateSubmissionAuditErrorNoContent, w http.ResponseWriter, span trace.Span) error { w.WriteHeader(204) span.SetStatus(codes.Ok, http.StatusText(204)) diff --git a/pkg/internal/oas_router_gen.go b/pkg/internal/oas_router_gen.go index a6615a9..e89474a 100644 --- a/pkg/internal/oas_router_gen.go +++ b/pkg/internal/oas_router_gen.go @@ -113,6 +113,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { + case 'c': // Prefix: "checklist" + + if l := len("checklist"); len(elem) >= l && elem[0:l] == "checklist" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleCreateMapfixAuditCheckListRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + case 'e': // Prefix: "error" if l := len("error"); len(elem) >= l && elem[0:l] == "error" { @@ -486,6 +508,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { break } switch elem[0] { + case 'c': // Prefix: "checklist" + + if l := len("checklist"); len(elem) >= l && elem[0:l] == "checklist" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleCreateSubmissionAuditCheckListRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + case 'e': // Prefix: "error" if l := len("error"); len(elem) >= l && elem[0:l] == "error" { @@ -812,6 +856,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { + case 'c': // Prefix: "checklist" + + if l := len("checklist"); len(elem) >= l && elem[0:l] == "checklist" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = CreateMapfixAuditCheckListOperation + r.summary = "Validator posts a checklist to the audit log" + r.operationID = "createMapfixAuditCheckList" + r.pathPattern = "/mapfixes/{MapfixID}/checklist" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + case 'e': // Prefix: "error" if l := len("error"); len(elem) >= l && elem[0:l] == "error" { @@ -1227,6 +1295,30 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { break } switch elem[0] { + case 'c': // Prefix: "checklist" + + if l := len("checklist"); len(elem) >= l && elem[0:l] == "checklist" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = CreateSubmissionAuditCheckListOperation + r.summary = "Validator posts a checklist to the audit log" + r.operationID = "createSubmissionAuditCheckList" + r.pathPattern = "/submissions/{SubmissionID}/checklist" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + case 'e': // Prefix: "error" if l := len("error"); len(elem) >= l && elem[0:l] == "error" { diff --git a/pkg/internal/oas_schemas_gen.go b/pkg/internal/oas_schemas_gen.go index 16c6e42..bcca85e 100644 --- a/pkg/internal/oas_schemas_gen.go +++ b/pkg/internal/oas_schemas_gen.go @@ -43,9 +43,54 @@ type ActionSubmissionUploadedNoContent struct{} // ActionSubmissionValidatedNoContent is response for ActionSubmissionValidated operation. type ActionSubmissionValidatedNoContent struct{} +// Ref: #/components/schemas/Check +type Check struct { + Name string `json:"Name"` + Summary string `json:"Summary"` + Passed bool `json:"Passed"` +} + +// GetName returns the value of Name. +func (s *Check) GetName() string { + return s.Name +} + +// GetSummary returns the value of Summary. +func (s *Check) GetSummary() string { + return s.Summary +} + +// GetPassed returns the value of Passed. +func (s *Check) GetPassed() bool { + return s.Passed +} + +// SetName sets the value of Name. +func (s *Check) SetName(val string) { + s.Name = val +} + +// SetSummary sets the value of Summary. +func (s *Check) SetSummary(val string) { + s.Summary = val +} + +// SetPassed sets the value of Passed. +func (s *Check) SetPassed(val bool) { + s.Passed = val +} + +type CheckList []Check + +// CreateMapfixAuditCheckListNoContent is response for CreateMapfixAuditCheckList operation. +type CreateMapfixAuditCheckListNoContent struct{} + // CreateMapfixAuditErrorNoContent is response for CreateMapfixAuditError operation. type CreateMapfixAuditErrorNoContent struct{} +// CreateSubmissionAuditCheckListNoContent is response for CreateSubmissionAuditCheckList operation. +type CreateSubmissionAuditCheckListNoContent struct{} + // CreateSubmissionAuditErrorNoContent is response for CreateSubmissionAuditError operation. type CreateSubmissionAuditErrorNoContent struct{} diff --git a/pkg/internal/oas_server_gen.go b/pkg/internal/oas_server_gen.go index f2bcdfc..58f028f 100644 --- a/pkg/internal/oas_server_gen.go +++ b/pkg/internal/oas_server_gen.go @@ -19,7 +19,7 @@ type Handler interface { // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /mapfixes/{MapfixID}/status/validator-request-changes - ActionMapfixRequestChanges(ctx context.Context, req CheckList, params ActionMapfixRequestChangesParams) error + ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error // ActionMapfixSubmitted implements actionMapfixSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -55,7 +55,7 @@ type Handler interface { // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /submissions/{SubmissionID}/status/validator-request-changes - ActionSubmissionRequestChanges(ctx context.Context, req CheckList, params ActionSubmissionRequestChangesParams) error + ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error // ActionSubmissionSubmitted implements actionSubmissionSubmitted operation. // // (Internal endpoint) Role Validator changes status from Submitting -> Submitted. @@ -80,6 +80,12 @@ type Handler interface { // // POST /mapfixes CreateMapfix(ctx context.Context, req *MapfixCreate) (*MapfixID, error) + // CreateMapfixAuditCheckList implements createMapfixAuditCheckList operation. + // + // Validator posts a checklist to the audit log. + // + // POST /mapfixes/{MapfixID}/checklist + CreateMapfixAuditCheckList(ctx context.Context, req CheckList, params CreateMapfixAuditCheckListParams) error // CreateMapfixAuditError implements createMapfixAuditError operation. // // Validator posts an error to the audit log. @@ -104,6 +110,12 @@ type Handler interface { // // POST /submissions CreateSubmission(ctx context.Context, req *SubmissionCreate) (*SubmissionID, error) + // CreateSubmissionAuditCheckList implements createSubmissionAuditCheckList operation. + // + // Validator posts a checklist to the audit log. + // + // POST /submissions/{SubmissionID}/checklist + CreateSubmissionAuditCheckList(ctx context.Context, req CheckList, params CreateSubmissionAuditCheckListParams) error // CreateSubmissionAuditError implements createSubmissionAuditError operation. // // Validator posts an error to the audit log. diff --git a/pkg/internal/oas_unimplemented_gen.go b/pkg/internal/oas_unimplemented_gen.go index b94f66e..6257f15 100644 --- a/pkg/internal/oas_unimplemented_gen.go +++ b/pkg/internal/oas_unimplemented_gen.go @@ -27,7 +27,7 @@ func (UnimplementedHandler) ActionMapfixAccepted(ctx context.Context, params Act // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /mapfixes/{MapfixID}/status/validator-request-changes -func (UnimplementedHandler) ActionMapfixRequestChanges(ctx context.Context, req CheckList, params ActionMapfixRequestChangesParams) error { +func (UnimplementedHandler) ActionMapfixRequestChanges(ctx context.Context, params ActionMapfixRequestChangesParams) error { return ht.ErrNotImplemented } @@ -81,7 +81,7 @@ func (UnimplementedHandler) ActionSubmissionAccepted(ctx context.Context, params // (Internal endpoint) Role Validator changes status from Submitting -> ChangesRequested. // // POST /submissions/{SubmissionID}/status/validator-request-changes -func (UnimplementedHandler) ActionSubmissionRequestChanges(ctx context.Context, req CheckList, params ActionSubmissionRequestChangesParams) error { +func (UnimplementedHandler) ActionSubmissionRequestChanges(ctx context.Context, params ActionSubmissionRequestChangesParams) error { return ht.ErrNotImplemented } @@ -121,6 +121,15 @@ func (UnimplementedHandler) CreateMapfix(ctx context.Context, req *MapfixCreate) return r, ht.ErrNotImplemented } +// CreateMapfixAuditCheckList implements createMapfixAuditCheckList operation. +// +// Validator posts a checklist to the audit log. +// +// POST /mapfixes/{MapfixID}/checklist +func (UnimplementedHandler) CreateMapfixAuditCheckList(ctx context.Context, req CheckList, params CreateMapfixAuditCheckListParams) error { + return ht.ErrNotImplemented +} + // CreateMapfixAuditError implements createMapfixAuditError operation. // // Validator posts an error to the audit log. @@ -157,6 +166,15 @@ func (UnimplementedHandler) CreateSubmission(ctx context.Context, req *Submissio return r, ht.ErrNotImplemented } +// CreateSubmissionAuditCheckList implements createSubmissionAuditCheckList operation. +// +// Validator posts a checklist to the audit log. +// +// POST /submissions/{SubmissionID}/checklist +func (UnimplementedHandler) CreateSubmissionAuditCheckList(ctx context.Context, req CheckList, params CreateSubmissionAuditCheckListParams) error { + return ht.ErrNotImplemented +} + // CreateSubmissionAuditError implements createSubmissionAuditError operation. // // Validator posts an error to the audit log. -- 2.49.1 From 34ca1b54635c39217ee787e2832dcafa80ec1e81 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 11 Jun 2025 18:19:38 -0700 Subject: [PATCH 13/17] submissions: fixups --- pkg/model/audit_event.go | 4 ++-- pkg/service_internal/audit_events.go | 21 +++++++++++++++++++++ pkg/service_internal/mapfixes.go | 2 +- pkg/service_internal/submissions.go | 2 +- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/pkg/model/audit_event.go b/pkg/model/audit_event.go index cd60447..3867431 100644 --- a/pkg/model/audit_event.go +++ b/pkg/model/audit_event.go @@ -55,9 +55,9 @@ type Check struct { } // Validator map checks details -const AuditEventTypeCheckReport AuditEventType = 7 +const AuditEventTypeCheckList AuditEventType = 7 -type AuditEventDataCheckReport struct { +type AuditEventDataCheckList struct { CheckList []Check `json:"checklist"` } diff --git a/pkg/service_internal/audit_events.go b/pkg/service_internal/audit_events.go index 82e6dd6..53e386c 100644 --- a/pkg/service_internal/audit_events.go +++ b/pkg/service_internal/audit_events.go @@ -69,3 +69,24 @@ func (svc *Service) CreateAuditEventError(ctx context.Context, userId uint64, re return nil } + +func (svc *Service) CreateAuditEventCheckList(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataCheckList) error { + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: resource.Type, + ResourceID: resource.ID, + EventType: model.AuditEventTypeCheckList, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go index a7fb2f4..ad8e1e3 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -103,7 +103,7 @@ func (svc *Service) ActionMapfixSubmitted(ctx context.Context, params internal.A // (Internal endpoint) Role Validator changes status from Submitting -> RequestChanges. // // POST /mapfixes/{MapfixID}/status/validator-request-changes -func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, check_list internal.CheckList, params internal.ActionMapfixRequestChangesParams) error { +func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params internal.ActionMapfixRequestChangesParams) error { // transaction target_status := model.MapfixStatusChangesRequested smap := datastore.Optional() diff --git a/pkg/service_internal/submissions.go b/pkg/service_internal/submissions.go index 1f377bc..c7c18a7 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -103,7 +103,7 @@ func (svc *Service) ActionSubmissionSubmitted(ctx context.Context, params intern // (Internal endpoint) Role Validator changes status from Submitting -> RequestChanges. // // POST /submissions/{SubmissionID}/status/validator-request-changes -func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, check_list internal.CheckList, params internal.ActionSubmissionRequestChangesParams) error { +func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params internal.ActionSubmissionRequestChangesParams) error { // transaction target_status := model.SubmissionStatusChangesRequested smap := datastore.Optional() -- 2.49.1 From 9efd87a6c93d0edd904f8ee692be4d049472acea Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 11 Jun 2025 18:21:27 -0700 Subject: [PATCH 14/17] validation: rename CheckReport to CheckList --- validation/src/check.rs | 6 +++--- validation/src/check_mapfix.rs | 6 +++--- validation/src/check_submission.rs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 72bf8dd..45da926 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -782,13 +782,13 @@ pub struct MapCheckList{ pub checks:Box<[Check;16]>, } -pub struct CheckReportAndVersion{ +pub struct CheckListAndVersion{ pub status:Result, pub version:u64, } impl crate::message_handler::MessageHandler{ - pub async fn check_inner(&self,check_info:CheckRequest)->Result{ + pub async fn check_inner(&self,check_info:CheckRequest)->Result{ // discover asset creator and latest version let info=self.cloud_context.get_asset_info( rbx_asset::cloud::GetAssetLatestRequest{asset_id:check_info.ModelID} @@ -826,6 +826,6 @@ impl crate::message_handler::MessageHandler{ Err(Err(e))=>return Err(Error::ToJsonValue(e)), }; - Ok(CheckReportAndVersion{status,version}) + Ok(CheckListAndVersion{status,version}) } } diff --git a/validation/src/check_mapfix.rs b/validation/src/check_mapfix.rs index a473fbf..ce0e840 100644 --- a/validation/src/check_mapfix.rs +++ b/validation/src/check_mapfix.rs @@ -1,4 +1,4 @@ -use crate::check::CheckReportAndVersion; +use crate::check::CheckListAndVersion; use crate::nats_types::CheckMapfixRequest; #[allow(dead_code)] @@ -21,7 +21,7 @@ impl crate::message_handler::MessageHandler{ // update the mapfix depending on the result match check_result{ - Ok(CheckReportAndVersion{status:Ok(map_info),version})=>{ + Ok(CheckListAndVersion{status:Ok(map_info),version})=>{ self.api.action_mapfix_submitted( submissions_api::types::ActionMapfixSubmittedRequest{ MapfixID:mapfix_id, @@ -36,7 +36,7 @@ impl crate::message_handler::MessageHandler{ return Ok(()); }, // update the mapfix model status to request changes - Ok(CheckReportAndVersion{status:Err(check_list),..})=>self.api.create_mapfix_audit_check_list( + Ok(CheckListAndVersion{status:Err(check_list),..})=>self.api.create_mapfix_audit_check_list( submissions_api::types::CreateMapfixAuditCheckListRequest{ MapfixID:mapfix_id, CheckList:check_list.checks.as_slice(), diff --git a/validation/src/check_submission.rs b/validation/src/check_submission.rs index 4e6d2b7..7f838b2 100644 --- a/validation/src/check_submission.rs +++ b/validation/src/check_submission.rs @@ -1,4 +1,4 @@ -use crate::check::CheckReportAndVersion; +use crate::check::CheckListAndVersion; use crate::nats_types::CheckSubmissionRequest; #[allow(dead_code)] @@ -22,7 +22,7 @@ impl crate::message_handler::MessageHandler{ // update the submission depending on the result match check_result{ // update the submission model status to submitted - Ok(CheckReportAndVersion{status:Ok(map_info),version})=>{ + Ok(CheckListAndVersion{status:Ok(map_info),version})=>{ self.api.action_submission_submitted( submissions_api::types::ActionSubmissionSubmittedRequest{ SubmissionID:submission_id, @@ -37,7 +37,7 @@ impl crate::message_handler::MessageHandler{ return Ok(()); }, // update the submission model status to request changes - Ok(CheckReportAndVersion{status:Err(check_list),..})=>self.api.create_submission_audit_check_list( + Ok(CheckListAndVersion{status:Err(check_list),..})=>self.api.create_submission_audit_check_list( submissions_api::types::CreateSubmissionAuditCheckListRequest{ SubmissionID:submission_id, CheckList:check_list.checks.as_slice(), -- 2.49.1 From 6bba14832cdf0ff37ad36a76f6dcd56936f5a4da Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 11 Jun 2025 18:23:46 -0700 Subject: [PATCH 15/17] submissions: snake_case CheckList audit event --- pkg/model/audit_event.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/model/audit_event.go b/pkg/model/audit_event.go index 3867431..f9550bc 100644 --- a/pkg/model/audit_event.go +++ b/pkg/model/audit_event.go @@ -58,7 +58,7 @@ type Check struct { const AuditEventTypeCheckList AuditEventType = 7 type AuditEventDataCheckList struct { - CheckList []Check `json:"checklist"` + CheckList []Check `json:"check_list"` } type AuditEvent struct { -- 2.49.1 From ef0044ce99c2e784eae5d07533f95d2938718779 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 11 Jun 2025 18:39:27 -0700 Subject: [PATCH 16/17] validation: remove unnecessary derives --- validation/api/src/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs index e591f38..afe058e 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -353,7 +353,7 @@ pub struct ActionSubmissionSubmittedRequest{ } #[allow(nonstandard_style)] -#[derive(Clone,Debug,serde::Serialize)] +#[derive(Clone,Debug)] pub struct ActionSubmissionRequestChangesRequest{ pub SubmissionID:SubmissionID, } @@ -407,7 +407,7 @@ pub struct ActionMapfixSubmittedRequest{ } #[allow(nonstandard_style)] -#[derive(Clone,Debug,serde::Serialize)] +#[derive(Clone,Debug)] pub struct ActionMapfixRequestChangesRequest{ pub MapfixID:MapfixID, } -- 2.49.1 From c38cc2d56e60d992371fddd5f7c404b2e711d3c9 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 11 Jun 2025 20:26:09 -0700 Subject: [PATCH 17/17] validation: tweak check name --- validation/src/check.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/validation/src/check.rs b/validation/src/check.rs index 45da926..8ce6f56 100644 --- a/validation/src/check.rs +++ b/validation/src/check.rs @@ -666,16 +666,16 @@ impl MapCheck<'_>{ } }; let (extra_finish,missing_finish)=match &self.mode_finish_counts{ - SetDifferenceCheck(Ok(()))=>(passed!("ExtraFinish"),passed!("MissingFinish")), + SetDifferenceCheck(Ok(()))=>(passed!("DanglingFinish"),passed!("MissingFinish")), SetDifferenceCheck(Err(context))=>( if context.extra.is_empty(){ - passed!("ExtraFinish") + passed!("DanglingFinish") }else{ let plural=if context.extra.len()==1{"zone"}else{"zones"}; let context=Separated::new(", ",||context.extra.iter().map(|(&mode_id,_names)| ModeElement{zone:Zone::Finish,mode_id} )); - summary_format!("ExtraFinish","No matching start zone for finish {plural}: {context}") + summary_format!("DanglingFinish","No matching start zone for finish {plural}: {context}") }, if context.missing.is_empty(){ passed!("MissingFinish") -- 2.49.1