Skip to content

Commit 0461e77

Browse files
authored
CXXCBC-442: Add support for raw_json and raw_string transcoders (#514)
1 parent aa44e21 commit 0461e77

File tree

5 files changed

+273
-2
lines changed

5 files changed

+273
-2
lines changed

couchbase/codec/json_transcoder.hxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class json_transcoder
4040
{
4141
if (encoded.flags != 0 && !codec_flags::has_common_flags(encoded.flags, codec_flags::json_common_flags)) {
4242
throw std::system_error(errc::common::decoding_failure,
43-
"json_transcoder excepts document to have JSON common flags, flags=" + std::to_string(encoded.flags));
43+
"json_transcoder expects document to have JSON common flags, flags=" + std::to_string(encoded.flags));
4444
}
4545

4646
return Serializer::template deserialize<Document>(encoded.data);

couchbase/codec/raw_binary_transcoder.hxx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <couchbase/codec/encoded_value.hxx>
2222
#include <couchbase/codec/transcoder_traits.hxx>
2323
#include <couchbase/error_codes.hxx>
24+
2425
#include <type_traits>
2526

2627
namespace couchbase::codec
@@ -40,7 +41,7 @@ class raw_binary_transcoder
4041
{
4142
if (!codec_flags::has_common_flags(encoded.flags, codec_flags::binary_common_flags)) {
4243
throw std::system_error(errc::common::decoding_failure,
43-
"raw_binary_transcoder excepts document to have BINARY common flags, flags=" +
44+
"raw_binary_transcoder expects document to have BINARY common flags, flags=" +
4445
std::to_string(encoded.flags));
4546
}
4647

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2+
/*
3+
* Copyright 2024. Couchbase, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
#pragma once
19+
20+
#include <couchbase/codec/codec_flags.hxx>
21+
#include <couchbase/codec/encoded_value.hxx>
22+
#include <couchbase/codec/transcoder_traits.hxx>
23+
#include <couchbase/error_codes.hxx>
24+
25+
#include <cstddef>
26+
#include <string>
27+
#include <string_view>
28+
#include <type_traits>
29+
#include <vector>
30+
31+
namespace couchbase
32+
{
33+
#ifndef COUCHBASE_CXX_CLIENT_DOXYGEN
34+
namespace core::utils
35+
{
36+
std::vector<std::byte>
37+
to_binary(std::string_view value) noexcept;
38+
} // namespace core::utils
39+
#endif
40+
41+
namespace codec
42+
{
43+
class raw_json_transcoder
44+
{
45+
public:
46+
template<typename Document, std::enable_if_t<std::is_same_v<Document, std::string> || std::is_same_v<Document, binary>, bool> = true>
47+
static auto encode(const Document& document) -> encoded_value
48+
{
49+
if constexpr (std::is_same_v<Document, std::string>) {
50+
return { core::utils::to_binary(document), codec_flags::json_common_flags };
51+
} else {
52+
return { std::move(document), codec_flags::json_common_flags };
53+
}
54+
}
55+
56+
template<typename Document, std::enable_if_t<std::is_same_v<Document, std::string> || std::is_same_v<Document, binary>, bool> = true>
57+
static auto decode(const encoded_value& encoded) -> Document
58+
{
59+
if (!codec_flags::has_common_flags(encoded.flags, codec_flags::json_common_flags)) {
60+
throw std::system_error(errc::common::decoding_failure,
61+
"raw_json_transcoder expects document to have JSON common flags, flags=" +
62+
std::to_string(encoded.flags));
63+
}
64+
if constexpr (std::is_same_v<Document, std::string>) {
65+
return std::string{ reinterpret_cast<const char*>(encoded.data.data()), encoded.data.size() };
66+
} else {
67+
return encoded.data;
68+
}
69+
}
70+
};
71+
72+
#ifndef COUCHBASE_CXX_CLIENT_DOXYGEN
73+
template<>
74+
struct is_transcoder<raw_json_transcoder> : public std::true_type {
75+
};
76+
#endif
77+
} // namespace codec
78+
} // namespace couchbase
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2+
/*
3+
* Copyright 2024. Couchbase, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
#pragma once
19+
20+
#include <couchbase/codec/codec_flags.hxx>
21+
#include <couchbase/codec/encoded_value.hxx>
22+
#include <couchbase/codec/transcoder_traits.hxx>
23+
#include <couchbase/error_codes.hxx>
24+
25+
#include <cstddef>
26+
#include <string>
27+
#include <string_view>
28+
#include <type_traits>
29+
#include <vector>
30+
31+
namespace couchbase
32+
{
33+
#ifndef COUCHBASE_CXX_CLIENT_DOXYGEN
34+
namespace core::utils
35+
{
36+
std::vector<std::byte>
37+
to_binary(std::string_view value) noexcept;
38+
} // namespace core::utils
39+
#endif
40+
41+
namespace codec
42+
{
43+
class raw_string_transcoder
44+
{
45+
public:
46+
using document_type = std::string;
47+
48+
static auto encode(document_type document) -> encoded_value
49+
{
50+
return { core::utils::to_binary(document), codec_flags::string_common_flags };
51+
}
52+
53+
template<typename Document = document_type, std::enable_if_t<std::is_same_v<Document, document_type>, bool> = true>
54+
static auto decode(const encoded_value& encoded) -> Document
55+
{
56+
if (!codec_flags::has_common_flags(encoded.flags, codec_flags::string_common_flags)) {
57+
throw std::system_error(errc::common::decoding_failure,
58+
"raw_string_transcoder expects document to have STRING common flags, flags=" +
59+
std::to_string(encoded.flags));
60+
}
61+
62+
return std::string{ reinterpret_cast<const char*>(encoded.data.data()), encoded.data.size() };
63+
}
64+
};
65+
66+
#ifndef COUCHBASE_CXX_CLIENT_DOXYGEN
67+
template<>
68+
struct is_transcoder<raw_string_transcoder> : public std::true_type {
69+
};
70+
#endif
71+
} // namespace codec
72+
} // namespace couchbase

test/test_integration_transcoders.cxx

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323

2424
#include <couchbase/cluster.hxx>
2525
#include <couchbase/codec/raw_binary_transcoder.hxx>
26+
#include <couchbase/codec/raw_json_transcoder.hxx>
27+
#include <couchbase/codec/raw_string_transcoder.hxx>
28+
#include <couchbase/codec/tao_json_serializer.hxx>
2629

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

@@ -594,3 +597,120 @@ TEST_CASE("integration: subdoc with public API", "[integration]")
594597
REQUIRE(resp.content_as<std::string>(couchbase::subdoc::lookup_in_macro::cas) == fmt::format("0x{:016x}", cas.value()));
595598
}
596599
}
600+
601+
TEST_CASE("integration: upsert with raw json transcoder, get with json and raw json transcoders", "[integration]")
602+
{
603+
test::utils::integration_test_guard integration;
604+
605+
test::utils::open_bucket(integration.cluster, integration.ctx.bucket);
606+
607+
auto collection = couchbase::cluster(integration.cluster)
608+
.bucket(integration.ctx.bucket)
609+
.scope(couchbase::scope::default_name)
610+
.collection(couchbase::collection::default_name);
611+
auto id = test::utils::uniq_id("foo");
612+
profile albert{ "this_guy_again", "Albert Einstein", 1879 };
613+
auto data = couchbase::codec::tao_json_serializer::serialize(albert);
614+
615+
// Upsert with raw_json_transcoder
616+
{
617+
auto [ctx, resp] = collection.upsert<couchbase::codec::raw_json_transcoder>(id, data, {}).get();
618+
REQUIRE_SUCCESS(ctx.ec());
619+
REQUIRE_FALSE(resp.cas().empty());
620+
REQUIRE(resp.mutation_token().has_value());
621+
}
622+
623+
// Get with default_json_transcoder
624+
{
625+
auto [ctx, resp] = collection.get(id, {}).get();
626+
REQUIRE_SUCCESS(ctx.ec());
627+
REQUIRE_FALSE(resp.cas().empty());
628+
REQUIRE(resp.content_as<profile>() == albert);
629+
}
630+
631+
// Get with raw_json_transcoder
632+
{
633+
auto [ctx, resp] = collection.get(id, {}).get();
634+
REQUIRE_SUCCESS(ctx.ec());
635+
REQUIRE_FALSE(resp.cas().empty());
636+
REQUIRE(resp.content_as<couchbase::codec::binary, couchbase::codec::raw_json_transcoder>() == data);
637+
}
638+
}
639+
640+
TEST_CASE("integration: upsert and get string with raw json transcoder", "[integration]")
641+
{
642+
test::utils::integration_test_guard integration;
643+
644+
test::utils::open_bucket(integration.cluster, integration.ctx.bucket);
645+
646+
auto collection = couchbase::cluster(integration.cluster)
647+
.bucket(integration.ctx.bucket)
648+
.scope(couchbase::scope::default_name)
649+
.collection(couchbase::collection::default_name);
650+
auto id = test::utils::uniq_id("foo");
651+
std::string data{ R"({"foo": "bar"})" };
652+
653+
// Upsert with raw_json_transcoder
654+
{
655+
auto [ctx, resp] = collection.upsert<couchbase::codec::raw_json_transcoder>(id, data, {}).get();
656+
REQUIRE_SUCCESS(ctx.ec());
657+
REQUIRE_FALSE(resp.cas().empty());
658+
REQUIRE(resp.mutation_token().has_value());
659+
}
660+
661+
// Get with default_json_transcoder
662+
{
663+
auto [ctx, resp] = collection.get(id, {}).get();
664+
REQUIRE_SUCCESS(ctx.ec());
665+
REQUIRE_FALSE(resp.cas().empty());
666+
REQUIRE(resp.content_as<tao::json::value>() == tao::json::value{ { "foo", "bar" } });
667+
}
668+
669+
// Get with raw_json_transcoder
670+
{
671+
auto [ctx, resp] = collection.get(id, {}).get();
672+
REQUIRE_SUCCESS(ctx.ec());
673+
REQUIRE_FALSE(resp.cas().empty());
674+
REQUIRE(resp.content_as<std::string, couchbase::codec::raw_json_transcoder>() == data);
675+
REQUIRE(resp.content_as<couchbase::codec::binary, couchbase::codec::raw_json_transcoder>() ==
676+
couchbase::core::utils::to_binary(data));
677+
}
678+
}
679+
680+
TEST_CASE("integration: upsert and get with raw string transcoder, attempt to get with json transcoder", "[integration]")
681+
{
682+
test::utils::integration_test_guard integration;
683+
684+
test::utils::open_bucket(integration.cluster, integration.ctx.bucket);
685+
686+
auto collection = couchbase::cluster(integration.cluster)
687+
.bucket(integration.ctx.bucket)
688+
.scope(couchbase::scope::default_name)
689+
.collection(couchbase::collection::default_name);
690+
auto id = test::utils::uniq_id("foo");
691+
std::string document{ "lorem ipsum dolor sit amet" };
692+
693+
// Upsert with raw_string_transcoder
694+
{
695+
auto [ctx, resp] = collection.upsert<couchbase::codec::raw_string_transcoder>(id, document, {}).get();
696+
REQUIRE_SUCCESS(ctx.ec());
697+
REQUIRE_FALSE(resp.cas().empty());
698+
REQUIRE(resp.mutation_token().has_value());
699+
}
700+
701+
// Get with raw_string_transcoder
702+
{
703+
auto [ctx, resp] = collection.get(id, {}).get();
704+
REQUIRE_SUCCESS(ctx.ec());
705+
REQUIRE_FALSE(resp.cas().empty());
706+
REQUIRE(resp.content_as<std::string, couchbase::codec::raw_string_transcoder>() == document);
707+
}
708+
709+
// Get with default_json_transcoder
710+
{
711+
auto [ctx, resp] = collection.get(id, {}).get();
712+
REQUIRE_SUCCESS(ctx.ec());
713+
REQUIRE_FALSE(resp.cas().empty());
714+
REQUIRE_THROWS_WITH(resp.content_as<std::string>(), ContainsSubstring("document to have JSON common flags"));
715+
}
716+
}

0 commit comments

Comments
 (0)