Update dependencies and tests
This commit updates the dependencies, vendor files and updates tests to take into account changes to the DB driver. Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
parent
069bdd8b6b
commit
97d03dd38d
693 changed files with 86307 additions and 28214 deletions
131
vendor/github.com/go-openapi/runtime/bytestream.go
generated
vendored
131
vendor/github.com/go-openapi/runtime/bytestream.go
generated
vendored
|
|
@ -38,9 +38,16 @@ type byteStreamOpts struct {
|
|||
Close bool
|
||||
}
|
||||
|
||||
// ByteStreamConsumer creates a consumer for byte streams,
|
||||
// takes a Writer/BinaryUnmarshaler interface or binary slice by reference,
|
||||
// and reads from the provided reader
|
||||
// ByteStreamConsumer creates a consumer for byte streams.
|
||||
//
|
||||
// The consumer consumes from a provided reader into the data passed by reference.
|
||||
//
|
||||
// Supported output underlying types and interfaces, prioritized in this order:
|
||||
// - io.ReaderFrom (for maximum control)
|
||||
// - io.Writer (performs io.Copy)
|
||||
// - encoding.BinaryUnmarshaler
|
||||
// - *string
|
||||
// - *[]byte
|
||||
func ByteStreamConsumer(opts ...byteStreamOpt) Consumer {
|
||||
var vals byteStreamOpts
|
||||
for _, opt := range opts {
|
||||
|
|
@ -51,10 +58,13 @@ func ByteStreamConsumer(opts ...byteStreamOpt) Consumer {
|
|||
if reader == nil {
|
||||
return errors.New("ByteStreamConsumer requires a reader") // early exit
|
||||
}
|
||||
if data == nil {
|
||||
return errors.New("nil destination for ByteStreamConsumer")
|
||||
}
|
||||
|
||||
closer := defaultCloser
|
||||
if vals.Close {
|
||||
if cl, ok := reader.(io.Closer); ok {
|
||||
if cl, isReaderCloser := reader.(io.Closer); isReaderCloser {
|
||||
closer = cl.Close
|
||||
}
|
||||
}
|
||||
|
|
@ -62,34 +72,56 @@ func ByteStreamConsumer(opts ...byteStreamOpt) Consumer {
|
|||
_ = closer()
|
||||
}()
|
||||
|
||||
if wrtr, ok := data.(io.Writer); ok {
|
||||
_, err := io.Copy(wrtr, reader)
|
||||
if readerFrom, isReaderFrom := data.(io.ReaderFrom); isReaderFrom {
|
||||
_, err := readerFrom.ReadFrom(reader)
|
||||
return err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if writer, isDataWriter := data.(io.Writer); isDataWriter {
|
||||
_, err := io.Copy(writer, reader)
|
||||
return err
|
||||
}
|
||||
|
||||
// buffers input before writing to data
|
||||
var buf bytes.Buffer
|
||||
_, err := buf.ReadFrom(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b := buf.Bytes()
|
||||
|
||||
if bu, ok := data.(encoding.BinaryUnmarshaler); ok {
|
||||
return bu.UnmarshalBinary(b)
|
||||
}
|
||||
switch destinationPointer := data.(type) {
|
||||
case encoding.BinaryUnmarshaler:
|
||||
return destinationPointer.UnmarshalBinary(b)
|
||||
case *any:
|
||||
switch (*destinationPointer).(type) {
|
||||
case string:
|
||||
*destinationPointer = string(b)
|
||||
|
||||
return nil
|
||||
|
||||
case []byte:
|
||||
*destinationPointer = b
|
||||
|
||||
if data != nil {
|
||||
if str, ok := data.(*string); ok {
|
||||
*str = string(b)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
// check for the underlying type to be pointer to []byte or string,
|
||||
if ptr := reflect.TypeOf(data); ptr.Kind() != reflect.Ptr {
|
||||
return errors.New("destination must be a pointer")
|
||||
}
|
||||
|
||||
if t := reflect.TypeOf(data); data != nil && t.Kind() == reflect.Ptr {
|
||||
v := reflect.Indirect(reflect.ValueOf(data))
|
||||
if t = v.Type(); t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
|
||||
t := v.Type()
|
||||
|
||||
switch {
|
||||
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8:
|
||||
v.SetBytes(b)
|
||||
return nil
|
||||
|
||||
case t.Kind() == reflect.String:
|
||||
v.SetString(string(b))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,21 +130,35 @@ func ByteStreamConsumer(opts ...byteStreamOpt) Consumer {
|
|||
})
|
||||
}
|
||||
|
||||
// ByteStreamProducer creates a producer for byte streams,
|
||||
// takes a Reader/BinaryMarshaler interface or binary slice,
|
||||
// and writes to a writer (essentially a pipe)
|
||||
// ByteStreamProducer creates a producer for byte streams.
|
||||
//
|
||||
// The producer takes input data then writes to an output writer (essentially as a pipe).
|
||||
//
|
||||
// Supported input underlying types and interfaces, prioritized in this order:
|
||||
// - io.WriterTo (for maximum control)
|
||||
// - io.Reader (performs io.Copy). A ReadCloser is closed before exiting.
|
||||
// - encoding.BinaryMarshaler
|
||||
// - error (writes as a string)
|
||||
// - []byte
|
||||
// - string
|
||||
// - struct, other slices: writes as JSON
|
||||
func ByteStreamProducer(opts ...byteStreamOpt) Producer {
|
||||
var vals byteStreamOpts
|
||||
for _, opt := range opts {
|
||||
opt(&vals)
|
||||
}
|
||||
|
||||
return ProducerFunc(func(writer io.Writer, data interface{}) error {
|
||||
if writer == nil {
|
||||
return errors.New("ByteStreamProducer requires a writer") // early exit
|
||||
}
|
||||
if data == nil {
|
||||
return errors.New("nil data for ByteStreamProducer")
|
||||
}
|
||||
|
||||
closer := defaultCloser
|
||||
if vals.Close {
|
||||
if cl, ok := writer.(io.Closer); ok {
|
||||
if cl, isWriterCloser := writer.(io.Closer); isWriterCloser {
|
||||
closer = cl.Close
|
||||
}
|
||||
}
|
||||
|
|
@ -120,46 +166,51 @@ func ByteStreamProducer(opts ...byteStreamOpt) Producer {
|
|||
_ = closer()
|
||||
}()
|
||||
|
||||
if rc, ok := data.(io.ReadCloser); ok {
|
||||
if rc, isDataCloser := data.(io.ReadCloser); isDataCloser {
|
||||
defer rc.Close()
|
||||
}
|
||||
|
||||
if rdr, ok := data.(io.Reader); ok {
|
||||
_, err := io.Copy(writer, rdr)
|
||||
switch origin := data.(type) {
|
||||
case io.WriterTo:
|
||||
_, err := origin.WriteTo(writer)
|
||||
return err
|
||||
}
|
||||
|
||||
if bm, ok := data.(encoding.BinaryMarshaler); ok {
|
||||
bytes, err := bm.MarshalBinary()
|
||||
case io.Reader:
|
||||
_, err := io.Copy(writer, origin)
|
||||
return err
|
||||
|
||||
case encoding.BinaryMarshaler:
|
||||
bytes, err := origin.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = writer.Write(bytes)
|
||||
return err
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
if str, ok := data.(string); ok {
|
||||
_, err := writer.Write([]byte(str))
|
||||
return err
|
||||
}
|
||||
|
||||
if e, ok := data.(error); ok {
|
||||
_, err := writer.Write([]byte(e.Error()))
|
||||
return err
|
||||
}
|
||||
case error:
|
||||
_, err := writer.Write([]byte(origin.Error()))
|
||||
return err
|
||||
|
||||
default:
|
||||
v := reflect.Indirect(reflect.ValueOf(data))
|
||||
if t := v.Type(); t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
|
||||
t := v.Type()
|
||||
|
||||
switch {
|
||||
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8:
|
||||
_, err := writer.Write(v.Bytes())
|
||||
return err
|
||||
}
|
||||
if t := v.Type(); t.Kind() == reflect.Struct || t.Kind() == reflect.Slice {
|
||||
|
||||
case t.Kind() == reflect.String:
|
||||
_, err := writer.Write([]byte(v.String()))
|
||||
return err
|
||||
|
||||
case t.Kind() == reflect.Struct || t.Kind() == reflect.Slice:
|
||||
b, err := swag.WriteJSON(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = writer.Write(b)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
4
vendor/github.com/go-openapi/runtime/client/request.go
generated
vendored
4
vendor/github.com/go-openapi/runtime/client/request.go
generated
vendored
|
|
@ -36,7 +36,7 @@ import (
|
|||
)
|
||||
|
||||
// NewRequest creates a new swagger http client request
|
||||
func newRequest(method, pathPattern string, writer runtime.ClientRequestWriter) (*request, error) {
|
||||
func newRequest(method, pathPattern string, writer runtime.ClientRequestWriter) *request {
|
||||
return &request{
|
||||
pathPattern: pathPattern,
|
||||
method: method,
|
||||
|
|
@ -45,7 +45,7 @@ func newRequest(method, pathPattern string, writer runtime.ClientRequestWriter)
|
|||
query: make(url.Values),
|
||||
timeout: DefaultTimeout,
|
||||
getBody: getRequestBuffer,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Request represents a swagger client request.
|
||||
|
|
|
|||
56
vendor/github.com/go-openapi/runtime/client/runtime.go
generated
vendored
56
vendor/github.com/go-openapi/runtime/client/runtime.go
generated
vendored
|
|
@ -22,6 +22,7 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
|
|
@ -31,12 +32,13 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/logger"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/runtime/yamlpc"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -142,7 +144,7 @@ func TLSClientAuth(opts TLSClientOptions) (*tls.Config, error) {
|
|||
return nil, fmt.Errorf("tls client priv key: %v", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("tls client priv key: unsupported key type")
|
||||
return nil, errors.New("tls client priv key: unsupported key type")
|
||||
}
|
||||
|
||||
block = pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes}
|
||||
|
|
@ -378,14 +380,11 @@ func (r *Runtime) EnableConnectionReuse() {
|
|||
func (r *Runtime) createHttpRequest(operation *runtime.ClientOperation) (*request, *http.Request, error) { //nolint:revive,stylecheck
|
||||
params, _, auth := operation.Params, operation.Reader, operation.AuthInfo
|
||||
|
||||
request, err := newRequest(operation.Method, operation.PathPattern, params)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
request := newRequest(operation.Method, operation.PathPattern, params)
|
||||
|
||||
var accept []string
|
||||
accept = append(accept, operation.ProducesMediaTypes...)
|
||||
if err = request.SetHeaderParam(runtime.HeaderAccept, accept...); err != nil {
|
||||
if err := request.SetHeaderParam(runtime.HeaderAccept, accept...); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
|
|
@ -457,27 +456,36 @@ func (r *Runtime) Submit(operation *runtime.ClientOperation) (interface{}, error
|
|||
r.logger.Debugf("%s\n", string(b))
|
||||
}
|
||||
|
||||
var hasTimeout bool
|
||||
pctx := operation.Context
|
||||
if pctx == nil {
|
||||
pctx = r.Context
|
||||
} else {
|
||||
hasTimeout = true
|
||||
var parentCtx context.Context
|
||||
switch {
|
||||
case operation.Context != nil:
|
||||
parentCtx = operation.Context
|
||||
case r.Context != nil:
|
||||
parentCtx = r.Context
|
||||
default:
|
||||
parentCtx = context.Background()
|
||||
}
|
||||
if pctx == nil {
|
||||
pctx = context.Background()
|
||||
}
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
if hasTimeout {
|
||||
ctx, cancel = context.WithCancel(pctx)
|
||||
|
||||
var (
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
)
|
||||
if request.timeout == 0 {
|
||||
// There may be a deadline in the context passed to the operation.
|
||||
// Otherwise, there is no timeout set.
|
||||
ctx, cancel = context.WithCancel(parentCtx)
|
||||
} else {
|
||||
ctx, cancel = context.WithTimeout(pctx, request.timeout)
|
||||
// Sets the timeout passed from request params (by default runtime.DefaultTimeout).
|
||||
// If there is already a deadline in the parent context, the shortest will
|
||||
// apply.
|
||||
ctx, cancel = context.WithTimeout(parentCtx, request.timeout)
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
client := operation.Client
|
||||
if client == nil {
|
||||
var client *http.Client
|
||||
if operation.Client != nil {
|
||||
client = operation.Client
|
||||
} else {
|
||||
client = r.client
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
|
|
|||
337
vendor/github.com/go-openapi/runtime/csv.go
generated
vendored
337
vendor/github.com/go-openapi/runtime/csv.go
generated
vendored
|
|
@ -16,62 +16,335 @@ package runtime
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// CSVConsumer creates a new CSV consumer
|
||||
func CSVConsumer() Consumer {
|
||||
// CSVConsumer creates a new CSV consumer.
|
||||
//
|
||||
// The consumer consumes CSV records from a provided reader into the data passed by reference.
|
||||
//
|
||||
// CSVOpts options may be specified to alter the default CSV behavior on the reader and the writer side (e.g. separator, skip header, ...).
|
||||
// The defaults are those of the standard library's csv.Reader and csv.Writer.
|
||||
//
|
||||
// Supported output underlying types and interfaces, prioritized in this order:
|
||||
// - *csv.Writer
|
||||
// - CSVWriter (writer options are ignored)
|
||||
// - io.Writer (as raw bytes)
|
||||
// - io.ReaderFrom (as raw bytes)
|
||||
// - encoding.BinaryUnmarshaler (as raw bytes)
|
||||
// - *[][]string (as a collection of records)
|
||||
// - *[]byte (as raw bytes)
|
||||
// - *string (a raw bytes)
|
||||
//
|
||||
// The consumer prioritizes situations where buffering the input is not required.
|
||||
func CSVConsumer(opts ...CSVOpt) Consumer {
|
||||
o := csvOptsWithDefaults(opts)
|
||||
|
||||
return ConsumerFunc(func(reader io.Reader, data interface{}) error {
|
||||
if reader == nil {
|
||||
return errors.New("CSVConsumer requires a reader")
|
||||
}
|
||||
if data == nil {
|
||||
return errors.New("nil destination for CSVConsumer")
|
||||
}
|
||||
|
||||
csvReader := csv.NewReader(reader)
|
||||
writer, ok := data.(io.Writer)
|
||||
if !ok {
|
||||
return errors.New("data type must be io.Writer")
|
||||
}
|
||||
csvWriter := csv.NewWriter(writer)
|
||||
records, err := csvReader.ReadAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range records {
|
||||
if err := csvWriter.Write(r); err != nil {
|
||||
return err
|
||||
o.applyToReader(csvReader)
|
||||
closer := defaultCloser
|
||||
if o.closeStream {
|
||||
if cl, isReaderCloser := reader.(io.Closer); isReaderCloser {
|
||||
closer = cl.Close
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
_ = closer()
|
||||
}()
|
||||
|
||||
switch destination := data.(type) {
|
||||
case *csv.Writer:
|
||||
csvWriter := destination
|
||||
o.applyToWriter(csvWriter)
|
||||
|
||||
return pipeCSV(csvWriter, csvReader, o)
|
||||
|
||||
case CSVWriter:
|
||||
csvWriter := destination
|
||||
// no writer options available
|
||||
|
||||
return pipeCSV(csvWriter, csvReader, o)
|
||||
|
||||
case io.Writer:
|
||||
csvWriter := csv.NewWriter(destination)
|
||||
o.applyToWriter(csvWriter)
|
||||
|
||||
return pipeCSV(csvWriter, csvReader, o)
|
||||
|
||||
case io.ReaderFrom:
|
||||
var buf bytes.Buffer
|
||||
csvWriter := csv.NewWriter(&buf)
|
||||
o.applyToWriter(csvWriter)
|
||||
if err := bufferedCSV(csvWriter, csvReader, o); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := destination.ReadFrom(&buf)
|
||||
|
||||
return err
|
||||
|
||||
case encoding.BinaryUnmarshaler:
|
||||
var buf bytes.Buffer
|
||||
csvWriter := csv.NewWriter(&buf)
|
||||
o.applyToWriter(csvWriter)
|
||||
if err := bufferedCSV(csvWriter, csvReader, o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return destination.UnmarshalBinary(buf.Bytes())
|
||||
|
||||
default:
|
||||
// support *[][]string, *[]byte, *string
|
||||
if ptr := reflect.TypeOf(data); ptr.Kind() != reflect.Ptr {
|
||||
return errors.New("destination must be a pointer")
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(data))
|
||||
t := v.Type()
|
||||
|
||||
switch {
|
||||
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Slice && t.Elem().Elem().Kind() == reflect.String:
|
||||
csvWriter := &csvRecordsWriter{}
|
||||
// writer options are ignored
|
||||
if err := pipeCSV(csvWriter, csvReader, o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.Grow(len(csvWriter.records))
|
||||
v.SetCap(len(csvWriter.records)) // in case Grow was unnessary, trim down the capacity
|
||||
v.SetLen(len(csvWriter.records))
|
||||
reflect.Copy(v, reflect.ValueOf(csvWriter.records))
|
||||
|
||||
return nil
|
||||
|
||||
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8:
|
||||
var buf bytes.Buffer
|
||||
csvWriter := csv.NewWriter(&buf)
|
||||
o.applyToWriter(csvWriter)
|
||||
if err := bufferedCSV(csvWriter, csvReader, o); err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetBytes(buf.Bytes())
|
||||
|
||||
return nil
|
||||
|
||||
case t.Kind() == reflect.String:
|
||||
var buf bytes.Buffer
|
||||
csvWriter := csv.NewWriter(&buf)
|
||||
o.applyToWriter(csvWriter)
|
||||
if err := bufferedCSV(csvWriter, csvReader, o); err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetString(buf.String())
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%v (%T) is not supported by the CSVConsumer, %s",
|
||||
data, data, "can be resolved by supporting CSVWriter/Writer/BinaryUnmarshaler interface",
|
||||
)
|
||||
}
|
||||
}
|
||||
csvWriter.Flush()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// CSVProducer creates a new CSV producer
|
||||
func CSVProducer() Producer {
|
||||
// CSVProducer creates a new CSV producer.
|
||||
//
|
||||
// The producer takes input data then writes as CSV to an output writer (essentially as a pipe).
|
||||
//
|
||||
// Supported input underlying types and interfaces, prioritized in this order:
|
||||
// - *csv.Reader
|
||||
// - CSVReader (reader options are ignored)
|
||||
// - io.Reader
|
||||
// - io.WriterTo
|
||||
// - encoding.BinaryMarshaler
|
||||
// - [][]string
|
||||
// - []byte
|
||||
// - string
|
||||
//
|
||||
// The producer prioritizes situations where buffering the input is not required.
|
||||
func CSVProducer(opts ...CSVOpt) Producer {
|
||||
o := csvOptsWithDefaults(opts)
|
||||
|
||||
return ProducerFunc(func(writer io.Writer, data interface{}) error {
|
||||
if writer == nil {
|
||||
return errors.New("CSVProducer requires a writer")
|
||||
}
|
||||
|
||||
dataBytes, ok := data.([]byte)
|
||||
if !ok {
|
||||
return errors.New("data type must be byte array")
|
||||
if data == nil {
|
||||
return errors.New("nil data for CSVProducer")
|
||||
}
|
||||
|
||||
csvReader := csv.NewReader(bytes.NewBuffer(dataBytes))
|
||||
records, err := csvReader.ReadAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
csvWriter := csv.NewWriter(writer)
|
||||
for _, r := range records {
|
||||
if err := csvWriter.Write(r); err != nil {
|
||||
return err
|
||||
o.applyToWriter(csvWriter)
|
||||
closer := defaultCloser
|
||||
if o.closeStream {
|
||||
if cl, isWriterCloser := writer.(io.Closer); isWriterCloser {
|
||||
closer = cl.Close
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
_ = closer()
|
||||
}()
|
||||
|
||||
if rc, isDataCloser := data.(io.ReadCloser); isDataCloser {
|
||||
defer rc.Close()
|
||||
}
|
||||
|
||||
switch origin := data.(type) {
|
||||
case *csv.Reader:
|
||||
csvReader := origin
|
||||
o.applyToReader(csvReader)
|
||||
|
||||
return pipeCSV(csvWriter, csvReader, o)
|
||||
|
||||
case CSVReader:
|
||||
csvReader := origin
|
||||
// no reader options available
|
||||
|
||||
return pipeCSV(csvWriter, csvReader, o)
|
||||
|
||||
case io.Reader:
|
||||
csvReader := csv.NewReader(origin)
|
||||
o.applyToReader(csvReader)
|
||||
|
||||
return pipeCSV(csvWriter, csvReader, o)
|
||||
|
||||
case io.WriterTo:
|
||||
// async piping of the writes performed by WriteTo
|
||||
r, w := io.Pipe()
|
||||
csvReader := csv.NewReader(r)
|
||||
o.applyToReader(csvReader)
|
||||
|
||||
pipe, _ := errgroup.WithContext(context.Background())
|
||||
pipe.Go(func() error {
|
||||
_, err := origin.WriteTo(w)
|
||||
_ = w.Close()
|
||||
return err
|
||||
})
|
||||
|
||||
pipe.Go(func() error {
|
||||
defer func() {
|
||||
_ = r.Close()
|
||||
}()
|
||||
|
||||
return pipeCSV(csvWriter, csvReader, o)
|
||||
})
|
||||
|
||||
return pipe.Wait()
|
||||
|
||||
case encoding.BinaryMarshaler:
|
||||
buf, err := origin.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rdr := bytes.NewBuffer(buf)
|
||||
csvReader := csv.NewReader(rdr)
|
||||
|
||||
return bufferedCSV(csvWriter, csvReader, o)
|
||||
|
||||
default:
|
||||
// support [][]string, []byte, string (or pointers to those)
|
||||
v := reflect.Indirect(reflect.ValueOf(data))
|
||||
t := v.Type()
|
||||
|
||||
switch {
|
||||
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Slice && t.Elem().Elem().Kind() == reflect.String:
|
||||
csvReader := &csvRecordsWriter{
|
||||
records: make([][]string, v.Len()),
|
||||
}
|
||||
reflect.Copy(reflect.ValueOf(csvReader.records), v)
|
||||
|
||||
return pipeCSV(csvWriter, csvReader, o)
|
||||
|
||||
case t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8:
|
||||
buf := bytes.NewBuffer(v.Bytes())
|
||||
csvReader := csv.NewReader(buf)
|
||||
o.applyToReader(csvReader)
|
||||
|
||||
return bufferedCSV(csvWriter, csvReader, o)
|
||||
|
||||
case t.Kind() == reflect.String:
|
||||
buf := bytes.NewBufferString(v.String())
|
||||
csvReader := csv.NewReader(buf)
|
||||
o.applyToReader(csvReader)
|
||||
|
||||
return bufferedCSV(csvWriter, csvReader, o)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("%v (%T) is not supported by the CSVProducer, %s",
|
||||
data, data, "can be resolved by supporting CSVReader/Reader/BinaryMarshaler interface",
|
||||
)
|
||||
}
|
||||
}
|
||||
csvWriter.Flush()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// pipeCSV copies CSV records from a CSV reader to a CSV writer
|
||||
func pipeCSV(csvWriter CSVWriter, csvReader CSVReader, opts csvOpts) error {
|
||||
for ; opts.skippedLines > 0; opts.skippedLines-- {
|
||||
_, err := csvReader.Read()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
record, err := csvReader.Read()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := csvWriter.Write(record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
csvWriter.Flush()
|
||||
|
||||
return csvWriter.Error()
|
||||
}
|
||||
|
||||
// bufferedCSV copies CSV records from a CSV reader to a CSV writer,
|
||||
// by first reading all records then writing them at once.
|
||||
func bufferedCSV(csvWriter *csv.Writer, csvReader *csv.Reader, opts csvOpts) error {
|
||||
for ; opts.skippedLines > 0; opts.skippedLines-- {
|
||||
_, err := csvReader.Read()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
records, err := csvReader.ReadAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return csvWriter.WriteAll(records)
|
||||
}
|
||||
|
|
|
|||
121
vendor/github.com/go-openapi/runtime/csv_options.go
generated
vendored
Normal file
121
vendor/github.com/go-openapi/runtime/csv_options.go
generated
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
)
|
||||
|
||||
// CSVOpts alter the behavior of the CSV consumer or producer.
|
||||
type CSVOpt func(*csvOpts)
|
||||
|
||||
type csvOpts struct {
|
||||
csvReader csv.Reader
|
||||
csvWriter csv.Writer
|
||||
skippedLines int
|
||||
closeStream bool
|
||||
}
|
||||
|
||||
// WithCSVReaderOpts specifies the options to csv.Reader
|
||||
// when reading CSV.
|
||||
func WithCSVReaderOpts(reader csv.Reader) CSVOpt {
|
||||
return func(o *csvOpts) {
|
||||
o.csvReader = reader
|
||||
}
|
||||
}
|
||||
|
||||
// WithCSVWriterOpts specifies the options to csv.Writer
|
||||
// when writing CSV.
|
||||
func WithCSVWriterOpts(writer csv.Writer) CSVOpt {
|
||||
return func(o *csvOpts) {
|
||||
o.csvWriter = writer
|
||||
}
|
||||
}
|
||||
|
||||
// WithCSVSkipLines will skip header lines.
|
||||
func WithCSVSkipLines(skipped int) CSVOpt {
|
||||
return func(o *csvOpts) {
|
||||
o.skippedLines = skipped
|
||||
}
|
||||
}
|
||||
|
||||
func WithCSVClosesStream() CSVOpt {
|
||||
return func(o *csvOpts) {
|
||||
o.closeStream = true
|
||||
}
|
||||
}
|
||||
|
||||
func (o csvOpts) applyToReader(in *csv.Reader) {
|
||||
if o.csvReader.Comma != 0 {
|
||||
in.Comma = o.csvReader.Comma
|
||||
}
|
||||
if o.csvReader.Comment != 0 {
|
||||
in.Comment = o.csvReader.Comment
|
||||
}
|
||||
if o.csvReader.FieldsPerRecord != 0 {
|
||||
in.FieldsPerRecord = o.csvReader.FieldsPerRecord
|
||||
}
|
||||
|
||||
in.LazyQuotes = o.csvReader.LazyQuotes
|
||||
in.TrimLeadingSpace = o.csvReader.TrimLeadingSpace
|
||||
in.ReuseRecord = o.csvReader.ReuseRecord
|
||||
}
|
||||
|
||||
func (o csvOpts) applyToWriter(in *csv.Writer) {
|
||||
if o.csvWriter.Comma != 0 {
|
||||
in.Comma = o.csvWriter.Comma
|
||||
}
|
||||
in.UseCRLF = o.csvWriter.UseCRLF
|
||||
}
|
||||
|
||||
func csvOptsWithDefaults(opts []CSVOpt) csvOpts {
|
||||
var o csvOpts
|
||||
for _, apply := range opts {
|
||||
apply(&o)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
type CSVWriter interface {
|
||||
Write([]string) error
|
||||
Flush()
|
||||
Error() error
|
||||
}
|
||||
|
||||
type CSVReader interface {
|
||||
Read() ([]string, error)
|
||||
}
|
||||
|
||||
var (
|
||||
_ CSVWriter = &csvRecordsWriter{}
|
||||
_ CSVReader = &csvRecordsWriter{}
|
||||
)
|
||||
|
||||
// csvRecordsWriter is an internal container to move CSV records back and forth
|
||||
type csvRecordsWriter struct {
|
||||
i int
|
||||
records [][]string
|
||||
}
|
||||
|
||||
func (w *csvRecordsWriter) Write(record []string) error {
|
||||
w.records = append(w.records, record)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *csvRecordsWriter) Read() ([]string, error) {
|
||||
if w.i >= len(w.records) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
defer func() {
|
||||
w.i++
|
||||
}()
|
||||
|
||||
return w.records[w.i], nil
|
||||
}
|
||||
|
||||
func (w *csvRecordsWriter) Flush() {}
|
||||
|
||||
func (w *csvRecordsWriter) Error() error {
|
||||
return nil
|
||||
}
|
||||
2
vendor/github.com/go-openapi/runtime/logger/standard.go
generated
vendored
2
vendor/github.com/go-openapi/runtime/logger/standard.go
generated
vendored
|
|
@ -5,6 +5,8 @@ import (
|
|||
"os"
|
||||
)
|
||||
|
||||
var _ Logger = StandardLogger{}
|
||||
|
||||
type StandardLogger struct{}
|
||||
|
||||
func (StandardLogger) Printf(format string, args ...interface{}) {
|
||||
|
|
|
|||
161
vendor/github.com/go-openapi/runtime/middleware/context.go
generated
vendored
161
vendor/github.com/go-openapi/runtime/middleware/context.go
generated
vendored
|
|
@ -18,6 +18,8 @@ import (
|
|||
stdContext "context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
|
@ -35,12 +37,21 @@ import (
|
|||
|
||||
// Debug when true turns on verbose logging
|
||||
var Debug = logger.DebugEnabled()
|
||||
|
||||
// Logger is the standard libray logger used for printing debug messages
|
||||
var Logger logger.Logger = logger.StandardLogger{}
|
||||
|
||||
func debugLog(format string, args ...interface{}) { //nolint:goprintffuncname
|
||||
if Debug {
|
||||
Logger.Printf(format, args...)
|
||||
func debugLogfFunc(lg logger.Logger) func(string, ...any) {
|
||||
if logger.DebugEnabled() {
|
||||
if lg == nil {
|
||||
return Logger.Debugf
|
||||
}
|
||||
|
||||
return lg.Debugf
|
||||
}
|
||||
|
||||
// muted logger
|
||||
return func(_ string, _ ...any) {}
|
||||
}
|
||||
|
||||
// A Builder can create middlewares
|
||||
|
|
@ -73,10 +84,11 @@ func (fn ResponderFunc) WriteResponse(rw http.ResponseWriter, pr runtime.Produce
|
|||
// used throughout to store request context with the standard context attached
|
||||
// to the http.Request
|
||||
type Context struct {
|
||||
spec *loads.Document
|
||||
analyzer *analysis.Spec
|
||||
api RoutableAPI
|
||||
router Router
|
||||
spec *loads.Document
|
||||
analyzer *analysis.Spec
|
||||
api RoutableAPI
|
||||
router Router
|
||||
debugLogf func(string, ...any) // a logging function to debug context and all components using it
|
||||
}
|
||||
|
||||
type routableUntypedAPI struct {
|
||||
|
|
@ -189,7 +201,9 @@ func (r *routableUntypedAPI) DefaultConsumes() string {
|
|||
return r.defaultConsumes
|
||||
}
|
||||
|
||||
// NewRoutableContext creates a new context for a routable API
|
||||
// NewRoutableContext creates a new context for a routable API.
|
||||
//
|
||||
// If a nil Router is provided, the DefaultRouter (denco-based) will be used.
|
||||
func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Router) *Context {
|
||||
var an *analysis.Spec
|
||||
if spec != nil {
|
||||
|
|
@ -199,26 +213,40 @@ func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Ro
|
|||
return NewRoutableContextWithAnalyzedSpec(spec, an, routableAPI, routes)
|
||||
}
|
||||
|
||||
// NewRoutableContextWithAnalyzedSpec is like NewRoutableContext but takes in input the analysed spec too
|
||||
// NewRoutableContextWithAnalyzedSpec is like NewRoutableContext but takes as input an already analysed spec.
|
||||
//
|
||||
// If a nil Router is provided, the DefaultRouter (denco-based) will be used.
|
||||
func NewRoutableContextWithAnalyzedSpec(spec *loads.Document, an *analysis.Spec, routableAPI RoutableAPI, routes Router) *Context {
|
||||
// Either there are no spec doc and analysis, or both of them.
|
||||
if !((spec == nil && an == nil) || (spec != nil && an != nil)) {
|
||||
panic(errors.New(http.StatusInternalServerError, "routable context requires either both spec doc and analysis, or none of them"))
|
||||
}
|
||||
|
||||
ctx := &Context{spec: spec, api: routableAPI, analyzer: an, router: routes}
|
||||
return ctx
|
||||
return &Context{
|
||||
spec: spec,
|
||||
api: routableAPI,
|
||||
analyzer: an,
|
||||
router: routes,
|
||||
debugLogf: debugLogfFunc(nil),
|
||||
}
|
||||
}
|
||||
|
||||
// NewContext creates a new context wrapper
|
||||
// NewContext creates a new context wrapper.
|
||||
//
|
||||
// If a nil Router is provided, the DefaultRouter (denco-based) will be used.
|
||||
func NewContext(spec *loads.Document, api *untyped.API, routes Router) *Context {
|
||||
var an *analysis.Spec
|
||||
if spec != nil {
|
||||
an = analysis.New(spec.Spec())
|
||||
}
|
||||
ctx := &Context{spec: spec, analyzer: an}
|
||||
ctx := &Context{
|
||||
spec: spec,
|
||||
analyzer: an,
|
||||
router: routes,
|
||||
debugLogf: debugLogfFunc(nil),
|
||||
}
|
||||
ctx.api = newRoutableUntypedAPI(spec, api, ctx)
|
||||
ctx.router = routes
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
|
|
@ -282,6 +310,13 @@ func (c *Context) BasePath() string {
|
|||
return c.spec.BasePath()
|
||||
}
|
||||
|
||||
// SetLogger allows for injecting a logger to catch debug entries.
|
||||
//
|
||||
// The logger is enabled in DEBUG mode only.
|
||||
func (c *Context) SetLogger(lg logger.Logger) {
|
||||
c.debugLogf = debugLogfFunc(lg)
|
||||
}
|
||||
|
||||
// RequiredProduces returns the accepted content types for responses
|
||||
func (c *Context) RequiredProduces() []string {
|
||||
return c.analyzer.RequiredProduces()
|
||||
|
|
@ -299,6 +334,7 @@ func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, b
|
|||
if err != nil {
|
||||
res = append(res, err)
|
||||
} else {
|
||||
c.debugLogf("validating content type for %q against [%s]", ct, strings.Join(route.Consumes, ", "))
|
||||
if err := validateContentType(route.Consumes, ct); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
|
@ -397,16 +433,16 @@ func (c *Context) ResponseFormat(r *http.Request, offers []string) (string, *htt
|
|||
var rCtx = r.Context()
|
||||
|
||||
if v, ok := rCtx.Value(ctxResponseFormat).(string); ok {
|
||||
debugLog("[%s %s] found response format %q in context", r.Method, r.URL.Path, v)
|
||||
c.debugLogf("[%s %s] found response format %q in context", r.Method, r.URL.Path, v)
|
||||
return v, r
|
||||
}
|
||||
|
||||
format := NegotiateContentType(r, offers, "")
|
||||
if format != "" {
|
||||
debugLog("[%s %s] set response format %q in context", r.Method, r.URL.Path, format)
|
||||
c.debugLogf("[%s %s] set response format %q in context", r.Method, r.URL.Path, format)
|
||||
r = r.WithContext(stdContext.WithValue(rCtx, ctxResponseFormat, format))
|
||||
}
|
||||
debugLog("[%s %s] negotiated response format %q", r.Method, r.URL.Path, format)
|
||||
c.debugLogf("[%s %s] negotiated response format %q", r.Method, r.URL.Path, format)
|
||||
return format, r
|
||||
}
|
||||
|
||||
|
|
@ -469,7 +505,7 @@ func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute)
|
|||
var rCtx = request.Context()
|
||||
|
||||
if v, ok := rCtx.Value(ctxBoundParams).(*validation); ok {
|
||||
debugLog("got cached validation (valid: %t)", len(v.result) == 0)
|
||||
c.debugLogf("got cached validation (valid: %t)", len(v.result) == 0)
|
||||
if len(v.result) > 0 {
|
||||
return v.bound, request, errors.CompositeValidationError(v.result...)
|
||||
}
|
||||
|
|
@ -481,7 +517,7 @@ func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute)
|
|||
if len(result.result) > 0 {
|
||||
return result.bound, request, errors.CompositeValidationError(result.result...)
|
||||
}
|
||||
debugLog("no validation errors found")
|
||||
c.debugLogf("no validation errors found")
|
||||
return result.bound, request, nil
|
||||
}
|
||||
|
||||
|
|
@ -492,7 +528,7 @@ func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Respond renders the response after doing some content negotiation
|
||||
func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) {
|
||||
debugLog("responding to %s %s with produces: %v", r.Method, r.URL.Path, produces)
|
||||
c.debugLogf("responding to %s %s with produces: %v", r.Method, r.URL.Path, produces)
|
||||
offers := []string{}
|
||||
for _, mt := range produces {
|
||||
if mt != c.api.DefaultProduces() {
|
||||
|
|
@ -501,7 +537,7 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
|
|||
}
|
||||
// the default producer is last so more specific producers take precedence
|
||||
offers = append(offers, c.api.DefaultProduces())
|
||||
debugLog("offers: %v", offers)
|
||||
c.debugLogf("offers: %v", offers)
|
||||
|
||||
var format string
|
||||
format, r = c.ResponseFormat(r, offers)
|
||||
|
|
@ -584,45 +620,92 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
|
|||
c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response"))
|
||||
}
|
||||
|
||||
func (c *Context) APIHandlerSwaggerUI(builder Builder) http.Handler {
|
||||
// APIHandlerSwaggerUI returns a handler to serve the API.
|
||||
//
|
||||
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
|
||||
//
|
||||
// A spec UI (SwaggerUI) is served at {API base path}/docs and the spec document at /swagger.json
|
||||
// (these can be modified with uiOptions).
|
||||
func (c *Context) APIHandlerSwaggerUI(builder Builder, opts ...UIOption) http.Handler {
|
||||
b := builder
|
||||
if b == nil {
|
||||
b = PassthroughBuilder
|
||||
}
|
||||
|
||||
var title string
|
||||
sp := c.spec.Spec()
|
||||
if sp != nil && sp.Info != nil && sp.Info.Title != "" {
|
||||
title = sp.Info.Title
|
||||
}
|
||||
specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
|
||||
var swaggerUIOpts SwaggerUIOpts
|
||||
fromCommonToAnyOptions(uiOpts, &swaggerUIOpts)
|
||||
|
||||
swaggerUIOpts := SwaggerUIOpts{
|
||||
BasePath: c.BasePath(),
|
||||
Title: title,
|
||||
}
|
||||
|
||||
return Spec("", c.spec.Raw(), SwaggerUI(swaggerUIOpts, c.RoutesHandler(b)))
|
||||
return Spec(specPath, c.spec.Raw(), SwaggerUI(swaggerUIOpts, c.RoutesHandler(b)), specOpts...)
|
||||
}
|
||||
|
||||
// APIHandler returns a handler to serve the API, this includes a swagger spec, router and the contract defined in the swagger spec
|
||||
func (c *Context) APIHandler(builder Builder) http.Handler {
|
||||
// APIHandlerRapiDoc returns a handler to serve the API.
|
||||
//
|
||||
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
|
||||
//
|
||||
// A spec UI (RapiDoc) is served at {API base path}/docs and the spec document at /swagger.json
|
||||
// (these can be modified with uiOptions).
|
||||
func (c *Context) APIHandlerRapiDoc(builder Builder, opts ...UIOption) http.Handler {
|
||||
b := builder
|
||||
if b == nil {
|
||||
b = PassthroughBuilder
|
||||
}
|
||||
|
||||
specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
|
||||
var rapidocUIOpts RapiDocOpts
|
||||
fromCommonToAnyOptions(uiOpts, &rapidocUIOpts)
|
||||
|
||||
return Spec(specPath, c.spec.Raw(), RapiDoc(rapidocUIOpts, c.RoutesHandler(b)), specOpts...)
|
||||
}
|
||||
|
||||
// APIHandler returns a handler to serve the API.
|
||||
//
|
||||
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
|
||||
//
|
||||
// A spec UI (Redoc) is served at {API base path}/docs and the spec document at /swagger.json
|
||||
// (these can be modified with uiOptions).
|
||||
func (c *Context) APIHandler(builder Builder, opts ...UIOption) http.Handler {
|
||||
b := builder
|
||||
if b == nil {
|
||||
b = PassthroughBuilder
|
||||
}
|
||||
|
||||
specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
|
||||
var redocOpts RedocOpts
|
||||
fromCommonToAnyOptions(uiOpts, &redocOpts)
|
||||
|
||||
return Spec(specPath, c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(b)), specOpts...)
|
||||
}
|
||||
|
||||
func (c Context) uiOptionsForHandler(opts []UIOption) (string, uiOptions, []SpecOption) {
|
||||
var title string
|
||||
sp := c.spec.Spec()
|
||||
if sp != nil && sp.Info != nil && sp.Info.Title != "" {
|
||||
title = sp.Info.Title
|
||||
}
|
||||
|
||||
redocOpts := RedocOpts{
|
||||
BasePath: c.BasePath(),
|
||||
Title: title,
|
||||
// default options (may be overridden)
|
||||
optsForContext := []UIOption{
|
||||
WithUIBasePath(c.BasePath()),
|
||||
WithUITitle(title),
|
||||
}
|
||||
optsForContext = append(optsForContext, opts...)
|
||||
uiOpts := uiOptionsWithDefaults(optsForContext)
|
||||
|
||||
// If spec URL is provided, there is a non-default path to serve the spec.
|
||||
// This makes sure that the UI middleware is aligned with the Spec middleware.
|
||||
u, _ := url.Parse(uiOpts.SpecURL)
|
||||
var specPath string
|
||||
if u != nil {
|
||||
specPath = u.Path
|
||||
}
|
||||
|
||||
return Spec("", c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(b)))
|
||||
pth, doc := path.Split(specPath)
|
||||
if pth == "." {
|
||||
pth = ""
|
||||
}
|
||||
|
||||
return pth, uiOpts, []SpecOption{WithSpecDocument(doc)}
|
||||
}
|
||||
|
||||
// RoutesHandler returns a handler to serve the API, just the routes and the contract defined in the swagger spec
|
||||
|
|
|
|||
30
vendor/github.com/go-openapi/runtime/middleware/denco/router.go
generated
vendored
30
vendor/github.com/go-openapi/runtime/middleware/denco/router.go
generated
vendored
|
|
@ -2,6 +2,7 @@
|
|||
package denco
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
|
@ -29,13 +30,13 @@ const (
|
|||
|
||||
// Router represents a URL router.
|
||||
type Router struct {
|
||||
param *doubleArray
|
||||
// SizeHint expects the maximum number of path parameters in records to Build.
|
||||
// SizeHint will be used to determine the capacity of the memory to allocate.
|
||||
// By default, SizeHint will be determined from given records to Build.
|
||||
SizeHint int
|
||||
|
||||
static map[string]interface{}
|
||||
param *doubleArray
|
||||
}
|
||||
|
||||
// New returns a new Router.
|
||||
|
|
@ -71,7 +72,7 @@ func (rt *Router) Lookup(path string) (data interface{}, params Params, found bo
|
|||
func (rt *Router) Build(records []Record) error {
|
||||
statics, params := makeRecords(records)
|
||||
if len(params) > MaxSize {
|
||||
return fmt.Errorf("denco: too many records")
|
||||
return errors.New("denco: too many records")
|
||||
}
|
||||
if rt.SizeHint < 0 {
|
||||
rt.SizeHint = 0
|
||||
|
|
@ -197,24 +198,29 @@ func (da *doubleArray) lookup(path string, params []Param, idx int) (*node, []Pa
|
|||
if next := nextIndex(da.bc[idx].Base(), TerminationCharacter); next < len(da.bc) && da.bc[next].Check() == TerminationCharacter {
|
||||
return da.node[da.bc[next].Base()], params, true
|
||||
}
|
||||
|
||||
BACKTRACKING:
|
||||
for j := len(indices) - 1; j >= 0; j-- {
|
||||
i, idx := int(indices[j]>>32), int(indices[j]&0xffffffff)
|
||||
if da.bc[idx].IsSingleParam() {
|
||||
idx := nextIndex(da.bc[idx].Base(), ParamCharacter) //nolint:govet
|
||||
if idx >= len(da.bc) {
|
||||
nextIdx := nextIndex(da.bc[idx].Base(), ParamCharacter)
|
||||
if nextIdx >= len(da.bc) {
|
||||
break
|
||||
}
|
||||
|
||||
next := NextSeparator(path, i)
|
||||
params := append(params, Param{Value: path[i:next]}) //nolint:govet
|
||||
if nd, params, found := da.lookup(path[next:], params, idx); found { //nolint:govet
|
||||
return nd, params, true
|
||||
nextParams := params
|
||||
nextParams = append(nextParams, Param{Value: path[i:next]})
|
||||
if nd, nextNextParams, found := da.lookup(path[next:], nextParams, nextIdx); found {
|
||||
return nd, nextNextParams, true
|
||||
}
|
||||
}
|
||||
|
||||
if da.bc[idx].IsWildcardParam() {
|
||||
idx := nextIndex(da.bc[idx].Base(), WildcardCharacter) //nolint:govet
|
||||
params := append(params, Param{Value: path[i:]}) //nolint:govet
|
||||
return da.node[da.bc[idx].Base()], params, true
|
||||
nextIdx := nextIndex(da.bc[idx].Base(), WildcardCharacter)
|
||||
nextParams := params
|
||||
nextParams = append(nextParams, Param{Value: path[i:]})
|
||||
return da.node[da.bc[nextIdx].Base()], nextParams, true
|
||||
}
|
||||
}
|
||||
return nil, nil, false
|
||||
|
|
@ -326,7 +332,7 @@ func (da *doubleArray) arrange(records []*record, idx, depth int, usedBase map[i
|
|||
}
|
||||
base = da.findBase(siblings, idx, usedBase)
|
||||
if base > MaxSize {
|
||||
return -1, nil, nil, fmt.Errorf("denco: too many elements of internal slice")
|
||||
return -1, nil, nil, errors.New("denco: too many elements of internal slice")
|
||||
}
|
||||
da.setBase(idx, base)
|
||||
return base, siblings, leaf, err
|
||||
|
|
@ -387,7 +393,7 @@ func makeSiblings(records []*record, depth int) (sib []sibling, leaf *record, er
|
|||
case pc == c:
|
||||
continue
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("denco: BUG: routing table hasn't been sorted")
|
||||
return nil, nil, errors.New("denco: BUG: routing table hasn't been sorted")
|
||||
}
|
||||
if n > 0 {
|
||||
sib[n-1].end = i
|
||||
|
|
|
|||
10
vendor/github.com/go-openapi/runtime/middleware/go18.go
generated
vendored
10
vendor/github.com/go-openapi/runtime/middleware/go18.go
generated
vendored
|
|
@ -1,10 +0,0 @@
|
|||
//go:build go1.8
|
||||
// +build go1.8
|
||||
|
||||
package middleware
|
||||
|
||||
import "net/url"
|
||||
|
||||
func pathUnescape(path string) (string, error) {
|
||||
return url.PathUnescape(path)
|
||||
}
|
||||
9
vendor/github.com/go-openapi/runtime/middleware/pre_go18.go
generated
vendored
9
vendor/github.com/go-openapi/runtime/middleware/pre_go18.go
generated
vendored
|
|
@ -1,9 +0,0 @@
|
|||
// +build !go1.8
|
||||
|
||||
package middleware
|
||||
|
||||
import "net/url"
|
||||
|
||||
func pathUnescape(path string) (string, error) {
|
||||
return url.QueryUnescape(path)
|
||||
}
|
||||
70
vendor/github.com/go-openapi/runtime/middleware/rapidoc.go
generated
vendored
70
vendor/github.com/go-openapi/runtime/middleware/rapidoc.go
generated
vendored
|
|
@ -1,4 +1,3 @@
|
|||
//nolint:dupl
|
||||
package middleware
|
||||
|
||||
import (
|
||||
|
|
@ -11,66 +10,57 @@ import (
|
|||
|
||||
// RapiDocOpts configures the RapiDoc middlewares
|
||||
type RapiDocOpts struct {
|
||||
// BasePath for the UI path, defaults to: /
|
||||
// BasePath for the UI, defaults to: /
|
||||
BasePath string
|
||||
// Path combines with BasePath for the full UI path, defaults to: docs
|
||||
|
||||
// Path combines with BasePath to construct the path to the UI, defaults to: "docs".
|
||||
Path string
|
||||
// SpecURL the url to find the spec for
|
||||
|
||||
// SpecURL is the URL of the spec document.
|
||||
//
|
||||
// Defaults to: /swagger.json
|
||||
SpecURL string
|
||||
// RapiDocURL for the js that generates the rapidoc site, defaults to: https://cdn.jsdelivr.net/npm/rapidoc/bundles/rapidoc.standalone.js
|
||||
RapiDocURL string
|
||||
|
||||
// Title for the documentation site, default to: API documentation
|
||||
Title string
|
||||
|
||||
// Template specifies a custom template to serve the UI
|
||||
Template string
|
||||
|
||||
// RapiDocURL points to the js asset that generates the rapidoc site.
|
||||
//
|
||||
// Defaults to https://unpkg.com/rapidoc/dist/rapidoc-min.js
|
||||
RapiDocURL string
|
||||
}
|
||||
|
||||
// EnsureDefaults in case some options are missing
|
||||
func (r *RapiDocOpts) EnsureDefaults() {
|
||||
if r.BasePath == "" {
|
||||
r.BasePath = "/"
|
||||
}
|
||||
if r.Path == "" {
|
||||
r.Path = defaultDocsPath
|
||||
}
|
||||
if r.SpecURL == "" {
|
||||
r.SpecURL = defaultDocsURL
|
||||
}
|
||||
common := toCommonUIOptions(r)
|
||||
common.EnsureDefaults()
|
||||
fromCommonToAnyOptions(common, r)
|
||||
|
||||
// rapidoc-specifics
|
||||
if r.RapiDocURL == "" {
|
||||
r.RapiDocURL = rapidocLatest
|
||||
}
|
||||
if r.Title == "" {
|
||||
r.Title = defaultDocsTitle
|
||||
if r.Template == "" {
|
||||
r.Template = rapidocTemplate
|
||||
}
|
||||
}
|
||||
|
||||
// RapiDoc creates a middleware to serve a documentation site for a swagger spec.
|
||||
//
|
||||
// This allows for altering the spec before starting the http listener.
|
||||
func RapiDoc(opts RapiDocOpts, next http.Handler) http.Handler {
|
||||
opts.EnsureDefaults()
|
||||
|
||||
pth := path.Join(opts.BasePath, opts.Path)
|
||||
tmpl := template.Must(template.New("rapidoc").Parse(rapidocTemplate))
|
||||
tmpl := template.Must(template.New("rapidoc").Parse(opts.Template))
|
||||
assets := bytes.NewBuffer(nil)
|
||||
if err := tmpl.Execute(assets, opts); err != nil {
|
||||
panic(fmt.Errorf("cannot execute template: %w", err))
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_ = tmpl.Execute(buf, opts)
|
||||
b := buf.Bytes()
|
||||
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == pth {
|
||||
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
_, _ = rw.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
if next == nil {
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
_, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
return serveUI(pth, assets.Bytes(), next)
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
|||
69
vendor/github.com/go-openapi/runtime/middleware/redoc.go
generated
vendored
69
vendor/github.com/go-openapi/runtime/middleware/redoc.go
generated
vendored
|
|
@ -1,4 +1,3 @@
|
|||
//nolint:dupl
|
||||
package middleware
|
||||
|
||||
import (
|
||||
|
|
@ -11,66 +10,58 @@ import (
|
|||
|
||||
// RedocOpts configures the Redoc middlewares
|
||||
type RedocOpts struct {
|
||||
// BasePath for the UI path, defaults to: /
|
||||
// BasePath for the UI, defaults to: /
|
||||
BasePath string
|
||||
// Path combines with BasePath for the full UI path, defaults to: docs
|
||||
|
||||
// Path combines with BasePath to construct the path to the UI, defaults to: "docs".
|
||||
Path string
|
||||
// SpecURL the url to find the spec for
|
||||
|
||||
// SpecURL is the URL of the spec document.
|
||||
//
|
||||
// Defaults to: /swagger.json
|
||||
SpecURL string
|
||||
// RedocURL for the js that generates the redoc site, defaults to: https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js
|
||||
RedocURL string
|
||||
|
||||
// Title for the documentation site, default to: API documentation
|
||||
Title string
|
||||
|
||||
// Template specifies a custom template to serve the UI
|
||||
Template string
|
||||
|
||||
// RedocURL points to the js that generates the redoc site.
|
||||
//
|
||||
// Defaults to: https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js
|
||||
RedocURL string
|
||||
}
|
||||
|
||||
// EnsureDefaults in case some options are missing
|
||||
func (r *RedocOpts) EnsureDefaults() {
|
||||
if r.BasePath == "" {
|
||||
r.BasePath = "/"
|
||||
}
|
||||
if r.Path == "" {
|
||||
r.Path = defaultDocsPath
|
||||
}
|
||||
if r.SpecURL == "" {
|
||||
r.SpecURL = defaultDocsURL
|
||||
}
|
||||
common := toCommonUIOptions(r)
|
||||
common.EnsureDefaults()
|
||||
fromCommonToAnyOptions(common, r)
|
||||
|
||||
// redoc-specifics
|
||||
if r.RedocURL == "" {
|
||||
r.RedocURL = redocLatest
|
||||
}
|
||||
if r.Title == "" {
|
||||
r.Title = defaultDocsTitle
|
||||
if r.Template == "" {
|
||||
r.Template = redocTemplate
|
||||
}
|
||||
}
|
||||
|
||||
// Redoc creates a middleware to serve a documentation site for a swagger spec.
|
||||
//
|
||||
// This allows for altering the spec before starting the http listener.
|
||||
func Redoc(opts RedocOpts, next http.Handler) http.Handler {
|
||||
opts.EnsureDefaults()
|
||||
|
||||
pth := path.Join(opts.BasePath, opts.Path)
|
||||
tmpl := template.Must(template.New("redoc").Parse(redocTemplate))
|
||||
tmpl := template.Must(template.New("redoc").Parse(opts.Template))
|
||||
assets := bytes.NewBuffer(nil)
|
||||
if err := tmpl.Execute(assets, opts); err != nil {
|
||||
panic(fmt.Errorf("cannot execute template: %w", err))
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_ = tmpl.Execute(buf, opts)
|
||||
b := buf.Bytes()
|
||||
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == pth {
|
||||
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
_, _ = rw.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
if next == nil {
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
_, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
return serveUI(pth, assets.Bytes(), next)
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
|||
21
vendor/github.com/go-openapi/runtime/middleware/request.go
generated
vendored
21
vendor/github.com/go-openapi/runtime/middleware/request.go
generated
vendored
|
|
@ -19,10 +19,10 @@ import (
|
|||
"reflect"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/logger"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
)
|
||||
|
||||
// UntypedRequestBinder binds and validates the data from a http request
|
||||
|
|
@ -31,6 +31,7 @@ type UntypedRequestBinder struct {
|
|||
Parameters map[string]spec.Parameter
|
||||
Formats strfmt.Registry
|
||||
paramBinders map[string]*untypedParamBinder
|
||||
debugLogf func(string, ...any) // a logging function to debug context and all components using it
|
||||
}
|
||||
|
||||
// NewUntypedRequestBinder creates a new binder for reading a request.
|
||||
|
|
@ -44,6 +45,7 @@ func NewUntypedRequestBinder(parameters map[string]spec.Parameter, spec *spec.Sw
|
|||
paramBinders: binders,
|
||||
Spec: spec,
|
||||
Formats: formats,
|
||||
debugLogf: debugLogfFunc(nil),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,10 +54,10 @@ func (o *UntypedRequestBinder) Bind(request *http.Request, routeParams RoutePara
|
|||
val := reflect.Indirect(reflect.ValueOf(data))
|
||||
isMap := val.Kind() == reflect.Map
|
||||
var result []error
|
||||
debugLog("binding %d parameters for %s %s", len(o.Parameters), request.Method, request.URL.EscapedPath())
|
||||
o.debugLogf("binding %d parameters for %s %s", len(o.Parameters), request.Method, request.URL.EscapedPath())
|
||||
for fieldName, param := range o.Parameters {
|
||||
binder := o.paramBinders[fieldName]
|
||||
debugLog("binding parameter %s for %s %s", fieldName, request.Method, request.URL.EscapedPath())
|
||||
o.debugLogf("binding parameter %s for %s %s", fieldName, request.Method, request.URL.EscapedPath())
|
||||
var target reflect.Value
|
||||
if !isMap {
|
||||
binder.Name = fieldName
|
||||
|
|
@ -102,3 +104,14 @@ func (o *UntypedRequestBinder) Bind(request *http.Request, routeParams RoutePara
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLogger allows for injecting a logger to catch debug entries.
|
||||
//
|
||||
// The logger is enabled in DEBUG mode only.
|
||||
func (o *UntypedRequestBinder) SetLogger(lg logger.Logger) {
|
||||
o.debugLogf = debugLogfFunc(lg)
|
||||
}
|
||||
|
||||
func (o *UntypedRequestBinder) setDebugLogf(fn func(string, ...any)) {
|
||||
o.debugLogf = fn
|
||||
}
|
||||
|
|
|
|||
103
vendor/github.com/go-openapi/runtime/middleware/router.go
generated
vendored
103
vendor/github.com/go-openapi/runtime/middleware/router.go
generated
vendored
|
|
@ -17,10 +17,12 @@ package middleware
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
fpath "path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/runtime/logger"
|
||||
"github.com/go-openapi/runtime/security"
|
||||
"github.com/go-openapi/swag"
|
||||
|
||||
|
|
@ -67,10 +69,10 @@ func (r RouteParams) GetOK(name string) ([]string, bool, bool) {
|
|||
return nil, false, false
|
||||
}
|
||||
|
||||
// NewRouter creates a new context aware router middleware
|
||||
// NewRouter creates a new context-aware router middleware
|
||||
func NewRouter(ctx *Context, next http.Handler) http.Handler {
|
||||
if ctx.router == nil {
|
||||
ctx.router = DefaultRouter(ctx.spec, ctx.api)
|
||||
ctx.router = DefaultRouter(ctx.spec, ctx.api, WithDefaultRouterLoggerFunc(ctx.debugLogf))
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -103,41 +105,75 @@ type RoutableAPI interface {
|
|||
DefaultConsumes() string
|
||||
}
|
||||
|
||||
// Router represents a swagger aware router
|
||||
// Router represents a swagger-aware router
|
||||
type Router interface {
|
||||
Lookup(method, path string) (*MatchedRoute, bool)
|
||||
OtherMethods(method, path string) []string
|
||||
}
|
||||
|
||||
type defaultRouteBuilder struct {
|
||||
spec *loads.Document
|
||||
analyzer *analysis.Spec
|
||||
api RoutableAPI
|
||||
records map[string][]denco.Record
|
||||
spec *loads.Document
|
||||
analyzer *analysis.Spec
|
||||
api RoutableAPI
|
||||
records map[string][]denco.Record
|
||||
debugLogf func(string, ...any) // a logging function to debug context and all components using it
|
||||
}
|
||||
|
||||
type defaultRouter struct {
|
||||
spec *loads.Document
|
||||
routers map[string]*denco.Router
|
||||
spec *loads.Document
|
||||
routers map[string]*denco.Router
|
||||
debugLogf func(string, ...any) // a logging function to debug context and all components using it
|
||||
}
|
||||
|
||||
func newDefaultRouteBuilder(spec *loads.Document, api RoutableAPI) *defaultRouteBuilder {
|
||||
func newDefaultRouteBuilder(spec *loads.Document, api RoutableAPI, opts ...DefaultRouterOpt) *defaultRouteBuilder {
|
||||
var o defaultRouterOpts
|
||||
for _, apply := range opts {
|
||||
apply(&o)
|
||||
}
|
||||
if o.debugLogf == nil {
|
||||
o.debugLogf = debugLogfFunc(nil) // defaults to standard logger
|
||||
}
|
||||
|
||||
return &defaultRouteBuilder{
|
||||
spec: spec,
|
||||
analyzer: analysis.New(spec.Spec()),
|
||||
api: api,
|
||||
records: make(map[string][]denco.Record),
|
||||
spec: spec,
|
||||
analyzer: analysis.New(spec.Spec()),
|
||||
api: api,
|
||||
records: make(map[string][]denco.Record),
|
||||
debugLogf: o.debugLogf,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRouter creates a default implemenation of the router
|
||||
func DefaultRouter(spec *loads.Document, api RoutableAPI) Router {
|
||||
builder := newDefaultRouteBuilder(spec, api)
|
||||
// DefaultRouterOpt allows to inject optional behavior to the default router.
|
||||
type DefaultRouterOpt func(*defaultRouterOpts)
|
||||
|
||||
type defaultRouterOpts struct {
|
||||
debugLogf func(string, ...any)
|
||||
}
|
||||
|
||||
// WithDefaultRouterLogger sets the debug logger for the default router.
|
||||
//
|
||||
// This is enabled only in DEBUG mode.
|
||||
func WithDefaultRouterLogger(lg logger.Logger) DefaultRouterOpt {
|
||||
return func(o *defaultRouterOpts) {
|
||||
o.debugLogf = debugLogfFunc(lg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultRouterLoggerFunc sets a logging debug method for the default router.
|
||||
func WithDefaultRouterLoggerFunc(fn func(string, ...any)) DefaultRouterOpt {
|
||||
return func(o *defaultRouterOpts) {
|
||||
o.debugLogf = fn
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRouter creates a default implementation of the router
|
||||
func DefaultRouter(spec *loads.Document, api RoutableAPI, opts ...DefaultRouterOpt) Router {
|
||||
builder := newDefaultRouteBuilder(spec, api, opts...)
|
||||
if spec != nil {
|
||||
for method, paths := range builder.analyzer.Operations() {
|
||||
for path, operation := range paths {
|
||||
fp := fpath.Join(spec.BasePath(), path)
|
||||
debugLog("adding route %s %s %q", method, fp, operation.ID)
|
||||
builder.debugLogf("adding route %s %s %q", method, fp, operation.ID)
|
||||
builder.AddRoute(method, fp, operation)
|
||||
}
|
||||
}
|
||||
|
|
@ -319,24 +355,24 @@ func (m *MatchedRoute) NeedsAuth() bool {
|
|||
|
||||
func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) {
|
||||
mth := strings.ToUpper(method)
|
||||
debugLog("looking up route for %s %s", method, path)
|
||||
d.debugLogf("looking up route for %s %s", method, path)
|
||||
if Debug {
|
||||
if len(d.routers) == 0 {
|
||||
debugLog("there are no known routers")
|
||||
d.debugLogf("there are no known routers")
|
||||
}
|
||||
for meth := range d.routers {
|
||||
debugLog("got a router for %s", meth)
|
||||
d.debugLogf("got a router for %s", meth)
|
||||
}
|
||||
}
|
||||
if router, ok := d.routers[mth]; ok {
|
||||
if m, rp, ok := router.Lookup(fpath.Clean(path)); ok && m != nil {
|
||||
if entry, ok := m.(*routeEntry); ok {
|
||||
debugLog("found a route for %s %s with %d parameters", method, path, len(entry.Parameters))
|
||||
d.debugLogf("found a route for %s %s with %d parameters", method, path, len(entry.Parameters))
|
||||
var params RouteParams
|
||||
for _, p := range rp {
|
||||
v, err := pathUnescape(p.Value)
|
||||
v, err := url.PathUnescape(p.Value)
|
||||
if err != nil {
|
||||
debugLog("failed to escape %q: %v", p.Value, err)
|
||||
d.debugLogf("failed to escape %q: %v", p.Value, err)
|
||||
v = p.Value
|
||||
}
|
||||
// a workaround to handle fragment/composing parameters until they are supported in denco router
|
||||
|
|
@ -356,10 +392,10 @@ func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) {
|
|||
return &MatchedRoute{routeEntry: *entry, Params: params}, true
|
||||
}
|
||||
} else {
|
||||
debugLog("couldn't find a route by path for %s %s", method, path)
|
||||
d.debugLogf("couldn't find a route by path for %s %s", method, path)
|
||||
}
|
||||
} else {
|
||||
debugLog("couldn't find a route by method for %s %s", method, path)
|
||||
d.debugLogf("couldn't find a route by method for %s %s", method, path)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
|
@ -378,6 +414,10 @@ func (d *defaultRouter) OtherMethods(method, path string) []string {
|
|||
return methods
|
||||
}
|
||||
|
||||
func (d *defaultRouter) SetLogger(lg logger.Logger) {
|
||||
d.debugLogf = debugLogfFunc(lg)
|
||||
}
|
||||
|
||||
// convert swagger parameters per path segment into a denco parameter as multiple parameters per segment are not supported in denco
|
||||
var pathConverter = regexp.MustCompile(`{(.+?)}([^/]*)`)
|
||||
|
||||
|
|
@ -413,7 +453,7 @@ func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Oper
|
|||
bp = bp[:len(bp)-1]
|
||||
}
|
||||
|
||||
debugLog("operation: %#v", *operation)
|
||||
d.debugLogf("operation: %#v", *operation)
|
||||
if handler, ok := d.api.HandlerFor(method, strings.TrimPrefix(path, bp)); ok {
|
||||
consumes := d.analyzer.ConsumesFor(operation)
|
||||
produces := d.analyzer.ProducesFor(operation)
|
||||
|
|
@ -428,6 +468,8 @@ func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Oper
|
|||
produces = append(produces, defProduces)
|
||||
}
|
||||
|
||||
requestBinder := NewUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats())
|
||||
requestBinder.setDebugLogf(d.debugLogf)
|
||||
record := denco.NewRecord(pathConverter.ReplaceAllString(path, ":$1"), &routeEntry{
|
||||
BasePath: bp,
|
||||
PathPattern: path,
|
||||
|
|
@ -439,7 +481,7 @@ func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Oper
|
|||
Producers: d.api.ProducersFor(normalizeOffers(produces)),
|
||||
Parameters: parameters,
|
||||
Formats: d.api.Formats(),
|
||||
Binder: NewUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats()),
|
||||
Binder: requestBinder,
|
||||
Authenticators: d.buildAuthenticators(operation),
|
||||
Authorizer: d.api.Authorizer(),
|
||||
})
|
||||
|
|
@ -482,7 +524,8 @@ func (d *defaultRouteBuilder) Build() *defaultRouter {
|
|||
routers[method] = router
|
||||
}
|
||||
return &defaultRouter{
|
||||
spec: d.spec,
|
||||
routers: routers,
|
||||
spec: d.spec,
|
||||
routers: routers,
|
||||
debugLogf: d.debugLogf,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
77
vendor/github.com/go-openapi/runtime/middleware/spec.go
generated
vendored
77
vendor/github.com/go-openapi/runtime/middleware/spec.go
generated
vendored
|
|
@ -19,29 +19,84 @@ import (
|
|||
"path"
|
||||
)
|
||||
|
||||
// Spec creates a middleware to serve a swagger spec.
|
||||
const (
|
||||
contentTypeHeader = "Content-Type"
|
||||
applicationJSON = "application/json"
|
||||
)
|
||||
|
||||
// SpecOption can be applied to the Spec serving middleware
|
||||
type SpecOption func(*specOptions)
|
||||
|
||||
var defaultSpecOptions = specOptions{
|
||||
Path: "",
|
||||
Document: "swagger.json",
|
||||
}
|
||||
|
||||
type specOptions struct {
|
||||
Path string
|
||||
Document string
|
||||
}
|
||||
|
||||
func specOptionsWithDefaults(opts []SpecOption) specOptions {
|
||||
o := defaultSpecOptions
|
||||
for _, apply := range opts {
|
||||
apply(&o)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// Spec creates a middleware to serve a swagger spec as a JSON document.
|
||||
//
|
||||
// This allows for altering the spec before starting the http listener.
|
||||
// This can be useful if you want to serve the swagger spec from another path than /swagger.json
|
||||
func Spec(basePath string, b []byte, next http.Handler) http.Handler {
|
||||
//
|
||||
// The basePath argument indicates the path of the spec document (defaults to "/").
|
||||
// Additional SpecOption can be used to change the name of the document (defaults to "swagger.json").
|
||||
func Spec(basePath string, b []byte, next http.Handler, opts ...SpecOption) http.Handler {
|
||||
if basePath == "" {
|
||||
basePath = "/"
|
||||
}
|
||||
pth := path.Join(basePath, "swagger.json")
|
||||
o := specOptionsWithDefaults(opts)
|
||||
pth := path.Join(basePath, o.Path, o.Document)
|
||||
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == pth {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
if path.Clean(r.URL.Path) == pth {
|
||||
rw.Header().Set(contentTypeHeader, applicationJSON)
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
//#nosec
|
||||
_, _ = rw.Write(b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if next == nil {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
if next != nil {
|
||||
next.ServeHTTP(rw, r)
|
||||
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(rw, r)
|
||||
|
||||
rw.Header().Set(contentTypeHeader, applicationJSON)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
// WithSpecPath sets the path to be joined to the base path of the Spec middleware.
|
||||
//
|
||||
// This is empty by default.
|
||||
func WithSpecPath(pth string) SpecOption {
|
||||
return func(o *specOptions) {
|
||||
o.Path = pth
|
||||
}
|
||||
}
|
||||
|
||||
// WithSpecDocument sets the name of the JSON document served as a spec.
|
||||
//
|
||||
// By default, this is "swagger.json"
|
||||
func WithSpecDocument(doc string) SpecOption {
|
||||
return func(o *specOptions) {
|
||||
if doc == "" {
|
||||
return
|
||||
}
|
||||
|
||||
o.Document = doc
|
||||
}
|
||||
}
|
||||
|
|
|
|||
87
vendor/github.com/go-openapi/runtime/middleware/swaggerui.go
generated
vendored
87
vendor/github.com/go-openapi/runtime/middleware/swaggerui.go
generated
vendored
|
|
@ -8,40 +8,65 @@ import (
|
|||
"path"
|
||||
)
|
||||
|
||||
// SwaggerUIOpts configures the Swaggerui middlewares
|
||||
// SwaggerUIOpts configures the SwaggerUI middleware
|
||||
type SwaggerUIOpts struct {
|
||||
// BasePath for the UI path, defaults to: /
|
||||
// BasePath for the API, defaults to: /
|
||||
BasePath string
|
||||
// Path combines with BasePath for the full UI path, defaults to: docs
|
||||
|
||||
// Path combines with BasePath to construct the path to the UI, defaults to: "docs".
|
||||
Path string
|
||||
// SpecURL the url to find the spec for
|
||||
|
||||
// SpecURL is the URL of the spec document.
|
||||
//
|
||||
// Defaults to: /swagger.json
|
||||
SpecURL string
|
||||
|
||||
// Title for the documentation site, default to: API documentation
|
||||
Title string
|
||||
|
||||
// Template specifies a custom template to serve the UI
|
||||
Template string
|
||||
|
||||
// OAuthCallbackURL the url called after OAuth2 login
|
||||
OAuthCallbackURL string
|
||||
|
||||
// The three components needed to embed swagger-ui
|
||||
SwaggerURL string
|
||||
|
||||
// SwaggerURL points to the js that generates the SwaggerUI site.
|
||||
//
|
||||
// Defaults to: https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js
|
||||
SwaggerURL string
|
||||
|
||||
SwaggerPresetURL string
|
||||
SwaggerStylesURL string
|
||||
|
||||
Favicon32 string
|
||||
Favicon16 string
|
||||
|
||||
// Title for the documentation site, default to: API documentation
|
||||
Title string
|
||||
}
|
||||
|
||||
// EnsureDefaults in case some options are missing
|
||||
func (r *SwaggerUIOpts) EnsureDefaults() {
|
||||
if r.BasePath == "" {
|
||||
r.BasePath = "/"
|
||||
r.ensureDefaults()
|
||||
|
||||
if r.Template == "" {
|
||||
r.Template = swaggeruiTemplate
|
||||
}
|
||||
if r.Path == "" {
|
||||
r.Path = defaultDocsPath
|
||||
}
|
||||
if r.SpecURL == "" {
|
||||
r.SpecURL = defaultDocsURL
|
||||
}
|
||||
|
||||
func (r *SwaggerUIOpts) EnsureDefaultsOauth2() {
|
||||
r.ensureDefaults()
|
||||
|
||||
if r.Template == "" {
|
||||
r.Template = swaggerOAuthTemplate
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SwaggerUIOpts) ensureDefaults() {
|
||||
common := toCommonUIOptions(r)
|
||||
common.EnsureDefaults()
|
||||
fromCommonToAnyOptions(common, r)
|
||||
|
||||
// swaggerui-specifics
|
||||
if r.OAuthCallbackURL == "" {
|
||||
r.OAuthCallbackURL = path.Join(r.BasePath, r.Path, "oauth2-callback")
|
||||
}
|
||||
|
|
@ -60,40 +85,22 @@ func (r *SwaggerUIOpts) EnsureDefaults() {
|
|||
if r.Favicon32 == "" {
|
||||
r.Favicon32 = swaggerFavicon32Latest
|
||||
}
|
||||
if r.Title == "" {
|
||||
r.Title = defaultDocsTitle
|
||||
}
|
||||
}
|
||||
|
||||
// SwaggerUI creates a middleware to serve a documentation site for a swagger spec.
|
||||
//
|
||||
// This allows for altering the spec before starting the http listener.
|
||||
func SwaggerUI(opts SwaggerUIOpts, next http.Handler) http.Handler {
|
||||
opts.EnsureDefaults()
|
||||
|
||||
pth := path.Join(opts.BasePath, opts.Path)
|
||||
tmpl := template.Must(template.New("swaggerui").Parse(swaggeruiTemplate))
|
||||
tmpl := template.Must(template.New("swaggerui").Parse(opts.Template))
|
||||
assets := bytes.NewBuffer(nil)
|
||||
if err := tmpl.Execute(assets, opts); err != nil {
|
||||
panic(fmt.Errorf("cannot execute template: %w", err))
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_ = tmpl.Execute(buf, &opts)
|
||||
b := buf.Bytes()
|
||||
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if path.Join(r.URL.Path) == pth {
|
||||
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
_, _ = rw.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
if next == nil {
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
_, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
return serveUI(pth, assets.Bytes(), next)
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
|||
31
vendor/github.com/go-openapi/runtime/middleware/swaggerui_oauth2.go
generated
vendored
31
vendor/github.com/go-openapi/runtime/middleware/swaggerui_oauth2.go
generated
vendored
|
|
@ -4,37 +4,20 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func SwaggerUIOAuth2Callback(opts SwaggerUIOpts, next http.Handler) http.Handler {
|
||||
opts.EnsureDefaults()
|
||||
opts.EnsureDefaultsOauth2()
|
||||
|
||||
pth := opts.OAuthCallbackURL
|
||||
tmpl := template.Must(template.New("swaggeroauth").Parse(swaggerOAuthTemplate))
|
||||
tmpl := template.Must(template.New("swaggeroauth").Parse(opts.Template))
|
||||
assets := bytes.NewBuffer(nil)
|
||||
if err := tmpl.Execute(assets, opts); err != nil {
|
||||
panic(fmt.Errorf("cannot execute template: %w", err))
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
_ = tmpl.Execute(buf, &opts)
|
||||
b := buf.Bytes()
|
||||
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if path.Join(r.URL.Path) == pth {
|
||||
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
_, _ = rw.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
if next == nil {
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
_, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
return serveUI(pth, assets.Bytes(), next)
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
|||
8
vendor/github.com/go-openapi/runtime/middleware/ui_defaults.go
generated
vendored
8
vendor/github.com/go-openapi/runtime/middleware/ui_defaults.go
generated
vendored
|
|
@ -1,8 +0,0 @@
|
|||
package middleware
|
||||
|
||||
const (
|
||||
// constants that are common to all UI-serving middlewares
|
||||
defaultDocsPath = "docs"
|
||||
defaultDocsURL = "/swagger.json"
|
||||
defaultDocsTitle = "API Documentation"
|
||||
)
|
||||
173
vendor/github.com/go-openapi/runtime/middleware/ui_options.go
generated
vendored
Normal file
173
vendor/github.com/go-openapi/runtime/middleware/ui_options.go
generated
vendored
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// constants that are common to all UI-serving middlewares
|
||||
defaultDocsPath = "docs"
|
||||
defaultDocsURL = "/swagger.json"
|
||||
defaultDocsTitle = "API Documentation"
|
||||
)
|
||||
|
||||
// uiOptions defines common options for UI serving middlewares.
|
||||
type uiOptions struct {
|
||||
// BasePath for the UI, defaults to: /
|
||||
BasePath string
|
||||
|
||||
// Path combines with BasePath to construct the path to the UI, defaults to: "docs".
|
||||
Path string
|
||||
|
||||
// SpecURL is the URL of the spec document.
|
||||
//
|
||||
// Defaults to: /swagger.json
|
||||
SpecURL string
|
||||
|
||||
// Title for the documentation site, default to: API documentation
|
||||
Title string
|
||||
|
||||
// Template specifies a custom template to serve the UI
|
||||
Template string
|
||||
}
|
||||
|
||||
// toCommonUIOptions converts any UI option type to retain the common options.
|
||||
//
|
||||
// This uses gob encoding/decoding to convert common fields from one struct to another.
|
||||
func toCommonUIOptions(opts interface{}) uiOptions {
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
dec := gob.NewDecoder(&buf)
|
||||
var o uiOptions
|
||||
err := enc.Encode(opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = dec.Decode(&o)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func fromCommonToAnyOptions[T any](source uiOptions, target *T) {
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
dec := gob.NewDecoder(&buf)
|
||||
err := enc.Encode(source)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = dec.Decode(target)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// UIOption can be applied to UI serving middleware, such as Context.APIHandler or
|
||||
// Context.APIHandlerSwaggerUI to alter the defaut behavior.
|
||||
type UIOption func(*uiOptions)
|
||||
|
||||
func uiOptionsWithDefaults(opts []UIOption) uiOptions {
|
||||
var o uiOptions
|
||||
for _, apply := range opts {
|
||||
apply(&o)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// WithUIBasePath sets the base path from where to serve the UI assets.
|
||||
//
|
||||
// By default, Context middleware sets this value to the API base path.
|
||||
func WithUIBasePath(base string) UIOption {
|
||||
return func(o *uiOptions) {
|
||||
if !strings.HasPrefix(base, "/") {
|
||||
base = "/" + base
|
||||
}
|
||||
o.BasePath = base
|
||||
}
|
||||
}
|
||||
|
||||
// WithUIPath sets the path from where to serve the UI assets (i.e. /{basepath}/{path}.
|
||||
func WithUIPath(pth string) UIOption {
|
||||
return func(o *uiOptions) {
|
||||
o.Path = pth
|
||||
}
|
||||
}
|
||||
|
||||
// WithUISpecURL sets the path from where to serve swagger spec document.
|
||||
//
|
||||
// This may be specified as a full URL or a path.
|
||||
//
|
||||
// By default, this is "/swagger.json"
|
||||
func WithUISpecURL(specURL string) UIOption {
|
||||
return func(o *uiOptions) {
|
||||
o.SpecURL = specURL
|
||||
}
|
||||
}
|
||||
|
||||
// WithUITitle sets the title of the UI.
|
||||
//
|
||||
// By default, Context middleware sets this value to the title found in the API spec.
|
||||
func WithUITitle(title string) UIOption {
|
||||
return func(o *uiOptions) {
|
||||
o.Title = title
|
||||
}
|
||||
}
|
||||
|
||||
// WithTemplate allows to set a custom template for the UI.
|
||||
//
|
||||
// UI middleware will panic if the template does not parse or execute properly.
|
||||
func WithTemplate(tpl string) UIOption {
|
||||
return func(o *uiOptions) {
|
||||
o.Template = tpl
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureDefaults in case some options are missing
|
||||
func (r *uiOptions) EnsureDefaults() {
|
||||
if r.BasePath == "" {
|
||||
r.BasePath = "/"
|
||||
}
|
||||
if r.Path == "" {
|
||||
r.Path = defaultDocsPath
|
||||
}
|
||||
if r.SpecURL == "" {
|
||||
r.SpecURL = defaultDocsURL
|
||||
}
|
||||
if r.Title == "" {
|
||||
r.Title = defaultDocsTitle
|
||||
}
|
||||
}
|
||||
|
||||
// serveUI creates a middleware that serves a templated asset as text/html.
|
||||
func serveUI(pth string, assets []byte, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if path.Clean(r.URL.Path) == pth {
|
||||
rw.Header().Set(contentTypeHeader, "text/html; charset=utf-8")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = rw.Write(assets)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if next != nil {
|
||||
next.ServeHTTP(rw, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set(contentTypeHeader, "text/plain")
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
_, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
|
||||
})
|
||||
}
|
||||
12
vendor/github.com/go-openapi/runtime/middleware/validation.go
generated
vendored
12
vendor/github.com/go-openapi/runtime/middleware/validation.go
generated
vendored
|
|
@ -35,7 +35,6 @@ type validation struct {
|
|||
|
||||
// ContentType validates the content type of a request
|
||||
func validateContentType(allowed []string, actual string) error {
|
||||
debugLog("validating content type for %q against [%s]", actual, strings.Join(allowed, ", "))
|
||||
if len(allowed) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -57,13 +56,13 @@ func validateContentType(allowed []string, actual string) error {
|
|||
}
|
||||
|
||||
func validateRequest(ctx *Context, request *http.Request, route *MatchedRoute) *validation {
|
||||
debugLog("validating request %s %s", request.Method, request.URL.EscapedPath())
|
||||
validate := &validation{
|
||||
context: ctx,
|
||||
request: request,
|
||||
route: route,
|
||||
bound: make(map[string]interface{}),
|
||||
}
|
||||
validate.debugLogf("validating request %s %s", request.Method, request.URL.EscapedPath())
|
||||
|
||||
validate.contentType()
|
||||
if len(validate.result) == 0 {
|
||||
|
|
@ -76,8 +75,12 @@ func validateRequest(ctx *Context, request *http.Request, route *MatchedRoute) *
|
|||
return validate
|
||||
}
|
||||
|
||||
func (v *validation) debugLogf(format string, args ...any) {
|
||||
v.context.debugLogf(format, args...)
|
||||
}
|
||||
|
||||
func (v *validation) parameters() {
|
||||
debugLog("validating request parameters for %s %s", v.request.Method, v.request.URL.EscapedPath())
|
||||
v.debugLogf("validating request parameters for %s %s", v.request.Method, v.request.URL.EscapedPath())
|
||||
if result := v.route.Binder.Bind(v.request, v.route.Params, v.route.Consumer, v.bound); result != nil {
|
||||
if result.Error() == "validation failure list" {
|
||||
for _, e := range result.(*errors.Validation).Value.([]interface{}) {
|
||||
|
|
@ -91,7 +94,7 @@ func (v *validation) parameters() {
|
|||
|
||||
func (v *validation) contentType() {
|
||||
if len(v.result) == 0 && runtime.HasBody(v.request) {
|
||||
debugLog("validating body content type for %s %s", v.request.Method, v.request.URL.EscapedPath())
|
||||
v.debugLogf("validating body content type for %s %s", v.request.Method, v.request.URL.EscapedPath())
|
||||
ct, _, req, err := v.context.ContentType(v.request)
|
||||
if err != nil {
|
||||
v.result = append(v.result, err)
|
||||
|
|
@ -100,6 +103,7 @@ func (v *validation) contentType() {
|
|||
}
|
||||
|
||||
if len(v.result) == 0 {
|
||||
v.debugLogf("validating content type for %q against [%s]", ct, strings.Join(v.route.Consumes, ", "))
|
||||
if err := validateContentType(v.route.Consumes, ct); err != nil {
|
||||
v.result = append(v.result, err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue