diff --git a/include/rfl/json/schema/Type.hpp b/include/rfl/json/schema/Type.hpp index 8ed247928..0ae023f5e 100644 --- a/include/rfl/json/schema/Type.hpp +++ b/include/rfl/json/schema/Type.hpp @@ -21,6 +21,7 @@ struct Type { std::optional description{}; std::optional deprecated{}; std::optional deprecationMessage{}; + rfl::Rename<"default", std::optional> defaultValue{}; }; struct Boolean { diff --git a/include/rfl/parsing/NamedTupleParser.hpp b/include/rfl/parsing/NamedTupleParser.hpp index de6153e46..8542cdb7b 100644 --- a/include/rfl/parsing/NamedTupleParser.hpp +++ b/include/rfl/parsing/NamedTupleParser.hpp @@ -31,6 +31,14 @@ #include "schema/Type.hpp" #include "to_single_error_message.hpp" +namespace rfl { + +/// forward declaration +template +Generic to_generic(const auto& _t); + +} // namespace rfl + namespace rfl::parsing { template static schema::Type to_schema( - std::map* _definitions) noexcept { + std::map* _definitions, + View* _view = nullptr) noexcept { SchemaType schema; - build_schema(_definitions, &schema, + build_schema(_definitions, &schema, _view, std::make_integer_sequence()); return schema::Type{schema}; } @@ -226,14 +236,25 @@ struct NamedTupleParser { } } - template + template static void add_field_to_schema( std::map* _definitions, - SchemaType* _schema) noexcept { + SchemaType* _schema, + View* _view) noexcept { using F = internal::nth_element_t<_i, FieldTypes...>; using U = std::remove_cvref_t; if constexpr (!internal::is_skip_v && !internal::is_extra_fields_v) { + // Add default value here auto s = Parser::to_schema(_definitions); + if constexpr (!std::is_same_v) { + s.variant_.visit([&](auto& value) { + if constexpr (std::is_same_v, + schema::Type::DefaultVal>) { + value.default_value_ = + rfl::to_generic((*rfl::get<_i>(*_view)).get()); + } + }); + } if constexpr (_no_field_names) { _schema->types_.emplace_back(std::move(s)); } else { @@ -249,11 +270,12 @@ struct NamedTupleParser { (add_field_to_object<_is>(_w, _tup, _ptr), ...); } - template + template static void build_schema(std::map* _definitions, SchemaType* _schema, + View* _view, std::integer_sequence) noexcept { - (add_field_to_schema<_is>(_definitions, _schema), ...); + (add_field_to_schema(_definitions, _schema, _view), ...); if constexpr (NamedTupleType::pos_extra_fields() != -1) { using F = internal::nth_element_t) { return make_deprecated(_definitions); + } else if constexpr (std::is_class_v && std::is_aggregate_v) { return make_reference(_definitions); @@ -637,9 +638,16 @@ struct Parser { } else { using NamedTupleType = internal::processed_t; - (*_definitions)[name] = - Parser::to_schema( - _definitions); + if constexpr (internal::has_default_val_v) { + auto t = U{}; + auto view = ProcessorsType::template process(to_view(t)); + (*_definitions)[name] = + Parser::to_schema( + _definitions, &view); + }else { + (*_definitions)[name] = + Parser::to_schema(_definitions); + } } } return Type{Type::Reference{name}}; diff --git a/include/rfl/parsing/schema/Type.hpp b/include/rfl/parsing/schema/Type.hpp index 9044e2fae..dcf74e8fd 100644 --- a/include/rfl/parsing/schema/Type.hpp +++ b/include/rfl/parsing/schema/Type.hpp @@ -61,6 +61,7 @@ struct RFL_API Type { /// using this or the Optional wrapper. struct DefaultVal { Ref type_; + Generic default_value_; }; struct DescribedLiteral { diff --git a/src/rfl/json/to_schema.cpp b/src/rfl/json/to_schema.cpp index 78a707cbd..b6ed76cd6 100644 --- a/src/rfl/json/to_schema.cpp +++ b/src/rfl/json/to_schema.cpp @@ -223,7 +223,12 @@ schema::Type type_to_json_schema_type(const parsing::schema::Type& _type, return schema::Type{.value = schema::Type::AnyOf{.anyOf = any_of}}; } else if constexpr (std::is_same()) { - return type_to_json_schema_type(*_t.type_, _no_required); + auto res = type_to_json_schema_type(*_t.type_, _no_required); + const auto update_prediction = [&](auto _v) -> schema::Type { + _v.annotations.value_.defaultValue = _t.default_value_; + return schema::Type{_v}; + }; + return rfl::visit(update_prediction, res.value); } else if constexpr (std::is_same()) { auto res = type_to_json_schema_type(*_t.type_, _no_required); diff --git a/tests/json/test_json_schema5.cpp b/tests/json/test_json_schema5.cpp index f6196a141..28e432ff7 100644 --- a/tests/json/test_json_schema5.cpp +++ b/tests/json/test_json_schema5.cpp @@ -5,8 +5,7 @@ namespace test_json_schema5 { -using Age = rfl::Validator, rfl::Minimum<0>, - rfl::Maximum<130>>; +using Age = rfl::Validator, rfl::Maximum<130>>; struct Person { rfl::Description<"Given name of this person", std::string> first_name; @@ -14,14 +13,14 @@ struct Person { std::optional> last_name; Age age; - rfl::DefaultVal nickname; + rfl::DefaultVal nickname = "peter"; }; TEST(json, test_json_schema5) { const auto json_schema = rfl::json::to_schema(); const std::string expected = - R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/$defs/test_json_schema5__Person","$defs":{"test_json_schema5__Person":{"type":"object","properties":{"first_name":{"type":"string","description":"Given name of this person"},"last_name":{"description":"Optional family name of this person","anyOf":[{"type":"string"},{"type":"null"}]},"age":{"allOf":[{"minimum":0,"type":"number"},{"maximum":130,"type":"number"}]},"nickname":{"type":"string"}},"required":["first_name"]}}})"; + R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/$defs/test_json_schema5__Person","$defs":{"test_json_schema5__Person":{"type":"object","properties":{"first_name":{"type":"string","description":"Given name of this person"},"last_name":{"description":"Optional family name of this person","anyOf":[{"type":"string"},{"type":"null"}]},"age":{"allOf":[{"minimum":0,"type":"integer"},{"maximum":130,"type":"integer"}]},"nickname":{"type":"string","default":"peter"}},"required":["first_name","age"]}}})"; EXPECT_EQ(json_schema, expected); } diff --git a/tests/json/test_json_schema_default_vals.cpp b/tests/json/test_json_schema_default_vals.cpp new file mode 100644 index 000000000..9236a329e --- /dev/null +++ b/tests/json/test_json_schema_default_vals.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +namespace test_schema_default { + +struct Config { + int port = 80; + bool autostart = true; +}; + +struct DefaultValField { + rfl::DefaultVal with_default = 10; +}; + +struct DefaultWithConfig { + rfl::DefaultVal with_default = Config{443, true}; +}; + +TEST(json, test_with_default) { + auto json_schema = rfl::json::to_schema(); + + std::string expected = + R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/$defs/test_schema_default__DefaultValField","$defs":{"test_schema_default__DefaultValField":{"type":"object","properties":{"with_default":{"type":"integer","default":10}},"required":[]}}})"; + + EXPECT_EQ(json_schema, expected) << json_schema; + + json_schema = rfl::json::to_schema(); + expected = + R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/$defs/test_schema_default__DefaultWithConfig","$defs":{"test_schema_default__Config":{"type":"object","properties":{"port":{"type":"integer"},"autostart":{"type":"boolean"}},"required":["port","autostart"]},"test_schema_default__DefaultWithConfig":{"type":"object","properties":{"with_default":{"$ref":"#/$defs/test_schema_default__Config","default":{"port":443,"autostart":true}}},"required":[]}}})"; + + EXPECT_EQ(json_schema, expected) << "is " << json_schema; +} + +} // namespace test_schema_default