1
+ /**
2
+ * Created by teklof on 27.8.14.
3
+ */
4
+
5
+ var _ = require ( 'lodash' ) ;
6
+ var ptr = require ( 'json-ptr' ) ;
7
+ var RandExpr = require ( 'randexp' ) ;
8
+
9
+ function JsonFromSchema ( schemas ) {
10
+ this . _schemas = _ . reduce ( schemas , function ( acc , schema ) {
11
+ if ( ! schema . id ) {
12
+ throw new Error ( "All schemas need ids" ) ;
13
+ }
14
+ var idLen = schema . id . length ;
15
+ var id = schema . id [ idLen - 1 ] === '#' ? schema . id . substring ( 0 , idLen - 1 ) : schema . id ;
16
+ acc [ id ] = _ . cloneDeep ( schema ) ;
17
+ return acc ;
18
+ } , { } ) ;
19
+ var self = this ;
20
+ _ . each ( this . _schemas , function ( schema ) {
21
+ self . _resolveRefs ( schema , self . _schemas ) ;
22
+ } ) ;
23
+ }
24
+
25
+ // JS uses double precision floats (52 bits in the mantissa), so the maximum representable integer is 2^52
26
+ var MAX_INT = Math . pow ( 2 , 52 ) ;
27
+
28
+ JsonFromSchema . prototype . _resolveRefs = exports . _resolveRefs = function _resolveRefs ( schema , schemasByIds , topSchema ) {
29
+ function isLocal ( ref ) {
30
+ return ref [ 0 ] === '#' ;
31
+ }
32
+
33
+ topSchema = topSchema || schema ;
34
+ var pointed ;
35
+ var $ref = schema . $ref ;
36
+ if ( $ref ) {
37
+ if ( isLocal ( $ref ) ) { // JSON pointer
38
+ pointed = ptr . create ( $ref ) . get ( topSchema ) ;
39
+ } else { // not a JSON pointer so blindly assume it's an ID
40
+ pointed = schemasByIds [ $ref ] ;
41
+ }
42
+
43
+ if ( ! pointed ) {
44
+ throw new ReferenceError ( "Pointer " + $ref + " didn't point to anything?" ) ;
45
+ }
46
+
47
+ if ( pointed . $ref ) {
48
+ /* if the schema being pointed to isn't the one we started in, topSchema needs to be set to the schema being
49
+ pointed to so its JSON pointers can be dereferenced properly */
50
+ _resolveRefs ( pointed , schemasByIds , isLocal ( pointed . $ref ) ? topSchema : pointed ) ;
51
+ }
52
+
53
+ delete schema . $ref ;
54
+ _ . assign ( schema , pointed ) ;
55
+ }
56
+
57
+ // this schema didn't have a reference, so go through all subschemas
58
+ _ . each ( schema , function ( subSchema , key ) {
59
+ if ( _ . isPlainObject ( subSchema ) ) {
60
+ _resolveRefs ( subSchema , schemasByIds , topSchema ) ;
61
+ }
62
+ } ) ;
63
+
64
+ } ;
65
+
66
+ JsonFromSchema . prototype . _generators = {
67
+ '_randomNumber' : function _randomNumber ( schema , options ) {
68
+ options = options || { } ;
69
+ var integer = schema . type === 'integer'
70
+ , minimum = schema . minimum || ( integer ? - MAX_INT : - MAX_INT * 0.671 ) // note: just random constants to make float generation work
71
+ , maximum = schema . maximum || ( integer ? MAX_INT : MAX_INT * 0.5663 ) ;
72
+
73
+ if ( options . exclusiveMinimum && integer ) { // TODO: floats
74
+ minimum += 1 ;
75
+ }
76
+
77
+ if ( options . exclusiveMaximum && integer ) { // TODO: floats
78
+ maximum -= 1 ;
79
+ }
80
+
81
+ return _ . random ( minimum , maximum , schema . type === 'number' ) ;
82
+ }
83
+
84
+ , 'boolean' : function ( ) {
85
+ return ! ! _ . random ( 1 ) ;
86
+ }
87
+
88
+ , 'string' : function ( schema , options ) {
89
+ options = options || { } ;
90
+ schema = schema || { } ;
91
+ var minCharCode = options . minCharCode || 32
92
+ , maxCharCode = options . maxCharCode || 126
93
+ , minLength = schema . minLength || 0
94
+ , maxLength = schema . maxLength || 32 ;
95
+
96
+ if ( schema . enum ) {
97
+ return this . enum ( schema ) ;
98
+ }
99
+
100
+ if ( schema . pattern ) {
101
+ var re = new RandExpr ( schema . pattern ) ;
102
+ re . anyRandChar = function ( ) {
103
+ return String . fromCharCode ( _ . random ( minCharCode , maxCharCode ) ) ;
104
+ } ;
105
+ // FIXME: randexp's max doesn't work as I expected; this needs a fix so it doesn't generate strings that go over maxLength
106
+ re . max = maxLength ;
107
+ return re . gen ( ) ;
108
+ }
109
+
110
+ var charCodes = _ . times ( _ . random ( minLength , maxLength ) , function ( ) {
111
+ return _ . random ( minCharCode , maxCharCode ) ;
112
+ } ) ;
113
+ return String . fromCharCode . apply ( null , charCodes ) ;
114
+ }
115
+
116
+ , 'number' : function ( schema , options ) {
117
+ schema = schema || { type : 'number' } ;
118
+ return this . _randomNumber ( schema , options ) ;
119
+ }
120
+
121
+ , 'integer' : function ( schema , options ) {
122
+ schema = schema || { type : 'integer' } ;
123
+ return this . _randomNumber ( schema , options ) ;
124
+ }
125
+
126
+ , 'enum' : function ( schema ) {
127
+ return _ . sample ( schema . enum ) ;
128
+ }
129
+
130
+ , 'array' : function ( schema , options ) {
131
+ options = options || { } ;
132
+ schema = schema || { } ;
133
+ var itemSchema = schema . items || { type : 'string' }
134
+ , itemType = itemSchema . type || ( 'enum' in itemSchema && 'enum' )
135
+ , minItems = schema . minItems || 0
136
+ , maxItems = schema . maxItems || 10
137
+ , len = _ . random ( minItems , maxItems ) ;
138
+
139
+ var self = this ;
140
+ return _ . times ( len , function ( ) {
141
+ return self [ itemType ] ( itemSchema , options ) ;
142
+ } ) ;
143
+ }
144
+
145
+ , '_randomObject' : function ( options ) {
146
+ var numKeys = _ . random ( options . minRandomKeys || 0 , options . maxRandomKeys || 10 ) ;
147
+ var self = this ;
148
+
149
+ var gens = [
150
+ _ . partial ( this . array , { items : { type : 'integer' } } )
151
+ , _ . partial ( this . array , { items : { type : 'number' } } )
152
+ , _ . partial ( this . array , { items : { type : 'string' } } )
153
+ , this . string
154
+ , this . integer
155
+ , this . number
156
+ ] ;
157
+
158
+ return _ ( _ . times ( numKeys , function ( ) {
159
+ return self . string ( ) ;
160
+ } ) ) . reduce ( function ( acc , key ) {
161
+ acc [ key ] = _ . sample ( gens ) . apply ( self ) ;
162
+ return acc ;
163
+ } , { } ) . valueOf ( ) ;
164
+ }
165
+
166
+ , 'object' : function ( schema , options ) {
167
+
168
+ options = options || { } ;
169
+ schema = schema || { } ;
170
+
171
+ var self = this
172
+ , required = schema . required || [ ]
173
+ , props = schema . properties && Object . keys ( schema . properties ) || [ ]
174
+ , patternProps = schema . patternProperties && Object . keys ( schema . patternProperties ) || [ ]
175
+ , additionals = "additionalProperties" in schema ? ! ! schema . additionalProperties : true
176
+ , maxPatternProps = options . maxPatternProps || 10
177
+ , nonRequiredProps = _ . difference ( props , required )
178
+ // generate all required properties plus a random amount of non-required properties
179
+ , propsToGenerate = _ . union ( required , _ . sample ( nonRequiredProps , _ . random ( nonRequiredProps . length ) ) ) ;
180
+
181
+ var obj = _ . reduce ( propsToGenerate , function ( acc , propName ) {
182
+ var propSchema = schema . properties [ propName ] ;
183
+
184
+ var type = propSchema . type || propSchema . enum && 'enum' ;
185
+ acc [ propName ] = self [ type ] ( propSchema , options ) ;
186
+ return acc ;
187
+ } , { } ) ;
188
+
189
+ if ( patternProps . length ) {
190
+ var nPats = _ . random ( maxPatternProps ) ;
191
+
192
+ var ppObj = _ ( _ . times ( nPats , function ( ) {
193
+ return _ . sample ( patternProps ) ;
194
+ } ) ) . reduce ( function ( acc , propPattern ) {
195
+ var propSchema = schema . patternProperties [ propPattern ] ;
196
+ var propName = self . string ( { pattern : propPattern } ) ;
197
+ var type = propSchema . type || propSchema . enum && 'enum' ;
198
+ acc [ propName ] = self [ type ] ( propSchema , options ) ;
199
+ return acc ;
200
+ } , { } ) . valueOf ( ) ;
201
+ _ . defaults ( obj , ppObj ) ;
202
+ }
203
+
204
+ if ( additionals ) { // if additionalProperties is true, add some random properties to the object
205
+ _ . defaults ( obj , this . _randomObject ( options ) ) ;
206
+ }
207
+
208
+
209
+ return obj ;
210
+ }
211
+ } ;
212
+
213
+ JsonFromSchema . prototype . generate = function generate ( schemaId , options ) {
214
+ var schema = this . _schemas [ schemaId ] ;
215
+
216
+ if ( ! schema ) {
217
+ throw new ReferenceError ( "No schema with ID " + schemaId + " registered" ) ;
218
+ }
219
+
220
+ var type = schema . type || schema . enum && 'enum' ;
221
+ return this . _generators [ type ] ( schema , options ) ;
222
+ } ;
223
+
224
+ exports . JsonFromSchema = JsonFromSchema ;
0 commit comments