Skip to content

Commit

Permalink
feat: add LDValue_SerializeJSON C binding (#458)
Browse files Browse the repository at this point in the history
This adds a new API to the `LDValue` interface for serializing to a JSON
string.

Example:

```c
LDValue foo = LDValue_NewBool(true);
char *json = LDValue_SerializeJSON(foo);
// json is the string 'true'
LDMemory_FreeString(json);
LDValue_Free(foo);
```

It's easier to view this PR commit to commit. The first one was
necessary in order to access the boost::json tag_invoke implementations
in `common`, where `LDValue` is implemented. Luckily, I only needed to
pull over the `value` headers and some utilities.

One thing to note is that boost serializes numbers using scientific
notation (e.g. `17` serializes to `1.7E1`). Kind of an odd choice, but
it is to JSON spec.

BEGIN_COMMIT_OVERRIDE
refactor: move json_errors, primitives, and value from internal to common lib
feat: add LDValue_SerializeJSON C binding
END_COMMIT_OVERRIDE
  • Loading branch information
cwaldren-ld authored Oct 30, 2024
1 parent cd6bbd0 commit 90eb880
Show file tree
Hide file tree
Showing 39 changed files with 130 additions and 43 deletions.
4 changes: 2 additions & 2 deletions contract-tests/client-contract-tests/src/client_entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
#include <launchdarkly/context_builder.hpp>
#include <launchdarkly/serialization/json_context.hpp>
#include <launchdarkly/serialization/json_evaluation_reason.hpp>
#include <launchdarkly/serialization/json_primitives.hpp>
#include <launchdarkly/serialization/json_value.hpp>
#include <launchdarkly/detail/serialization/json_primitives.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>
#include <launchdarkly/value.hpp>

#include <chrono>
Expand Down
4 changes: 2 additions & 2 deletions contract-tests/server-contract-tests/src/client_entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
#include <launchdarkly/context_builder.hpp>
#include <launchdarkly/serialization/json_context.hpp>
#include <launchdarkly/serialization/json_evaluation_reason.hpp>
#include <launchdarkly/serialization/json_primitives.hpp>
#include <launchdarkly/serialization/json_value.hpp>
#include <launchdarkly/detail/serialization/json_primitives.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>
#include <launchdarkly/server_side/serialization/json_all_flags_state.hpp>
#include <launchdarkly/value.hpp>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <launchdarkly/encoding/base_64.hpp>
#include <launchdarkly/serialization/json_evaluation_result.hpp>
#include <launchdarkly/serialization/json_item_descriptor.hpp>
#include <launchdarkly/serialization/json_primitives.hpp>
#include <launchdarkly/detail/serialization/json_primitives.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>

#include <boost/core/ignore_unused.hpp>
Expand Down
2 changes: 1 addition & 1 deletion libs/client-sdk/src/flag_manager/flag_persistence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#include <launchdarkly/serialization/json_evaluation_result.hpp>
#include <launchdarkly/serialization/json_item_descriptor.hpp>
#include <launchdarkly/serialization/json_primitives.hpp>
#include <launchdarkly/detail/serialization/json_primitives.hpp>

#include <utility>

Expand Down
15 changes: 15 additions & 0 deletions libs/common/include/launchdarkly/bindings/c/value.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,21 @@ LDValue_GetString(LDValue val);
LD_EXPORT(unsigned int)
LDValue_Count(LDValue val);

/**
* Serializes the LDValue to a JSON value. The returning string should be
* freed with @ref LDMemory_FreeString.
*
* Please note that numbers are serialized using scientific notation;
* for example the number 17 would be serialized as '1.7E1'.
*
* @param val Target LDValue. Must not be NULL.
* @return A string containing the JSON representation of the LDValue. The
* string should be freed with @ref LDMemory_FreeString.
*
*/
LD_EXPORT(char*)
LDValue_SerializeJSON(LDValue val);

/**
* Obtain iterator over an array-type @ref LDValue, otherwise NULL.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>
#include <tl/expected.hpp>

#include <boost/core/ignore_unused.hpp>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>
#include <launchdarkly/value.hpp>

#include <boost/json/fwd.hpp>
Expand Down
3 changes: 3 additions & 0 deletions libs/common/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ file(GLOB HEADER_LIST CONFIGURE_DEPENDS
"${LaunchDarklyCommonSdk_SOURCE_DIR}/include/launchdarkly/data/*.hpp"
"${LaunchDarklyCommonSdk_SOURCE_DIR}/include/launchdarkly/logging/*.hpp"
"${LaunchDarklyCommonSdk_SOURCE_DIR}/include/launchdarkly/data_sources/*.hpp"
"${LaunchDarklyCommonSdk_SOURCE_DIR}/include/launchdarkly/data_sources/persistence/*.hpp"
)

# Automatic library: static or dynamic based on user config.
Expand Down Expand Up @@ -53,6 +54,8 @@ add_library(${LIBNAME} OBJECT
bindings/c/memory_routines.cpp
bindings/c/data_source/error_info.cpp
bindings/c/logging/log_level.cpp
serialization/json_errors.cpp
serialization/json_value.cpp
log_level.cpp
config/persistence_builder.cpp
config/logging_builder.cpp
Expand Down
16 changes: 16 additions & 0 deletions libs/common/src/bindings/c/value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
#include <launchdarkly/detail/unreachable.hpp>
#include <launchdarkly/value.hpp>

#include <launchdarkly/detail/serialization/json_value.hpp>

#include <boost/json.hpp>

using launchdarkly::Value;

#define AS_VALUE(x) reinterpret_cast<Value*>(x)
Expand Down Expand Up @@ -95,6 +99,18 @@ LD_EXPORT(unsigned int) LDValue_Count(LDValue val) {
}
}

LD_EXPORT(char*) LDValue_SerializeJSON(LDValue val) {
LD_ASSERT_NOT_NULL(val);

auto const value = AS_VALUE(val);

auto boost_value = boost::json::value_from(*value);

std::string json = boost::json::serialize(boost_value);

return strdup(json.c_str());
}

LD_EXPORT(LDValue_ArrayIter) LDValue_ArrayIter_New(LDValue val) {
LD_ASSERT_NOT_NULL(val);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>

namespace launchdarkly {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#include <boost/json.hpp>
#include <launchdarkly/detail/unreachable.hpp>

Check notice on line 1 in libs/common/src/serialization/json_value.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

Run clang-format on libs/common/src/serialization/json_value.cpp

File libs/common/src/serialization/json_value.cpp does not conform to Custom style guidelines. (lines 1)
#include <launchdarkly/serialization/json_value.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>
#include <launchdarkly/detail/serialization/json_primitives.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>

#include <boost/json.hpp>

namespace launchdarkly {
// NOLINTBEGIN modernize-return-braced-init-list
Expand Down
55 changes: 55 additions & 0 deletions libs/common/tests/bindings/value_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include "launchdarkly/bindings/c/object_builder.h"
#include "launchdarkly/bindings/c/value.h"

#include "launchdarkly/bindings/c/memory_routines.h"

TEST(ValueCBindingTests, CanCreateNull) {
auto* ptr = LDValue_NewNull();

Expand Down Expand Up @@ -168,3 +170,56 @@ TEST(ValueCBindingTests, CanCreateObject) {

LDValue_Free(val_ptr);
}

// Helper to serialize an LDValue, automatically converts to
// std::string and frees the result using LDMemory_FreeString.
std::string serialize(LDValue const val) {
char* serialized = LDValue_SerializeJSON(val);
std::string result(serialized);
LDMemory_FreeString(serialized);
return result;
}

TEST(ValueCBindingTests, CanSerializeToJSON) {
auto* null_val = LDValue_NewNull();
auto* bool_val_true = LDValue_NewBool(true);
auto* bool_val_false = LDValue_NewBool(false);

auto* num_val = LDValue_NewNumber(17);
auto* float_val = LDValue_NewNumber(3.141);
auto* str_val = LDValue_NewString("Potato");

EXPECT_EQ("null", serialize(null_val));
EXPECT_EQ("true", serialize(bool_val_true));
EXPECT_EQ("false", serialize(bool_val_false));
EXPECT_EQ("1.7E1", serialize(num_val));
EXPECT_EQ("3.141E0", serialize(float_val));
EXPECT_EQ("\"Potato\"", serialize(str_val));

// Object builder is going to take care of freeing all the primitives
// (except for bool_val_false.)
auto* object_builder = LDObjectBuilder_New();
LDObjectBuilder_Add(object_builder, "null", null_val);
LDObjectBuilder_Add(object_builder, "bool", bool_val_true);
LDObjectBuilder_Add(object_builder, "num", num_val);
LDObjectBuilder_Add(object_builder, "float", float_val);
LDObjectBuilder_Add(object_builder, "str", str_val);

auto* obj_ptr = LDObjectBuilder_Build(object_builder);

EXPECT_EQ(
"{\"bool\":true,\"float\":3.141E0,\"null\":null,\"num\":1.7E1,\"str\":"
"\"Potato\"}",
serialize(obj_ptr));

LDValue_Free(obj_ptr);

// Array builder is going to take care of freeing bool_val_false.
auto* array_builder = LDArrayBuilder_New();
LDArrayBuilder_Add(array_builder, bool_val_false);
auto* array_ptr = LDArrayBuilder_Build(array_builder);

EXPECT_EQ("[false]", serialize(array_ptr));

LDValue_Free(array_ptr);
}
2 changes: 1 addition & 1 deletion libs/common/tests/value_test.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <gtest/gtest.h>

#include <launchdarkly/serialization/json_value.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>

#include <boost/json.hpp>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <launchdarkly/context.hpp>
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>

#include <boost/json/fwd.hpp>
#include <tl/expected.hpp>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <launchdarkly/attribute_reference.hpp>
#include <launchdarkly/data_model/context_aware_reference.hpp>
#include <launchdarkly/serialization/json_context_kind.hpp>
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>

#include <boost/json.hpp>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <launchdarkly/data_model/context_kind.hpp>
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>

#include <boost/json.hpp>
#include <tl/expected.hpp>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <launchdarkly/data/evaluation_reason.hpp>
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>

#include <boost/json/fwd.hpp>
#include <tl/expected.hpp>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <launchdarkly/data/evaluation_result.hpp>
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>

#include <boost/json/fwd.hpp>
#include <tl/expected.hpp>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <launchdarkly/data_model/flag.hpp>
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>

#include <boost/json/fwd.hpp>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#pragma once

#include <launchdarkly/data_model/item_descriptor.hpp>
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/serialization/json_primitives.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_primitives.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>

#include <boost/json.hpp>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <launchdarkly/data_model/rule_clause.hpp>
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>

#include <boost/json/fwd.hpp>
#include <tl/expected.hpp>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <launchdarkly/data_model/sdk_data_set.hpp>
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>

#include <boost/json/fwd.hpp>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <launchdarkly/data_model/segment.hpp>
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>

#include <boost/json/fwd.hpp>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#pragma once

#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/serialization/json_primitives.hpp>
#include <launchdarkly/detail/serialization/json_primitives.hpp>

#include <boost/core/ignore_unused.hpp>
#include <boost/json.hpp>
Expand Down
2 changes: 0 additions & 2 deletions libs/internal/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ add_library(${LIBNAME} OBJECT
serialization/events/json_events.cpp
serialization/json_attributes.cpp
serialization/json_context.cpp
serialization/json_errors.cpp
serialization/json_evaluation_reason.cpp
serialization/json_value.cpp
serialization/value_mapping.cpp
serialization/json_evaluation_result.cpp
serialization/json_sdk_data_set.cpp
Expand Down
2 changes: 1 addition & 1 deletion libs/internal/src/serialization/events/json_events.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <launchdarkly/serialization/events/json_events.hpp>

Check notice on line 1 in libs/internal/src/serialization/events/json_events.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

Run clang-format on libs/internal/src/serialization/events/json_events.cpp

File libs/internal/src/serialization/events/json_events.cpp does not conform to Custom style guidelines. (lines 1)
#include <launchdarkly/serialization/json_evaluation_reason.hpp>
#include <launchdarkly/serialization/json_value.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>

namespace launchdarkly::events {
void tag_invoke(boost::json::value_from_tag const& tag,
Expand Down
2 changes: 1 addition & 1 deletion libs/internal/src/serialization/json_attributes.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include <launchdarkly/serialization/json_attributes.hpp>

Check notice on line 1 in libs/internal/src/serialization/json_attributes.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

Run clang-format on libs/internal/src/serialization/json_attributes.cpp

File libs/internal/src/serialization/json_attributes.cpp does not conform to Custom style guidelines. (lines 1)
#include <launchdarkly/serialization/json_value.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>

#include <boost/core/ignore_unused.hpp>
#include <boost/json.hpp>
Expand Down
4 changes: 2 additions & 2 deletions libs/internal/src/serialization/json_context.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#include <launchdarkly/context_builder.hpp>
#include <launchdarkly/detail/serialization/json_primitives.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>
#include <launchdarkly/serialization/json_attributes.hpp>
#include <launchdarkly/serialization/json_context.hpp>
#include <launchdarkly/serialization/json_primitives.hpp>
#include <launchdarkly/serialization/json_value.hpp>

#include <boost/core/ignore_unused.hpp>
#include <boost/json.hpp>
Expand Down
2 changes: 1 addition & 1 deletion libs/internal/src/serialization/json_evaluation_reason.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>
#include <launchdarkly/serialization/json_evaluation_reason.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>

Expand Down
4 changes: 2 additions & 2 deletions libs/internal/src/serialization/json_evaluation_result.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include <launchdarkly/serialization/json_errors.hpp>
#include <launchdarkly/detail/serialization/json_errors.hpp>

Check notice on line 1 in libs/internal/src/serialization/json_evaluation_result.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

Run clang-format on libs/internal/src/serialization/json_evaluation_result.cpp

File libs/internal/src/serialization/json_evaluation_result.cpp does not conform to Custom style guidelines. (lines 1, 135, 152)
#include <launchdarkly/serialization/json_evaluation_reason.hpp>
#include <launchdarkly/serialization/json_evaluation_result.hpp>
#include <launchdarkly/serialization/json_value.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>

#include <boost/core/ignore_unused.hpp>
Expand Down
2 changes: 1 addition & 1 deletion libs/internal/src/serialization/json_flag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#include <launchdarkly/serialization/json_context_aware_reference.hpp>
#include <launchdarkly/serialization/json_flag.hpp>
#include <launchdarkly/serialization/json_rule_clause.hpp>
#include <launchdarkly/serialization/json_value.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>

namespace launchdarkly {
Expand Down
2 changes: 1 addition & 1 deletion libs/internal/src/serialization/json_primitives.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include <launchdarkly/serialization/json_primitives.hpp>
#include <launchdarkly/detail/serialization/json_primitives.hpp>

namespace launchdarkly {
tl::expected<std::optional<bool>, JsonError> tag_invoke(
Expand Down
2 changes: 1 addition & 1 deletion libs/internal/src/serialization/json_rule_clause.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#include <boost/json.hpp>
#include <launchdarkly/serialization/json_context_aware_reference.hpp>
#include <launchdarkly/serialization/json_rule_clause.hpp>
#include <launchdarkly/serialization/json_value.hpp>
#include <launchdarkly/detail/serialization/json_value.hpp>
#include <launchdarkly/serialization/value_mapping.hpp>

namespace launchdarkly {
Expand Down
Loading

0 comments on commit 90eb880

Please sign in to comment.