diff --git a/README.md b/README.md new file mode 100644 index 0000000..4833cc9 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# STRAFES.NET API + +## How to Begin Development + +1. Run `go generate` to ensure the generated API is up-to-date. This project uses [ogen](https://github.com/ogen-go/ogen). + ```bash + go generate -run "go run github.com/ogen-go/ogen/cmd/ogen@latest --target api --clean openapi.yaml" + ``` +2. Build the project. + ```bash + go build git.itzana.me/strafesnet/public-api + ``` + + By default, the project opens at `localhost:8080`. \ No newline at end of file diff --git a/api/oas_cfg_gen.go b/api/oas_cfg_gen.go new file mode 100644 index 0000000..fc3ff34 --- /dev/null +++ b/api/oas_cfg_gen.go @@ -0,0 +1,283 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "net/http" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" + + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/middleware" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/otelogen" +) + +var ( + // Allocate option closure once. + clientSpanKind = trace.WithSpanKind(trace.SpanKindClient) + // Allocate option closure once. + serverSpanKind = trace.WithSpanKind(trace.SpanKindServer) +) + +type ( + optionFunc[C any] func(*C) + otelOptionFunc func(*otelConfig) +) + +type otelConfig struct { + TracerProvider trace.TracerProvider + Tracer trace.Tracer + MeterProvider metric.MeterProvider + Meter metric.Meter +} + +func (cfg *otelConfig) initOTEL() { + if cfg.TracerProvider == nil { + cfg.TracerProvider = otel.GetTracerProvider() + } + if cfg.MeterProvider == nil { + cfg.MeterProvider = otel.GetMeterProvider() + } + cfg.Tracer = cfg.TracerProvider.Tracer(otelogen.Name, + trace.WithInstrumentationVersion(otelogen.SemVersion()), + ) + cfg.Meter = cfg.MeterProvider.Meter(otelogen.Name, + metric.WithInstrumentationVersion(otelogen.SemVersion()), + ) +} + +// ErrorHandler is error handler. +type ErrorHandler = ogenerrors.ErrorHandler + +type serverConfig struct { + otelConfig + NotFound http.HandlerFunc + MethodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string) + ErrorHandler ErrorHandler + Prefix string + Middleware Middleware + MaxMultipartMemory int64 +} + +// ServerOption is server config option. +type ServerOption interface { + applyServer(*serverConfig) +} + +var _ ServerOption = (optionFunc[serverConfig])(nil) + +func (o optionFunc[C]) applyServer(c *C) { + o(c) +} + +var _ ServerOption = (otelOptionFunc)(nil) + +func (o otelOptionFunc) applyServer(c *serverConfig) { + o(&c.otelConfig) +} + +func newServerConfig(opts ...ServerOption) serverConfig { + cfg := serverConfig{ + NotFound: http.NotFound, + MethodNotAllowed: func(w http.ResponseWriter, r *http.Request, allowed string) { + status := http.StatusMethodNotAllowed + if r.Method == "OPTIONS" { + w.Header().Set("Access-Control-Allow-Methods", allowed) + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + status = http.StatusNoContent + } else { + w.Header().Set("Allow", allowed) + } + w.WriteHeader(status) + }, + ErrorHandler: ogenerrors.DefaultErrorHandler, + Middleware: nil, + MaxMultipartMemory: 32 << 20, // 32 MB + } + for _, opt := range opts { + opt.applyServer(&cfg) + } + cfg.initOTEL() + return cfg +} + +type baseServer struct { + cfg serverConfig + requests metric.Int64Counter + errors metric.Int64Counter + duration metric.Float64Histogram +} + +func (s baseServer) notFound(w http.ResponseWriter, r *http.Request) { + s.cfg.NotFound(w, r) +} + +func (s baseServer) notAllowed(w http.ResponseWriter, r *http.Request, allowed string) { + s.cfg.MethodNotAllowed(w, r, allowed) +} + +func (cfg serverConfig) baseServer() (s baseServer, err error) { + s = baseServer{cfg: cfg} + if s.requests, err = otelogen.ServerRequestCountCounter(s.cfg.Meter); err != nil { + return s, err + } + if s.errors, err = otelogen.ServerErrorsCountCounter(s.cfg.Meter); err != nil { + return s, err + } + if s.duration, err = otelogen.ServerDurationHistogram(s.cfg.Meter); err != nil { + return s, err + } + return s, nil +} + +type clientConfig struct { + otelConfig + Client ht.Client +} + +// ClientOption is client config option. +type ClientOption interface { + applyClient(*clientConfig) +} + +var _ ClientOption = (optionFunc[clientConfig])(nil) + +func (o optionFunc[C]) applyClient(c *C) { + o(c) +} + +var _ ClientOption = (otelOptionFunc)(nil) + +func (o otelOptionFunc) applyClient(c *clientConfig) { + o(&c.otelConfig) +} + +func newClientConfig(opts ...ClientOption) clientConfig { + cfg := clientConfig{ + Client: http.DefaultClient, + } + for _, opt := range opts { + opt.applyClient(&cfg) + } + cfg.initOTEL() + return cfg +} + +type baseClient struct { + cfg clientConfig + requests metric.Int64Counter + errors metric.Int64Counter + duration metric.Float64Histogram +} + +func (cfg clientConfig) baseClient() (c baseClient, err error) { + c = baseClient{cfg: cfg} + if c.requests, err = otelogen.ClientRequestCountCounter(c.cfg.Meter); err != nil { + return c, err + } + if c.errors, err = otelogen.ClientErrorsCountCounter(c.cfg.Meter); err != nil { + return c, err + } + if c.duration, err = otelogen.ClientDurationHistogram(c.cfg.Meter); err != nil { + return c, err + } + return c, nil +} + +// Option is config option. +type Option interface { + ServerOption + ClientOption +} + +// WithTracerProvider specifies a tracer provider to use for creating a tracer. +// +// If none is specified, the global provider is used. +func WithTracerProvider(provider trace.TracerProvider) Option { + return otelOptionFunc(func(cfg *otelConfig) { + if provider != nil { + cfg.TracerProvider = provider + } + }) +} + +// WithMeterProvider specifies a meter provider to use for creating a meter. +// +// If none is specified, the otel.GetMeterProvider() is used. +func WithMeterProvider(provider metric.MeterProvider) Option { + return otelOptionFunc(func(cfg *otelConfig) { + if provider != nil { + cfg.MeterProvider = provider + } + }) +} + +// WithClient specifies http client to use. +func WithClient(client ht.Client) ClientOption { + return optionFunc[clientConfig](func(cfg *clientConfig) { + if client != nil { + cfg.Client = client + } + }) +} + +// WithNotFound specifies Not Found handler to use. +func WithNotFound(notFound http.HandlerFunc) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if notFound != nil { + cfg.NotFound = notFound + } + }) +} + +// WithMethodNotAllowed specifies Method Not Allowed handler to use. +func WithMethodNotAllowed(methodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string)) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if methodNotAllowed != nil { + cfg.MethodNotAllowed = methodNotAllowed + } + }) +} + +// WithErrorHandler specifies error handler to use. +func WithErrorHandler(h ErrorHandler) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if h != nil { + cfg.ErrorHandler = h + } + }) +} + +// WithPathPrefix specifies server path prefix. +func WithPathPrefix(prefix string) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + cfg.Prefix = prefix + }) +} + +// WithMiddleware specifies middlewares to use. +func WithMiddleware(m ...Middleware) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + switch len(m) { + case 0: + cfg.Middleware = nil + case 1: + cfg.Middleware = m[0] + default: + cfg.Middleware = middleware.ChainMiddlewares(m...) + } + }) +} + +// WithMaxMultipartMemory specifies limit of memory for storing file parts. +// File parts which can't be stored in memory will be stored on disk in temporary files. +func WithMaxMultipartMemory(max int64) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if max > 0 { + cfg.MaxMultipartMemory = max + } + }) +} diff --git a/api/oas_client_gen.go b/api/oas_client_gen.go new file mode 100644 index 0000000..5f284cb --- /dev/null +++ b/api/oas_client_gen.go @@ -0,0 +1,543 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "context" + "net/url" + "strings" + "time" + + "github.com/go-faster/errors" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + semconv "go.opentelemetry.io/otel/semconv/v1.19.0" + "go.opentelemetry.io/otel/trace" + + "github.com/ogen-go/ogen/conv" + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/otelogen" + "github.com/ogen-go/ogen/uri" +) + +// Invoker invokes operations described by OpenAPI v3 specification. +type Invoker interface { + // GetUser invokes getUser operation. + // + // Retrieve user with ID. + // + // GET /users/{UserID} + GetUser(ctx context.Context, params GetUserParams) (*User, error) + // GetUserRank invokes getUserRank operation. + // + // Retrieve rank of user. + // + // GET /users/{UserID}/rank + GetUserRank(ctx context.Context, params GetUserRankParams) (*Rank, error) + // ListRanks invokes listRanks operation. + // + // Get list of ranks. + // + // GET /ranks + ListRanks(ctx context.Context, params ListRanksParams) ([]Rank, error) + // ListTimes invokes listTimes operation. + // + // Get list of times. + // + // GET /times + ListTimes(ctx context.Context, params ListTimesParams) ([]Time, error) +} + +// Client implements OAS client. +type Client struct { + serverURL *url.URL + baseClient +} +type errorHandler interface { + NewError(ctx context.Context, err error) *ErrorStatusCode +} + +var _ Handler = struct { + errorHandler + *Client +}{} + +func trimTrailingSlashes(u *url.URL) { + u.Path = strings.TrimRight(u.Path, "/") + u.RawPath = strings.TrimRight(u.RawPath, "/") +} + +// NewClient initializes new Client defined by OAS. +func NewClient(serverURL string, opts ...ClientOption) (*Client, error) { + u, err := url.Parse(serverURL) + if err != nil { + return nil, err + } + trimTrailingSlashes(u) + + c, err := newClientConfig(opts...).baseClient() + if err != nil { + return nil, err + } + return &Client{ + serverURL: u, + baseClient: c, + }, nil +} + +type serverURLKey struct{} + +// WithServerURL sets context key to override server URL. +func WithServerURL(ctx context.Context, u *url.URL) context.Context { + return context.WithValue(ctx, serverURLKey{}, u) +} + +func (c *Client) requestURL(ctx context.Context) *url.URL { + u, ok := ctx.Value(serverURLKey{}).(*url.URL) + if !ok { + return c.serverURL + } + return u +} + +// GetUser invokes getUser operation. +// +// Retrieve user with ID. +// +// GET /users/{UserID} +func (c *Client) GetUser(ctx context.Context, params GetUserParams) (*User, error) { + res, err := c.sendGetUser(ctx, params) + return res, err +} + +func (c *Client) sendGetUser(ctx context.Context, params GetUserParams) (res *User, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getUser"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/users/{UserID}"), + } + + // 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(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, "GetUser", + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [2]string + pathParts[0] = "/users/" + { + // Encode "UserID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "UserID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.UserID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + 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 := decodeGetUserResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// GetUserRank invokes getUserRank operation. +// +// Retrieve rank of user. +// +// GET /users/{UserID}/rank +func (c *Client) GetUserRank(ctx context.Context, params GetUserRankParams) (*Rank, error) { + res, err := c.sendGetUserRank(ctx, params) + return res, err +} + +func (c *Client) sendGetUserRank(ctx context.Context, params GetUserRankParams) (res *Rank, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getUserRank"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/users/{UserID}/rank"), + } + + // 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(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, "GetUserRank", + 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] = "/users/" + { + // Encode "UserID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "UserID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.UserID)) + }(); 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] = "/rank" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "StyleID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "StyleID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(params.StyleID)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "GameID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "GameID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(params.GameID)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "ModeID" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "ModeID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(params.ModeID)) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + 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 := decodeGetUserRankResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// ListRanks invokes listRanks operation. +// +// Get list of ranks. +// +// GET /ranks +func (c *Client) ListRanks(ctx context.Context, params ListRanksParams) ([]Rank, error) { + res, err := c.sendListRanks(ctx, params) + return res, err +} + +func (c *Client) sendListRanks(ctx context.Context, params ListRanksParams) (res []Rank, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listRanks"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/ranks"), + } + + // 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(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, "ListRanks", + 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] = "/ranks" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "page" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "page", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return params.Page.EncodeURI(e) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "filter" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "filter", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.Filter.Get(); ok { + return val.EncodeURI(e) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + 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 := decodeListRanksResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// ListTimes invokes listTimes operation. +// +// Get list of times. +// +// GET /times +func (c *Client) ListTimes(ctx context.Context, params ListTimesParams) ([]Time, error) { + res, err := c.sendListTimes(ctx, params) + return res, err +} + +func (c *Client) sendListTimes(ctx context.Context, params ListTimesParams) (res []Time, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listTimes"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/times"), + } + + // 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(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, "ListTimes", + 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] = "/times" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "page" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "page", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + return params.Page.EncodeURI(e) + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + { + // Encode "filter" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "filter", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.Filter.Get(); ok { + return val.EncodeURI(e) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + 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 := decodeListTimesResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} diff --git a/api/oas_handlers_gen.go b/api/oas_handlers_gen.go new file mode 100644 index 0000000..3b274bf --- /dev/null +++ b/api/oas_handlers_gen.go @@ -0,0 +1,533 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "context" + "net/http" + "time" + + "github.com/go-faster/errors" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + semconv "go.opentelemetry.io/otel/semconv/v1.19.0" + "go.opentelemetry.io/otel/trace" + + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/middleware" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/otelogen" +) + +// handleGetUserRequest handles getUser operation. +// +// Retrieve user with ID. +// +// GET /users/{UserID} +func (s *Server) handleGetUserRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getUser"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/users/{UserID}"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), "GetUser", + 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) + attrOpt := metric.WithAttributeSet(labeler.AttributeSet()) + + // 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(float64(elapsedDuration)/float64(time.Millisecond)), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + s.errors.Add(ctx, 1, metric.WithAttributeSet(labeler.AttributeSet())) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: "GetUser", + ID: "getUser", + } + ) + params, err := decodeGetUserParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *User + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: "GetUser", + OperationSummary: "Retrieve user with ID", + OperationID: "getUser", + Body: nil, + Params: middleware.Parameters{ + { + Name: "UserID", + In: "path", + }: params.UserID, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = GetUserParams + Response = *User + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackGetUserParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetUser(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.GetUser(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeGetUserResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleGetUserRankRequest handles getUserRank operation. +// +// Retrieve rank of user. +// +// GET /users/{UserID}/rank +func (s *Server) handleGetUserRankRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getUserRank"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/users/{UserID}/rank"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), "GetUserRank", + 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) + attrOpt := metric.WithAttributeSet(labeler.AttributeSet()) + + // 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(float64(elapsedDuration)/float64(time.Millisecond)), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + s.errors.Add(ctx, 1, metric.WithAttributeSet(labeler.AttributeSet())) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: "GetUserRank", + ID: "getUserRank", + } + ) + params, err := decodeGetUserRankParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *Rank + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: "GetUserRank", + OperationSummary: "Retrieve rank of user", + OperationID: "getUserRank", + Body: nil, + Params: middleware.Parameters{ + { + Name: "UserID", + In: "path", + }: params.UserID, + { + Name: "StyleID", + In: "query", + }: params.StyleID, + { + Name: "GameID", + In: "query", + }: params.GameID, + { + Name: "ModeID", + In: "query", + }: params.ModeID, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = GetUserRankParams + Response = *Rank + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackGetUserRankParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetUserRank(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.GetUserRank(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeGetUserRankResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleListRanksRequest handles listRanks operation. +// +// Get list of ranks. +// +// GET /ranks +func (s *Server) handleListRanksRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listRanks"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/ranks"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), "ListRanks", + 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) + attrOpt := metric.WithAttributeSet(labeler.AttributeSet()) + + // 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(float64(elapsedDuration)/float64(time.Millisecond)), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + s.errors.Add(ctx, 1, metric.WithAttributeSet(labeler.AttributeSet())) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: "ListRanks", + ID: "listRanks", + } + ) + params, err := decodeListRanksParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response []Rank + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: "ListRanks", + OperationSummary: "Get list of ranks", + OperationID: "listRanks", + Body: nil, + Params: middleware.Parameters{ + { + Name: "page", + In: "query", + }: params.Page, + { + Name: "filter", + In: "query", + }: params.Filter, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ListRanksParams + Response = []Rank + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackListRanksParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.ListRanks(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.ListRanks(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeListRanksResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleListTimesRequest handles listTimes operation. +// +// Get list of times. +// +// GET /times +func (s *Server) handleListTimesRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listTimes"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/times"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), "ListTimes", + 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) + attrOpt := metric.WithAttributeSet(labeler.AttributeSet()) + + // 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(float64(elapsedDuration)/float64(time.Millisecond)), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + s.errors.Add(ctx, 1, metric.WithAttributeSet(labeler.AttributeSet())) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: "ListTimes", + ID: "listTimes", + } + ) + params, err := decodeListTimesParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response []Time + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: "ListTimes", + OperationSummary: "Get list of times", + OperationID: "listTimes", + Body: nil, + Params: middleware.Parameters{ + { + Name: "page", + In: "query", + }: params.Page, + { + Name: "filter", + In: "query", + }: params.Filter, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = ListTimesParams + Response = []Time + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackListTimesParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.ListTimes(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.ListTimes(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeListTimesResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} diff --git a/api/oas_json_gen.go b/api/oas_json_gen.go new file mode 100644 index 0000000..01fd2fb --- /dev/null +++ b/api/oas_json_gen.go @@ -0,0 +1,924 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "math/bits" + "strconv" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + + "github.com/ogen-go/ogen/validate" +) + +// Encode implements json.Marshaler. +func (s *Error) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Error) encodeFields(e *jx.Encoder) { + { + e.FieldStart("code") + e.Int64(s.Code) + } + { + e.FieldStart("message") + e.Str(s.Message) + } +} + +var jsonFieldsNameOfError = [2]string{ + 0: "code", + 1: "message", +} + +// Decode decodes Error from json. +func (s *Error) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Error to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "code": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.Code = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"code\"") + } + case "message": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Str() + s.Message = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"message\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Error") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + 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(jsonFieldsNameOfError) { + name = jsonFieldsNameOfError[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 *Error) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Error) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *Map) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Map) encodeFields(e *jx.Encoder) { + { + if s.ID.Set { + e.FieldStart("ID") + s.ID.Encode(e) + } + } + { + if s.DisplayName.Set { + e.FieldStart("DisplayName") + s.DisplayName.Encode(e) + } + } + { + if s.Creator.Set { + e.FieldStart("Creator") + s.Creator.Encode(e) + } + } + { + if s.GameID.Set { + e.FieldStart("GameID") + s.GameID.Encode(e) + } + } + { + if s.Date.Set { + e.FieldStart("Date") + s.Date.Encode(e) + } + } +} + +var jsonFieldsNameOfMap = [5]string{ + 0: "ID", + 1: "DisplayName", + 2: "Creator", + 3: "GameID", + 4: "Date", +} + +// Decode decodes Map from json. +func (s *Map) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Map to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "ID": + if err := func() error { + s.ID.Reset() + if err := s.ID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ID\"") + } + case "DisplayName": + if err := func() error { + s.DisplayName.Reset() + if err := s.DisplayName.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"DisplayName\"") + } + case "Creator": + if err := func() error { + s.Creator.Reset() + if err := s.Creator.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Creator\"") + } + case "GameID": + if err := func() error { + s.GameID.Reset() + if err := s.GameID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"GameID\"") + } + case "Date": + if err := func() error { + s.Date.Reset() + if err := s.Date.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Date\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Map") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *Map) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Map) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes float64 as json. +func (o OptFloat64) Encode(e *jx.Encoder) { + if !o.Set { + return + } + e.Float64(float64(o.Value)) +} + +// Decode decodes float64 from json. +func (o *OptFloat64) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptFloat64 to nil") + } + o.Set = true + v, err := d.Float64() + if err != nil { + return err + } + o.Value = float64(v) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptFloat64) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptFloat64) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes int32 as json. +func (o OptInt32) Encode(e *jx.Encoder) { + if !o.Set { + return + } + e.Int32(int32(o.Value)) +} + +// Decode decodes int32 from json. +func (o *OptInt32) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptInt32 to nil") + } + o.Set = true + v, err := d.Int32() + if err != nil { + return err + } + o.Value = int32(v) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptInt32) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptInt32) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes int64 as json. +func (o OptInt64) Encode(e *jx.Encoder) { + if !o.Set { + return + } + e.Int64(int64(o.Value)) +} + +// Decode decodes int64 from json. +func (o *OptInt64) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptInt64 to nil") + } + o.Set = true + v, err := d.Int64() + if err != nil { + return err + } + o.Value = int64(v) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptInt64) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptInt64) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes Map as json. +func (o OptMap) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes Map from json. +func (o *OptMap) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptMap to nil") + } + o.Set = true + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptMap) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptMap) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes string as json. +func (o OptString) Encode(e *jx.Encoder) { + if !o.Set { + return + } + e.Str(string(o.Value)) +} + +// Decode decodes string from json. +func (o *OptString) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptString to nil") + } + o.Set = true + v, err := d.Str() + if err != nil { + return err + } + o.Value = string(v) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptString) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptString) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes User as json. +func (o OptUser) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes User from json. +func (o *OptUser) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptUser to nil") + } + o.Set = true + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptUser) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptUser) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *Rank) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Rank) encodeFields(e *jx.Encoder) { + { + if s.ID.Set { + e.FieldStart("ID") + s.ID.Encode(e) + } + } + { + if s.User.Set { + e.FieldStart("User") + s.User.Encode(e) + } + } + { + if s.StyleID.Set { + e.FieldStart("StyleID") + s.StyleID.Encode(e) + } + } + { + if s.ModeID.Set { + e.FieldStart("ModeID") + s.ModeID.Encode(e) + } + } + { + if s.GameID.Set { + e.FieldStart("GameID") + s.GameID.Encode(e) + } + } + { + if s.Rank.Set { + e.FieldStart("Rank") + s.Rank.Encode(e) + } + } + { + if s.Skill.Set { + e.FieldStart("Skill") + s.Skill.Encode(e) + } + } + { + if s.UpdatedAt.Set { + e.FieldStart("UpdatedAt") + s.UpdatedAt.Encode(e) + } + } +} + +var jsonFieldsNameOfRank = [8]string{ + 0: "ID", + 1: "User", + 2: "StyleID", + 3: "ModeID", + 4: "GameID", + 5: "Rank", + 6: "Skill", + 7: "UpdatedAt", +} + +// Decode decodes Rank from json. +func (s *Rank) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Rank to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "ID": + if err := func() error { + s.ID.Reset() + if err := s.ID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ID\"") + } + case "User": + if err := func() error { + s.User.Reset() + if err := s.User.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"User\"") + } + case "StyleID": + if err := func() error { + s.StyleID.Reset() + if err := s.StyleID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"StyleID\"") + } + case "ModeID": + if err := func() error { + s.ModeID.Reset() + if err := s.ModeID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ModeID\"") + } + case "GameID": + if err := func() error { + s.GameID.Reset() + if err := s.GameID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"GameID\"") + } + case "Rank": + if err := func() error { + s.Rank.Reset() + if err := s.Rank.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Rank\"") + } + case "Skill": + if err := func() error { + s.Skill.Reset() + if err := s.Skill.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Skill\"") + } + case "UpdatedAt": + if err := func() error { + s.UpdatedAt.Reset() + if err := s.UpdatedAt.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"UpdatedAt\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Rank") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *Rank) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Rank) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *Time) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Time) encodeFields(e *jx.Encoder) { + { + if s.ID.Set { + e.FieldStart("ID") + s.ID.Encode(e) + } + } + { + if s.Time.Set { + e.FieldStart("Time") + s.Time.Encode(e) + } + } + { + if s.User.Set { + e.FieldStart("User") + s.User.Encode(e) + } + } + { + if s.Map.Set { + e.FieldStart("Map") + s.Map.Encode(e) + } + } + { + if s.Date.Set { + e.FieldStart("Date") + s.Date.Encode(e) + } + } + { + if s.StyleID.Set { + e.FieldStart("StyleID") + s.StyleID.Encode(e) + } + } + { + if s.ModeID.Set { + e.FieldStart("ModeID") + s.ModeID.Encode(e) + } + } + { + if s.GameID.Set { + e.FieldStart("GameID") + s.GameID.Encode(e) + } + } +} + +var jsonFieldsNameOfTime = [8]string{ + 0: "ID", + 1: "Time", + 2: "User", + 3: "Map", + 4: "Date", + 5: "StyleID", + 6: "ModeID", + 7: "GameID", +} + +// Decode decodes Time from json. +func (s *Time) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Time to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "ID": + if err := func() error { + s.ID.Reset() + if err := s.ID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ID\"") + } + case "Time": + if err := func() error { + s.Time.Reset() + if err := s.Time.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Time\"") + } + case "User": + if err := func() error { + s.User.Reset() + if err := s.User.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"User\"") + } + case "Map": + if err := func() error { + s.Map.Reset() + if err := s.Map.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Map\"") + } + case "Date": + if err := func() error { + s.Date.Reset() + if err := s.Date.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Date\"") + } + case "StyleID": + if err := func() error { + s.StyleID.Reset() + if err := s.StyleID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"StyleID\"") + } + case "ModeID": + if err := func() error { + s.ModeID.Reset() + if err := s.ModeID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ModeID\"") + } + case "GameID": + if err := func() error { + s.GameID.Reset() + if err := s.GameID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"GameID\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Time") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *Time) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Time) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *User) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *User) encodeFields(e *jx.Encoder) { + { + if s.ID.Set { + e.FieldStart("ID") + s.ID.Encode(e) + } + } + { + if s.Username.Set { + e.FieldStart("Username") + s.Username.Encode(e) + } + } + { + if s.StateID.Set { + e.FieldStart("StateID") + s.StateID.Encode(e) + } + } +} + +var jsonFieldsNameOfUser = [3]string{ + 0: "ID", + 1: "Username", + 2: "StateID", +} + +// Decode decodes User from json. +func (s *User) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode User to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "ID": + if err := func() error { + s.ID.Reset() + if err := s.ID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ID\"") + } + case "Username": + if err := func() error { + s.Username.Reset() + if err := s.Username.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Username\"") + } + case "StateID": + if err := func() error { + s.StateID.Reset() + if err := s.StateID.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"StateID\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode User") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *User) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *User) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} diff --git a/api/oas_labeler_gen.go b/api/oas_labeler_gen.go new file mode 100644 index 0000000..7e519e8 --- /dev/null +++ b/api/oas_labeler_gen.go @@ -0,0 +1,42 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" +) + +// Labeler is used to allow adding custom attributes to the server request metrics. +type Labeler struct { + attrs []attribute.KeyValue +} + +// Add attributes to the Labeler. +func (l *Labeler) Add(attrs ...attribute.KeyValue) { + l.attrs = append(l.attrs, attrs...) +} + +// AttributeSet returns the attributes added to the Labeler as an attribute.Set. +func (l *Labeler) AttributeSet() attribute.Set { + return attribute.NewSet(l.attrs...) +} + +type labelerContextKey struct{} + +// LabelerFromContext retrieves the Labeler from the provided context, if present. +// +// If no Labeler was found in the provided context a new, empty Labeler is returned and the second +// return value is false. In this case it is safe to use the Labeler but any attributes added to +// it will not be used. +func LabelerFromContext(ctx context.Context) (*Labeler, bool) { + if l, ok := ctx.Value(labelerContextKey{}).(*Labeler); ok { + return l, true + } + return &Labeler{}, false +} + +func contextWithLabeler(ctx context.Context, l *Labeler) context.Context { + return context.WithValue(ctx, labelerContextKey{}, l) +} diff --git a/api/oas_middleware_gen.go b/api/oas_middleware_gen.go new file mode 100644 index 0000000..6f58a1a --- /dev/null +++ b/api/oas_middleware_gen.go @@ -0,0 +1,10 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "github.com/ogen-go/ogen/middleware" +) + +// Middleware is middleware type. +type Middleware = middleware.Middleware diff --git a/api/oas_parameters_gen.go b/api/oas_parameters_gen.go new file mode 100644 index 0000000..6783bfe --- /dev/null +++ b/api/oas_parameters_gen.go @@ -0,0 +1,471 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "net/http" + "net/url" + + "github.com/go-faster/errors" + + "github.com/ogen-go/ogen/conv" + "github.com/ogen-go/ogen/middleware" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/uri" + "github.com/ogen-go/ogen/validate" +) + +// GetUserParams is parameters of getUser operation. +type GetUserParams struct { + UserID int64 +} + +func unpackGetUserParams(packed middleware.Parameters) (params GetUserParams) { + { + key := middleware.ParameterKey{ + Name: "UserID", + In: "path", + } + params.UserID = packed[key].(int64) + } + return params +} + +func decodeGetUserParams(args [1]string, argsEscaped bool, r *http.Request) (params GetUserParams, _ error) { + // Decode path: UserID. + 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: "UserID", + 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.UserID = c + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "UserID", + In: "path", + Err: err, + } + } + return params, nil +} + +// GetUserRankParams is parameters of getUserRank operation. +type GetUserRankParams struct { + UserID int64 + StyleID int32 + GameID int32 + ModeID int32 +} + +func unpackGetUserRankParams(packed middleware.Parameters) (params GetUserRankParams) { + { + key := middleware.ParameterKey{ + Name: "UserID", + In: "path", + } + params.UserID = packed[key].(int64) + } + { + key := middleware.ParameterKey{ + Name: "StyleID", + In: "query", + } + params.StyleID = packed[key].(int32) + } + { + key := middleware.ParameterKey{ + Name: "GameID", + In: "query", + } + params.GameID = packed[key].(int32) + } + { + key := middleware.ParameterKey{ + Name: "ModeID", + In: "query", + } + params.ModeID = packed[key].(int32) + } + return params +} + +func decodeGetUserRankParams(args [1]string, argsEscaped bool, r *http.Request) (params GetUserRankParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + // Decode path: UserID. + 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: "UserID", + 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.UserID = c + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "UserID", + In: "path", + Err: err, + } + } + // Decode query: StyleID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "StyleID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.StyleID = c + return nil + }); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "StyleID", + In: "query", + Err: err, + } + } + // Decode query: GameID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "GameID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.GameID = c + return nil + }); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "GameID", + In: "query", + Err: err, + } + } + // Decode query: ModeID. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "ModeID", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + params.ModeID = c + return nil + }); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "ModeID", + In: "query", + Err: err, + } + } + return params, nil +} + +// ListRanksParams is parameters of listRanks operation. +type ListRanksParams struct { + Page Pagination + Filter OptRankFilter +} + +func unpackListRanksParams(packed middleware.Parameters) (params ListRanksParams) { + { + key := middleware.ParameterKey{ + Name: "page", + In: "query", + } + params.Page = packed[key].(Pagination) + } + { + key := middleware.ParameterKey{ + Name: "filter", + In: "query", + } + if v, ok := packed[key]; ok { + params.Filter = v.(OptRankFilter) + } + } + return params +} + +func decodeListRanksParams(args [0]string, argsEscaped bool, r *http.Request) (params ListRanksParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + // Decode query: page. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "page", + Style: uri.QueryStyleForm, + Explode: true, + Fields: []uri.QueryParameterObjectField{{Name: "Page", Required: true}, {Name: "Limit", Required: true}}, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + return params.Page.DecodeURI(d) + }); err != nil { + return err + } + if err := func() error { + if err := params.Page.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "page", + In: "query", + Err: err, + } + } + // Decode query: filter. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "filter", + Style: uri.QueryStyleForm, + Explode: true, + Fields: []uri.QueryParameterObjectField{{Name: "StyleID", Required: false}, {Name: "GameID", Required: false}, {Name: "ModeID", Required: false}, {Name: "Sort", Required: false}}, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotFilterVal RankFilter + if err := func() error { + return paramsDotFilterVal.DecodeURI(d) + }(); err != nil { + return err + } + params.Filter.SetTo(paramsDotFilterVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "filter", + In: "query", + Err: err, + } + } + return params, nil +} + +// ListTimesParams is parameters of listTimes operation. +type ListTimesParams struct { + Page Pagination + Filter OptTimeFilter +} + +func unpackListTimesParams(packed middleware.Parameters) (params ListTimesParams) { + { + key := middleware.ParameterKey{ + Name: "page", + In: "query", + } + params.Page = packed[key].(Pagination) + } + { + key := middleware.ParameterKey{ + Name: "filter", + In: "query", + } + if v, ok := packed[key]; ok { + params.Filter = v.(OptTimeFilter) + } + } + return params +} + +func decodeListTimesParams(args [0]string, argsEscaped bool, r *http.Request) (params ListTimesParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + // Decode query: page. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "page", + Style: uri.QueryStyleForm, + Explode: true, + Fields: []uri.QueryParameterObjectField{{Name: "Page", Required: true}, {Name: "Limit", Required: true}}, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + return params.Page.DecodeURI(d) + }); err != nil { + return err + } + if err := func() error { + if err := params.Page.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "page", + In: "query", + Err: err, + } + } + // Decode query: filter. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "filter", + Style: uri.QueryStyleForm, + Explode: true, + Fields: []uri.QueryParameterObjectField{{Name: "ID", Required: false}, {Name: "Time", Required: false}, {Name: "UserID", Required: false}, {Name: "MapID", Required: false}, {Name: "StyleID", Required: false}, {Name: "ModeID", Required: false}, {Name: "GameID", Required: false}}, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotFilterVal TimeFilter + if err := func() error { + return paramsDotFilterVal.DecodeURI(d) + }(); err != nil { + return err + } + params.Filter.SetTo(paramsDotFilterVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "filter", + In: "query", + Err: err, + } + } + return params, nil +} diff --git a/api/oas_request_decoders_gen.go b/api/oas_request_decoders_gen.go new file mode 100644 index 0000000..ae379a2 --- /dev/null +++ b/api/oas_request_decoders_gen.go @@ -0,0 +1,3 @@ +// Code generated by ogen, DO NOT EDIT. + +package api diff --git a/api/oas_request_encoders_gen.go b/api/oas_request_encoders_gen.go new file mode 100644 index 0000000..ae379a2 --- /dev/null +++ b/api/oas_request_encoders_gen.go @@ -0,0 +1,3 @@ +// Code generated by ogen, DO NOT EDIT. + +package api diff --git a/api/oas_response_decoders_gen.go b/api/oas_response_decoders_gen.go new file mode 100644 index 0000000..6046004 --- /dev/null +++ b/api/oas_response_decoders_gen.go @@ -0,0 +1,408 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "fmt" + "io" + "mime" + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/validate" +) + +func decodeGetUserResponse(resp *http.Response) (res *User, _ 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 User + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeGetUserRankResponse(resp *http.Response) (res *Rank, _ 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 Rank + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeListRanksResponse(resp *http.Response) (res []Rank, _ 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 []Rank + if err := func() error { + response = make([]Rank, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem Rank + if err := elem.Decode(d); err != nil { + return err + } + response = append(response, elem) + return nil + }); 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 response == nil { + return errors.New("nil is invalid value") + } + var failures []validate.FieldError + for i, elem := range response { + 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 { + return res, errors.Wrap(err, "validate") + } + return response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeListTimesResponse(resp *http.Response) (res []Time, _ 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 []Time + if err := func() error { + response = make([]Time, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem Time + if err := elem.Decode(d); err != nil { + return err + } + response = append(response, elem) + return nil + }); 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 response == nil { + return errors.New("nil is invalid value") + } + 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") +} diff --git a/api/oas_response_encoders_gen.go b/api/oas_response_encoders_gen.go new file mode 100644 index 0000000..fc6d3ad --- /dev/null +++ b/api/oas_response_encoders_gen.go @@ -0,0 +1,105 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + + ht "github.com/ogen-go/ogen/http" +) + +func encodeGetUserResponse(response *User, 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 encodeGetUserRankResponse(response *Rank, 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 encodeListRanksResponse(response []Rank, 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) + e.ArrStart() + for _, elem := range response { + elem.Encode(e) + } + e.ArrEnd() + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + +func encodeListTimesResponse(response []Time, 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) + e.ArrStart() + for _, elem := range response { + elem.Encode(e) + } + e.ArrEnd() + 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 + if code == 0 { + // Set default status code. + code = http.StatusOK + } + w.WriteHeader(code) + if st := http.StatusText(code); code >= http.StatusBadRequest { + span.SetStatus(codes.Error, st) + } else { + span.SetStatus(codes.Ok, st) + } + + e := new(jx.Encoder) + response.Response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + if code >= http.StatusInternalServerError { + return errors.Wrapf(ht.ErrInternalServerErrorResponse, "code: %d, message: %s", code, http.StatusText(code)) + } + return nil + +} diff --git a/api/oas_router_gen.go b/api/oas_router_gen.go new file mode 100644 index 0000000..a427d35 --- /dev/null +++ b/api/oas_router_gen.go @@ -0,0 +1,373 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "net/http" + "net/url" + "strings" + + "github.com/ogen-go/ogen/uri" +) + +func (s *Server) cutPrefix(path string) (string, bool) { + prefix := s.cfg.Prefix + if prefix == "" { + return path, true + } + if !strings.HasPrefix(path, prefix) { + // Prefix doesn't match. + return "", false + } + // Cut prefix from the path. + return strings.TrimPrefix(path, prefix), true +} + +// ServeHTTP serves http request as defined by OpenAPI v3 specification, +// calling handler that matches the path or returning not found error. +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + elem := r.URL.Path + elemIsEscaped := false + if rawPath := r.URL.RawPath; rawPath != "" { + if normalized, ok := uri.NormalizeEscapedPath(rawPath); ok { + elem = normalized + elemIsEscaped = strings.ContainsRune(elem, '%') + } + } + + elem, ok := s.cutPrefix(elem) + if !ok || len(elem) == 0 { + s.notFound(w, r) + return + } + args := [1]string{} + + // Static code generated router with unwrapped path search. + switch { + default: + if len(elem) == 0 { + break + } + switch elem[0] { + case '/': // Prefix: "/" + origElem := elem + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'r': // Prefix: "ranks" + origElem := elem + if l := len("ranks"); len(elem) >= l && elem[0:l] == "ranks" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleListRanksRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + + elem = origElem + case 't': // Prefix: "times" + origElem := elem + if l := len("times"); len(elem) >= l && elem[0:l] == "times" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleListTimesRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + + elem = origElem + case 'u': // Prefix: "users/" + origElem := elem + if l := len("users/"); len(elem) >= l && elem[0:l] == "users/" { + elem = elem[l:] + } else { + break + } + + // Param: "UserID" + // Match until "/" + idx := strings.IndexByte(elem, '/') + if idx < 0 { + idx = len(elem) + } + args[0] = elem[:idx] + elem = elem[idx:] + + if len(elem) == 0 { + switch r.Method { + case "GET": + s.handleGetUserRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + switch elem[0] { + case '/': // Prefix: "/rank" + origElem := elem + if l := len("/rank"); len(elem) >= l && elem[0:l] == "/rank" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleGetUserRankRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + + elem = origElem + } + + elem = origElem + } + + elem = origElem + } + } + s.notFound(w, r) +} + +// Route is route object. +type Route struct { + name string + summary string + operationID string + pathPattern string + count int + args [1]string +} + +// Name returns ogen operation name. +// +// It is guaranteed to be unique and not empty. +func (r Route) Name() string { + return r.name +} + +// Summary returns OpenAPI summary. +func (r Route) Summary() string { + return r.summary +} + +// OperationID returns OpenAPI operationId. +func (r Route) OperationID() string { + return r.operationID +} + +// PathPattern returns OpenAPI path. +func (r Route) PathPattern() string { + return r.pathPattern +} + +// Args returns parsed arguments. +func (r Route) Args() []string { + return r.args[:r.count] +} + +// FindRoute finds Route for given method and path. +// +// Note: this method does not unescape path or handle reserved characters in path properly. Use FindPath instead. +func (s *Server) FindRoute(method, path string) (Route, bool) { + return s.FindPath(method, &url.URL{Path: path}) +} + +// FindPath finds Route for given method and URL. +func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { + var ( + elem = u.Path + args = r.args + ) + if rawPath := u.RawPath; rawPath != "" { + if normalized, ok := uri.NormalizeEscapedPath(rawPath); ok { + elem = normalized + } + defer func() { + for i, arg := range r.args[:r.count] { + if unescaped, err := url.PathUnescape(arg); err == nil { + r.args[i] = unescaped + } + } + }() + } + + elem, ok := s.cutPrefix(elem) + if !ok { + return r, false + } + + // Static code generated router with unwrapped path search. + switch { + default: + if len(elem) == 0 { + break + } + switch elem[0] { + case '/': // Prefix: "/" + origElem := elem + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'r': // Prefix: "ranks" + origElem := elem + if l := len("ranks"); len(elem) >= l && elem[0:l] == "ranks" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "GET": + r.name = "ListRanks" + r.summary = "Get list of ranks" + r.operationID = "listRanks" + r.pathPattern = "/ranks" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + elem = origElem + case 't': // Prefix: "times" + origElem := elem + if l := len("times"); len(elem) >= l && elem[0:l] == "times" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "GET": + r.name = "ListTimes" + r.summary = "Get list of times" + r.operationID = "listTimes" + r.pathPattern = "/times" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + elem = origElem + case 'u': // Prefix: "users/" + origElem := elem + if l := len("users/"); len(elem) >= l && elem[0:l] == "users/" { + elem = elem[l:] + } else { + break + } + + // Param: "UserID" + // Match until "/" + idx := strings.IndexByte(elem, '/') + if idx < 0 { + idx = len(elem) + } + args[0] = elem[:idx] + elem = elem[idx:] + + if len(elem) == 0 { + switch method { + case "GET": + r.name = "GetUser" + r.summary = "Retrieve user with ID" + r.operationID = "getUser" + r.pathPattern = "/users/{UserID}" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + switch elem[0] { + case '/': // Prefix: "/rank" + origElem := elem + if l := len("/rank"); len(elem) >= l && elem[0:l] == "/rank" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "GET": + r.name = "GetUserRank" + r.summary = "Retrieve rank of user" + r.operationID = "getUserRank" + r.pathPattern = "/users/{UserID}/rank" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + elem = origElem + } + + elem = origElem + } + + elem = origElem + } + } + return r, false +} diff --git a/api/oas_schemas_gen.go b/api/oas_schemas_gen.go new file mode 100644 index 0000000..904c2b6 --- /dev/null +++ b/api/oas_schemas_gen.go @@ -0,0 +1,867 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "fmt" +) + +func (s *ErrorStatusCode) Error() string { + return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response) +} + +// Represents error object. +// Ref: #/components/schemas/Error +type Error struct { + Code int64 `json:"code"` + Message string `json:"message"` +} + +// GetCode returns the value of Code. +func (s *Error) GetCode() int64 { + return s.Code +} + +// GetMessage returns the value of Message. +func (s *Error) GetMessage() string { + return s.Message +} + +// SetCode sets the value of Code. +func (s *Error) SetCode(val int64) { + s.Code = val +} + +// SetMessage sets the value of Message. +func (s *Error) SetMessage(val string) { + s.Message = val +} + +// ErrorStatusCode wraps Error with StatusCode. +type ErrorStatusCode struct { + StatusCode int + Response Error +} + +// GetStatusCode returns the value of StatusCode. +func (s *ErrorStatusCode) GetStatusCode() int { + return s.StatusCode +} + +// GetResponse returns the value of Response. +func (s *ErrorStatusCode) GetResponse() Error { + return s.Response +} + +// SetStatusCode sets the value of StatusCode. +func (s *ErrorStatusCode) SetStatusCode(val int) { + s.StatusCode = val +} + +// SetResponse sets the value of Response. +func (s *ErrorStatusCode) SetResponse(val Error) { + s.Response = val +} + +// Ref: #/components/schemas/Map +type Map struct { + ID OptInt64 `json:"ID"` + DisplayName OptString `json:"DisplayName"` + Creator OptString `json:"Creator"` + GameID OptInt32 `json:"GameID"` + Date OptInt64 `json:"Date"` +} + +// GetID returns the value of ID. +func (s *Map) GetID() OptInt64 { + return s.ID +} + +// GetDisplayName returns the value of DisplayName. +func (s *Map) GetDisplayName() OptString { + return s.DisplayName +} + +// GetCreator returns the value of Creator. +func (s *Map) GetCreator() OptString { + return s.Creator +} + +// GetGameID returns the value of GameID. +func (s *Map) GetGameID() OptInt32 { + return s.GameID +} + +// GetDate returns the value of Date. +func (s *Map) GetDate() OptInt64 { + return s.Date +} + +// SetID sets the value of ID. +func (s *Map) SetID(val OptInt64) { + s.ID = val +} + +// SetDisplayName sets the value of DisplayName. +func (s *Map) SetDisplayName(val OptString) { + s.DisplayName = val +} + +// SetCreator sets the value of Creator. +func (s *Map) SetCreator(val OptString) { + s.Creator = val +} + +// SetGameID sets the value of GameID. +func (s *Map) SetGameID(val OptInt32) { + s.GameID = val +} + +// SetDate sets the value of Date. +func (s *Map) SetDate(val OptInt64) { + s.Date = val +} + +// NewOptFloat64 returns new OptFloat64 with value set to v. +func NewOptFloat64(v float64) OptFloat64 { + return OptFloat64{ + Value: v, + Set: true, + } +} + +// OptFloat64 is optional float64. +type OptFloat64 struct { + Value float64 + Set bool +} + +// IsSet returns true if OptFloat64 was set. +func (o OptFloat64) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptFloat64) Reset() { + var v float64 + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptFloat64) SetTo(v float64) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptFloat64) Get() (v float64, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptFloat64) Or(d float64) float64 { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptInt32 returns new OptInt32 with value set to v. +func NewOptInt32(v int32) OptInt32 { + return OptInt32{ + Value: v, + Set: true, + } +} + +// OptInt32 is optional int32. +type OptInt32 struct { + Value int32 + Set bool +} + +// IsSet returns true if OptInt32 was set. +func (o OptInt32) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptInt32) Reset() { + var v int32 + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptInt32) SetTo(v int32) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptInt32) Get() (v int32, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptInt32) Or(d int32) int32 { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptInt64 returns new OptInt64 with value set to v. +func NewOptInt64(v int64) OptInt64 { + return OptInt64{ + Value: v, + Set: true, + } +} + +// OptInt64 is optional int64. +type OptInt64 struct { + Value int64 + Set bool +} + +// IsSet returns true if OptInt64 was set. +func (o OptInt64) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptInt64) Reset() { + var v int64 + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptInt64) SetTo(v int64) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptInt64) Get() (v int64, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptInt64) Or(d int64) int64 { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptMap returns new OptMap with value set to v. +func NewOptMap(v Map) OptMap { + return OptMap{ + Value: v, + Set: true, + } +} + +// OptMap is optional Map. +type OptMap struct { + Value Map + Set bool +} + +// IsSet returns true if OptMap was set. +func (o OptMap) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptMap) Reset() { + var v Map + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptMap) SetTo(v Map) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptMap) Get() (v Map, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptMap) Or(d Map) Map { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptRankFilter returns new OptRankFilter with value set to v. +func NewOptRankFilter(v RankFilter) OptRankFilter { + return OptRankFilter{ + Value: v, + Set: true, + } +} + +// OptRankFilter is optional RankFilter. +type OptRankFilter struct { + Value RankFilter + Set bool +} + +// IsSet returns true if OptRankFilter was set. +func (o OptRankFilter) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptRankFilter) Reset() { + var v RankFilter + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptRankFilter) SetTo(v RankFilter) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptRankFilter) Get() (v RankFilter, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptRankFilter) Or(d RankFilter) RankFilter { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptString returns new OptString with value set to v. +func NewOptString(v string) OptString { + return OptString{ + Value: v, + Set: true, + } +} + +// OptString is optional string. +type OptString struct { + Value string + Set bool +} + +// IsSet returns true if OptString was set. +func (o OptString) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptString) Reset() { + var v string + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptString) SetTo(v string) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptString) Get() (v string, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptString) Or(d string) string { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptTimeFilter returns new OptTimeFilter with value set to v. +func NewOptTimeFilter(v TimeFilter) OptTimeFilter { + return OptTimeFilter{ + Value: v, + Set: true, + } +} + +// OptTimeFilter is optional TimeFilter. +type OptTimeFilter struct { + Value TimeFilter + Set bool +} + +// IsSet returns true if OptTimeFilter was set. +func (o OptTimeFilter) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptTimeFilter) Reset() { + var v TimeFilter + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptTimeFilter) SetTo(v TimeFilter) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptTimeFilter) Get() (v TimeFilter, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptTimeFilter) Or(d TimeFilter) TimeFilter { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptUser returns new OptUser with value set to v. +func NewOptUser(v User) OptUser { + return OptUser{ + Value: v, + Set: true, + } +} + +// OptUser is optional User. +type OptUser struct { + Value User + Set bool +} + +// IsSet returns true if OptUser was set. +func (o OptUser) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptUser) Reset() { + var v User + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptUser) SetTo(v User) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptUser) Get() (v User, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptUser) Or(d User) User { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// Ref: #/components/schemas/Pagination +type Pagination struct { + Page int32 `json:"Page"` + Limit int32 `json:"Limit"` +} + +// GetPage returns the value of Page. +func (s *Pagination) GetPage() int32 { + return s.Page +} + +// GetLimit returns the value of Limit. +func (s *Pagination) GetLimit() int32 { + return s.Limit +} + +// SetPage sets the value of Page. +func (s *Pagination) SetPage(val int32) { + s.Page = val +} + +// SetLimit sets the value of Limit. +func (s *Pagination) SetLimit(val int32) { + s.Limit = val +} + +// Ref: #/components/schemas/Rank +type Rank struct { + ID OptInt64 `json:"ID"` + User OptUser `json:"User"` + StyleID OptInt32 `json:"StyleID"` + ModeID OptInt32 `json:"ModeID"` + GameID OptInt32 `json:"GameID"` + Rank OptFloat64 `json:"Rank"` + Skill OptFloat64 `json:"Skill"` + UpdatedAt OptInt64 `json:"UpdatedAt"` +} + +// GetID returns the value of ID. +func (s *Rank) GetID() OptInt64 { + return s.ID +} + +// GetUser returns the value of User. +func (s *Rank) GetUser() OptUser { + return s.User +} + +// GetStyleID returns the value of StyleID. +func (s *Rank) GetStyleID() OptInt32 { + return s.StyleID +} + +// GetModeID returns the value of ModeID. +func (s *Rank) GetModeID() OptInt32 { + return s.ModeID +} + +// GetGameID returns the value of GameID. +func (s *Rank) GetGameID() OptInt32 { + return s.GameID +} + +// GetRank returns the value of Rank. +func (s *Rank) GetRank() OptFloat64 { + return s.Rank +} + +// GetSkill returns the value of Skill. +func (s *Rank) GetSkill() OptFloat64 { + return s.Skill +} + +// GetUpdatedAt returns the value of UpdatedAt. +func (s *Rank) GetUpdatedAt() OptInt64 { + return s.UpdatedAt +} + +// SetID sets the value of ID. +func (s *Rank) SetID(val OptInt64) { + s.ID = val +} + +// SetUser sets the value of User. +func (s *Rank) SetUser(val OptUser) { + s.User = val +} + +// SetStyleID sets the value of StyleID. +func (s *Rank) SetStyleID(val OptInt32) { + s.StyleID = val +} + +// SetModeID sets the value of ModeID. +func (s *Rank) SetModeID(val OptInt32) { + s.ModeID = val +} + +// SetGameID sets the value of GameID. +func (s *Rank) SetGameID(val OptInt32) { + s.GameID = val +} + +// SetRank sets the value of Rank. +func (s *Rank) SetRank(val OptFloat64) { + s.Rank = val +} + +// SetSkill sets the value of Skill. +func (s *Rank) SetSkill(val OptFloat64) { + s.Skill = val +} + +// SetUpdatedAt sets the value of UpdatedAt. +func (s *Rank) SetUpdatedAt(val OptInt64) { + s.UpdatedAt = val +} + +// Ref: #/components/schemas/RankFilter +type RankFilter struct { + StyleID OptInt32 `json:"StyleID"` + GameID OptInt32 `json:"GameID"` + ModeID OptInt32 `json:"ModeID"` + Sort OptInt64 `json:"Sort"` +} + +// GetStyleID returns the value of StyleID. +func (s *RankFilter) GetStyleID() OptInt32 { + return s.StyleID +} + +// GetGameID returns the value of GameID. +func (s *RankFilter) GetGameID() OptInt32 { + return s.GameID +} + +// GetModeID returns the value of ModeID. +func (s *RankFilter) GetModeID() OptInt32 { + return s.ModeID +} + +// GetSort returns the value of Sort. +func (s *RankFilter) GetSort() OptInt64 { + return s.Sort +} + +// SetStyleID sets the value of StyleID. +func (s *RankFilter) SetStyleID(val OptInt32) { + s.StyleID = val +} + +// SetGameID sets the value of GameID. +func (s *RankFilter) SetGameID(val OptInt32) { + s.GameID = val +} + +// SetModeID sets the value of ModeID. +func (s *RankFilter) SetModeID(val OptInt32) { + s.ModeID = val +} + +// SetSort sets the value of Sort. +func (s *RankFilter) SetSort(val OptInt64) { + s.Sort = val +} + +// Ref: #/components/schemas/Time +type Time struct { + ID OptInt64 `json:"ID"` + Time OptInt64 `json:"Time"` + User OptUser `json:"User"` + Map OptMap `json:"Map"` + Date OptInt64 `json:"Date"` + StyleID OptInt32 `json:"StyleID"` + ModeID OptInt32 `json:"ModeID"` + GameID OptInt32 `json:"GameID"` +} + +// GetID returns the value of ID. +func (s *Time) GetID() OptInt64 { + return s.ID +} + +// GetTime returns the value of Time. +func (s *Time) GetTime() OptInt64 { + return s.Time +} + +// GetUser returns the value of User. +func (s *Time) GetUser() OptUser { + return s.User +} + +// GetMap returns the value of Map. +func (s *Time) GetMap() OptMap { + return s.Map +} + +// GetDate returns the value of Date. +func (s *Time) GetDate() OptInt64 { + return s.Date +} + +// GetStyleID returns the value of StyleID. +func (s *Time) GetStyleID() OptInt32 { + return s.StyleID +} + +// GetModeID returns the value of ModeID. +func (s *Time) GetModeID() OptInt32 { + return s.ModeID +} + +// GetGameID returns the value of GameID. +func (s *Time) GetGameID() OptInt32 { + return s.GameID +} + +// SetID sets the value of ID. +func (s *Time) SetID(val OptInt64) { + s.ID = val +} + +// SetTime sets the value of Time. +func (s *Time) SetTime(val OptInt64) { + s.Time = val +} + +// SetUser sets the value of User. +func (s *Time) SetUser(val OptUser) { + s.User = val +} + +// SetMap sets the value of Map. +func (s *Time) SetMap(val OptMap) { + s.Map = val +} + +// SetDate sets the value of Date. +func (s *Time) SetDate(val OptInt64) { + s.Date = val +} + +// SetStyleID sets the value of StyleID. +func (s *Time) SetStyleID(val OptInt32) { + s.StyleID = val +} + +// SetModeID sets the value of ModeID. +func (s *Time) SetModeID(val OptInt32) { + s.ModeID = val +} + +// SetGameID sets the value of GameID. +func (s *Time) SetGameID(val OptInt32) { + s.GameID = val +} + +// Ref: #/components/schemas/TimeFilter +type TimeFilter struct { + ID OptInt64 `json:"ID"` + Time OptInt64 `json:"Time"` + UserID OptInt64 `json:"UserID"` + MapID OptInt64 `json:"MapID"` + StyleID OptInt32 `json:"StyleID"` + ModeID OptInt32 `json:"ModeID"` + GameID OptInt32 `json:"GameID"` +} + +// GetID returns the value of ID. +func (s *TimeFilter) GetID() OptInt64 { + return s.ID +} + +// GetTime returns the value of Time. +func (s *TimeFilter) GetTime() OptInt64 { + return s.Time +} + +// GetUserID returns the value of UserID. +func (s *TimeFilter) GetUserID() OptInt64 { + return s.UserID +} + +// GetMapID returns the value of MapID. +func (s *TimeFilter) GetMapID() OptInt64 { + return s.MapID +} + +// GetStyleID returns the value of StyleID. +func (s *TimeFilter) GetStyleID() OptInt32 { + return s.StyleID +} + +// GetModeID returns the value of ModeID. +func (s *TimeFilter) GetModeID() OptInt32 { + return s.ModeID +} + +// GetGameID returns the value of GameID. +func (s *TimeFilter) GetGameID() OptInt32 { + return s.GameID +} + +// SetID sets the value of ID. +func (s *TimeFilter) SetID(val OptInt64) { + s.ID = val +} + +// SetTime sets the value of Time. +func (s *TimeFilter) SetTime(val OptInt64) { + s.Time = val +} + +// SetUserID sets the value of UserID. +func (s *TimeFilter) SetUserID(val OptInt64) { + s.UserID = val +} + +// SetMapID sets the value of MapID. +func (s *TimeFilter) SetMapID(val OptInt64) { + s.MapID = val +} + +// SetStyleID sets the value of StyleID. +func (s *TimeFilter) SetStyleID(val OptInt32) { + s.StyleID = val +} + +// SetModeID sets the value of ModeID. +func (s *TimeFilter) SetModeID(val OptInt32) { + s.ModeID = val +} + +// SetGameID sets the value of GameID. +func (s *TimeFilter) SetGameID(val OptInt32) { + s.GameID = val +} + +// Ref: #/components/schemas/User +type User struct { + ID OptInt64 `json:"ID"` + Username OptString `json:"Username"` + StateID OptInt32 `json:"StateID"` +} + +// GetID returns the value of ID. +func (s *User) GetID() OptInt64 { + return s.ID +} + +// GetUsername returns the value of Username. +func (s *User) GetUsername() OptString { + return s.Username +} + +// GetStateID returns the value of StateID. +func (s *User) GetStateID() OptInt32 { + return s.StateID +} + +// SetID sets the value of ID. +func (s *User) SetID(val OptInt64) { + s.ID = val +} + +// SetUsername sets the value of Username. +func (s *User) SetUsername(val OptString) { + s.Username = val +} + +// SetStateID sets the value of StateID. +func (s *User) SetStateID(val OptInt32) { + s.StateID = val +} diff --git a/api/oas_server_gen.go b/api/oas_server_gen.go new file mode 100644 index 0000000..fa23779 --- /dev/null +++ b/api/oas_server_gen.go @@ -0,0 +1,58 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "context" +) + +// Handler handles operations described by OpenAPI v3 specification. +type Handler interface { + // GetUser implements getUser operation. + // + // Retrieve user with ID. + // + // GET /users/{UserID} + GetUser(ctx context.Context, params GetUserParams) (*User, error) + // GetUserRank implements getUserRank operation. + // + // Retrieve rank of user. + // + // GET /users/{UserID}/rank + GetUserRank(ctx context.Context, params GetUserRankParams) (*Rank, error) + // ListRanks implements listRanks operation. + // + // Get list of ranks. + // + // GET /ranks + ListRanks(ctx context.Context, params ListRanksParams) ([]Rank, error) + // ListTimes implements listTimes operation. + // + // Get list of times. + // + // GET /times + ListTimes(ctx context.Context, params ListTimesParams) ([]Time, error) + // NewError creates *ErrorStatusCode from error returned by handler. + // + // Used for common default response. + NewError(ctx context.Context, err error) *ErrorStatusCode +} + +// Server implements http server based on OpenAPI v3 specification and +// calls Handler to handle requests. +type Server struct { + h Handler + baseServer +} + +// NewServer creates new Server. +func NewServer(h Handler, opts ...ServerOption) (*Server, error) { + s, err := newServerConfig(opts...).baseServer() + if err != nil { + return nil, err + } + return &Server{ + h: h, + baseServer: s, + }, nil +} diff --git a/api/oas_unimplemented_gen.go b/api/oas_unimplemented_gen.go new file mode 100644 index 0000000..9682318 --- /dev/null +++ b/api/oas_unimplemented_gen.go @@ -0,0 +1,58 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "context" + + ht "github.com/ogen-go/ogen/http" +) + +// UnimplementedHandler is no-op Handler which returns http.ErrNotImplemented. +type UnimplementedHandler struct{} + +var _ Handler = UnimplementedHandler{} + +// GetUser implements getUser operation. +// +// Retrieve user with ID. +// +// GET /users/{UserID} +func (UnimplementedHandler) GetUser(ctx context.Context, params GetUserParams) (r *User, _ error) { + return r, ht.ErrNotImplemented +} + +// GetUserRank implements getUserRank operation. +// +// Retrieve rank of user. +// +// GET /users/{UserID}/rank +func (UnimplementedHandler) GetUserRank(ctx context.Context, params GetUserRankParams) (r *Rank, _ error) { + return r, ht.ErrNotImplemented +} + +// ListRanks implements listRanks operation. +// +// Get list of ranks. +// +// GET /ranks +func (UnimplementedHandler) ListRanks(ctx context.Context, params ListRanksParams) (r []Rank, _ error) { + return r, ht.ErrNotImplemented +} + +// ListTimes implements listTimes operation. +// +// Get list of times. +// +// GET /times +func (UnimplementedHandler) ListTimes(ctx context.Context, params ListTimesParams) (r []Time, _ error) { + return r, ht.ErrNotImplemented +} + +// NewError creates *ErrorStatusCode from error returned by handler. +// +// Used for common default response. +func (UnimplementedHandler) NewError(ctx context.Context, err error) (r *ErrorStatusCode) { + r = new(ErrorStatusCode) + return r +} diff --git a/api/oas_uri_gen.go b/api/oas_uri_gen.go new file mode 100644 index 0000000..4eec33c --- /dev/null +++ b/api/oas_uri_gen.go @@ -0,0 +1,539 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "math/bits" + "strconv" + + "github.com/go-faster/errors" + + "github.com/ogen-go/ogen/conv" + "github.com/ogen-go/ogen/uri" + "github.com/ogen-go/ogen/validate" +) + +// EncodeURI encodes Pagination as URI form. +func (s *Pagination) EncodeURI(e uri.Encoder) error { + if err := e.EncodeField("Page", func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(s.Page)) + }); err != nil { + return errors.Wrap(err, "encode field \"Page\"") + } + if err := e.EncodeField("Limit", func(e uri.Encoder) error { + return e.EncodeValue(conv.Int32ToString(s.Limit)) + }); err != nil { + return errors.Wrap(err, "encode field \"Limit\"") + } + return nil +} + +var uriFieldsNameOfPagination = [2]string{ + 0: "Page", + 1: "Limit", +} + +// DecodeURI decodes Pagination from URI form. +func (s *Pagination) DecodeURI(d uri.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Pagination to nil") + } + var requiredBitSet [1]uint8 + + if err := d.DecodeFields(func(k string, d uri.Decoder) error { + switch k { + case "Page": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + s.Page = c + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Page\"") + } + case "Limit": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + s.Limit = c + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Limit\"") + } + default: + return nil + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Pagination") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + 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(uriFieldsNameOfPagination) { + name = uriFieldsNameOfPagination[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 +} + +// EncodeURI encodes RankFilter as URI form. +func (s *RankFilter) EncodeURI(e uri.Encoder) error { + if err := e.EncodeField("StyleID", func(e uri.Encoder) error { + if val, ok := s.StyleID.Get(); ok { + return e.EncodeValue(conv.Int32ToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode field \"StyleID\"") + } + if err := e.EncodeField("GameID", func(e uri.Encoder) error { + if val, ok := s.GameID.Get(); ok { + return e.EncodeValue(conv.Int32ToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode field \"GameID\"") + } + if err := e.EncodeField("ModeID", func(e uri.Encoder) error { + if val, ok := s.ModeID.Get(); ok { + return e.EncodeValue(conv.Int32ToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode field \"ModeID\"") + } + if err := e.EncodeField("Sort", func(e uri.Encoder) error { + if val, ok := s.Sort.Get(); ok { + return e.EncodeValue(conv.Int64ToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode field \"Sort\"") + } + return nil +} + +var uriFieldsNameOfRankFilter = [4]string{ + 0: "StyleID", + 1: "GameID", + 2: "ModeID", + 3: "Sort", +} + +// DecodeURI decodes RankFilter from URI form. +func (s *RankFilter) DecodeURI(d uri.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode RankFilter to nil") + } + + if err := d.DecodeFields(func(k string, d uri.Decoder) error { + switch k { + case "StyleID": + if err := func() error { + var sDotStyleIDVal int32 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + sDotStyleIDVal = c + return nil + }(); err != nil { + return err + } + s.StyleID.SetTo(sDotStyleIDVal) + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"StyleID\"") + } + case "GameID": + if err := func() error { + var sDotGameIDVal int32 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + sDotGameIDVal = c + return nil + }(); err != nil { + return err + } + s.GameID.SetTo(sDotGameIDVal) + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"GameID\"") + } + case "ModeID": + if err := func() error { + var sDotModeIDVal int32 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + sDotModeIDVal = c + return nil + }(); err != nil { + return err + } + s.ModeID.SetTo(sDotModeIDVal) + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ModeID\"") + } + case "Sort": + if err := func() error { + var sDotSortVal int64 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + sDotSortVal = c + return nil + }(); err != nil { + return err + } + s.Sort.SetTo(sDotSortVal) + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Sort\"") + } + default: + return nil + } + return nil + }); err != nil { + return errors.Wrap(err, "decode RankFilter") + } + + return nil +} + +// EncodeURI encodes TimeFilter as URI form. +func (s *TimeFilter) EncodeURI(e uri.Encoder) error { + if err := e.EncodeField("ID", func(e uri.Encoder) error { + if val, ok := s.ID.Get(); ok { + return e.EncodeValue(conv.Int64ToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode field \"ID\"") + } + if err := e.EncodeField("Time", func(e uri.Encoder) error { + if val, ok := s.Time.Get(); ok { + return e.EncodeValue(conv.Int64ToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode field \"Time\"") + } + if err := e.EncodeField("UserID", func(e uri.Encoder) error { + if val, ok := s.UserID.Get(); ok { + return e.EncodeValue(conv.Int64ToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode field \"UserID\"") + } + if err := e.EncodeField("MapID", func(e uri.Encoder) error { + if val, ok := s.MapID.Get(); ok { + return e.EncodeValue(conv.Int64ToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode field \"MapID\"") + } + if err := e.EncodeField("StyleID", func(e uri.Encoder) error { + if val, ok := s.StyleID.Get(); ok { + return e.EncodeValue(conv.Int32ToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode field \"StyleID\"") + } + if err := e.EncodeField("ModeID", func(e uri.Encoder) error { + if val, ok := s.ModeID.Get(); ok { + return e.EncodeValue(conv.Int32ToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode field \"ModeID\"") + } + if err := e.EncodeField("GameID", func(e uri.Encoder) error { + if val, ok := s.GameID.Get(); ok { + return e.EncodeValue(conv.Int32ToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode field \"GameID\"") + } + return nil +} + +var uriFieldsNameOfTimeFilter = [7]string{ + 0: "ID", + 1: "Time", + 2: "UserID", + 3: "MapID", + 4: "StyleID", + 5: "ModeID", + 6: "GameID", +} + +// DecodeURI decodes TimeFilter from URI form. +func (s *TimeFilter) DecodeURI(d uri.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode TimeFilter to nil") + } + + if err := d.DecodeFields(func(k string, d uri.Decoder) error { + switch k { + case "ID": + if err := func() error { + var sDotIDVal int64 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + sDotIDVal = c + return nil + }(); err != nil { + return err + } + s.ID.SetTo(sDotIDVal) + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ID\"") + } + case "Time": + if err := func() error { + var sDotTimeVal int64 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + sDotTimeVal = c + return nil + }(); err != nil { + return err + } + s.Time.SetTo(sDotTimeVal) + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"Time\"") + } + case "UserID": + if err := func() error { + var sDotUserIDVal int64 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + sDotUserIDVal = c + return nil + }(); err != nil { + return err + } + s.UserID.SetTo(sDotUserIDVal) + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"UserID\"") + } + case "MapID": + if err := func() error { + var sDotMapIDVal int64 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt64(val) + if err != nil { + return err + } + + sDotMapIDVal = c + return nil + }(); err != nil { + return err + } + s.MapID.SetTo(sDotMapIDVal) + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"MapID\"") + } + case "StyleID": + if err := func() error { + var sDotStyleIDVal int32 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + sDotStyleIDVal = c + return nil + }(); err != nil { + return err + } + s.StyleID.SetTo(sDotStyleIDVal) + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"StyleID\"") + } + case "ModeID": + if err := func() error { + var sDotModeIDVal int32 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + sDotModeIDVal = c + return nil + }(); err != nil { + return err + } + s.ModeID.SetTo(sDotModeIDVal) + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ModeID\"") + } + case "GameID": + if err := func() error { + var sDotGameIDVal int32 + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToInt32(val) + if err != nil { + return err + } + + sDotGameIDVal = c + return nil + }(); err != nil { + return err + } + s.GameID.SetTo(sDotGameIDVal) + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"GameID\"") + } + default: + return nil + } + return nil + }); err != nil { + return errors.Wrap(err, "decode TimeFilter") + } + + return nil +} diff --git a/api/oas_validators_gen.go b/api/oas_validators_gen.go new file mode 100644 index 0000000..b812fe6 --- /dev/null +++ b/api/oas_validators_gen.go @@ -0,0 +1,109 @@ +// Code generated by ogen, DO NOT EDIT. + +package api + +import ( + "github.com/go-faster/errors" + + "github.com/ogen-go/ogen/validate" +) + +func (s *Pagination) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 1, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(s.Page)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Page", + Error: err, + }) + } + if err := func() error { + if err := (validate.Int{ + MinSet: true, + Min: 1, + MaxSet: true, + Max: 100, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + }).Validate(int64(s.Limit)); err != nil { + return errors.Wrap(err, "int") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Limit", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *Rank) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if value, ok := s.Rank.Get(); ok { + if err := func() error { + if err := (validate.Float{}).Validate(float64(value)); err != nil { + return errors.Wrap(err, "float") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Rank", + Error: err, + }) + } + if err := func() error { + if value, ok := s.Skill.Get(); ok { + if err := func() error { + if err := (validate.Float{}).Validate(float64(value)); err != nil { + return errors.Wrap(err, "float") + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Skill", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} diff --git a/generate.go b/generate.go new file mode 100644 index 0000000..32d8034 --- /dev/null +++ b/generate.go @@ -0,0 +1,3 @@ +package main + +//go:generate go run github.com/ogen-go/ogen/cmd/ogen@latest --target api --clean openapi.yaml diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..21d187e --- /dev/null +++ b/go.mod @@ -0,0 +1,40 @@ +module git.itzana.me/strafesnet/public-api + +go 1.21.5 + +require ( + git.itzana.me/strafesnet/go-grpc v0.0.0-20240302215338-8ebbc7cffb0a + github.com/go-faster/errors v0.7.1 + github.com/go-faster/jx v1.1.0 + github.com/ogen-go/ogen v1.2.1 + go.opentelemetry.io/otel v1.27.0 + go.opentelemetry.io/otel/metric v1.27.0 + go.opentelemetry.io/otel/trace v1.27.0 + google.golang.org/grpc v1.64.0 +) + +require google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect + +require ( + github.com/dlclark/regexp2 v1.11.0 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-faster/yaml v0.4.6 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + // github.com/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/segmentio/asm v1.2.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + // google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b55dae3 --- /dev/null +++ b/go.sum @@ -0,0 +1,143 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +git.itzana.me/strafesnet/go-grpc v0.0.0-20240302215338-8ebbc7cffb0a h1:679jxkSaW3ZEj0XW6v+sr3Wr3LcmHRbbDLmo80xty5s= +git.itzana.me/strafesnet/go-grpc v0.0.0-20240302215338-8ebbc7cffb0a/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/go-faster/jx v1.1.0 h1:ZsW3wD+snOdmTDy9eIVgQdjUpXRRV4rqW8NS3t+20bg= +github.com/go-faster/jx v1.1.0/go.mod h1:vKDNikrKoyUmpzaJ0OkIkRQClNHFX/nF3dnTJZb3skg= +github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I= +github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ogen-go/ogen v1.2.1 h1:C5A0lvUMu2wl+eWIxnpXMWnuOJ26a2FyzR1CIC2qG0M= +github.com/ogen-go/ogen v1.2.1/go.mod h1:P2zQdEu8UqaVRfD5GEFvl+9q63VjMLvDquq1wVbyInM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= +golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..f52bc70 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,294 @@ +openapi: 3.0.0 +info: + title: StrafesNET - OpenAPI 3.0 + description: |- + TBD + version: 0.1.0 +servers: + - url: https://api.strafes.net/v2 +tags: + - name: Times + description: Retrieve player times + - name: User + description: User operations +paths: + /times: + get: + summary: Get list of times + operationId: listTimes + tags: + - Times + parameters: + - name: page + in: query + required: true + schema: + $ref: "#/components/schemas/Pagination" + - name: filter + in: query + required: false + schema: + $ref: "#/components/schemas/TimeFilter" + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Time" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /users/{UserID}: + get: + summary: Retrieve user with ID + operationId: getUser + tags: + - User + parameters: + - name: UserID + in: path + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: Successful response + content: + application/json: + schema: + $ref: "#/components/schemas/User" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /users/{UserID}/rank: + get: + summary: Retrieve rank of user + operationId: getUserRank + tags: + - User + parameters: + - name: UserID + in: path + required: true + schema: + type: integer + format: int64 + - name: StyleID + in: query + required: true + schema: + type: integer + format: int32 + - name: GameID + in: query + required: true + schema: + type: integer + format: int32 + - name: ModeID + in: query + required: true + schema: + type: integer + format: int32 + responses: + "200": + description: Successful response + content: + application/json: + schema: + $ref: "#/components/schemas/Rank" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /ranks: + get: + summary: Get list of ranks + operationId: listRanks + tags: + - User + parameters: + - name: page + in: query + required: true + schema: + $ref: "#/components/schemas/Pagination" + - name: filter + in: query + required: false + schema: + $ref: "#/components/schemas/RankFilter" + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Rank" + default: + description: General Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + User: + type: object + properties: + ID: + type: integer + format: int64 + Username: + type: string + StateID: + type: integer + format: int32 + Map: + type: object + properties: + ID: + type: integer + format: int64 + DisplayName: + type: string + Creator: + type: string + GameID: + type: integer + format: int32 + Date: + type: integer + format: int64 + Time: + type: object + properties: + ID: + type: integer + format: int64 + Time: + type: integer + format: int64 + User: + $ref: "#/components/schemas/User" + Map: + $ref: "#/components/schemas/Map" + Date: + type: integer + format: int64 + StyleID: + type: integer + format: int32 + ModeID: + type: integer + format: int32 + GameID: + type: integer + format: int32 + TimeFilter: + type: object + properties: + ID: + type: integer + format: int64 + Time: + type: integer + format: int64 + UserID: + type: integer + format: int64 + MapID: + type: integer + format: int64 + StyleID: + type: integer + format: int32 + ModeID: + type: integer + format: int32 + GameID: + type: integer + format: int32 + Rank: + type: object + properties: + ID: + type: integer + format: int64 + User: + $ref: "#/components/schemas/User" + StyleID: + type: integer + format: int32 + ModeID: + type: integer + format: int32 + GameID: + type: integer + format: int32 + Rank: + type: number + format: double + Skill: + type: number + format: double + UpdatedAt: + type: integer + format: int64 + RankFilter: + type: object + properties: + StyleID: + type: integer + format: int32 + GameID: + type: integer + format: int32 + ModeID: + type: integer + format: int32 + Sort: + type: integer + format: int64 + Pagination: + type: object + required: + - Page + - Limit + properties: + Page: + type: integer + format: int32 + minimum: 1 + Limit: + type: integer + format: int32 + minimum: 1 + maximum: 100 + Error: + description: Represents error object + type: object + properties: + code: + type: integer + format: int64 + message: + type: string + required: + - code + - message \ No newline at end of file diff --git a/server.go b/server.go new file mode 100644 index 0000000..76c69ea --- /dev/null +++ b/server.go @@ -0,0 +1,227 @@ +package main + +import ( + "context" + "log" + "net/http" + + "git.itzana.me/strafesnet/go-grpc/ranks" + "git.itzana.me/strafesnet/go-grpc/times" + "git.itzana.me/strafesnet/go-grpc/users" + "git.itzana.me/strafesnet/public-api/api" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type apiServer struct { + client *grpc.ClientConn +} + +// GetUserRank implements api.Handler. +func (m *apiServer) GetUserRank(ctx context.Context, params api.GetUserRankParams) (*api.Rank, error) { + client := ranks.NewRanksServiceClient(m.client) + + response, err := client.Get(ctx, &ranks.GetRequest{ + UserID: params.UserID, + StyleID: params.StyleID, + ModeID: params.ModeID, + GameID: params.GameID, + }) + if err != nil { + return nil, err + } + + return &api.Rank{ + ID: api.NewOptInt64(response.ID), + User: api.NewOptUser(api.User{ + ID: api.NewOptInt64(response.User.ID), + Username: api.NewOptString(response.User.Username), + StateID: api.NewOptInt32(response.User.StateID), + }), + StyleID: api.NewOptInt32(response.StyleID), + ModeID: api.NewOptInt32(response.ModeID), + GameID: api.NewOptInt32(response.GameID), + Rank: api.NewOptFloat64(response.Rank), + Skill: api.NewOptFloat64(response.Skill), + UpdatedAt: api.NewOptInt64(response.UpdatedAt), + }, nil +} + +// NewError implements api.Handler. +func (m *apiServer) NewError(ctx context.Context, err error) *api.ErrorStatusCode { + return &api.ErrorStatusCode{ + StatusCode: 500, + Response: api.Error{Message: err.Error()}, + } +} + +// GetTimes implements api.Handler. +func (m *apiServer) ListTimes(ctx context.Context, params api.ListTimesParams) ([]api.Time, error) { + client := times.NewTimesServiceClient(m.client) + + // Call the List method using params + req := ×.ListRequest{ + Page: ×.Pagination{ + Number: params.Page.GetPage(), + Size: params.Page.GetLimit(), + }, + } + + if filter, ok := params.Filter.Get(); ok { + if id := filter.GetID(); id.IsSet() { + req.Filter.ID = &id.Value + } + if time := filter.GetTime(); time.IsSet() { + req.Filter.Time = &time.Value + } + if userID := filter.GetUserID(); userID.IsSet() { + req.Filter.UserID = &userID.Value + } + if mapID := filter.GetMapID(); mapID.IsSet() { + req.Filter.MapID = &mapID.Value + } + if styleID := filter.GetStyleID(); styleID.IsSet() { + req.Filter.StyleID = &styleID.Value + } + if modeID := filter.GetModeID(); modeID.IsSet() { + req.Filter.ModeID = &modeID.Value + } + if gameID := filter.GetGameID(); gameID.IsSet() { + req.Filter.GameID = &gameID.Value + } + } + + response, err := client.List(ctx, req) + if err != nil { + return nil, err + } + + return convertTimes(response.Times), nil +} + +// GetUser implements api.Handler. +func (m *apiServer) GetUser(ctx context.Context, params api.GetUserParams) (*api.User, error) { + client := users.NewUsersServiceClient(m.client) + + response, err := client.Get(ctx, &users.IdMessage{ + ID: params.UserID, + }) + if err != nil { + return nil, err + } + + return &api.User{ + ID: api.NewOptInt64(response.ID), + Username: api.NewOptString(response.Username), + StateID: api.NewOptInt32(response.StateID), + }, nil +} + +// ListRanks implements api.Handler. +func (m *apiServer) ListRanks(ctx context.Context, params api.ListRanksParams) ([]api.Rank, error) { + client := ranks.NewRanksServiceClient(m.client) + + req := &ranks.ListRequest{ + Page: &ranks.Pagination{ + Number: params.Page.GetPage(), + Size: params.Page.GetLimit(), + }, + } + + if filter, ok := params.Filter.Get(); ok { + if gameID, ok := filter.GetGameID().Get(); ok { + req.GameID = gameID + } + + if modeID, ok := filter.GetModeID().Get(); ok { + req.ModeID = modeID + } + + if styleID, ok := filter.GetStyleID().Get(); ok { + req.StyleID = styleID + } + + if sort, ok := filter.GetSort().Get(); ok { + req.Sort = sort + } + } + + response, err := client.List(ctx, req) + if err != nil { + return nil, err + } + + ranks := make([]api.Rank, len(response.Ranks)) + for i, r := range response.Ranks { + ranks[i] = api.Rank{ + ID: api.NewOptInt64(r.ID), + User: api.NewOptUser(api.User{ + ID: api.NewOptInt64(r.User.ID), + Username: api.NewOptString(r.User.Username), + StateID: api.NewOptInt32(r.User.StateID), + }), + StyleID: api.NewOptInt32(r.StyleID), + ModeID: api.NewOptInt32(r.ModeID), + GameID: api.NewOptInt32(r.GameID), + Rank: api.NewOptFloat64(r.Rank), + Skill: api.NewOptFloat64(r.Skill), + UpdatedAt: api.NewOptInt64(r.UpdatedAt), + } + } + + return ranks, nil +} + +func main() { + // new grpc client + conn, err := grpc.Dial("localhost:9000", grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatal(err) + } + + svc := &apiServer{ + client: conn, + } + + srv, err := api.NewServer( + svc, + api.WithPathPrefix("/v2"), + ) + if err != nil { + log.Fatal(err) + } + + if err := http.ListenAndServe(":8080", srv); err != nil { + log.Fatal(err) + } +} + +func convertTime(t *times.TimeResponse) api.Time { + return api.Time{ + ID: api.NewOptInt64(t.ID), + Time: api.NewOptInt64(t.Time), + User: api.NewOptUser(api.User{ + ID: api.NewOptInt64(t.User.ID), + Username: api.NewOptString(t.User.Username), + StateID: api.NewOptInt32(t.User.StateID), + }), + Map: api.NewOptMap(api.Map{ + ID: api.NewOptInt64(t.Map.ID), + DisplayName: api.NewOptString(t.Map.DisplayName), + Creator: api.NewOptString(t.Map.Creator), + Date: api.NewOptInt64(t.Map.Date), + }), + Date: api.NewOptInt64(t.Date), + StyleID: api.NewOptInt32(t.StyleID), + ModeID: api.NewOptInt32(t.ModeID), + GameID: api.NewOptInt32(t.GameID), + } +} + +func convertTimes(timeList []*times.TimeResponse) []api.Time { + times := make([]api.Time, len(timeList)) + for i, t := range timeList { + times[i] = convertTime(t) + } + return times +}