Skip to content

Commit 7b7351f

Browse files
authored
Root $id not overrided by S.extend (#54)
* Breaking change: Move to S.object().extend(schema)
1 parent 6ef9625 commit 7b7351f

File tree

11 files changed

+99
-24
lines changed

11 files changed

+99
-24
lines changed

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
language: node_js
22

33
node_js:
4-
- "11.10.1" # tmp workaround https://stackoverflow.com/questions/55059748/travis-jest-typeerror-cannot-assign-to-read-only-property-symbolsymbol-tostr
4+
- "13"
5+
- "11"
56
- "10"
67
- "8"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ A fluent API to generate JSON schemas (draft-07) for Node.js and browser.
1212
- Runtime errors for invalid options or keywords misuse
1313
- Javascript constants can be used in the JSON schema (e.g. _enum_, _const_, _default_ ) avoiding discrepancies between model and schema
1414
- Typescript definitions
15-
- Zero dependencies
1615
- Coverage 99%
1716

1817
## Install
@@ -289,10 +288,11 @@ const userBaseSchema = S.object()
289288
.prop('username', S.string())
290289
.prop('password', S.string())
291290

292-
const userSchema = S.extend(userBaseSchema)
291+
const userSchema = S.object()
293292
.prop('id', S.string().format('uuid'))
294293
.prop('createdAt', S.string().format('time'))
295294
.prop('updatedAt', S.string().format('time'))
295+
.extend(userBaseSchema)
296296

297297
console.log(userSchema)
298298
```

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,8 @@
6262
"lodash.merge": "^4.6.2",
6363
"prettier": "^1.14.3",
6464
"typescript": "^3.2.2"
65+
},
66+
"dependencies": {
67+
"deepmerge": "^4.2.2"
6568
}
6669
}

src/BaseSchema.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ const BaseSchema = (
365365
* @private It returns the internal schema data structure
366366
* @returns {object}
367367
*/
368+
// TODO LS if we implement S.raw() we can drop this hack because from a JSON we can rebuild a fluent-schema
368369
_getState: () => {
369370
return schema
370371
},

src/FluentSchema.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export interface ObjectSchema extends BaseSchema<ObjectSchema> {
114114
patternProperties: (options: PatternPropertiesOptions) => ObjectSchema
115115
dependencies: (options: DependenciesOptions) => ObjectSchema
116116
propertyNames: (value: JSONSchema) => ObjectSchema
117+
extend: (schema: ObjectSchema) => ObjectSchema
117118
}
118119

119120
export interface MixedSchema<T> extends BaseSchema<T> {
@@ -145,7 +146,6 @@ export interface S extends BaseSchema<S> {
145146
array: () => ArraySchema
146147
object: () => ObjectSchema
147148
null: () => NullSchema
148-
extend: (schema: ObjectSchema) => ObjectSchema
149149
//FIXME LS we should return only a MixedSchema
150150
mixed: <T>(types: TYPE[]) => MixedSchema<T> & any
151151
}

src/FluentSchema.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
'use strict'
2+
const merge = require('deepmerge')
3+
24
const { FORMATS, TYPES } = require('./utils')
35

46
const { BaseSchema } = require('./BaseSchema')
@@ -170,16 +172,6 @@ module.exports = {
170172
string: () => S().string(),
171173
mixed: types => S().mixed(types),
172174
object: () => S().object(),
173-
extend: schema => {
174-
if (!schema) {
175-
throw new Error("Schema can't be null or undefined")
176-
}
177-
if (!schema.isFluentSchema) {
178-
throw new Error("Schema isn't FluentSchema type")
179-
}
180-
const state = schema._getState()
181-
return S().object(state)
182-
},
183175
array: () => S().array(),
184176
boolean: () => S().boolean(),
185177
integer: () => S().integer(),

src/ObjectSchema.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict'
2+
const merge = require('deepmerge')
23
const { BaseSchema } = require('./BaseSchema')
34
const {
45
omit,
@@ -246,6 +247,19 @@ const ObjectSchema = ({ schema = initialState, ...options } = {}) => {
246247
})
247248
},
248249

250+
extend: base => {
251+
if (!base) {
252+
throw new Error("Schema can't be null or undefined")
253+
}
254+
if (!base.isFluentSchema) {
255+
throw new Error("Schema isn't FluentSchema type")
256+
}
257+
const state = base._getState()
258+
const extended = merge(state, schema)
259+
260+
return ObjectSchema({ schema: extended, ...options })
261+
},
262+
249263
/**
250264
* The "definitions" keywords provides a standardized location for schema authors to inline re-usable JSON Schemas into a more general schema.
251265
* There are no restrictions placed on the values within the array.

src/ObjectSchema.test.js

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -554,13 +554,21 @@ describe('ObjectSchema', () => {
554554
describe('extend', () => {
555555
it('extends a simple schema', () => {
556556
const base = S.object()
557+
.id('base')
558+
.title('base')
557559
.additionalProperties(false)
558560
.prop('foo', S.string().minLength(5))
559561

560-
const extended = S.extend(base).prop('bar', S.number())
562+
const extended = S.object()
563+
.id('extended')
564+
.title('extended')
565+
.prop('bar', S.number())
566+
.extend(base)
561567

562568
expect(extended.valueOf()).toEqual({
563569
$schema: 'http://json-schema.org/draft-07/schema#',
570+
$id: 'extended',
571+
title: 'extended',
564572
additionalProperties: false,
565573
properties: {
566574
foo: {
@@ -576,6 +584,7 @@ describe('ObjectSchema', () => {
576584
})
577585
it('extends a nested schema', () => {
578586
const base = S.object()
587+
.id('base')
579588
.additionalProperties(false)
580589
.prop(
581590
'foo',
@@ -590,10 +599,14 @@ describe('ObjectSchema', () => {
590599
.prop('bol', S.boolean().required())
591600
.prop('num', S.integer().required())
592601

593-
const extended = S.extend(base).prop('bar', S.number())
602+
const extended = S.object()
603+
.id('extended')
604+
.prop('bar', S.number())
605+
.extend(base)
594606

595607
expect(extended.valueOf()).toEqual({
596608
$schema: 'http://json-schema.org/draft-07/schema#',
609+
$id: 'extended',
597610
additionalProperties: false,
598611
properties: {
599612
foo: {
@@ -623,14 +636,63 @@ describe('ObjectSchema', () => {
623636
type: 'object',
624637
})
625638
})
639+
it('extends a schema with definitions', () => {
640+
const base = S.object()
641+
.id('base')
642+
.additionalProperties(false)
643+
.definition('def1', S.object().prop('some'))
644+
.definition('def2', S.object().prop('somethingElse'))
645+
.prop(
646+
'foo',
647+
S.object().prop(
648+
'id',
649+
S.string()
650+
.format('uuid')
651+
.required()
652+
)
653+
)
654+
.prop('str', S.string().required())
655+
.prop('bol', S.boolean().required())
656+
.prop('num', S.integer().required())
657+
658+
const extended = S.object()
659+
.id('extended')
660+
.definition('def1', S.object().prop('someExtended'))
661+
.prop('bar', S.number())
662+
.extend(base)
663+
664+
expect(extended.valueOf()).toEqual({
665+
$schema: 'http://json-schema.org/draft-07/schema#',
666+
definitions: {
667+
def1: { type: 'object', properties: { someExtended: {} } },
668+
def2: { type: 'object', properties: { somethingElse: {} } },
669+
},
670+
type: 'object',
671+
$id: 'extended',
672+
additionalProperties: false,
673+
properties: {
674+
foo: {
675+
type: 'object',
676+
properties: { id: { type: 'string', format: 'uuid' } },
677+
required: ['id'],
678+
},
679+
str: { type: 'string' },
680+
bol: { type: 'boolean' },
681+
num: { type: 'integer' },
682+
bar: { type: 'number' },
683+
},
684+
required: ['str', 'bol', 'num'],
685+
})
686+
})
687+
626688
it('throws an error if a schema is not provided', () => {
627689
expect(() => {
628-
S.extend()
690+
S.object().extend()
629691
}).toThrow("Schema can't be null or undefined")
630692
})
631693
it('throws an error if a schema is not provided', () => {
632694
expect(() => {
633-
S.extend('boom!')
695+
S.object().extend('boom!')
634696
}).toThrow("Schema isn't FluentSchema type")
635697
})
636698
})

src/example.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,11 @@ const userBaseSchema = S.object()
113113
.prop('username', S.string())
114114
.prop('password', S.string())
115115

116-
const userSchema = S.extend(userBaseSchema)
116+
const userSchema = S.object()
117117
.prop('id', S.string().format('uuid'))
118118
.prop('createdAt', S.string().format('time'))
119119
.prop('updatedAt', S.string().format('time'))
120+
.extend(userBaseSchema)
120121
.valueOf()
121122

122123
console.log(userSchema.valueOf())

src/types/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,19 @@ const schema = S.object()
5353
.writeOnly(true)
5454
.valueOf()
5555

56-
console.log(JSON.stringify(schema))
57-
console.log(S.object().isFluentSchema)
56+
console.log('example:\n', JSON.stringify(schema))
57+
console.log('isFluentSchema:', S.object().isFluentSchema)
5858

5959
const userBaseSchema = S.object()
6060
.additionalProperties(false)
6161
.prop('username', S.string())
6262
.prop('password', S.string())
6363

64-
const userSchema = S.extend(userBaseSchema)
64+
const userSchema = S.object()
6565
.prop('id', S.string().format('uuid'))
6666
.prop('createdAt', S.string().format('time'))
6767
.prop('updatedAt', S.string().format('time'))
68+
.extend(userBaseSchema)
6869
.valueOf()
6970

70-
console.log('\n user:', JSON.stringify(userSchema))
71+
console.log('user:\n', JSON.stringify(userSchema))

0 commit comments

Comments
 (0)