Skip to content

Commit

Permalink
Merge branch 'main' into f/toml
Browse files Browse the repository at this point in the history
  • Loading branch information
liuzicheng1987 committed Mar 29, 2024
2 parents 2a3dae8 + 1f2e216 commit 813ffbd
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 5 deletions.
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ age: 45
```
This will work for just about any example in the entire documentation
and any supported format:
and any supported format, except where explicitly noted otherwise:
```cpp
rfl::bson::write(homer);
Expand Down Expand Up @@ -200,6 +200,35 @@ Found 5 errors:
5) Field named 'children' not found.
```

## JSON schema

reflect-cpp also supports generating JSON schemata:

```cpp
struct Person {
std::string first_name;
std::string last_name;
rfl::Description<"Must be a proper email in the form [email protected].",
rfl::Email>
email;
rfl::Description<
"The person's children. Pass an empty array for no children.",
std::vector<Person>>
children;
float salary;
};

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 [email protected].","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.

## Enums

reflect-cpp supports scoped enumerations:
Expand Down Expand Up @@ -391,6 +420,7 @@ reflect-cpp supports the following containers from the C++ standard library:
- `std::unordered_set`
- `std::variant`
- `std::vector`
- `std::wstring`
### Additional containers
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@

2.4) [Size validation](https://github.com/getml/reflect-cpp/blob/main/docs/size_validation.md) - For imposing size constraints on containers such as `std::vector` or `std::string`.

2.5) [JSON schema](https://github.com/getml/reflect-cpp/blob/main/docs/json_schema.md) - For validating your schema before you even send it to your C++ backend.

## 3) Custom classes

3.1) [Custom classes](https://github.com/getml/reflect-cpp/blob/main/docs/custom_classes.md) - For custom classes with private fields.
Expand Down
192 changes: 192 additions & 0 deletions docs/json_schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# JSON schema

JSON schemata are a powerful tool for expressing the expected structure of your input. You can use it to validate your input before you even send it to your C++ backend, which will result in better UX.

It can also be used for code generation. For instance, tools such as https://app.quicktype.io/ allow you to generate code in multiple programming languages from the JSON schema (even though the validations are usually omitted).

If you are interacting with Python, we warmly recommend https://docs.pydantic.dev/latest/integrations/datamodel_code_generator/. This allows you to generate Pydantic dataclasses, including the validation, from the JSON schema.

Note that this is only supported for JSON, not for other formats.

## Basic idea

Suppose you have a struct like this:

```cpp
struct Person {
std::string first_name;
std::string last_name;
rfl::Email email;
std::vector<Person> children;
float salary;
};
```
You can generate a JSON schema like this:
```cpp
const std::string json_schema = rfl::json::to_schema<Person>(rfl::json::pretty);
```

You do not have to pass `rfl::json::pretty`, but for the purposes of this documentation it is better to do so.

This will result in the following JSON schema:

```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/definitions/Person",
"definitions": {
"Person": {
"type": "object",
"properties": {
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/Person"
}
},
"email": {
"type": "string",
"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"
]
}
}
}
```

You can insert this into the tools mentioned above and see the generated code.

## Adding descriptions

JSON schemata also often contain descriptions. reflect-cpp supports this as well.

```cpp
struct Person {
std::string first_name;
std::string last_name;
rfl::Description<"Must be a proper email in the form [email protected].",
rfl::Email>
email;
rfl::Description<
"The person's children. Pass an empty array for no children.",
std::vector<Person>>
children;
float salary;
};
```
```cpp
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": {
"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 [email protected].",
"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"
]
}
}
}
```

You also add a description to the entire JSON schema:

```cpp
const std::string json_schema = rfl::json::to_schema<
rfl::Description<"JSON schema that describes the required "
"attributes for the person class.",
Person>>(rfl::json::pretty);
```

```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/definitions/Person",
"description": "JSON schema that describes the required attributes for the person class.",
"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 [email protected].",
"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"
]
}
}
}
```
6 changes: 3 additions & 3 deletions include/rfl/NamedTuple.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ class NamedTuple {

/// Invokes a callable object once for each field in order.
template <typename F>
void apply(const F& _f) {
void apply(F&& _f) {
const auto apply_to_field =
[&_f]<typename... AFields>(AFields&&... fields) {
((_f(std::forward<AFields>(fields))), ...);
Expand All @@ -209,7 +209,7 @@ class NamedTuple {

/// Invokes a callable object once for each field in order.
template <typename F>
void apply(const F& _f) const {
void apply(F&& _f) const {
const auto apply_to_field = [&_f](const auto&... fields) {
((_f(fields)), ...);
};
Expand Down Expand Up @@ -602,7 +602,7 @@ class NamedTuple<> {

/// Does nothing at all.
template <typename F>
void apply(const F& _f) const {}
void apply(F&& _f) const {}

/// Returns an empty tuple.
auto fields() const { return std::tuple(); }
Expand Down
2 changes: 1 addition & 1 deletion tests/json/test_apply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ void test() {
}
});

rfl::to_view(lisa).apply([](auto field) {
rfl::to_view(lisa).apply([](auto field) mutable {
if constexpr (decltype(field)::name() == "first_name") {
*field.value() = "Bart";
}
Expand Down

0 comments on commit 813ffbd

Please sign in to comment.