diff --git a/CMakeLists.txt b/CMakeLists.txt index 6147262d4..992c3704f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ option(BUILD_TESTING "Enable C++ unit tests." ON) option(TESTING_SANITIZERS "Enable sanitizers for unit tests." ON) if (BUILD_TESTING) + add_compile_definitions(LAUNCHDARKLY_USE_ASSERT) if (TESTING_SANITIZERS) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize=undefined -fsanitize=leak") @@ -62,7 +63,7 @@ else () else () set(Boost_USE_STATIC_LIBS ON) endif () -endif() +endif () set(Boost_USE_MULTITHREADED ON) set(Boost_USE_STATIC_RUNTIME OFF) diff --git a/apps/hello-cpp/main.cpp b/apps/hello-cpp/main.cpp index f1b9a03db..ae9d81804 100644 --- a/apps/hello-cpp/main.cpp +++ b/apps/hello-cpp/main.cpp @@ -25,25 +25,29 @@ int main() { return 1; } - Client client( - ConfigBuilder(key) - .ServiceEndpoints( - launchdarkly::client_side::EndpointsBuilder() - // Set to http to demonstrate redirect to https. - .PollingBaseUrl("http://sdk.launchdarkly.com") - .StreamingBaseUrl("https://stream.launchdarkly.com") - .EventsBaseUrl("https://events.launchdarkly.com")) - .DataSource(DataSourceBuilder() - .Method(DataSourceBuilder::Polling().PollInterval( - std::chrono::seconds{30})) - .WithReasons(true) - .UseReport(true)) - .Logging(LoggingBuilder::BasicLogging().Level(LogLevel::kDebug)) - .Events(launchdarkly::client_side::EventsBuilder().FlushInterval( - std::chrono::seconds(5))) - .Build() - .value(), - ContextBuilder().kind("user", "ryan").build()); + auto config_builder = ConfigBuilder(key); + + config_builder.ServiceEndpoints() + .PollingBaseUrl("http://sdk.launchdarkly.com") + .StreamingBaseUrl("https://stream.launchdarkly.com") + .EventsBaseUrl("https://events.launchdarkly.com"); + config_builder.DataSource() + .Method( + DataSourceBuilder::Polling().PollInterval(std::chrono::seconds{30})) + .WithReasons(true) + .UseReport(true); + config_builder.Logging().Logging( + LoggingBuilder::BasicLogging().Level(LogLevel::kDebug)); + config_builder.Events().FlushInterval(std::chrono::seconds(5)); + + auto config = config_builder.Build(); + if (!config) { + std::cout << config.error(); + return 1; + } + + Client client(std::move(*config), + ContextBuilder().kind("user", "ryan").build()); std::cout << "Initial Status: " << client.DataSourceStatus().Status() << std::endl; diff --git a/apps/sdk-contract-tests/src/entity_manager.cpp b/apps/sdk-contract-tests/src/entity_manager.cpp index 06b1c21e6..b9055f349 100644 --- a/apps/sdk-contract-tests/src/entity_manager.cpp +++ b/apps/sdk-contract-tests/src/entity_manager.cpp @@ -31,14 +31,12 @@ std::optional EntityManager::create(ConfigParams const& in) { auto default_endpoints = launchdarkly::client_side::Defaults::ServiceEndpoints(); - auto endpoints = - EndpointsBuilder() + auto& endpoints = + config_builder.ServiceEndpoints() .EventsBaseUrl(default_endpoints.EventsBaseUrl()) .PollingBaseUrl(default_endpoints.PollingBaseUrl()) .StreamingBaseUrl(default_endpoints.StreamingBaseUrl()); - auto datasource = DataSourceBuilder(); - if (in.serviceEndpoints) { if (in.serviceEndpoints->streaming) { endpoints.StreamingBaseUrl(*in.serviceEndpoints->streaming); @@ -57,6 +55,8 @@ std::optional EntityManager::create(ConfigParams const& in) { } } + auto& datasource = config_builder.DataSource(); + if (in.polling) { if (in.polling->baseUri) { endpoints.PollingBaseUrl(*in.polling->baseUri); @@ -73,7 +73,7 @@ std::optional EntityManager::create(ConfigParams const& in) { } } - auto event_config = EventsBuilder(); + auto& event_config = config_builder.Events(); if (in.events) { ConfigEventParams const& events = *in.events; @@ -106,10 +106,6 @@ std::optional EntityManager::create(ConfigParams const& in) { event_config.Disable(); } - config_builder.Events(std::move(event_config)); - - config_builder.ServiceEndpoints(std::move(endpoints)); - if (in.clientSide->evaluationReasons) { datasource.WithReasons(*in.clientSide->evaluationReasons); } @@ -118,8 +114,6 @@ std::optional EntityManager::create(ConfigParams const& in) { datasource.UseReport(*in.clientSide->useReport); } - config_builder.DataSource(std::move(datasource)); - auto config = config_builder.Build(); if (!config) { LD_LOG(logger_, LogLevel::kWarn) diff --git a/libs/common/include/launchdarkly/bindings/c/config/builder.h b/libs/common/include/launchdarkly/bindings/c/config/builder.h index b5aeef534..0f53c3ebe 100644 --- a/libs/common/include/launchdarkly/bindings/c/config/builder.h +++ b/libs/common/include/launchdarkly/bindings/c/config/builder.h @@ -6,23 +6,459 @@ #include #include +#include + #ifdef __cplusplus extern "C" { // only need to export C interface if // used by C++ source code #endif typedef struct _LDClientConfigBuilder* LDClientConfigBuilder; +typedef struct _LDDataSourceStreamBuilder* LDDataSourceStreamBuilder; +typedef struct _LDDataSourcePollBuilder* LDDataSourcePollBuilder; +typedef struct _LDLoggingCustomBuilder* LDLoggingCustomBuilder; +typedef struct _LDLoggingBasicBuilder* LDLoggingBasicBuilder; + +/** + * Defines the log levels used with the SDK's default logger, or a user-provided + * custom logger. + */ +enum LDLogLevel { + LD_LOG_DEBUG = 0, + LD_LOG_INFO = 1, + LD_LOG_WARN = 2, + LD_LOG_ERROR = 3, +}; + +/** + * Defines a logging interface suitable for use with SDK configuration. + */ +struct LDLogBackend { + typedef bool (*EnabledFn)(enum LDLogLevel level, void* user_data); + typedef void (*WriteFn)(enum LDLogLevel level, + char const* msg, + void* user_data); + + /** + * Check if the specified log level is enabled. Must be thread safe. + * @param level The log level to check. + * @return Returns true if the level is enabled. + */ + EnabledFn Enabled; + + /** + * Write a message to the specified level. The message pointer is valid only + * for the duration of this function call. Must be thread safe. + * @param level The level to write the message to. + * @param message The message to write. + */ + WriteFn Write; + /** + * UserData is forwarded into both Enabled and Write. + */ + void* UserData; +}; + +/** + * Initializes a custom log backend. Must be called before passing a custom + * backend into configuration. + * @param backend Backend to initialize. + */ +LD_EXPORT(void) LDLogBackend_Init(struct LDLogBackend* backend); + +/** + * Constructs a client-side config builder. + */ LD_EXPORT(LDClientConfigBuilder) LDClientConfigBuilder_New(char const* sdk_key); +/** + * Sets a custom URL for the polling service. + * @param b Client config builder. Must not be NULL. + * @param url Target URL. Must not be NULL. + */ +LD_EXPORT(void) +LDClientConfigBuilder_ServiceEndpoints_PollingBaseURL(LDClientConfigBuilder b, + char const* url); +/** + * Sets a custom URL for the streaming service. + * @param b Client config builder. Must not be NULL. + * @param url Target URL. Must not be NULL. + */ +LD_EXPORT(void) +LDClientConfigBuilder_ServiceEndpoints_StreamingBaseURL(LDClientConfigBuilder b, + char const* url); +/** + * Sets a custom URL for the events service. + * @param b Client config builder. Must not be NULL. + * @param url Target URL. Must not be NULL. + */ +LD_EXPORT(void) +LDClientConfigBuilder_ServiceEndpoints_EventsBaseURL(LDClientConfigBuilder b, + char const* url); +/** + * Sets a custom URL for a Relay Proxy instance. The streaming, + * polling, and events URLs are set automatically. + * @param b Client config builder. Must not be NULL. + * @param url Target URL. Must not be NULL. + */ +LD_EXPORT(void) +LDClientConfigBuilder_ServiceEndpoints_RelayProxyBaseURL( + LDClientConfigBuilder b, + char const* url); + +/** + * Sets an identifier for the application. + * @param b Client config builder. Must not be NULL. + * @param app_id Non-empty string. Must be <= 64 chars. Must be alphanumeric, + * '-', '.', or '_'. Must not be NULL. + */ +LD_EXPORT(void) +LDClientConfigBuilder_AppInfo_Identifier(LDClientConfigBuilder b, + char const* app_id); + +/** + * Sets a version for the application. + * @param b Client config builder. Must not be NULL. + * @param app_version Non-empty string. Must be <= 64 chars. Must be + * alphanumeric, + * '-', '.', or '_'. Must not be NULL. + */ +LD_EXPORT(void) +LDClientConfigBuilder_AppInfo_Version(LDClientConfigBuilder b, + char const* app_version); + +/** + * Enables or disables "Offline" mode. True means + * Offline mode is enabled. + * @param b Client config builder. Must not be NULL. + * @param offline True if offline. + */ +LD_EXPORT(void) +LDClientConfigBuilder_Offline(LDClientConfigBuilder b, bool offline); + +/** + * Specify if event-sending should be enabled or not. By default, + * events are enabled. + * @param b Client config builder. Must not be NULL. + * @param enabled True to enable event-sending. + */ +LD_EXPORT(void) +LDClientConfigBuilder_Events_Enabled(LDClientConfigBuilder b, bool enabled); + +/** + * Sets the capacity of the event processor. When more events are generated + * within the processor's flush interval than this value, events will be + * dropped. + * @param b Client config builder. Must not be NULL. + * @param capacity Event queue capacity. + */ +LD_EXPORT(void) +LDClientConfigBuilder_Events_Capacity(LDClientConfigBuilder b, size_t capacity); + +/** + * Sets the flush interval of the event processor. The processor queues + * outgoing events based on the capacity parameter; these events are then + * delivered based on the flush interval. + * @param b Client config builder. Must not be NULL. + * @param milliseconds Interval between automatic flushes. + */ +LD_EXPORT(void) +LDClientConfigBuilder_Events_FlushIntervalMs(LDClientConfigBuilder b, + unsigned int milliseconds); + +/** + * Attribute privacy indicates whether or not attributes should be + * retained by LaunchDarkly after being sent upon initialization, + * and if attributes should later be sent in events. + * + * Attribute privacy may be specified in 3 ways: + * + * (1) To specify that all attributes should be considered private - not + * just those designated private on a per-context basis - call this method + * with true as the parameter. + * + * (2) To specify that a specific set of attributes should be considered + * private - in addition to those designated private on a per-context basis + * - call @ref PrivateAttribute. + * + * (3) To specify private attributes on a per-context basis, it is not + * necessary to call either of these methods, as the default behavior is to + * treat all attributes as non-private unless otherwise specified. + * + * @param b Client config builder. Must not be NULL. + * @param all_attributes_private True for behavior of (1), false for default + * behavior of (2) or (3). + */ +LD_EXPORT(void) +LDClientConfigBuilder_Events_AllAttributesPrivate(LDClientConfigBuilder b, + bool all_attributes_private); + +/** + * Specifies a single private attribute. May be called multiple times + * with additional private attributes. + * @param b Client config builder. Must not be NULL. + * @param attribute_reference Attribute to mark private. + */ +LD_EXPORT(void) +LDClientConfigBuilder_Events_PrivateAttribute(LDClientConfigBuilder b, + char const* attribute_reference); +/** + * * Whether LaunchDarkly should provide additional information about how flag + * values were calculated. + * + * The additional information will then be available through the client's + * VariationDetail methods. Since this increases the size of network + * requests, such information is not sent unless you set this option to + * true. + * @param b Client config builder. Must not be NULL. + * @param with_reasons True to enable reasons. + */ +LD_EXPORT(void) +LDClientConfigBuilder_DataSource_WithReasons(LDClientConfigBuilder b, + bool with_reasons); + +/** + * Whether or not to use the REPORT verb to fetch flag settings. + * + * If this is true, flag settings will be fetched with a REPORT request + * including a JSON entity body with the context object. + * + * Otherwise (by default) a GET request will be issued with the context + * passed as a base64 URL-encoded path parameter. + * + * Do not use unless advised by LaunchDarkly. + * @param b Client config builder. Must not be NULL. + * @param use_reasons True to use the REPORT verb. + */ +LD_EXPORT(void) +LDClientConfigBuilder_DataSource_UseReport(LDClientConfigBuilder b, + bool use_report); +/** + * Set the streaming configuration for the builder. + * + * A data source may either be streaming or polling. Setting a streaming + * builder indicates the data source will use streaming. Setting a polling + * builder will indicate the use of polling. + * + * @param b Client config builder. Must not be NULL. + * @param stream_builder The streaming builder. The builder is consumed; do not + * free it. + */ +LD_EXPORT(void) +LDClientConfigBuilder_DataSource_MethodStream( + LDClientConfigBuilder b, + LDDataSourceStreamBuilder stream_builder); + +/** + * Set the polling configuration for the builder. + * + * A data source may either be streaming or polling. Setting a stream + * builder indicates the data source will use streaming. Setting a polling + * builder will indicate the use of polling. + * + * @param b Client config builder. Must not be NULL. + * @param poll_builder The polling builder. The builder is consumed; do not free + * it. + */ +LD_EXPORT(void) +LDClientConfigBuilder_DataSource_MethodPoll( + LDClientConfigBuilder b, + LDDataSourcePollBuilder poll_builder); + +/** + * Creates a new DataSource builder for the Streaming method. + * + * If not passed into the config + * builder, must be manually freed with LDDataSourceStreamBuilder_Free. + * + * @return New builder for Streaming method. + */ +LD_EXPORT(LDDataSourceStreamBuilder) +LDDataSourceStreamBuilder_New(); + +/** + * Sets the initial reconnect delay for the streaming connection. + * + * The streaming service uses a backoff algorithm (with jitter) every time + * the connection needs to be reestablished.The delay for the first + * reconnection will start near this value, and then increase exponentially + * for any subsequent connection failures. + * + * @param b Streaming method builder. Must not be NULL. + * @param milliseconds Initial delay for a reconnection attempt. + */ +LD_EXPORT(void) +LDDataSourceStreamBuilder_InitialReconnectDelayMs(LDDataSourceStreamBuilder b, + unsigned int milliseconds); + +/** + * Frees a Streaming method builder. Do not call if the builder was consumed by + * the config builder. + * + * @param b Builder to free. + */ +LD_EXPORT(void) LDDataSourceStreamBuilder_Free(LDDataSourceStreamBuilder b); + +/** + * Creates a new DataSource builder for the Polling method. + * + * If not passed into the config + * builder, must be manually freed with LDDataSourcePollBuilder_Free. + * + * @return New builder for Polling method. + */ + +LD_EXPORT(LDDataSourcePollBuilder) +LDDataSourcePollBuilder_New(); + +/** + * Sets the interval at which the SDK will poll for feature flag updates. + * @param b Polling method builder. Must not be NULL. + * @param milliseconds Polling interval. + */ +LD_EXPORT(void) +LDDataSourcePollBuilder_IntervalS(LDDataSourcePollBuilder b, + unsigned int seconds); + +/** + * Frees a Polling method builder. Do not call if the builder was consumed by + * the config builder. + * + * @param b Builder to free. + */ +LD_EXPORT(void) LDDataSourcePollBuilder_Free(LDDataSourcePollBuilder b); + +/** + * This should be used for wrapper SDKs to set the wrapper name. + * + * Wrapper information will be included in request headers. + * @param b Client config builder. Must not be NULL. + * @param wrapper_name Name of the wrapper. + */ +LD_EXPORT(void) +LDClientConfigBuilder_HttpProperties_WrapperName(LDClientConfigBuilder b, + char const* wrapper_name); + +/** + * This should be used for wrapper SDKs to set the wrapper version. + * + * Wrapper information will be included in request headers. + * @param b Client config builder. Must not be NULL. + * @param wrapper_version Version of the wrapper. + */ +LD_EXPORT(void) +LDClientConfigBuilder_HttpProperties_WrapperVersion( + LDClientConfigBuilder b, + char const* wrapper_version); + +/** + * Set a custom header value. May be called more than once with additional + * headers. + * + * @param b Client config builder. Must not be NULL. + * @param key Name of the header. Must not be NULL. + * @param value Value of the header. Must not be NULL. + */ +LD_EXPORT(void) +LDClientConfigBuilder_HttpProperties_Header(LDClientConfigBuilder b, + char const* key, + char const* value); + +/** + * Creates a new builder for LaunchDarkly's default logger. + * + * If not passed into the config + * builder, must be manually freed with LDLoggingBasicBuilder_Free. + * @return New builder. + */ +LD_EXPORT(LDLoggingBasicBuilder) LDLoggingBasicBuilder_New(); + +/** + * Frees a basic logging builder. Do not call if the builder was consumed by + * the config builder. + * @param b Builder to free. + */ +LD_EXPORT(void) LDLoggingBasicBuilder_Free(LDLoggingBasicBuilder b); + +/** + * Sets the enabled log level. The default level is LD_LOG_INFO. + * @param b Client config builder. Must not be NULL. + * @param level Level to set. + */ +LD_EXPORT(void) +LDLoggingBasicBuilder_Level(LDLoggingBasicBuilder b, enum LDLogLevel level); + +/** + * Set a tag for this logger. This tag will be included at the start + * of log entries in square brackets. + * + * If the name was "LaunchDarkly", then log entries will be prefixed + * with "[LaunchDarkly]". The default tag is "LaunchDarkly". + * @param b Client config builder. Must not be NULL. + * @param tag Tag to set. Must not be NULL. + */ +LD_EXPORT(void) +LDLoggingBasicBuilder_Tag(LDLoggingBasicBuilder b, char const* tag); + +/** + * Disables the default SDK logging. + * @param b Client config builder. Must not be NULL. + */ +LD_EXPORT(void) +LDClientConfigBuilder_Logging_Disable(LDClientConfigBuilder b); + +/** + * Configures the SDK with basic logging. + * @param b Client config builder. Must not be NULL. + * @param basic_builder The basic logging builder. Must not be NULL. + */ +LD_EXPORT(void) +LDClientConfigBuilder_Logging_Basic(LDClientConfigBuilder b, + LDLoggingBasicBuilder basic_builder); + +/** + * Creates a new builder for a custom, user-provided logger. + * + * If not passed into the config + * builder, must be manually freed with LDLoggingCustomBuilder_Free. + * @return New builder. + */ +LD_EXPORT(LDLoggingCustomBuilder) LDLoggingCustomBuilder_New(); + +/** + * Frees a custom logging builder. Do not call if the builder was consumed by + * the config builder. + * @param b Builder to free. + */ +LD_EXPORT(void) LDLoggingCustomBuilder_Free(LDLoggingCustomBuilder b); + +/** + * Sets a custom log backend. + * @param b Custom logging builder. Must not be NULL. + * @param backend The backend to use for logging. Ensure the backend was + * initialized with LDLogBackend_Init. + */ +LD_EXPORT(void) +LDLoggingCustomBuilder_Backend(LDLoggingCustomBuilder b, LDLogBackend backend); + +/** + * Configures the SDK with custom logging. + * @param b Client config builder. Must not be NULL. + * @param basic_builder The custom logging builder. Must not be NULL. + */ +LD_EXPORT(void) +LDClientConfigBuilder_Logging_Custom(LDClientConfigBuilder b, + LDLoggingCustomBuilder custom_builder); + /** * Creates an LDClientConfig. The LDClientConfigBuilder is consumed. * On success, the config will be stored in out_config; otherwise, - * out_config will be set to NULL and the returned LDStatus will indicate the - * error. - * @param builder Builder to consume. + * out_config will be set to NULL and the returned LDStatus will indicate + * the error. + * @param builder Builder to consume. Must not be NULL. * @param out_config Pointer to where the built config will be - * stored. + * stored. Must not be NULL. * @return Error status on failure. */ LD_EXPORT(LDStatus) diff --git a/libs/common/include/launchdarkly/config/client.hpp b/libs/common/include/launchdarkly/config/client.hpp index 65d574244..63f7af2fe 100644 --- a/libs/common/include/launchdarkly/config/client.hpp +++ b/libs/common/include/launchdarkly/config/client.hpp @@ -19,7 +19,7 @@ using EventsBuilder = config::shared::builders::EventsBuilder; using HttpPropertiesBuilder = config::shared::builders::HttpPropertiesBuilder; using DataSourceBuilder = config::shared::builders::DataSourceBuilder; - +using LoggingBuilder = config::shared::builders::LoggingBuilder; using Config = config::Config; } // namespace launchdarkly::client_side diff --git a/libs/common/include/launchdarkly/config/shared/builders/config_builder.hpp b/libs/common/include/launchdarkly/config/shared/builders/config_builder.hpp index 35857700c..9458dae11 100644 --- a/libs/common/include/launchdarkly/config/shared/builders/config_builder.hpp +++ b/libs/common/include/launchdarkly/config/shared/builders/config_builder.hpp @@ -32,6 +32,8 @@ class ConfigBuilder { launchdarkly::config::shared::builders::DataSourceBuilder; using HttpPropertiesBuilder = launchdarkly::config::shared::builders::HttpPropertiesBuilder; + using LoggingBuilder = + launchdarkly::config::shared::builders::LoggingBuilder; /** * A minimal configuration consists of a LaunchDarkly SDK Key. @@ -45,7 +47,7 @@ class ConfigBuilder { * @param builder An EndpointsBuilder. * @return Reference to this builder. */ - ConfigBuilder& ServiceEndpoints(EndpointsBuilder builder); + EndpointsBuilder& ServiceEndpoints(); /** * To include metadata about the application that is utilizing the SDK, @@ -53,10 +55,10 @@ class ConfigBuilder { * @param builder An AppInfoBuilder. * @return Reference to this builder. */ - ConfigBuilder& AppInfo(AppInfoBuilder builder); + AppInfoBuilder& AppInfo(); /** - * To enable or disable "Offline" mode, pass a boolean value. True means + * Enables or disables "Offline" mode. True means * Offline mode is enabled. * @param offline True if the SDK should operate in Offline mode. * @return Reference to this builder. @@ -69,7 +71,7 @@ class ConfigBuilder { * @param builder An EventsBuilder. * @return Reference to this builder. */ - ConfigBuilder& Events(EventsBuilder builder); + EventsBuilder& Events(); /** * Sets the configuration of the component that receives feature flag data @@ -77,7 +79,7 @@ class ConfigBuilder { * @param builder A DataSourceConfig builder. * @return Reference to this builder. */ - ConfigBuilder& DataSource(DataSourceBuilder builder); + DataSourceBuilder& DataSource(); /** * Sets the SDK's networking configuration, using an HttpPropertiesBuilder. @@ -85,14 +87,14 @@ class ConfigBuilder { * @param builder A HttpPropertiesBuilder builder. * @return Reference to this builder. */ - ConfigBuilder& HttpProperties(HttpPropertiesBuilder builder); + HttpPropertiesBuilder& HttpProperties(); /** * Sets the logging configuration for the SDK. * @param builder A Logging builder. * @return Reference to this builder. */ - ConfigBuilder& Logging(LoggingBuilder builder); + LoggingBuilder& Logging(); /** * Builds a Configuration, suitable for passing into an instance of Client. @@ -103,12 +105,12 @@ class ConfigBuilder { private: std::string sdk_key_; std::optional offline_; - std::optional service_endpoints_builder_; - std::optional app_info_builder_; - std::optional events_builder_; - std::optional data_source_builder_; - std::optional http_properties_builder_; - std::optional logging_config_builder_; + EndpointsBuilder service_endpoints_builder_; + AppInfoBuilder app_info_builder_; + EventsBuilder events_builder_; + DataSourceBuilder data_source_builder_; + HttpPropertiesBuilder http_properties_builder_; + LoggingBuilder logging_config_builder_; }; } // namespace launchdarkly::config::shared::builders diff --git a/libs/common/include/launchdarkly/config/shared/builders/data_source_builder.hpp b/libs/common/include/launchdarkly/config/shared/builders/data_source_builder.hpp index 8acafb42f..ab1f668b9 100644 --- a/libs/common/include/launchdarkly/config/shared/builders/data_source_builder.hpp +++ b/libs/common/include/launchdarkly/config/shared/builders/data_source_builder.hpp @@ -106,7 +106,7 @@ class DataSourceBuilder { * values were calculated. * * The additional information will then be available through the client's - * {TODO variation detail} method. Since this increases the size of network + * VariationDetail methods. Since this increases the size of network * requests, such information is not sent unless you set this option to * true. * @param value True to enable reasons. @@ -124,7 +124,7 @@ class DataSourceBuilder { * passed as a base64 URL-encoded path parameter. * * Do not use unless advised by LaunchDarkly. - * @param value True to enable using the REPORT verb. + * @param value True to use the REPORT verb. * @return Reference to this builder. */ DataSourceBuilder& UseReport(bool value); diff --git a/libs/common/include/launchdarkly/config/shared/builders/endpoints_builder.hpp b/libs/common/include/launchdarkly/config/shared/builders/endpoints_builder.hpp index 9d40d2aca..cde3b315d 100644 --- a/libs/common/include/launchdarkly/config/shared/builders/endpoints_builder.hpp +++ b/libs/common/include/launchdarkly/config/shared/builders/endpoints_builder.hpp @@ -56,7 +56,8 @@ class EndpointsBuilder { EndpointsBuilder& EventsBaseUrl(std::string url); /** - * Sets a single base URL for a Relay Proxy instance. + * Sets a custom URL for a Relay Proxy instance. The streaming, + * polling, and events URLs are set automatically. * @param url URL to set. * @return Reference to this builder. */ @@ -64,10 +65,11 @@ class EndpointsBuilder { /** * Builds a ServiceEndpoints if the configuration is valid. If not, - * returns an error. + * returns an error. If any streaming, polling, or events URL is set, + * then all URLs must be set. * @return Unique pointer to ServiceEndpoints, or error. */ - [[nodiscard]] tl::expected Build(); + [[nodiscard]] tl::expected Build() const; private: std::optional polling_base_url_; diff --git a/libs/common/include/launchdarkly/config/shared/builders/events_builder.hpp b/libs/common/include/launchdarkly/config/shared/builders/events_builder.hpp index 9656ca627..18dc77c3f 100644 --- a/libs/common/include/launchdarkly/config/shared/builders/events_builder.hpp +++ b/libs/common/include/launchdarkly/config/shared/builders/events_builder.hpp @@ -80,7 +80,7 @@ class EventsBuilder { * * (2) To specify that a specific set of attributes should be considered * private - in addition to those designated private on a per-context basis - * - call @ref PrivateAttributes. + * - call @ref PrivateAttributes or PrivateAttribute. * * (3) To specify private attributes on a per-context basis, it is not * necessary to call either of these methods, as the default behavior is to @@ -93,16 +93,25 @@ class EventsBuilder { EventsBuilder& AllAttributesPrivate(bool all_attributes_private); /** - * Specify that a set of attributes are private. + * Specify a set of private attributes. Any existing private attributes + * are overwritten. * @return Reference to this builder. */ EventsBuilder& PrivateAttributes(AttributeReference::SetType private_attrs); + /** + * Specifies a single private attribute. May be called multiple times + * with additional private attributes. + * @param attribute Attribute to mark private. + * @return Reference to this builder. + */ + EventsBuilder& PrivateAttribute(AttributeReference attribute); + /** * Builds Events configuration, if the configuration is valid. * @return Events config, or error. */ - [[nodiscard]] tl::expected Build(); + [[nodiscard]] tl::expected Build() const; private: built::Events config_; diff --git a/libs/common/include/launchdarkly/config/shared/builders/http_properties_builder.hpp b/libs/common/include/launchdarkly/config/shared/builders/http_properties_builder.hpp index 6861d77a1..59da6f096 100644 --- a/libs/common/include/launchdarkly/config/shared/builders/http_properties_builder.hpp +++ b/libs/common/include/launchdarkly/config/shared/builders/http_properties_builder.hpp @@ -98,17 +98,17 @@ class HttpPropertiesBuilder { /** * Set all custom headers. This will replace any other customer headers * that were set with the Header method, or any previously set - * headers using the CustomHeaders method. + * headers using the Headers method. * @param base_headers The custom headers. * @return A reference to this builder. */ - HttpPropertiesBuilder& CustomHeaders( + HttpPropertiesBuilder& Headers( std::map base_headers); /** * Set a custom header value. * - * Calling CustomHeaders will replace any previously set values. + * Calling Headers will replace any previously set values. * @param key The key for the header. * @param value The header value. * @return A reference to this builder. diff --git a/libs/common/include/launchdarkly/error.hpp b/libs/common/include/launchdarkly/error.hpp index 2ee542b6e..a50afe433 100644 --- a/libs/common/include/launchdarkly/error.hpp +++ b/libs/common/include/launchdarkly/error.hpp @@ -21,7 +21,6 @@ enum class Error : std::uint32_t { kConfig_Events_ZeroCapacity = 300, kConfig_SDKKey_Empty = 400, - /* Client-side errors: 10000-19999 */ /* Server-side errors: 20000-29999 */ diff --git a/libs/common/include/launchdarkly/logging/log_backend.hpp b/libs/common/include/launchdarkly/logging/log_backend.hpp index 227ccdc64..c7af08816 100644 --- a/libs/common/include/launchdarkly/logging/log_backend.hpp +++ b/libs/common/include/launchdarkly/logging/log_backend.hpp @@ -23,8 +23,8 @@ class ILogBackend { /** * Write a message to the specified level. This method must be thread safe. - * @param level The level to write the message for. - * @param message The message to Write. + * @param level The level to write the message to. + * @param message The message to write. */ virtual void Write(LogLevel level, std::string message) noexcept = 0; diff --git a/libs/common/include/launchdarkly/logging/log_level.hpp b/libs/common/include/launchdarkly/logging/log_level.hpp index c89ca99e0..46977cbfc 100644 --- a/libs/common/include/launchdarkly/logging/log_level.hpp +++ b/libs/common/include/launchdarkly/logging/log_level.hpp @@ -3,13 +3,14 @@ namespace launchdarkly { /** * Log levels with kDebug being lowest severity and kError being highest - * severity. + * severity. The values must not be changed to ensure backwards compatibility + * with the C API. */ enum class LogLevel { kDebug = 0, - kInfo, - kWarn, - kError, + kInfo = 1, + kWarn = 2, + kError = 3, }; /** diff --git a/libs/common/src/bindings/c/config/builder.cpp b/libs/common/src/bindings/c/config/builder.cpp index 384ceb00c..4c2368107 100644 --- a/libs/common/src/bindings/c/config/builder.cpp +++ b/libs/common/src/bindings/c/config/builder.cpp @@ -10,22 +10,323 @@ using namespace launchdarkly::client_side; #define TO_BUILDER(ptr) (reinterpret_cast(ptr)) #define FROM_BUILDER(ptr) (reinterpret_cast(ptr)) +#define TO_STREAM_BUILDER(ptr) \ + (reinterpret_cast(ptr)) + +#define FROM_STREAM_BUILDER(ptr) \ + (reinterpret_cast(ptr)) + +#define TO_POLL_BUILDER(ptr) \ + (reinterpret_cast(ptr)) + +#define FROM_POLL_BUILDER(ptr) (reinterpret_cast(ptr)) + +#define TO_BASIC_LOGGING_BUILDER(ptr) \ + (reinterpret_cast(ptr)) + +#define FROM_BASIC_LOGGING_BUILDER(ptr) \ + (reinterpret_cast(ptr)) + +#define TO_CUSTOM_LOGGING_BUILDER(ptr) \ + (reinterpret_cast(ptr)) + +#define FROM_CUSTOM_LOGGING_BUILDER(ptr) \ + (reinterpret_cast(ptr)) + +/** + * Utility class to allow user-provided backends to satisfy the ILogBackend + * interface. + */ +class LogBackendWrapper : public launchdarkly::ILogBackend { + public: + explicit LogBackendWrapper(LDLogBackend backend) : backend_(backend) {} + bool Enabled(launchdarkly::LogLevel level) noexcept override { + return backend_.Enabled(static_cast(level), + backend_.UserData); + } + void Write(launchdarkly::LogLevel level, + std::string message) noexcept override { + return backend_.Write(static_cast(level), message.c_str(), + backend_.UserData); + } + + private: + LDLogBackend backend_; +}; + LD_EXPORT(LDClientConfigBuilder) LDClientConfigBuilder_New(char const* sdk_key) { - return FROM_BUILDER(new ConfigBuilder(sdk_key ? sdk_key : "")); + LD_ASSERT_NOT_NULL(sdk_key); + return FROM_BUILDER(new ConfigBuilder(sdk_key)); +} + +LD_EXPORT(LDStatus) +LDClientConfigBuilder_Build(LDClientConfigBuilder b, + LDClientConfig* out_config) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(out_config); + return ConsumeBuilder(b, out_config); } LD_EXPORT(void) LDClientConfigBuilder_Free(LDClientConfigBuilder builder) { - if (ConfigBuilder* b = TO_BUILDER(builder)) { - delete b; - } + delete TO_BUILDER(builder); } -LD_EXPORT(LDStatus) -LDClientConfigBuilder_Build(LDClientConfigBuilder builder, - LDClientConfig* out_config) { - return ConsumeBuilder(builder, out_config); +LD_EXPORT(void) +LDClientConfigBuilder_ServiceEndpoints_PollingBaseURL(LDClientConfigBuilder b, + char const* url) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(url); + TO_BUILDER(b)->ServiceEndpoints().PollingBaseUrl(url); +} + +LD_EXPORT(void) +LDClientConfigBuilder_ServiceEndpoints_StreamingBaseURL(LDClientConfigBuilder b, + char const* url) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(url); + TO_BUILDER(b)->ServiceEndpoints().StreamingBaseUrl(url); +} + +LD_EXPORT(void) +LDClientConfigBuilder_ServiceEndpoints_EventsBaseURL(LDClientConfigBuilder b, + char const* url) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(url); + TO_BUILDER(b)->ServiceEndpoints().EventsBaseUrl(url); +} + +LD_EXPORT(void) +LDClientConfigBuilder_ServiceEndpoints_RelayProxyBaseURL( + LDClientConfigBuilder b, + char const* url) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(url); + TO_BUILDER(b)->ServiceEndpoints().RelayProxy(url); +} + +LD_EXPORT(void) +LDClientConfigBuilder_AppInfo_Identifier(LDClientConfigBuilder b, + char const* app_id) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(app_id); + TO_BUILDER(b)->AppInfo().Identifier(app_id); +} + +LD_EXPORT(void) +LDClientConfigBuilder_AppInfo_Version(LDClientConfigBuilder b, + char const* app_version) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(app_version); + TO_BUILDER(b)->AppInfo().Version(app_version); +} + +LD_EXPORT(void) +LDClientConfigBuilder_Offline(LDClientConfigBuilder b, bool offline) { + LD_ASSERT_NOT_NULL(b); + TO_BUILDER(b)->Offline(offline); +} + +LD_EXPORT(void) +LDClientConfigBuilder_Events_Enabled(LDClientConfigBuilder b, bool enabled) { + LD_ASSERT_NOT_NULL(b); + TO_BUILDER(b)->Events().Enabled(enabled); +} + +LD_EXPORT(void) +LDClientConfigBuilder_Events_Capacity(LDClientConfigBuilder b, + size_t capacity) { + LD_ASSERT_NOT_NULL(b); + TO_BUILDER(b)->Events().Capacity(capacity); +} + +LD_EXPORT(void) +LDClientConfigBuilder_Events_FlushIntervalMs(LDClientConfigBuilder b, + unsigned int milliseconds) { + LD_ASSERT_NOT_NULL(b); + TO_BUILDER(b)->Events().FlushInterval( + std::chrono::milliseconds{milliseconds}); +} + +LD_EXPORT(void) +LDClientConfigBuilder_Events_AllAttributesPrivate(LDClientConfigBuilder b, + bool all_attributes_private) { + LD_ASSERT_NOT_NULL(b); + TO_BUILDER(b)->Events().AllAttributesPrivate(all_attributes_private); +} + +LD_EXPORT(void) +LDClientConfigBuilder_Events_PrivateAttribute(LDClientConfigBuilder b, + char const* attribute_reference) { + LD_ASSERT_NOT_NULL(b); + TO_BUILDER(b)->Events().PrivateAttribute(attribute_reference); +} + +LD_EXPORT(void) +LDClientConfigBuilder_DataSource_WithReasons(LDClientConfigBuilder b, + bool with_reasons) { + LD_ASSERT_NOT_NULL(b); + TO_BUILDER(b)->DataSource().WithReasons(with_reasons); +} + +LD_EXPORT(void) +LDClientConfigBuilder_DataSource_UseReport(LDClientConfigBuilder b, + bool use_report) { + LD_ASSERT_NOT_NULL(b); + TO_BUILDER(b)->DataSource().UseReport(use_report); +} + +LD_EXPORT(void) +LDClientConfigBuilder_DataSource_MethodStream( + LDClientConfigBuilder b, + LDDataSourceStreamBuilder stream_builder) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(stream_builder); + + DataSourceBuilder::Streaming* sb = TO_STREAM_BUILDER(stream_builder); + TO_BUILDER(b)->DataSource().Method(*sb); + LDDataSourceStreamBuilder_Free(stream_builder); +} + +LD_EXPORT(void) +LDClientConfigBuilder_DataSource_MethodPoll( + LDClientConfigBuilder b, + LDDataSourcePollBuilder poll_builder) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(poll_builder); + + DataSourceBuilder::Polling* pb = TO_POLL_BUILDER(poll_builder); + TO_BUILDER(b)->DataSource().Method(*pb); + LDDataSourcePollBuilder_Free(poll_builder); +} + +LD_EXPORT(LDDataSourceStreamBuilder) LDDataSourceStreamBuilder_New() { + return FROM_STREAM_BUILDER(new DataSourceBuilder::Streaming()); +} + +LD_EXPORT(void) +LDDataSourceStreamBuilder_InitialReconnectDelayMs(LDDataSourceStreamBuilder b, + unsigned int milliseconds) { + LD_ASSERT_NOT_NULL(b); + TO_STREAM_BUILDER(b)->InitialReconnectDelay( + std::chrono::milliseconds{milliseconds}); +} + +LD_EXPORT(void) LDDataSourceStreamBuilder_Free(LDDataSourceStreamBuilder b) { + delete TO_STREAM_BUILDER(b); +} + +LD_EXPORT(LDDataSourcePollBuilder) LDDataSourcePollBuilder_New() { + return FROM_POLL_BUILDER(new DataSourceBuilder::Polling()); +} + +LD_EXPORT(void) +LDDataSourcePollBuilder_IntervalS(LDDataSourcePollBuilder b, + unsigned int seconds) { + LD_ASSERT_NOT_NULL(b); + TO_POLL_BUILDER(b)->PollInterval(std::chrono::seconds{seconds}); +} + +LD_EXPORT(void) LDDataSourcePollBuilder_Free(LDDataSourcePollBuilder b) { + delete TO_POLL_BUILDER(b); +} + +LD_EXPORT(void) +LDClientConfigBuilder_HttpProperties_WrapperName(LDClientConfigBuilder b, + char const* wrapper_name) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(wrapper_name); + TO_BUILDER(b)->HttpProperties().WrapperName(wrapper_name); +} + +LD_EXPORT(void) +LDClientConfigBuilder_HttpProperties_WrapperVersion( + LDClientConfigBuilder b, + char const* wrapper_version) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(wrapper_version); + TO_BUILDER(b)->HttpProperties().WrapperVersion(wrapper_version); +} + +LD_EXPORT(void) +LDClientConfigBuilder_HttpProperties_Header(LDClientConfigBuilder b, + char const* key, + char const* value) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(key); + LD_ASSERT_NOT_NULL(value); + TO_BUILDER(b)->HttpProperties().Header(key, value); +} + +LD_EXPORT(LDLoggingBasicBuilder) LDLoggingBasicBuilder_New() { + return FROM_BASIC_LOGGING_BUILDER(new LoggingBuilder::BasicLogging()); +} + +LD_EXPORT(void) LDLoggingBasicBuilder_Free(LDLoggingBasicBuilder b) { + delete TO_BASIC_LOGGING_BUILDER(b); +} + +LD_EXPORT(void) +LDClientConfigBuilder_Logging_Disable(LDClientConfigBuilder b) { + LD_ASSERT_NOT_NULL(b); + TO_BUILDER(b)->Logging().Logging(LoggingBuilder::NoLogging()); +} + +LD_EXPORT(void) +LDLoggingBasicBuilder_Level(LDLoggingBasicBuilder b, enum LDLogLevel level) { + using launchdarkly::LogLevel; + LD_ASSERT_NOT_NULL(b); + LoggingBuilder::BasicLogging* logger = TO_BASIC_LOGGING_BUILDER(b); + logger->Level(static_cast(level)); +} + +void LDLoggingBasicBuilder_Tag(LDLoggingBasicBuilder b, char const* tag) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(tag); + TO_BASIC_LOGGING_BUILDER(b)->Tag(tag); +} + +LD_EXPORT(void) +LDClientConfigBuilder_Logging_Basic(LDClientConfigBuilder b, + LDLoggingBasicBuilder basic_builder) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(basic_builder); + LoggingBuilder::BasicLogging* bb = TO_BASIC_LOGGING_BUILDER(basic_builder); + TO_BUILDER(b)->Logging().Logging(*bb); + LDLoggingBasicBuilder_Free(basic_builder); +} + +LD_EXPORT(void) LDLogBackend_Init(struct LDLogBackend* backend) { + backend->Enabled = [](enum LDLogLevel, void*) { return false; }; + backend->Write = [](enum LDLogLevel, char const*, void*) {}; + backend->UserData = nullptr; +} + +LD_EXPORT(LDLoggingCustomBuilder) LDLoggingCustomBuilder_New() { + return FROM_CUSTOM_LOGGING_BUILDER(new LoggingBuilder::CustomLogging()); +} + +LD_EXPORT(void) LDLoggingCustomBuilder_Free(LDLoggingCustomBuilder b) { + delete TO_CUSTOM_LOGGING_BUILDER(b); +} + +LD_EXPORT(void) +LDClientConfigBuilder_Logging_Custom(LDClientConfigBuilder b, + LDLoggingCustomBuilder custom_builder) { + LD_ASSERT_NOT_NULL(b); + LD_ASSERT_NOT_NULL(custom_builder); + LoggingBuilder::CustomLogging* cb = + TO_CUSTOM_LOGGING_BUILDER(custom_builder); + TO_BUILDER(b)->Logging().Logging(*cb); + LDLoggingCustomBuilder_Free(custom_builder); +} + +LD_EXPORT(void) +LDLoggingCustomBuilder_Backend(LDLoggingCustomBuilder b, LDLogBackend backend) { + LD_ASSERT_NOT_NULL(b); + TO_CUSTOM_LOGGING_BUILDER(b)->Backend( + std::make_shared(backend)); } // NOLINTEND cppcoreguidelines-pro-type-reinterpret-cast diff --git a/libs/common/src/bindings/c/config/config.cpp b/libs/common/src/bindings/c/config/config.cpp index 24367aecb..d9dd3c476 100644 --- a/libs/common/src/bindings/c/config/config.cpp +++ b/libs/common/src/bindings/c/config/config.cpp @@ -10,9 +10,7 @@ using namespace launchdarkly::client_side; LD_EXPORT(void) LDClientConfig_Free(LDClientConfig config) { - if (Config* c = TO_CONFIG(config)) { - delete c; - } + delete TO_CONFIG(config); } // NOLINTEND cppcoreguidelines-pro-type-reinterpret-cast diff --git a/libs/common/src/bindings/c/value.cpp b/libs/common/src/bindings/c/value.cpp index 32ddc0da3..dadd18c09 100644 --- a/libs/common/src/bindings/c/value.cpp +++ b/libs/common/src/bindings/c/value.cpp @@ -4,9 +4,7 @@ #include #include #include - -#include -#include +#include "../../../src/c_binding_helpers.hpp" using launchdarkly::Value; @@ -57,7 +55,7 @@ LD_EXPORT(enum LDValueType) LDValue_Type(LDValue val) { case Value::Type::kArray: return LDValueType_Array; } - assert(!"Unsupported value type."); + LD_ASSERT(!"Unsupported value type."); } LD_EXPORT(bool) LDValue_GetBool(LDValue val) { diff --git a/libs/common/src/c_binding_helpers.hpp b/libs/common/src/c_binding_helpers.hpp index e205acdea..5d2007f23 100644 --- a/libs/common/src/c_binding_helpers.hpp +++ b/libs/common/src/c_binding_helpers.hpp @@ -1,7 +1,7 @@ #include -#include - +#include #include +#include #include "tl/expected.hpp" @@ -64,4 +64,14 @@ LDStatus ConsumeBuilder(OpaqueBuilder opaque_builder, return LDStatus_Success(); } +// Macro is named the same as in the C Server SDK. + +#ifdef LAUNCHDARKLY_USE_ASSERT +#define LD_ASSERT(cond) assert(cond) +#else +#define LD_ASSERT(cond) +#endif + +#define LD_ASSERT_NOT_NULL(param) LD_ASSERT(param != nullptr) + // NOLINTEND cppcoreguidelines-pro-type-reinterpret-cast diff --git a/libs/common/src/config/config_builder.cpp b/libs/common/src/config/config_builder.cpp index c87246a7a..cb93f507c 100644 --- a/libs/common/src/config/config_builder.cpp +++ b/libs/common/src/config/config_builder.cpp @@ -8,22 +8,19 @@ ConfigBuilder::ConfigBuilder(std::string sdk_key) : sdk_key_(std::move(sdk_key)) {} template -ConfigBuilder& ConfigBuilder::ServiceEndpoints( - EndpointsBuilder builder) { - service_endpoints_builder_ = std::move(builder); - return *this; +typename ConfigBuilder::EndpointsBuilder& +ConfigBuilder::ServiceEndpoints() { + return service_endpoints_builder_; } template -ConfigBuilder& ConfigBuilder::Events(EventsBuilder builder) { - events_builder_ = std::move(builder); - return *this; +typename ConfigBuilder::EventsBuilder& ConfigBuilder::Events() { + return events_builder_; } template -ConfigBuilder& ConfigBuilder::AppInfo(AppInfoBuilder builder) { - app_info_builder_ = std::move(builder); - return *this; +AppInfoBuilder& ConfigBuilder::AppInfo() { + return app_info_builder_; } template @@ -33,22 +30,20 @@ ConfigBuilder& ConfigBuilder::Offline(bool offline) { } template -ConfigBuilder& ConfigBuilder::DataSource(DataSourceBuilder builder) { - data_source_builder_ = builder; - return *this; +typename ConfigBuilder::DataSourceBuilder& +ConfigBuilder::DataSource() { + return data_source_builder_; } template -ConfigBuilder& ConfigBuilder::HttpProperties( - HttpPropertiesBuilder builder) { - http_properties_builder_ = builder; - return *this; +typename ConfigBuilder::HttpPropertiesBuilder& +ConfigBuilder::HttpProperties() { + return http_properties_builder_; } template -ConfigBuilder& ConfigBuilder::Logging(LoggingBuilder builder) { - logging_config_builder_ = builder; - return *this; +LoggingBuilder& ConfigBuilder::Logging() { + return logging_config_builder_; } template @@ -59,30 +54,22 @@ ConfigBuilder::Build() const { return tl::make_unexpected(Error::kConfig_SDKKey_Empty); } auto offline = offline_.value_or(Defaults::Offline()); - auto endpoints_config = - service_endpoints_builder_.value_or(EndpointsBuilder()).Build(); + auto endpoints_config = service_endpoints_builder_.Build(); if (!endpoints_config) { return tl::make_unexpected(endpoints_config.error()); } - auto events_config = events_builder_.value_or(EventsBuilder()).Build(); + auto events_config = events_builder_.Build(); if (!events_config) { return tl::make_unexpected(events_config.error()); } - std::optional app_tag; - if (app_info_builder_) { - app_tag = app_info_builder_->Build(); - } + std::optional app_tag = app_info_builder_.Build(); - auto data_source_config = data_source_builder_ - ? data_source_builder_.value().Build() - : Defaults::DataSourceConfig(); + auto data_source_config = data_source_builder_.Build(); - auto http_properties = http_properties_builder_ - ? http_properties_builder_.value().Build() - : Defaults::HttpProperties(); + auto http_properties = http_properties_builder_.Build(); - auto logging = logging_config_builder_.value_or(LoggingBuilder()).Build(); + auto logging = logging_config_builder_.Build(); return {tl::in_place, sdk_key, diff --git a/libs/common/src/config/endpoints_builder.cpp b/libs/common/src/config/endpoints_builder.cpp index 656823876..825043da3 100644 --- a/libs/common/src/config/endpoints_builder.cpp +++ b/libs/common/src/config/endpoints_builder.cpp @@ -43,7 +43,8 @@ bool empty_string(std::optional const& opt_string) { } template -tl::expected EndpointsBuilder::Build() { +tl::expected EndpointsBuilder::Build() + const { // Empty URLs are not allowed. if (empty_string(polling_base_url_) || empty_string(streaming_base_url_) || empty_string(events_base_url_)) { diff --git a/libs/common/src/config/events_builder.cpp b/libs/common/src/config/events_builder.cpp index 88424f4f5..2f67b8c14 100644 --- a/libs/common/src/config/events_builder.cpp +++ b/libs/common/src/config/events_builder.cpp @@ -46,7 +46,14 @@ EventsBuilder& EventsBuilder::PrivateAttributes( } template -tl::expected EventsBuilder::Build() { +EventsBuilder& EventsBuilder::PrivateAttribute( + AttributeReference attribute) { + config_.private_attributes_.insert(std::move(attribute)); + return *this; +} + +template +tl::expected EventsBuilder::Build() const { if (config_.Capacity() == 0) { return tl::unexpected(Error::kConfig_Events_ZeroCapacity); } diff --git a/libs/common/src/config/http_properties_builder.cpp b/libs/common/src/config/http_properties_builder.cpp index 976ec7874..fc6e37f5f 100644 --- a/libs/common/src/config/http_properties_builder.cpp +++ b/libs/common/src/config/http_properties_builder.cpp @@ -35,6 +35,13 @@ HttpPropertiesBuilder& HttpPropertiesBuilder::ReadTimeout( return *this; } +template +HttpPropertiesBuilder& HttpPropertiesBuilder::WriteTimeout( + std::chrono::milliseconds write_timeout) { + write_timeout_ = write_timeout; + return *this; +} + template HttpPropertiesBuilder& HttpPropertiesBuilder::ResponseTimeout( std::chrono::milliseconds response_timeout) { @@ -57,7 +64,7 @@ HttpPropertiesBuilder& HttpPropertiesBuilder::WrapperVersion( } template -HttpPropertiesBuilder& HttpPropertiesBuilder::CustomHeaders( +HttpPropertiesBuilder& HttpPropertiesBuilder::Headers( std::map base_headers) { base_headers_ = std::move(base_headers); return *this; diff --git a/libs/common/tests/client_config_c_binding_test.cpp b/libs/common/tests/client_config_c_binding_test.cpp index f63bfc4fa..5a2d8c051 100644 --- a/libs/common/tests/client_config_c_binding_test.cpp +++ b/libs/common/tests/client_config_c_binding_test.cpp @@ -1,5 +1,6 @@ #include #include +#include TEST(ClientConfigBindings, ConfigBuilderNewFree) { LDClientConfigBuilder builder = LDClientConfigBuilder_New("sdk-123"); @@ -8,7 +9,7 @@ TEST(ClientConfigBindings, ConfigBuilderNewFree) { } TEST(ClientConfigBindings, ConfigBuilderEmptyResultsInError) { - LDClientConfigBuilder builder = LDClientConfigBuilder_New(nullptr); + LDClientConfigBuilder builder = LDClientConfigBuilder_New(""); LDClientConfig config = nullptr; LDStatus status = LDClientConfigBuilder_Build(builder, &config); @@ -32,3 +33,152 @@ TEST(ClientConfigBindings, MinimalValidConfig) { LDClientConfig_Free(config); // LDClientConfigBuilder is consumed by Build; no need to free it. } + +TEST(ClientConfigBindings, AllConfigs) { + LDClientConfigBuilder builder = LDClientConfigBuilder_New("sdk-123"); + + LDClientConfigBuilder_ServiceEndpoints_PollingBaseURL( + builder, "https://launchdarkly.com"); + LDClientConfigBuilder_ServiceEndpoints_StreamingBaseURL( + builder, "https://launchdarkly.com"); + LDClientConfigBuilder_ServiceEndpoints_EventsBaseURL( + builder, "https://launchdarkly.com"); + LDClientConfigBuilder_ServiceEndpoints_RelayProxyBaseURL( + builder, "https://launchdarkly.com"); + + LDClientConfigBuilder_Offline(builder, false); + + LDClientConfigBuilder_AppInfo_Identifier(builder, "app"); + LDClientConfigBuilder_AppInfo_Version(builder, "v1.2.3"); + + LDClientConfigBuilder_Events_Enabled(builder, true); + LDClientConfigBuilder_Events_Capacity(builder, 100); + LDClientConfigBuilder_Events_FlushIntervalMs(builder, 1000); + LDClientConfigBuilder_Events_AllAttributesPrivate(builder, false); + LDClientConfigBuilder_Events_PrivateAttribute(builder, "/foo/bar"); + + LDClientConfigBuilder_DataSource_UseReport(builder, true); + LDClientConfigBuilder_DataSource_WithReasons(builder, true); + + LDDataSourceStreamBuilder stream_builder = LDDataSourceStreamBuilder_New(); + LDDataSourceStreamBuilder_InitialReconnectDelayMs(stream_builder, 500); + LDClientConfigBuilder_DataSource_MethodStream(builder, stream_builder); + + LDDataSourcePollBuilder poll_builder = LDDataSourcePollBuilder_New(); + LDDataSourcePollBuilder_IntervalS(poll_builder, 10); + LDClientConfigBuilder_DataSource_MethodPoll(builder, poll_builder); + + LDClientConfigBuilder_HttpProperties_Header(builder, "foo", "bar"); + LDClientConfigBuilder_HttpProperties_WrapperName(builder, "wrapper"); + LDClientConfigBuilder_HttpProperties_WrapperVersion(builder, "v1.2.3"); + + LDClientConfigBuilder_Logging_Disable(builder); + + LDLoggingBasicBuilder log_builder = LDLoggingBasicBuilder_New(); + LDLoggingBasicBuilder_Level(log_builder, LD_LOG_WARN); + LDLoggingBasicBuilder_Tag(log_builder, "tag"); + + LDClientConfigBuilder_Logging_Basic(builder, log_builder); + + LDLoggingCustomBuilder custom_logger = LDLoggingCustomBuilder_New(); + + struct LDLogBackend backend; + LDLogBackend_Init(&backend); + + LDLoggingCustomBuilder_Backend(custom_logger, backend); + + LDClientConfigBuilder_Logging_Custom(builder, custom_logger); + + LDClientConfig config = nullptr; + LDStatus status = LDClientConfigBuilder_Build(builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + ASSERT_TRUE(config); + + LDClientConfig_Free(config); +} + +TEST(ClientConfigBindings, CustomNoopLogger) { + using namespace launchdarkly; + + LDClientConfigBuilder builder = LDClientConfigBuilder_New("sdk-123"); + + LDLoggingCustomBuilder custom = LDLoggingCustomBuilder_New(); + + struct LDLogBackend backend; + LDLogBackend_Init(&backend); + + LDLoggingCustomBuilder_Backend(custom, backend); + LDClientConfigBuilder_Logging_Custom(builder, custom); + + LDClientConfig config = nullptr; + LDStatus status = LDClientConfigBuilder_Build(builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + auto client_config = reinterpret_cast(config); + + // These should not abort since LDLogBackend_Init sets up the function + // pointers to be no-ops. + client_config->Logging().backend->Enabled(LogLevel::kError); + client_config->Logging().backend->Write(LogLevel::kError, "hello"); + + LDClientConfig_Free(config); +} + +struct LogArgs { + std::optional enabled; + + struct WriteArgs { + LDLogLevel level; + std::string msg; + WriteArgs(LDLogLevel level, char const* msg) : level(level), msg(msg) {} + }; + + std::optional write; +}; + +TEST(ClientConfigBindings, CustomLogger) { + using namespace launchdarkly; + + LDClientConfigBuilder builder = LDClientConfigBuilder_New("sdk-123"); + + LDLoggingCustomBuilder custom = LDLoggingCustomBuilder_New(); + + struct LDLogBackend backend; + LDLogBackend_Init(&backend); + + LogArgs args; + + backend.UserData = &args; + + backend.Enabled = [](enum LDLogLevel level, void* user_data) -> bool { + auto spy = reinterpret_cast(user_data); + spy->enabled.emplace(level); + return true; + }; + backend.Write = [](enum LDLogLevel level, char const* msg, + void* user_data) { + auto spy = reinterpret_cast(user_data); + spy->write.emplace(level, msg); + }; + + LDLoggingCustomBuilder_Backend(custom, backend); + LDClientConfigBuilder_Logging_Custom(builder, custom); + + LDClientConfig config = nullptr; + LDStatus status = LDClientConfigBuilder_Build(builder, &config); + ASSERT_TRUE(LDStatus_Ok(status)); + + auto client_config = reinterpret_cast(config); + + client_config->Logging().backend->Enabled(LogLevel::kWarn); + client_config->Logging().backend->Write(LogLevel::kError, "hello"); + + LDClientConfig_Free(config); + + ASSERT_TRUE(args.enabled); + ASSERT_EQ(args.enabled, LD_LOG_WARN); + + ASSERT_TRUE(args.write); + ASSERT_EQ(args.write->level, LD_LOG_ERROR); + ASSERT_EQ(args.write->msg, "hello"); +} diff --git a/libs/common/tests/config_builder_test.cpp b/libs/common/tests/config_builder_test.cpp index 132b9cf50..ebef818f1 100644 --- a/libs/common/tests/config_builder_test.cpp +++ b/libs/common/tests/config_builder_test.cpp @@ -47,12 +47,11 @@ TEST_F(ConfigBuilderTest, // This test should exercise all of the config options. TEST_F(ConfigBuilderTest, CustomBuilderReflectsChanges) { using namespace launchdarkly::client_side; - auto config = - ConfigBuilder("sdk-123") - .Offline(true) - .ServiceEndpoints(EndpointsBuilder().RelayProxy("foo")) - .AppInfo(AppInfoBuilder().Identifier("bar").Version("baz")) - .Build(); + auto builder = ConfigBuilder("sdk-123").Offline(true); + builder.ServiceEndpoints().RelayProxy("foo"); + builder.AppInfo().Identifier("bar").Version("baz"); + + auto config = builder.Build(); ASSERT_TRUE(config); ASSERT_EQ(config->SdkKey(), "sdk-123"); @@ -97,9 +96,9 @@ TEST_F(ConfigBuilderTest, ServerConfig_CanSetDataSource) { using namespace launchdarkly::server_side; ConfigBuilder builder("sdk-123"); - builder.DataSource(ConfigBuilder::DataSourceBuilder().Method( + builder.DataSource().Method( ConfigBuilder::DataSourceBuilder::Streaming().InitialReconnectDelay( - std::chrono::milliseconds{5000}))); + std::chrono::milliseconds{5000})); auto cfg = builder.Build(); @@ -114,12 +113,12 @@ TEST_F(ConfigBuilderTest, ClientConfig_CanSetDataSource) { using namespace launchdarkly::client_side; ConfigBuilder builder("sdk-123"); - builder.DataSource( - ConfigBuilder::DataSourceBuilder() - .Method(ConfigBuilder::DataSourceBuilder::Streaming() - .InitialReconnectDelay(std::chrono::milliseconds{5000})) - .UseReport(true) - .WithReasons(true)); + builder.DataSource() + .Method( + ConfigBuilder::DataSourceBuilder::Streaming().InitialReconnectDelay( + std::chrono::milliseconds{5000})) + .UseReport(true) + .WithReasons(true); auto cfg = builder.Build(); ASSERT_TRUE(cfg); @@ -153,14 +152,13 @@ TEST_F(ConfigBuilderTest, TEST_F(ConfigBuilderTest, DefaultConstruction_CanSetHttpProperties) { using namespace launchdarkly::client_side; ConfigBuilder builder("sdk-123"); - builder.HttpProperties( - ConfigBuilder::HttpPropertiesBuilder() - .ConnectTimeout(std::chrono::milliseconds{1234}) - .ReadTimeout(std::chrono::milliseconds{123456}) - .WrapperName("potato") - .WrapperVersion("2.0-chip") - .CustomHeaders( - std::map{{"color", "green"}})); + builder.HttpProperties() + .ConnectTimeout(std::chrono::milliseconds{1234}) + .ReadTimeout(std::chrono::milliseconds{123456}) + .WriteTimeout(std::chrono::milliseconds{123456}) + .WrapperName("potato") + .WrapperVersion("2.0-chip") + .Headers(std::map{{"color", "green"}}); auto cfg = builder.Build(); ASSERT_TRUE(cfg); diff --git a/libs/internal/src/serialization/json_errors.cpp b/libs/internal/src/serialization/json_errors.cpp index 864c50775..c2ec4b2d3 100644 --- a/libs/internal/src/serialization/json_errors.cpp +++ b/libs/internal/src/serialization/json_errors.cpp @@ -34,6 +34,8 @@ char const* ErrorToString(JsonError err) { "reference"; case JsonError::kContextInvalidPrivateAttributesField: return "context 'privateAttributes' field is invalid"; + default: + return "unknown error"; } } diff --git a/libs/internal/tests/event_processor_test.cpp b/libs/internal/tests/event_processor_test.cpp index ca493dca9..3ca36e99f 100644 --- a/libs/internal/tests/event_processor_test.cpp +++ b/libs/internal/tests/event_processor_test.cpp @@ -68,11 +68,9 @@ TEST(EventProcessorTests, ProcessorCompiles) { std::make_shared(LogLevel::kDebug, "test")}; boost::asio::io_context ioc; - auto config = - client_side::ConfigBuilder("sdk-123") - .Events(client_side::EventsBuilder().Capacity(10).FlushInterval( - std::chrono::seconds(1))) - .Build(); + auto config_builder = client_side::ConfigBuilder("sdk-123"); + config_builder.Events().Capacity(10).FlushInterval(std::chrono::seconds(1)); + auto config = config_builder.Build(); ASSERT_TRUE(config);