diff --git a/openapi-internal.yaml b/openapi-internal.yaml index 8166333..a6dcaa0 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,25 @@ components: type: integer format: int32 minimum: 0 + Check: + required: + - Name + - Summary + - Passed + type: object + properties: + Name: + type: string + maxLength: 128 + Summary: + type: string + maxLength: 4096 + Passed: + type: boolean + CheckList: + type: array + items: + $ref: "#/components/schemas/Check" Error: description: Represents error object type: object diff --git a/pkg/internal/oas_client_gen.go b/pkg/internal/oas_client_gen.go index 927f983..ba9a38c 100644 --- a/pkg/internal/oas_client_gen.go +++ b/pkg/internal/oas_client_gen.go @@ -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. @@ -1441,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. @@ -1775,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 a7c67c7..fc18a64 100644 --- a/pkg/internal/oas_handlers_gen.go +++ b/pkg/internal/oas_handlers_gen.go @@ -1858,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. @@ -2458,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_json_gen.go b/pkg/internal/oas_json_gen.go index 9a74a1a..4f95e1c 100644 --- a/pkg/internal/oas_json_gen.go +++ b/pkg/internal/oas_json_gen.go @@ -12,6 +12,186 @@ 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) + } +} + +var jsonFieldsNameOfCheck = [3]string{ + 0: "Name", + 1: "Summary", + 2: "Passed", +} + +// 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\"") + } + 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{ + 0b00000111, + } { + 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 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_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 016e05d..3d02cd5 100644 --- a/pkg/internal/oas_request_decoders_gen.go +++ b/pkg/internal/oas_request_decoders_gen.go @@ -85,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, @@ -297,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 1e38200..a819e46 100644 --- a/pkg/internal/oas_request_encoders_gen.go +++ b/pkg/internal/oas_request_encoders_gen.go @@ -25,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, @@ -66,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 b091f81..58f028f 100644 --- a/pkg/internal/oas_server_gen.go +++ b/pkg/internal/oas_server_gen.go @@ -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 666fc14..6257f15 100644 --- a/pkg/internal/oas_unimplemented_gen.go +++ b/pkg/internal/oas_unimplemented_gen.go @@ -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. 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 diff --git a/pkg/model/audit_event.go b/pkg/model/audit_event.go index 53b0632..f9550bc 100644 --- a/pkg/model/audit_event.go +++ b/pkg/model/audit_event.go @@ -48,6 +48,19 @@ type AuditEventDataError struct { Error string `json:"error"` } +type Check struct { + Name string `json:"name"` + Summary string `json:"summary"` + Passed bool `json:"passed"` +} + +// Validator map checks details +const AuditEventTypeCheckList AuditEventType = 7 + +type AuditEventDataCheckList struct { + CheckList []Check `json:"check_list"` +} + type AuditEvent struct { ID int64 `gorm:"primaryKey"` CreatedAt time.Time 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 7c44a89..ad8e1e3 100644 --- a/pkg/service_internal/mapfixes.go +++ b/pkg/service_internal/mapfixes.go @@ -222,6 +222,36 @@ 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, + } + } + + 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..c7c18a7 100644 --- a/pkg/service_internal/submissions.go +++ b/pkg/service_internal/submissions.go @@ -242,6 +242,36 @@ 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, + } + } + + 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 diff --git a/validation/api/src/internal.rs b/validation/api/src/internal.rs index 28fe19c..773c8ac 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.CheckList).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: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.CheckList).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..afe058e 100644 --- a/validation/api/src/types.rs +++ b/validation/api/src/types.rs @@ -334,6 +334,14 @@ 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, +} + #[allow(nonstandard_style)] #[derive(Clone,Debug)] pub struct ActionSubmissionSubmittedRequest{ @@ -370,6 +378,13 @@ pub struct CreateSubmissionAuditErrorRequest{ pub ErrorMessage:String, } +#[allow(nonstandard_style)] +#[derive(Clone,Debug)] +pub struct CreateSubmissionAuditCheckListRequest<'a>{ + pub SubmissionID:SubmissionID, + pub CheckList:&'a [Check], +} + #[derive(Clone,Copy,Debug,serde::Serialize,serde::Deserialize)] pub struct SubmissionID(pub(crate)i64); @@ -416,6 +431,13 @@ pub struct CreateMapfixAuditErrorRequest{ pub ErrorMessage:String, } +#[allow(nonstandard_style)] +#[derive(Clone,Debug)] +pub struct CreateMapfixAuditCheckListRequest<'a>{ + pub MapfixID:MapfixID, + pub CheckList:&'a [Check], +} + #[derive(Clone,Copy,Debug,serde::Serialize,serde::Deserialize)] pub struct MapfixID(pub(crate)i64); diff --git a/validation/src/check.rs b/validation/src/check.rs index 8c39abe..8ce6f56 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,31 @@ 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, } } } macro_rules! summary{ - ($name:literal,$summary:expr,$details:expr)=>{ - CheckSummary{ - name:$name, - summary:$summary, - passed:false, - details:serde_json::to_value($details)?, + ($name:literal,$summary:expr)=>{ + Check{ + Name:$name, + Summary:$summary, + Passed:false, } }; } macro_rules! summary_format{ - ($name:literal,$fmt:literal,$details:expr)=>{ - CheckSummary{ - name:$name, - summary:format!($fmt), - passed:false, - details:serde_json::to_value($details)?, + ($name:literal,$fmt:literal)=>{ + Check{ + Name:$name, + Summary:format!($fmt), + Passed:false, } }; } @@ -634,134 +626,134 @@ macro_rules! summary_format{ impl MapCheck<'_>{ fn itemize(&self)->Result{ let model_class=match &self.model_class{ - StringCheck(Ok(()))=>CheckSummary::passed("ModelClass"), - StringCheck(Err(context))=>summary_format!("ModelClass","Invalid model class: {context}",()), + 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(Err(context))=>summary_format!("ModelName","Model name must have snake_case: {context}",()), + 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(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(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()), }; let creator=match &self.creator{ - Ok(Ok(_))=>CheckSummary::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(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"), - Err(ParseGameIDError)=>summary!("GameID","Model name must be prefixed with bhop_ surf_ or flytrials_".to_owned(),()), + 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"), - Err(Absent)=>summary_format!("MapStart","Model has no 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()) )); - summary_format!("DuplicateStart","Duplicate start zones: {context}",()) + summary_format!("DuplicateStart","Duplicate start zones: {context}") } }; let (extra_finish,missing_finish)=match &self.mode_finish_counts{ - SetDifferenceCheck(Ok(()))=>(CheckSummary::passed("ExtraFinish"),CheckSummary::passed("MissingFinish")), + SetDifferenceCheck(Ok(()))=>(passed!("DanglingFinish"),passed!("MissingFinish")), SetDifferenceCheck(Err(context))=>( if context.extra.is_empty(){ - CheckSummary::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(){ - 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| ModeElement{zone:Zone::Finish,mode_id} )); - summary_format!("MissingFinish","Missing finish {plural}: {context}",()) + summary_format!("MissingFinish","Missing finish {plural}: {context}") } ), }; 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)| 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)=>CheckSummary::passed("Spawn1"), - Err(Absent)=>summary_format!("Spawn1","Model has no 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"}; 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{ - 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) )); - summary_format!("DuplicateSpawn","Duplicate Spawn: {context}",()) + summary_format!("DuplicateSpawn","Duplicate Spawn: {context}") } }; 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} )); - summary_format!("ExtraWormholeIn","WormholeIn with no matching WormholeOut: {context}",()) + 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 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}") } ) }; 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) )); - summary_format!("DuplicateWormholeOut","Duplicate WormholeOut: {context}",()) + summary_format!("DuplicateWormholeOut","Duplicate WormholeOut: {context}") } }; Ok(MapCheckList{checks:Box::new([ @@ -786,29 +778,17 @@ 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 MapCheckList{ + pub checks:Box<[Check;16]>, } -pub struct Summary{ - pub summary:String, - pub json:serde_json::Value, -} - -pub struct CheckReportAndVersion{ - pub status:Result, +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} @@ -842,13 +822,10 @@ 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)), }; - Ok(CheckReportAndVersion{status,version}) + Ok(CheckListAndVersion{status,version}) } } diff --git a/validation/src/check_mapfix.rs b/validation/src/check_mapfix.rs index e66b323..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,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(CheckListAndVersion{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..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,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(CheckListAndVersion{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 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}`);