From 8e03a1e0879118a37ecf31d9f460e73c0d03e6e3 Mon Sep 17 00:00:00 2001 From: Bastien Riviere Date: Mon, 29 Jan 2024 19:51:23 +0100 Subject: [PATCH] feat: implement retrieve by hash function --- .gitignore | 1 + go.mod | 20 +++++-- go.sum | 25 +++++++++ internal/api/handler.go | 38 +++++++++++++ internal/oas/oas_client_gen.go | 41 ++++++++------ internal/oas/oas_handlers_gen.go | 63 ++++++++++++---------- internal/oas/oas_interfaces_gen.go | 8 +-- internal/oas/oas_json_gen.go | 58 ++++++++++---------- internal/oas/oas_parameters_gen.go | 8 +-- internal/oas/oas_request_decoders_gen.go | 6 +-- internal/oas/oas_request_encoders_gen.go | 4 +- internal/oas/oas_response_decoders_gen.go | 12 ++--- internal/oas/oas_response_encoders_gen.go | 12 ++--- internal/oas/oas_router_gen.go | 16 +++--- internal/oas/oas_schemas_gen.go | 66 +++++++++++------------ internal/oas/oas_server_gen.go | 10 ++-- internal/oas/oas_unimplemented_gen.go | 10 ++-- main.go | 29 +++++++++- openapi.yaml | 3 ++ 19 files changed, 275 insertions(+), 155 deletions(-) create mode 100644 internal/api/handler.go diff --git a/.gitignore b/.gitignore index 3759cb6..d1c7360 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.dll *.so *.dylib +/short # Test binary, built with `go test -c` *.test diff --git a/go.mod b/go.mod index 9a5b87b..1e37340 100644 --- a/go.mod +++ b/go.mod @@ -2,20 +2,32 @@ module github.com/babariviere/short go 1.21.6 +require ( + github.com/go-faster/errors v0.7.1 + github.com/go-faster/jx v1.1.0 + github.com/jackc/pgx/v5 v5.5.2 + github.com/ogen-go/ogen v0.81.2 + go.opentelemetry.io/otel v1.22.0 + go.opentelemetry.io/otel/metric v1.22.0 + go.opentelemetry.io/otel/trace v1.22.0 + go.uber.org/multierr v1.11.0 +) + require ( github.com/dlclark/regexp2 v1.10.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-faster/errors v0.7.1 // indirect - github.com/go-faster/jx v1.1.0 // indirect github.com/go-faster/yaml v0.4.6 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.5.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/ogen-go/ogen v0.81.2 // indirect github.com/segmentio/asm v1.2.0 // indirect - go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect + golang.org/x/crypto v0.18.0 // indirect golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.20.0 // indirect diff --git a/go.sum b/go.sum index f41f8bd..3f1d0a8 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= @@ -10,8 +11,19 @@ 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.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/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/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.2 h1:iLlpgp4Cp/gC9Xuscl7lFL1PhhW+ZLtXZcrfCt4C3tA= +github.com/jackc/pgx/v5 v5.5.2/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= 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= @@ -19,12 +31,24 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/ogen-go/ogen v0.81.2 h1:Dj5vSgC/1oqLE5t0T5qd4ARgsKTupJWsh3rW9/C7Lvk= github.com/ogen-go/ogen v0.81.2/go.mod h1:10Ch7SIzBMSLB8TVEt8KclMKkRyJ5qCh4Cfs0pdeoh8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= 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.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= @@ -44,3 +68,4 @@ golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/api/handler.go b/internal/api/handler.go new file mode 100644 index 0000000..a2d324b --- /dev/null +++ b/internal/api/handler.go @@ -0,0 +1,38 @@ +package api + +import ( + "context" + "errors" + "fmt" + + "github.com/babariviere/short/internal/db" + "github.com/babariviere/short/internal/oas" + "github.com/jackc/pgx/v5" +) + +var _ oas.Handler = (*handler)(nil) + +type handler struct { + oas.UnimplementedHandler + queries *db.Queries +} + +func NewHandler(queries *db.Queries) *handler { + return &handler{ + queries: queries, + } +} + +func (h *handler) RedirectLongURL(ctx context.Context, params oas.RedirectLongURLParams) (oas.RedirectLongURLRes, error) { + res, err := h.queries.GetURLByHash(ctx, params.Hash) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return &oas.RedirectLongURLNotFound{}, nil + } + return nil, fmt.Errorf("unable to fetch URL by hash: %w", err) + } + + return &oas.RedirectLongURLTemporaryRedirect{ + Location: oas.NewOptString(res.LongUrl), + }, nil +} diff --git a/internal/oas/oas_client_gen.go b/internal/oas/oas_client_gen.go index 381d105..347f100 100644 --- a/internal/oas/oas_client_gen.go +++ b/internal/oas/oas_client_gen.go @@ -17,21 +17,24 @@ import ( "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 { - // CreatePost invokes POST /create operation. + // CreateShortURL invokes createShortURL operation. + // + // Create a shorten URL. // // POST /create - CreatePost(ctx context.Context, request *CreatePostReq) (CreatePostRes, error) - // HashGet invokes GET /{hash} operation. + CreateShortURL(ctx context.Context, request *CreateShortURLReq) (CreateShortURLRes, error) + // RedirectLongURL invokes redirectLongURL operation. // // Redirect client to long URL. // // GET /{hash} - HashGet(ctx context.Context, params HashGetParams) (HashGetRes, error) + RedirectLongURL(ctx context.Context, params RedirectLongURLParams) (RedirectLongURLRes, error) } // Client implements OAS client. @@ -82,16 +85,19 @@ func (c *Client) requestURL(ctx context.Context) *url.URL { return u } -// CreatePost invokes POST /create operation. +// CreateShortURL invokes createShortURL operation. +// +// Create a shorten URL. // // POST /create -func (c *Client) CreatePost(ctx context.Context, request *CreatePostReq) (CreatePostRes, error) { - res, err := c.sendCreatePost(ctx, request) +func (c *Client) CreateShortURL(ctx context.Context, request *CreateShortURLReq) (CreateShortURLRes, error) { + res, err := c.sendCreateShortURL(ctx, request) return res, err } -func (c *Client) sendCreatePost(ctx context.Context, request *CreatePostReq) (res CreatePostRes, err error) { +func (c *Client) sendCreateShortURL(ctx context.Context, request *CreateShortURLReq) (res CreateShortURLRes, err error) { otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createShortURL"), semconv.HTTPMethodKey.String("POST"), semconv.HTTPRouteKey.String("/create"), } @@ -108,7 +114,7 @@ func (c *Client) sendCreatePost(ctx context.Context, request *CreatePostReq) (re c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) // Start a span for this request. - ctx, span := c.cfg.Tracer.Start(ctx, "CreatePost", + ctx, span := c.cfg.Tracer.Start(ctx, "CreateShortURL", trace.WithAttributes(otelAttrs...), clientSpanKind, ) @@ -134,7 +140,7 @@ func (c *Client) sendCreatePost(ctx context.Context, request *CreatePostReq) (re if err != nil { return res, errors.Wrap(err, "create request") } - if err := encodeCreatePostRequest(request, r); err != nil { + if err := encodeCreateShortURLRequest(request, r); err != nil { return res, errors.Wrap(err, "encode request") } @@ -146,7 +152,7 @@ func (c *Client) sendCreatePost(ctx context.Context, request *CreatePostReq) (re defer resp.Body.Close() stage = "DecodeResponse" - result, err := decodeCreatePostResponse(resp) + result, err := decodeCreateShortURLResponse(resp) if err != nil { return res, errors.Wrap(err, "decode response") } @@ -154,18 +160,19 @@ func (c *Client) sendCreatePost(ctx context.Context, request *CreatePostReq) (re return result, nil } -// HashGet invokes GET /{hash} operation. +// RedirectLongURL invokes redirectLongURL 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) +func (c *Client) RedirectLongURL(ctx context.Context, params RedirectLongURLParams) (RedirectLongURLRes, error) { + res, err := c.sendRedirectLongURL(ctx, params) return res, err } -func (c *Client) sendHashGet(ctx context.Context, params HashGetParams) (res HashGetRes, err error) { +func (c *Client) sendRedirectLongURL(ctx context.Context, params RedirectLongURLParams) (res RedirectLongURLRes, err error) { otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("redirectLongURL"), semconv.HTTPMethodKey.String("GET"), semconv.HTTPRouteKey.String("/{hash}"), } @@ -182,7 +189,7 @@ func (c *Client) sendHashGet(ctx context.Context, params HashGetParams) (res Has c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) // Start a span for this request. - ctx, span := c.cfg.Tracer.Start(ctx, "HashGet", + ctx, span := c.cfg.Tracer.Start(ctx, "RedirectLongURL", trace.WithAttributes(otelAttrs...), clientSpanKind, ) @@ -235,7 +242,7 @@ func (c *Client) sendHashGet(ctx context.Context, params HashGetParams) (res Has defer resp.Body.Close() stage = "DecodeResponse" - result, err := decodeHashGetResponse(resp) + result, err := decodeRedirectLongURLResponse(resp) if err != nil { return res, errors.Wrap(err, "decode response") } diff --git a/internal/oas/oas_handlers_gen.go b/internal/oas/oas_handlers_gen.go index 7bbc225..1b2446d 100644 --- a/internal/oas/oas_handlers_gen.go +++ b/internal/oas/oas_handlers_gen.go @@ -17,19 +17,23 @@ import ( 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" ) -// handleCreatePostRequest handles POST /create operation. +// handleCreateShortURLRequest handles createShortURL operation. +// +// Create a shorten URL. // // POST /create -func (s *Server) handleCreatePostRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { +func (s *Server) handleCreateShortURLRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("createShortURL"), semconv.HTTPMethodKey.String("POST"), semconv.HTTPRouteKey.String("/create"), } // Start a span for this request. - ctx, span := s.cfg.Tracer.Start(r.Context(), "CreatePost", + ctx, span := s.cfg.Tracer.Start(r.Context(), "CreateShortURL", trace.WithAttributes(otelAttrs...), serverSpanKind, ) @@ -54,11 +58,11 @@ func (s *Server) handleCreatePostRequest(args [0]string, argsEscaped bool, w htt } err error opErrContext = ogenerrors.OperationContext{ - Name: "CreatePost", - ID: "", + Name: "CreateShortURL", + ID: "createShortURL", } ) - request, close, err := s.decodeCreatePostRequest(r) + request, close, err := s.decodeCreateShortURLRequest(r) if err != nil { err = &ogenerrors.DecodeRequestError{ OperationContext: opErrContext, @@ -74,22 +78,22 @@ func (s *Server) handleCreatePostRequest(args [0]string, argsEscaped bool, w htt } }() - var response CreatePostRes + var response CreateShortURLRes if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, - OperationName: "CreatePost", + OperationName: "CreateShortURL", OperationSummary: "", - OperationID: "", + OperationID: "createShortURL", Body: request, Params: middleware.Parameters{}, Raw: r, } type ( - Request = *CreatePostReq + Request = *CreateShortURLReq Params = struct{} - Response = CreatePostRes + Response = CreateShortURLRes ) response, err = middleware.HookMiddleware[ Request, @@ -100,12 +104,12 @@ func (s *Server) handleCreatePostRequest(args [0]string, argsEscaped bool, w htt mreq, nil, func(ctx context.Context, request Request, params Params) (response Response, err error) { - response, err = s.h.CreatePost(ctx, request) + response, err = s.h.CreateShortURL(ctx, request) return response, err }, ) } else { - response, err = s.h.CreatePost(ctx, request) + response, err = s.h.CreateShortURL(ctx, request) } if err != nil { recordError("Internal", err) @@ -113,7 +117,7 @@ func (s *Server) handleCreatePostRequest(args [0]string, argsEscaped bool, w htt return } - if err := encodeCreatePostResponse(response, w, span); err != nil { + if err := encodeCreateShortURLResponse(response, w, span); err != nil { recordError("EncodeResponse", err) if !errors.Is(err, ht.ErrInternalServerErrorResponse) { s.cfg.ErrorHandler(ctx, w, r, err) @@ -122,19 +126,20 @@ func (s *Server) handleCreatePostRequest(args [0]string, argsEscaped bool, w htt } } -// handleHashGetRequest handles GET /{hash} operation. +// handleRedirectLongURLRequest handles redirectLongURL operation. // // Redirect client to long URL. // // GET /{hash} -func (s *Server) handleHashGetRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { +func (s *Server) handleRedirectLongURLRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("redirectLongURL"), semconv.HTTPMethodKey.String("GET"), semconv.HTTPRouteKey.String("/{hash}"), } // Start a span for this request. - ctx, span := s.cfg.Tracer.Start(r.Context(), "HashGet", + ctx, span := s.cfg.Tracer.Start(r.Context(), "RedirectLongURL", trace.WithAttributes(otelAttrs...), serverSpanKind, ) @@ -159,11 +164,11 @@ func (s *Server) handleHashGetRequest(args [1]string, argsEscaped bool, w http.R } err error opErrContext = ogenerrors.OperationContext{ - Name: "HashGet", - ID: "", + Name: "RedirectLongURL", + ID: "redirectLongURL", } ) - params, err := decodeHashGetParams(args, argsEscaped, r) + params, err := decodeRedirectLongURLParams(args, argsEscaped, r) if err != nil { err = &ogenerrors.DecodeParamsError{ OperationContext: opErrContext, @@ -174,13 +179,13 @@ func (s *Server) handleHashGetRequest(args [1]string, argsEscaped bool, w http.R return } - var response HashGetRes + var response RedirectLongURLRes if m := s.cfg.Middleware; m != nil { mreq := middleware.Request{ Context: ctx, - OperationName: "HashGet", + OperationName: "RedirectLongURL", OperationSummary: "", - OperationID: "", + OperationID: "redirectLongURL", Body: nil, Params: middleware.Parameters{ { @@ -193,8 +198,8 @@ func (s *Server) handleHashGetRequest(args [1]string, argsEscaped bool, w http.R type ( Request = struct{} - Params = HashGetParams - Response = HashGetRes + Params = RedirectLongURLParams + Response = RedirectLongURLRes ) response, err = middleware.HookMiddleware[ Request, @@ -203,14 +208,14 @@ func (s *Server) handleHashGetRequest(args [1]string, argsEscaped bool, w http.R ]( m, mreq, - unpackHashGetParams, + unpackRedirectLongURLParams, func(ctx context.Context, request Request, params Params) (response Response, err error) { - response, err = s.h.HashGet(ctx, params) + response, err = s.h.RedirectLongURL(ctx, params) return response, err }, ) } else { - response, err = s.h.HashGet(ctx, params) + response, err = s.h.RedirectLongURL(ctx, params) } if err != nil { recordError("Internal", err) @@ -218,7 +223,7 @@ func (s *Server) handleHashGetRequest(args [1]string, argsEscaped bool, w http.R return } - if err := encodeHashGetResponse(response, w, span); err != nil { + if err := encodeRedirectLongURLResponse(response, w, span); err != nil { recordError("EncodeResponse", err) if !errors.Is(err, ht.ErrInternalServerErrorResponse) { s.cfg.ErrorHandler(ctx, w, r, err) diff --git a/internal/oas/oas_interfaces_gen.go b/internal/oas/oas_interfaces_gen.go index 3724f84..8b64778 100644 --- a/internal/oas/oas_interfaces_gen.go +++ b/internal/oas/oas_interfaces_gen.go @@ -1,10 +1,10 @@ // Code generated by ogen, DO NOT EDIT. package oas -type CreatePostRes interface { - createPostRes() +type CreateShortURLRes interface { + createShortURLRes() } -type HashGetRes interface { - hashGetRes() +type RedirectLongURLRes interface { + redirectLongURLRes() } diff --git a/internal/oas/oas_json_gen.go b/internal/oas/oas_json_gen.go index 401feef..8d5b7d6 100644 --- a/internal/oas/oas_json_gen.go +++ b/internal/oas/oas_json_gen.go @@ -13,14 +13,14 @@ import ( ) // Encode implements json.Marshaler. -func (s *CreatePostBadRequest) Encode(e *jx.Encoder) { +func (s *CreateShortURLBadRequest) Encode(e *jx.Encoder) { e.ObjStart() s.encodeFields(e) e.ObjEnd() } // encodeFields encodes fields. -func (s *CreatePostBadRequest) encodeFields(e *jx.Encoder) { +func (s *CreateShortURLBadRequest) encodeFields(e *jx.Encoder) { { if s.Message.Set { e.FieldStart("message") @@ -29,14 +29,14 @@ func (s *CreatePostBadRequest) encodeFields(e *jx.Encoder) { } } -var jsonFieldsNameOfCreatePostBadRequest = [1]string{ +var jsonFieldsNameOfCreateShortURLBadRequest = [1]string{ 0: "message", } -// Decode decodes CreatePostBadRequest from json. -func (s *CreatePostBadRequest) Decode(d *jx.Decoder) error { +// Decode decodes CreateShortURLBadRequest from json. +func (s *CreateShortURLBadRequest) Decode(d *jx.Decoder) error { if s == nil { - return errors.New("invalid: unable to decode CreatePostBadRequest to nil") + return errors.New("invalid: unable to decode CreateShortURLBadRequest to nil") } if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { @@ -56,34 +56,34 @@ func (s *CreatePostBadRequest) Decode(d *jx.Decoder) error { } return nil }); err != nil { - return errors.Wrap(err, "decode CreatePostBadRequest") + return errors.Wrap(err, "decode CreateShortURLBadRequest") } return nil } // MarshalJSON implements stdjson.Marshaler. -func (s *CreatePostBadRequest) MarshalJSON() ([]byte, error) { +func (s *CreateShortURLBadRequest) MarshalJSON() ([]byte, error) { e := jx.Encoder{} s.Encode(&e) return e.Bytes(), nil } // UnmarshalJSON implements stdjson.Unmarshaler. -func (s *CreatePostBadRequest) UnmarshalJSON(data []byte) error { +func (s *CreateShortURLBadRequest) UnmarshalJSON(data []byte) error { d := jx.DecodeBytes(data) return s.Decode(d) } // Encode implements json.Marshaler. -func (s *CreatePostCreated) Encode(e *jx.Encoder) { +func (s *CreateShortURLCreated) Encode(e *jx.Encoder) { e.ObjStart() s.encodeFields(e) e.ObjEnd() } // encodeFields encodes fields. -func (s *CreatePostCreated) encodeFields(e *jx.Encoder) { +func (s *CreateShortURLCreated) encodeFields(e *jx.Encoder) { { if s.Shorten.Set { e.FieldStart("shorten") @@ -92,14 +92,14 @@ func (s *CreatePostCreated) encodeFields(e *jx.Encoder) { } } -var jsonFieldsNameOfCreatePostCreated = [1]string{ +var jsonFieldsNameOfCreateShortURLCreated = [1]string{ 0: "shorten", } -// Decode decodes CreatePostCreated from json. -func (s *CreatePostCreated) Decode(d *jx.Decoder) error { +// Decode decodes CreateShortURLCreated from json. +func (s *CreateShortURLCreated) Decode(d *jx.Decoder) error { if s == nil { - return errors.New("invalid: unable to decode CreatePostCreated to nil") + return errors.New("invalid: unable to decode CreateShortURLCreated to nil") } if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { @@ -119,48 +119,48 @@ func (s *CreatePostCreated) Decode(d *jx.Decoder) error { } return nil }); err != nil { - return errors.Wrap(err, "decode CreatePostCreated") + return errors.Wrap(err, "decode CreateShortURLCreated") } return nil } // MarshalJSON implements stdjson.Marshaler. -func (s *CreatePostCreated) MarshalJSON() ([]byte, error) { +func (s *CreateShortURLCreated) MarshalJSON() ([]byte, error) { e := jx.Encoder{} s.Encode(&e) return e.Bytes(), nil } // UnmarshalJSON implements stdjson.Unmarshaler. -func (s *CreatePostCreated) UnmarshalJSON(data []byte) error { +func (s *CreateShortURLCreated) UnmarshalJSON(data []byte) error { d := jx.DecodeBytes(data) return s.Decode(d) } // Encode implements json.Marshaler. -func (s *CreatePostReq) Encode(e *jx.Encoder) { +func (s *CreateShortURLReq) Encode(e *jx.Encoder) { e.ObjStart() s.encodeFields(e) e.ObjEnd() } // encodeFields encodes fields. -func (s *CreatePostReq) encodeFields(e *jx.Encoder) { +func (s *CreateShortURLReq) encodeFields(e *jx.Encoder) { { e.FieldStart("url") e.Str(s.URL) } } -var jsonFieldsNameOfCreatePostReq = [1]string{ +var jsonFieldsNameOfCreateShortURLReq = [1]string{ 0: "url", } -// Decode decodes CreatePostReq from json. -func (s *CreatePostReq) Decode(d *jx.Decoder) error { +// Decode decodes CreateShortURLReq from json. +func (s *CreateShortURLReq) Decode(d *jx.Decoder) error { if s == nil { - return errors.New("invalid: unable to decode CreatePostReq to nil") + return errors.New("invalid: unable to decode CreateShortURLReq to nil") } var requiredBitSet [1]uint8 @@ -183,7 +183,7 @@ func (s *CreatePostReq) Decode(d *jx.Decoder) error { } return nil }); err != nil { - return errors.Wrap(err, "decode CreatePostReq") + return errors.Wrap(err, "decode CreateShortURLReq") } // Validate required fields. var failures []validate.FieldError @@ -200,8 +200,8 @@ func (s *CreatePostReq) Decode(d *jx.Decoder) error { bitIdx := bits.TrailingZeros8(result) fieldIdx := i*8 + bitIdx var name string - if fieldIdx < len(jsonFieldsNameOfCreatePostReq) { - name = jsonFieldsNameOfCreatePostReq[fieldIdx] + if fieldIdx < len(jsonFieldsNameOfCreateShortURLReq) { + name = jsonFieldsNameOfCreateShortURLReq[fieldIdx] } else { name = strconv.Itoa(fieldIdx) } @@ -222,14 +222,14 @@ func (s *CreatePostReq) Decode(d *jx.Decoder) error { } // MarshalJSON implements stdjson.Marshaler. -func (s *CreatePostReq) MarshalJSON() ([]byte, error) { +func (s *CreateShortURLReq) MarshalJSON() ([]byte, error) { e := jx.Encoder{} s.Encode(&e) return e.Bytes(), nil } // UnmarshalJSON implements stdjson.Unmarshaler. -func (s *CreatePostReq) UnmarshalJSON(data []byte) error { +func (s *CreateShortURLReq) UnmarshalJSON(data []byte) error { d := jx.DecodeBytes(data) return s.Decode(d) } diff --git a/internal/oas/oas_parameters_gen.go b/internal/oas/oas_parameters_gen.go index 57071c8..63baafa 100644 --- a/internal/oas/oas_parameters_gen.go +++ b/internal/oas/oas_parameters_gen.go @@ -15,13 +15,13 @@ import ( "github.com/ogen-go/ogen/validate" ) -// HashGetParams is parameters of GET /{hash} operation. -type HashGetParams struct { +// RedirectLongURLParams is parameters of redirectLongURL operation. +type RedirectLongURLParams struct { // Hash of shorten URL. Hash string } -func unpackHashGetParams(packed middleware.Parameters) (params HashGetParams) { +func unpackRedirectLongURLParams(packed middleware.Parameters) (params RedirectLongURLParams) { { key := middleware.ParameterKey{ Name: "hash", @@ -32,7 +32,7 @@ func unpackHashGetParams(packed middleware.Parameters) (params HashGetParams) { return params } -func decodeHashGetParams(args [1]string, argsEscaped bool, r *http.Request) (params HashGetParams, _ error) { +func decodeRedirectLongURLParams(args [1]string, argsEscaped bool, r *http.Request) (params RedirectLongURLParams, _ error) { // Decode path: hash. if err := func() error { param := args[0] diff --git a/internal/oas/oas_request_decoders_gen.go b/internal/oas/oas_request_decoders_gen.go index 08ea3c1..7d1b436 100644 --- a/internal/oas/oas_request_decoders_gen.go +++ b/internal/oas/oas_request_decoders_gen.go @@ -15,8 +15,8 @@ import ( "github.com/ogen-go/ogen/validate" ) -func (s *Server) decodeCreatePostRequest(r *http.Request) ( - req *CreatePostReq, +func (s *Server) decodeCreateShortURLRequest(r *http.Request) ( + req *CreateShortURLReq, close func() error, rerr error, ) { @@ -55,7 +55,7 @@ func (s *Server) decodeCreatePostRequest(r *http.Request) ( d := jx.DecodeBytes(buf) - var request CreatePostReq + var request CreateShortURLReq if err := func() error { if err := request.Decode(d); err != nil { return err diff --git a/internal/oas/oas_request_encoders_gen.go b/internal/oas/oas_request_encoders_gen.go index 3cf44b5..ab65fc8 100644 --- a/internal/oas/oas_request_encoders_gen.go +++ b/internal/oas/oas_request_encoders_gen.go @@ -11,8 +11,8 @@ import ( ht "github.com/ogen-go/ogen/http" ) -func encodeCreatePostRequest( - req *CreatePostReq, +func encodeCreateShortURLRequest( + req *CreateShortURLReq, r *http.Request, ) error { const contentType = "application/json" diff --git a/internal/oas/oas_response_decoders_gen.go b/internal/oas/oas_response_decoders_gen.go index fcea426..29552e5 100644 --- a/internal/oas/oas_response_decoders_gen.go +++ b/internal/oas/oas_response_decoders_gen.go @@ -16,7 +16,7 @@ import ( "github.com/ogen-go/ogen/validate" ) -func decodeCreatePostResponse(resp *http.Response) (res CreatePostRes, _ error) { +func decodeCreateShortURLResponse(resp *http.Response) (res CreateShortURLRes, _ error) { switch resp.StatusCode { case 201: // Code 201. @@ -32,7 +32,7 @@ func decodeCreatePostResponse(resp *http.Response) (res CreatePostRes, _ error) } d := jx.DecodeBytes(buf) - var response CreatePostCreated + var response CreateShortURLCreated if err := func() error { if err := response.Decode(d); err != nil { return err @@ -67,7 +67,7 @@ func decodeCreatePostResponse(resp *http.Response) (res CreatePostRes, _ error) } d := jx.DecodeBytes(buf) - var response CreatePostBadRequest + var response CreateShortURLBadRequest if err := func() error { if err := response.Decode(d); err != nil { return err @@ -92,11 +92,11 @@ func decodeCreatePostResponse(resp *http.Response) (res CreatePostRes, _ error) return res, validate.UnexpectedStatusCode(resp.StatusCode) } -func decodeHashGetResponse(resp *http.Response) (res HashGetRes, _ error) { +func decodeRedirectLongURLResponse(resp *http.Response) (res RedirectLongURLRes, _ error) { switch resp.StatusCode { case 307: // Code 307. - var wrapper HashGetTemporaryRedirect + var wrapper RedirectLongURLTemporaryRedirect h := uri.NewHeaderDecoder(resp.Header) // Parse "Location" header. { @@ -138,7 +138,7 @@ func decodeHashGetResponse(resp *http.Response) (res HashGetRes, _ error) { return &wrapper, nil case 404: // Code 404. - return &HashGetNotFound{}, nil + return &RedirectLongURLNotFound{}, 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 index fe87f3b..918fcea 100644 --- a/internal/oas/oas_response_encoders_gen.go +++ b/internal/oas/oas_response_encoders_gen.go @@ -14,9 +14,9 @@ import ( "github.com/ogen-go/ogen/uri" ) -func encodeCreatePostResponse(response CreatePostRes, w http.ResponseWriter, span trace.Span) error { +func encodeCreateShortURLResponse(response CreateShortURLRes, w http.ResponseWriter, span trace.Span) error { switch response := response.(type) { - case *CreatePostCreated: + case *CreateShortURLCreated: w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(201) span.SetStatus(codes.Ok, http.StatusText(201)) @@ -29,7 +29,7 @@ func encodeCreatePostResponse(response CreatePostRes, w http.ResponseWriter, spa return nil - case *CreatePostBadRequest: + case *CreateShortURLBadRequest: w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(400) span.SetStatus(codes.Error, http.StatusText(400)) @@ -47,9 +47,9 @@ func encodeCreatePostResponse(response CreatePostRes, w http.ResponseWriter, spa } } -func encodeHashGetResponse(response HashGetRes, w http.ResponseWriter, span trace.Span) error { +func encodeRedirectLongURLResponse(response RedirectLongURLRes, w http.ResponseWriter, span trace.Span) error { switch response := response.(type) { - case *HashGetTemporaryRedirect: + case *RedirectLongURLTemporaryRedirect: // Encoding response headers. { h := uri.NewHeaderEncoder(w.Header()) @@ -74,7 +74,7 @@ func encodeHashGetResponse(response HashGetRes, w http.ResponseWriter, span trac return nil - case *HashGetNotFound: + case *RedirectLongURLNotFound: w.WriteHeader(404) span.SetStatus(codes.Error, http.StatusText(404)) diff --git a/internal/oas/oas_router_gen.go b/internal/oas/oas_router_gen.go index c8d720f..9c1c6e1 100644 --- a/internal/oas/oas_router_gen.go +++ b/internal/oas/oas_router_gen.go @@ -73,7 +73,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Leaf node. switch r.Method { case "POST": - s.handleCreatePostRequest([0]string{}, elemIsEscaped, w, r) + s.handleCreateShortURLRequest([0]string{}, elemIsEscaped, w, r) default: s.notAllowed(w, r, "POST") } @@ -92,7 +92,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Leaf node. switch r.Method { case "GET": - s.handleHashGetRequest([1]string{ + s.handleRedirectLongURLRequest([1]string{ args[0], }, elemIsEscaped, w, r) default: @@ -206,10 +206,10 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { if len(elem) == 0 { switch method { case "POST": - // Leaf: CreatePost - r.name = "CreatePost" + // Leaf: CreateShortURL + r.name = "CreateShortURL" r.summary = "" - r.operationID = "" + r.operationID = "createShortURL" r.pathPattern = "/create" r.args = args r.count = 0 @@ -229,10 +229,10 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { if len(elem) == 0 { switch method { case "GET": - // Leaf: HashGet - r.name = "HashGet" + // Leaf: RedirectLongURL + r.name = "RedirectLongURL" r.summary = "" - r.operationID = "" + r.operationID = "redirectLongURL" r.pathPattern = "/{hash}" r.args = args r.count = 1 diff --git a/internal/oas/oas_schemas_gen.go b/internal/oas/oas_schemas_gen.go index ef4d4ae..655b359 100644 --- a/internal/oas/oas_schemas_gen.go +++ b/internal/oas/oas_schemas_gen.go @@ -2,76 +2,54 @@ package oas -type CreatePostBadRequest struct { +type CreateShortURLBadRequest struct { Message OptString `json:"message"` } // GetMessage returns the value of Message. -func (s *CreatePostBadRequest) GetMessage() OptString { +func (s *CreateShortURLBadRequest) GetMessage() OptString { return s.Message } // SetMessage sets the value of Message. -func (s *CreatePostBadRequest) SetMessage(val OptString) { +func (s *CreateShortURLBadRequest) SetMessage(val OptString) { s.Message = val } -func (*CreatePostBadRequest) createPostRes() {} +func (*CreateShortURLBadRequest) createShortURLRes() {} -type CreatePostCreated struct { +type CreateShortURLCreated 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 { +func (s *CreateShortURLCreated) GetShorten() OptString { return s.Shorten } // SetShorten sets the value of Shorten. -func (s *CreatePostCreated) SetShorten(val OptString) { +func (s *CreateShortURLCreated) SetShorten(val OptString) { s.Shorten = val } -func (*CreatePostCreated) createPostRes() {} +func (*CreateShortURLCreated) createShortURLRes() {} -type CreatePostReq struct { +type CreateShortURLReq struct { // URL to shorten. URL string `json:"url"` } // GetURL returns the value of URL. -func (s *CreatePostReq) GetURL() string { +func (s *CreateShortURLReq) GetURL() string { return s.URL } // SetURL sets the value of URL. -func (s *CreatePostReq) SetURL(val string) { +func (s *CreateShortURLReq) 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{ @@ -117,3 +95,25 @@ func (o OptString) Or(d string) string { } return d } + +// RedirectLongURLNotFound is response for RedirectLongURL operation. +type RedirectLongURLNotFound struct{} + +func (*RedirectLongURLNotFound) redirectLongURLRes() {} + +// RedirectLongURLTemporaryRedirect is response for RedirectLongURL operation. +type RedirectLongURLTemporaryRedirect struct { + Location OptString +} + +// GetLocation returns the value of Location. +func (s *RedirectLongURLTemporaryRedirect) GetLocation() OptString { + return s.Location +} + +// SetLocation sets the value of Location. +func (s *RedirectLongURLTemporaryRedirect) SetLocation(val OptString) { + s.Location = val +} + +func (*RedirectLongURLTemporaryRedirect) redirectLongURLRes() {} diff --git a/internal/oas/oas_server_gen.go b/internal/oas/oas_server_gen.go index cbbf569..0de553e 100644 --- a/internal/oas/oas_server_gen.go +++ b/internal/oas/oas_server_gen.go @@ -8,16 +8,18 @@ import ( // Handler handles operations described by OpenAPI v3 specification. type Handler interface { - // CreatePost implements POST /create operation. + // CreateShortURL implements createShortURL operation. + // + // Create a shorten URL. // // POST /create - CreatePost(ctx context.Context, req *CreatePostReq) (CreatePostRes, error) - // HashGet implements GET /{hash} operation. + CreateShortURL(ctx context.Context, req *CreateShortURLReq) (CreateShortURLRes, error) + // RedirectLongURL implements redirectLongURL operation. // // Redirect client to long URL. // // GET /{hash} - HashGet(ctx context.Context, params HashGetParams) (HashGetRes, error) + RedirectLongURL(ctx context.Context, params RedirectLongURLParams) (RedirectLongURLRes, error) } // Server implements http server based on OpenAPI v3 specification and diff --git a/internal/oas/oas_unimplemented_gen.go b/internal/oas/oas_unimplemented_gen.go index 1cfded8..53658ea 100644 --- a/internal/oas/oas_unimplemented_gen.go +++ b/internal/oas/oas_unimplemented_gen.go @@ -13,18 +13,20 @@ type UnimplementedHandler struct{} var _ Handler = UnimplementedHandler{} -// CreatePost implements POST /create operation. +// CreateShortURL implements createShortURL operation. +// +// Create a shorten URL. // // POST /create -func (UnimplementedHandler) CreatePost(ctx context.Context, req *CreatePostReq) (r CreatePostRes, _ error) { +func (UnimplementedHandler) CreateShortURL(ctx context.Context, req *CreateShortURLReq) (r CreateShortURLRes, _ error) { return r, ht.ErrNotImplemented } -// HashGet implements GET /{hash} operation. +// RedirectLongURL implements redirectLongURL operation. // // Redirect client to long URL. // // GET /{hash} -func (UnimplementedHandler) HashGet(ctx context.Context, params HashGetParams) (r HashGetRes, _ error) { +func (UnimplementedHandler) RedirectLongURL(ctx context.Context, params RedirectLongURLParams) (r RedirectLongURLRes, _ error) { return r, ht.ErrNotImplemented } diff --git a/main.go b/main.go index cb765cd..0a6fbbb 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,32 @@ package main -import "fmt" +import ( + "context" + "log" + "net/http" + + "github.com/babariviere/short/internal/api" + "github.com/babariviere/short/internal/db" + "github.com/babariviere/short/internal/oas" + "github.com/jackc/pgx/v5" +) func main() { - fmt.Println("Hello, short!") + psql, err := pgx.Connect(context.Background(), "postgres://short:short@localhost:5432/short") + if err != nil { + log.Fatalf("cannot connect to database: %v", err) + } + + queries := db.New(psql) + handler := api.NewHandler(queries) + + srv, err := oas.NewServer(handler) + if err != nil { + log.Fatalf("failed te create OpenAPI server: %v", err) + } + + log.Println("Listening on :8080") + if err = http.ListenAndServe(":8080", srv); err != nil { + log.Fatal(err) + } } diff --git a/openapi.yaml b/openapi.yaml index 7272273..55a60c2 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -17,6 +17,7 @@ paths: "404": description: Not Found description: Redirect client to long URL. + operationId: redirectLongURL parameters: - schema: type: string @@ -64,3 +65,5 @@ paths: Example 1: value: message: URL already exist. + description: Create a shorten URL. + operationId: createShortURL