From e887ba8d7526c0b841670f0136ffc2f07d2fbabe Mon Sep 17 00:00:00 2001 From: Pierre Fenoll Date: Thu, 13 Oct 2022 11:59:21 +0200 Subject: [PATCH] Introduce `(openapi3.*Server).BasePath()` and `(openapi3.Servers).BasePath()` (#633) --- openapi3/server.go | 32 ++++++++++++++++ openapi3/server_test.go | 85 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/openapi3/server.go b/openapi3/server.go index 88fdcc0f3..3a1a7cef9 100644 --- a/openapi3/server.go +++ b/openapi3/server.go @@ -25,6 +25,14 @@ func (servers Servers) Validate(ctx context.Context) error { return nil } +// BasePath returns the base path of the first server in the list, or /. +func (servers Servers) BasePath() (string, error) { + for _, server := range servers { + return server.BasePath() + } + return "/", nil +} + func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string) { rawURL := parsedURL.String() if i := strings.IndexByte(rawURL, '?'); i >= 0 { @@ -49,6 +57,30 @@ type Server struct { Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"` } +// BasePath returns the base path extracted from the default values of variables, if any. +// Assumes a valid struct (per Validate()). +func (server *Server) BasePath() (string, error) { + if server == nil { + return "/", nil + } + + uri := server.URL + for name, svar := range server.Variables { + uri = strings.ReplaceAll(uri, "{"+name+"}", svar.Default) + } + + u, err := url.ParseRequestURI(uri) + if err != nil { + return "", err + } + + if bp := u.Path; bp != "" { + return bp, nil + } + + return "/", nil +} + // MarshalJSON returns the JSON encoding of Server. func (server *Server) MarshalJSON() ([]byte, error) { return jsoninfo.MarshalStrictStruct(server) diff --git a/openapi3/server_test.go b/openapi3/server_test.go index 0ace0345f..c59b86e56 100644 --- a/openapi3/server_test.go +++ b/openapi3/server_test.go @@ -116,3 +116,88 @@ func newServerMatch(remaining string, args ...string) *serverMatch { Args: args, } } + +func TestServersBasePath(t *testing.T) { + for _, testcase := range []struct { + title string + servers Servers + expected string + }{ + { + title: "empty servers", + servers: nil, + expected: "/", + }, + { + title: "URL set, missing trailing slash", + servers: Servers{&Server{URL: "https://example.com"}}, + expected: "/", + }, + { + title: "URL set, with trailing slash", + servers: Servers{&Server{URL: "https://example.com/"}}, + expected: "/", + }, + { + title: "URL set", + servers: Servers{&Server{URL: "https://example.com/b/l/a"}}, + expected: "/b/l/a", + }, + { + title: "URL set with variables", + servers: Servers{&Server{ + URL: "{scheme}://example.com/b/l/a", + Variables: map[string]*ServerVariable{ + "scheme": { + Enum: []string{"http", "https"}, + Default: "https", + }, + }, + }}, + expected: "/b/l/a", + }, + { + title: "URL set with variables in path", + servers: Servers{&Server{ + URL: "http://example.com/b/{var1}/a", + Variables: map[string]*ServerVariable{ + "var1": { + Default: "lllll", + }, + }, + }}, + expected: "/b/lllll/a", + }, + { + title: "URLs set with variables in path", + servers: Servers{ + &Server{ + URL: "http://example.com/b/{var2}/a", + Variables: map[string]*ServerVariable{ + "var2": { + Default: "LLLLL", + }, + }, + }, + &Server{ + URL: "https://example.com/b/{var1}/a", + Variables: map[string]*ServerVariable{ + "var1": { + Default: "lllll", + }, + }, + }, + }, + expected: "/b/LLLLL/a", + }, + } { + t.Run(testcase.title, func(t *testing.T) { + err := testcase.servers.Validate(context.Background()) + require.NoError(t, err) + + got, err := testcase.servers.BasePath() + require.NoError(t, err) + require.Exactly(t, testcase.expected, got) + }) + } +}