mirror of
https://github.com/derfenix/webarchive.git
synced 2026-03-11 21:35:34 +03:00
Improve API
This commit is contained in:
@@ -62,6 +62,22 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/page'
|
$ref: '#/components/schemas/page'
|
||||||
|
400:
|
||||||
|
description: Bad request
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
field:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
required:
|
||||||
|
- error
|
||||||
|
- field
|
||||||
default:
|
default:
|
||||||
$ref: '#/components/responses/undefinedError'
|
$ref: '#/components/responses/undefinedError'
|
||||||
|
|
||||||
|
|||||||
@@ -76,13 +76,13 @@ func (c *Client) requestURL(ctx context.Context) *url.URL {
|
|||||||
// Add new page.
|
// Add new page.
|
||||||
//
|
//
|
||||||
// POST /pages
|
// POST /pages
|
||||||
func (c *Client) AddPage(ctx context.Context, request OptAddPageReq, params AddPageParams) (*Page, error) {
|
func (c *Client) AddPage(ctx context.Context, request OptAddPageReq, params AddPageParams) (AddPageRes, error) {
|
||||||
res, err := c.sendAddPage(ctx, request, params)
|
res, err := c.sendAddPage(ctx, request, params)
|
||||||
_ = res
|
_ = res
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) sendAddPage(ctx context.Context, request OptAddPageReq, params AddPageParams) (res *Page, err error) {
|
func (c *Client) sendAddPage(ctx context.Context, request OptAddPageReq, params AddPageParams) (res AddPageRes, err error) {
|
||||||
otelAttrs := []attribute.KeyValue{
|
otelAttrs := []attribute.KeyValue{
|
||||||
otelogen.OperationID("addPage"),
|
otelogen.OperationID("addPage"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ func (s *Server) handleAddPageRequest(args [0]string, argsEscaped bool, w http.R
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var response *Page
|
var response AddPageRes
|
||||||
if m := s.cfg.Middleware; m != nil {
|
if m := s.cfg.Middleware; m != nil {
|
||||||
mreq := middleware.Request{
|
mreq := middleware.Request{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
@@ -113,7 +113,7 @@ func (s *Server) handleAddPageRequest(args [0]string, argsEscaped bool, w http.R
|
|||||||
type (
|
type (
|
||||||
Request = OptAddPageReq
|
Request = OptAddPageReq
|
||||||
Params = AddPageParams
|
Params = AddPageParams
|
||||||
Response = *Page
|
Response = AddPageRes
|
||||||
)
|
)
|
||||||
response, err = middleware.HookMiddleware[
|
response, err = middleware.HookMiddleware[
|
||||||
Request,
|
Request,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
// Code generated by ogen, DO NOT EDIT.
|
// Code generated by ogen, DO NOT EDIT.
|
||||||
package openapi
|
package openapi
|
||||||
|
|
||||||
|
type AddPageRes interface {
|
||||||
|
addPageRes()
|
||||||
|
}
|
||||||
|
|
||||||
type GetFileRes interface {
|
type GetFileRes interface {
|
||||||
getFileRes()
|
getFileRes()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,121 @@ import (
|
|||||||
"github.com/ogen-go/ogen/validate"
|
"github.com/ogen-go/ogen/validate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Encode implements json.Marshaler.
|
||||||
|
func (s *AddPageBadRequest) Encode(e *jx.Encoder) {
|
||||||
|
e.ObjStart()
|
||||||
|
s.encodeFields(e)
|
||||||
|
e.ObjEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeFields encodes fields.
|
||||||
|
func (s *AddPageBadRequest) encodeFields(e *jx.Encoder) {
|
||||||
|
{
|
||||||
|
|
||||||
|
e.FieldStart("field")
|
||||||
|
e.Str(s.Field)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
|
||||||
|
e.FieldStart("error")
|
||||||
|
e.Str(s.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonFieldsNameOfAddPageBadRequest = [2]string{
|
||||||
|
0: "field",
|
||||||
|
1: "error",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes AddPageBadRequest from json.
|
||||||
|
func (s *AddPageBadRequest) Decode(d *jx.Decoder) error {
|
||||||
|
if s == nil {
|
||||||
|
return errors.New("invalid: unable to decode AddPageBadRequest to nil")
|
||||||
|
}
|
||||||
|
var requiredBitSet [1]uint8
|
||||||
|
|
||||||
|
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
|
||||||
|
switch string(k) {
|
||||||
|
case "field":
|
||||||
|
requiredBitSet[0] |= 1 << 0
|
||||||
|
if err := func() error {
|
||||||
|
v, err := d.Str()
|
||||||
|
s.Field = string(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}(); err != nil {
|
||||||
|
return errors.Wrap(err, "decode field \"field\"")
|
||||||
|
}
|
||||||
|
case "error":
|
||||||
|
requiredBitSet[0] |= 1 << 1
|
||||||
|
if err := func() error {
|
||||||
|
v, err := d.Str()
|
||||||
|
s.Error = string(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}(); err != nil {
|
||||||
|
return errors.Wrap(err, "decode field \"error\"")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return d.Skip()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "decode AddPageBadRequest")
|
||||||
|
}
|
||||||
|
// 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(jsonFieldsNameOfAddPageBadRequest) {
|
||||||
|
name = jsonFieldsNameOfAddPageBadRequest[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 *AddPageBadRequest) MarshalJSON() ([]byte, error) {
|
||||||
|
e := jx.Encoder{}
|
||||||
|
s.Encode(&e)
|
||||||
|
return e.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements stdjson.Unmarshaler.
|
||||||
|
func (s *AddPageBadRequest) UnmarshalJSON(data []byte) error {
|
||||||
|
d := jx.DecodeBytes(data)
|
||||||
|
return s.Decode(d)
|
||||||
|
}
|
||||||
|
|
||||||
// Encode implements json.Marshaler.
|
// Encode implements json.Marshaler.
|
||||||
func (s *AddPageReq) Encode(e *jx.Encoder) {
|
func (s *AddPageReq) Encode(e *jx.Encoder) {
|
||||||
e.ObjStart()
|
e.ObjStart()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/ogen-go/ogen/validate"
|
"github.com/ogen-go/ogen/validate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func decodeAddPageResponse(resp *http.Response) (res *Page, err error) {
|
func decodeAddPageResponse(resp *http.Response) (res AddPageRes, err error) {
|
||||||
switch resp.StatusCode {
|
switch resp.StatusCode {
|
||||||
case 201:
|
case 201:
|
||||||
// Code 201.
|
// Code 201.
|
||||||
@@ -52,6 +52,41 @@ func decodeAddPageResponse(resp *http.Response) (res *Page, err error) {
|
|||||||
default:
|
default:
|
||||||
return res, validate.InvalidContentType(ct)
|
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 AddPageBadRequest
|
||||||
|
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.
|
// Convenient error response.
|
||||||
defRes, err := func() (res *ErrorStatusCode, err error) {
|
defRes, err := func() (res *ErrorStatusCode, err error) {
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import (
|
|||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func encodeAddPageResponse(response *Page, w http.ResponseWriter, span trace.Span) error {
|
func encodeAddPageResponse(response AddPageRes, w http.ResponseWriter, span trace.Span) error {
|
||||||
|
switch response := response.(type) {
|
||||||
|
case *Page:
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(201)
|
w.WriteHeader(201)
|
||||||
span.SetStatus(codes.Ok, http.StatusText(201))
|
span.SetStatus(codes.Ok, http.StatusText(201))
|
||||||
@@ -23,6 +25,22 @@ func encodeAddPageResponse(response *Page, w http.ResponseWriter, span trace.Spa
|
|||||||
return errors.Wrap(err, "write")
|
return errors.Wrap(err, "write")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
case *AddPageBadRequest:
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(400)
|
||||||
|
span.SetStatus(codes.Error, http.StatusText(400))
|
||||||
|
|
||||||
|
e := jx.GetEncoder()
|
||||||
|
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 encodeGetFileResponse(response GetFileRes, w http.ResponseWriter, span trace.Span) error {
|
func encodeGetFileResponse(response GetFileRes, w http.ResponseWriter, span trace.Span) error {
|
||||||
|
|||||||
@@ -15,6 +15,33 @@ func (s *ErrorStatusCode) Error() string {
|
|||||||
return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response)
|
return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AddPageBadRequest struct {
|
||||||
|
Field string `json:"field"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetField returns the value of Field.
|
||||||
|
func (s *AddPageBadRequest) GetField() string {
|
||||||
|
return s.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetError returns the value of Error.
|
||||||
|
func (s *AddPageBadRequest) GetError() string {
|
||||||
|
return s.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetField sets the value of Field.
|
||||||
|
func (s *AddPageBadRequest) SetField(val string) {
|
||||||
|
s.Field = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetError sets the value of Error.
|
||||||
|
func (s *AddPageBadRequest) SetError(val string) {
|
||||||
|
s.Error = val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*AddPageBadRequest) addPageRes() {}
|
||||||
|
|
||||||
type AddPageReq struct {
|
type AddPageReq struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Description OptString `json:"description"`
|
Description OptString `json:"description"`
|
||||||
@@ -349,6 +376,8 @@ func (s *Page) SetStatus(val Status) {
|
|||||||
s.Status = val
|
s.Status = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*Page) addPageRes() {}
|
||||||
|
|
||||||
// Merged schema.
|
// Merged schema.
|
||||||
// Ref: #/components/schemas/pageWithResults
|
// Ref: #/components/schemas/pageWithResults
|
||||||
type PageWithResults struct {
|
type PageWithResults struct {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type Handler interface {
|
|||||||
// Add new page.
|
// Add new page.
|
||||||
//
|
//
|
||||||
// POST /pages
|
// POST /pages
|
||||||
AddPage(ctx context.Context, req OptAddPageReq, params AddPageParams) (*Page, error)
|
AddPage(ctx context.Context, req OptAddPageReq, params AddPageParams) (AddPageRes, error)
|
||||||
// GetFile implements getFile operation.
|
// GetFile implements getFile operation.
|
||||||
//
|
//
|
||||||
// Get file content.
|
// Get file content.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ var _ Handler = UnimplementedHandler{}
|
|||||||
// Add new page.
|
// Add new page.
|
||||||
//
|
//
|
||||||
// POST /pages
|
// POST /pages
|
||||||
func (UnimplementedHandler) AddPage(ctx context.Context, req OptAddPageReq, params AddPageParams) (r *Page, _ error) {
|
func (UnimplementedHandler) AddPage(ctx context.Context, req OptAddPageReq, params AddPageParams) (r AddPageRes, _ error) {
|
||||||
return r, ht.ErrNotImplemented
|
return r, ht.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package rest
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/derfenix/webarchive/api/openapi"
|
"github.com/derfenix/webarchive/api/openapi"
|
||||||
"github.com/derfenix/webarchive/entity"
|
"github.com/derfenix/webarchive/entity"
|
||||||
)
|
)
|
||||||
@@ -93,7 +95,7 @@ func StatusToRest(s entity.Status) openapi.Status {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FormatFromRest(format []openapi.Format) []entity.Format {
|
func FormatFromRest(format []openapi.Format) ([]entity.Format, error) {
|
||||||
var formats []entity.Format
|
var formats []entity.Format
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -112,11 +114,14 @@ func FormatFromRest(format []openapi.Format) []entity.Format {
|
|||||||
|
|
||||||
case openapi.FormatSingleFile:
|
case openapi.FormatSingleFile:
|
||||||
formats[i] = entity.FormatSingleFile
|
formats[i] = entity.FormatSingleFile
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid format value %s", format)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return formats
|
return formats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FormatToRest(format entity.Format) openapi.Format {
|
func FormatToRest(format entity.Format) openapi.Format {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (s *Service) GetPage(ctx context.Context, params openapi.GetPageParams) (op
|
|||||||
return &restPage, nil
|
return &restPage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) AddPage(ctx context.Context, req openapi.OptAddPageReq, params openapi.AddPageParams) (*openapi.Page, error) {
|
func (s *Service) AddPage(ctx context.Context, req openapi.OptAddPageReq, params openapi.AddPageParams) (openapi.AddPageRes, error) {
|
||||||
url := params.URL.Or(req.Value.URL)
|
url := params.URL.Or(req.Value.URL)
|
||||||
description := params.Description.Or(req.Value.Description.Value)
|
description := params.Description.Or(req.Value.Description.Value)
|
||||||
|
|
||||||
@@ -60,19 +60,32 @@ func (s *Service) AddPage(ctx context.Context, req openapi.OptAddPageReq, params
|
|||||||
url = params.URL.Value
|
url = params.URL.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
page := entity.NewPage(url, description, FormatFromRest(formats)...)
|
if url == "" {
|
||||||
|
return &openapi.AddPageBadRequest{
|
||||||
|
Field: "url",
|
||||||
|
Error: "Value is required",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
domainFormats, err := FormatFromRest(formats)
|
||||||
|
if err != nil {
|
||||||
|
return &openapi.AddPageBadRequest{
|
||||||
|
Field: "formats",
|
||||||
|
Error: err.Error(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
page := entity.NewPage(url, description, domainFormats...)
|
||||||
page.Status = entity.StatusProcessing
|
page.Status = entity.StatusProcessing
|
||||||
|
|
||||||
err := s.pages.Save(ctx, page)
|
if err := s.pages.Save(ctx, page); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("save page: %w", err)
|
return nil, fmt.Errorf("save page: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.ch <- page
|
|
||||||
|
|
||||||
res := PageToRest(page)
|
res := PageToRest(page)
|
||||||
|
|
||||||
|
s.ch <- page
|
||||||
|
|
||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user