draconisplusplus/include/rfl/json/to_schema.hpp
2024-06-08 14:10:59 -04:00

268 lines
11 KiB
C++

#ifndef RFL_JSON_TOSCHEMA_HPP_
#define RFL_JSON_TOSCHEMA_HPP_
#include <map>
#include <string>
#include <type_traits>
#include <variant>
#include <yyjson.h>
#include "../Literal.hpp"
#include "../Processors.hpp"
#include "../parsing/schema/Type.hpp"
#include "../parsing/schema/ValidationType.hpp"
#include "../parsing/schema/make.hpp"
#include "Reader.hpp"
#include "Writer.hpp"
#include "schema/JSONSchema.hpp"
#include "schema/Type.hpp"
#include "write.hpp"
namespace rfl::json {
inline schema::Type type_to_json_schema_type(
const parsing::schema::Type& _type);
inline bool is_optional(const parsing::schema::Type& _t) {
const auto handle = [](const auto& _v) -> bool {
using T = std::remove_cvref_t<decltype(_v)>;
return std::is_same<T, parsing::schema::Type::Optional>();
};
return std::visit(handle, _t.variant_);
}
inline std::string numeric_type_to_string(
const parsing::schema::Type& _type) {
const auto handle_variant = [](const auto& _t) -> std::string {
using T = std::remove_cvref_t<decltype(_t)>;
using Type = parsing::schema::Type;
if constexpr (std::is_same<T, Type::Int32>() ||
std::is_same<T, Type::Int64>() ||
std::is_same<T, Type::UInt32>() ||
std::is_same<T, Type::UInt64>() ||
std::is_same<T, Type::Integer>()) {
return schema::Type::Integer {}.type.str();
} else {
return schema::Type::Number {}.type.str();
}
};
return std::visit(handle_variant, _type.variant_);
}
inline schema::Type handle_validation_type(
const parsing::schema::Type& _type,
const parsing::schema::ValidationType& _validation_type) {
const auto handle_variant = [&](const auto& _v) -> schema::Type {
using T = std::remove_cvref_t<decltype(_v)>;
using ValidationType = parsing::schema::ValidationType;
if constexpr (std::is_same<T, ValidationType::AllOf>()) {
auto all_of = std::vector<schema::Type>();
for (const auto& t : _v.types_) {
all_of.emplace_back(handle_validation_type(_type, t));
}
return schema::Type {.value = schema::Type::AllOf {.allOf = all_of}};
} else if constexpr (std::is_same<T, ValidationType::AnyOf>()) {
auto any_of = std::vector<schema::Type>();
for (const auto& t : _v.types_) {
any_of.emplace_back(handle_validation_type(_type, t));
}
return schema::Type {.value = schema::Type::AnyOf {.anyOf = any_of}};
} else if constexpr (std::is_same<T, ValidationType::OneOf>()) {
auto one_of = std::vector<schema::Type>();
for (const auto& t : _v.types_) {
one_of.emplace_back(handle_validation_type(_type, t));
}
return schema::Type {.value = schema::Type::OneOf {.oneOf = one_of}};
} else if constexpr (std::is_same<T, ValidationType::Regex>()) {
return schema::Type {.value =
schema::Type::Regex {.pattern = _v.pattern_}};
} else if constexpr (std::is_same<T, ValidationType::Size>()) {
return type_to_json_schema_type(_type);
} else if constexpr (std::is_same<T,
ValidationType::ExclusiveMaximum>()) {
return schema::Type {.value = schema::Type::ExclusiveMaximum {
.exclusiveMaximum = _v.value_,
.type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T,
ValidationType::ExclusiveMinimum>()) {
return schema::Type {.value = schema::Type::ExclusiveMinimum {
.exclusiveMinimum = _v.value_,
.type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T, ValidationType::Maximum>()) {
return schema::Type {
.value = schema::Type::Maximum {
.maximum = _v.value_, .type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T, ValidationType::Minimum>()) {
return schema::Type {
.value = schema::Type::Minimum {
.minimum = _v.value_, .type = numeric_type_to_string(_type)}};
} else if constexpr (std::is_same<T, ValidationType::EqualTo>()) {
const auto maximum = schema::Type {
.value = schema::Type::Maximum {
.maximum = _v.value_, .type = numeric_type_to_string(_type)}};
const auto minimum = schema::Type {
.value = schema::Type::Minimum {
.minimum = _v.value_, .type = numeric_type_to_string(_type)}};
return schema::Type {
.value = schema::Type::AllOf {.allOf = {maximum, minimum}}};
} else if constexpr (std::is_same<T, ValidationType::NotEqualTo>()) {
const auto excl_maximum =
schema::Type {.value = schema::Type::ExclusiveMaximum {
.exclusiveMaximum = _v.value_,
.type = numeric_type_to_string(_type)}};
const auto excl_minimum =
schema::Type {.value = schema::Type::ExclusiveMinimum {
.exclusiveMinimum = _v.value_,
.type = numeric_type_to_string(_type)}};
return schema::Type {.value = schema::Type::AnyOf {
.anyOf = {excl_maximum, excl_minimum}}};
} else {
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
}
};
return std::visit(handle_variant, _validation_type.variant_);
}
inline schema::Type type_to_json_schema_type(
const parsing::schema::Type& _type) {
const auto handle_variant = [](const auto& _t) -> schema::Type {
using T = std::remove_cvref_t<decltype(_t)>;
using Type = parsing::schema::Type;
if constexpr (std::is_same<T, Type::Boolean>()) {
return schema::Type {.value = schema::Type::Boolean {}};
} else if constexpr (std::is_same<T, Type::Int32>() ||
std::is_same<T, Type::Int64>() ||
std::is_same<T, Type::UInt32>() ||
std::is_same<T, Type::UInt64>() ||
std::is_same<T, Type::Integer>()) {
return schema::Type {.value = schema::Type::Integer {}};
} else if constexpr (std::is_same<T, Type::Float>() ||
std::is_same<T, Type::Double>()) {
return schema::Type {.value = schema::Type::Number {}};
} else if constexpr (std::is_same<T, Type::String>()) {
return schema::Type {.value = schema::Type::String {}};
} else if constexpr (std::is_same<T, Type::AnyOf>()) {
auto any_of = std::vector<schema::Type>();
for (const auto& t : _t.types_) {
any_of.emplace_back(type_to_json_schema_type(t));
}
return schema::Type {.value = schema::Type::AnyOf {.anyOf = any_of}};
} else if constexpr (std::is_same<T, Type::Description>()) {
auto res = type_to_json_schema_type(*_t.type_);
const auto update_prediction = [&](auto _v) -> schema::Type {
_v.description = _t.description_;
return schema::Type {_v};
};
return std::visit(update_prediction, res.value);
} else if constexpr (std::is_same<T, Type::FixedSizeTypedArray>()) {
return schema::Type {.value = schema::Type::FixedSizeTypedArray {
.items = Ref<schema::Type>::make(
type_to_json_schema_type(*_t.type_)),
.minContains = _t.size_,
.maxContains = _t.size_}};
} else if constexpr (std::is_same<T, Type::Literal>()) {
return schema::Type {
.value = schema::Type::StringEnum {.values = _t.values_}};
} else if constexpr (std::is_same<T, Type::Object>()) {
auto properties = std::map<std::string, schema::Type>();
auto required = std::vector<std::string>();
for (const auto& [k, v] : _t.types_) {
properties[k] = type_to_json_schema_type(v);
if (!is_optional(v)) { required.push_back(k); }
}
return schema::Type {.value =
schema::Type::Object {.properties = properties,
.required = required}};
} else if constexpr (std::is_same<T, Type::Optional>()) {
return schema::Type {
.value = schema::Type::AnyOf {
.anyOf = {type_to_json_schema_type(*_t.type_),
schema::Type {schema::Type::Null {}}}}};
} else if constexpr (std::is_same<T, Type::Reference>()) {
return schema::Type {.value = schema::Type::Reference {
.ref = "#/definitions/" + _t.name_}};
} else if constexpr (std::is_same<T, Type::StringMap>()) {
return schema::Type {
.value = schema::Type::StringMap {
.additionalProperties = Ref<schema::Type>::make(
type_to_json_schema_type(*_t.value_type_))}};
} else if constexpr (std::is_same<T, Type::Tuple>()) {
auto items = std::vector<schema::Type>();
for (const auto& t : _t.types_) {
items.emplace_back(type_to_json_schema_type(t));
}
return schema::Type {.value =
schema::Type::Tuple {.prefixItems = items}};
} else if constexpr (std::is_same<T, Type::TypedArray>()) {
return schema::Type {.value = schema::Type::TypedArray {
.items = Ref<schema::Type>::make(
type_to_json_schema_type(*_t.type_))}};
} else if constexpr (std::is_same<T, Type::Validated>()) {
return handle_validation_type(*_t.type_, _t.validation_);
} else {
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
}
};
return std::visit(handle_variant, _type.variant_);
}
template <class T>
struct TypeHelper {};
template <class... Ts>
struct TypeHelper<std::variant<Ts...>> {
using JSONSchemaType = std::variant<schema::JSONSchema<Ts>...>;
};
/// Returns the JSON schema for a class.
template <class T, class... Ps>
std::string to_schema(const yyjson_write_flag _flag = 0) {
const auto internal_schema =
parsing::schema::make<Reader, Writer, T, Processors<Ps...>>();
auto definitions = std::map<std::string, schema::Type>();
for (const auto& [k, v] : internal_schema.definitions_) {
definitions[k] = type_to_json_schema_type(v);
}
using JSONSchemaType =
typename TypeHelper<schema::Type::ReflectionType>::JSONSchemaType;
const auto to_schema = [&](auto&& _root) -> JSONSchemaType {
using U = std::decay_t<decltype(_root)>;
return schema::JSONSchema<U> {.root = std::move(_root),
.definitions = definitions};
};
auto root = type_to_json_schema_type(internal_schema.root_);
const auto json_schema = std::visit(to_schema, std::move(root.value));
return write(json_schema, _flag);
}
} // namespace rfl::json
#endif