mirror of
https://github.com/derfenix/webarchive.git
synced 2026-03-11 12:41:54 +03:00
Initial commit
This commit is contained in:
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
.idea/**/aws.xml
|
||||
.idea/**/contentModel.xml
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
cmake-build-*/
|
||||
.idea/**/mongoSettings.xml
|
||||
*.iws
|
||||
out/
|
||||
.idea_modules/
|
||||
atlassian-ide-plugin.xml
|
||||
.idea/replstate.xml
|
||||
.idea/sonarlint/
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
.idea/httpRequests
|
||||
.idea/caches/build_file_checksums.ser
|
||||
*~
|
||||
.fuse_hidden*
|
||||
.directory
|
||||
.Trash-*
|
||||
.nfs*
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.test
|
||||
*.out
|
||||
go.work
|
||||
test.http
|
||||
db
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
10
.idea/go.imports.xml
generated
Normal file
10
.idea/go.imports.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GoImports">
|
||||
<option name="excludedPackages">
|
||||
<array>
|
||||
<option value="golang.org/x/net/context" />
|
||||
</array>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
18
.idea/golinter.xml
generated
Normal file
18
.idea/golinter.xml
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GoLinterSettings">
|
||||
<option name="enabledLinters">
|
||||
<list>
|
||||
<option value="ineffassign" />
|
||||
<option value="staticcheck" />
|
||||
<option value="govet" />
|
||||
<option value="typecheck" />
|
||||
<option value="errcheck" />
|
||||
<option value="unused" />
|
||||
<option value="gosimple" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="goLinterExe" value="$USER_HOME$/.local/bin/golangci-lint" />
|
||||
<option name="linterSelected" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/webarchive.iml" filepath="$PROJECT_DIR$/.idea/webarchive.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
9
.idea/webarchive.iml
generated
Normal file
9
.idea/webarchive.iml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
53
adapters/processors/headers.go
Normal file
53
adapters/processors/headers.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package processors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/derfenix/webarchive/entity"
|
||||
)
|
||||
|
||||
func NewHeaders(client *http.Client) *Headers {
|
||||
return &Headers{client: client}
|
||||
}
|
||||
|
||||
type Headers struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (h *Headers) Process(ctx context.Context, url string) ([]entity.File, error) {
|
||||
var (
|
||||
headersFile entity.File
|
||||
err error
|
||||
)
|
||||
|
||||
req, reqErr := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)
|
||||
if reqErr != nil {
|
||||
return nil, fmt.Errorf("create request: %w", reqErr)
|
||||
}
|
||||
|
||||
resp, doErr := h.client.Do(req)
|
||||
if doErr != nil {
|
||||
return nil, fmt.Errorf("call url: %w", doErr)
|
||||
}
|
||||
|
||||
headersFile, err = h.newFile(resp.Header)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new file from headers: %w", err)
|
||||
}
|
||||
|
||||
return []entity.File{headersFile}, nil
|
||||
}
|
||||
|
||||
func (h *Headers) newFile(headers http.Header) (entity.File, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
if err := headers.Write(buf); err != nil {
|
||||
return entity.File{}, fmt.Errorf("write headers: %w", err)
|
||||
}
|
||||
|
||||
return entity.NewFile("headers", buf.Bytes()), nil
|
||||
}
|
||||
52
adapters/processors/pdf.go
Normal file
52
adapters/processors/pdf.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package processors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SebastiaanKlippert/go-wkhtmltopdf"
|
||||
|
||||
"github.com/derfenix/webarchive/entity"
|
||||
)
|
||||
|
||||
func NewPDF() *PDF {
|
||||
return &PDF{}
|
||||
}
|
||||
|
||||
type PDF struct{}
|
||||
|
||||
func (P *PDF) Process(_ context.Context, url string) ([]entity.File, error) {
|
||||
gen, err := wkhtmltopdf.NewPDFGenerator()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new pdf generator: %w", err)
|
||||
}
|
||||
|
||||
gen.Dpi.Set(300)
|
||||
gen.PageSize.Set(wkhtmltopdf.PageSizeA4)
|
||||
gen.Orientation.Set(wkhtmltopdf.OrientationPortrait)
|
||||
gen.Grayscale.Set(false)
|
||||
gen.Title.Set(url)
|
||||
|
||||
page := wkhtmltopdf.NewPage(url)
|
||||
page.JavascriptDelay.Set(200)
|
||||
page.LoadMediaErrorHandling.Set("abort")
|
||||
page.FooterRight.Set("[page]")
|
||||
page.HeaderLeft.Set(url)
|
||||
page.HeaderRight.Set(time.Now().Format(time.DateOnly))
|
||||
page.FooterFontSize.Set(10)
|
||||
page.Zoom.Set(1)
|
||||
page.ViewportSize.Set("1920x1080")
|
||||
|
||||
gen.AddPage(page)
|
||||
|
||||
// Create PDF document in internal buffer
|
||||
err = gen.Create()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create pdf: %w", err)
|
||||
}
|
||||
|
||||
file := entity.NewFile("page.pdf", gen.Bytes())
|
||||
|
||||
return []entity.File{file}, nil
|
||||
}
|
||||
29
adapters/processors/pdf_test.go
Normal file
29
adapters/processors/pdf_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package processors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPDF_Process(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("skip test with external resource")
|
||||
}
|
||||
|
||||
files, err := (&PDF{}).Process(context.Background(), "https://github.com/SebastiaanKlippert/go-wkhtmltopdf")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, files, 1)
|
||||
|
||||
f := files[0]
|
||||
fmt.Println("ID ", f.ID)
|
||||
fmt.Println("Name ", f.Name)
|
||||
fmt.Println("MimeType ", f.MimeType)
|
||||
fmt.Println("Size ", f.Size)
|
||||
fmt.Println("Created ", f.Created.Format(time.RFC3339))
|
||||
}
|
||||
93
adapters/processors/processors.go
Normal file
93
adapters/processors/processors.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package processors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"time"
|
||||
|
||||
"github.com/derfenix/webarchive/entity"
|
||||
)
|
||||
|
||||
type processor interface {
|
||||
Process(ctx context.Context, url string) ([]entity.File, error)
|
||||
}
|
||||
|
||||
func NewProcessors() (*Processors, error) {
|
||||
jar, err := cookiejar.New(&cookiejar.Options{
|
||||
PublicSuffixList: nil,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create cookie jar: %w", err)
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: time.Second * 10,
|
||||
KeepAlive: time.Second * 10,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 20,
|
||||
MaxIdleConnsPerHost: 5,
|
||||
MaxConnsPerHost: 10,
|
||||
IdleConnTimeout: time.Second * 60,
|
||||
ResponseHeaderTimeout: time.Second * 20,
|
||||
MaxResponseHeaderBytes: 1024 * 1024 * 50,
|
||||
WriteBufferSize: 256,
|
||||
ReadBufferSize: 1024 * 64,
|
||||
ForceAttemptHTTP2: true,
|
||||
},
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
if len(via) > 3 {
|
||||
return fmt.Errorf("too many redirects")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Jar: jar,
|
||||
Timeout: time.Second * 30,
|
||||
}
|
||||
|
||||
procs := Processors{
|
||||
processors: map[entity.Format]processor{
|
||||
entity.FormatHeaders: NewHeaders(httpClient),
|
||||
entity.FormatPDF: NewPDF(),
|
||||
},
|
||||
}
|
||||
|
||||
return &procs, nil
|
||||
}
|
||||
|
||||
type Processors struct {
|
||||
processors map[entity.Format]processor
|
||||
}
|
||||
|
||||
func (p *Processors) Process(ctx context.Context, format entity.Format, url string) entity.Result {
|
||||
result := entity.Result{Format: format}
|
||||
|
||||
proc, ok := p.processors[format]
|
||||
if !ok {
|
||||
result.Err = fmt.Errorf("no processor registered for format %v", format)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
files, err := proc.Process(ctx, url)
|
||||
if err != nil {
|
||||
result.Err = fmt.Errorf("process: %w", err)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
result.Files = files
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Processors) Override(format entity.Format, proc processor) error {
|
||||
p.processors[format] = proc
|
||||
|
||||
return nil
|
||||
}
|
||||
BIN
adapters/processors/simplesample.pdf
Normal file
BIN
adapters/processors/simplesample.pdf
Normal file
Binary file not shown.
128
adapters/repository/badger/db.go
Normal file
128
adapters/repository/badger/db.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package badger
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/dgraph-io/badger/v4/options"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
backupStartPath = "backup_start.db"
|
||||
backupStopPath = "backup_stop.db"
|
||||
)
|
||||
|
||||
type BackupType uint8
|
||||
|
||||
const (
|
||||
BackupStart BackupType = iota
|
||||
BackupStop
|
||||
)
|
||||
|
||||
var ErrDBClosed = fmt.Errorf("database is closed")
|
||||
|
||||
type logger struct {
|
||||
*zap.SugaredLogger
|
||||
}
|
||||
|
||||
func (l *logger) Warningf(s string, i ...interface{}) {
|
||||
l.SugaredLogger.Warnf(s, i...)
|
||||
}
|
||||
|
||||
func NewBadger(dir string, log *zap.Logger) (*badger.DB, error) {
|
||||
opts := badger.DefaultOptions(dir)
|
||||
opts.Logger = &logger{SugaredLogger: log.Sugar()}
|
||||
opts.Compression = options.ZSTD
|
||||
opts.ZSTDCompressionLevel = 6
|
||||
|
||||
db, err := badger.Open(opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open database: %w", err)
|
||||
}
|
||||
|
||||
if err := Backup(db, BackupStart); err != nil {
|
||||
log.Error("backup on start failed", zap.Error(err))
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func Backup(db *badger.DB, bt BackupType) error {
|
||||
dir := db.Opts().Dir
|
||||
var backupPath string
|
||||
|
||||
switch bt {
|
||||
case BackupStart:
|
||||
backupPath = path.Join(dir, backupStartPath)
|
||||
case BackupStop:
|
||||
backupPath = path.Join(dir, backupStopPath)
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(backupPath, os.O_CREATE|os.O_WRONLY, os.FileMode(0600))
|
||||
if err != nil {
|
||||
return fmt.Errorf("open backup file %s: %w", backupPath, err)
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
_, err = db.Backup(file, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("backup: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Restore(db *badger.DB) error {
|
||||
dir := db.Opts().Dir
|
||||
|
||||
backupPathStart := path.Join(dir, backupStartPath)
|
||||
backupPathStop := path.Join(dir, backupStopPath)
|
||||
|
||||
startStat, err := os.Stat(backupPathStart)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("stat file %s: %w", backupPathStart, err)
|
||||
}
|
||||
|
||||
stopStat, err := os.Stat(backupPathStop)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("stat file %s: %w", backupPathStop, err)
|
||||
}
|
||||
|
||||
var backupFile string
|
||||
|
||||
switch {
|
||||
case stopStat != nil && startStat != nil:
|
||||
if stopStat.ModTime().After(startStat.ModTime()) {
|
||||
backupFile = backupPathStop
|
||||
} else {
|
||||
backupFile = backupPathStart
|
||||
}
|
||||
|
||||
case stopStat != nil:
|
||||
backupFile = backupPathStart
|
||||
|
||||
case startStat != nil:
|
||||
backupFile = backupPathStop
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(backupFile, os.O_RDONLY, os.FileMode(0600))
|
||||
if err != nil {
|
||||
return fmt.Errorf("open backup file %s: %w", backupFile, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
if err := db.Load(file, 20); err != nil {
|
||||
return fmt.Errorf("load backup: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
40
adapters/repository/badger/file.go
Normal file
40
adapters/repository/badger/file.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package badger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
|
||||
"github.com/derfenix/webarchive/entity"
|
||||
)
|
||||
|
||||
func NewFile(db *badger.DB) *File {
|
||||
return &File{db: db, prefix: []byte("file:")}
|
||||
}
|
||||
|
||||
type File struct {
|
||||
db *badger.DB
|
||||
prefix []byte
|
||||
}
|
||||
|
||||
func (f *File) SaveTx(_ context.Context, txn *badger.Txn, file *entity.File) error {
|
||||
if f.db.IsClosed() {
|
||||
return ErrDBClosed
|
||||
}
|
||||
|
||||
marshaled, err := marshal(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal data: %w", err)
|
||||
}
|
||||
|
||||
if err := txn.Set(f.key(file), marshaled); err != nil {
|
||||
return fmt.Errorf("put data: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *File) key(file *entity.File) []byte {
|
||||
return append(f.prefix, []byte(file.ID.String())...)
|
||||
}
|
||||
13
adapters/repository/badger/marshal.go
Normal file
13
adapters/repository/badger/marshal.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package badger
|
||||
|
||||
import (
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
func marshal(v interface{}) ([]byte, error) {
|
||||
return msgpack.Marshal(v)
|
||||
}
|
||||
|
||||
func unmarshal(b []byte, v interface{}) error {
|
||||
return msgpack.Unmarshal(b, v)
|
||||
}
|
||||
142
adapters/repository/badger/page.go
Normal file
142
adapters/repository/badger/page.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package badger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/derfenix/webarchive/entity"
|
||||
)
|
||||
|
||||
func NewPage(db *badger.DB, file *File) (*Page, error) {
|
||||
return &Page{
|
||||
db: db,
|
||||
prefix: []byte("page:"),
|
||||
file: file,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Page struct {
|
||||
db *badger.DB
|
||||
prefix []byte
|
||||
file *File
|
||||
}
|
||||
|
||||
func (p *Page) Save(ctx context.Context, site *entity.Page) error {
|
||||
if p.db.IsClosed() {
|
||||
return ErrDBClosed
|
||||
}
|
||||
|
||||
marshaled, err := marshal(site)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal data: %w", err)
|
||||
}
|
||||
|
||||
if err := p.db.Update(func(txn *badger.Txn) error {
|
||||
if err := txn.Set(p.key(site), marshaled); err != nil {
|
||||
return fmt.Errorf("put data: %w", err)
|
||||
}
|
||||
|
||||
for i, result := range site.Results.Results() {
|
||||
for j, file := range result.Files {
|
||||
if err := p.file.SaveTx(ctx, txn, &file); err != nil {
|
||||
return fmt.Errorf("save file %d (%s) for result %d: %w", j, file.ID.String(), i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("update db: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Page) Get(_ context.Context, id uuid.UUID) (*entity.Page, error) {
|
||||
site := entity.Page{ID: id}
|
||||
|
||||
err := p.db.View(func(txn *badger.Txn) error {
|
||||
data, err := txn.Get(p.key(&site))
|
||||
if err != nil {
|
||||
return fmt.Errorf("get data: %w", err)
|
||||
}
|
||||
|
||||
err = data.Value(func(val []byte) error {
|
||||
if err := unmarshal(val, &site); err != nil {
|
||||
return fmt.Errorf("unmarshal data: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get value: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("view: %w", err)
|
||||
}
|
||||
|
||||
return &site, nil
|
||||
}
|
||||
|
||||
func (p *Page) ListAll(ctx context.Context) ([]*entity.Page, error) {
|
||||
pages := make([]*entity.Page, 0, 100)
|
||||
|
||||
err := p.db.View(func(txn *badger.Txn) error {
|
||||
iterator := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||
|
||||
defer iterator.Close()
|
||||
|
||||
for iterator.Seek(p.prefix); iterator.ValidForPrefix(p.prefix); iterator.Next() {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return fmt.Errorf("context canceled: %w", err)
|
||||
}
|
||||
|
||||
var page entity.Page
|
||||
|
||||
err := iterator.Item().Value(func(val []byte) error {
|
||||
if err := unmarshal(val, &page); err != nil {
|
||||
return fmt.Errorf("unmarshal: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("get item: %w", err)
|
||||
}
|
||||
|
||||
pages = append(pages, &entity.Page{
|
||||
ID: page.ID,
|
||||
URL: page.URL,
|
||||
Description: page.Description,
|
||||
Created: page.Created,
|
||||
Formats: page.Formats,
|
||||
Version: page.Version,
|
||||
Status: page.Status,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("view: %w", err)
|
||||
}
|
||||
|
||||
sort.Slice(pages, func(i, j int) bool {
|
||||
return pages[i].Created.After(pages[j].Created)
|
||||
})
|
||||
|
||||
return pages, nil
|
||||
}
|
||||
|
||||
func (p *Page) key(site *entity.Page) []byte {
|
||||
return append(p.prefix, []byte(site.ID.String())...)
|
||||
}
|
||||
60
adapters/repository/badger/page_test.go
Normal file
60
adapters/repository/badger/page_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package badger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
"github.com/derfenix/webarchive/entity"
|
||||
)
|
||||
|
||||
func TestSite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("skip db test")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
tempDir, err := os.MkdirTemp(os.TempDir(), "badger_test")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
assert.NoError(t, os.RemoveAll(tempDir))
|
||||
})
|
||||
|
||||
log := zaptest.NewLogger(t)
|
||||
|
||||
db, err := NewBadger(tempDir, log.Named("db"))
|
||||
require.NoError(t, err)
|
||||
|
||||
siteRepo, err := NewPage(db, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("base path", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
site := entity.NewPage("https://google.com", "Save all google", entity.FormatPDF, entity.FormatSingleFile)
|
||||
site.Created = site.Created.Truncate(time.Microsecond)
|
||||
|
||||
err := siteRepo.Save(ctx, site)
|
||||
require.NoError(t, err)
|
||||
|
||||
storedSite, err := siteRepo.Get(ctx, site.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, site, storedSite)
|
||||
|
||||
all, err := siteRepo.ListAll(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, all, 1)
|
||||
|
||||
assert.Equal(t, site, all[0])
|
||||
})
|
||||
}
|
||||
3
api/gen.go
Normal file
3
api/gen.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package api
|
||||
|
||||
//go:generate go run github.com/ogen-go/ogen/cmd/ogen@v0.60.1 --target ./openapi -package openapi --clean openapi.yaml
|
||||
174
api/openapi.yaml
Normal file
174
api/openapi.yaml
Normal file
@@ -0,0 +1,174 @@
|
||||
openapi: 3.0.1
|
||||
info:
|
||||
title: Sample API
|
||||
description: API description in Markdown.
|
||||
version: 1.0.0
|
||||
servers:
|
||||
- url: 'https://api.example.com'
|
||||
paths:
|
||||
/pages:
|
||||
get:
|
||||
operationId: getPages
|
||||
summary: Get all pages
|
||||
responses:
|
||||
200:
|
||||
description: All pages data
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/pages'
|
||||
default:
|
||||
$ref: '#/components/responses/undefinedError'
|
||||
post:
|
||||
operationId: addPage
|
||||
summary: Add new page
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
formats:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/format'
|
||||
required:
|
||||
- url
|
||||
responses:
|
||||
201:
|
||||
description: Page added
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/page'
|
||||
default:
|
||||
$ref: '#/components/responses/undefinedError'
|
||||
|
||||
/pages/{id}:
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
get:
|
||||
operationId: getPage
|
||||
description: Get page details
|
||||
responses:
|
||||
200:
|
||||
description: Page data
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/pageWithResults'
|
||||
404:
|
||||
description: Page not found
|
||||
default:
|
||||
$ref: '#/components/responses/undefinedError'
|
||||
|
||||
components:
|
||||
responses:
|
||||
undefinedError:
|
||||
description: Undefined Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/error'
|
||||
schemas:
|
||||
format:
|
||||
type: string
|
||||
enum:
|
||||
- all
|
||||
- pdf
|
||||
- single_page
|
||||
- headers
|
||||
error:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
localized:
|
||||
type: string
|
||||
required:
|
||||
- message
|
||||
pages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/page'
|
||||
page:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
url:
|
||||
type: string
|
||||
created:
|
||||
type: string
|
||||
format: date-time
|
||||
formats:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/format'
|
||||
status:
|
||||
$ref: '#/components/schemas/status'
|
||||
required:
|
||||
- id
|
||||
- url
|
||||
- formats
|
||||
- status
|
||||
- created
|
||||
result:
|
||||
type: object
|
||||
properties:
|
||||
format:
|
||||
$ref: '#/components/schemas/format'
|
||||
error:
|
||||
type: string
|
||||
files:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
mimetype:
|
||||
type: string
|
||||
size:
|
||||
type: integer
|
||||
format: int64
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- mimetype
|
||||
- size
|
||||
required:
|
||||
- format
|
||||
- files
|
||||
pageWithResults:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/page'
|
||||
- type: object
|
||||
properties:
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/result'
|
||||
required:
|
||||
- results
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- new
|
||||
- processing
|
||||
- done
|
||||
- failed
|
||||
- with_errors
|
||||
277
api/openapi/oas_cfg_gen.go
Normal file
277
api/openapi/oas_cfg_gen.go
Normal file
@@ -0,0 +1,277 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/metric/instrument"
|
||||
"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 = metric.NewNoopMeterProvider()
|
||||
}
|
||||
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),
|
||||
(otelOptionFunc)(nil),
|
||||
}
|
||||
|
||||
func (o optionFunc[C]) applyServer(c *C) {
|
||||
o(c)
|
||||
}
|
||||
|
||||
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) {
|
||||
w.Header().Set("Allow", allowed)
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
},
|
||||
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 instrument.Int64Counter
|
||||
errors instrument.Int64Counter
|
||||
duration instrument.Int64Histogram
|
||||
}
|
||||
|
||||
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.Int64Histogram(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),
|
||||
(otelOptionFunc)(nil),
|
||||
}
|
||||
|
||||
func (o optionFunc[C]) applyClient(c *C) {
|
||||
o(c)
|
||||
}
|
||||
|
||||
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 instrument.Int64Counter
|
||||
errors instrument.Int64Counter
|
||||
duration instrument.Int64Histogram
|
||||
}
|
||||
|
||||
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.Int64Histogram(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 metric.NewNoopMeterProvider 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
|
||||
}
|
||||
})
|
||||
}
|
||||
319
api/openapi/oas_client_gen.go
Normal file
319
api/openapi/oas_client_gen.go
Normal file
@@ -0,0 +1,319 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
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/trace"
|
||||
|
||||
"github.com/ogen-go/ogen/conv"
|
||||
ht "github.com/ogen-go/ogen/http"
|
||||
"github.com/ogen-go/ogen/otelogen"
|
||||
"github.com/ogen-go/ogen/uri"
|
||||
)
|
||||
|
||||
// Client implements OAS client.
|
||||
type Client struct {
|
||||
serverURL *url.URL
|
||||
baseClient
|
||||
}
|
||||
type errorHandler interface {
|
||||
NewError(ctx context.Context, err error) *ErrorStatusCode
|
||||
}
|
||||
|
||||
var _ Handler = struct {
|
||||
errorHandler
|
||||
*Client
|
||||
}{}
|
||||
|
||||
func trimTrailingSlashes(u *url.URL) {
|
||||
u.Path = strings.TrimRight(u.Path, "/")
|
||||
u.RawPath = strings.TrimRight(u.RawPath, "/")
|
||||
}
|
||||
|
||||
// NewClient initializes new Client defined by OAS.
|
||||
func NewClient(serverURL string, opts ...ClientOption) (*Client, error) {
|
||||
u, err := url.Parse(serverURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trimTrailingSlashes(u)
|
||||
|
||||
c, err := newClientConfig(opts...).baseClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{
|
||||
serverURL: u,
|
||||
baseClient: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type serverURLKey struct{}
|
||||
|
||||
// WithServerURL sets context key to override server URL.
|
||||
func WithServerURL(ctx context.Context, u *url.URL) context.Context {
|
||||
return context.WithValue(ctx, serverURLKey{}, u)
|
||||
}
|
||||
|
||||
func (c *Client) requestURL(ctx context.Context) *url.URL {
|
||||
u, ok := ctx.Value(serverURLKey{}).(*url.URL)
|
||||
if !ok {
|
||||
return c.serverURL
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// AddPage invokes addPage operation.
|
||||
//
|
||||
// Add new page.
|
||||
//
|
||||
// POST /pages
|
||||
func (c *Client) AddPage(ctx context.Context, request OptAddPageReq) (*Page, error) {
|
||||
res, err := c.sendAddPage(ctx, request)
|
||||
_ = res
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (c *Client) sendAddPage(ctx context.Context, request OptAddPageReq) (res *Page, err error) {
|
||||
otelAttrs := []attribute.KeyValue{
|
||||
otelogen.OperationID("addPage"),
|
||||
}
|
||||
// Validate request before sending.
|
||||
if err := func() error {
|
||||
if request.Set {
|
||||
if err := func() error {
|
||||
if err := request.Value.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return res, errors.Wrap(err, "validate")
|
||||
}
|
||||
|
||||
// Run stopwatch.
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
elapsedDuration := time.Since(startTime)
|
||||
c.duration.Record(ctx, elapsedDuration.Microseconds(), otelAttrs...)
|
||||
}()
|
||||
|
||||
// Increment request counter.
|
||||
c.requests.Add(ctx, 1, otelAttrs...)
|
||||
|
||||
// Start a span for this request.
|
||||
ctx, span := c.cfg.Tracer.Start(ctx, "AddPage",
|
||||
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, otelAttrs...)
|
||||
}
|
||||
span.End()
|
||||
}()
|
||||
|
||||
stage = "BuildURL"
|
||||
u := uri.Clone(c.requestURL(ctx))
|
||||
var pathParts [1]string
|
||||
pathParts[0] = "/pages"
|
||||
uri.AddPathParts(u, pathParts[:]...)
|
||||
|
||||
stage = "EncodeRequest"
|
||||
r, err := ht.NewRequest(ctx, "POST", u, nil)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "create request")
|
||||
}
|
||||
if err := encodeAddPageRequest(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 := decodeAddPageResponse(resp)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "decode response")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetPage invokes getPage operation.
|
||||
//
|
||||
// Get page details.
|
||||
//
|
||||
// GET /pages/{id}
|
||||
func (c *Client) GetPage(ctx context.Context, params GetPageParams) (GetPageRes, error) {
|
||||
res, err := c.sendGetPage(ctx, params)
|
||||
_ = res
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (c *Client) sendGetPage(ctx context.Context, params GetPageParams) (res GetPageRes, err error) {
|
||||
otelAttrs := []attribute.KeyValue{
|
||||
otelogen.OperationID("getPage"),
|
||||
}
|
||||
|
||||
// Run stopwatch.
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
elapsedDuration := time.Since(startTime)
|
||||
c.duration.Record(ctx, elapsedDuration.Microseconds(), otelAttrs...)
|
||||
}()
|
||||
|
||||
// Increment request counter.
|
||||
c.requests.Add(ctx, 1, otelAttrs...)
|
||||
|
||||
// Start a span for this request.
|
||||
ctx, span := c.cfg.Tracer.Start(ctx, "GetPage",
|
||||
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, otelAttrs...)
|
||||
}
|
||||
span.End()
|
||||
}()
|
||||
|
||||
stage = "BuildURL"
|
||||
u := uri.Clone(c.requestURL(ctx))
|
||||
var pathParts [2]string
|
||||
pathParts[0] = "/pages/"
|
||||
{
|
||||
// Encode "id" parameter.
|
||||
e := uri.NewPathEncoder(uri.PathEncoderConfig{
|
||||
Param: "id",
|
||||
Style: uri.PathStyleSimple,
|
||||
Explode: false,
|
||||
})
|
||||
if err := func() error {
|
||||
return e.EncodeValue(conv.UUIDToString(params.ID))
|
||||
}(); 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, nil)
|
||||
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 := decodeGetPageResponse(resp)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "decode response")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetPages invokes getPages operation.
|
||||
//
|
||||
// Get all pages.
|
||||
//
|
||||
// GET /pages
|
||||
func (c *Client) GetPages(ctx context.Context) (Pages, error) {
|
||||
res, err := c.sendGetPages(ctx)
|
||||
_ = res
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (c *Client) sendGetPages(ctx context.Context) (res Pages, err error) {
|
||||
otelAttrs := []attribute.KeyValue{
|
||||
otelogen.OperationID("getPages"),
|
||||
}
|
||||
|
||||
// Run stopwatch.
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
elapsedDuration := time.Since(startTime)
|
||||
c.duration.Record(ctx, elapsedDuration.Microseconds(), otelAttrs...)
|
||||
}()
|
||||
|
||||
// Increment request counter.
|
||||
c.requests.Add(ctx, 1, otelAttrs...)
|
||||
|
||||
// Start a span for this request.
|
||||
ctx, span := c.cfg.Tracer.Start(ctx, "GetPages",
|
||||
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, otelAttrs...)
|
||||
}
|
||||
span.End()
|
||||
}()
|
||||
|
||||
stage = "BuildURL"
|
||||
u := uri.Clone(c.requestURL(ctx))
|
||||
var pathParts [1]string
|
||||
pathParts[0] = "/pages"
|
||||
uri.AddPathParts(u, pathParts[:]...)
|
||||
|
||||
stage = "EncodeRequest"
|
||||
r, err := ht.NewRequest(ctx, "GET", u, nil)
|
||||
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 := decodeGetPagesResponse(resp)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "decode response")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
331
api/openapi/oas_handlers_gen.go
Normal file
331
api/openapi/oas_handlers_gen.go
Normal file
@@ -0,0 +1,331 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
ht "github.com/ogen-go/ogen/http"
|
||||
"github.com/ogen-go/ogen/middleware"
|
||||
"github.com/ogen-go/ogen/ogenerrors"
|
||||
"github.com/ogen-go/ogen/otelogen"
|
||||
)
|
||||
|
||||
// handleAddPageRequest handles addPage operation.
|
||||
//
|
||||
// Add new page.
|
||||
//
|
||||
// POST /pages
|
||||
func (s *Server) handleAddPageRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
|
||||
otelAttrs := []attribute.KeyValue{
|
||||
otelogen.OperationID("addPage"),
|
||||
semconv.HTTPMethodKey.String("POST"),
|
||||
semconv.HTTPRouteKey.String("/pages"),
|
||||
}
|
||||
|
||||
// Start a span for this request.
|
||||
ctx, span := s.cfg.Tracer.Start(r.Context(), "AddPage",
|
||||
trace.WithAttributes(otelAttrs...),
|
||||
serverSpanKind,
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
// Run stopwatch.
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
elapsedDuration := time.Since(startTime)
|
||||
s.duration.Record(ctx, elapsedDuration.Microseconds(), otelAttrs...)
|
||||
}()
|
||||
|
||||
// Increment request counter.
|
||||
s.requests.Add(ctx, 1, otelAttrs...)
|
||||
|
||||
var (
|
||||
recordError = func(stage string, err error) {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, stage)
|
||||
s.errors.Add(ctx, 1, otelAttrs...)
|
||||
}
|
||||
err error
|
||||
opErrContext = ogenerrors.OperationContext{
|
||||
Name: "AddPage",
|
||||
ID: "addPage",
|
||||
}
|
||||
)
|
||||
request, close, err := s.decodeAddPageRequest(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 *Page
|
||||
if m := s.cfg.Middleware; m != nil {
|
||||
mreq := middleware.Request{
|
||||
Context: ctx,
|
||||
OperationName: "AddPage",
|
||||
OperationID: "addPage",
|
||||
Body: request,
|
||||
Params: middleware.Parameters{},
|
||||
Raw: r,
|
||||
}
|
||||
|
||||
type (
|
||||
Request = OptAddPageReq
|
||||
Params = struct{}
|
||||
Response = *Page
|
||||
)
|
||||
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.AddPage(ctx, request)
|
||||
return response, err
|
||||
},
|
||||
)
|
||||
} else {
|
||||
response, err = s.h.AddPage(ctx, request)
|
||||
}
|
||||
if err != nil {
|
||||
recordError("Internal", err)
|
||||
if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
|
||||
encodeErrorResponse(errRes, w, span)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, ht.ErrNotImplemented) {
|
||||
s.cfg.ErrorHandler(ctx, w, r, err)
|
||||
return
|
||||
}
|
||||
encodeErrorResponse(s.h.NewError(ctx, err), w, span)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeAddPageResponse(response, w, span); err != nil {
|
||||
recordError("EncodeResponse", err)
|
||||
s.cfg.ErrorHandler(ctx, w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handleGetPageRequest handles getPage operation.
|
||||
//
|
||||
// Get page details.
|
||||
//
|
||||
// GET /pages/{id}
|
||||
func (s *Server) handleGetPageRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
|
||||
otelAttrs := []attribute.KeyValue{
|
||||
otelogen.OperationID("getPage"),
|
||||
semconv.HTTPMethodKey.String("GET"),
|
||||
semconv.HTTPRouteKey.String("/pages/{id}"),
|
||||
}
|
||||
|
||||
// Start a span for this request.
|
||||
ctx, span := s.cfg.Tracer.Start(r.Context(), "GetPage",
|
||||
trace.WithAttributes(otelAttrs...),
|
||||
serverSpanKind,
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
// Run stopwatch.
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
elapsedDuration := time.Since(startTime)
|
||||
s.duration.Record(ctx, elapsedDuration.Microseconds(), otelAttrs...)
|
||||
}()
|
||||
|
||||
// Increment request counter.
|
||||
s.requests.Add(ctx, 1, otelAttrs...)
|
||||
|
||||
var (
|
||||
recordError = func(stage string, err error) {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, stage)
|
||||
s.errors.Add(ctx, 1, otelAttrs...)
|
||||
}
|
||||
err error
|
||||
opErrContext = ogenerrors.OperationContext{
|
||||
Name: "GetPage",
|
||||
ID: "getPage",
|
||||
}
|
||||
)
|
||||
params, err := decodeGetPageParams(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 GetPageRes
|
||||
if m := s.cfg.Middleware; m != nil {
|
||||
mreq := middleware.Request{
|
||||
Context: ctx,
|
||||
OperationName: "GetPage",
|
||||
OperationID: "getPage",
|
||||
Body: nil,
|
||||
Params: middleware.Parameters{
|
||||
{
|
||||
Name: "id",
|
||||
In: "path",
|
||||
}: params.ID,
|
||||
},
|
||||
Raw: r,
|
||||
}
|
||||
|
||||
type (
|
||||
Request = struct{}
|
||||
Params = GetPageParams
|
||||
Response = GetPageRes
|
||||
)
|
||||
response, err = middleware.HookMiddleware[
|
||||
Request,
|
||||
Params,
|
||||
Response,
|
||||
](
|
||||
m,
|
||||
mreq,
|
||||
unpackGetPageParams,
|
||||
func(ctx context.Context, request Request, params Params) (response Response, err error) {
|
||||
response, err = s.h.GetPage(ctx, params)
|
||||
return response, err
|
||||
},
|
||||
)
|
||||
} else {
|
||||
response, err = s.h.GetPage(ctx, params)
|
||||
}
|
||||
if err != nil {
|
||||
recordError("Internal", err)
|
||||
if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
|
||||
encodeErrorResponse(errRes, w, span)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, ht.ErrNotImplemented) {
|
||||
s.cfg.ErrorHandler(ctx, w, r, err)
|
||||
return
|
||||
}
|
||||
encodeErrorResponse(s.h.NewError(ctx, err), w, span)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeGetPageResponse(response, w, span); err != nil {
|
||||
recordError("EncodeResponse", err)
|
||||
s.cfg.ErrorHandler(ctx, w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handleGetPagesRequest handles getPages operation.
|
||||
//
|
||||
// Get all pages.
|
||||
//
|
||||
// GET /pages
|
||||
func (s *Server) handleGetPagesRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
|
||||
otelAttrs := []attribute.KeyValue{
|
||||
otelogen.OperationID("getPages"),
|
||||
semconv.HTTPMethodKey.String("GET"),
|
||||
semconv.HTTPRouteKey.String("/pages"),
|
||||
}
|
||||
|
||||
// Start a span for this request.
|
||||
ctx, span := s.cfg.Tracer.Start(r.Context(), "GetPages",
|
||||
trace.WithAttributes(otelAttrs...),
|
||||
serverSpanKind,
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
// Run stopwatch.
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
elapsedDuration := time.Since(startTime)
|
||||
s.duration.Record(ctx, elapsedDuration.Microseconds(), otelAttrs...)
|
||||
}()
|
||||
|
||||
// Increment request counter.
|
||||
s.requests.Add(ctx, 1, otelAttrs...)
|
||||
|
||||
var (
|
||||
recordError = func(stage string, err error) {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, stage)
|
||||
s.errors.Add(ctx, 1, otelAttrs...)
|
||||
}
|
||||
err error
|
||||
)
|
||||
|
||||
var response Pages
|
||||
if m := s.cfg.Middleware; m != nil {
|
||||
mreq := middleware.Request{
|
||||
Context: ctx,
|
||||
OperationName: "GetPages",
|
||||
OperationID: "getPages",
|
||||
Body: nil,
|
||||
Params: middleware.Parameters{},
|
||||
Raw: r,
|
||||
}
|
||||
|
||||
type (
|
||||
Request = struct{}
|
||||
Params = struct{}
|
||||
Response = Pages
|
||||
)
|
||||
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.GetPages(ctx)
|
||||
return response, err
|
||||
},
|
||||
)
|
||||
} else {
|
||||
response, err = s.h.GetPages(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
recordError("Internal", err)
|
||||
if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
|
||||
encodeErrorResponse(errRes, w, span)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, ht.ErrNotImplemented) {
|
||||
s.cfg.ErrorHandler(ctx, w, r, err)
|
||||
return
|
||||
}
|
||||
encodeErrorResponse(s.h.NewError(ctx, err), w, span)
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeGetPagesResponse(response, w, span); err != nil {
|
||||
recordError("EncodeResponse", err)
|
||||
s.cfg.ErrorHandler(ctx, w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
6
api/openapi/oas_interfaces_gen.go
Normal file
6
api/openapi/oas_interfaces_gen.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
package openapi
|
||||
|
||||
type GetPageRes interface {
|
||||
getPageRes()
|
||||
}
|
||||
1151
api/openapi/oas_json_gen.go
Normal file
1151
api/openapi/oas_json_gen.go
Normal file
File diff suppressed because it is too large
Load Diff
10
api/openapi/oas_middleware_gen.go
Normal file
10
api/openapi/oas_middleware_gen.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"github.com/ogen-go/ogen/middleware"
|
||||
)
|
||||
|
||||
// Middleware is middleware type.
|
||||
type Middleware = middleware.Middleware
|
||||
82
api/openapi/oas_parameters_gen.go
Normal file
82
api/openapi/oas_parameters_gen.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// GetPageParams is parameters of getPage operation.
|
||||
type GetPageParams struct {
|
||||
ID uuid.UUID
|
||||
}
|
||||
|
||||
func unpackGetPageParams(packed middleware.Parameters) (params GetPageParams) {
|
||||
{
|
||||
key := middleware.ParameterKey{
|
||||
Name: "id",
|
||||
In: "path",
|
||||
}
|
||||
params.ID = packed[key].(uuid.UUID)
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func decodeGetPageParams(args [1]string, argsEscaped bool, r *http.Request) (params GetPageParams, _ error) {
|
||||
// Decode path: id.
|
||||
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: "id",
|
||||
Value: param,
|
||||
Style: uri.PathStyleSimple,
|
||||
Explode: false,
|
||||
})
|
||||
|
||||
if err := func() error {
|
||||
val, err := d.DecodeValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := conv.ToUUID(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params.ID = c
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return validate.ErrFieldRequired
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return params, &ogenerrors.DecodeParamError{
|
||||
Name: "id",
|
||||
In: "path",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
98
api/openapi/oas_request_decoders_gen.go
Normal file
98
api/openapi/oas_request_decoders_gen.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
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) decodeAddPageRequest(r *http.Request) (
|
||||
req OptAddPageReq,
|
||||
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())
|
||||
}
|
||||
}()
|
||||
if _, ok := r.Header["Content-Type"]; !ok && r.ContentLength == 0 {
|
||||
return req, close, nil
|
||||
}
|
||||
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, nil
|
||||
}
|
||||
buf, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return req, close, err
|
||||
}
|
||||
|
||||
if len(buf) == 0 {
|
||||
return req, close, nil
|
||||
}
|
||||
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var request OptAddPageReq
|
||||
if err := func() error {
|
||||
request.Reset()
|
||||
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
|
||||
}
|
||||
if err := func() error {
|
||||
if request.Set {
|
||||
if err := func() error {
|
||||
if err := request.Value.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return req, close, errors.Wrap(err, "validate")
|
||||
}
|
||||
return request, close, nil
|
||||
default:
|
||||
return req, close, validate.InvalidContentType(ct)
|
||||
}
|
||||
}
|
||||
32
api/openapi/oas_request_encoders_gen.go
Normal file
32
api/openapi/oas_request_encoders_gen.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-faster/jx"
|
||||
|
||||
ht "github.com/ogen-go/ogen/http"
|
||||
)
|
||||
|
||||
func encodeAddPageRequest(
|
||||
req OptAddPageReq,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
if !req.Set {
|
||||
// Keep request with empty body if value is not set.
|
||||
return nil
|
||||
}
|
||||
e := jx.GetEncoder()
|
||||
{
|
||||
if req.Set {
|
||||
req.Encode(e)
|
||||
}
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
267
api/openapi/oas_response_decoders_gen.go
Normal file
267
api/openapi/oas_response_decoders_gen.go
Normal file
@@ -0,0 +1,267 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
"github.com/go-faster/jx"
|
||||
|
||||
"github.com/ogen-go/ogen/ogenerrors"
|
||||
"github.com/ogen-go/ogen/validate"
|
||||
)
|
||||
|
||||
func decodeAddPageResponse(resp *http.Response) (res *Page, err 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 Page
|
||||
if err := func() error {
|
||||
if err := response.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Skip(); err != io.EOF {
|
||||
return errors.New("unexpected trailing data")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
err = &ogenerrors.DecodeBodyError{
|
||||
ContentType: ct,
|
||||
Body: buf,
|
||||
Err: err,
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
return &response, nil
|
||||
default:
|
||||
return res, validate.InvalidContentType(ct)
|
||||
}
|
||||
}
|
||||
// Convenient error response.
|
||||
defRes, err := func() (res *ErrorStatusCode, err error) {
|
||||
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var response Error
|
||||
if err := func() error {
|
||||
if err := response.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Skip(); err != io.EOF {
|
||||
return errors.New("unexpected trailing data")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
err = &ogenerrors.DecodeBodyError{
|
||||
ContentType: ct,
|
||||
Body: buf,
|
||||
Err: err,
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
return &ErrorStatusCode{
|
||||
StatusCode: resp.StatusCode,
|
||||
Response: response,
|
||||
}, nil
|
||||
default:
|
||||
return res, validate.InvalidContentType(ct)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "default")
|
||||
}
|
||||
return res, errors.Wrap(defRes, "error")
|
||||
}
|
||||
|
||||
func decodeGetPageResponse(resp *http.Response) (res GetPageRes, err error) {
|
||||
switch resp.StatusCode {
|
||||
case 200:
|
||||
// Code 200.
|
||||
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var response PageWithResults
|
||||
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 404:
|
||||
// Code 404.
|
||||
return &GetPageNotFound{}, nil
|
||||
}
|
||||
// Convenient error response.
|
||||
defRes, err := func() (res *ErrorStatusCode, err error) {
|
||||
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var response Error
|
||||
if err := func() error {
|
||||
if err := response.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Skip(); err != io.EOF {
|
||||
return errors.New("unexpected trailing data")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
err = &ogenerrors.DecodeBodyError{
|
||||
ContentType: ct,
|
||||
Body: buf,
|
||||
Err: err,
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
return &ErrorStatusCode{
|
||||
StatusCode: resp.StatusCode,
|
||||
Response: response,
|
||||
}, nil
|
||||
default:
|
||||
return res, validate.InvalidContentType(ct)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "default")
|
||||
}
|
||||
return res, errors.Wrap(defRes, "error")
|
||||
}
|
||||
|
||||
func decodeGetPagesResponse(resp *http.Response) (res Pages, err error) {
|
||||
switch resp.StatusCode {
|
||||
case 200:
|
||||
// Code 200.
|
||||
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var response Pages
|
||||
if err := func() error {
|
||||
if err := response.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Skip(); err != io.EOF {
|
||||
return errors.New("unexpected trailing data")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
err = &ogenerrors.DecodeBodyError{
|
||||
ContentType: ct,
|
||||
Body: buf,
|
||||
Err: err,
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
return response, nil
|
||||
default:
|
||||
return res, validate.InvalidContentType(ct)
|
||||
}
|
||||
}
|
||||
// Convenient error response.
|
||||
defRes, err := func() (res *ErrorStatusCode, err error) {
|
||||
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "parse media type")
|
||||
}
|
||||
switch {
|
||||
case ct == "application/json":
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
d := jx.DecodeBytes(buf)
|
||||
|
||||
var response Error
|
||||
if err := func() error {
|
||||
if err := response.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Skip(); err != io.EOF {
|
||||
return errors.New("unexpected trailing data")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
err = &ogenerrors.DecodeBodyError{
|
||||
ContentType: ct,
|
||||
Body: buf,
|
||||
Err: err,
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
return &ErrorStatusCode{
|
||||
StatusCode: resp.StatusCode,
|
||||
Response: response,
|
||||
}, nil
|
||||
default:
|
||||
return res, validate.InvalidContentType(ct)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "default")
|
||||
}
|
||||
return res, errors.Wrap(defRes, "error")
|
||||
}
|
||||
87
api/openapi/oas_response_encoders_gen.go
Normal file
87
api/openapi/oas_response_encoders_gen.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
"github.com/go-faster/jx"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"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))
|
||||
|
||||
e := jx.GetEncoder()
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeGetPageResponse(response GetPageRes, w http.ResponseWriter, span trace.Span) error {
|
||||
switch response := response.(type) {
|
||||
case *PageWithResults:
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(200)
|
||||
span.SetStatus(codes.Ok, http.StatusText(200))
|
||||
|
||||
e := jx.GetEncoder()
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
return nil
|
||||
|
||||
case *GetPageNotFound:
|
||||
w.WriteHeader(404)
|
||||
span.SetStatus(codes.Error, http.StatusText(404))
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
return errors.Errorf("unexpected response type: %T", response)
|
||||
}
|
||||
}
|
||||
|
||||
func encodeGetPagesResponse(response Pages, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(200)
|
||||
span.SetStatus(codes.Ok, http.StatusText(200))
|
||||
|
||||
e := jx.GetEncoder()
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeErrorResponse(response *ErrorStatusCode, w http.ResponseWriter, span trace.Span) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
code := response.StatusCode
|
||||
if code == 0 {
|
||||
// Set default status code.
|
||||
code = http.StatusOK
|
||||
}
|
||||
w.WriteHeader(code)
|
||||
st := http.StatusText(code)
|
||||
if code >= http.StatusBadRequest {
|
||||
span.SetStatus(codes.Error, st)
|
||||
} else {
|
||||
span.SetStatus(codes.Ok, st)
|
||||
}
|
||||
|
||||
e := jx.GetEncoder()
|
||||
response.Response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
220
api/openapi/oas_router_gen.go
Normal file
220
api/openapi/oas_router_gen.go
Normal file
@@ -0,0 +1,220 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/ogen-go/ogen/uri"
|
||||
)
|
||||
|
||||
// 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, '%')
|
||||
}
|
||||
}
|
||||
if prefix := s.cfg.Prefix; len(prefix) > 0 {
|
||||
if strings.HasPrefix(elem, prefix) {
|
||||
// Cut prefix from the path.
|
||||
elem = strings.TrimPrefix(elem, prefix)
|
||||
} else {
|
||||
// Prefix doesn't match.
|
||||
s.notFound(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
if 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: "/pages"
|
||||
if l := len("/pages"); len(elem) >= l && elem[0:l] == "/pages" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
s.handleGetPagesRequest([0]string{}, elemIsEscaped, w, r)
|
||||
case "POST":
|
||||
s.handleAddPageRequest([0]string{}, elemIsEscaped, w, r)
|
||||
default:
|
||||
s.notAllowed(w, r, "GET,POST")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
switch elem[0] {
|
||||
case '/': // Prefix: "/"
|
||||
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
// Param: "id"
|
||||
// Leaf parameter
|
||||
args[0] = elem
|
||||
elem = ""
|
||||
|
||||
if len(elem) == 0 {
|
||||
// Leaf node.
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
s.handleGetPageRequest([1]string{
|
||||
args[0],
|
||||
}, elemIsEscaped, w, r)
|
||||
default:
|
||||
s.notAllowed(w, r, "GET")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s.notFound(w, r)
|
||||
}
|
||||
|
||||
// Route is route object.
|
||||
type Route struct {
|
||||
name 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Static code generated router with unwrapped path search.
|
||||
switch {
|
||||
default:
|
||||
if len(elem) == 0 {
|
||||
break
|
||||
}
|
||||
switch elem[0] {
|
||||
case '/': // Prefix: "/pages"
|
||||
if l := len("/pages"); len(elem) >= l && elem[0:l] == "/pages" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
switch method {
|
||||
case "GET":
|
||||
r.name = "GetPages"
|
||||
r.operationID = "getPages"
|
||||
r.pathPattern = "/pages"
|
||||
r.args = args
|
||||
r.count = 0
|
||||
return r, true
|
||||
case "POST":
|
||||
r.name = "AddPage"
|
||||
r.operationID = "addPage"
|
||||
r.pathPattern = "/pages"
|
||||
r.args = args
|
||||
r.count = 0
|
||||
return r, true
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
switch elem[0] {
|
||||
case '/': // Prefix: "/"
|
||||
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
// Param: "id"
|
||||
// Leaf parameter
|
||||
args[0] = elem
|
||||
elem = ""
|
||||
|
||||
if len(elem) == 0 {
|
||||
switch method {
|
||||
case "GET":
|
||||
// Leaf: GetPage
|
||||
r.name = "GetPage"
|
||||
r.operationID = "getPage"
|
||||
r.pathPattern = "/pages/{id}"
|
||||
r.args = args
|
||||
r.count = 1
|
||||
return r, true
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, false
|
||||
}
|
||||
516
api/openapi/oas_schemas_gen.go
Normal file
516
api/openapi/oas_schemas_gen.go
Normal file
@@ -0,0 +1,516 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func (s *ErrorStatusCode) Error() string {
|
||||
return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response)
|
||||
}
|
||||
|
||||
type AddPageReq struct {
|
||||
URL string `json:"url"`
|
||||
Description OptString `json:"description"`
|
||||
Formats []Format `json:"formats"`
|
||||
}
|
||||
|
||||
// GetURL returns the value of URL.
|
||||
func (s *AddPageReq) GetURL() string {
|
||||
return s.URL
|
||||
}
|
||||
|
||||
// GetDescription returns the value of Description.
|
||||
func (s *AddPageReq) GetDescription() OptString {
|
||||
return s.Description
|
||||
}
|
||||
|
||||
// GetFormats returns the value of Formats.
|
||||
func (s *AddPageReq) GetFormats() []Format {
|
||||
return s.Formats
|
||||
}
|
||||
|
||||
// SetURL sets the value of URL.
|
||||
func (s *AddPageReq) SetURL(val string) {
|
||||
s.URL = val
|
||||
}
|
||||
|
||||
// SetDescription sets the value of Description.
|
||||
func (s *AddPageReq) SetDescription(val OptString) {
|
||||
s.Description = val
|
||||
}
|
||||
|
||||
// SetFormats sets the value of Formats.
|
||||
func (s *AddPageReq) SetFormats(val []Format) {
|
||||
s.Formats = val
|
||||
}
|
||||
|
||||
// Ref: #/components/schemas/error
|
||||
type Error struct {
|
||||
Message string `json:"message"`
|
||||
Localized OptString `json:"localized"`
|
||||
}
|
||||
|
||||
// GetMessage returns the value of Message.
|
||||
func (s *Error) GetMessage() string {
|
||||
return s.Message
|
||||
}
|
||||
|
||||
// GetLocalized returns the value of Localized.
|
||||
func (s *Error) GetLocalized() OptString {
|
||||
return s.Localized
|
||||
}
|
||||
|
||||
// SetMessage sets the value of Message.
|
||||
func (s *Error) SetMessage(val string) {
|
||||
s.Message = val
|
||||
}
|
||||
|
||||
// SetLocalized sets the value of Localized.
|
||||
func (s *Error) SetLocalized(val OptString) {
|
||||
s.Localized = val
|
||||
}
|
||||
|
||||
// ErrorStatusCode wraps Error with StatusCode.
|
||||
type ErrorStatusCode struct {
|
||||
StatusCode int
|
||||
Response Error
|
||||
}
|
||||
|
||||
// GetStatusCode returns the value of StatusCode.
|
||||
func (s *ErrorStatusCode) GetStatusCode() int {
|
||||
return s.StatusCode
|
||||
}
|
||||
|
||||
// GetResponse returns the value of Response.
|
||||
func (s *ErrorStatusCode) GetResponse() Error {
|
||||
return s.Response
|
||||
}
|
||||
|
||||
// SetStatusCode sets the value of StatusCode.
|
||||
func (s *ErrorStatusCode) SetStatusCode(val int) {
|
||||
s.StatusCode = val
|
||||
}
|
||||
|
||||
// SetResponse sets the value of Response.
|
||||
func (s *ErrorStatusCode) SetResponse(val Error) {
|
||||
s.Response = val
|
||||
}
|
||||
|
||||
// Ref: #/components/schemas/format
|
||||
type Format string
|
||||
|
||||
const (
|
||||
FormatAll Format = "all"
|
||||
FormatPdf Format = "pdf"
|
||||
FormatSinglePage Format = "single_page"
|
||||
FormatHeaders Format = "headers"
|
||||
)
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (s Format) MarshalText() ([]byte, error) {
|
||||
switch s {
|
||||
case FormatAll:
|
||||
return []byte(s), nil
|
||||
case FormatPdf:
|
||||
return []byte(s), nil
|
||||
case FormatSinglePage:
|
||||
return []byte(s), nil
|
||||
case FormatHeaders:
|
||||
return []byte(s), nil
|
||||
default:
|
||||
return nil, errors.Errorf("invalid value: %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (s *Format) UnmarshalText(data []byte) error {
|
||||
switch Format(data) {
|
||||
case FormatAll:
|
||||
*s = FormatAll
|
||||
return nil
|
||||
case FormatPdf:
|
||||
*s = FormatPdf
|
||||
return nil
|
||||
case FormatSinglePage:
|
||||
*s = FormatSinglePage
|
||||
return nil
|
||||
case FormatHeaders:
|
||||
*s = FormatHeaders
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %q", data)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPageNotFound is response for GetPage operation.
|
||||
type GetPageNotFound struct{}
|
||||
|
||||
func (*GetPageNotFound) getPageRes() {}
|
||||
|
||||
// NewOptAddPageReq returns new OptAddPageReq with value set to v.
|
||||
func NewOptAddPageReq(v AddPageReq) OptAddPageReq {
|
||||
return OptAddPageReq{
|
||||
Value: v,
|
||||
Set: true,
|
||||
}
|
||||
}
|
||||
|
||||
// OptAddPageReq is optional AddPageReq.
|
||||
type OptAddPageReq struct {
|
||||
Value AddPageReq
|
||||
Set bool
|
||||
}
|
||||
|
||||
// IsSet returns true if OptAddPageReq was set.
|
||||
func (o OptAddPageReq) IsSet() bool { return o.Set }
|
||||
|
||||
// Reset unsets value.
|
||||
func (o *OptAddPageReq) Reset() {
|
||||
var v AddPageReq
|
||||
o.Value = v
|
||||
o.Set = false
|
||||
}
|
||||
|
||||
// SetTo sets value to v.
|
||||
func (o *OptAddPageReq) SetTo(v AddPageReq) {
|
||||
o.Set = true
|
||||
o.Value = v
|
||||
}
|
||||
|
||||
// Get returns value and boolean that denotes whether value was set.
|
||||
func (o OptAddPageReq) Get() (v AddPageReq, 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 OptAddPageReq) Or(d AddPageReq) AddPageReq {
|
||||
if v, ok := o.Get(); ok {
|
||||
return v
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// NewOptString returns new OptString with value set to v.
|
||||
func NewOptString(v string) OptString {
|
||||
return OptString{
|
||||
Value: v,
|
||||
Set: true,
|
||||
}
|
||||
}
|
||||
|
||||
// OptString is optional string.
|
||||
type OptString struct {
|
||||
Value string
|
||||
Set bool
|
||||
}
|
||||
|
||||
// IsSet returns true if OptString was set.
|
||||
func (o OptString) IsSet() bool { return o.Set }
|
||||
|
||||
// Reset unsets value.
|
||||
func (o *OptString) Reset() {
|
||||
var v string
|
||||
o.Value = v
|
||||
o.Set = false
|
||||
}
|
||||
|
||||
// SetTo sets value to v.
|
||||
func (o *OptString) SetTo(v string) {
|
||||
o.Set = true
|
||||
o.Value = v
|
||||
}
|
||||
|
||||
// Get returns value and boolean that denotes whether value was set.
|
||||
func (o OptString) Get() (v string, ok bool) {
|
||||
if !o.Set {
|
||||
return v, false
|
||||
}
|
||||
return o.Value, true
|
||||
}
|
||||
|
||||
// Or returns value if set, or given parameter if does not.
|
||||
func (o OptString) Or(d string) string {
|
||||
if v, ok := o.Get(); ok {
|
||||
return v
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Ref: #/components/schemas/page
|
||||
type Page struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
URL string `json:"url"`
|
||||
Created time.Time `json:"created"`
|
||||
Formats []Format `json:"formats"`
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// GetID returns the value of ID.
|
||||
func (s *Page) GetID() uuid.UUID {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
// GetURL returns the value of URL.
|
||||
func (s *Page) GetURL() string {
|
||||
return s.URL
|
||||
}
|
||||
|
||||
// GetCreated returns the value of Created.
|
||||
func (s *Page) GetCreated() time.Time {
|
||||
return s.Created
|
||||
}
|
||||
|
||||
// GetFormats returns the value of Formats.
|
||||
func (s *Page) GetFormats() []Format {
|
||||
return s.Formats
|
||||
}
|
||||
|
||||
// GetStatus returns the value of Status.
|
||||
func (s *Page) GetStatus() Status {
|
||||
return s.Status
|
||||
}
|
||||
|
||||
// SetID sets the value of ID.
|
||||
func (s *Page) SetID(val uuid.UUID) {
|
||||
s.ID = val
|
||||
}
|
||||
|
||||
// SetURL sets the value of URL.
|
||||
func (s *Page) SetURL(val string) {
|
||||
s.URL = val
|
||||
}
|
||||
|
||||
// SetCreated sets the value of Created.
|
||||
func (s *Page) SetCreated(val time.Time) {
|
||||
s.Created = val
|
||||
}
|
||||
|
||||
// SetFormats sets the value of Formats.
|
||||
func (s *Page) SetFormats(val []Format) {
|
||||
s.Formats = val
|
||||
}
|
||||
|
||||
// SetStatus sets the value of Status.
|
||||
func (s *Page) SetStatus(val Status) {
|
||||
s.Status = val
|
||||
}
|
||||
|
||||
// Merged schema.
|
||||
// Ref: #/components/schemas/pageWithResults
|
||||
type PageWithResults struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
URL string `json:"url"`
|
||||
Created time.Time `json:"created"`
|
||||
Formats []Format `json:"formats"`
|
||||
Status Status `json:"status"`
|
||||
Results []Result `json:"results"`
|
||||
}
|
||||
|
||||
// GetID returns the value of ID.
|
||||
func (s *PageWithResults) GetID() uuid.UUID {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
// GetURL returns the value of URL.
|
||||
func (s *PageWithResults) GetURL() string {
|
||||
return s.URL
|
||||
}
|
||||
|
||||
// GetCreated returns the value of Created.
|
||||
func (s *PageWithResults) GetCreated() time.Time {
|
||||
return s.Created
|
||||
}
|
||||
|
||||
// GetFormats returns the value of Formats.
|
||||
func (s *PageWithResults) GetFormats() []Format {
|
||||
return s.Formats
|
||||
}
|
||||
|
||||
// GetStatus returns the value of Status.
|
||||
func (s *PageWithResults) GetStatus() Status {
|
||||
return s.Status
|
||||
}
|
||||
|
||||
// GetResults returns the value of Results.
|
||||
func (s *PageWithResults) GetResults() []Result {
|
||||
return s.Results
|
||||
}
|
||||
|
||||
// SetID sets the value of ID.
|
||||
func (s *PageWithResults) SetID(val uuid.UUID) {
|
||||
s.ID = val
|
||||
}
|
||||
|
||||
// SetURL sets the value of URL.
|
||||
func (s *PageWithResults) SetURL(val string) {
|
||||
s.URL = val
|
||||
}
|
||||
|
||||
// SetCreated sets the value of Created.
|
||||
func (s *PageWithResults) SetCreated(val time.Time) {
|
||||
s.Created = val
|
||||
}
|
||||
|
||||
// SetFormats sets the value of Formats.
|
||||
func (s *PageWithResults) SetFormats(val []Format) {
|
||||
s.Formats = val
|
||||
}
|
||||
|
||||
// SetStatus sets the value of Status.
|
||||
func (s *PageWithResults) SetStatus(val Status) {
|
||||
s.Status = val
|
||||
}
|
||||
|
||||
// SetResults sets the value of Results.
|
||||
func (s *PageWithResults) SetResults(val []Result) {
|
||||
s.Results = val
|
||||
}
|
||||
|
||||
func (*PageWithResults) getPageRes() {}
|
||||
|
||||
type Pages []Page
|
||||
|
||||
// Ref: #/components/schemas/result
|
||||
type Result struct {
|
||||
Format Format `json:"format"`
|
||||
Error OptString `json:"error"`
|
||||
Files []ResultFilesItem `json:"files"`
|
||||
}
|
||||
|
||||
// GetFormat returns the value of Format.
|
||||
func (s *Result) GetFormat() Format {
|
||||
return s.Format
|
||||
}
|
||||
|
||||
// GetError returns the value of Error.
|
||||
func (s *Result) GetError() OptString {
|
||||
return s.Error
|
||||
}
|
||||
|
||||
// GetFiles returns the value of Files.
|
||||
func (s *Result) GetFiles() []ResultFilesItem {
|
||||
return s.Files
|
||||
}
|
||||
|
||||
// SetFormat sets the value of Format.
|
||||
func (s *Result) SetFormat(val Format) {
|
||||
s.Format = val
|
||||
}
|
||||
|
||||
// SetError sets the value of Error.
|
||||
func (s *Result) SetError(val OptString) {
|
||||
s.Error = val
|
||||
}
|
||||
|
||||
// SetFiles sets the value of Files.
|
||||
func (s *Result) SetFiles(val []ResultFilesItem) {
|
||||
s.Files = val
|
||||
}
|
||||
|
||||
type ResultFilesItem struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Mimetype string `json:"mimetype"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
// GetID returns the value of ID.
|
||||
func (s *ResultFilesItem) GetID() uuid.UUID {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
// GetName returns the value of Name.
|
||||
func (s *ResultFilesItem) GetName() string {
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// GetMimetype returns the value of Mimetype.
|
||||
func (s *ResultFilesItem) GetMimetype() string {
|
||||
return s.Mimetype
|
||||
}
|
||||
|
||||
// GetSize returns the value of Size.
|
||||
func (s *ResultFilesItem) GetSize() int64 {
|
||||
return s.Size
|
||||
}
|
||||
|
||||
// SetID sets the value of ID.
|
||||
func (s *ResultFilesItem) SetID(val uuid.UUID) {
|
||||
s.ID = val
|
||||
}
|
||||
|
||||
// SetName sets the value of Name.
|
||||
func (s *ResultFilesItem) SetName(val string) {
|
||||
s.Name = val
|
||||
}
|
||||
|
||||
// SetMimetype sets the value of Mimetype.
|
||||
func (s *ResultFilesItem) SetMimetype(val string) {
|
||||
s.Mimetype = val
|
||||
}
|
||||
|
||||
// SetSize sets the value of Size.
|
||||
func (s *ResultFilesItem) SetSize(val int64) {
|
||||
s.Size = val
|
||||
}
|
||||
|
||||
// Ref: #/components/schemas/status
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusNew Status = "new"
|
||||
StatusProcessing Status = "processing"
|
||||
StatusDone Status = "done"
|
||||
StatusFailed Status = "failed"
|
||||
StatusWithErrors Status = "with_errors"
|
||||
)
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (s Status) MarshalText() ([]byte, error) {
|
||||
switch s {
|
||||
case StatusNew:
|
||||
return []byte(s), nil
|
||||
case StatusProcessing:
|
||||
return []byte(s), nil
|
||||
case StatusDone:
|
||||
return []byte(s), nil
|
||||
case StatusFailed:
|
||||
return []byte(s), nil
|
||||
case StatusWithErrors:
|
||||
return []byte(s), nil
|
||||
default:
|
||||
return nil, errors.Errorf("invalid value: %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (s *Status) UnmarshalText(data []byte) error {
|
||||
switch Status(data) {
|
||||
case StatusNew:
|
||||
*s = StatusNew
|
||||
return nil
|
||||
case StatusProcessing:
|
||||
*s = StatusProcessing
|
||||
return nil
|
||||
case StatusDone:
|
||||
*s = StatusDone
|
||||
return nil
|
||||
case StatusFailed:
|
||||
*s = StatusFailed
|
||||
return nil
|
||||
case StatusWithErrors:
|
||||
*s = StatusWithErrors
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %q", data)
|
||||
}
|
||||
}
|
||||
52
api/openapi/oas_server_gen.go
Normal file
52
api/openapi/oas_server_gen.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// Handler handles operations described by OpenAPI v3 specification.
|
||||
type Handler interface {
|
||||
// AddPage implements addPage operation.
|
||||
//
|
||||
// Add new page.
|
||||
//
|
||||
// POST /pages
|
||||
AddPage(ctx context.Context, req OptAddPageReq) (*Page, error)
|
||||
// GetPage implements getPage operation.
|
||||
//
|
||||
// Get page details.
|
||||
//
|
||||
// GET /pages/{id}
|
||||
GetPage(ctx context.Context, params GetPageParams) (GetPageRes, error)
|
||||
// GetPages implements getPages operation.
|
||||
//
|
||||
// Get all pages.
|
||||
//
|
||||
// GET /pages
|
||||
GetPages(ctx context.Context) (Pages, error)
|
||||
// NewError creates *ErrorStatusCode from error returned by handler.
|
||||
//
|
||||
// Used for common default response.
|
||||
NewError(ctx context.Context, err error) *ErrorStatusCode
|
||||
}
|
||||
|
||||
// Server implements http server based on OpenAPI v3 specification and
|
||||
// calls Handler to handle requests.
|
||||
type Server struct {
|
||||
h Handler
|
||||
baseServer
|
||||
}
|
||||
|
||||
// NewServer creates new Server.
|
||||
func NewServer(h Handler, opts ...ServerOption) (*Server, error) {
|
||||
s, err := newServerConfig(opts...).baseServer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Server{
|
||||
h: h,
|
||||
baseServer: s,
|
||||
}, nil
|
||||
}
|
||||
49
api/openapi/oas_unimplemented_gen.go
Normal file
49
api/openapi/oas_unimplemented_gen.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
ht "github.com/ogen-go/ogen/http"
|
||||
)
|
||||
|
||||
// UnimplementedHandler is no-op Handler which returns http.ErrNotImplemented.
|
||||
type UnimplementedHandler struct{}
|
||||
|
||||
var _ Handler = UnimplementedHandler{}
|
||||
|
||||
// AddPage implements addPage operation.
|
||||
//
|
||||
// Add new page.
|
||||
//
|
||||
// POST /pages
|
||||
func (UnimplementedHandler) AddPage(ctx context.Context, req OptAddPageReq) (r *Page, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// GetPage implements getPage operation.
|
||||
//
|
||||
// Get page details.
|
||||
//
|
||||
// GET /pages/{id}
|
||||
func (UnimplementedHandler) GetPage(ctx context.Context, params GetPageParams) (r GetPageRes, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// GetPages implements getPages operation.
|
||||
//
|
||||
// Get all pages.
|
||||
//
|
||||
// GET /pages
|
||||
func (UnimplementedHandler) GetPages(ctx context.Context) (r Pages, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// NewError creates *ErrorStatusCode from error returned by handler.
|
||||
//
|
||||
// Used for common default response.
|
||||
func (UnimplementedHandler) NewError(ctx context.Context, err error) (r *ErrorStatusCode) {
|
||||
r = new(ErrorStatusCode)
|
||||
return r
|
||||
}
|
||||
247
api/openapi/oas_validators_gen.go
Normal file
247
api/openapi/oas_validators_gen.go
Normal file
@@ -0,0 +1,247 @@
|
||||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"github.com/ogen-go/ogen/validate"
|
||||
)
|
||||
|
||||
func (s *AddPageReq) Validate() error {
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
var failures []validate.FieldError
|
||||
for i, elem := range s.Formats {
|
||||
if err := func() error {
|
||||
if err := elem.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: fmt.Sprintf("[%d]", i),
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "formats",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s Format) Validate() error {
|
||||
switch s {
|
||||
case "all":
|
||||
return nil
|
||||
case "pdf":
|
||||
return nil
|
||||
case "single_page":
|
||||
return nil
|
||||
case "headers":
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Page) Validate() error {
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Formats == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
var failures []validate.FieldError
|
||||
for i, elem := range s.Formats {
|
||||
if err := func() error {
|
||||
if err := elem.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: fmt.Sprintf("[%d]", i),
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "formats",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if err := s.Status.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "status",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *PageWithResults) Validate() error {
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Formats == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
var failures []validate.FieldError
|
||||
for i, elem := range s.Formats {
|
||||
if err := func() error {
|
||||
if err := elem.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: fmt.Sprintf("[%d]", i),
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "formats",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if err := s.Status.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "status",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if s.Results == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
var failures []validate.FieldError
|
||||
for i, elem := range s.Results {
|
||||
if err := func() error {
|
||||
if err := elem.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: fmt.Sprintf("[%d]", i),
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "results",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s Pages) Validate() error {
|
||||
if s == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
var failures []validate.FieldError
|
||||
for i, elem := range s {
|
||||
if err := func() error {
|
||||
if err := elem.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: fmt.Sprintf("[%d]", i),
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *Result) Validate() error {
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := s.Format.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "format",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if s.Files == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "files",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s Status) Validate() error {
|
||||
switch s {
|
||||
case "new":
|
||||
return nil
|
||||
case "processing":
|
||||
return nil
|
||||
case "done":
|
||||
return nil
|
||||
case "failed":
|
||||
return nil
|
||||
case "with_errors":
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %v", s)
|
||||
}
|
||||
}
|
||||
182
application/application.go
Normal file
182
application/application.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/ogen-go/ogen/middleware"
|
||||
"go.uber.org/multierr"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/derfenix/webarchive/adapters/processors"
|
||||
badgerRepo "github.com/derfenix/webarchive/adapters/repository/badger"
|
||||
"github.com/derfenix/webarchive/api/openapi"
|
||||
"github.com/derfenix/webarchive/entity"
|
||||
"github.com/derfenix/webarchive/ports/rest"
|
||||
)
|
||||
|
||||
func NewApplication(cfg Config) (Application, error) {
|
||||
log, err := newLogger(cfg.Logging)
|
||||
if err != nil {
|
||||
return Application{}, fmt.Errorf("new logger: %w", err)
|
||||
}
|
||||
|
||||
db, err := badgerRepo.NewBadger(cfg.DB.Path, log.Named("db"))
|
||||
if err != nil {
|
||||
return Application{}, fmt.Errorf("new badger: %w", err)
|
||||
}
|
||||
|
||||
fileRepo := badgerRepo.NewFile(db)
|
||||
pageRepo, err := badgerRepo.NewPage(db, fileRepo)
|
||||
if err != nil {
|
||||
return Application{}, fmt.Errorf("new page repo: %w", err)
|
||||
}
|
||||
|
||||
processor, err := processors.NewProcessors()
|
||||
if err != nil {
|
||||
return Application{}, fmt.Errorf("new processors: %w", err)
|
||||
}
|
||||
|
||||
server, err := openapi.NewServer(
|
||||
rest.NewService(pageRepo),
|
||||
openapi.WithMiddleware(
|
||||
func(r middleware.Request, next middleware.Next) (middleware.Response, error) {
|
||||
start := time.Now()
|
||||
|
||||
log := log.With(
|
||||
zap.String("operation_id", r.OperationID),
|
||||
zap.String("uri", r.Raw.RequestURI),
|
||||
)
|
||||
|
||||
var response middleware.Response
|
||||
var reqErr error
|
||||
|
||||
response, reqErr = next(r)
|
||||
|
||||
log.Debug("request completed", zap.Duration("duration", time.Since(start)), zap.Error(err))
|
||||
|
||||
return response, reqErr
|
||||
},
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return Application{}, fmt.Errorf("new rest server: %w", err)
|
||||
}
|
||||
|
||||
httpServer := http.Server{
|
||||
Addr: "0.0.0.0:5001",
|
||||
Handler: server,
|
||||
ReadTimeout: time.Second * 15,
|
||||
ReadHeaderTimeout: time.Second * 5,
|
||||
IdleTimeout: time.Second * 30,
|
||||
MaxHeaderBytes: 1024 * 2,
|
||||
}
|
||||
|
||||
return Application{
|
||||
cfg: cfg,
|
||||
log: log,
|
||||
db: db,
|
||||
processor: processor,
|
||||
httpServer: &httpServer,
|
||||
|
||||
pageRepo: pageRepo,
|
||||
fileRepo: fileRepo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Application struct {
|
||||
cfg Config
|
||||
log *zap.Logger
|
||||
db *badger.DB
|
||||
processor entity.Processor
|
||||
|
||||
httpServer *http.Server
|
||||
|
||||
pageRepo *badgerRepo.Page
|
||||
fileRepo *badgerRepo.File
|
||||
}
|
||||
|
||||
func (a *Application) Log() *zap.Logger {
|
||||
return a.log
|
||||
}
|
||||
|
||||
func (a *Application) Start(ctx context.Context, wg *sync.WaitGroup) error {
|
||||
wg.Add(2)
|
||||
|
||||
a.httpServer.BaseContext = func(net.Listener) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := a.httpServer.Shutdown(shutdownCtx); err != nil {
|
||||
a.log.Warn("http graceful shutdown failed", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
a.log.Info("starting http server", zap.String("address", a.httpServer.Addr))
|
||||
|
||||
if err := a.httpServer.ListenAndServe(); err != nil {
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
a.log.Error("http serve error", zap.Error(err))
|
||||
}
|
||||
|
||||
a.log.Info("http server stopped")
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) Stop() error {
|
||||
var errs error
|
||||
|
||||
if err := a.db.Sync(); err != nil {
|
||||
errs = multierr.Append(errs, fmt.Errorf("sync db: %w", err))
|
||||
}
|
||||
|
||||
if err := badgerRepo.Backup(a.db, badgerRepo.BackupStop); err != nil {
|
||||
errs = multierr.Append(errs, fmt.Errorf("backup on stop: %w", err))
|
||||
}
|
||||
|
||||
if err := a.db.Close(); err != nil {
|
||||
errs = multierr.Append(errs, fmt.Errorf("close db: %w", err))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func newLogger(cfg Logging) (*zap.Logger, error) {
|
||||
logCfg := zap.NewProductionConfig()
|
||||
logCfg.EncoderConfig.EncodeTime = zapcore.RFC3339TimeEncoder
|
||||
logCfg.EncoderConfig.EncodeDuration = zapcore.NanosDurationEncoder
|
||||
logCfg.DisableCaller = true
|
||||
|
||||
logCfg.Level = zap.NewAtomicLevelAt(zapcore.InfoLevel)
|
||||
if cfg.Debug {
|
||||
logCfg.Level = zap.NewAtomicLevelAt(zapcore.DebugLevel)
|
||||
}
|
||||
|
||||
log, err := logCfg.Build()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build logger: %w", err)
|
||||
}
|
||||
|
||||
return log, nil
|
||||
}
|
||||
38
application/config.go
Normal file
38
application/config.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/sethvargo/go-envconfig"
|
||||
)
|
||||
|
||||
const envPrefix = "WEBARCHIVE_"
|
||||
|
||||
func NewConfig(ctx context.Context) (Config, error) {
|
||||
cfg := Config{}
|
||||
|
||||
lookuper := envconfig.MultiLookuper(
|
||||
envconfig.PrefixLookuper(envPrefix, envconfig.OsLookuper()),
|
||||
envconfig.OsLookuper(),
|
||||
)
|
||||
|
||||
if err := envconfig.ProcessWith(ctx, &cfg, lookuper); err != nil {
|
||||
return Config{}, fmt.Errorf("process env: %w", err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
DB DB `env:",prefix=DB_"`
|
||||
Logging Logging `env:",prefix=LOGGING_"`
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
Path string `env:"PATH,default=./db"`
|
||||
}
|
||||
|
||||
type Logging struct {
|
||||
Debug bool `env:"DEBUG"`
|
||||
}
|
||||
42
application/config_test.go
Normal file
42
application/config_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("no envs", func(t *testing.T) {
|
||||
|
||||
config, err := NewConfig(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "./db", config.DB.Path)
|
||||
})
|
||||
|
||||
t.Run("env without prefix", func(t *testing.T) {
|
||||
require.NoError(t, os.Setenv("DB_PATH", "./old_db"))
|
||||
|
||||
config, err := NewConfig(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "./old_db", config.DB.Path)
|
||||
})
|
||||
|
||||
t.Run("prefix env override", func(t *testing.T) {
|
||||
require.NoError(t, os.Setenv("WEBARCHIVE_DB_PATH", "./new_db"))
|
||||
|
||||
config, err := NewConfig(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "./new_db", config.DB.Path)
|
||||
})
|
||||
}
|
||||
42
cmd/service/main.go
Normal file
42
cmd/service/main.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/derfenix/webarchive/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
|
||||
defer cancel()
|
||||
|
||||
cfg, err := application.NewConfig(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to init config: %s", err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
app, err := application.NewApplication(cfg)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to init application: %s", err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
if err := app.Start(ctx, &wg); err != nil {
|
||||
app.Log().Fatal("failed to start application", zap.Error(err))
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if err := app.Stop(); err != nil {
|
||||
app.Log().Fatal("failed to graceful stop", zap.Error(err))
|
||||
}
|
||||
}
|
||||
30
entity/file.go
Normal file
30
entity/file.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func NewFile(name string, data []byte) File {
|
||||
detected := mimetype.Detect(data)
|
||||
|
||||
return File{
|
||||
ID: uuid.New(),
|
||||
Name: name,
|
||||
MimeType: detected.String(),
|
||||
Size: int64(len(data)),
|
||||
Data: data,
|
||||
Created: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
type File struct {
|
||||
ID uuid.UUID
|
||||
Name string
|
||||
MimeType string
|
||||
Size int64
|
||||
Data []byte
|
||||
Created time.Time
|
||||
}
|
||||
99
entity/page.go
Normal file
99
entity/page.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Processor interface {
|
||||
Process(ctx context.Context, format Format, url string) Result
|
||||
}
|
||||
|
||||
type Format uint8
|
||||
|
||||
const (
|
||||
FormatHeaders Format = iota
|
||||
FormatSingleFile
|
||||
FormatPDF
|
||||
)
|
||||
|
||||
type Status uint8
|
||||
|
||||
const (
|
||||
StatusNew Status = iota
|
||||
StatusProcessing
|
||||
StatusDone
|
||||
StatusFailed
|
||||
StatusWithErrors
|
||||
)
|
||||
|
||||
func NewPage(url string, description string, formats ...Format) *Page {
|
||||
return &Page{
|
||||
ID: uuid.New(),
|
||||
URL: url,
|
||||
Description: description,
|
||||
Formats: formats,
|
||||
Created: time.Now(),
|
||||
Version: 1,
|
||||
}
|
||||
}
|
||||
|
||||
type Page struct {
|
||||
ID uuid.UUID
|
||||
URL string
|
||||
Description string
|
||||
Created time.Time
|
||||
Formats []Format
|
||||
Results Results
|
||||
Version uint16
|
||||
Status Status
|
||||
}
|
||||
|
||||
func (p *Page) SetProcessing() {
|
||||
p.Status = StatusProcessing
|
||||
}
|
||||
|
||||
func (p *Page) Process(ctx context.Context, wg *sync.WaitGroup, processor Processor) {
|
||||
defer wg.Done()
|
||||
|
||||
innerWG := sync.WaitGroup{}
|
||||
innerWG.Add(len(p.Formats))
|
||||
|
||||
for _, format := range p.Formats {
|
||||
go func(format Format) {
|
||||
defer innerWG.Done()
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
p.Results.Add(Result{Format: format, Err: fmt.Errorf("recovered from panic: %v", err)})
|
||||
}
|
||||
}()
|
||||
|
||||
result := processor.Process(ctx, format, p.URL)
|
||||
p.Results.Add(result)
|
||||
}(format)
|
||||
}
|
||||
|
||||
var hasResultWithOutErrors bool
|
||||
for _, result := range p.Results.Results() {
|
||||
if result.Err != nil {
|
||||
p.Status = StatusWithErrors
|
||||
} else {
|
||||
hasResultWithOutErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
if !hasResultWithOutErrors {
|
||||
p.Status = StatusFailed
|
||||
}
|
||||
|
||||
if p.Status == StatusProcessing {
|
||||
p.Status = StatusDone
|
||||
}
|
||||
|
||||
innerWG.Wait()
|
||||
}
|
||||
39
entity/result.go
Normal file
39
entity/result.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
Format Format
|
||||
Err error
|
||||
Files []File
|
||||
}
|
||||
|
||||
type Results struct {
|
||||
mu sync.RWMutex
|
||||
results []Result
|
||||
}
|
||||
|
||||
func (r *Results) MarshalMsgpack() ([]byte, error) {
|
||||
return msgpack.Marshal(r.results)
|
||||
}
|
||||
|
||||
func (r *Results) UnmarshalMsgpack(b []byte) error {
|
||||
return msgpack.Unmarshal(b, r.results)
|
||||
}
|
||||
|
||||
func (r *Results) Add(result Result) {
|
||||
r.mu.Lock()
|
||||
r.results = append(r.results, result)
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
func (r *Results) Results() []Result {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
return r.results
|
||||
}
|
||||
59
go.mod
Normal file
59
go.mod
Normal file
@@ -0,0 +1,59 @@
|
||||
module github.com/derfenix/webarchive
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0
|
||||
github.com/dgraph-io/badger/v4 v4.0.1
|
||||
github.com/gabriel-vasile/mimetype v1.4.2
|
||||
github.com/go-faster/errors v0.6.1
|
||||
github.com/go-faster/jx v1.0.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/ogen-go/ogen v0.60.1
|
||||
github.com/sethvargo/go-envconfig v0.9.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5
|
||||
go.opentelemetry.io/otel v1.14.0
|
||||
go.opentelemetry.io/otel/metric v0.37.0
|
||||
go.opentelemetry.io/otel/trace v1.14.0
|
||||
go.uber.org/multierr v1.10.0
|
||||
go.uber.org/zap v1.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.8.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-faster/yamlx v0.4.1 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/glog v1.1.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/flatbuffers v23.3.3+incompatible // indirect
|
||||
github.com/klauspost/compress v1.16.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
231
go.sum
Normal file
231
go.sum
Normal file
@@ -0,0 +1,231 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0 h1:DNrExYwvyyI404SxdUCCANAj9TwnGjRfa3cYFMNY1AU=
|
||||
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.0/go.mod h1:SQq4xfIdvf6WYKSDxAJc+xOJdolt+/bc1jnQKMtPMvQ=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger/v4 v4.0.1 h1:zwLYFc4sfxKdaRTvS6wlHsSuYWNUiWnYLU+TS+/nCDI=
|
||||
github.com/dgraph-io/badger/v4 v4.0.1/go.mod h1:edFJfgVfwYjg+grodpS7Yj2vohQMK3VL6eCaR6EpRJU=
|
||||
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
|
||||
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI=
|
||||
github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY=
|
||||
github.com/go-faster/jx v1.0.0 h1:HE+ms2e6ZGkZ6u13t8u+onBinrPvIPI+0hWXGELm74g=
|
||||
github.com/go-faster/jx v1.0.0/go.mod h1:zm8SlkwK+H0TYNKYtVJ/7cWFS7soJBQWhcPctKyYL/4=
|
||||
github.com/go-faster/yamlx v0.4.1 h1:00RQkZopoLDF1SgBDJVHuN6epTOK7T0TkN427vbvEBk=
|
||||
github.com/go-faster/yamlx v0.4.1/go.mod h1:QXr/i3Z00jRhskgyWkoGsEdseebd/ZbZEpGS6DJv8oo=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw=
|
||||
github.com/golang/glog v1.1.1/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v23.3.3+incompatible h1:5PJI/WbJkaMTvpGxsHVKG/LurN/KnWXNyGpwSCDgen0=
|
||||
github.com/google/flatbuffers v23.3.3+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
|
||||
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/ogen-go/ogen v0.60.1 h1:yOt0i6NcH7jM3rBi9nnv5VsGUQRw4ACUMsiJojnqrAM=
|
||||
github.com/ogen-go/ogen v0.60.1/go.mod h1:tcwLpHe4vyk9xtbTMe3yu3Qtcbz8VjrpBz9LzsdwWvQ=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sethvargo/go-envconfig v0.9.0 h1:Q6FQ6hVEeTECULvkJZakq3dZMeBQ3JUpcKMfPQbKMDE=
|
||||
github.com/sethvargo/go-envconfig v0.9.0/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
|
||||
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
|
||||
go.opentelemetry.io/otel/metric v0.37.0 h1:pHDQuLQOZwYD+Km0eb657A25NaRzy0a+eLyKfDXedEs=
|
||||
go.opentelemetry.io/otel/metric v0.37.0/go.mod h1:DmdaHfGt54iV6UKxsV9slj2bBRJcKC1B1uvDLIioc1s=
|
||||
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
|
||||
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
|
||||
golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
132
ports/rest/converter.go
Normal file
132
ports/rest/converter.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/derfenix/webarchive/api/openapi"
|
||||
"github.com/derfenix/webarchive/entity"
|
||||
)
|
||||
|
||||
func PageToRestWithResults(page *entity.Page) openapi.PageWithResults {
|
||||
return openapi.PageWithResults{
|
||||
ID: page.ID,
|
||||
URL: page.URL,
|
||||
Created: page.Created,
|
||||
Formats: func() []openapi.Format {
|
||||
res := make([]openapi.Format, len(page.Formats))
|
||||
|
||||
for i, format := range page.Formats {
|
||||
res[i] = FormatToRest(format)
|
||||
}
|
||||
|
||||
return res
|
||||
}(),
|
||||
Status: StatusToRest(page.Status),
|
||||
Results: func() []openapi.Result {
|
||||
results := make([]openapi.Result, len(page.Results.Results()))
|
||||
|
||||
for i := range results {
|
||||
result := &page.Results.Results()[i]
|
||||
|
||||
results[i] = openapi.Result{
|
||||
Format: FormatToRest(result.Format),
|
||||
Error: openapi.NewOptString(result.Err.Error()),
|
||||
Files: func() []openapi.ResultFilesItem {
|
||||
files := make([]openapi.ResultFilesItem, len(results[i].Files))
|
||||
|
||||
for j := range files {
|
||||
file := &result.Files[j]
|
||||
|
||||
files[i] = openapi.ResultFilesItem{
|
||||
ID: file.ID,
|
||||
Name: file.Name,
|
||||
Mimetype: file.MimeType,
|
||||
Size: file.Size,
|
||||
}
|
||||
}
|
||||
|
||||
return files
|
||||
}(),
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}(),
|
||||
}
|
||||
}
|
||||
|
||||
func PageToRest(page *entity.Page) openapi.Page {
|
||||
return openapi.Page{
|
||||
ID: page.ID,
|
||||
URL: page.URL,
|
||||
Created: page.Created,
|
||||
Formats: func() []openapi.Format {
|
||||
res := make([]openapi.Format, len(page.Formats))
|
||||
|
||||
for i, format := range page.Formats {
|
||||
res[i] = FormatToRest(format)
|
||||
}
|
||||
|
||||
return res
|
||||
}(),
|
||||
Status: StatusToRest(page.Status),
|
||||
}
|
||||
}
|
||||
|
||||
func StatusToRest(s entity.Status) openapi.Status {
|
||||
switch s {
|
||||
case entity.StatusNew:
|
||||
return openapi.StatusNew
|
||||
case entity.StatusProcessing:
|
||||
return openapi.StatusProcessing
|
||||
case entity.StatusDone:
|
||||
return openapi.StatusDone
|
||||
case entity.StatusFailed:
|
||||
return openapi.StatusFailed
|
||||
case entity.StatusWithErrors:
|
||||
return openapi.StatusWithErrors
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func FormatFromRest(format []openapi.Format) []entity.Format {
|
||||
var formats []entity.Format
|
||||
|
||||
switch {
|
||||
case len(format) == 0 || (len(format) == 1 && format[0] == openapi.FormatAll):
|
||||
formats = []entity.Format{
|
||||
entity.FormatHeaders,
|
||||
entity.FormatPDF,
|
||||
entity.FormatSingleFile,
|
||||
}
|
||||
|
||||
default:
|
||||
formats = make([]entity.Format, len(format))
|
||||
for i, format := range format {
|
||||
switch format {
|
||||
case openapi.FormatPdf:
|
||||
formats[i] = entity.FormatPDF
|
||||
|
||||
case openapi.FormatHeaders:
|
||||
formats[i] = entity.FormatHeaders
|
||||
|
||||
case openapi.FormatSinglePage:
|
||||
formats[i] = entity.FormatSingleFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formats
|
||||
}
|
||||
|
||||
func FormatToRest(format entity.Format) openapi.Format {
|
||||
switch format {
|
||||
case entity.FormatPDF:
|
||||
return openapi.FormatPdf
|
||||
case entity.FormatSingleFile:
|
||||
return openapi.FormatSinglePage
|
||||
case entity.FormatHeaders:
|
||||
return openapi.FormatHeaders
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
75
ports/rest/service.go
Normal file
75
ports/rest/service.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/derfenix/webarchive/api/openapi"
|
||||
"github.com/derfenix/webarchive/entity"
|
||||
)
|
||||
|
||||
type Pages interface {
|
||||
ListAll(ctx context.Context) ([]*entity.Page, error)
|
||||
Save(ctx context.Context, site *entity.Page) error
|
||||
Get(_ context.Context, id uuid.UUID) (*entity.Page, error)
|
||||
}
|
||||
|
||||
func NewService(sites Pages) *Service {
|
||||
return &Service{pages: sites}
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
openapi.UnimplementedHandler
|
||||
pages Pages
|
||||
}
|
||||
|
||||
func (s *Service) GetPage(ctx context.Context, params openapi.GetPageParams) (openapi.GetPageRes, error) {
|
||||
page, err := s.pages.Get(ctx, params.ID)
|
||||
if err != nil {
|
||||
return &openapi.GetPageNotFound{}, nil
|
||||
}
|
||||
|
||||
restPage := PageToRestWithResults(page)
|
||||
|
||||
return &restPage, nil
|
||||
}
|
||||
|
||||
func (s *Service) AddPage(ctx context.Context, req openapi.OptAddPageReq) (*openapi.Page, error) {
|
||||
site := entity.NewPage(req.Value.URL, req.Value.Description.Value, FormatFromRest(req.Value.Formats)...)
|
||||
|
||||
err := s.pages.Save(ctx, site)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("save site: %w", err)
|
||||
}
|
||||
|
||||
res := PageToRest(site)
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetPages(ctx context.Context) (openapi.Pages, error) {
|
||||
sites, err := s.pages.ListAll(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list all: %w", err)
|
||||
}
|
||||
|
||||
res := make(openapi.Pages, len(sites))
|
||||
for i := range res {
|
||||
res[i] = PageToRest(sites[i])
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Service) NewError(_ context.Context, err error) *openapi.ErrorStatusCode {
|
||||
return &openapi.ErrorStatusCode{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Response: openapi.Error{
|
||||
Message: err.Error(),
|
||||
Localized: openapi.OptString{},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user