152 lines
4.4 KiB
C++
152 lines
4.4 KiB
C++
/*
|
|
* Copyright (c) 2023, Alibaba Group Holding Limited;
|
|
*
|
|
* 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 <cstdint>
|
|
#include <optional>
|
|
|
|
namespace struct_pack {
|
|
/*!
|
|
* \ingroup struct_pack
|
|
* \brief similar to std::optional<T>.
|
|
* However, its semantics are forward compatible field.
|
|
*
|
|
* For example,
|
|
*
|
|
* ```cpp
|
|
* struct person_v1 {
|
|
* int age;
|
|
* std::string name;
|
|
* };
|
|
*
|
|
* struct person_v2 {
|
|
* int age;
|
|
* struct_pack::compatible<bool, 20210101> id; // version number is 20210101
|
|
* struct_pack::compatible<double, 20210101> salary;
|
|
* std::string name;
|
|
* };
|
|
*
|
|
* struct person_v3 {
|
|
* int age;
|
|
* struct_pack::compatible<std::string, 20210302> nickname; // new version
|
|
* number is bigger than 20210101 struct_pack::compatible<bool, 20210101> id;
|
|
* struct_pack::compatible<double, 20210101> salary;
|
|
* std::string name;
|
|
* };
|
|
* ```
|
|
*
|
|
* `struct_pack::compatible<T, version_number>` can be null, thus ensuring
|
|
* forward and backward compatibility.
|
|
*
|
|
* For example, serialize person_v2, and then deserialize it according to
|
|
* person_v1, the extra fields will be directly ignored during deserialization.
|
|
* If person_v1 is serialized and then deserialized according to person_v2, the
|
|
* compatibale fields in person_v2 are all null values.
|
|
*
|
|
* The default value of version_number in `struct_pack::compatible<T,
|
|
* version_number>` is 0.
|
|
*
|
|
* ```cpp
|
|
* person_v2 p2{20, "tom", 114.1, "tom"};
|
|
* auto buf = struct_pack::serialize(p2);
|
|
*
|
|
* person_v1 p1;
|
|
* // deserialize person_v2 as person_v1
|
|
* auto ec = struct_pack::deserialize_to(p1, buf.data(), buf.size());
|
|
* CHECK(ec == std::errc{});
|
|
*
|
|
* auto buf1 = struct_pack::serialize(p1);
|
|
* person_v2 p3;
|
|
* // deserialize person_v1 as person_v2
|
|
* auto ec = struct_pack::deserialize_to(p3, buf1.data(), buf1.size());
|
|
* CHECK(ec == std::errc{});
|
|
* ```
|
|
*
|
|
* When you update your struct:
|
|
* 1. The new compatible field's version number should bigger than last changed.
|
|
* 2. Don't remove or change any old field.
|
|
*
|
|
* For example,
|
|
*
|
|
* ```cpp
|
|
* struct loginRequest_V1
|
|
* {
|
|
* string user_name;
|
|
* string pass_word;
|
|
* struct_pack::compatible<string> verification_code;
|
|
* };
|
|
*
|
|
* struct loginRequest_V2
|
|
* {
|
|
* string user_name;
|
|
* string pass_word;
|
|
* struct_pack::compatible<network::ip> ip_address;
|
|
* };
|
|
*
|
|
* auto data=struct_pack::serialize(loginRequest_V1{});
|
|
* loginRequest_V2 req;
|
|
* struct_pack::deserialize_to(req, data); // undefined behavior!
|
|
*
|
|
* ```
|
|
*
|
|
* ```cpp
|
|
* struct loginRequest_V1
|
|
* {
|
|
* string user_name;
|
|
* string pass_word;
|
|
* struct_pack::compatible<string, 20210101> verification_code;
|
|
* };
|
|
*
|
|
* struct loginRequest_V2
|
|
* {
|
|
* string user_name;
|
|
* string pass_word;
|
|
* struct_pack::compatible<string, 20210101> verification_code;
|
|
* struct_pack::compatible<network::ip, 20000101> ip_address;
|
|
* };
|
|
*
|
|
* auto data=struct_pack::serialize(loginRequest_V1{});
|
|
* loginRequest_V2 req;
|
|
* struct_pack::deserialize_to(req, data); // undefined behavior!
|
|
*
|
|
* The value of compatible can be empty.
|
|
*
|
|
* TODO: add doc
|
|
*
|
|
* @tparam T field type
|
|
*/
|
|
|
|
template <typename T, uint64_t version>
|
|
struct compatible : public std::optional<T> {
|
|
constexpr compatible() = default;
|
|
constexpr compatible(const compatible &other) = default;
|
|
constexpr compatible(compatible &&other) = default;
|
|
constexpr compatible(std::optional<T> &&other)
|
|
: std::optional<T>(std::move(other)){};
|
|
constexpr compatible(const std::optional<T> &other)
|
|
: std::optional<T>(other){};
|
|
constexpr compatible &operator=(const compatible &other) = default;
|
|
constexpr compatible &operator=(compatible &&other) = default;
|
|
using std::optional<T>::optional;
|
|
friend bool operator==(const compatible<T, version> &self,
|
|
const compatible<T, version> &other) {
|
|
return static_cast<bool>(self) == static_cast<bool>(other) &&
|
|
(!self || *self == *other);
|
|
}
|
|
static constexpr uint64_t version_number = version;
|
|
};
|
|
} // namespace struct_pack
|