draconisplusplus/include/rfl/json/to_schema.hpp

251 lines
11 KiB
C++
Raw Normal View History

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