feat: predictions
This commit is contained in:
parent
42e7924be9
commit
11be8f351f
42 changed files with 2221 additions and 516 deletions
|
|
@ -33,6 +33,12 @@ type Invoker interface {
|
|||
//
|
||||
// POST /api/v1/prediction
|
||||
PerformPrediction(ctx context.Context, request OptPredictionParameters, params PerformPredictionParams) (*PredictionResult, error)
|
||||
// ReadinessCheck invokes readinessCheck operation.
|
||||
//
|
||||
// Readiness check.
|
||||
//
|
||||
// GET /ready
|
||||
ReadinessCheck(ctx context.Context) (*ReadinessResponse, error)
|
||||
}
|
||||
|
||||
// Client implements OAS client.
|
||||
|
|
@ -177,3 +183,75 @@ func (c *Client) sendPerformPrediction(ctx context.Context, request OptPredictio
|
|||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ReadinessCheck invokes readinessCheck operation.
|
||||
//
|
||||
// Readiness check.
|
||||
//
|
||||
// GET /ready
|
||||
func (c *Client) ReadinessCheck(ctx context.Context) (*ReadinessResponse, error) {
|
||||
res, err := c.sendReadinessCheck(ctx)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (c *Client) sendReadinessCheck(ctx context.Context) (res *ReadinessResponse, err error) {
|
||||
otelAttrs := []attribute.KeyValue{
|
||||
otelogen.OperationID("readinessCheck"),
|
||||
semconv.HTTPRequestMethodKey.String("GET"),
|
||||
semconv.HTTPRouteKey.String("/ready"),
|
||||
}
|
||||
|
||||
// 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, ReadinessCheckOperation,
|
||||
trace.WithAttributes(otelAttrs...),
|
||||
clientSpanKind,
|
||||
)
|
||||
// Track stage for error reporting.
|
||||
var stage string
|
||||
defer func() {
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, stage)
|
||||
c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
|
||||
}
|
||||
span.End()
|
||||
}()
|
||||
|
||||
stage = "BuildURL"
|
||||
u := uri.Clone(c.requestURL(ctx))
|
||||
var pathParts [1]string
|
||||
pathParts[0] = "/ready"
|
||||
uri.AddPathParts(u, pathParts[:]...)
|
||||
|
||||
stage = "EncodeRequest"
|
||||
r, err := ht.NewRequest(ctx, "GET", u)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "create request")
|
||||
}
|
||||
|
||||
stage = "SendRequest"
|
||||
resp, err := c.cfg.Client.Do(r)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "do request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
stage = "DecodeResponse"
|
||||
result, err := decodeReadinessCheckResponse(resp)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "decode response")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,3 +193,133 @@ func (s *Server) handlePerformPredictionRequest(args [0]string, argsEscaped bool
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handleReadinessCheckRequest handles readinessCheck operation.
|
||||
//
|
||||
// Readiness check.
|
||||
//
|
||||
// GET /ready
|
||||
func (s *Server) handleReadinessCheckRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
|
||||
statusWriter := &codeRecorder{ResponseWriter: w}
|
||||
w = statusWriter
|
||||
otelAttrs := []attribute.KeyValue{
|
||||
otelogen.OperationID("readinessCheck"),
|
||||
semconv.HTTPRequestMethodKey.String("GET"),
|
||||
semconv.HTTPRouteKey.String("/ready"),
|
||||
}
|
||||
|
||||
// Start a span for this request.
|
||||
ctx, span := s.cfg.Tracer.Start(r.Context(), ReadinessCheckOperation,
|
||||
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
|
||||
)
|
||||
|
||||
var response *ReadinessResponse
|
||||
if m := s.cfg.Middleware; m != nil {
|
||||
mreq := middleware.Request{
|
||||
Context: ctx,
|
||||
OperationName: ReadinessCheckOperation,
|
||||
OperationSummary: "Readiness check",
|
||||
OperationID: "readinessCheck",
|
||||
Body: nil,
|
||||
Params: middleware.Parameters{},
|
||||
Raw: r,
|
||||
}
|
||||
|
||||
type (
|
||||
Request = struct{}
|
||||
Params = struct{}
|
||||
Response = *ReadinessResponse
|
||||
)
|
||||
response, err = middleware.HookMiddleware[
|
||||
Request,
|
||||
Params,
|
||||
Response,
|
||||
](
|
||||
m,
|
||||
mreq,
|
||||
nil,
|
||||
func(ctx context.Context, request Request, params Params) (response Response, err error) {
|
||||
response, err = s.h.ReadinessCheck(ctx)
|
||||
return response, err
|
||||
},
|
||||
)
|
||||
} else {
|
||||
response, err = s.h.ReadinessCheck(ctx)
|
||||
}
|
||||
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 := encodeReadinessCheckResponse(response, w, span); err != nil {
|
||||
defer recordError("EncodeResponse", err)
|
||||
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
|
||||
s.cfg.ErrorHandler(ctx, w, r, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1154,24 +1154,127 @@ func (s *PredictionResultPredictionItemTrajectoryItem) Encode(e *jx.Encoder) {
|
|||
|
||||
// encodeFields encodes fields.
|
||||
func (s *PredictionResultPredictionItemTrajectoryItem) encodeFields(e *jx.Encoder) {
|
||||
{
|
||||
e.FieldStart("datetime")
|
||||
json.EncodeDateTime(e, s.Datetime)
|
||||
}
|
||||
{
|
||||
e.FieldStart("latitude")
|
||||
e.Float64(s.Latitude)
|
||||
}
|
||||
{
|
||||
e.FieldStart("longitude")
|
||||
e.Float64(s.Longitude)
|
||||
}
|
||||
{
|
||||
e.FieldStart("altitude")
|
||||
e.Float64(s.Altitude)
|
||||
}
|
||||
}
|
||||
|
||||
var jsonFieldsNameOfPredictionResultPredictionItemTrajectoryItem = [0]string{}
|
||||
var jsonFieldsNameOfPredictionResultPredictionItemTrajectoryItem = [4]string{
|
||||
0: "datetime",
|
||||
1: "latitude",
|
||||
2: "longitude",
|
||||
3: "altitude",
|
||||
}
|
||||
|
||||
// Decode decodes PredictionResultPredictionItemTrajectoryItem from json.
|
||||
func (s *PredictionResultPredictionItemTrajectoryItem) Decode(d *jx.Decoder) error {
|
||||
if s == nil {
|
||||
return errors.New("invalid: unable to decode PredictionResultPredictionItemTrajectoryItem to nil")
|
||||
}
|
||||
var requiredBitSet [1]uint8
|
||||
|
||||
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
|
||||
switch string(k) {
|
||||
case "datetime":
|
||||
requiredBitSet[0] |= 1 << 0
|
||||
if err := func() error {
|
||||
v, err := json.DecodeDateTime(d)
|
||||
s.Datetime = v
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"datetime\"")
|
||||
}
|
||||
case "latitude":
|
||||
requiredBitSet[0] |= 1 << 1
|
||||
if err := func() error {
|
||||
v, err := d.Float64()
|
||||
s.Latitude = float64(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"latitude\"")
|
||||
}
|
||||
case "longitude":
|
||||
requiredBitSet[0] |= 1 << 2
|
||||
if err := func() error {
|
||||
v, err := d.Float64()
|
||||
s.Longitude = float64(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"longitude\"")
|
||||
}
|
||||
case "altitude":
|
||||
requiredBitSet[0] |= 1 << 3
|
||||
if err := func() error {
|
||||
v, err := d.Float64()
|
||||
s.Altitude = float64(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"altitude\"")
|
||||
}
|
||||
default:
|
||||
return d.Skip()
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "decode PredictionResultPredictionItemTrajectoryItem")
|
||||
}
|
||||
// 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(jsonFieldsNameOfPredictionResultPredictionItemTrajectoryItem) {
|
||||
name = jsonFieldsNameOfPredictionResultPredictionItemTrajectoryItem[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
|
||||
}
|
||||
|
|
@ -1188,3 +1291,190 @@ func (s *PredictionResultPredictionItemTrajectoryItem) UnmarshalJSON(data []byte
|
|||
d := jx.DecodeBytes(data)
|
||||
return s.Decode(d)
|
||||
}
|
||||
|
||||
// Encode implements json.Marshaler.
|
||||
func (s *ReadinessResponse) Encode(e *jx.Encoder) {
|
||||
e.ObjStart()
|
||||
s.encodeFields(e)
|
||||
e.ObjEnd()
|
||||
}
|
||||
|
||||
// encodeFields encodes fields.
|
||||
func (s *ReadinessResponse) encodeFields(e *jx.Encoder) {
|
||||
{
|
||||
e.FieldStart("status")
|
||||
s.Status.Encode(e)
|
||||
}
|
||||
{
|
||||
if s.LastUpdate.Set {
|
||||
e.FieldStart("last_update")
|
||||
s.LastUpdate.Encode(e, json.EncodeDateTime)
|
||||
}
|
||||
}
|
||||
{
|
||||
if s.IsFresh.Set {
|
||||
e.FieldStart("is_fresh")
|
||||
s.IsFresh.Encode(e)
|
||||
}
|
||||
}
|
||||
{
|
||||
if s.ErrorMessage.Set {
|
||||
e.FieldStart("error_message")
|
||||
s.ErrorMessage.Encode(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var jsonFieldsNameOfReadinessResponse = [4]string{
|
||||
0: "status",
|
||||
1: "last_update",
|
||||
2: "is_fresh",
|
||||
3: "error_message",
|
||||
}
|
||||
|
||||
// Decode decodes ReadinessResponse from json.
|
||||
func (s *ReadinessResponse) Decode(d *jx.Decoder) error {
|
||||
if s == nil {
|
||||
return errors.New("invalid: unable to decode ReadinessResponse to nil")
|
||||
}
|
||||
var requiredBitSet [1]uint8
|
||||
|
||||
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
|
||||
switch string(k) {
|
||||
case "status":
|
||||
requiredBitSet[0] |= 1 << 0
|
||||
if err := func() error {
|
||||
if err := s.Status.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"status\"")
|
||||
}
|
||||
case "last_update":
|
||||
if err := func() error {
|
||||
s.LastUpdate.Reset()
|
||||
if err := s.LastUpdate.Decode(d, json.DecodeDateTime); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"last_update\"")
|
||||
}
|
||||
case "is_fresh":
|
||||
if err := func() error {
|
||||
s.IsFresh.Reset()
|
||||
if err := s.IsFresh.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"is_fresh\"")
|
||||
}
|
||||
case "error_message":
|
||||
if err := func() error {
|
||||
s.ErrorMessage.Reset()
|
||||
if err := s.ErrorMessage.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"error_message\"")
|
||||
}
|
||||
default:
|
||||
return d.Skip()
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "decode ReadinessResponse")
|
||||
}
|
||||
// Validate required fields.
|
||||
var failures []validate.FieldError
|
||||
for i, mask := range [1]uint8{
|
||||
0b00000001,
|
||||
} {
|
||||
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
|
||||
// Mask only required fields and check equality to mask using XOR.
|
||||
//
|
||||
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
|
||||
// Bits of fields which would be set are actually bits of missed fields.
|
||||
missed := bits.OnesCount8(result)
|
||||
for bitN := 0; bitN < missed; bitN++ {
|
||||
bitIdx := bits.TrailingZeros8(result)
|
||||
fieldIdx := i*8 + bitIdx
|
||||
var name string
|
||||
if fieldIdx < len(jsonFieldsNameOfReadinessResponse) {
|
||||
name = jsonFieldsNameOfReadinessResponse[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 *ReadinessResponse) MarshalJSON() ([]byte, error) {
|
||||
e := jx.Encoder{}
|
||||
s.Encode(&e)
|
||||
return e.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements stdjson.Unmarshaler.
|
||||
func (s *ReadinessResponse) UnmarshalJSON(data []byte) error {
|
||||
d := jx.DecodeBytes(data)
|
||||
return s.Decode(d)
|
||||
}
|
||||
|
||||
// Encode encodes ReadinessResponseStatus as json.
|
||||
func (s ReadinessResponseStatus) Encode(e *jx.Encoder) {
|
||||
e.Str(string(s))
|
||||
}
|
||||
|
||||
// Decode decodes ReadinessResponseStatus from json.
|
||||
func (s *ReadinessResponseStatus) Decode(d *jx.Decoder) error {
|
||||
if s == nil {
|
||||
return errors.New("invalid: unable to decode ReadinessResponseStatus to nil")
|
||||
}
|
||||
v, err := d.StrBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Try to use constant string.
|
||||
switch ReadinessResponseStatus(v) {
|
||||
case ReadinessResponseStatusOk:
|
||||
*s = ReadinessResponseStatusOk
|
||||
case ReadinessResponseStatusNotReady:
|
||||
*s = ReadinessResponseStatusNotReady
|
||||
case ReadinessResponseStatusError:
|
||||
*s = ReadinessResponseStatusError
|
||||
default:
|
||||
*s = ReadinessResponseStatus(v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements stdjson.Marshaler.
|
||||
func (s ReadinessResponseStatus) MarshalJSON() ([]byte, error) {
|
||||
e := jx.Encoder{}
|
||||
s.Encode(&e)
|
||||
return e.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements stdjson.Unmarshaler.
|
||||
func (s *ReadinessResponseStatus) UnmarshalJSON(data []byte) error {
|
||||
d := jx.DecodeBytes(data)
|
||||
return s.Decode(d)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@ type OperationName = string
|
|||
|
||||
const (
|
||||
PerformPredictionOperation OperationName = "PerformPrediction"
|
||||
ReadinessCheckOperation OperationName = "ReadinessCheck"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -105,3 +105,95 @@ func decodePerformPredictionResponse(resp *http.Response) (res *PredictionResult
|
|||
}
|
||||
return res, errors.Wrap(defRes, "error")
|
||||
}
|
||||
|
||||
func decodeReadinessCheckResponse(resp *http.Response) (res *ReadinessResponse, _ error) {
|
||||
switch resp.StatusCode {
|
||||
case 200:
|
||||
// Code 200.
|
||||
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var response ReadinessResponse
|
||||
if err := func() error {
|
||||
if err := response.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Skip(); err != io.EOF {
|
||||
return errors.New("unexpected trailing data")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
err = &ogenerrors.DecodeBodyError{
|
||||
ContentType: ct,
|
||||
Body: buf,
|
||||
Err: err,
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
// Validate response.
|
||||
if err := func() error {
|
||||
if err := response.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return res, errors.Wrap(err, "validate")
|
||||
}
|
||||
return &response, nil
|
||||
default:
|
||||
return res, validate.InvalidContentType(ct)
|
||||
}
|
||||
}
|
||||
// Convenient error response.
|
||||
defRes, err := func() (res *ErrorStatusCode, err error) {
|
||||
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var response Error
|
||||
if err := func() error {
|
||||
if err := response.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Skip(); err != io.EOF {
|
||||
return errors.New("unexpected trailing data")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
err = &ogenerrors.DecodeBodyError{
|
||||
ContentType: ct,
|
||||
Body: buf,
|
||||
Err: err,
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
return &ErrorStatusCode{
|
||||
StatusCode: resp.StatusCode,
|
||||
Response: response,
|
||||
}, nil
|
||||
default:
|
||||
return res, validate.InvalidContentType(ct)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
|
||||
}
|
||||
return res, errors.Wrap(defRes, "error")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,20 @@ func encodePerformPredictionResponse(response *PredictionResult, w http.Response
|
|||
return nil
|
||||
}
|
||||
|
||||
func encodeReadinessCheckResponse(response *ReadinessResponse, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
span.SetStatus(codes.Ok, http.StatusText(200))
|
||||
|
||||
e := new(jx.Encoder)
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeErrorResponse(response *ErrorStatusCode, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
code := response.StatusCode
|
||||
|
|
|
|||
|
|
@ -48,24 +48,58 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
break
|
||||
}
|
||||
switch elem[0] {
|
||||
case '/': // Prefix: "/api/v1/prediction"
|
||||
case '/': // Prefix: "/"
|
||||
|
||||
if l := len("/api/v1/prediction"); len(elem) >= l && elem[0:l] == "/api/v1/prediction" {
|
||||
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
// Leaf node.
|
||||
switch r.Method {
|
||||
case "POST":
|
||||
s.handlePerformPredictionRequest([0]string{}, elemIsEscaped, w, r)
|
||||
default:
|
||||
s.notAllowed(w, r, "POST")
|
||||
break
|
||||
}
|
||||
switch elem[0] {
|
||||
case 'a': // Prefix: "api/v1/prediction"
|
||||
|
||||
if l := len("api/v1/prediction"); len(elem) >= l && elem[0:l] == "api/v1/prediction" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
// Leaf node.
|
||||
switch r.Method {
|
||||
case "POST":
|
||||
s.handlePerformPredictionRequest([0]string{}, elemIsEscaped, w, r)
|
||||
default:
|
||||
s.notAllowed(w, r, "POST")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case 'r': // Prefix: "ready"
|
||||
|
||||
if l := len("ready"); len(elem) >= l && elem[0:l] == "ready" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
// Leaf node.
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
s.handleReadinessCheckRequest([0]string{}, elemIsEscaped, w, r)
|
||||
default:
|
||||
s.notAllowed(w, r, "GET")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -148,28 +182,66 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
|
|||
break
|
||||
}
|
||||
switch elem[0] {
|
||||
case '/': // Prefix: "/api/v1/prediction"
|
||||
case '/': // Prefix: "/"
|
||||
|
||||
if l := len("/api/v1/prediction"); len(elem) >= l && elem[0:l] == "/api/v1/prediction" {
|
||||
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
// Leaf node.
|
||||
switch method {
|
||||
case "POST":
|
||||
r.name = PerformPredictionOperation
|
||||
r.summary = "Perform preidction"
|
||||
r.operationID = "performPrediction"
|
||||
r.pathPattern = "/api/v1/prediction"
|
||||
r.args = args
|
||||
r.count = 0
|
||||
return r, true
|
||||
default:
|
||||
return
|
||||
break
|
||||
}
|
||||
switch elem[0] {
|
||||
case 'a': // Prefix: "api/v1/prediction"
|
||||
|
||||
if l := len("api/v1/prediction"); len(elem) >= l && elem[0:l] == "api/v1/prediction" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
// Leaf node.
|
||||
switch method {
|
||||
case "POST":
|
||||
r.name = PerformPredictionOperation
|
||||
r.summary = "Perform preidction"
|
||||
r.operationID = "performPrediction"
|
||||
r.pathPattern = "/api/v1/prediction"
|
||||
r.args = args
|
||||
r.count = 0
|
||||
return r, true
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
case 'r': // Prefix: "ready"
|
||||
|
||||
if l := len("ready"); len(elem) >= l && elem[0:l] == "ready" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
// Leaf node.
|
||||
switch method {
|
||||
case "GET":
|
||||
r.name = ReadinessCheckOperation
|
||||
r.summary = "Readiness check"
|
||||
r.operationID = "readinessCheck"
|
||||
r.pathPattern = "/ready"
|
||||
r.args = args
|
||||
r.count = 0
|
||||
return r, true
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -764,4 +764,145 @@ func (s *PredictionResultPredictionItemStage) UnmarshalText(data []byte) error {
|
|||
}
|
||||
}
|
||||
|
||||
type PredictionResultPredictionItemTrajectoryItem struct{}
|
||||
type PredictionResultPredictionItemTrajectoryItem struct {
|
||||
Datetime time.Time `json:"datetime"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Altitude float64 `json:"altitude"`
|
||||
}
|
||||
|
||||
// GetDatetime returns the value of Datetime.
|
||||
func (s *PredictionResultPredictionItemTrajectoryItem) GetDatetime() time.Time {
|
||||
return s.Datetime
|
||||
}
|
||||
|
||||
// GetLatitude returns the value of Latitude.
|
||||
func (s *PredictionResultPredictionItemTrajectoryItem) GetLatitude() float64 {
|
||||
return s.Latitude
|
||||
}
|
||||
|
||||
// GetLongitude returns the value of Longitude.
|
||||
func (s *PredictionResultPredictionItemTrajectoryItem) GetLongitude() float64 {
|
||||
return s.Longitude
|
||||
}
|
||||
|
||||
// GetAltitude returns the value of Altitude.
|
||||
func (s *PredictionResultPredictionItemTrajectoryItem) GetAltitude() float64 {
|
||||
return s.Altitude
|
||||
}
|
||||
|
||||
// SetDatetime sets the value of Datetime.
|
||||
func (s *PredictionResultPredictionItemTrajectoryItem) SetDatetime(val time.Time) {
|
||||
s.Datetime = val
|
||||
}
|
||||
|
||||
// SetLatitude sets the value of Latitude.
|
||||
func (s *PredictionResultPredictionItemTrajectoryItem) SetLatitude(val float64) {
|
||||
s.Latitude = val
|
||||
}
|
||||
|
||||
// SetLongitude sets the value of Longitude.
|
||||
func (s *PredictionResultPredictionItemTrajectoryItem) SetLongitude(val float64) {
|
||||
s.Longitude = val
|
||||
}
|
||||
|
||||
// SetAltitude sets the value of Altitude.
|
||||
func (s *PredictionResultPredictionItemTrajectoryItem) SetAltitude(val float64) {
|
||||
s.Altitude = val
|
||||
}
|
||||
|
||||
// Ref: #/components/schemas/ReadinessResponse
|
||||
type ReadinessResponse struct {
|
||||
Status ReadinessResponseStatus `json:"status"`
|
||||
LastUpdate OptDateTime `json:"last_update"`
|
||||
IsFresh OptBool `json:"is_fresh"`
|
||||
ErrorMessage OptString `json:"error_message"`
|
||||
}
|
||||
|
||||
// GetStatus returns the value of Status.
|
||||
func (s *ReadinessResponse) GetStatus() ReadinessResponseStatus {
|
||||
return s.Status
|
||||
}
|
||||
|
||||
// GetLastUpdate returns the value of LastUpdate.
|
||||
func (s *ReadinessResponse) GetLastUpdate() OptDateTime {
|
||||
return s.LastUpdate
|
||||
}
|
||||
|
||||
// GetIsFresh returns the value of IsFresh.
|
||||
func (s *ReadinessResponse) GetIsFresh() OptBool {
|
||||
return s.IsFresh
|
||||
}
|
||||
|
||||
// GetErrorMessage returns the value of ErrorMessage.
|
||||
func (s *ReadinessResponse) GetErrorMessage() OptString {
|
||||
return s.ErrorMessage
|
||||
}
|
||||
|
||||
// SetStatus sets the value of Status.
|
||||
func (s *ReadinessResponse) SetStatus(val ReadinessResponseStatus) {
|
||||
s.Status = val
|
||||
}
|
||||
|
||||
// SetLastUpdate sets the value of LastUpdate.
|
||||
func (s *ReadinessResponse) SetLastUpdate(val OptDateTime) {
|
||||
s.LastUpdate = val
|
||||
}
|
||||
|
||||
// SetIsFresh sets the value of IsFresh.
|
||||
func (s *ReadinessResponse) SetIsFresh(val OptBool) {
|
||||
s.IsFresh = val
|
||||
}
|
||||
|
||||
// SetErrorMessage sets the value of ErrorMessage.
|
||||
func (s *ReadinessResponse) SetErrorMessage(val OptString) {
|
||||
s.ErrorMessage = val
|
||||
}
|
||||
|
||||
type ReadinessResponseStatus string
|
||||
|
||||
const (
|
||||
ReadinessResponseStatusOk ReadinessResponseStatus = "ok"
|
||||
ReadinessResponseStatusNotReady ReadinessResponseStatus = "not_ready"
|
||||
ReadinessResponseStatusError ReadinessResponseStatus = "error"
|
||||
)
|
||||
|
||||
// AllValues returns all ReadinessResponseStatus values.
|
||||
func (ReadinessResponseStatus) AllValues() []ReadinessResponseStatus {
|
||||
return []ReadinessResponseStatus{
|
||||
ReadinessResponseStatusOk,
|
||||
ReadinessResponseStatusNotReady,
|
||||
ReadinessResponseStatusError,
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (s ReadinessResponseStatus) MarshalText() ([]byte, error) {
|
||||
switch s {
|
||||
case ReadinessResponseStatusOk:
|
||||
return []byte(s), nil
|
||||
case ReadinessResponseStatusNotReady:
|
||||
return []byte(s), nil
|
||||
case ReadinessResponseStatusError:
|
||||
return []byte(s), nil
|
||||
default:
|
||||
return nil, errors.Errorf("invalid value: %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (s *ReadinessResponseStatus) UnmarshalText(data []byte) error {
|
||||
switch ReadinessResponseStatus(data) {
|
||||
case ReadinessResponseStatusOk:
|
||||
*s = ReadinessResponseStatusOk
|
||||
return nil
|
||||
case ReadinessResponseStatusNotReady:
|
||||
*s = ReadinessResponseStatusNotReady
|
||||
return nil
|
||||
case ReadinessResponseStatusError:
|
||||
*s = ReadinessResponseStatusError
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %q", data)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ type Handler interface {
|
|||
//
|
||||
// POST /api/v1/prediction
|
||||
PerformPrediction(ctx context.Context, req OptPredictionParameters, params PerformPredictionParams) (*PredictionResult, error)
|
||||
// ReadinessCheck implements readinessCheck operation.
|
||||
//
|
||||
// Readiness check.
|
||||
//
|
||||
// GET /ready
|
||||
ReadinessCheck(ctx context.Context) (*ReadinessResponse, error)
|
||||
// NewError creates *ErrorStatusCode from error returned by handler.
|
||||
//
|
||||
// Used for common default response.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,15 @@ func (UnimplementedHandler) PerformPrediction(ctx context.Context, req OptPredic
|
|||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// ReadinessCheck implements readinessCheck operation.
|
||||
//
|
||||
// Readiness check.
|
||||
//
|
||||
// GET /ready
|
||||
func (UnimplementedHandler) ReadinessCheck(ctx context.Context) (r *ReadinessResponse, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// NewError creates *ErrorStatusCode from error returned by handler.
|
||||
//
|
||||
// Used for common default response.
|
||||
|
|
|
|||
|
|
@ -269,6 +269,23 @@ func (s *PredictionResultPredictionItem) Validate() error {
|
|||
if s.Trajectory == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
var failures []validate.FieldError
|
||||
for i, elem := range s.Trajectory {
|
||||
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
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
|
|
@ -292,3 +309,84 @@ func (s PredictionResultPredictionItemStage) Validate() error {
|
|||
return errors.Errorf("invalid value: %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PredictionResultPredictionItemTrajectoryItem) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := (validate.Float{}).Validate(float64(s.Latitude)); err != nil {
|
||||
return errors.Wrap(err, "float")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "latitude",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if err := (validate.Float{}).Validate(float64(s.Longitude)); err != nil {
|
||||
return errors.Wrap(err, "float")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "longitude",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if err := (validate.Float{}).Validate(float64(s.Altitude)); err != nil {
|
||||
return errors.Wrap(err, "float")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "altitude",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ReadinessResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := s.Status.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "status",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s ReadinessResponseStatus) Validate() error {
|
||||
switch s {
|
||||
case "ok":
|
||||
return nil
|
||||
case "not_ready":
|
||||
return nil
|
||||
case "error":
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %v", s)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue