Skip to content

Commit

Permalink
feat chaotic: more stuff for openapi parameters
Browse files Browse the repository at this point in the history
commit_hash:33b332173cadcb98aee398cc4d24484abff1dbf5
  • Loading branch information
segoon committed Nov 28, 2024
1 parent 12e33cf commit 8238116
Show file tree
Hide file tree
Showing 8 changed files with 398 additions and 59 deletions.
1 change: 1 addition & 0 deletions .mapping.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@
"chaotic/src/chaotic/io/userver/utils/datetime/time_point_tz.cpp":"taxi/uservices/userver/chaotic/src/chaotic/io/userver/utils/datetime/time_point_tz.cpp",
"chaotic/src/chaotic/io/userver/utils/datetime/time_point_tz_iso_basic.cpp":"taxi/uservices/userver/chaotic/src/chaotic/io/userver/utils/datetime/time_point_tz_iso_basic.cpp",
"chaotic/src/chaotic/openapi/parameters.cpp":"taxi/uservices/userver/chaotic/src/chaotic/openapi/parameters.cpp",
"chaotic/src/chaotic/openapi/parameters_read.cpp":"taxi/uservices/userver/chaotic/src/chaotic/openapi/parameters_read.cpp",
"chaotic/src/chaotic/openapi/parameters_read_test.cpp":"taxi/uservices/userver/chaotic/src/chaotic/openapi/parameters_read_test.cpp",
"chaotic/src/chaotic/openapi/parameters_write.cpp":"taxi/uservices/userver/chaotic/src/chaotic/openapi/parameters_write.cpp",
"chaotic/src/chaotic/openapi/parameters_write_test.cpp":"taxi/uservices/userver/chaotic/src/chaotic/openapi/parameters_write_test.cpp",
Expand Down
46 changes: 33 additions & 13 deletions chaotic/include/userver/chaotic/openapi/parameters.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include <string_view>
#include <string>
#include <vector>

USERVER_NAMESPACE_BEGIN
Expand All @@ -17,33 +17,53 @@ enum class In {

using Name = const char* const;

enum class ParameterType {
kTrivial,
kArray,
// kObject,
template <typename T>
inline constexpr bool kIsTrivialRawType = std::is_integral_v<T>;

template <>
inline constexpr bool kIsTrivialRawType<bool> = true;

template <>
inline constexpr bool kIsTrivialRawType<std::string> = true;

template <>
inline constexpr bool kIsTrivialRawType<double> = true;

template <typename RawType_, typename UserType_>
struct TrivialParameterBase {
using RawType = RawType_;
using UserType = UserType_;

static_assert(kIsTrivialRawType<RawType>);
};

template <In In_, const Name& Name_, typename RawType_, typename UserType_ = RawType_>
struct TrivialParameter {
static constexpr auto kIn = In_;
static constexpr auto& kName = Name_;

using RawType = RawType_;
using UserType = UserType_;
static constexpr ParameterType Type = ParameterType::kTrivial;
using Base = TrivialParameterBase<RawType_, UserType_>;

static_assert(kIn != In::kQueryExplode);
};

template <In In_, const Name& Name_, char Delimiter_, typename RawItemType_, typename UserItemType_ = RawItemType_>
struct ArrayParameter {
static constexpr auto kIn = In_;
static constexpr auto& kName = Name_;
template <In In_, char Delimiter_, typename RawItemType_, typename UserItemType_ = RawItemType_>
struct ArrayParameterBase {
static constexpr char kDelimiter = Delimiter_;
static constexpr auto kIn = In_;

using RawType = std::vector<std::string>;
using RawItemType = RawItemType_;
using UserType = std::vector<UserItemType_>;
using UserItemType = UserItemType_;
static constexpr ParameterType Type = ParameterType::kArray;
};

template <In In_, const Name& Name_, char Delimiter_, typename RawItemType_, typename UserItemType_ = RawItemType_>
struct ArrayParameter {
static constexpr auto& kName = Name_;
static constexpr auto kIn = In_;

using Base = ArrayParameterBase<In_, Delimiter_, RawItemType_, UserItemType_>;
};

} // namespace chaotic::openapi
Expand Down
86 changes: 81 additions & 5 deletions chaotic/include/userver/chaotic/openapi/parameters_read.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
#pragma once

#include <userver/chaotic/convert.hpp>
#include <userver/chaotic/openapi/parameters.hpp>
#include <userver/utils/function_ref.hpp>

#include <userver/server/http/http_request.hpp>
#include <userver/utils/from_string.hpp>

USERVER_NAMESPACE_BEGIN

namespace chaotic::openapi {

/*
* All parameters are parsed according to the following pipeline:
*
* [easy] -> str -> raw -> user
*
* Where:
* - [easy] is curl easy handler
* - str is `string` or `vector of strings`
* - raw is one of JSON Schema types (e.g. boolean, integer or string)
* - user is a type shown to the user (x-usrv-cpp-type value or same as raw)
*
*/

template <In In_>
auto GetParameter(std::string_view name, const server::http::HttpRequest& source) {
if constexpr (In_ == In::kPath) {
Expand All @@ -24,15 +40,75 @@ auto GetParameter(std::string_view name, const server::http::HttpRequest& source
}
}

namespace parse {
template <typename T>
struct To {};
} // namespace parse

template <typename T>
auto ParseParameter(const T& raw_value) {
return raw_value;
std::enable_if_t<std::is_integral_v<T>, T> FromStr(std::string&& s, parse::To<T>) {
return utils::FromString<T>(s);
}

std::string FromStr(std::string&& str_value, parse::To<std::string>);

bool FromStr(std::string&& str_value, parse::To<bool>);

double FromStr(std::string&& str_value, parse::To<double>);

template <typename Parameter>
struct ParseParameter {
static std::string Parse(typename Parameter::RawType&& t) {
static_assert(sizeof(t) && false, "Cannot find `ParseParameter`");
return {};
}
};

template <typename RawType, typename UserType>
struct ParseParameter<TrivialParameterBase<RawType, UserType>> {
static UserType Parse(std::string&& str_value) {
auto raw_value = openapi::FromStr(std::move(str_value), parse::To<RawType>());
return Convert(std::move(raw_value), convert::To<UserType>());
}
};

namespace impl {

void SplitByDelimiter(std::string_view str, char delimiter, utils::function_ref<void(std::string)>);

}

template <In In_, char Delimiter, typename RawType, typename UserType>
struct ParseParameter<ArrayParameterBase<In_, Delimiter, RawType, UserType>> {
static auto Parse(std::string&& str_value) {
openapi::ParseParameter<TrivialParameterBase<RawType, UserType>> parser;

std::vector<UserType> result;
impl::SplitByDelimiter(str_value, Delimiter, [&result, &parser](std::string str) {
result.emplace_back(parser.Parse(std::move(str)));
});
return result;
}
};

template <char Delimiter, typename RawType, typename UserType>
struct ParseParameter<ArrayParameterBase<In::kQueryExplode, Delimiter, RawType, UserType>> {
static auto Parse(std::vector<std::string>&& str_value) {
openapi::ParseParameter<TrivialParameterBase<RawType, UserType>> parser;

std::vector<UserType> result;
result.reserve(str_value.size());
for (auto&& str_item : str_value) {
result.emplace_back(parser.Parse(std::move(str_item)));
}
return result;
}
};

template <typename Parameter>
auto ReadParameter(const server::http::HttpRequest& source) {
auto raw_value = USERVER_NAMESPACE::chaotic::openapi::GetParameter<Parameter::kIn>(Parameter::kName, source);
return USERVER_NAMESPACE::chaotic::openapi::ParseParameter(raw_value);
typename Parameter::Base::UserType ReadParameter(const server::http::HttpRequest& source) {
auto str_or_array_value = openapi::GetParameter<Parameter::kIn>(Parameter::kName, source);
return openapi::ParseParameter<typename Parameter::Base>::Parse(std::move(str_or_array_value));
}

} // namespace chaotic::openapi
Expand Down
113 changes: 90 additions & 23 deletions chaotic/include/userver/chaotic/openapi/parameters_write.hpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
#pragma once

#include <fmt/args.h>
#include <fmt/ranges.h>

#include <userver/chaotic/convert.hpp>
#include <userver/chaotic/openapi/parameters.hpp>
#include <userver/clients/http/request.hpp>
#include <userver/http/url.hpp>
#include <userver/utils/impl/projecting_view.hpp>
#include <userver/utils/meta.hpp>
#include <userver/utils/text_light.hpp>

USERVER_NAMESPACE_BEGIN

namespace chaotic::openapi {

/*
* All parameters are serialized according to the following pipeline:
*
* user -> raw -> str -> [easy]
*
* Where:
* - user is a type shown to the user (x-usrv-cpp-type value or same as raw)
* - raw is one of JSON Schema types (e.g. boolean, integer or string)
* - str is `string` or `vector of strings`
* - [easy] is curl easy handler
*
*/

/// @brief curl::easy abstraction
class ParameterSinkBase {
public:
Expand Down Expand Up @@ -48,52 +65,102 @@ class ParameterSinkHttpClient final : public ParameterSinkBase {

void ValidatePathVariableValue(std::string_view name, std::string_view value);

template <In In, typename RawType>
void SetParameter(Name& name, RawType&& raw_value, ParameterSinkBase& dest) {
template <In In, typename StrType>
void SetParameter(Name& name, StrType&& str_value, ParameterSinkBase& dest) {
static_assert(std::is_same_v<StrType, std::string> || std::is_same_v<StrType, std::vector<std::string>>);

if constexpr (In == In::kPath) {
USERVER_NAMESPACE::chaotic::openapi::ValidatePathVariableValue(name, raw_value);
dest.SetPath(name, std::forward<RawType>(raw_value));
USERVER_NAMESPACE::chaotic::openapi::ValidatePathVariableValue(name, str_value);
dest.SetPath(name, std::forward<StrType>(str_value));
} else if constexpr (In == In::kCookie) {
dest.SetCookie(name, std::forward<RawType>(raw_value));
dest.SetCookie(name, std::forward<StrType>(str_value));
} else if constexpr (In == In::kHeader) {
dest.SetHeader(name, std::forward<RawType>(raw_value));
dest.SetHeader(name, std::forward<StrType>(str_value));
} else if constexpr (In == In::kQuery) {
dest.SetQuery(name, std::forward<RawType>(raw_value));
dest.SetQuery(name, std::forward<StrType>(str_value));
} else {
static_assert(In == In::kQueryExplode, "Unknown 'In'");
dest.SetMultiQuery(name, std::forward<RawType>(raw_value));
dest.SetMultiQuery(name, std::forward<StrType>(str_value));
}
}

template <typename T>
std::enable_if_t<std::is_integral_v<T>, std::string> ToRawParameter(T s) {
std::enable_if_t<std::is_integral_v<T>, std::string> ToStrParameter(T s) noexcept {
return std::to_string(s);
}

template <typename T>
std::enable_if_t<std::is_enum_v<T>, std::string> ToRawParameter(T s) {
std::enable_if_t<std::is_enum_v<T>, std::string> ToStrParameter(T s) noexcept {
return ToString(s);
}

std::string ToRawParameter(std::string&& s);
std::string ToStrParameter(bool value) noexcept;

std::vector<std::string> ToRawParameter(std::vector<std::string>&& s);
std::string ToStrParameter(double value) noexcept;

template <typename Parameter>
auto SerializeParameter(const typename Parameter::UserType& value) {
if constexpr (Parameter::Type == ParameterType::kTrivial) {
// TODO: Convert()
return USERVER_NAMESPACE::chaotic::openapi::ToRawParameter(typename Parameter::UserType{value});
} else if constexpr (Parameter::Type == ParameterType::kArray) {
// TODO: Convert()
return USERVER_NAMESPACE::utils::text::Join(value, std::string(1, Parameter::kDelimiter));
std::string ToStrParameter(std::string&& s) noexcept;

std::vector<std::string> ToStrParameter(std::vector<std::string>&& s) noexcept;

template <typename T>
std::enable_if_t<std::is_same_v<meta::RangeValueType<T>, std::string>, std::vector<std::string>> ToStrParameter(
T&& collection
) noexcept {
std::vector<std::string> result;
result.reserve(collection.size());
for (auto&& item : collection) {
result.emplace_back(ToStrParameter(item));
}
return result;
}

template <typename Parameter>
void WriteParameter(const typename Parameter::UserType& value, ParameterSinkBase& dest) {
auto raw_value = USERVER_NAMESPACE::chaotic::openapi::SerializeParameter<Parameter>(value);
USERVER_NAMESPACE::chaotic::openapi::SetParameter<Parameter::kIn>(Parameter::kName, std::move(raw_value), dest);
struct SerializeParameter {
static std::string Serialize(const typename Parameter::UserType&) {
static_assert(sizeof(Parameter) && false, "No SerializeParameter specialization found for `Parameter`");
return {};
}
};

template <typename T>
struct SerializeParameter<TrivialParameterBase<T, T>> {
static auto Serialize(const T& value) { return ToStrParameter(T{value}); }
};

template <typename RawType, typename UserType>
struct SerializeParameter<TrivialParameterBase<RawType, UserType>> {
static auto Serialize(const UserType& value) { return ToStrParameter(Convert(value, convert::To<RawType>{})); }
};

template <char Delimiter, typename T, typename U>
struct SerializeParameter<ArrayParameterBase<In::kQueryExplode, Delimiter, T, U>> {
static std::vector<std::string> Serialize(const std::vector<U>& collection) {
// Note: ignore Delimiter
std::vector<std::string> result;
result.reserve(collection.size());
for (const auto& item : collection) {
result.emplace_back(SerializeParameter<TrivialParameterBase<T, U>>().Serialize(item));
}
return result;
}
};

template <In In_, char Delimiter, typename RawType, typename UserType>
struct SerializeParameter<ArrayParameterBase<In_, Delimiter, RawType, UserType>> {
static_assert(In_ != In::kQueryExplode);

std::string operator()(const UserType& item) const { return ToStrParameter(Convert(item, convert::To<RawType>{})); }

static std::string Serialize(const std::vector<UserType>& value) {
using ProjectingView = utils::impl::ProjectingView<const std::vector<UserType>, SerializeParameter>;
return fmt::to_string(fmt::join(ProjectingView{value, SerializeParameter{}}, std::string(1, Delimiter)));
}
};

template <typename Parameter>
void WriteParameter(const typename Parameter::Base::UserType& value, ParameterSinkBase& dest) {
auto str_value = openapi::SerializeParameter<typename Parameter::Base>::Serialize(value);
openapi::SetParameter<Parameter::kIn>(Parameter::kName, std::move(str_value), dest);
}

} // namespace chaotic::openapi
Expand Down
37 changes: 37 additions & 0 deletions chaotic/src/chaotic/openapi/parameters_read.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <userver/chaotic/openapi/parameters_read.hpp>

#include <stdexcept>

#include <boost/algorithm/string/split.hpp>

USERVER_NAMESPACE_BEGIN

namespace chaotic::openapi {

namespace impl {

void SplitByDelimiter(std::string_view str, char delimiter, utils::function_ref<void(std::string)> func) {
for (auto it = boost::algorithm::make_split_iterator(str, boost::token_finder([delimiter](char c) {
return c == delimiter;
}));
it != decltype(it){};
++it) {
func(std::string{it->begin(), it->end()});
}
}

} // namespace impl

std::string FromStr(std::string&& str_value, parse::To<std::string>) { return std::move(str_value); }

bool FromStr(std::string&& str_value, parse::To<bool>) {
if (str_value == "true") return true;
if (str_value == "false") return false;
throw std::runtime_error("Unknown bool value: " + str_value);
}

double FromStr(std::string&& str_value, parse::To<double>) { return utils::FromString<double>(str_value); }

} // namespace chaotic::openapi

USERVER_NAMESPACE_END
Loading

0 comments on commit 8238116

Please sign in to comment.