diff --git a/interfaces/service_endpoints.go b/interfaces/service_endpoints.go index 5fe3c582..dbcb6e87 100644 --- a/interfaces/service_endpoints.go +++ b/interfaces/service_endpoints.go @@ -7,9 +7,31 @@ package interfaces // values such as Streaming, or use the helper method // [github.com/launchdarkly/go-server-sdk/v7/ldcomponents.RelayProxyEndpoints]. // +// Important note: if one or more URI is set to a custom value, then all URIs should be set to custom values. +// Otherwise, the SDK will emit an error-level log to surface this potential misconfiguration, while using default values for +// the unset URIs. +// +// There are some scenarios where it is desirable to set only some of the fields, but this is not recommended for general +// usage. If you're scenario requires it, you can call [WithPartialSpecification] to suppress the +// error message. +// // See Config.ServiceEndpoints for more details. type ServiceEndpoints struct { - Streaming string - Polling string - Events string + Streaming string + Polling string + Events string + allowPartialSpecification bool +} + +// WithPartialSpecification returns a copy of this ServiceEndpoints that will not trigger an error-level log message +// if only some, but not all the fields are set to custom values. This is an advanced configuration and likely not necessary +// for most use-cases. +func (s ServiceEndpoints) WithPartialSpecification() ServiceEndpoints { + s.allowPartialSpecification = true + return s +} + +// PartialSpecificationRequested returns true if this ServiceEndpoints should not be treated as malformed if some, but not all fields are set. +func (s ServiceEndpoints) PartialSpecificationRequested() bool { + return s.allowPartialSpecification } diff --git a/internal/endpoints/configure_endpoints.go b/internal/endpoints/configure_endpoints.go index 8e38dffb..85dfac1e 100644 --- a/internal/endpoints/configure_endpoints.go +++ b/internal/endpoints/configure_endpoints.go @@ -77,10 +77,12 @@ func SelectBaseURI( if anyCustom(serviceEndpoints) { configuredBaseURI = getCustom(serviceEndpoints, serviceType) if configuredBaseURI == "" { - loggers.Errorf( - "You have set custom ServiceEndpoints without specifying the %s base URI; connections may not work properly", - serviceType, - ) + if !serviceEndpoints.PartialSpecificationRequested() { + loggers.Errorf( + "You have set custom ServiceEndpoints without specifying the %s base URI; connections may not work properly", + serviceType, + ) + } configuredBaseURI = DefaultBaseURI(serviceType) } } else { diff --git a/internal/endpoints/configure_endpoints_test.go b/internal/endpoints/configure_endpoints_test.go index e6172a1f..fe61c314 100644 --- a/internal/endpoints/configure_endpoints_test.go +++ b/internal/endpoints/configure_endpoints_test.go @@ -40,7 +40,7 @@ func TestSelectCustomURIs(t *testing.T) { } func TestLogErrorIfAtLeastOneButNotAllCustomURISpecified(t *testing.T) { - logger := ldlogtest.NewMockLog() + const customURI = "http://custom_uri" cases := []struct { @@ -60,14 +60,29 @@ func TestLogErrorIfAtLeastOneButNotAllCustomURISpecified(t *testing.T) { {interfaces.ServiceEndpoints{Streaming: customURI, Polling: customURI}, EventsService}, } - // Even if the configuration is considered to be likely malformed, we should still return the proper default URI for - // the service that wasn't configured. - for _, c := range cases { - assert.Equal(t, strings.TrimSuffix(DefaultBaseURI(c.service), "/"), SelectBaseURI(c.endpoints, c.service, logger.Loggers)) - } + t.Run("without explicit partial specification", func(t *testing.T) { + logger := ldlogtest.NewMockLog() + + // Even if the configuration is considered to be likely malformed, we should still return the proper default URI for + // the service that wasn't configured. + for _, c := range cases { + assert.Equal(t, strings.TrimSuffix(DefaultBaseURI(c.service), "/"), SelectBaseURI(c.endpoints, c.service, logger.Loggers)) + } + + // For each service that wasn't configured, we should see a log message indicating that. + for _, c := range cases { + logger.AssertMessageMatch(t, true, ldlog.Error, fmt.Sprintf("You have set custom ServiceEndpoints without specifying the %s base URI", c.service)) + } + }) + + t.Run("with partial specification", func(t *testing.T) { + logger := ldlogtest.NewMockLog() + + for _, c := range cases { + endpoints := c.endpoints.WithPartialSpecification() + assert.Equal(t, strings.TrimSuffix(DefaultBaseURI(c.service), "/"), SelectBaseURI(endpoints, c.service, logger.Loggers)) + } + assert.Empty(t, logger.GetOutput(ldlog.Error)) + }) - // For each service that wasn't configured, we should see a log message indicating that. - for _, c := range cases { - logger.AssertMessageMatch(t, true, ldlog.Error, fmt.Sprintf("You have set custom ServiceEndpoints without specifying the %s base URI", c.service)) - } } diff --git a/ldcomponents/service_endpoints.go b/ldcomponents/service_endpoints.go index 3d633c8f..40d6ee61 100644 --- a/ldcomponents/service_endpoints.go +++ b/ldcomponents/service_endpoints.go @@ -31,7 +31,8 @@ func RelayProxyEndpoints(relayProxyBaseURI string) interfaces.ServiceEndpoints { } // RelayProxyEndpointsWithoutEvents specifies a single base URI for a Relay Proxy instance, telling -// the SDK to use the Relay Proxy for all services except analytics events. +// the SDK to use the Relay Proxy for all services except analytics events. Note that this does not disable events, it +// instead means events (if enabled) will be sent directly to LaunchDarkly. // // When using the LaunchDarkly Relay Proxy (https://docs.launchdarkly.com/home/relay-proxy), the SDK // only needs to know the single base URI of the Relay Proxy, which will provide all of the proxied @@ -51,5 +52,5 @@ func RelayProxyEndpointsWithoutEvents(relayProxyBaseURI string) interfaces.Servi return interfaces.ServiceEndpoints{ Streaming: relayProxyBaseURI, Polling: relayProxyBaseURI, - } + }.WithPartialSpecification() }