Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ const std::string json_schema = rfl::json::to_schema<Person>();
The resulting JSON schema looks like this:

```json
{"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/definitions/Person","definitions":{"Person":{"type":"object","properties":{"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/definitions/Person"}},"email":{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},"first_name":{"type":"string"},"last_name":{"type":"string"},"salary":{"type":"number"}},"required":["children","email","first_name","last_name","salary"]}}}
{"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/$defs/Person","$defs":{"Person":{"type":"object","properties":{"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/$defs/Person"}},"email":{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},"first_name":{"type":"string"},"last_name":{"type":"string"},"salary":{"type":"number"}},"required":["children","email","first_name","last_name","salary"]}}}
```

Note that this is currently supported for JSON only, since most other formats do not support schemata in the first place.
Expand Down
18 changes: 9 additions & 9 deletions docs/json_schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ This will result in the following JSON schema:
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/definitions/Person",
"definitions": {
"$ref": "#/$defs/Person",
"$defs": {
"Person": {
"type": "object",
"properties": {
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/Person"
"$ref": "#/$defs/Person"
}
},
"email": {
Expand Down Expand Up @@ -100,16 +100,16 @@ const std::string json_schema = rfl::json::to_schema<Person>(rfl::json::pretty);
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/definitions/Person",
"definitions": {
"$ref": "#/$defs/Person",
"$defs": {
"Person": {
"type": "object",
"properties": {
"children": {
"type": "array",
"description": "The person's children. Pass an empty array for no children.",
"items": {
"$ref": "#/definitions/Person"
"$ref": "#/$defs/Person"
}
},
"email": {
Expand Down Expand Up @@ -151,17 +151,17 @@ const std::string json_schema = rfl::json::to_schema<
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/definitions/Person",
"$ref": "#/$defs/Person",
"description": "JSON schema that describes the required attributes for the person class.",
"definitions": {
"$defs": {
"Person": {
"type": "object",
"properties": {
"children": {
"type": "array",
"description": "The person's children. Pass an empty array for no children.",
"items": {
"$ref": "#/definitions/Person"
"$ref": "#/$defs/Person"
}
},
"email": {
Expand Down
3 changes: 2 additions & 1 deletion include/rfl/json/schema/JSONSchema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ template <class T>
struct JSONSchema {
Rename<"$schema", Literal<"https://json-schema.org/draft/2020-12/schema">>
schema{};
rfl::Rename<"$comment", std::optional<std::string>> comment{};
Flatten<T> root{};
std::map<std::string, Type> definitions{};
rfl::Rename<"$defs", std::map<std::string, Type>> definitions{};
};

} // namespace rfl::json::schema
Expand Down
17 changes: 9 additions & 8 deletions include/rfl/json/to_schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@

#include <string>

//#include "../Literal.hpp"
// #include "../Literal.hpp"
#include "../Processors.hpp"
#include "../Variant.hpp"
//#include "../parsing/schema/Type.hpp"
//#include "../parsing/schema/ValidationType.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"
// #include "schema/Type.hpp"
// #include "write.hpp"

namespace rfl::json {

Expand All @@ -33,15 +33,16 @@ struct TypeHelper<rfl::Variant<Ts...>> {

std::string to_schema_internal_schema(
const parsing::schema::Definition& internal_schema, const yyjson_write_flag,
const bool _no_required);
const bool _no_required, const std::string& comment = "");

/// Returns the JSON schema for a class.
template <class T, class... Ps>
std::string to_schema(const yyjson_write_flag _flag = 0) {
std::string to_schema(const yyjson_write_flag _flag = 0,
const std::string& comment = "") {
using P = Processors<Ps...>;
const auto internal_schema = parsing::schema::make<Reader, Writer, T, P>();
return to_schema_internal_schema(internal_schema, _flag,
P::default_if_missing_);
P::default_if_missing_, comment);
Comment thread
AndrewNolte marked this conversation as resolved.
}
} // namespace rfl::json

Expand Down
14 changes: 10 additions & 4 deletions src/rfl/json/to_schema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ SOFTWARE.
*/

#include "rfl/json/to_schema.hpp"

#include "rfl/json/schema/Type.hpp"
#include "rfl/json/write.hpp"

Expand Down Expand Up @@ -263,7 +264,7 @@ schema::Type type_to_json_schema_type(const parsing::schema::Type& _type,

} else if constexpr (std::is_same<T, Type::Reference>()) {
return schema::Type{
.value = schema::Type::Reference{.ref = "#/definitions/" + _t.name_}};
.value = schema::Type::Reference{.ref = "#/$defs/" + _t.name_}};

} else if constexpr (std::is_same<T, Type::StringMap>()) {
return schema::Type{
Expand Down Expand Up @@ -297,7 +298,8 @@ schema::Type type_to_json_schema_type(const parsing::schema::Type& _type,

std::string to_schema_internal_schema(
const parsing::schema::Definition& internal_schema,
const yyjson_write_flag _flag, const bool _no_required) {
const yyjson_write_flag _flag, const bool _no_required,
const std::string& comment) {
auto definitions = std::map<std::string, schema::Type>();
for (const auto& [k, v] : internal_schema.definitions_) {
definitions[k] = type_to_json_schema_type(v, _no_required);
Expand All @@ -306,8 +308,12 @@ std::string to_schema_internal_schema(
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};
return schema::JSONSchema<U>{
.comment =
!comment.empty() ? std::optional(std::move(comment)) : std::nullopt,
.root = std::forward<decltype(_root)>(_root),
.definitions = definitions,
};
};
auto root = type_to_json_schema_type(internal_schema.root_, _no_required);
const auto json_schema = rfl::visit(to_schema, std::move(root.value));
Expand Down
2 changes: 1 addition & 1 deletion tests/json/test_json_schema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ TEST(json, test_json_schema) {
Person>>();

const std::string expected =
R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/definitions/test_json_schema__Person","description":"JSON schema that describes the required attributes for the person class.","definitions":{"rfl__Generic":{"anyOf":[{"anyOf":[{"type":"boolean"},{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"object","additionalProperties":{"$ref":"#/definitions/rfl__Generic"}},{"type":"array","items":{"$ref":"#/definitions/rfl__Generic"}}]},{"type":"null"}]},"test_json_schema__Circle":{"type":"object","properties":{"radius":{"type":"number"}},"required":["radius"]},"test_json_schema__Circle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Circle"]},"radius":{"type":"number"}},"required":["shape","radius"]},"test_json_schema__Person":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},"town":{"type":"string"},"color":{"type":"string","enum":["red","green","blue"]},"age":{"allOf":[{"minimum":0,"type":"integer"},{"maximum":130,"type":"integer"}]},"salary":{"type":"number"},"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/definitions/test_json_schema__Person"}},"variant":{"anyOf":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/definitions/test_json_schema__Person"}},{"type":"integer"}]},"tuple":{"type":"array","prefixItems":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/definitions/test_json_schema__Person"}},{"type":"integer"}],"items":false},"taggedUnion":{"anyOf":[{"$ref":"#/definitions/test_json_schema__Rectangle__tagged"},{"$ref":"#/definitions/test_json_schema__Square__tagged"},{"$ref":"#/definitions/test_json_schema__Circle__tagged"}]},"fieldVariant":{"anyOf":[{"type":"object","properties":{"rectangle":{"$ref":"#/definitions/test_json_schema__Rectangle"}},"required":["rectangle"]},{"type":"object","properties":{"square":{"$ref":"#/definitions/test_json_schema__Square"}},"required":["square"]},{"type":"object","properties":{"circle":{"$ref":"#/definitions/test_json_schema__Circle"}},"required":["circle"]}]},"generic":{"$ref":"#/definitions/rfl__Generic"},"with_extra_fields":{"$ref":"#/definitions/test_json_schema__WithExtraFields"}},"required":["firstName","lastName","email","town","color","age","salary","children","variant","tuple","taggedUnion","fieldVariant","generic","with_extra_fields"]},"test_json_schema__Rectangle":{"type":"object","properties":{"width":{"type":"number"},"height":{"type":"number"}},"required":["width","height"]},"test_json_schema__Rectangle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Rectangle"]},"width":{"type":"number"},"height":{"type":"number"}},"required":["shape","width","height"]},"test_json_schema__Square":{"type":"object","properties":{"width":{"type":"number"}},"required":["width"]},"test_json_schema__Square__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Square"]},"width":{"type":"number"}},"required":["shape","width"]},"test_json_schema__WithExtraFields":{"type":"object","properties":{"f1":{"type":"string"},"f2":{"type":"string"}},"required":["f1","f2"],"additionalProperties":{"type":"string"}}}})";
R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/$defs/test_json_schema__Person","description":"JSON schema that describes the required attributes for the person class.","$defs":{"rfl__Generic":{"anyOf":[{"anyOf":[{"type":"boolean"},{"type":"integer"},{"type":"number"},{"type":"string"},{"type":"object","additionalProperties":{"$ref":"#/$defs/rfl__Generic"}},{"type":"array","items":{"$ref":"#/$defs/rfl__Generic"}}]},{"type":"null"}]},"test_json_schema__Circle":{"type":"object","properties":{"radius":{"type":"number"}},"required":["radius"]},"test_json_schema__Circle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Circle"]},"radius":{"type":"number"}},"required":["shape","radius"]},"test_json_schema__Person":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},"town":{"type":"string"},"color":{"type":"string","enum":["red","green","blue"]},"age":{"allOf":[{"minimum":0,"type":"integer"},{"maximum":130,"type":"integer"}]},"salary":{"type":"number"},"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/$defs/test_json_schema__Person"}},"variant":{"anyOf":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/$defs/test_json_schema__Person"}},{"type":"integer"}]},"tuple":{"type":"array","prefixItems":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/$defs/test_json_schema__Person"}},{"type":"integer"}],"items":false},"taggedUnion":{"anyOf":[{"$ref":"#/$defs/test_json_schema__Rectangle__tagged"},{"$ref":"#/$defs/test_json_schema__Square__tagged"},{"$ref":"#/$defs/test_json_schema__Circle__tagged"}]},"fieldVariant":{"anyOf":[{"type":"object","properties":{"rectangle":{"$ref":"#/$defs/test_json_schema__Rectangle"}},"required":["rectangle"]},{"type":"object","properties":{"square":{"$ref":"#/$defs/test_json_schema__Square"}},"required":["square"]},{"type":"object","properties":{"circle":{"$ref":"#/$defs/test_json_schema__Circle"}},"required":["circle"]}]},"generic":{"$ref":"#/$defs/rfl__Generic"},"with_extra_fields":{"$ref":"#/$defs/test_json_schema__WithExtraFields"}},"required":["firstName","lastName","email","town","color","age","salary","children","variant","tuple","taggedUnion","fieldVariant","generic","with_extra_fields"]},"test_json_schema__Rectangle":{"type":"object","properties":{"width":{"type":"number"},"height":{"type":"number"}},"required":["width","height"]},"test_json_schema__Rectangle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Rectangle"]},"width":{"type":"number"},"height":{"type":"number"}},"required":["shape","width","height"]},"test_json_schema__Square":{"type":"object","properties":{"width":{"type":"number"}},"required":["width"]},"test_json_schema__Square__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Square"]},"width":{"type":"number"}},"required":["shape","width"]},"test_json_schema__WithExtraFields":{"type":"object","properties":{"f1":{"type":"string"},"f2":{"type":"string"}},"required":["f1","f2"],"additionalProperties":{"type":"string"}}}})";

EXPECT_EQ(json_schema, expected);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/json/test_json_schema2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ TEST(json, test_json_schema2) {
rfl::json::to_schema<DescribedType, rfl::SnakeCaseToCamelCase>();

const std::string expected =
R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/definitions/test_json_schema2__Person","description":"JSON schema that describes the required attributes for the person class.","definitions":{"test_json_schema2__Circle":{"type":"object","properties":{"radius":{"type":"number"}},"required":["radius"]},"test_json_schema2__Circle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Circle"]},"radius":{"type":"number"}},"required":["shape","radius"]},"test_json_schema2__Person":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},"town":{"type":"string"},"color":{"type":"string","enum":["red","green","blue"]},"age":{"allOf":[{"minimum":0,"type":"integer"},{"maximum":130,"type":"integer"}]},"salary":{"type":"number"},"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/definitions/test_json_schema2__Person"}},"variant":{"anyOf":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/definitions/test_json_schema2__Person"}},{"type":"integer"}]},"tuple":{"type":"array","prefixItems":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/definitions/test_json_schema2__Person"}},{"type":"integer"}],"items":false},"taggedUnion":{"anyOf":[{"$ref":"#/definitions/test_json_schema2__Rectangle__tagged"},{"$ref":"#/definitions/test_json_schema2__Square__tagged"},{"$ref":"#/definitions/test_json_schema2__Circle__tagged"}]},"fieldVariant":{"anyOf":[{"type":"object","properties":{"rectangle":{"$ref":"#/definitions/test_json_schema2__Rectangle"}},"required":["rectangle"]},{"type":"object","properties":{"square":{"$ref":"#/definitions/test_json_schema2__Square"}},"required":["square"]},{"type":"object","properties":{"circle":{"$ref":"#/definitions/test_json_schema2__Circle"}},"required":["circle"]}]}},"required":["firstName","lastName","email","town","color","age","salary","children","variant","tuple","taggedUnion","fieldVariant"]},"test_json_schema2__Rectangle":{"type":"object","properties":{"width":{"type":"number"},"height":{"type":"number"}},"required":["width","height"]},"test_json_schema2__Rectangle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Rectangle"]},"width":{"type":"number"},"height":{"type":"number"}},"required":["shape","width","height"]},"test_json_schema2__Square":{"type":"object","properties":{"width":{"type":"number"}},"required":["width"]},"test_json_schema2__Square__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Square"]},"width":{"type":"number"}},"required":["shape","width"]}}})";
R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$ref":"#/$defs/test_json_schema2__Person","description":"JSON schema that describes the required attributes for the person class.","$defs":{"test_json_schema2__Circle":{"type":"object","properties":{"radius":{"type":"number"}},"required":["radius"]},"test_json_schema2__Circle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Circle"]},"radius":{"type":"number"}},"required":["shape","radius"]},"test_json_schema2__Person":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","description":"Must be a proper email in the form xxx@xxx.xxx.","pattern":"^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"},"town":{"type":"string"},"color":{"type":"string","enum":["red","green","blue"]},"age":{"allOf":[{"minimum":0,"type":"integer"},{"maximum":130,"type":"integer"}]},"salary":{"type":"number"},"children":{"type":"array","description":"The person's children. Pass an empty array for no children.","items":{"$ref":"#/$defs/test_json_schema2__Person"}},"variant":{"anyOf":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/$defs/test_json_schema2__Person"}},{"type":"integer"}]},"tuple":{"type":"array","prefixItems":[{"type":"string","enum":["red","green","blue"]},{"type":"array","items":{"$ref":"#/$defs/test_json_schema2__Person"}},{"type":"integer"}],"items":false},"taggedUnion":{"anyOf":[{"$ref":"#/$defs/test_json_schema2__Rectangle__tagged"},{"$ref":"#/$defs/test_json_schema2__Square__tagged"},{"$ref":"#/$defs/test_json_schema2__Circle__tagged"}]},"fieldVariant":{"anyOf":[{"type":"object","properties":{"rectangle":{"$ref":"#/$defs/test_json_schema2__Rectangle"}},"required":["rectangle"]},{"type":"object","properties":{"square":{"$ref":"#/$defs/test_json_schema2__Square"}},"required":["square"]},{"type":"object","properties":{"circle":{"$ref":"#/$defs/test_json_schema2__Circle"}},"required":["circle"]}]}},"required":["firstName","lastName","email","town","color","age","salary","children","variant","tuple","taggedUnion","fieldVariant"]},"test_json_schema2__Rectangle":{"type":"object","properties":{"width":{"type":"number"},"height":{"type":"number"}},"required":["width","height"]},"test_json_schema2__Rectangle__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Rectangle"]},"width":{"type":"number"},"height":{"type":"number"}},"required":["shape","width","height"]},"test_json_schema2__Square":{"type":"object","properties":{"width":{"type":"number"}},"required":["width"]},"test_json_schema2__Square__tagged":{"type":"object","properties":{"shape":{"type":"string","enum":["Square"]},"width":{"type":"number"}},"required":["shape","width"]}}})";

EXPECT_EQ(json_schema, expected);
}
Expand Down
Loading
Loading