#ifndef RFL_JSON_TOSCHEMA_HPP_ #define RFL_JSON_TOSCHEMA_HPP_ #include #include #include #include #include #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; return std::is_same(); }; 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; using Type = parsing::schema::Type; if constexpr (std::is_same() || std::is_same() || std::is_same() || std::is_same() || std::is_same()) { 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; using ValidationType = parsing::schema::ValidationType; if constexpr (std::is_same()) { auto all_of = std::vector(); 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()) { auto any_of = std::vector(); 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()) { auto one_of = std::vector(); 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()) { return schema::Type { .value = schema::Type::Regex {.pattern = _v.pattern_} }; } else if constexpr (std::is_same()) { return type_to_json_schema_type(_type); } else if constexpr (std::is_same( )) { return schema::Type { .value = schema::Type::ExclusiveMaximum { .exclusiveMaximum = _v.value_, .type = numeric_type_to_string(_type) } }; } else if constexpr (std::is_same( )) { return schema::Type { .value = schema::Type::ExclusiveMinimum { .exclusiveMinimum = _v.value_, .type = numeric_type_to_string(_type) } }; } else if constexpr (std::is_same()) { return schema::Type { .value = schema::Type::Maximum { .maximum = _v.value_, .type = numeric_type_to_string(_type) } }; } else if constexpr (std::is_same()) { return schema::Type { .value = schema::Type::Minimum { .minimum = _v.value_, .type = numeric_type_to_string(_type) } }; } else if constexpr (std::is_same()) { 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()) { 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, "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; using Type = parsing::schema::Type; if constexpr (std::is_same()) { return schema::Type {.value = schema::Type::Boolean {}}; } else if constexpr (std::is_same() || std::is_same() || std::is_same() || std::is_same() || std::is_same()) { return schema::Type {.value = schema::Type::Integer {}}; } else if constexpr (std::is_same() || std::is_same()) { return schema::Type {.value = schema::Type::Number {}}; } else if constexpr (std::is_same()) { return schema::Type {.value = schema::Type::String {}}; } else if constexpr (std::is_same()) { auto any_of = std::vector(); 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()) { 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()) { return schema::Type { .value = schema::Type::FixedSizeTypedArray { .items = Ref::make( type_to_json_schema_type(*_t.type_) ), .minContains = _t.size_, .maxContains = _t.size_ } }; } else if constexpr (std::is_same()) { return schema::Type { .value = schema::Type::StringEnum {.values = _t.values_} }; } else if constexpr (std::is_same()) { auto properties = std::map(); auto required = std::vector(); 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()) { 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()) { return schema::Type { .value = schema::Type::Reference {.ref = "#/definitions/" + _t.name_} }; } else if constexpr (std::is_same()) { return schema::Type { .value = schema::Type::StringMap { .additionalProperties = Ref::make( type_to_json_schema_type(*_t.value_type_) ) } }; } else if constexpr (std::is_same()) { auto items = std::vector(); 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()) { return schema::Type { .value = schema::Type::TypedArray { .items = Ref::make( type_to_json_schema_type(*_t.type_) ) } }; } else if constexpr (std::is_same()) { return handle_validation_type(*_t.type_, _t.validation_); } else { static_assert(rfl::always_false_v, "Not all cases were covered."); } }; return std::visit(handle_variant, _type.variant_); } template struct TypeHelper {}; template struct TypeHelper> { using JSONSchemaType = std::variant...>; }; /// Returns the JSON schema for a class. template std::string to_schema(const yyjson_write_flag _flag = 0) { const auto internal_schema = parsing::schema::make>(); auto definitions = std::map(); for (const auto& [k, v] : internal_schema.definitions_) { definitions[k] = type_to_json_schema_type(v); } using JSONSchemaType = typename TypeHelper::JSONSchemaType; const auto to_schema = [&](auto&& _root) -> JSONSchemaType { using U = std::decay_t; return schema::JSONSchema { .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