mirror of
https://github.com/derfenix/webarchive.git
synced 2026-03-11 12:41:54 +03:00
Improve API
This commit is contained in:
@@ -62,6 +62,22 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$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:
|
||||
$ref: '#/components/responses/undefinedError'
|
||||
|
||||
@@ -109,7 +125,7 @@ paths:
|
||||
200:
|
||||
description: File content
|
||||
content:
|
||||
application/pdf: {}
|
||||
application/pdf: { }
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
@@ -76,13 +76,13 @@ func (c *Client) requestURL(ctx context.Context) *url.URL {
|
||||
// Add new page.
|
||||
//
|
||||
// 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
|
||||
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{
|
||||
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 {
|
||||
mreq := middleware.Request{
|
||||
Context: ctx,
|
||||
@@ -113,7 +113,7 @@ func (s *Server) handleAddPageRequest(args [0]string, argsEscaped bool, w http.R
|
||||
type (
|
||||
Request = OptAddPageReq
|
||||
Params = AddPageParams
|
||||
Response = *Page
|
||||
Response = AddPageRes
|
||||
)
|
||||
response, err = middleware.HookMiddleware[
|
||||
Request,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
package openapi
|
||||
|
||||
type AddPageRes interface {
|
||||
addPageRes()
|
||||
}
|
||||
|
||||
type GetFileRes interface {
|
||||
getFileRes()
|
||||
}
|
||||
|
||||
@@ -13,6 +13,121 @@ import (
|
||||
"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.
|
||||
func (s *AddPageReq) Encode(e *jx.Encoder) {
|
||||
e.ObjStart()
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"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 {
|
||||
case 201:
|
||||
// Code 201.
|
||||
@@ -52,6 +52,41 @@ func decodeAddPageResponse(resp *http.Response) (res *Page, err error) {
|
||||
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 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.
|
||||
defRes, err := func() (res *ErrorStatusCode, err error) {
|
||||
|
||||
@@ -12,17 +12,35 @@ import (
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
func encodeAddPageResponse(response *Page, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(201)
|
||||
span.SetStatus(codes.Ok, http.StatusText(201))
|
||||
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.WriteHeader(201)
|
||||
span.SetStatus(codes.Ok, http.StatusText(201))
|
||||
|
||||
e := jx.GetEncoder()
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
e := jx.GetEncoder()
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
URL string `json:"url"`
|
||||
Description OptString `json:"description"`
|
||||
@@ -349,6 +376,8 @@ func (s *Page) SetStatus(val Status) {
|
||||
s.Status = val
|
||||
}
|
||||
|
||||
func (*Page) addPageRes() {}
|
||||
|
||||
// Merged schema.
|
||||
// Ref: #/components/schemas/pageWithResults
|
||||
type PageWithResults struct {
|
||||
|
||||
@@ -13,7 +13,7 @@ type Handler interface {
|
||||
// Add new page.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Get file content.
|
||||
|
||||
@@ -18,7 +18,7 @@ var _ Handler = UnimplementedHandler{}
|
||||
// Add new page.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/derfenix/webarchive/api/openapi"
|
||||
"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
|
||||
|
||||
switch {
|
||||
@@ -112,11 +114,14 @@ func FormatFromRest(format []openapi.Format) []entity.Format {
|
||||
|
||||
case openapi.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 {
|
||||
|
||||
@@ -41,7 +41,7 @@ func (s *Service) GetPage(ctx context.Context, params openapi.GetPageParams) (op
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
err := s.pages.Save(ctx, page)
|
||||
if err != nil {
|
||||
if err := s.pages.Save(ctx, page); err != nil {
|
||||
return nil, fmt.Errorf("save page: %w", err)
|
||||
}
|
||||
|
||||
s.ch <- page
|
||||
|
||||
res := PageToRest(page)
|
||||
|
||||
s.ch <- page
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user