Repository: getkin/kin-openapi
Pull request #633 (2022-10-13 09:23:01)

Introduce `(openapi3.*Server).BasePath()` and `(openapi3.Servers).BasePath()`


Patch:
diff --git a/openapi3/server.go b/openapi3/server.go
index 88fdcc0..3a1a7ce 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)


Test patch:
diff --git a/openapi3/server_test.go b/openapi3/server_test.go
index 0ace034..c59b86e 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)
+		})
+	}
+}