Skip to content

Commit 92d1c54

Browse files
committed
Add required in nested prop. It works somehow but it's too fragile (issue #19)
1 parent 353ea92 commit 92d1c54

File tree

7 files changed

+143
-27
lines changed

7 files changed

+143
-27
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,20 @@ const schema = FluentSchema()
3434
FluentSchema()
3535
.asString()
3636
.format(FORMATS.EMAIL)
37+
.required()
3738
)
38-
.required()
3939
.prop(
4040
'password',
4141
FluentSchema()
4242
.asString()
4343
.minLength(8)
4444
)
45-
.required()
4645
.prop(
4746
'role',
4847
FluentSchema()
4948
.enum(['ADMIN', 'USER'])
5049
.default('USER')
50+
.required()
5151
)
5252
.definition(
5353
'address',

docs/API.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ There are no restrictions placed on the values within the array.</p>
4848
<p><a href="reference">https://json-schema.org/latest/json-schema-validation.html#rfc.section.10.2</a></p>
4949
</dd>
5050
<dt><a href="#required">required()</a> ⇒ <code><a href="#FluentSchema">FluentSchema</a></code></dt>
51-
<dd><p>Required&#39; has to be chained to a property:
51+
<dd><p>Required has to be chained to a property:
5252
Examples:</p>
5353
<ul>
5454
<li>FluentSchema().prop(&#39;prop&#39;).required()</li>
@@ -375,7 +375,7 @@ There are no restrictions placed on the value of this keyword.
375375

376376
## required() ⇒ [<code>FluentSchema</code>](#FluentSchema)
377377

378-
Required' has to be chained to a property:
378+
Required has to be chained to a property:
379379
Examples:
380380

381381
- FluentSchema().prop('prop').required()

src/FluentSchema.integration.test.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,19 +187,23 @@ describe('FluentSchema', () => {
187187
)
188188
.prop('username')
189189
.required()
190-
.prop('password')
191-
.required()
190+
.prop(
191+
'password',
192+
FluentSchema()
193+
.asString()
194+
.required()
195+
)
192196
.prop('address')
193197
.ref('#address')
194198
.required()
195199
.prop(
196200
'role',
197201
FluentSchema()
198202
.id('http://foo.com/role')
203+
.required()
199204
.prop('name')
200205
.prop('permissions')
201206
)
202-
.required()
203207
.prop('age')
204208
.asNumber()
205209
.valueOf()

src/FluentSchema.js

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ const {
66
isFluentSchema,
77
last,
88
patchIdsWithParentId,
9+
appendRequired,
910
FORMATS,
11+
REQUIRED,
1012
} = require('./utils')
1113

1214
const initialState = {
@@ -166,9 +168,24 @@ const FluentSchema = (
166168
let attributes = props.valueOf()
167169
const $id =
168170
attributes.$id || (options.generateIds ? `#${target}/${name}` : undefined)
169-
attributes = isFluentSchema(props)
170-
? patchIdsWithParentId({ schema: attributes, parentId: $id, ...options })
171-
: attributes
171+
if (isFluentSchema(props)) {
172+
attributes = patchIdsWithParentId({
173+
schema: attributes,
174+
parentId: $id,
175+
...options,
176+
})
177+
178+
const [schemaPatched, attributesPatched] = appendRequired({
179+
schema,
180+
attributes: {
181+
...attributes,
182+
name,
183+
},
184+
})
185+
186+
schema = schemaPatched
187+
attributes = attributesPatched
188+
}
172189

173190
const {
174191
type = hasCombiningKeywords(attributes) ? undefined : 'string',
@@ -270,7 +287,7 @@ const FluentSchema = (
270287
// object
271288
maxProperties !== undefined ? { maxProperties } : undefined,
272289
minProperties !== undefined ? { minProperties } : undefined,
273-
required !== undefined ? { required } : undefined,
290+
required && required.length > 0 ? { required } : undefined,
274291
properties !== undefined ? { properties } : undefined,
275292
patternProperties !== undefined
276293
? { patternProperties }
@@ -326,7 +343,7 @@ const FluentSchema = (
326343
},
327344

328345
/**
329-
* Required' has to be chained to a property:
346+
* Required has to be chained to a property:
330347
* Examples:
331348
* - FluentSchema().prop('prop').required()
332349
* - FluentSchema().prop('prop', FluentSchema().asNumber()).required()
@@ -337,12 +354,11 @@ const FluentSchema = (
337354

338355
required: () => {
339356
const currentProp = last(schema.properties)
340-
if (!currentProp)
341-
throw new Error(
342-
"'required' has to be chained to a prop: \nExamples: \n- FluentSchema().prop('prop').required() \n- FluentSchema().prop('prop', FluentSchema().asNumber()).required()"
343-
)
357+
const required = currentProp
358+
? [...schema.required, currentProp.name]
359+
: [REQUIRED]
344360
return FluentSchema({
345-
schema: { ...schema, required: [...schema.required, currentProp.name] },
361+
schema: { ...schema, required },
346362
options,
347363
})
348364
},

src/FluentSchema.test.js

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -380,14 +380,74 @@ describe('FluentSchema', () => {
380380
).toEqual([prop])
381381
})
382382

383-
it('invalid', () => {
384-
expect(() => {
385-
FluentSchema()
386-
.asString()
387-
.required()
388-
}).toThrow(
389-
"'required' has to be chained to a prop: \nExamples: \n- FluentSchema().prop('prop').required() \n- FluentSchema().prop('prop', FluentSchema().asNumber()).required()"
390-
)
383+
it('nested', () => {
384+
const prop = 'foo'
385+
const schema = FluentSchema()
386+
.prop(
387+
prop,
388+
FluentSchema()
389+
.asNumber()
390+
.required()
391+
)
392+
.valueOf()
393+
expect(schema.required).toEqual([prop])
394+
expect(schema.properties[prop]).toEqual({ type: 'number' })
395+
})
396+
397+
it('deep nested', () => {
398+
const prop = 'foo'
399+
const schema = FluentSchema()
400+
.prop(
401+
prop,
402+
FluentSchema()
403+
.required()
404+
.prop('bar')
405+
.required()
406+
)
407+
.valueOf()
408+
expect(schema).toEqual({
409+
$schema: 'http://json-schema.org/draft-07/schema#',
410+
properties: {
411+
foo: {
412+
properties: { bar: { type: 'string' } },
413+
required: ['bar'],
414+
type: 'object',
415+
},
416+
},
417+
required: ['foo'],
418+
type: 'object',
419+
})
420+
})
421+
422+
it('multiple deep nested', () => {
423+
const schema = FluentSchema()
424+
.prop(
425+
'foo',
426+
FluentSchema()
427+
.required()
428+
.prop('bar')
429+
.required()
430+
)
431+
.prop(
432+
'prop',
433+
FluentSchema()
434+
.asString()
435+
.required()
436+
)
437+
.valueOf()
438+
expect(schema).toEqual({
439+
$schema: 'http://json-schema.org/draft-07/schema#',
440+
properties: {
441+
foo: {
442+
properties: { bar: { type: 'string' } },
443+
required: ['bar'],
444+
type: 'object',
445+
},
446+
prop: { type: 'string' },
447+
},
448+
required: ['foo', 'prop'],
449+
type: 'object',
450+
})
391451
})
392452
})
393453

src/example.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ const userSchema = FluentSchema()
1111
FluentSchema()
1212
.asString()
1313
.format(FORMATS.EMAIL)
14+
.required()
1415
)
15-
.required()
1616
.prop(
1717
'password',
1818
FluentSchema()
1919
.asString()
2020
.minLength(8)
21+
.required()
2122
)
22-
.required()
2323
.prop(
2424
'role',
2525
FluentSchema()

src/utils.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const flat = array =>
3939
}
4040
}, {})
4141

42+
const REQUIRED = Symbol('required')
43+
4244
const RELATIVE_JSON_POINTER = 'relative-json-pointer'
4345
const JSON_POINTER = 'json-pointer'
4446
const UUID = 'uuid'
@@ -96,13 +98,47 @@ const patchIdsWithParentId = ({ schema, generateIds, parentId }) => {
9698
}
9799
}
98100

101+
const appendRequired = ({
102+
attributes: { name, required, ...attributes },
103+
schema,
104+
}) => {
105+
const { schemaRequired, attributeRequired } = (required || []).reduce(
106+
(memo, item) => {
107+
return item === REQUIRED
108+
? {
109+
...memo,
110+
// append prop name to the schema.required
111+
schemaRequired: [...memo.schemaRequired, name],
112+
}
113+
: {
114+
...memo,
115+
// propagate required attributes
116+
attributeRequired: [...memo.attributeRequired, item],
117+
}
118+
},
119+
{ schemaRequired: [], attributeRequired: [] }
120+
)
121+
122+
const schemaPatched = {
123+
...schema,
124+
required: [...schema.required, ...schemaRequired],
125+
}
126+
const attributesPatched = {
127+
...attributes,
128+
required: attributeRequired,
129+
}
130+
return [schemaPatched, attributesPatched]
131+
}
132+
99133
module.exports = {
100134
isFluentSchema,
101135
hasCombiningKeywords,
102136
last,
103137
flat,
104138
omit,
139+
REQUIRED,
105140
deepOmit,
106141
patchIdsWithParentId,
142+
appendRequired,
107143
FORMATS,
108144
}

0 commit comments

Comments
 (0)