yalantinglibs/include/ylt/struct_pack/compatible.hpp

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