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-08 14:10:59 -04:00
|
|
|
inline schema::Type type_to_json_schema_type(
|
2024-06-08 15:53:06 -04:00
|
|
|
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-08 15:53:06 -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;
|
|
|
|
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();
|
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-08 15:53:06 -04:00
|
|
|
const parsing::schema::Type& _type,
|
|
|
|
const parsing::schema::ValidationType& _validation_type
|
|
|
|
) {
|
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>();
|
|
|
|
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>();
|
|
|
|
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>();
|
|
|
|
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>()) {
|
2024-06-08 15:53:06 -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-08 15:53:06 -04:00
|
|
|
} 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)
|
|
|
|
}
|
|
|
|
};
|
2024-06-08 14:10:59 -04:00
|
|
|
|
2024-06-08 15:53:06 -04:00
|
|
|
} 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)
|
|
|
|
}
|
|
|
|
};
|
2024-06-08 14:10:59 -04:00
|
|
|
|
|
|
|
} else if constexpr (std::is_same<T, ValidationType::Maximum>()) {
|
|
|
|
return schema::Type {
|
2024-06-08 15:53:06 -04:00
|
|
|
.value =
|
|
|
|
schema::Type::Maximum {
|
|
|
|
.maximum = _v.value_, .type = numeric_type_to_string(_type)
|
|
|
|
}
|
|
|
|
};
|
2024-06-08 14:10:59 -04:00
|
|
|
|
|
|
|
} else if constexpr (std::is_same<T, ValidationType::Minimum>()) {
|
|
|
|
return schema::Type {
|
2024-06-08 15:53:06 -04:00
|
|
|
.value =
|
|
|
|
schema::Type::Minimum {
|
|
|
|
.minimum = _v.value_, .type = numeric_type_to_string(_type)
|
|
|
|
}
|
|
|
|
};
|
2024-06-08 14:10:59 -04:00
|
|
|
|
|
|
|
} else if constexpr (std::is_same<T, ValidationType::EqualTo>()) {
|
|
|
|
const auto maximum = schema::Type {
|
2024-06-08 15:53:06 -04:00
|
|
|
.value =
|
|
|
|
schema::Type::Maximum {
|
|
|
|
.maximum = _v.value_, .type = numeric_type_to_string(_type)
|
|
|
|
}
|
|
|
|
};
|
2024-06-08 14:10:59 -04:00
|
|
|
const auto minimum = schema::Type {
|
2024-06-08 15:53:06 -04:00
|
|
|
.value =
|
|
|
|
schema::Type::Minimum {
|
|
|
|
.minimum = _v.value_, .type = numeric_type_to_string(_type)
|
|
|
|
}
|
|
|
|
};
|
2024-06-08 14:10:59 -04:00
|
|
|
return schema::Type {
|
2024-06-08 15:53:06 -04:00
|
|
|
.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 {
|
|
|
|
.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}}
|
|
|
|
};
|
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_);
|
|
|
|
}
|
|
|
|
|
|
|
|
inline schema::Type type_to_json_schema_type(
|
2024-06-08 15:53:06 -04:00
|
|
|
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>()) {
|
|
|
|
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));
|
2024-06-05 19:04:53 -04:00
|
|
|
}
|
2024-06-08 14:10:59 -04:00
|
|
|
return schema::Type {.value = schema::Type::AnyOf {.anyOf = any_of}};
|
|
|
|
|
|
|
|
} 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_;
|
|
|
|
return schema::Type {_v};
|
|
|
|
};
|
|
|
|
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 {
|
|
|
|
.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 14:10:59 -04:00
|
|
|
|
|
|
|
} else if constexpr (std::is_same<T, Type::Literal>()) {
|
|
|
|
return schema::Type {
|
2024-06-08 15:53:06 -04:00
|
|
|
.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 {
|
|
|
|
.value =
|
|
|
|
schema::Type::Object {
|
|
|
|
.properties = properties, .required = required
|
|
|
|
}
|
|
|
|
};
|
2024-06-08 14:10:59 -04:00
|
|
|
|
|
|
|
} else if constexpr (std::is_same<T, Type::Optional>()) {
|
|
|
|
return schema::Type {
|
2024-06-08 15:53:06 -04:00
|
|
|
.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-08 15:53:06 -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>()) {
|
|
|
|
return schema::Type {
|
2024-06-08 15:53:06 -04:00
|
|
|
.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>();
|
|
|
|
for (const auto& t : _t.types_) {
|
|
|
|
items.emplace_back(type_to_json_schema_type(t));
|
|
|
|
}
|
2024-06-08 15:53:06 -04:00
|
|
|
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-08 15:53:06 -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) {
|
|
|
|
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)>;
|
2024-06-08 15:53:06 -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
|