Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CXXCBC-442: Add support for the raw_json and raw_string transcoders #514

Merged
merged 1 commit into from Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion couchbase/codec/json_transcoder.hxx
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
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
@@ -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
@@ -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
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"));
}
}