New Upstream Release - golang-github-go-openapi-runtime
Ready changes
Summary
Merged new upstream version: 0.25.0 (was: 0.23.3).
Diff
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index cac5e4e..6d8db66 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
- go-version: 1.17.1
+ go-version: 1.18.8
- name: Setup gotestsum
uses: autero1/action-gotestsum@v1.0.0
diff --git a/client/keepalive.go b/client/keepalive.go
index e9c250d..bc7b7fa 100644
--- a/client/keepalive.go
+++ b/client/keepalive.go
@@ -2,7 +2,6 @@ package client
import (
"io"
- "io/ioutil"
"net/http"
"sync/atomic"
)
@@ -50,7 +49,7 @@ func (d *drainingReadCloser) Close() error {
// some bytes, but the closer ignores them to keep the underling
// connection open.
//nolint:errcheck
- io.Copy(ioutil.Discard, d.rdr)
+ io.Copy(io.Discard, d.rdr)
}
return d.rdr.Close()
}
diff --git a/client/keepalive_test.go b/client/keepalive_test.go
index d6b95b4..5046450 100644
--- a/client/keepalive_test.go
+++ b/client/keepalive_test.go
@@ -3,7 +3,6 @@ package client
import (
"bytes"
"io"
- "io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
@@ -39,10 +38,10 @@ func (c *countingReadCloser) Close() error {
func TestDrainingReadCloser(t *testing.T) {
rdr := newCountingReader(bytes.NewBufferString("There are many things to do"), false)
- prevDisc := ioutil.Discard
+ prevDisc := io.Discard
disc := bytes.NewBuffer(nil)
- ioutil.Discard = disc
- defer func() { ioutil.Discard = prevDisc }()
+ io.Discard = disc
+ defer func() { io.Discard = prevDisc }()
buf := make([]byte, 5)
ts := &drainingReadCloser{rdr: rdr}
@@ -57,10 +56,10 @@ func TestDrainingReadCloser(t *testing.T) {
func TestDrainingReadCloser_SeenEOF(t *testing.T) {
rdr := newCountingReader(bytes.NewBufferString("There are many things to do"), true)
- prevDisc := ioutil.Discard
+ prevDisc := io.Discard
disc := bytes.NewBuffer(nil)
- ioutil.Discard = disc
- defer func() { ioutil.Discard = prevDisc }()
+ io.Discard = disc
+ defer func() { io.Discard = prevDisc }()
buf := make([]byte, 5)
ts := &drainingReadCloser{rdr: rdr}
diff --git a/client/opentelemetry.go b/client/opentelemetry.go
new file mode 100644
index 0000000..8a38ea3
--- /dev/null
+++ b/client/opentelemetry.go
@@ -0,0 +1,207 @@
+package client
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/go-openapi/runtime"
+ "github.com/go-openapi/strfmt"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/codes"
+ "go.opentelemetry.io/otel/propagation"
+ semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
+ "go.opentelemetry.io/otel/trace"
+)
+
+const (
+ instrumentationVersion = "1.0.0"
+ tracerName = "go-openapi"
+)
+
+type config struct {
+ Tracer trace.Tracer
+ Propagator propagation.TextMapPropagator
+ SpanStartOptions []trace.SpanStartOption
+ SpanNameFormatter func(*runtime.ClientOperation) string
+ TracerProvider trace.TracerProvider
+}
+
+type OpenTelemetryOpt interface {
+ apply(*config)
+}
+
+type optionFunc func(*config)
+
+func (o optionFunc) apply(c *config) {
+ o(c)
+}
+
+// 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) OpenTelemetryOpt {
+ return optionFunc(func(c *config) {
+ if provider != nil {
+ c.TracerProvider = provider
+ }
+ })
+}
+
+// WithPropagators configures specific propagators. If this
+// option isn't specified, then the global TextMapPropagator is used.
+func WithPropagators(ps propagation.TextMapPropagator) OpenTelemetryOpt {
+ return optionFunc(func(c *config) {
+ if ps != nil {
+ c.Propagator = ps
+ }
+ })
+}
+
+// WithSpanOptions configures an additional set of
+// trace.SpanOptions, which are applied to each new span.
+func WithSpanOptions(opts ...trace.SpanStartOption) OpenTelemetryOpt {
+ return optionFunc(func(c *config) {
+ c.SpanStartOptions = append(c.SpanStartOptions, opts...)
+ })
+}
+
+// WithSpanNameFormatter takes a function that will be called on every
+// request and the returned string will become the Span Name.
+func WithSpanNameFormatter(f func(op *runtime.ClientOperation) string) OpenTelemetryOpt {
+ return optionFunc(func(c *config) {
+ c.SpanNameFormatter = f
+ })
+}
+
+func defaultTransportFormatter(op *runtime.ClientOperation) string {
+ if op.ID != "" {
+ return op.ID
+ }
+
+ return fmt.Sprintf("%s_%s", strings.ToLower(op.Method), op.PathPattern)
+}
+
+type openTelemetryTransport struct {
+ transport runtime.ClientTransport
+ host string
+ tracer trace.Tracer
+ config *config
+}
+
+func newOpenTelemetryTransport(transport runtime.ClientTransport, host string, opts []OpenTelemetryOpt) *openTelemetryTransport {
+ tr := &openTelemetryTransport{
+ transport: transport,
+ host: host,
+ }
+
+ defaultOpts := []OpenTelemetryOpt{
+ WithSpanOptions(trace.WithSpanKind(trace.SpanKindClient)),
+ WithSpanNameFormatter(defaultTransportFormatter),
+ WithPropagators(otel.GetTextMapPropagator()),
+ WithTracerProvider(otel.GetTracerProvider()),
+ }
+
+ c := newConfig(append(defaultOpts, opts...)...)
+ tr.config = c
+
+ return tr
+}
+
+func (t *openTelemetryTransport) Submit(op *runtime.ClientOperation) (interface{}, error) {
+ if op.Context == nil {
+ return t.transport.Submit(op)
+ }
+
+ params := op.Params
+ reader := op.Reader
+
+ var span trace.Span
+ defer func() {
+ if span != nil {
+ span.End()
+ }
+ }()
+
+ op.Params = runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error {
+ span = t.newOpenTelemetrySpan(op, req.GetHeaderParams())
+ return params.WriteToRequest(req, reg)
+ })
+
+ op.Reader = runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
+ if span != nil {
+ statusCode := response.Code()
+ span.SetAttributes(attribute.Int(string(semconv.HTTPStatusCodeKey), statusCode))
+ span.SetStatus(semconv.SpanStatusFromHTTPStatusCodeAndSpanKind(statusCode, trace.SpanKindClient))
+ }
+
+ return reader.ReadResponse(response, consumer)
+ })
+
+ submit, err := t.transport.Submit(op)
+ if err != nil && span != nil {
+ span.RecordError(err)
+ span.SetStatus(codes.Error, err.Error())
+ }
+
+ return submit, err
+}
+
+func (t *openTelemetryTransport) newOpenTelemetrySpan(op *runtime.ClientOperation, header http.Header) trace.Span {
+ ctx := op.Context
+
+ tracer := t.tracer
+ if tracer == nil {
+ if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() {
+ tracer = newTracer(span.TracerProvider())
+ } else {
+ tracer = newTracer(otel.GetTracerProvider())
+ }
+ }
+
+ ctx, span := tracer.Start(ctx, t.config.SpanNameFormatter(op), t.config.SpanStartOptions...)
+
+ var scheme string
+ if len(op.Schemes) > 0 {
+ scheme = op.Schemes[0]
+ }
+
+ span.SetAttributes(
+ attribute.String("net.peer.name", t.host),
+ attribute.String(string(semconv.HTTPRouteKey), op.PathPattern),
+ attribute.String(string(semconv.HTTPMethodKey), op.Method),
+ attribute.String("span.kind", trace.SpanKindClient.String()),
+ attribute.String("http.scheme", scheme),
+ )
+
+ carrier := propagation.HeaderCarrier(header)
+ t.config.Propagator.Inject(ctx, carrier)
+
+ return span
+}
+
+func newTracer(tp trace.TracerProvider) trace.Tracer {
+ return tp.Tracer(tracerName, trace.WithInstrumentationVersion(version()))
+}
+
+func newConfig(opts ...OpenTelemetryOpt) *config {
+ c := &config{
+ Propagator: otel.GetTextMapPropagator(),
+ }
+
+ for _, opt := range opts {
+ opt.apply(c)
+ }
+
+ // Tracer is only initialized if manually specified. Otherwise, can be passed with the tracing context.
+ if c.TracerProvider != nil {
+ c.Tracer = newTracer(c.TracerProvider)
+ }
+
+ return c
+}
+
+// Version is the current release version of the go-runtime instrumentation.
+func version() string {
+ return instrumentationVersion
+}
diff --git a/client/opentelemetry_test.go b/client/opentelemetry_test.go
new file mode 100644
index 0000000..94d8ead
--- /dev/null
+++ b/client/opentelemetry_test.go
@@ -0,0 +1,124 @@
+package client
+
+import (
+ "context"
+ "testing"
+
+ "github.com/go-openapi/runtime"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/codes"
+ "go.opentelemetry.io/otel/propagation"
+ tracesdk "go.opentelemetry.io/otel/sdk/trace"
+ "go.opentelemetry.io/otel/sdk/trace/tracetest"
+ "go.opentelemetry.io/otel/trace"
+)
+
+func Test_OpenTelemetryRuntime_submit(t *testing.T) {
+ t.Parallel()
+
+ exporter := tracetest.NewInMemoryExporter()
+
+ tp := tracesdk.NewTracerProvider(
+ tracesdk.WithSampler(tracesdk.AlwaysSample()),
+ tracesdk.WithSyncer(exporter),
+ )
+
+ otel.SetTracerProvider(tp)
+
+ tracer := tp.Tracer("go-runtime")
+ ctx, _ := tracer.Start(context.Background(), "op")
+
+ assertOpenTelemetrySubmit(t, testOperation(ctx), exporter, 1)
+}
+
+func Test_OpenTelemetryRuntime_submit_nilAuthInfo(t *testing.T) {
+ t.Parallel()
+
+ exporter := tracetest.NewInMemoryExporter()
+
+ tp := tracesdk.NewTracerProvider(
+ tracesdk.WithSampler(tracesdk.AlwaysSample()),
+ tracesdk.WithSyncer(exporter),
+ )
+
+ otel.SetTracerProvider(tp)
+
+ tracer := tp.Tracer("go-runtime")
+ ctx, _ := tracer.Start(context.Background(), "op")
+
+ operation := testOperation(ctx)
+ operation.AuthInfo = nil
+ assertOpenTelemetrySubmit(t, operation, exporter, 1)
+}
+
+func Test_OpenTelemetryRuntime_submit_nilContext(t *testing.T) {
+ exporter := tracetest.NewInMemoryExporter()
+
+ tp := tracesdk.NewTracerProvider(
+ tracesdk.WithSampler(tracesdk.AlwaysSample()),
+ tracesdk.WithSyncer(exporter),
+ )
+
+ otel.SetTracerProvider(tp)
+
+ tracer := tp.Tracer("go-runtime")
+ ctx, _ := tracer.Start(context.Background(), "op")
+ operation := testOperation(ctx)
+ operation.Context = nil
+
+ assertOpenTelemetrySubmit(t, operation, exporter, 0) // just don't panic
+}
+
+func Test_injectOpenTelemetrySpanContext(t *testing.T) {
+ t.Parallel()
+
+ exporter := tracetest.NewInMemoryExporter()
+
+ tp := tracesdk.NewTracerProvider(
+ tracesdk.WithSampler(tracesdk.AlwaysSample()),
+ tracesdk.WithSyncer(exporter),
+ )
+
+ otel.SetTracerProvider(tp)
+
+ tracer := tp.Tracer("go-runtime")
+ ctx, _ := tracer.Start(context.Background(), "op")
+ operation := testOperation(ctx)
+
+ header := map[string][]string{}
+ tr := newOpenTelemetryTransport(&mockRuntime{runtime.TestClientRequest{Headers: header}}, "", nil)
+ tr.config.Propagator = propagation.TraceContext{}
+ _, err := tr.Submit(operation)
+ assert.NoError(t, err)
+
+ assert.Len(t, header, 1)
+}
+
+func assertOpenTelemetrySubmit(t *testing.T, operation *runtime.ClientOperation, exporter *tracetest.InMemoryExporter, expectedSpanCount int) {
+ header := map[string][]string{}
+ tr := newOpenTelemetryTransport(&mockRuntime{runtime.TestClientRequest{Headers: header}}, "remote_host", nil)
+
+ _, err := tr.Submit(operation)
+ require.NoError(t, err)
+
+ spans := exporter.GetSpans()
+ assert.Len(t, spans, expectedSpanCount)
+
+ if expectedSpanCount == 1 {
+ span := spans[0]
+ assert.Equal(t, "getCluster", span.Name)
+ assert.Equal(t, "go-openapi", span.InstrumentationLibrary.Name)
+ assert.Equal(t, span.Status.Code, codes.Error)
+ assert.Equal(t, []attribute.KeyValue{
+ attribute.String("net.peer.name", "remote_host"),
+ attribute.String("http.route", "/kubernetes-clusters/{cluster_id}"),
+ attribute.String("http.method", "GET"),
+ attribute.String("span.kind", trace.SpanKindClient.String()),
+ attribute.String("http.scheme", "https"),
+ attribute.Int("http.status_code", 490),
+ }, span.Attributes)
+ }
+}
diff --git a/client/opentracing_test.go b/client/opentracing_test.go
index 650a437..d3df065 100644
--- a/client/opentracing_test.go
+++ b/client/opentracing_test.go
@@ -4,7 +4,6 @@ import (
"bytes"
"context"
"io"
- "io/ioutil"
"testing"
"github.com/go-openapi/strfmt"
@@ -33,7 +32,7 @@ func (r tres) GetHeaders(_ string) []string {
return []string{"the headers", "the headers2"}
}
func (r tres) Body() io.ReadCloser {
- return ioutil.NopCloser(bytes.NewBufferString("the content"))
+ return io.NopCloser(bytes.NewBufferString("the content"))
}
type mockRuntime struct {
diff --git a/client/request_test.go b/client/request_test.go
index a6d73af..e6a0698 100644
--- a/client/request_test.go
+++ b/client/request_test.go
@@ -20,7 +20,6 @@ import (
"encoding/xml"
"errors"
"io"
- "io/ioutil"
"mime"
"mime/multipart"
"net/http"
@@ -165,7 +164,7 @@ func TestBuildRequest_BuildHTTP_Payload(t *testing.T) {
assert.Equal(t, "world", req.URL.Query().Get("hello"))
assert.Equal(t, "/flats/1234/", req.URL.Path)
expectedBody, _ := json.Marshal(bd)
- actualBody, _ := ioutil.ReadAll(req.Body)
+ actualBody, _ := io.ReadAll(req.Body)
assert.Equal(t, append(expectedBody, '\n'), actualBody)
}
}
@@ -197,7 +196,7 @@ func TestBuildRequest_BuildHTTP_SetsInAuth(t *testing.T) {
assert.Equal(t, "world", req.URL.Query().Get("hello"))
assert.Equal(t, "/flats/1234/", req.URL.Path)
expectedBody, _ := json.Marshal(bd)
- actualBody, _ := ioutil.ReadAll(req.Body)
+ actualBody, _ := io.ReadAll(req.Body)
assert.Equal(t, append(expectedBody, '\n'), actualBody)
}
}
@@ -224,7 +223,7 @@ func TestBuildRequest_BuildHTTP_XMLPayload(t *testing.T) {
assert.Equal(t, "world", req.URL.Query().Get("hello"))
assert.Equal(t, "/flats/1234/", req.URL.Path)
expectedBody, _ := xml.Marshal(bd)
- actualBody, _ := ioutil.ReadAll(req.Body)
+ actualBody, _ := io.ReadAll(req.Body)
assert.Equal(t, expectedBody, actualBody)
}
}
@@ -247,7 +246,7 @@ func TestBuildRequest_BuildHTTP_TextPayload(t *testing.T) {
assert.Equal(t, "world", req.URL.Query().Get("hello"))
assert.Equal(t, "/flats/1234/", req.URL.Path)
expectedBody := []byte(bd)
- actualBody, _ := ioutil.ReadAll(req.Body)
+ actualBody, _ := io.ReadAll(req.Body)
assert.Equal(t, expectedBody, actualBody)
}
}
@@ -269,7 +268,7 @@ func TestBuildRequest_BuildHTTP_Form(t *testing.T) {
assert.Equal(t, "world", req.URL.Query().Get("hello"))
assert.Equal(t, "/flats/1234/", req.URL.Path)
expected := []byte("something=some+value")
- actual, _ := ioutil.ReadAll(req.Body)
+ actual, _ := io.ReadAll(req.Body)
assert.Equal(t, expected, actual)
}
}
@@ -292,7 +291,7 @@ func TestBuildRequest_BuildHTTP_Form_URLEncoded(t *testing.T) {
assert.Equal(t, "world", req.URL.Query().Get("hello"))
assert.Equal(t, "/flats/1234/", req.URL.Path)
expected := []byte("something=some+value")
- actual, _ := ioutil.ReadAll(req.Body)
+ actual, _ := io.ReadAll(req.Body)
assert.Equal(t, expected, actual)
}
}
@@ -316,7 +315,7 @@ func TestBuildRequest_BuildHTTP_Form_Content_Length(t *testing.T) {
assert.Condition(t, func() bool { return req.ContentLength > 0 },
"ContentLength must great than 0. got %d", req.ContentLength)
expected := []byte("something=some+value")
- actual, _ := ioutil.ReadAll(req.Body)
+ actual, _ := io.ReadAll(req.Body)
assert.Equal(t, expected, actual)
}
}
@@ -339,7 +338,7 @@ func TestBuildRequest_BuildHTTP_FormMultipart(t *testing.T) {
assert.Equal(t, "/flats/1234/", req.URL.Path)
expected1 := []byte("Content-Disposition: form-data; name=\"something\"")
expected2 := []byte("some value")
- actual, _ := ioutil.ReadAll(req.Body)
+ actual, _ := io.ReadAll(req.Body)
actuallines := bytes.Split(actual, []byte("\r\n"))
assert.Equal(t, 6, len(actuallines))
boundary := string(actuallines[0])
@@ -371,7 +370,7 @@ func TestBuildRequest_BuildHTTP_FormMultiples(t *testing.T) {
expected1 := []byte("Content-Disposition: form-data; name=\"something\"")
expected2 := []byte("some value")
expected3 := []byte("another value")
- actual, _ := ioutil.ReadAll(req.Body)
+ actual, _ := io.ReadAll(req.Body)
actuallines := bytes.Split(actual, []byte("\r\n"))
assert.Equal(t, 10, len(actuallines))
boundary := string(actuallines[0])
@@ -388,8 +387,8 @@ func TestBuildRequest_BuildHTTP_FormMultiples(t *testing.T) {
}
func TestBuildRequest_BuildHTTP_Files(t *testing.T) {
- cont, _ := ioutil.ReadFile("./runtime.go")
- cont2, _ := ioutil.ReadFile("./request.go")
+ cont, _ := os.ReadFile("./runtime.go")
+ cont2, _ := os.ReadFile("./request.go")
reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error {
_ = req.SetFormParam("something", "some value")
_ = req.SetFileParam("file", mustGetFile("./runtime.go"))
@@ -420,7 +419,7 @@ func TestBuildRequest_BuildHTTP_Files(t *testing.T) {
mpf, _ := mpff.Open()
defer mpf.Close()
assert.Equal(t, filename, mpff.Filename)
- actual, _ := ioutil.ReadAll(mpf)
+ actual, _ := io.ReadAll(mpf)
assert.Equal(t, content, actual)
}
fileverifier("file", 0, "runtime.go", cont)
@@ -432,8 +431,8 @@ func TestBuildRequest_BuildHTTP_Files(t *testing.T) {
}
}
func TestBuildRequest_BuildHTTP_Files_URLEncoded(t *testing.T) {
- cont, _ := ioutil.ReadFile("./runtime.go")
- cont2, _ := ioutil.ReadFile("./request.go")
+ cont, _ := os.ReadFile("./runtime.go")
+ cont2, _ := os.ReadFile("./request.go")
reqWrtr := runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error {
_ = req.SetFormParam("something", "some value")
_ = req.SetFileParam("file", mustGetFile("./runtime.go"))
@@ -464,7 +463,7 @@ func TestBuildRequest_BuildHTTP_Files_URLEncoded(t *testing.T) {
mpf, _ := mpff.Open()
defer mpf.Close()
assert.Equal(t, filename, mpff.Filename)
- actual, _ := ioutil.ReadAll(mpf)
+ actual, _ := io.ReadAll(mpf)
assert.Equal(t, content, actual)
}
fileverifier("file", 0, "runtime.go", cont)
@@ -611,7 +610,7 @@ func TestGetBodyCallsBeforeRoundTrip(t *testing.T) {
// Read the body once before sending the request
body, err := req.GetBody()
require.NoError(t, err)
- bodyContent, err := ioutil.ReadAll(io.Reader(body))
+ bodyContent, err := io.ReadAll(io.Reader(body))
require.EqualValues(t, req.ContentLength, len(bodyContent))
require.NoError(t, err)
require.EqualValues(t, "\"test body\"\n", string(bodyContent))
@@ -619,7 +618,7 @@ func TestGetBodyCallsBeforeRoundTrip(t *testing.T) {
// Read the body a second time before sending the request
body, err = req.GetBody()
require.NoError(t, err)
- bodyContent, err = ioutil.ReadAll(io.Reader(body))
+ bodyContent, err = io.ReadAll(io.Reader(body))
require.NoError(t, err)
require.EqualValues(t, req.ContentLength, len(bodyContent))
require.EqualValues(t, "\"test body\"\n", string(bodyContent))
diff --git a/client/response.go b/client/response.go
index b297a12..0bbd388 100644
--- a/client/response.go
+++ b/client/response.go
@@ -23,6 +23,8 @@ import (
var _ runtime.ClientResponse = response{}
+func newResponse(resp *http.Response) runtime.ClientResponse { return response{resp: resp} }
+
type response struct {
resp *http.Response
}
diff --git a/client/response_test.go b/client/response_test.go
index 5d4694e..458dd04 100644
--- a/client/response_test.go
+++ b/client/response_test.go
@@ -16,7 +16,7 @@ package client
import (
"bytes"
- "io/ioutil"
+ "io"
"net/http"
"testing"
@@ -31,7 +31,7 @@ func TestResponse(t *testing.T) {
under.StatusCode = 392
under.Header = make(http.Header)
under.Header.Set("Blah", "blah blah")
- under.Body = ioutil.NopCloser(bytes.NewBufferString("some content"))
+ under.Body = io.NopCloser(bytes.NewBufferString("some content"))
var resp runtime.ClientResponse = response{under}
assert.EqualValues(t, under.StatusCode, resp.Code())
diff --git a/client/runtime.go b/client/runtime.go
index 6556bac..ccec041 100644
--- a/client/runtime.go
+++ b/client/runtime.go
@@ -23,21 +23,21 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
- "io/ioutil"
"mime"
"net/http"
"net/http/httputil"
+ "os"
"strings"
"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"
)
// TLSClientOptions to configure client authentication with mutual TLS
@@ -164,7 +164,7 @@ func TLSClientAuth(opts TLSClientOptions) (*tls.Config, error) {
cfg.RootCAs = caCertPool
} else if opts.CA != "" {
// load ca cert
- caCert, err := ioutil.ReadFile(opts.CA)
+ caCert, err := os.ReadFile(opts.CA)
if err != nil {
return nil, fmt.Errorf("tls client ca: %v", err)
}
@@ -181,8 +181,6 @@ func TLSClientAuth(opts TLSClientOptions) (*tls.Config, error) {
cfg.ServerName = opts.ServerName
}
- cfg.BuildNameToCertificate()
-
return cfg, nil
}
@@ -225,7 +223,7 @@ type Runtime struct {
Transport http.RoundTripper
Jar http.CookieJar
- //Spec *spec.Document
+ // Spec *spec.Document
Host string
BasePath string
Formats strfmt.Registry
@@ -237,6 +235,7 @@ type Runtime struct {
clientOnce *sync.Once
client *http.Client
schemes []string
+ response ClientResponseFunc
}
// New creates a new default runtime for a swagger api runtime.Client
@@ -275,6 +274,7 @@ func New(host, basePath string, schemes []string) *Runtime {
rt.Debug = logger.DebugEnabled()
rt.logger = logger.StandardLogger{}
+ rt.response = newResponse
if len(schemes) > 0 {
rt.schemes = schemes
@@ -301,6 +301,14 @@ func (r *Runtime) WithOpenTracing(opts ...opentracing.StartSpanOption) runtime.C
return newOpenTracingTransport(r, r.Host, opts)
}
+// WithOpenTelemetry adds opentelemetry support to the provided runtime.
+// A new client span is created for each request.
+// If the context of the client operation does not contain an active span, no span is created.
+// The provided opts are applied to each spans - for example to add global tags.
+func (r *Runtime) WithOpenTelemetry(opts ...OpenTelemetryOpt) runtime.ClientTransport {
+ return newOpenTelemetryTransport(r, r.Host, opts)
+}
+
func (r *Runtime) pickScheme(schemes []string) string {
if v := r.selectScheme(r.schemes); v != "" {
return v
@@ -329,6 +337,7 @@ func (r *Runtime) selectScheme(schemes []string) string {
}
return scheme
}
+
func transportOrDefault(left, right http.RoundTripper) http.RoundTripper {
if left == nil {
return right
@@ -358,20 +367,19 @@ func (r *Runtime) EnableConnectionReuse() {
)
}
-// Submit a request and when there is a body on success it will turn that into the result
-// all other things are turned into an api error for swagger which retains the status code
-func (r *Runtime) Submit(operation *runtime.ClientOperation) (interface{}, error) {
- params, readResponse, auth := operation.Params, operation.Reader, operation.AuthInfo
+// takes a client operation and creates equivalent http.Request
+func (r *Runtime) createHttpRequest(operation *runtime.ClientOperation) (*request, *http.Request, error) {
+ params, _, auth := operation.Params, operation.Reader, operation.AuthInfo
request, err := newRequest(operation.Method, operation.PathPattern, params)
if err != nil {
- return nil, err
+ return nil, nil, err
}
var accept []string
accept = append(accept, operation.ProducesMediaTypes...)
if err = request.SetHeaderParam(runtime.HeaderAccept, accept...); err != nil {
- return nil, err
+ return nil, nil, err
}
if auth == nil && r.DefaultAuthentication != nil {
@@ -382,7 +390,7 @@ func (r *Runtime) Submit(operation *runtime.ClientOperation) (interface{}, error
return r.DefaultAuthentication.AuthenticateRequest(req, reg)
})
}
- //if auth != nil {
+ // if auth != nil {
// if err := auth.AuthenticateRequest(request, r.Formats); err != nil {
// return nil, err
// }
@@ -399,16 +407,33 @@ func (r *Runtime) Submit(operation *runtime.ClientOperation) (interface{}, error
}
if _, ok := r.Producers[cmt]; !ok && cmt != runtime.MultipartFormMime && cmt != runtime.URLencodedFormMime {
- return nil, fmt.Errorf("none of producers: %v registered. try %s", r.Producers, cmt)
+ return nil, nil, fmt.Errorf("none of producers: %v registered. try %s", r.Producers, cmt)
}
req, err := request.buildHTTP(cmt, r.BasePath, r.Producers, r.Formats, auth)
if err != nil {
- return nil, err
+ return nil, nil, err
}
req.URL.Scheme = r.pickScheme(operation.Schemes)
req.URL.Host = r.Host
req.Host = r.Host
+ return request, req, nil
+}
+
+func (r *Runtime) CreateHttpRequest(operation *runtime.ClientOperation) (req *http.Request, err error) {
+ _, req, err = r.createHttpRequest(operation)
+ return
+}
+
+// Submit a request and when there is a body on success it will turn that into the result
+// all other things are turned into an api error for swagger which retains the status code
+func (r *Runtime) Submit(operation *runtime.ClientOperation) (interface{}, error) {
+ _, readResponse, _ := operation.Params, operation.Reader, operation.AuthInfo
+
+ request, req, err := r.createHttpRequest(operation)
+ if err != nil {
+ return nil, err
+ }
r.clientOnce.Do(func() {
r.client = &http.Client{
@@ -484,7 +509,7 @@ func (r *Runtime) Submit(operation *runtime.ClientOperation) (interface{}, error
return nil, fmt.Errorf("no consumer: %q", ct)
}
}
- return readResponse.ReadResponse(response{res}, cons)
+ return readResponse.ReadResponse(r.response(res), cons)
}
// SetDebug changes the debug flag.
@@ -500,3 +525,13 @@ func (r *Runtime) SetLogger(logger logger.Logger) {
r.logger = logger
middleware.Logger = logger
}
+
+type ClientResponseFunc = func(*http.Response) runtime.ClientResponse
+
+// SetResponseReader changes the response reader implementation.
+func (r *Runtime) SetResponseReader(f ClientResponseFunc) {
+ if f == nil {
+ return
+ }
+ r.response = f
+}
diff --git a/client/runtime_test.go b/client/runtime_test.go
index 0b05186..3c4c7c5 100644
--- a/client/runtime_test.go
+++ b/client/runtime_test.go
@@ -19,7 +19,7 @@ import (
"encoding/json"
"encoding/xml"
"errors"
- "io/ioutil"
+ "io"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
@@ -70,7 +70,7 @@ func TestRuntime_TLSAuthConfig(t *testing.T) {
}
func TestRuntime_TLSAuthConfigWithRSAKey(t *testing.T) {
- keyPem, err := ioutil.ReadFile("../fixtures/certs/myclient.key")
+ keyPem, err := os.ReadFile("../fixtures/certs/myclient.key")
require.NoError(t, err)
keyDer, _ := pem.Decode(keyPem)
@@ -79,7 +79,7 @@ func TestRuntime_TLSAuthConfigWithRSAKey(t *testing.T) {
key, err := x509.ParsePKCS1PrivateKey(keyDer.Bytes)
require.NoError(t, err)
- certPem, err := ioutil.ReadFile("../fixtures/certs/myclient.crt")
+ certPem, err := os.ReadFile("../fixtures/certs/myclient.crt")
require.NoError(t, err)
certDer, _ := pem.Decode(certPem)
@@ -101,7 +101,7 @@ func TestRuntime_TLSAuthConfigWithRSAKey(t *testing.T) {
}
func TestRuntime_TLSAuthConfigWithECKey(t *testing.T) {
- keyPem, err := ioutil.ReadFile("../fixtures/certs/myclient-ecc.key")
+ keyPem, err := os.ReadFile("../fixtures/certs/myclient-ecc.key")
require.NoError(t, err)
_, remainder := pem.Decode(keyPem)
@@ -111,7 +111,7 @@ func TestRuntime_TLSAuthConfigWithECKey(t *testing.T) {
key, err := x509.ParseECPrivateKey(keyDer.Bytes)
require.NoError(t, err)
- certPem, err := ioutil.ReadFile("../fixtures/certs/myclient-ecc.crt")
+ certPem, err := os.ReadFile("../fixtures/certs/myclient-ecc.crt")
require.NoError(t, err)
certDer, _ := pem.Decode(certPem)
@@ -133,7 +133,7 @@ func TestRuntime_TLSAuthConfigWithECKey(t *testing.T) {
}
func TestRuntime_TLSAuthConfigWithLoadedCA(t *testing.T) {
- certPem, err := ioutil.ReadFile("../fixtures/certs/myCA.crt")
+ certPem, err := os.ReadFile("../fixtures/certs/myCA.crt")
require.NoError(t, err)
block, _ := pem.Decode(certPem)
@@ -154,7 +154,7 @@ func TestRuntime_TLSAuthConfigWithLoadedCA(t *testing.T) {
}
func TestRuntime_TLSAuthConfigWithLoadedCAPool(t *testing.T) {
- certPem, err := ioutil.ReadFile("../fixtures/certs/myCA.crt")
+ certPem, err := os.ReadFile("../fixtures/certs/myCA.crt")
require.NoError(t, err)
block, _ := pem.Decode(certPem)
@@ -176,13 +176,13 @@ func TestRuntime_TLSAuthConfigWithLoadedCAPool(t *testing.T) {
// Using require.Len prints the (very large and incomprehensible)
// Subjects list on failure, so instead use require.Equal.
- require.Equal(t, 1, len(cfg.RootCAs.Subjects()))
+ require.Equal(t, 1, len(cfg.RootCAs.Subjects())) // nolint:staticcheck
}
}
}
func TestRuntime_TLSAuthConfigWithLoadedCAPoolAndLoadedCA(t *testing.T) {
- certPem, err := ioutil.ReadFile("../fixtures/certs/myCA.crt")
+ certPem, err := os.ReadFile("../fixtures/certs/myCA.crt")
require.NoError(t, err)
block, _ := pem.Decode(certPem)
@@ -199,7 +199,7 @@ func TestRuntime_TLSAuthConfigWithLoadedCAPoolAndLoadedCA(t *testing.T) {
pool, err = x509.SystemCertPool()
require.NoError(t, err)
}
- startingCertCount := len(pool.Subjects())
+ startingCertCount := len(pool.Subjects()) // nolint:staticcheck
var opts TLSClientOptions
opts.LoadedCAPool = pool
@@ -212,7 +212,7 @@ func TestRuntime_TLSAuthConfigWithLoadedCAPoolAndLoadedCA(t *testing.T) {
// Using require.Len prints the (very large and incomprehensible)
// Subjects list on failure, so instead use require.Equal.
- require.Equal(t, startingCertCount+1, len(cfg.RootCAs.Subjects()))
+ require.Equal(t, startingCertCount+1, len(cfg.RootCAs.Subjects())) // nolint:staticcheck
}
}
}
@@ -251,7 +251,7 @@ func TestRuntime_ManualCertificateValidation(t *testing.T) {
// root cert
rootCertFile := "../fixtures/certs/myCA.crt"
- rootCertPem, err := ioutil.ReadFile(rootCertFile)
+ rootCertPem, err := os.ReadFile(rootCertFile)
require.NoError(t, err)
rootCertRaw, _ := pem.Decode(rootCertPem)
require.NotNil(t, rootCertRaw)
@@ -623,7 +623,7 @@ func TestRuntime_CustomTransport(t *testing.T) {
buf := bytes.NewBuffer(nil)
enc := json.NewEncoder(buf)
_ = enc.Encode(result)
- resp.Body = ioutil.NopCloser(buf)
+ resp.Body = io.NopCloser(buf)
return &resp, nil
})
@@ -969,7 +969,7 @@ func (o *overrideRoundTripper) RoundTrip(req *http.Request) (*http.Response, err
o.overriden = true
res := new(http.Response)
res.StatusCode = 200
- res.Body = ioutil.NopCloser(bytes.NewBufferString("OK"))
+ res.Body = io.NopCloser(bytes.NewBufferString("OK"))
return res, nil
}
diff --git a/client_request.go b/client_request.go
index 3efda34..d4d2b58 100644
--- a/client_request.go
+++ b/client_request.go
@@ -16,7 +16,6 @@ package runtime
import (
"io"
- "io/ioutil"
"net/http"
"net/url"
"time"
@@ -79,7 +78,7 @@ type NamedReadCloser interface {
func NamedReader(name string, rdr io.Reader) NamedReadCloser {
rc, ok := rdr.(io.ReadCloser)
if !ok {
- rc = ioutil.NopCloser(rdr)
+ rc = io.NopCloser(rdr)
}
return &namedReadCloser{
name: name,
diff --git a/client_response.go b/client_response.go
index 0b7e382..0d16911 100644
--- a/client_response.go
+++ b/client_response.go
@@ -15,10 +15,9 @@
package runtime
import (
+ "encoding/json"
"fmt"
"io"
-
- "encoding/json"
)
// A ClientResponse represents a client response
@@ -61,13 +60,18 @@ type APIError struct {
Code int
}
-func (a *APIError) Error() string {
- resp, _ := json.Marshal(a.Response)
- return fmt.Sprintf("%s (status %d): %s", a.OperationName, a.Code, resp)
+func (o *APIError) Error() string {
+ var resp []byte
+ if err, ok := o.Response.(error); ok {
+ resp = []byte("'" + err.Error() + "'")
+ } else {
+ resp, _ = json.Marshal(o.Response)
+ }
+ return fmt.Sprintf("%s (status %d): %s", o.OperationName, o.Code, resp)
}
-func (a *APIError) String() string {
- return a.Error()
+func (o *APIError) String() string {
+ return o.Error()
}
// IsSuccess returns true when this elapse o k response returns a 2xx status code
diff --git a/client_response_test.go b/client_response_test.go
index ee13d9d..75a6758 100644
--- a/client_response_test.go
+++ b/client_response_test.go
@@ -16,8 +16,10 @@ package runtime
import (
"bytes"
+ "errors"
"io"
- "io/ioutil"
+ "io/fs"
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -39,7 +41,7 @@ func (r response) GetHeaders(_ string) []string {
return []string{"the headers", "the headers2"}
}
func (r response) Body() io.ReadCloser {
- return ioutil.NopCloser(bytes.NewBufferString("the content"))
+ return io.NopCloser(bytes.NewBufferString("the content"))
}
func TestResponseReaderFunc(t *testing.T) {
@@ -48,7 +50,7 @@ func TestResponseReaderFunc(t *testing.T) {
Code int
}
reader := ClientResponseReaderFunc(func(r ClientResponse, _ Consumer) (interface{}, error) {
- b, _ := ioutil.ReadAll(r.Body())
+ b, _ := io.ReadAll(r.Body())
actual.Body = string(b)
actual.Code = r.Code()
actual.Message = r.Message()
@@ -61,3 +63,27 @@ func TestResponseReaderFunc(t *testing.T) {
assert.Equal(t, "the header", actual.Header)
assert.Equal(t, 490, actual.Code)
}
+
+func TestResponseReaderFuncError(t *testing.T) {
+ reader := ClientResponseReaderFunc(func(r ClientResponse, _ Consumer) (interface{}, error) {
+ _, _ = io.ReadAll(r.Body())
+ return nil, NewAPIError("fake", errors.New("writer closed"), 490)
+ })
+ _, err := reader.ReadResponse(response{}, nil)
+ assert.NotNil(t, err)
+ assert.True(t, strings.Contains(err.Error(), "writer closed"), err.Error())
+
+ reader = func(r ClientResponse, _ Consumer) (interface{}, error) {
+ _, _ = io.ReadAll(r.Body())
+ err := &fs.PathError{
+ Op: "write",
+ Path: "path/to/fake",
+ Err: fs.ErrClosed,
+ }
+ return nil, NewAPIError("fake", err, 200)
+ }
+ _, err = reader.ReadResponse(response{}, nil)
+ assert.NotNil(t, err)
+ assert.True(t, strings.Contains(err.Error(), "file already closed"), err.Error())
+
+}
diff --git a/debian/changelog b/debian/changelog
index b9b9097..0a198c6 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-go-openapi-runtime (0.25.0-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Fri, 07 Apr 2023 05:21:59 -0000
+
golang-github-go-openapi-runtime (0.23.3-1) unstable; urgency=medium
* Team upload.
diff --git a/go.mod b/go.mod
index e5db4c1..a94c638 100644
--- a/go.mod
+++ b/go.mod
@@ -1,7 +1,6 @@
module github.com/go-openapi/runtime
require (
- github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/docker/go-units v0.4.0
github.com/go-openapi/analysis v0.21.2
github.com/go-openapi/errors v0.20.2
@@ -10,14 +9,34 @@ require (
github.com/go-openapi/strfmt v0.21.2
github.com/go-openapi/swag v0.21.1
github.com/go-openapi/validate v0.21.0
+ github.com/opentracing/opentracing-go v1.2.0
+ github.com/stretchr/testify v1.8.0
+ go.opentelemetry.io/otel v1.11.1
+ go.opentelemetry.io/otel/sdk v1.11.1
+ go.opentelemetry.io/otel/trace v1.11.1
+ gopkg.in/yaml.v2 v2.4.0
+)
+
+require (
+ github.com/PuerkitoBio/purell v1.1.1 // indirect
+ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
+ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/go-logr/logr v1.2.3 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-openapi/jsonpointer v0.19.5 // indirect
+ github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
- github.com/opentracing/opentracing-go v1.2.0
- github.com/stretchr/testify v1.7.0
+ github.com/oklog/ulid v1.3.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
go.mongodb.org/mongo-driver v1.8.3 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
- gopkg.in/yaml.v2 v2.4.0
+ golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
+ golang.org/x/text v0.3.7 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
)
-go 1.15
+go 1.18
diff --git a/go.sum b/go.sum
index c821eb7..2e043cb 100644
--- a/go.sum
+++ b/go.sum
@@ -12,6 +12,11 @@ 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/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+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/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU=
github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
@@ -65,8 +70,8 @@ github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGt
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@@ -117,11 +122,14 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@@ -132,6 +140,12 @@ go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R7
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
go.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4=
go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
+go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
+go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
+go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs=
+go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys=
+go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
+go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
@@ -156,11 +170,10 @@ golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
+golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -171,7 +184,6 @@ golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -185,5 +197,6 @@ 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.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=
diff --git a/middleware/context.go b/middleware/context.go
index 250e35f..d21ae4e 100644
--- a/middleware/context.go
+++ b/middleware/context.go
@@ -195,6 +195,17 @@ func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Ro
if spec != nil {
an = analysis.New(spec.Spec())
}
+
+ return NewRoutableContextWithAnalyzedSpec(spec, an, routableAPI, routes)
+}
+
+// NewRoutableContextWithAnalyzedSpec is like NewRoutableContext but takes in input the analysed spec too
+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
}
@@ -498,7 +509,9 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
if resp, ok := data.(Responder); ok {
producers := route.Producers
- prod, ok := producers[format]
+ // producers contains keys with normalized format, if a format has MIME type parameter such as `text/plain; charset=utf-8`
+ // then you must provide `text/plain` to get the correct producer. HOWEVER, format here is not normalized.
+ prod, ok := producers[normalizeOffer(format)]
if !ok {
prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
pr, ok := prods[c.api.DefaultProduces()]
diff --git a/middleware/denco/server_test.go b/middleware/denco/server_test.go
index 4ef43e4..a256284 100644
--- a/middleware/denco/server_test.go
+++ b/middleware/denco/server_test.go
@@ -2,7 +2,7 @@ package denco_test
import (
"fmt"
- "io/ioutil"
+ "io"
"net/http"
"net/http/httptest"
"testing"
@@ -59,7 +59,7 @@ func TestMux(t *testing.T) {
continue
}
defer res.Body.Close()
- body, err := ioutil.ReadAll(res.Body)
+ body, err := io.ReadAll(res.Body)
if err != nil {
t.Error(err)
continue
@@ -94,7 +94,7 @@ func TestNotFound(t *testing.T) {
t.Fatal(err)
}
defer res.Body.Close()
- body, err := ioutil.ReadAll(res.Body)
+ body, err := io.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
diff --git a/middleware/parameter.go b/middleware/parameter.go
index 8fa0cf4..9aaf659 100644
--- a/middleware/parameter.go
+++ b/middleware/parameter.go
@@ -206,7 +206,11 @@ func (p *untypedParamBinder) Bind(request *http.Request, routeParams RouteParams
if p.parameter.Type == "file" {
file, header, ffErr := request.FormFile(p.parameter.Name)
if ffErr != nil {
- return errors.NewParseError(p.Name, p.parameter.In, "", ffErr)
+ if p.parameter.Required {
+ return errors.NewParseError(p.Name, p.parameter.In, "", ffErr)
+ } else {
+ return nil
+ }
}
target.Set(reflect.ValueOf(runtime.File{Data: file, Header: header}))
return nil
diff --git a/middleware/request_test.go b/middleware/request_test.go
index 0c424a8..65ef69a 100644
--- a/middleware/request_test.go
+++ b/middleware/request_test.go
@@ -17,7 +17,6 @@ package middleware
import (
"bytes"
"io"
- "io/ioutil"
"mime/multipart"
"net/http"
"net/url"
@@ -406,7 +405,7 @@ type fileRequest struct {
func paramsForFileUpload() *UntypedRequestBinder {
nameParam := spec.FormDataParam("name").Typed("string", "")
- fileParam := spec.FileParam("file")
+ fileParam := spec.FileParam("file").AsRequired()
params := map[string]spec.Parameter{"Name": *nameParam, "File": *fileParam}
return NewUntypedRequestBinder(params, new(spec.Swagger), strfmt.Default)
@@ -435,7 +434,7 @@ func TestBindingFileUpload(t *testing.T) {
assert.NotNil(t, data.File.Header)
assert.Equal(t, "plain-jane.txt", data.File.Header.Filename)
- bb, err := ioutil.ReadAll(data.File.Data)
+ bb, err := io.ReadAll(data.File.Data)
assert.NoError(t, err)
assert.Equal(t, []byte("the file contents"), bb)
@@ -469,4 +468,63 @@ func TestBindingFileUpload(t *testing.T) {
data = fileRequest{}
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+
+ writer = multipart.NewWriter(body)
+ _ = writer.WriteField("name", "the-name")
+ assert.NoError(t, writer.Close())
+
+ req, _ = http.NewRequest("POST", urlStr, body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+
+ data = fileRequest{}
+ assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+}
+
+func paramsForOptionalFileUpload() *UntypedRequestBinder {
+ nameParam := spec.FormDataParam("name").Typed("string", "")
+ fileParam := spec.FileParam("file").AsOptional()
+
+ params := map[string]spec.Parameter{"Name": *nameParam, "File": *fileParam}
+ return NewUntypedRequestBinder(params, new(spec.Swagger), strfmt.Default)
+}
+
+func TestBindingOptionalFileUpload(t *testing.T) {
+ binder := paramsForOptionalFileUpload()
+
+ body := bytes.NewBuffer(nil)
+ writer := multipart.NewWriter(body)
+ _ = writer.WriteField("name", "the-name")
+ assert.NoError(t, writer.Close())
+
+ urlStr := "http://localhost:8002/hello"
+ req, _ := http.NewRequest("POST", urlStr, body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+
+ data := fileRequest{}
+ assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+ assert.Equal(t, "the-name", data.Name)
+ assert.Nil(t, data.File.Data)
+ assert.Nil(t, data.File.Header)
+
+ writer = multipart.NewWriter(body)
+ part, err := writer.CreateFormFile("file", "plain-jane.txt")
+ assert.NoError(t, err)
+ _, _ = part.Write([]byte("the file contents"))
+ _ = writer.WriteField("name", "the-name")
+ assert.NoError(t, writer.Close())
+
+ req, _ = http.NewRequest("POST", urlStr, body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+ assert.NoError(t, writer.Close())
+
+ data = fileRequest{}
+ assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+ assert.Equal(t, "the-name", data.Name)
+ assert.NotNil(t, data.File)
+ assert.NotNil(t, data.File.Header)
+ assert.Equal(t, "plain-jane.txt", data.File.Header.Filename)
+
+ bb, err := io.ReadAll(data.File.Data)
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("the file contents"), bb)
}
diff --git a/middleware/swaggerui.go b/middleware/swaggerui.go
index 2c92f5c..b4dea29 100644
--- a/middleware/swaggerui.go
+++ b/middleware/swaggerui.go
@@ -16,6 +16,8 @@ type SwaggerUIOpts struct {
Path string
// SpecURL the url to find the spec for
SpecURL string
+ // OAuthCallbackURL the url called after OAuth2 login
+ OAuthCallbackURL string
// The three components needed to embed swagger-ui
SwaggerURL string
@@ -40,6 +42,9 @@ func (r *SwaggerUIOpts) EnsureDefaults() {
if r.SpecURL == "" {
r.SpecURL = "/swagger.json"
}
+ if r.OAuthCallbackURL == "" {
+ r.OAuthCallbackURL = path.Join(r.BasePath, r.Path, "oauth2-callback")
+ }
if r.SwaggerURL == "" {
r.SwaggerURL = swaggerLatest
}
@@ -73,7 +78,7 @@ func SwaggerUI(opts SwaggerUIOpts, next http.Handler) http.Handler {
b := buf.Bytes()
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
- if r.URL.Path == pth {
+ if path.Join(r.URL.Path) == pth {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
rw.WriteHeader(http.StatusOK)
@@ -149,7 +154,8 @@ const (
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
- layout: "StandaloneLayout"
+ layout: "StandaloneLayout",
+ oauth2RedirectUrl: '{{ .OAuthCallbackURL }}'
})
// End Swagger UI call region
diff --git a/middleware/swaggerui_oauth2.go b/middleware/swaggerui_oauth2.go
new file mode 100644
index 0000000..576f600
--- /dev/null
+++ b/middleware/swaggerui_oauth2.go
@@ -0,0 +1,122 @@
+package middleware
+
+import (
+ "bytes"
+ "fmt"
+ "net/http"
+ "path"
+ "text/template"
+)
+
+func SwaggerUIOAuth2Callback(opts SwaggerUIOpts, next http.Handler) http.Handler {
+ opts.EnsureDefaults()
+
+ pth := opts.OAuthCallbackURL
+ tmpl := template.Must(template.New("swaggeroauth").Parse(swaggerOAuthTemplate))
+
+ 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)
+ })
+}
+
+const (
+ swaggerOAuthTemplate = `
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>{{ .Title }}</title>
+</head>
+<body>
+<script>
+ 'use strict';
+ function run () {
+ var oauth2 = window.opener.swaggerUIRedirectOauth2;
+ var sentState = oauth2.state;
+ var redirectUrl = oauth2.redirectUrl;
+ var isValid, qp, arr;
+
+ if (/code|token|error/.test(window.location.hash)) {
+ qp = window.location.hash.substring(1).replace('?', '&');
+ } else {
+ qp = location.search.substring(1);
+ }
+
+ arr = qp.split("&");
+ arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
+ qp = qp ? JSON.parse('{' + arr.join() + '}',
+ function (key, value) {
+ return key === "" ? value : decodeURIComponent(value);
+ }
+ ) : {};
+
+ isValid = qp.state === sentState;
+
+ if ((
+ oauth2.auth.schema.get("flow") === "accessCode" ||
+ oauth2.auth.schema.get("flow") === "authorizationCode" ||
+ oauth2.auth.schema.get("flow") === "authorization_code"
+ ) && !oauth2.auth.code) {
+ if (!isValid) {
+ oauth2.errCb({
+ authId: oauth2.auth.name,
+ source: "auth",
+ level: "warning",
+ message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
+ });
+ }
+
+ if (qp.code) {
+ delete oauth2.state;
+ oauth2.auth.code = qp.code;
+ oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
+ } else {
+ let oauthErrorMsg;
+ if (qp.error) {
+ oauthErrorMsg = "["+qp.error+"]: " +
+ (qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
+ (qp.error_uri ? "More info: "+qp.error_uri : "");
+ }
+
+ oauth2.errCb({
+ authId: oauth2.auth.name,
+ source: "auth",
+ level: "error",
+ message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
+ });
+ }
+ } else {
+ oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
+ }
+ window.close();
+ }
+
+ if (document.readyState !== 'loading') {
+ run();
+ } else {
+ document.addEventListener('DOMContentLoaded', function () {
+ run();
+ });
+ }
+</script>
+</body>
+</html>
+`
+)
diff --git a/middleware/swaggerui_oauth2_test.go b/middleware/swaggerui_oauth2_test.go
new file mode 100644
index 0000000..eab1639
--- /dev/null
+++ b/middleware/swaggerui_oauth2_test.go
@@ -0,0 +1,20 @@
+package middleware
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSwaggerUIOAuth2CallbackMiddleware(t *testing.T) {
+ redoc := SwaggerUIOAuth2Callback(SwaggerUIOpts{}, nil)
+
+ req, _ := http.NewRequest("GET", "/docs/oauth2-callback", nil)
+ recorder := httptest.NewRecorder()
+ redoc.ServeHTTP(recorder, req)
+ assert.Equal(t, 200, recorder.Code)
+ assert.Equal(t, "text/html; charset=utf-8", recorder.Header().Get("Content-Type"))
+ assert.Contains(t, recorder.Body.String(), "<title>API documentation</title>")
+}
diff --git a/middleware/untyped_request_test.go b/middleware/untyped_request_test.go
index e44249d..f156f35 100644
--- a/middleware/untyped_request_test.go
+++ b/middleware/untyped_request_test.go
@@ -18,7 +18,7 @@ import (
"bytes"
"encoding/base64"
"encoding/json"
- "io/ioutil"
+ "io"
"mime/multipart"
"net/http"
"net/url"
@@ -77,7 +77,7 @@ func TestUntypedFileUpload(t *testing.T) {
assert.NotNil(t, file.Header)
assert.Equal(t, "plain-jane.txt", file.Header.Filename)
- bb, err := ioutil.ReadAll(file.Data)
+ bb, err := io.ReadAll(file.Data)
assert.NoError(t, err)
assert.Equal(t, []byte("the file contents"), bb)
@@ -111,6 +111,54 @@ func TestUntypedFileUpload(t *testing.T) {
data = make(map[string]interface{})
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+
+ writer = multipart.NewWriter(body)
+ _ = writer.WriteField("name", "the-name")
+ assert.NoError(t, writer.Close())
+
+ req, _ = http.NewRequest("POST", urlStr, body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+
+ data = make(map[string]interface{})
+ assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+}
+
+func TestUntypedOptionalFileUpload(t *testing.T) {
+ binder := paramsForOptionalFileUpload()
+
+ body := bytes.NewBuffer(nil)
+ writer := multipart.NewWriter(body)
+ _ = writer.WriteField("name", "the-name")
+ assert.NoError(t, writer.Close())
+
+ urlStr := "http://localhost:8002/hello"
+ req, _ := http.NewRequest("POST", urlStr, body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+
+ data := make(map[string]interface{})
+ assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+ assert.Equal(t, "the-name", data["name"])
+
+ writer = multipart.NewWriter(body)
+ part, err := writer.CreateFormFile("file", "plain-jane.txt")
+ assert.NoError(t, err)
+ _, _ = part.Write([]byte("the file contents"))
+ _ = writer.WriteField("name", "the-name")
+ assert.NoError(t, writer.Close())
+
+ req, _ = http.NewRequest("POST", urlStr, body)
+ req.Header.Set("Content-Type", writer.FormDataContentType())
+ assert.NoError(t, writer.Close())
+
+ data = make(map[string]interface{})
+ assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
+ assert.Equal(t, "the-name", data["name"])
+ assert.NotNil(t, data["file"])
+ assert.IsType(t, runtime.File{}, data["file"])
+ file := data["file"].(runtime.File)
+ assert.NotNil(t, file.Header)
+ assert.Equal(t, "plain-jane.txt", file.Header.Filename)
+
}
func TestUntypedBindingTypesForValid(t *testing.T) {
diff --git a/request_test.go b/request_test.go
index 3850071..7dec2ae 100644
--- a/request_test.go
+++ b/request_test.go
@@ -18,7 +18,6 @@ import (
"bufio"
"bytes"
"io"
- "io/ioutil"
"net/http"
"net/url"
"strings"
@@ -89,7 +88,7 @@ func TestPeekingReader(t *testing.T) {
// just passes to original reader when nothing called
exp1 := []byte("original")
pr1 := newPeekingReader(closeReader(bytes.NewReader(exp1)))
- b1, err := ioutil.ReadAll(pr1)
+ b1, err := io.ReadAll(pr1)
if assert.NoError(t, err) {
assert.Equal(t, exp1, b1)
}
@@ -100,7 +99,7 @@ func TestPeekingReader(t *testing.T) {
peeked, err := pr2.underlying.Peek(1)
require.NoError(t, err)
require.Equal(t, "a", string(peeked))
- b2, err := ioutil.ReadAll(pr2)
+ b2, err := io.ReadAll(pr2)
if assert.NoError(t, err) {
assert.Equal(t, string(exp2), string(b2))
}
@@ -132,7 +131,7 @@ func TestPeekingReader(t *testing.T) {
require.Equal(t, 1, cbr.peeks)
require.Equal(t, 0, cbr.reads)
- b, err := ioutil.ReadAll(pr)
+ b, err := io.ReadAll(pr)
require.NoError(t, err)
require.Equal(t, "hello", string(b))
require.Equal(t, 2, cbr.buffereds)