all, transport/http: read default mTLS endpoint from Discovery Doc (#541)

Read default mTLS endpoint from Discovery Doc instead of generating it via regex. This is achieved by introducing a new internaloption "DefaultMTLSEndpoint". The generator is updated to include mtlsBasePath. Testdata is updated to include mtlsRootUrl.
diff --git a/google-api-go-generator/gen.go b/google-api-go-generator/gen.go
index 8d137b5..33aeaf4 100644
--- a/google-api-go-generator/gen.go
+++ b/google-api-go-generator/gen.go
@@ -495,6 +495,13 @@
 	return resolveRelative(base, rel)
 }
 
+func (a *API) mtlsAPIBaseURL() string {
+	if a.doc.MTLSRootURL != "" {
+		return resolveRelative(a.doc.MTLSRootURL, a.doc.ServicePath)
+	}
+	return ""
+}
+
 func (a *API) needsDataWrapper() bool {
 	for _, feature := range a.doc.Features {
 		if feature == "dataWrapper" {
@@ -719,6 +726,9 @@
 	pn("const apiName = %q", a.doc.Name)
 	pn("const apiVersion = %q", a.doc.Version)
 	pn("const basePath = %q", a.apiBaseURL())
+	if mtlsBase := a.mtlsAPIBaseURL(); mtlsBase != "" {
+		pn("const mtlsBasePath = %q", mtlsBase)
+	}
 
 	a.generateScopeConstants()
 	a.PopulateSchemas()
@@ -741,6 +751,9 @@
 		pn("opts = append([]option.ClientOption{scopesOption}, opts...)")
 	}
 	pn("opts = append(opts, internaloption.WithDefaultEndpoint(basePath))")
+	if a.mtlsAPIBaseURL() != "" {
+		pn("opts = append(opts, internaloption.WithDefaultMTLSEndpoint(mtlsBasePath))")
+	}
 	pn("client, endpoint, err := htransport.NewClient(ctx, opts...)")
 	pn("if err != nil { return nil, err }")
 	pn("s, err := New(client)")
diff --git a/google-api-go-generator/internal/disco/disco.go b/google-api-go-generator/internal/disco/disco.go
index 0dee96a..b43d6ed 100644
--- a/google-api-go-generator/internal/disco/disco.go
+++ b/google-api-go-generator/internal/disco/disco.go
@@ -20,6 +20,7 @@
 	Version           string             `json:"version"`
 	Title             string             `json:"title"`
 	RootURL           string             `json:"rootUrl"`
+	MTLSRootURL       string             `json:"mtlsRootUrl"`
 	ServicePath       string             `json:"servicePath"`
 	BasePath          string             `json:"basePath"`
 	DocumentationLink string             `json:"documentationLink"`
diff --git a/google-api-go-generator/testdata/any.json b/google-api-go-generator/testdata/any.json
index 405109a..a577208 100644
--- a/google-api-go-generator/testdata/any.json
+++ b/google-api-go-generator/testdata/any.json
@@ -19,6 +19,7 @@
  "baseUrl": "https://logging.googleapis.com/",
  "basePath": "",
  "rootUrl": "https://logging.googleapis.com/",
+ "mtlsRootUrl": "https://logging.mtls.googleapis.com/",
  "servicePath": "",
  "batchPath": "batch",
  "parameters": {
diff --git a/google-api-go-generator/testdata/any.want b/google-api-go-generator/testdata/any.want
index 34a1ae7..cb3b10c 100644
--- a/google-api-go-generator/testdata/any.want
+++ b/google-api-go-generator/testdata/any.want
@@ -75,6 +75,7 @@
 const apiName = "logging"
 const apiVersion = "v1beta3"
 const basePath = "https://logging.googleapis.com/"
+const mtlsBasePath = "https://logging.mtls.googleapis.com/"
 
 // OAuth2 scopes used by this API.
 const (
@@ -90,6 +91,7 @@
 	// NOTE: prepend, so we don't override user-specified scopes.
 	opts = append([]option.ClientOption{scopesOption}, opts...)
 	opts = append(opts, internaloption.WithDefaultEndpoint(basePath))
+	opts = append(opts, internaloption.WithDefaultMTLSEndpoint(mtlsBasePath))
 	client, endpoint, err := htransport.NewClient(ctx, opts...)
 	if err != nil {
 		return nil, err
diff --git a/google-api-go-generator/testdata/blogger-3.json b/google-api-go-generator/testdata/blogger-3.json
index 53caa83..ca0ae7e 100644
--- a/google-api-go-generator/testdata/blogger-3.json
+++ b/google-api-go-generator/testdata/blogger-3.json
@@ -21,6 +21,7 @@
  "baseUrl": "https://www.googleapis.com/blogger/v3/",
  "basePath": "/blogger/v3/",
  "rootUrl": "https://www.googleapis.com/",
+ "mtlsRootUrl": "https://www.mtls.googleapis.com/",
  "servicePath": "blogger/v3/",
  "batchPath": "batch",
  "parameters": {
diff --git a/google-api-go-generator/testdata/blogger-3.want b/google-api-go-generator/testdata/blogger-3.want
index 7492d2c..3ee1a52 100644
--- a/google-api-go-generator/testdata/blogger-3.want
+++ b/google-api-go-generator/testdata/blogger-3.want
@@ -79,6 +79,7 @@
 const apiName = "blogger"
 const apiVersion = "v3"
 const basePath = "https://www.googleapis.com/blogger/v3/"
+const mtlsBasePath = "https://www.mtls.googleapis.com/blogger/v3/"
 
 // OAuth2 scopes used by this API.
 const (
@@ -98,6 +99,7 @@
 	// NOTE: prepend, so we don't override user-specified scopes.
 	opts = append([]option.ClientOption{scopesOption}, opts...)
 	opts = append(opts, internaloption.WithDefaultEndpoint(basePath))
+	opts = append(opts, internaloption.WithDefaultMTLSEndpoint(mtlsBasePath))
 	client, endpoint, err := htransport.NewClient(ctx, opts...)
 	if err != nil {
 		return nil, err
diff --git a/google-api-go-generator/testdata/floats.json b/google-api-go-generator/testdata/floats.json
index 0973308..7b74fce 100644
--- a/google-api-go-generator/testdata/floats.json
+++ b/google-api-go-generator/testdata/floats.json
@@ -9,6 +9,7 @@
  "baseUrl": "https://appengine.googleapis.com/",
  "basePath": "",
  "rootUrl": "https://appengine.googleapis.com/",
+ "mtlsRootUrl": "https://appengine.mtls.googleapis.com/",
  "servicePath": "",
  "batchPath": "batch",
  "schemas": {  
diff --git a/google-api-go-generator/testdata/floats.want b/google-api-go-generator/testdata/floats.want
index b01d096..8fae5dd 100644
--- a/google-api-go-generator/testdata/floats.want
+++ b/google-api-go-generator/testdata/floats.want
@@ -75,10 +75,12 @@
 const apiName = "X"
 const apiVersion = "v1"
 const basePath = "https://appengine.googleapis.com/"
+const mtlsBasePath = "https://appengine.mtls.googleapis.com/"
 
 // NewService creates a new Service.
 func NewService(ctx context.Context, opts ...option.ClientOption) (*Service, error) {
 	opts = append(opts, internaloption.WithDefaultEndpoint(basePath))
+	opts = append(opts, internaloption.WithDefaultMTLSEndpoint(mtlsBasePath))
 	client, endpoint, err := htransport.NewClient(ctx, opts...)
 	if err != nil {
 		return nil, err
diff --git a/google-api-go-generator/testdata/http-body.json b/google-api-go-generator/testdata/http-body.json
index ce638f6..534cd9d 100644
--- a/google-api-go-generator/testdata/http-body.json
+++ b/google-api-go-generator/testdata/http-body.json
@@ -164,6 +164,7 @@
   },
   "version": "v1beta1",
   "baseUrl": "https://healthcare.googleapis.com/",
+  "mtlsRootUrl": "https://healthcare.mtls.googleapis.com/",
   "kind": "discovery#restDescription",
   "description": "Manage, store, and access healthcare data in Google Cloud Platform.",
   "servicePath": "",
diff --git a/google-api-go-generator/testdata/http-body.want b/google-api-go-generator/testdata/http-body.want
index e32ebcf..a53e58a 100644
--- a/google-api-go-generator/testdata/http-body.want
+++ b/google-api-go-generator/testdata/http-body.want
@@ -75,6 +75,7 @@
 const apiName = "healthcare"
 const apiVersion = "v1beta1"
 const basePath = "https://healthcare.googleapis.com/"
+const mtlsBasePath = "https://healthcare.mtls.googleapis.com/"
 
 // OAuth2 scopes used by this API.
 const (
@@ -90,6 +91,7 @@
 	// NOTE: prepend, so we don't override user-specified scopes.
 	opts = append([]option.ClientOption{scopesOption}, opts...)
 	opts = append(opts, internaloption.WithDefaultEndpoint(basePath))
+	opts = append(opts, internaloption.WithDefaultMTLSEndpoint(mtlsBasePath))
 	client, endpoint, err := htransport.NewClient(ctx, opts...)
 	if err != nil {
 		return nil, err
diff --git a/google-api-go-generator/testdata/json-body.json b/google-api-go-generator/testdata/json-body.json
index 3ec0391..a78e9b6 100644
--- a/google-api-go-generator/testdata/json-body.json
+++ b/google-api-go-generator/testdata/json-body.json
@@ -10,6 +10,7 @@
   },
   "basePath": "",
   "baseUrl": "https://ml.googleapis.com/",
+  "mtlsRootUrl": "https://ml.mtls.googleapis.com/",
   "batchPath": "batch",
   "canonicalName": "Cloud Machine Learning Engine",
   "description": "An API to enable creating and using machine learning models.",
diff --git a/google-api-go-generator/testdata/json-body.want b/google-api-go-generator/testdata/json-body.want
index 270814d..0a4810c 100644
--- a/google-api-go-generator/testdata/json-body.want
+++ b/google-api-go-generator/testdata/json-body.want
@@ -75,6 +75,7 @@
 const apiName = "ml"
 const apiVersion = "v1"
 const basePath = "https://ml.googleapis.com/"
+const mtlsBasePath = "https://ml.mtls.googleapis.com/"
 
 // OAuth2 scopes used by this API.
 const (
@@ -90,6 +91,7 @@
 	// NOTE: prepend, so we don't override user-specified scopes.
 	opts = append([]option.ClientOption{scopesOption}, opts...)
 	opts = append(opts, internaloption.WithDefaultEndpoint(basePath))
+	opts = append(opts, internaloption.WithDefaultMTLSEndpoint(mtlsBasePath))
 	client, endpoint, err := htransport.NewClient(ctx, opts...)
 	if err != nil {
 		return nil, err
diff --git a/google-api-go-generator/testdata/quotednum.json b/google-api-go-generator/testdata/quotednum.json
index 738555e..c49b0cd 100644
--- a/google-api-go-generator/testdata/quotednum.json
+++ b/google-api-go-generator/testdata/quotednum.json
@@ -18,6 +18,7 @@
  "baseUrl": "https://www.googleapis.com/adexchangebuyer/v1.1/",
  "basePath": "/adexchangebuyer/v1.1/",
  "rootUrl": "https://www.googleapis.com/",
+ "mtlsRootUrl": "https://www.mtls.googleapis.com/",
  "servicePath": "adexchangebuyer/v1.1/",
  "batchPath": "batch",
  "parameters": {
diff --git a/google-api-go-generator/testdata/quotednum.want b/google-api-go-generator/testdata/quotednum.want
index 54ac67b..56c9fc7 100644
--- a/google-api-go-generator/testdata/quotednum.want
+++ b/google-api-go-generator/testdata/quotednum.want
@@ -75,6 +75,7 @@
 const apiName = "adexchangebuyer"
 const apiVersion = "v1.1"
 const basePath = "https://www.googleapis.com/adexchangebuyer/v1.1/"
+const mtlsBasePath = "https://www.mtls.googleapis.com/adexchangebuyer/v1.1/"
 
 // OAuth2 scopes used by this API.
 const (
@@ -90,6 +91,7 @@
 	// NOTE: prepend, so we don't override user-specified scopes.
 	opts = append([]option.ClientOption{scopesOption}, opts...)
 	opts = append(opts, internaloption.WithDefaultEndpoint(basePath))
+	opts = append(opts, internaloption.WithDefaultMTLSEndpoint(mtlsBasePath))
 	client, endpoint, err := htransport.NewClient(ctx, opts...)
 	if err != nil {
 		return nil, err
diff --git a/google-api-go-generator/testdata/resource-named-service.json b/google-api-go-generator/testdata/resource-named-service.json
index 936867d..2c1ae24 100644
--- a/google-api-go-generator/testdata/resource-named-service.json
+++ b/google-api-go-generator/testdata/resource-named-service.json
@@ -19,6 +19,7 @@
  "baseUrl": "https://appengine.googleapis.com/",
  "basePath": "",
  "rootUrl": "https://appengine.googleapis.com/",
+ "mtlsRootUrl": "https://appengine.mtls.googleapis.com/",
  "servicePath": "",
  "batchPath": "batch",
  "version_module": true,
diff --git a/google-api-go-generator/testdata/resource-named-service.want b/google-api-go-generator/testdata/resource-named-service.want
index 9eaedf8..9fd5d31 100644
--- a/google-api-go-generator/testdata/resource-named-service.want
+++ b/google-api-go-generator/testdata/resource-named-service.want
@@ -75,6 +75,7 @@
 const apiName = "appengine"
 const apiVersion = "v1"
 const basePath = "https://appengine.googleapis.com/"
+const mtlsBasePath = "https://appengine.mtls.googleapis.com/"
 
 // OAuth2 scopes used by this API.
 const (
@@ -90,6 +91,7 @@
 	// NOTE: prepend, so we don't override user-specified scopes.
 	opts = append([]option.ClientOption{scopesOption}, opts...)
 	opts = append(opts, internaloption.WithDefaultEndpoint(basePath))
+	opts = append(opts, internaloption.WithDefaultMTLSEndpoint(mtlsBasePath))
 	client, endpoint, err := htransport.NewClient(ctx, opts...)
 	if err != nil {
 		return nil, err
diff --git a/internal/settings.go b/internal/settings.go
index 0d8210b..f435519 100644
--- a/internal/settings.go
+++ b/internal/settings.go
@@ -18,25 +18,26 @@
 // DialSettings holds information needed to establish a connection with a
 // Google API service.
 type DialSettings struct {
-	Endpoint          string
-	DefaultEndpoint   string
-	Scopes            []string
-	TokenSource       oauth2.TokenSource
-	Credentials       *google.Credentials
-	CredentialsFile   string // if set, Token Source is ignored.
-	CredentialsJSON   []byte
-	UserAgent         string
-	APIKey            string
-	Audiences         []string
-	HTTPClient        *http.Client
-	GRPCDialOpts      []grpc.DialOption
-	GRPCConn          *grpc.ClientConn
-	GRPCConnPool      ConnPool
-	GRPCConnPoolSize  int
-	NoAuth            bool
-	TelemetryDisabled bool
-	ClientCertSource  func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
-	CustomClaims      map[string]interface{}
+	Endpoint            string
+	DefaultEndpoint     string
+	DefaultMTLSEndpoint string
+	Scopes              []string
+	TokenSource         oauth2.TokenSource
+	Credentials         *google.Credentials
+	CredentialsFile     string // if set, Token Source is ignored.
+	CredentialsJSON     []byte
+	UserAgent           string
+	APIKey              string
+	Audiences           []string
+	HTTPClient          *http.Client
+	GRPCDialOpts        []grpc.DialOption
+	GRPCConn            *grpc.ClientConn
+	GRPCConnPool        ConnPool
+	GRPCConnPoolSize    int
+	NoAuth              bool
+	TelemetryDisabled   bool
+	ClientCertSource    func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
+	CustomClaims        map[string]interface{}
 
 	// Google API system parameters. For more information please read:
 	// https://cloud.google.com/apis/docs/system-parameters
diff --git a/option/internaloption/internaloption.go b/option/internaloption/internaloption.go
index 48121e4..ff5b530 100644
--- a/option/internaloption/internaloption.go
+++ b/option/internaloption/internaloption.go
@@ -24,3 +24,17 @@
 func WithDefaultEndpoint(url string) option.ClientOption {
 	return defaultEndpointOption(url)
 }
+
+type defaultMTLSEndpointOption string
+
+func (o defaultMTLSEndpointOption) Apply(settings *internal.DialSettings) {
+	settings.DefaultMTLSEndpoint = string(o)
+}
+
+// WithDefaultMTLSEndpoint is an option that indicates the default mTLS endpoint.
+//
+// It should only be used internally by generated clients.
+//
+func WithDefaultMTLSEndpoint(url string) option.ClientOption {
+	return defaultMTLSEndpointOption(url)
+}
diff --git a/transport/http/dial.go b/transport/http/dial.go
index 19a4c9a..4450301 100644
--- a/transport/http/dial.go
+++ b/transport/http/dial.go
@@ -269,7 +269,7 @@
 	if settings.Endpoint == "" {
 		mtlsMode := getMTLSMode()
 		if mtlsMode == mTLSModeAlways || (clientCertSource != nil && mtlsMode == mTLSModeAuto) {
-			return generateDefaultMtlsEndpoint(settings.DefaultEndpoint), nil
+			return settings.DefaultMTLSEndpoint, nil
 		}
 		return settings.DefaultEndpoint, nil
 	}
@@ -302,26 +302,3 @@
 	u.Host = newHost
 	return u.String(), nil
 }
-
-// generateDefaultMtlsEndpoint attempts to derive the mTLS version of the
-// defaultEndpoint via regex, and returns defaultEndpoint if unsuccessful.
-//
-// We need to applying the following 2 transformations:
-// 1. pubsub.googleapis.com to pubsub.mtls.googleapis.com
-// 2. pubsub.sandbox.googleapis.com to pubsub.mtls.sandbox.googleapis.com
-//
-// TODO(andyzhao): In the future, the mTLS endpoint will be read from the Discovery Document
-// and passed in as defaultMtlsEndpoint instead of generated from defaultEndpoint,
-// and this function will be removed.
-func generateDefaultMtlsEndpoint(defaultEndpoint string) string {
-	var domains = []string{
-		".sandbox.googleapis.com", // must come first because .googleapis.com is a substring
-		".googleapis.com",
-	}
-	for _, domain := range domains {
-		if strings.Contains(defaultEndpoint, domain) {
-			return strings.Replace(defaultEndpoint, domain, ".mtls"+domain, -1)
-		}
-	}
-	return defaultEndpoint
-}
diff --git a/transport/http/dial_test.go b/transport/http/dial_test.go
index 0dfab10..ddbdc6e 100644
--- a/transport/http/dial_test.go
+++ b/transport/http/dial_test.go
@@ -9,7 +9,6 @@
 
 	"crypto/tls"
 
-	"github.com/google/go-cmp/cmp"
 	"google.golang.org/api/internal"
 )
 
@@ -111,27 +110,3 @@
 		}
 	}
 }
-
-func TestGenerateDefaultMtlsEndpoint(t *testing.T) {
-	mtlsEndpoint := generateDefaultMtlsEndpoint("pubsub.googleapis.com")
-	wantMtlsEndpoint := "pubsub.mtls.googleapis.com"
-	if !cmp.Equal(mtlsEndpoint, wantMtlsEndpoint) {
-		t.Error(cmp.Diff(wantMtlsEndpoint, wantMtlsEndpoint))
-	}
-}
-
-func TestGenerateDefaultMtlsEndpointSandbox(t *testing.T) {
-	mtlsEndpoint := generateDefaultMtlsEndpoint("staging-pubsub.sandbox.googleapis.com")
-	wantMtlsEndpoint := "staging-pubsub.mtls.sandbox.googleapis.com"
-	if !cmp.Equal(mtlsEndpoint, wantMtlsEndpoint) {
-		t.Error(cmp.Diff(wantMtlsEndpoint, wantMtlsEndpoint))
-	}
-}
-
-func TestGenerateDefaultMtlsEndpointUnsupported(t *testing.T) {
-	mtlsEndpoint := generateDefaultMtlsEndpoint("unsupported.google.com")
-	wantMtlsEndpoint := "unsupported.google.com"
-	if !cmp.Equal(mtlsEndpoint, wantMtlsEndpoint) {
-		t.Error(cmp.Diff(wantMtlsEndpoint, wantMtlsEndpoint))
-	}
-}