paradocs 1.0.24 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.readthedocs.yml +5 -0
- data/docs/custom_configuration.md +10 -0
- data/docs/documentation_generation.md +291 -0
- data/docs/faq.md +21 -0
- data/docs/form_objects_dsl.md +90 -0
- data/docs/index.md +106 -0
- data/docs/payload_builder.md +104 -0
- data/docs/policies.md +309 -0
- data/docs/schema.md +294 -0
- data/docs/struct.md +135 -0
- data/docs/subschema.md +29 -0
- data/lib/paradocs/extensions/payload_builder.rb +43 -0
- data/lib/paradocs/extensions/structure.rb +118 -0
- data/lib/paradocs/schema.rb +30 -9
- data/lib/paradocs/version.rb +1 -1
- data/mkdocs.yml +16 -0
- data/paradocs.gemspec +2 -2
- data/requirements.txt +1 -0
- data/spec/extensions/payload_builder_spec.rb +70 -0
- data/spec/extensions/structures_spec.rb +237 -0
- data/spec/schema_spec.rb +1 -1
- data/spec/subschema_spec.rb +7 -4
- metadata +26 -10
- data/lib/paradocs/extensions/insides.rb +0 -77
- data/spec/schema_structures_spec.rb +0 -169
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0df2185fac7b1e70254c9531622e135c6609f309
|
4
|
+
data.tar.gz: 701ec92673e9044bc8a8384e251276aa7863d37e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa14905a01be3d1d54589aabc53fbc2f14d3a17352742a9eece1e76b324f146d94f5860ae4f59d738007e809c1fb0eb3ae95579d2a8237798e80a7a6f43ff2b9
|
7
|
+
data.tar.gz: 0c5fedee64178f630d403db573b2d8a4e2e4bc44b88e65df636c88aa1d9857836267d77ed34a1236036ff97bb105e37252f4948d46fcdc600850e682a5db082d
|
data/.readthedocs.yml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Configuration
|
2
|
+
```rb
|
3
|
+
Paradocs.configure do |config|
|
4
|
+
config.explicit_errors = false # set to true if you want all errors from the policies to be explicitly registered in the policy
|
5
|
+
config.whitelisted_keys = [] # enrich it with global white-listed keys if you use WhiteList feature
|
6
|
+
config.default_schema_name = :schema # this name will be set for unnamed schemas
|
7
|
+
config.meta_prefix = "_" # used in #structure and #flatten_structure methods. All the metadata will be prefixed with this prefix.
|
8
|
+
config.whitelist_coercion = nil # set up a Proc here, that receives |value, field.meta| for each whitelisted field in order to enrich the whitelisting logic.
|
9
|
+
end
|
10
|
+
```
|
@@ -0,0 +1,291 @@
|
|
1
|
+
# Documentation Generation
|
2
|
+
|
3
|
+
A `Schema` instance has a `#structure` method that return `Paradocs::Extensions::Structure` instance that allows instrospecting schema meta data.
|
4
|
+
|
5
|
+
It's supposed to have the following schema:
|
6
|
+
```ruby
|
7
|
+
schema = Paradocs::Schema.new do
|
8
|
+
field(:data).type(:object).present.schema do
|
9
|
+
field(:id).type(:integer).present.policy(:policy_with_error)
|
10
|
+
field(:name).type(:string).meta(label: "very important staff")
|
11
|
+
field(:role).type(:string).declared.options(["admin", "user"]).default("user").mutates_schema! do |*|
|
12
|
+
:test_subschema
|
13
|
+
end
|
14
|
+
field(:extra).type(:array).required.schema do
|
15
|
+
field(:extra).declared.default(false).policy(:policy_with_silent_error)
|
16
|
+
end
|
17
|
+
|
18
|
+
mutation_by!(:name) { :subschema }
|
19
|
+
|
20
|
+
subschema(:subschema) do
|
21
|
+
field(:test_field).present
|
22
|
+
end
|
23
|
+
subschema(:test_subschema) do
|
24
|
+
field(:test1).present
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
## Structure#nested
|
31
|
+
> This method returns schema structure in a nested way including subschemes.
|
32
|
+
```ruby
|
33
|
+
schema.structure.nested.to_json # =>
|
34
|
+
{
|
35
|
+
"_errors": ["ArgumentError"],
|
36
|
+
"_subschemes": {},
|
37
|
+
"data": {
|
38
|
+
"type": "object",
|
39
|
+
"required": true,
|
40
|
+
"present": true,
|
41
|
+
"json_path": "$.data",
|
42
|
+
"nested_name": "data",
|
43
|
+
"structure": {
|
44
|
+
"_subschemes": {
|
45
|
+
"subschema": {
|
46
|
+
"_errors": [],
|
47
|
+
"_subschemes": {},
|
48
|
+
"test_field": {
|
49
|
+
"required": true,
|
50
|
+
"present": true,
|
51
|
+
"json_path": "$.data.test_field",
|
52
|
+
"nested_name": "data.test_field"
|
53
|
+
}
|
54
|
+
},
|
55
|
+
"test_subschema": {
|
56
|
+
"_errors": [],
|
57
|
+
"_subschemes": {},
|
58
|
+
"test1": {
|
59
|
+
"required": true,
|
60
|
+
"present": true,
|
61
|
+
"json_path": "$.data.test1",
|
62
|
+
"nested_name": "data.test1"
|
63
|
+
}
|
64
|
+
}
|
65
|
+
},
|
66
|
+
"id": {
|
67
|
+
"type": "integer",
|
68
|
+
"required": true,
|
69
|
+
"present": true,
|
70
|
+
"policy_with_error": {"errors": ["ArgumentError"]},
|
71
|
+
"json_path": "$.data.id",
|
72
|
+
"nested_name": "data.id"
|
73
|
+
},
|
74
|
+
"name": {
|
75
|
+
"type": "string",
|
76
|
+
"label": "very important staff",
|
77
|
+
"json_path": "$.data.name",
|
78
|
+
"mutates_schema": true,
|
79
|
+
"nested_name": "data.name"
|
80
|
+
},
|
81
|
+
"role": {
|
82
|
+
"type": "string",
|
83
|
+
"options": ["admin", "user"],
|
84
|
+
"default": "user",
|
85
|
+
"json_path": "$.data.role",
|
86
|
+
"mutates_schema": true,
|
87
|
+
"nested_name": "data.role"
|
88
|
+
},
|
89
|
+
"extra": {
|
90
|
+
"type": "array",
|
91
|
+
"required": true,
|
92
|
+
"json_path": "$.data.extra[]",
|
93
|
+
"nested_name": "data.extra",
|
94
|
+
"structure": {
|
95
|
+
"_subschemes": {},
|
96
|
+
"extra": {
|
97
|
+
"default": false,
|
98
|
+
"policy_with_silent_error": {"errors": []},
|
99
|
+
"json_path": "$.data.extra[].extra",
|
100
|
+
"nested_name": "data.extra.extra"
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
}
|
107
|
+
```
|
108
|
+
|
109
|
+
## Structure#flatten
|
110
|
+
> This method returns schema structure in a flatten (without deep nesting) way including subschemes.
|
111
|
+
```rb
|
112
|
+
schema.structure.flatten.to_json # =>
|
113
|
+
{
|
114
|
+
"_errors": ["ArgumentError"],
|
115
|
+
"_subschemes": {
|
116
|
+
"subschema": {
|
117
|
+
"_errors": [],
|
118
|
+
"_subschemes": {},
|
119
|
+
"data.test_field": {
|
120
|
+
"required": true,
|
121
|
+
"present": true,
|
122
|
+
"json_path": "$.data.test_field"
|
123
|
+
}
|
124
|
+
},
|
125
|
+
"test_subschema": {
|
126
|
+
"_errors": [],
|
127
|
+
"_subschemes": {},
|
128
|
+
"data.test1": {
|
129
|
+
"required": true,
|
130
|
+
"present": true,
|
131
|
+
"json_path": "$.data.test1"
|
132
|
+
}
|
133
|
+
}
|
134
|
+
},
|
135
|
+
"data": {
|
136
|
+
"type": "object",
|
137
|
+
"required": true,
|
138
|
+
"present": true,
|
139
|
+
"json_path": "$.data"
|
140
|
+
},
|
141
|
+
"data.id": {
|
142
|
+
"type": "integer",
|
143
|
+
"required": true,
|
144
|
+
"present": true,
|
145
|
+
"policy_with_error": {"errors": ["ArgumentError"]},
|
146
|
+
"json_path": "$.data.id"
|
147
|
+
},
|
148
|
+
"data.name": {
|
149
|
+
"type": "string",
|
150
|
+
"label": "very important staff",
|
151
|
+
"json_path": "$.data.name",
|
152
|
+
"mutates_schema": true
|
153
|
+
},
|
154
|
+
"data.role": {
|
155
|
+
"type": "string",
|
156
|
+
"options": ["admin", "user"],
|
157
|
+
"default": "user",
|
158
|
+
"json_path": "$.data.role",
|
159
|
+
"mutates_schema": true
|
160
|
+
},
|
161
|
+
"data.extra": {
|
162
|
+
"type": "array",
|
163
|
+
"required": true,
|
164
|
+
"json_path": "$.data.extra[]"
|
165
|
+
},
|
166
|
+
"data.extra.extra": {
|
167
|
+
"default": false,
|
168
|
+
"policy_with_silent_error": {"errors": []},
|
169
|
+
"json_path": "$.data.extra[].extra"
|
170
|
+
}
|
171
|
+
}
|
172
|
+
```
|
173
|
+
|
174
|
+
|
175
|
+
## Structure#all_nested
|
176
|
+
|
177
|
+
> This method returns all available combinations of schema (built on subschemas) saving the nesting.
|
178
|
+
|
179
|
+
Will return a hash with 2 structures named by the names of declared subschemas:
|
180
|
+
```rb
|
181
|
+
all_nested = schema.structure.all_nested
|
182
|
+
all_nested.keys # => [:subschema, :test_subschema]
|
183
|
+
all_nested[:subschema] # =>
|
184
|
+
{
|
185
|
+
_errors: [],
|
186
|
+
"data" => {
|
187
|
+
type: :object,
|
188
|
+
required: true,
|
189
|
+
present: true,
|
190
|
+
json_path: "$.data",
|
191
|
+
structure: {
|
192
|
+
"role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", mutates_schema: true},
|
193
|
+
"extra" => {type: :array, required: true, json_path: "$.data.extra[]", structure: {"extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra"}}},
|
194
|
+
"test_field" => {required: true, present: true, json_path: "$.data.test_field"},
|
195
|
+
"id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, json_path: "$.data.id"},
|
196
|
+
"name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true}
|
197
|
+
}
|
198
|
+
}
|
199
|
+
}
|
200
|
+
all_nested[:test_subschema] # =>
|
201
|
+
{
|
202
|
+
_errors: [],
|
203
|
+
"data" => {
|
204
|
+
type: :object,
|
205
|
+
required: true,
|
206
|
+
present: true,
|
207
|
+
json_path: "$.data",
|
208
|
+
structure: {
|
209
|
+
"role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", mutates_schema: true},
|
210
|
+
"extra" => {type: :array, required: true, json_path: "$.data.extra[]", structure: {"extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra"}}},
|
211
|
+
"test1" => {required: true, present: true, json_path: "$.data.test1"},
|
212
|
+
"id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, json_path: "$.data.id"},
|
213
|
+
"name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true}
|
214
|
+
}
|
215
|
+
}
|
216
|
+
}
|
217
|
+
```
|
218
|
+
|
219
|
+
## Structure#all_flatten
|
220
|
+
> This method returns all available combinations of schema (built on subschema) without nesting (the same way as Structure#flatten method does)
|
221
|
+
|
222
|
+
Schema is the same as described in Structure#all_nested
|
223
|
+
```rb
|
224
|
+
schema.structure.all_flatten # =>
|
225
|
+
{
|
226
|
+
subschema: {
|
227
|
+
_errors: [],
|
228
|
+
"data" => {type: :object, required: true, present: true, json_path: "$.data"},
|
229
|
+
"data.id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, json_path: "$.data.id"},
|
230
|
+
"data.name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true},
|
231
|
+
"data.role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", mutates_schema: true},
|
232
|
+
"data.extra" => {type: :array, required: true, json_path: "$.data.extra[]"},
|
233
|
+
"data.extra.extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra"},
|
234
|
+
"data.test_field" => {required: true, present: true, json_path: "$.data.test_field"}
|
235
|
+
},
|
236
|
+
test_subschema: {
|
237
|
+
_errors: [],
|
238
|
+
"data" => {type: :object, required: true, present: true, json_path: "$.data"},
|
239
|
+
"data.id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, json_path: "$.data.id"},
|
240
|
+
"data.name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true},
|
241
|
+
"data.role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", mutates_schema: true},
|
242
|
+
"data.extra" => {type: :array, required: true, json_path: "$.data.extra[]"},
|
243
|
+
"data.extra.extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra"},
|
244
|
+
"data.test1" => {required: true, present: true, json_path: "$.data.test1"}
|
245
|
+
}
|
246
|
+
}
|
247
|
+
```
|
248
|
+
|
249
|
+
## Schema#walk
|
250
|
+
|
251
|
+
The `#walk` method can recursively walk a schema definition and extract meta data or field attributes.
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
schema_documentation = create_user_schema.walk do |field|
|
255
|
+
{type: field.meta_data[:type], label: field.meta_data[:label]}
|
256
|
+
end.output
|
257
|
+
|
258
|
+
# Returns
|
259
|
+
|
260
|
+
{
|
261
|
+
name: {type: :string, label: "User's full name"},
|
262
|
+
age: {type: :integer, label: "User's age"},
|
263
|
+
status: {type: :string, label: nil},
|
264
|
+
friends: [
|
265
|
+
{
|
266
|
+
name: {type: :string, label: "Friend full name"},
|
267
|
+
email: {type: nil, label: "Friend email"}
|
268
|
+
}
|
269
|
+
]
|
270
|
+
}
|
271
|
+
```
|
272
|
+
|
273
|
+
When passed a _symbol_, it will collect that key from field meta data.
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
schema_labels = create_user_schema.walk(:label).output
|
277
|
+
|
278
|
+
# returns
|
279
|
+
|
280
|
+
{
|
281
|
+
name: "User's full name",
|
282
|
+
age: "User's age",
|
283
|
+
status: nil,
|
284
|
+
friends: [
|
285
|
+
{name: "Friend full name", email: "Friend email"}
|
286
|
+
]
|
287
|
+
}
|
288
|
+
```
|
289
|
+
|
290
|
+
Potential uses for this are generating documentation (HTML, or [JSON Schema](http://json-schema.org/), [Swagger](http://swagger.io/), or maybe even mock API endpoints with example data.
|
291
|
+
|
data/docs/faq.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# FAQ
|
2
|
+
## Defaults
|
3
|
+
### Q: I need the child schema to be enriched with the specified defaults is parent key is absent.
|
4
|
+
```rb
|
5
|
+
schema do
|
6
|
+
field(:top_level).type(:string).required.default('top_level')
|
7
|
+
field(:nested).type(:object).required.schema do
|
8
|
+
field(:start_date).type(:datetime).required.default(->(a,b,c) { Time.now })
|
9
|
+
field(:ends_after).type(:integer).required.default(5)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
# usage
|
13
|
+
TestSchema.schema.resolve({}).output # => {:top_level=>"top_level", :nested=>nil, :configurations=>nil}
|
14
|
+
TestSchema.schema.resolve({nested: {}}).output # => {:top_level=>"top_level", :nested=>{:start_date=>#<DateTime: 2020-08-31T15:36:43+02:00 ((2459093j,49003s,0n),+7200s,2299161j)>, :ends_after=>5}, :configurations=>nil}
|
15
|
+
# I want resolving on {} to include the :nested structure.
|
16
|
+
```
|
17
|
+
|
18
|
+
### A: Set `.default({})` to your `:nested` field.
|
19
|
+
> Fields from nested schema are invoked only when the object for the schema exists.
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Form objects DSL
|
2
|
+
|
3
|
+
## DSL
|
4
|
+
You can use schemas and fields on their own, or include the `DSL` module in your own classes to define form objects.
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
require "parametric/dsl"
|
8
|
+
|
9
|
+
class CreateUserForm
|
10
|
+
include Paradocs::DSL
|
11
|
+
|
12
|
+
schema(:test) do
|
13
|
+
field(:name).type(:string).required
|
14
|
+
field(:email).policy(:email).required
|
15
|
+
field(:age).type(:integer)
|
16
|
+
subschema_by(:age) { |age| age > 18 ? :allow : :deny }
|
17
|
+
end
|
18
|
+
|
19
|
+
subschema_for(:test, name: :allow) { field(:role).options(["sign_in"]) }
|
20
|
+
subschema_for(:test, name: :deny) { field(:role).options([]) }
|
21
|
+
|
22
|
+
attr_reader :params, :errors
|
23
|
+
|
24
|
+
def initialize(input_data)
|
25
|
+
results = self.class.schema.resolve(input_data)
|
26
|
+
@params = results.output
|
27
|
+
@errors = results.errors
|
28
|
+
end
|
29
|
+
|
30
|
+
def run!
|
31
|
+
if !valid?
|
32
|
+
raise InvalidFormError.new(errors)
|
33
|
+
end
|
34
|
+
|
35
|
+
run
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid?
|
39
|
+
!errors.any?
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def run
|
45
|
+
User.create!(params)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
Form schemas can also be defined by passing another form or schema instance. This can be useful when building form classes in runtime.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
UserSchema = Paradocs::Schema.new do
|
54
|
+
field(:name).type(:string).present
|
55
|
+
field(:age).type(:integer)
|
56
|
+
end
|
57
|
+
|
58
|
+
class CreateUserForm
|
59
|
+
include Paradocs::DSL
|
60
|
+
# copy from UserSchema
|
61
|
+
schema UserSchema
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
## Form object inheritance
|
66
|
+
|
67
|
+
Sub classes of classes using the DSL will inherit schemas defined on the parent class.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class UpdateUserForm < CreateUserForm
|
71
|
+
# All field definitions in the parent are conserved.
|
72
|
+
# New fields can be defined
|
73
|
+
# or existing fields overriden
|
74
|
+
schema do
|
75
|
+
# make this field optional
|
76
|
+
field(:name).declared.present
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize(user, input_data)
|
80
|
+
super input_data
|
81
|
+
@user = user
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def run
|
86
|
+
@user.update params
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
data/docs/index.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Getting Started
|
2
|
+
## Introduction
|
3
|
+
> [Paradocs](https://github.com/mtkachenk0/paradocs) = Extended [Parametric gem](https://github.com/ismasan/parametric) + Documentation Generation
|
4
|
+
|
5
|
+

|
6
|
+
|
7
|
+
Declaratively define data schemas in your Ruby objects, and use them to whitelist, validate or transform inputs to your programs.
|
8
|
+
|
9
|
+
Useful for building self-documeting APIs, search or form objects. Or possibly as an alternative to Rails' _strong parameters_ (it has no dependencies on Rails and can be used stand-alone).
|
10
|
+
## Installation
|
11
|
+
```sh
|
12
|
+
$ gem install paradocs
|
13
|
+
```
|
14
|
+
|
15
|
+
Or with Bundler in your Gemfile.
|
16
|
+
```rb
|
17
|
+
gem 'paradocs'
|
18
|
+
```
|
19
|
+
|
20
|
+
## Try it out
|
21
|
+
|
22
|
+
Define a schema
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
schema = Paradocs::Schema.new do
|
26
|
+
field(:title).type(:string).present
|
27
|
+
field(:status).options(["draft", "published"]).default("draft")
|
28
|
+
field(:tags).type(:array)
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
Populate and use. Missing keys return defaults, if provided.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
form = schema.resolve(title: "A new blog post", tags: ["tech"])
|
36
|
+
|
37
|
+
form.output # => {title: "A new blog post", tags: ["tech"], status: "draft"}
|
38
|
+
form.errors # => {}
|
39
|
+
```
|
40
|
+
|
41
|
+
Undeclared keys are ignored.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
form = schema.resolve(foobar: "BARFOO", title: "A new blog post", tags: ["tech"])
|
45
|
+
|
46
|
+
form.output # => {title: "A new blog post", tags: ["tech"], status: "draft"}
|
47
|
+
```
|
48
|
+
|
49
|
+
Validations are run and errors returned
|
50
|
+
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
form = schema.resolve({})
|
54
|
+
form.errors # => {"$.title" => ["is required"]}
|
55
|
+
```
|
56
|
+
|
57
|
+
If options are defined, it validates that value is in options
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
form = schema.resolve({title: "A new blog post", status: "foobar"})
|
61
|
+
form.errors # => {"$.status" => ["expected one of draft, published but got foobar"]}
|
62
|
+
```
|
63
|
+
|
64
|
+
## Nested schemas
|
65
|
+
|
66
|
+
A schema can have nested schemas, for example for defining complex forms.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
person_schema = Paradocs::Schema.new do
|
70
|
+
field(:name).type(:string).required
|
71
|
+
field(:age).type(:integer)
|
72
|
+
field(:friends).type(:array).schema do
|
73
|
+
field(:name).type(:string).required
|
74
|
+
field(:email).policy(:email)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
It works as expected
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
results = person_schema.resolve(
|
83
|
+
name: "Joe",
|
84
|
+
age: "38",
|
85
|
+
friends: [
|
86
|
+
{name: "Jane", email: "jane@email.com"}
|
87
|
+
]
|
88
|
+
)
|
89
|
+
|
90
|
+
results.output # => {name: "Joe", age: 38, friends: [{name: "Jane", email: "jane@email.com"}]}
|
91
|
+
```
|
92
|
+
|
93
|
+
Validation errors use [JSON path](http://goessner.net/articles/JsonPath/) expressions to describe errors in nested structures
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
results = person_schema.resolve(
|
97
|
+
name: "Joe",
|
98
|
+
age: "38",
|
99
|
+
friends: [
|
100
|
+
{email: "jane@email.com"}
|
101
|
+
]
|
102
|
+
)
|
103
|
+
|
104
|
+
results.errors # => {"$.friends[0].name" => "is required"}
|
105
|
+
```
|
106
|
+
|