From e17aa7dc38b16490d08edfc00330c1d0370f0ffe Mon Sep 17 00:00:00 2001 From: Bastien Riviere Date: Mon, 29 Jan 2024 19:32:16 +0100 Subject: [PATCH] feat: generate OpenAPI server --- gen.go | 9 + internal/oas/oas_cfg_gen.go | 281 ++++++++++++++++++++++ internal/oas/oas_client_gen.go | 244 +++++++++++++++++++ internal/oas/oas_handlers_gen.go | 228 ++++++++++++++++++ internal/oas/oas_interfaces_gen.go | 10 + internal/oas/oas_json_gen.go | 270 +++++++++++++++++++++ internal/oas/oas_middleware_gen.go | 10 + internal/oas/oas_parameters_gen.go | 82 +++++++ internal/oas/oas_request_decoders_gen.go | 79 ++++++ internal/oas/oas_request_encoders_gen.go | 26 ++ internal/oas/oas_response_decoders_gen.go | 144 +++++++++++ internal/oas/oas_response_encoders_gen.go | 86 +++++++ internal/oas/oas_router_gen.go | 249 +++++++++++++++++++ internal/oas/oas_schemas_gen.go | 119 +++++++++ internal/oas/oas_server_gen.go | 40 +++ internal/oas/oas_unimplemented_gen.go | 30 +++ openapi.yaml | 3 - 17 files changed, 1907 insertions(+), 3 deletions(-) create mode 100644 gen.go create mode 100644 internal/oas/oas_cfg_gen.go create mode 100644 internal/oas/oas_client_gen.go create mode 100644 internal/oas/oas_handlers_gen.go create mode 100644 internal/oas/oas_interfaces_gen.go create mode 100644 internal/oas/oas_json_gen.go create mode 100644 internal/oas/oas_middleware_gen.go create mode 100644 internal/oas/oas_parameters_gen.go create mode 100644 internal/oas/oas_request_decoders_gen.go create mode 100644 internal/oas/oas_request_encoders_gen.go create mode 100644 internal/oas/oas_response_decoders_gen.go create mode 100644 internal/oas/oas_response_encoders_gen.go create mode 100644 internal/oas/oas_router_gen.go create mode 100644 internal/oas/oas_schemas_gen.go create mode 100644 internal/oas/oas_server_gen.go create mode 100644 internal/oas/oas_unimplemented_gen.go diff --git a/gen.go b/gen.go new file mode 100644 index 0000000..6c7053f --- /dev/null +++ b/gen.go @@ -0,0 +1,9 @@ +//go:generate go run github.com/ogen-go/ogen/cmd/ogen --target internal/oas -package oas --clean openapi.yaml +//go:generate sqlc generate + +package main + +import ( + // For go get ./... + _ "github.com/ogen-go/ogen" +) diff --git a/internal/oas/oas_cfg_gen.go b/internal/oas/oas_cfg_gen.go new file mode 100644 index 0000000..a929ba4 --- /dev/null +++ b/internal/oas/oas_cfg_gen.go @@ -0,0 +1,281 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +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) +} + +// 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 = s.cfg.Meter.Int64Counter(otelogen.ServerRequestCount); err != nil { + return s, err + } + if s.errors, err = s.cfg.Meter.Int64Counter(otelogen.ServerErrorsCount); err != nil { + return s, err + } + if s.duration, err = s.cfg.Meter.Float64Histogram(otelogen.ServerDuration); 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 = c.cfg.Meter.Int64Counter(otelogen.ClientRequestCount); err != nil { + return c, err + } + if c.errors, err = c.cfg.Meter.Int64Counter(otelogen.ClientErrorsCount); err != nil { + return c, err + } + if c.duration, err = c.cfg.Meter.Float64Histogram(otelogen.ClientDuration); 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/internal/oas/oas_client_gen.go b/internal/oas/oas_client_gen.go new file mode 100644 index 0000000..381d105 --- /dev/null +++ b/internal/oas/oas_client_gen.go @@ -0,0 +1,244 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +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/uri" +) + +// Invoker invokes operations described by OpenAPI v3 specification. +type Invoker interface { + // CreatePost invokes POST /create operation. + // + // POST /create + CreatePost(ctx context.Context, request *CreatePostReq) (CreatePostRes, error) + // HashGet invokes GET /{hash} operation. + // + // Redirect client to long URL. + // + // GET /{hash} + HashGet(ctx context.Context, params HashGetParams) (HashGetRes, error) +} + +// Client implements OAS client. +type Client struct { + serverURL *url.URL + baseClient +} + +var _ Handler = struct { + *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 +} + +// CreatePost invokes POST /create operation. +// +// POST /create +func (c *Client) CreatePost(ctx context.Context, request *CreatePostReq) (CreatePostRes, error) { + res, err := c.sendCreatePost(ctx, request) + return res, err +} + +func (c *Client) sendCreatePost(ctx context.Context, request *CreatePostReq) (res CreatePostRes, err error) { + otelAttrs := []attribute.KeyValue{ + semconv.HTTPMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/create"), + } + + // 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, "CreatePost", + 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] = "/create" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeCreatePostRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeCreatePostResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// HashGet invokes GET /{hash} operation. +// +// Redirect client to long URL. +// +// GET /{hash} +func (c *Client) HashGet(ctx context.Context, params HashGetParams) (HashGetRes, error) { + res, err := c.sendHashGet(ctx, params) + return res, err +} + +func (c *Client) sendHashGet(ctx context.Context, params HashGetParams) (res HashGetRes, err error) { + otelAttrs := []attribute.KeyValue{ + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/{hash}"), + } + + // 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, "HashGet", + 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] = "/" + { + // Encode "hash" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "hash", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.StringToString(params.Hash)) + }(); 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 := decodeHashGetResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} diff --git a/internal/oas/oas_handlers_gen.go b/internal/oas/oas_handlers_gen.go new file mode 100644 index 0000000..7bbc225 --- /dev/null +++ b/internal/oas/oas_handlers_gen.go @@ -0,0 +1,228 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +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" +) + +// handleCreatePostRequest handles POST /create operation. +// +// POST /create +func (s *Server) handleCreatePostRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + otelAttrs := []attribute.KeyValue{ + semconv.HTTPMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/create"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), "CreatePost", + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(float64(elapsedDuration)/float64(time.Millisecond)), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + s.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + s.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: "CreatePost", + ID: "", + } + ) + request, close, err := s.decodeCreatePostRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response CreatePostRes + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: "CreatePost", + OperationSummary: "", + OperationID: "", + Body: request, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = *CreatePostReq + Params = struct{} + Response = CreatePostRes + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.CreatePost(ctx, request) + return response, err + }, + ) + } else { + response, err = s.h.CreatePost(ctx, request) + } + if err != nil { + recordError("Internal", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + if err := encodeCreatePostResponse(response, w, span); err != nil { + recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleHashGetRequest handles GET /{hash} operation. +// +// Redirect client to long URL. +// +// GET /{hash} +func (s *Server) handleHashGetRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + otelAttrs := []attribute.KeyValue{ + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/{hash}"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), "HashGet", + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(float64(elapsedDuration)/float64(time.Millisecond)), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + s.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + s.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: "HashGet", + ID: "", + } + ) + params, err := decodeHashGetParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response HashGetRes + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: "HashGet", + OperationSummary: "", + OperationID: "", + Body: nil, + Params: middleware.Parameters{ + { + Name: "hash", + In: "path", + }: params.Hash, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = HashGetParams + Response = HashGetRes + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackHashGetParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.HashGet(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.HashGet(ctx, params) + } + if err != nil { + recordError("Internal", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + if err := encodeHashGetResponse(response, w, span); err != nil { + recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} diff --git a/internal/oas/oas_interfaces_gen.go b/internal/oas/oas_interfaces_gen.go new file mode 100644 index 0000000..3724f84 --- /dev/null +++ b/internal/oas/oas_interfaces_gen.go @@ -0,0 +1,10 @@ +// Code generated by ogen, DO NOT EDIT. +package oas + +type CreatePostRes interface { + createPostRes() +} + +type HashGetRes interface { + hashGetRes() +} diff --git a/internal/oas/oas_json_gen.go b/internal/oas/oas_json_gen.go new file mode 100644 index 0000000..401feef --- /dev/null +++ b/internal/oas/oas_json_gen.go @@ -0,0 +1,270 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +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 *CreatePostBadRequest) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *CreatePostBadRequest) encodeFields(e *jx.Encoder) { + { + if s.Message.Set { + e.FieldStart("message") + s.Message.Encode(e) + } + } +} + +var jsonFieldsNameOfCreatePostBadRequest = [1]string{ + 0: "message", +} + +// Decode decodes CreatePostBadRequest from json. +func (s *CreatePostBadRequest) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode CreatePostBadRequest to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "message": + if err := func() error { + s.Message.Reset() + if err := s.Message.Decode(d); 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 CreatePostBadRequest") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *CreatePostBadRequest) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *CreatePostBadRequest) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *CreatePostCreated) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *CreatePostCreated) encodeFields(e *jx.Encoder) { + { + if s.Shorten.Set { + e.FieldStart("shorten") + s.Shorten.Encode(e) + } + } +} + +var jsonFieldsNameOfCreatePostCreated = [1]string{ + 0: "shorten", +} + +// Decode decodes CreatePostCreated from json. +func (s *CreatePostCreated) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode CreatePostCreated to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "shorten": + if err := func() error { + s.Shorten.Reset() + if err := s.Shorten.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"shorten\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode CreatePostCreated") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *CreatePostCreated) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *CreatePostCreated) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *CreatePostReq) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *CreatePostReq) encodeFields(e *jx.Encoder) { + { + e.FieldStart("url") + e.Str(s.URL) + } +} + +var jsonFieldsNameOfCreatePostReq = [1]string{ + 0: "url", +} + +// Decode decodes CreatePostReq from json. +func (s *CreatePostReq) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode CreatePostReq to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "url": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Str() + s.URL = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"url\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode CreatePostReq") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfCreatePostReq) { + name = jsonFieldsNameOfCreatePostReq[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 *CreatePostReq) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *CreatePostReq) 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) +} diff --git a/internal/oas/oas_middleware_gen.go b/internal/oas/oas_middleware_gen.go new file mode 100644 index 0000000..7ba299f --- /dev/null +++ b/internal/oas/oas_middleware_gen.go @@ -0,0 +1,10 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "github.com/ogen-go/ogen/middleware" +) + +// Middleware is middleware type. +type Middleware = middleware.Middleware diff --git a/internal/oas/oas_parameters_gen.go b/internal/oas/oas_parameters_gen.go new file mode 100644 index 0000000..57071c8 --- /dev/null +++ b/internal/oas/oas_parameters_gen.go @@ -0,0 +1,82 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +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" +) + +// HashGetParams is parameters of GET /{hash} operation. +type HashGetParams struct { + // Hash of shorten URL. + Hash string +} + +func unpackHashGetParams(packed middleware.Parameters) (params HashGetParams) { + { + key := middleware.ParameterKey{ + Name: "hash", + In: "path", + } + params.Hash = packed[key].(string) + } + return params +} + +func decodeHashGetParams(args [1]string, argsEscaped bool, r *http.Request) (params HashGetParams, _ error) { + // Decode path: hash. + 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: "hash", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + params.Hash = c + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "hash", + In: "path", + Err: err, + } + } + return params, nil +} diff --git a/internal/oas/oas_request_decoders_gen.go b/internal/oas/oas_request_decoders_gen.go new file mode 100644 index 0000000..08ea3c1 --- /dev/null +++ b/internal/oas/oas_request_decoders_gen.go @@ -0,0 +1,79 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "io" + "mime" + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + "go.uber.org/multierr" + + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/validate" +) + +func (s *Server) decodeCreatePostRequest(r *http.Request) ( + req *CreatePostReq, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = multierr.Append(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = multierr.Append(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + if r.ContentLength == 0 { + return req, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + if err != nil { + return req, close, err + } + + if len(buf) == 0 { + return req, close, validate.ErrBodyRequired + } + + d := jx.DecodeBytes(buf) + + var request CreatePostReq + if err := func() error { + if err := request.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return req, close, err + } + return &request, close, nil + default: + return req, close, validate.InvalidContentType(ct) + } +} diff --git a/internal/oas/oas_request_encoders_gen.go b/internal/oas/oas_request_encoders_gen.go new file mode 100644 index 0000000..3cf44b5 --- /dev/null +++ b/internal/oas/oas_request_encoders_gen.go @@ -0,0 +1,26 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "bytes" + "net/http" + + "github.com/go-faster/jx" + + ht "github.com/ogen-go/ogen/http" +) + +func encodeCreatePostRequest( + req *CreatePostReq, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} diff --git a/internal/oas/oas_response_decoders_gen.go b/internal/oas/oas_response_decoders_gen.go new file mode 100644 index 0000000..fcea426 --- /dev/null +++ b/internal/oas/oas_response_decoders_gen.go @@ -0,0 +1,144 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "io" + "mime" + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + + "github.com/ogen-go/ogen/conv" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/uri" + "github.com/ogen-go/ogen/validate" +) + +func decodeCreatePostResponse(resp *http.Response) (res CreatePostRes, _ error) { + switch resp.StatusCode { + case 201: + // Code 201. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response CreatePostCreated + 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) + } + case 400: + // Code 400. + 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 CreatePostBadRequest + 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) + } + } + return res, validate.UnexpectedStatusCode(resp.StatusCode) +} + +func decodeHashGetResponse(resp *http.Response) (res HashGetRes, _ error) { + switch resp.StatusCode { + case 307: + // Code 307. + var wrapper HashGetTemporaryRedirect + h := uri.NewHeaderDecoder(resp.Header) + // Parse "Location" header. + { + cfg := uri.HeaderParameterDecodingConfig{ + Name: "Location", + Explode: false, + } + if err := func() error { + if err := h.HasParam(cfg); err == nil { + if err := h.DecodeParam(cfg, func(d uri.Decoder) error { + var wrapperDotLocationVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + wrapperDotLocationVal = c + return nil + }(); err != nil { + return err + } + wrapper.Location.SetTo(wrapperDotLocationVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "parse Location header") + } + } + return &wrapper, nil + case 404: + // Code 404. + return &HashGetNotFound{}, nil + } + return res, validate.UnexpectedStatusCode(resp.StatusCode) +} diff --git a/internal/oas/oas_response_encoders_gen.go b/internal/oas/oas_response_encoders_gen.go new file mode 100644 index 0000000..fe87f3b --- /dev/null +++ b/internal/oas/oas_response_encoders_gen.go @@ -0,0 +1,86 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + + "github.com/ogen-go/ogen/conv" + "github.com/ogen-go/ogen/uri" +) + +func encodeCreatePostResponse(response CreatePostRes, w http.ResponseWriter, span trace.Span) error { + switch response := response.(type) { + case *CreatePostCreated: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(201) + span.SetStatus(codes.Ok, http.StatusText(201)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + case *CreatePostBadRequest: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(400) + span.SetStatus(codes.Error, http.StatusText(400)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + default: + return errors.Errorf("unexpected response type: %T", response) + } +} + +func encodeHashGetResponse(response HashGetRes, w http.ResponseWriter, span trace.Span) error { + switch response := response.(type) { + case *HashGetTemporaryRedirect: + // Encoding response headers. + { + h := uri.NewHeaderEncoder(w.Header()) + // Encode "Location" header. + { + cfg := uri.HeaderParameterEncodingConfig{ + Name: "Location", + Explode: false, + } + if err := h.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := response.Location.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode Location header") + } + } + } + w.WriteHeader(307) + span.SetStatus(codes.Ok, http.StatusText(307)) + + return nil + + case *HashGetNotFound: + w.WriteHeader(404) + span.SetStatus(codes.Error, http.StatusText(404)) + + return nil + + default: + return errors.Errorf("unexpected response type: %T", response) + } +} diff --git a/internal/oas/oas_router_gen.go b/internal/oas/oas_router_gen.go new file mode 100644 index 0000000..c8d720f --- /dev/null +++ b/internal/oas/oas_router_gen.go @@ -0,0 +1,249 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +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 'c': // Prefix: "create" + origElem := elem + if l := len("create"); len(elem) >= l && elem[0:l] == "create" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleCreatePostRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + elem = origElem + } + // Param: "hash" + // Leaf parameter + args[0] = elem + elem = "" + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleHashGetRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + + 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 'c': // Prefix: "create" + origElem := elem + if l := len("create"); len(elem) >= l && elem[0:l] == "create" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch method { + case "POST": + // Leaf: CreatePost + r.name = "CreatePost" + r.summary = "" + r.operationID = "" + r.pathPattern = "/create" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + elem = origElem + } + // Param: "hash" + // Leaf parameter + args[0] = elem + elem = "" + + if len(elem) == 0 { + switch method { + case "GET": + // Leaf: HashGet + r.name = "HashGet" + r.summary = "" + r.operationID = "" + r.pathPattern = "/{hash}" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + elem = origElem + } + } + return r, false +} diff --git a/internal/oas/oas_schemas_gen.go b/internal/oas/oas_schemas_gen.go new file mode 100644 index 0000000..ef4d4ae --- /dev/null +++ b/internal/oas/oas_schemas_gen.go @@ -0,0 +1,119 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +type CreatePostBadRequest struct { + Message OptString `json:"message"` +} + +// GetMessage returns the value of Message. +func (s *CreatePostBadRequest) GetMessage() OptString { + return s.Message +} + +// SetMessage sets the value of Message. +func (s *CreatePostBadRequest) SetMessage(val OptString) { + s.Message = val +} + +func (*CreatePostBadRequest) createPostRes() {} + +type CreatePostCreated struct { + // Created shorten URL. Going to this URL should redirect to URL from request body. + Shorten OptString `json:"shorten"` +} + +// GetShorten returns the value of Shorten. +func (s *CreatePostCreated) GetShorten() OptString { + return s.Shorten +} + +// SetShorten sets the value of Shorten. +func (s *CreatePostCreated) SetShorten(val OptString) { + s.Shorten = val +} + +func (*CreatePostCreated) createPostRes() {} + +type CreatePostReq struct { + // URL to shorten. + URL string `json:"url"` +} + +// GetURL returns the value of URL. +func (s *CreatePostReq) GetURL() string { + return s.URL +} + +// SetURL sets the value of URL. +func (s *CreatePostReq) SetURL(val string) { + s.URL = val +} + +// HashGetNotFound is response for HashGet operation. +type HashGetNotFound struct{} + +func (*HashGetNotFound) hashGetRes() {} + +// HashGetTemporaryRedirect is response for HashGet operation. +type HashGetTemporaryRedirect struct { + Location OptString +} + +// GetLocation returns the value of Location. +func (s *HashGetTemporaryRedirect) GetLocation() OptString { + return s.Location +} + +// SetLocation sets the value of Location. +func (s *HashGetTemporaryRedirect) SetLocation(val OptString) { + s.Location = val +} + +func (*HashGetTemporaryRedirect) hashGetRes() {} + +// 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 +} diff --git a/internal/oas/oas_server_gen.go b/internal/oas/oas_server_gen.go new file mode 100644 index 0000000..cbbf569 --- /dev/null +++ b/internal/oas/oas_server_gen.go @@ -0,0 +1,40 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "context" +) + +// Handler handles operations described by OpenAPI v3 specification. +type Handler interface { + // CreatePost implements POST /create operation. + // + // POST /create + CreatePost(ctx context.Context, req *CreatePostReq) (CreatePostRes, error) + // HashGet implements GET /{hash} operation. + // + // Redirect client to long URL. + // + // GET /{hash} + HashGet(ctx context.Context, params HashGetParams) (HashGetRes, error) +} + +// 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/internal/oas/oas_unimplemented_gen.go b/internal/oas/oas_unimplemented_gen.go new file mode 100644 index 0000000..1cfded8 --- /dev/null +++ b/internal/oas/oas_unimplemented_gen.go @@ -0,0 +1,30 @@ +// Code generated by ogen, DO NOT EDIT. + +package oas + +import ( + "context" + + ht "github.com/ogen-go/ogen/http" +) + +// UnimplementedHandler is no-op Handler which returns http.ErrNotImplemented. +type UnimplementedHandler struct{} + +var _ Handler = UnimplementedHandler{} + +// CreatePost implements POST /create operation. +// +// POST /create +func (UnimplementedHandler) CreatePost(ctx context.Context, req *CreatePostReq) (r CreatePostRes, _ error) { + return r, ht.ErrNotImplemented +} + +// HashGet implements GET /{hash} operation. +// +// Redirect client to long URL. +// +// GET /{hash} +func (UnimplementedHandler) HashGet(ctx context.Context, params HashGetParams) (r HashGetRes, _ error) { + return r, ht.ErrNotImplemented +} diff --git a/openapi.yaml b/openapi.yaml index b7bebb8..7272273 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -51,7 +51,6 @@ paths: shorten: type: string description: Created shorten URL. Going to this URL should redirect to URL from request body. - examples: {} "400": description: Bad Request content: @@ -65,5 +64,3 @@ paths: Example 1: value: message: URL already exist. -components: - schemas: {}