Skip to content

Commit

Permalink
CXXCBC-442: Add support for raw_json and raw_string transcoders (#514)
Browse files Browse the repository at this point in the history
  • Loading branch information
DemetrisChr authored Feb 1, 2024
1 parent aa44e21 commit 0461e77
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 2 deletions.
2 changes: 1 addition & 1 deletion couchbase/codec/json_transcoder.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class json_transcoder
{
if (encoded.flags != 0 && !codec_flags::has_common_flags(encoded.flags, codec_flags::json_common_flags)) {
throw std::system_error(errc::common::decoding_failure,
"json_transcoder excepts document to have JSON common flags, flags=" + std::to_string(encoded.flags));
"json_transcoder expects document to have JSON common flags, flags=" + std::to_string(encoded.flags));
}

return Serializer::template deserialize<Document>(encoded.data);
Expand Down
3 changes: 2 additions & 1 deletion couchbase/codec/raw_binary_transcoder.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <couchbase/codec/encoded_value.hxx>
#include <couchbase/codec/transcoder_traits.hxx>
#include <couchbase/error_codes.hxx>

#include <type_traits>

namespace couchbase::codec
Expand All @@ -40,7 +41,7 @@ class raw_binary_transcoder
{
if (!codec_flags::has_common_flags(encoded.flags, codec_flags::binary_common_flags)) {
throw std::system_error(errc::common::decoding_failure,
"raw_binary_transcoder excepts document to have BINARY common flags, flags=" +
"raw_binary_transcoder expects document to have BINARY common flags, flags=" +
std::to_string(encoded.flags));
}

Expand Down
78 changes: 78 additions & 0 deletions couchbase/codec/raw_json_transcoder.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright 2024. Couchbase, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <couchbase/codec/codec_flags.hxx>
#include <couchbase/codec/encoded_value.hxx>
#include <couchbase/codec/transcoder_traits.hxx>
#include <couchbase/error_codes.hxx>

#include <cstddef>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>

namespace couchbase
{
#ifndef COUCHBASE_CXX_CLIENT_DOXYGEN
namespace core::utils
{
std::vector<std::byte>
to_binary(std::string_view value) noexcept;
} // namespace core::utils
#endif

namespace codec
{
class raw_json_transcoder
{
public:
template<typename Document, std::enable_if_t<std::is_same_v<Document, std::string> || std::is_same_v<Document, binary>, bool> = true>
static auto encode(const Document& document) -> encoded_value
{
if constexpr (std::is_same_v<Document, std::string>) {
return { core::utils::to_binary(document), codec_flags::json_common_flags };
} else {
return { std::move(document), codec_flags::json_common_flags };
}
}

template<typename Document, std::enable_if_t<std::is_same_v<Document, std::string> || std::is_same_v<Document, binary>, bool> = true>
static auto decode(const encoded_value& encoded) -> Document
{
if (!codec_flags::has_common_flags(encoded.flags, codec_flags::json_common_flags)) {
throw std::system_error(errc::common::decoding_failure,
"raw_json_transcoder expects document to have JSON common flags, flags=" +
std::to_string(encoded.flags));
}
if constexpr (std::is_same_v<Document, std::string>) {
return std::string{ reinterpret_cast<const char*>(encoded.data.data()), encoded.data.size() };
} else {
return encoded.data;
}
}
};

#ifndef COUCHBASE_CXX_CLIENT_DOXYGEN
template<>
struct is_transcoder<raw_json_transcoder> : public std::true_type {
};
#endif
} // namespace codec
} // namespace couchbase
72 changes: 72 additions & 0 deletions couchbase/codec/raw_string_transcoder.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright 2024. Couchbase, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <couchbase/codec/codec_flags.hxx>
#include <couchbase/codec/encoded_value.hxx>
#include <couchbase/codec/transcoder_traits.hxx>
#include <couchbase/error_codes.hxx>

#include <cstddef>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>

namespace couchbase
{
#ifndef COUCHBASE_CXX_CLIENT_DOXYGEN
namespace core::utils
{
std::vector<std::byte>
to_binary(std::string_view value) noexcept;
} // namespace core::utils
#endif

namespace codec
{
class raw_string_transcoder
{
public:
using document_type = std::string;

static auto encode(document_type document) -> encoded_value
{
return { core::utils::to_binary(document), codec_flags::string_common_flags };
}

template<typename Document = document_type, std::enable_if_t<std::is_same_v<Document, document_type>, bool> = true>
static auto decode(const encoded_value& encoded) -> Document
{
if (!codec_flags::has_common_flags(encoded.flags, codec_flags::string_common_flags)) {
throw std::system_error(errc::common::decoding_failure,
"raw_string_transcoder expects document to have STRING common flags, flags=" +
std::to_string(encoded.flags));
}

return std::string{ reinterpret_cast<const char*>(encoded.data.data()), encoded.data.size() };
}
};

#ifndef COUCHBASE_CXX_CLIENT_DOXYGEN
template<>
struct is_transcoder<raw_string_transcoder> : public std::true_type {
};
#endif
} // namespace codec
} // namespace couchbase
120 changes: 120 additions & 0 deletions test/test_integration_transcoders.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@

#include <couchbase/cluster.hxx>
#include <couchbase/codec/raw_binary_transcoder.hxx>
#include <couchbase/codec/raw_json_transcoder.hxx>
#include <couchbase/codec/raw_string_transcoder.hxx>
#include <couchbase/codec/tao_json_serializer.hxx>

#include <tao/json/contrib/vector_traits.hpp>

Expand Down Expand Up @@ -594,3 +597,120 @@ TEST_CASE("integration: subdoc with public API", "[integration]")
REQUIRE(resp.content_as<std::string>(couchbase::subdoc::lookup_in_macro::cas) == fmt::format("0x{:016x}", cas.value()));
}
}

TEST_CASE("integration: upsert with raw json transcoder, get with json and raw json transcoders", "[integration]")
{
test::utils::integration_test_guard integration;

test::utils::open_bucket(integration.cluster, integration.ctx.bucket);

auto collection = couchbase::cluster(integration.cluster)
.bucket(integration.ctx.bucket)
.scope(couchbase::scope::default_name)
.collection(couchbase::collection::default_name);
auto id = test::utils::uniq_id("foo");
profile albert{ "this_guy_again", "Albert Einstein", 1879 };
auto data = couchbase::codec::tao_json_serializer::serialize(albert);

// Upsert with raw_json_transcoder
{
auto [ctx, resp] = collection.upsert<couchbase::codec::raw_json_transcoder>(id, data, {}).get();
REQUIRE_SUCCESS(ctx.ec());
REQUIRE_FALSE(resp.cas().empty());
REQUIRE(resp.mutation_token().has_value());
}

// Get with default_json_transcoder
{
auto [ctx, resp] = collection.get(id, {}).get();
REQUIRE_SUCCESS(ctx.ec());
REQUIRE_FALSE(resp.cas().empty());
REQUIRE(resp.content_as<profile>() == albert);
}

// Get with raw_json_transcoder
{
auto [ctx, resp] = collection.get(id, {}).get();
REQUIRE_SUCCESS(ctx.ec());
REQUIRE_FALSE(resp.cas().empty());
REQUIRE(resp.content_as<couchbase::codec::binary, couchbase::codec::raw_json_transcoder>() == data);
}
}

TEST_CASE("integration: upsert and get string with raw json transcoder", "[integration]")
{
test::utils::integration_test_guard integration;

test::utils::open_bucket(integration.cluster, integration.ctx.bucket);

auto collection = couchbase::cluster(integration.cluster)
.bucket(integration.ctx.bucket)
.scope(couchbase::scope::default_name)
.collection(couchbase::collection::default_name);
auto id = test::utils::uniq_id("foo");
std::string data{ R"({"foo": "bar"})" };

// Upsert with raw_json_transcoder
{
auto [ctx, resp] = collection.upsert<couchbase::codec::raw_json_transcoder>(id, data, {}).get();
REQUIRE_SUCCESS(ctx.ec());
REQUIRE_FALSE(resp.cas().empty());
REQUIRE(resp.mutation_token().has_value());
}

// Get with default_json_transcoder
{
auto [ctx, resp] = collection.get(id, {}).get();
REQUIRE_SUCCESS(ctx.ec());
REQUIRE_FALSE(resp.cas().empty());
REQUIRE(resp.content_as<tao::json::value>() == tao::json::value{ { "foo", "bar" } });
}

// Get with raw_json_transcoder
{
auto [ctx, resp] = collection.get(id, {}).get();
REQUIRE_SUCCESS(ctx.ec());
REQUIRE_FALSE(resp.cas().empty());
REQUIRE(resp.content_as<std::string, couchbase::codec::raw_json_transcoder>() == data);
REQUIRE(resp.content_as<couchbase::codec::binary, couchbase::codec::raw_json_transcoder>() ==
couchbase::core::utils::to_binary(data));
}
}

TEST_CASE("integration: upsert and get with raw string transcoder, attempt to get with json transcoder", "[integration]")
{
test::utils::integration_test_guard integration;

test::utils::open_bucket(integration.cluster, integration.ctx.bucket);

auto collection = couchbase::cluster(integration.cluster)
.bucket(integration.ctx.bucket)
.scope(couchbase::scope::default_name)
.collection(couchbase::collection::default_name);
auto id = test::utils::uniq_id("foo");
std::string document{ "lorem ipsum dolor sit amet" };

// Upsert with raw_string_transcoder
{
auto [ctx, resp] = collection.upsert<couchbase::codec::raw_string_transcoder>(id, document, {}).get();
REQUIRE_SUCCESS(ctx.ec());
REQUIRE_FALSE(resp.cas().empty());
REQUIRE(resp.mutation_token().has_value());
}

// Get with raw_string_transcoder
{
auto [ctx, resp] = collection.get(id, {}).get();
REQUIRE_SUCCESS(ctx.ec());
REQUIRE_FALSE(resp.cas().empty());
REQUIRE(resp.content_as<std::string, couchbase::codec::raw_string_transcoder>() == document);
}

// Get with default_json_transcoder
{
auto [ctx, resp] = collection.get(id, {}).get();
REQUIRE_SUCCESS(ctx.ec());
REQUIRE_FALSE(resp.cas().empty());
REQUIRE_THROWS_WITH(resp.content_as<std::string>(), ContainsSubstring("document to have JSON common flags"));
}
}

0 comments on commit 0461e77

Please sign in to comment.