1- const _ = require ( 'lodash' ) ;
2- const Interface = require ( 'forest-express' ) ;
31const utils = require ( '../utils/schema' ) ;
2+ const Analyser = require ( '../utils/field-analyser' ) ;
43
54/* eslint-disable */
65function unflatten ( data ) {
@@ -25,288 +24,11 @@ function unflatten(data) {
2524module . exports = async ( model , opts ) => {
2625 const fields = [ ] ;
2726 const paths = unflatten ( model . schema . paths ) ;
28- const { Mongoose } = opts ;
29- // NOTICE: mongoose.base is used when opts.mongoose is not the default connection.
30- let schemaType ;
31-
32- function formatRef ( ref ) {
33- const referenceModel = utils . getReferenceModel ( opts , ref ) ;
34- if ( referenceModel ) {
35- return utils . getModelName ( referenceModel ) ;
36- }
37- Interface . logger . warn ( `Cannot find the reference "${ ref } " on the model "${ model . modelName } ".` ) ;
38- return null ;
39- }
40-
41- function detectReference ( fieldInfo ) {
42- if ( fieldInfo . options ) {
43- if ( fieldInfo . options . ref && fieldInfo . options . type ) {
44- return `${ formatRef ( fieldInfo . options . ref ) } ._id` ;
45- }
46- if ( _ . isArray ( fieldInfo . options . type ) && fieldInfo . options . type . length
47- && fieldInfo . options . type [ 0 ] . ref && fieldInfo . options . type [ 0 ] . type ) {
48- return `${ formatRef ( fieldInfo . options . type [ 0 ] . ref ) } ._id` ;
49- }
50- }
51- return null ;
52- }
53-
54- function objectType ( fieldsInfo , getType ) {
55- const type = { fields : [ ] } ;
56-
57- Object . keys ( fieldsInfo ) . forEach ( ( fieldName ) => {
58- const fieldInfo = fieldsInfo [ fieldName ] ;
59- const field = {
60- field : fieldName ,
61- type : getType ( fieldName ) ,
62- } ;
63-
64- if ( fieldName === '_id' ) {
65- field . isPrimaryKey = true ;
66- }
67-
68- if ( ! field . type ) { return ; }
69-
70- const ref = detectReference ( fieldInfo ) ;
71- if ( ref ) { field . reference = ref ; }
72-
73- if ( fieldInfo . enumValues && fieldInfo . enumValues . length ) {
74- field . enums = fieldInfo . enumValues ;
75- }
76-
77- if ( fieldInfo . enum
78- && Array . isArray ( fieldInfo . enum )
79- && fieldInfo . enum . length ) {
80- field . enums = fieldInfo . enum ;
81- }
82-
83- type . fields . push ( field ) ;
84- } ) ;
85-
86- return type ;
87- }
88-
89- function getTypeFromNative ( type ) {
90- if ( type instanceof Array ) {
91- if ( _ . isEmpty ( type ) ) {
92- return [ null ] ;
93- }
94- return [ getTypeFromNative ( type [ 0 ] . type || type [ 0 ] ) ] ;
95- }
96- if ( _ . isPlainObject ( type ) ) {
97- if ( _ . isEmpty ( type ) ) { return 'Json' ; }
98-
99- if ( type . type ) {
100- if ( type . enum ) {
101- // NOTICE: Detect enum values for Enums in subdocument arrays.
102- return 'Enum' ;
103- }
104- return getTypeFromNative ( type . type ) ;
105- }
106- return objectType ( type , ( key ) => getTypeFromNative ( type [ key ] ) ) ;
107- }
108- if ( _ . isFunction ( type ) && type . name === 'ObjectId' ) {
109- return 'String' ;
110- }
111- if ( type instanceof Mongoose . Schema ) {
112- return schemaType ( type ) ;
113- }
114-
115- switch ( type ) {
116- case String :
117- return 'String' ;
118- case Boolean :
119- return 'Boolean' ;
120- case Number :
121- return 'Number' ;
122- case Date :
123- return 'Date' ;
124- default :
125- return null ;
126- }
127- }
128-
129- function getTypeFromMongoose ( fieldInfo ) {
130- if ( _ . isPlainObject ( fieldInfo ) && ! fieldInfo . path ) {
131- // Deal with Object
132- return objectType ( fieldInfo , ( fieldName ) => getTypeFromMongoose ( fieldInfo [ fieldName ] ) ) ;
133- }
134- if ( fieldInfo . instance === 'Array' ) {
135- if ( _ . isEmpty ( fieldInfo . options . type ) && ! _ . isUndefined ( fieldInfo . options . type ) ) {
136- return 'Json' ;
137- }
138-
139- // Deal with Array
140- if ( fieldInfo . caster . instance && ( fieldInfo . caster . options . ref
141- || _ . keys ( fieldInfo . caster . options ) . length === 0 ) ) {
142- return [ getTypeFromMongoose ( fieldInfo . caster ) ] ;
143- }
144- if ( fieldInfo . options . type [ 0 ] instanceof Mongoose . Schema ) {
145- // Schema
146- return [ schemaType ( fieldInfo . options . type [ 0 ] ) ] ;
147- }
148-
149- // NOTICE: Object with `type` reserved keyword.
150- // See: https://mongoosejs.com/docs/schematypes.html#type-key
151- if ( fieldInfo . options . type [ 0 ] instanceof Object
152- && fieldInfo . options . type [ 0 ] . type
153- // NOTICE: Bypass for schemas like `[{ type: {type: String}, ... }]` where "type" is used
154- // as property, and thus we are in the case of an array of embedded documents.
155- // See: https://mongoosejs.com/docs/faq.html#type-key
156- && ! fieldInfo . options . type [ 0 ] . type . type ) {
157- return [ getTypeFromNative ( fieldInfo . options . type [ 0 ] ) ] ;
158- }
159-
160- // Object
161- return [ objectType ( fieldInfo . options . type [ 0 ] , ( key ) =>
162- getTypeFromNative ( fieldInfo . options . type [ 0 ] [ key ] ) ) ] ;
163- }
164- if ( fieldInfo . enumValues && fieldInfo . enumValues . length ) {
165- return 'Enum' ;
166- }
167- if ( fieldInfo . instance === 'ObjectID' ) {
168- // Deal with ObjectID
169- return 'String' ;
170- }
171- if ( fieldInfo . instance === 'Embedded' ) {
172- return objectType ( fieldInfo . schema . obj , ( fieldName ) =>
173- getTypeFromNative ( fieldInfo . schema . obj [ fieldName ] ) ) ;
174- }
175- if ( fieldInfo . instance === 'Mixed' ) {
176- // Deal with Mixed object
177-
178- // NOTICE: Object and {} are detected as Json type as they don't have schema.
179- if ( _ . isEmpty ( fieldInfo . options . type ) && ! _ . isUndefined ( fieldInfo . options . type ) ) {
180- return 'Json' ;
181- }
182- if ( _ . isEmpty ( fieldInfo . options ) && ! _ . isUndefined ( fieldInfo . options ) ) {
183- return 'Json' ;
184- }
185-
186- return null ;
187- }
188- // Deal with primitive type
189- return fieldInfo . instance
190- || ( fieldInfo . options && getTypeFromNative ( fieldInfo . options . type ) )
191- || null ;
192- }
193-
194- schemaType = ( type ) => ( {
195- fields : _ . map ( type . paths , ( fieldType , fieldName ) => {
196- const field = {
197- field : fieldName ,
198- type : getTypeFromMongoose ( fieldType ) ,
199- } ;
200-
201- if ( fieldName === '_id' ) {
202- field . isPrimaryKey = true ;
203- }
204-
205- if ( fieldType . enumValues && fieldType . enumValues . length ) {
206- field . enums = fieldType . enumValues ;
207- }
208-
209- return field ;
210- } ) ,
211- } ) ;
212-
213- function getRequired ( fieldInfo ) {
214- return fieldInfo . isRequired === true
215- || (
216- fieldInfo . path === '_id'
217- && ! fieldInfo . options . auto
218- && fieldInfo . options . type !== Mongoose . ObjectId
219- ) ;
220- }
221-
222- function getValidations ( fieldInfo ) {
223- const validations = [ ] ;
224-
225- if ( fieldInfo . validators && fieldInfo . validators . length > 0 ) {
226- _ . each ( fieldInfo . validators , ( validator ) => {
227- if ( validator . type === 'required' ) {
228- validations . push ( {
229- type : 'is present' ,
230- } ) ;
231- }
232-
233- if ( validator . type === 'minlength' ) {
234- validations . push ( {
235- type : 'is longer than' ,
236- value : validator . minlength ,
237- } ) ;
238- }
239-
240- if ( validator . type === 'maxlength' ) {
241- validations . push ( {
242- type : 'is shorter than' ,
243- value : validator . maxlength ,
244- } ) ;
245- }
246-
247- if ( validator . type === 'min' ) {
248- validations . push ( {
249- type : 'is greater than' ,
250- value : validator . min ,
251- } ) ;
252- }
253-
254- if ( validator . type === 'max' ) {
255- validations . push ( {
256- type : 'is less than' ,
257- value : validator . max ,
258- } ) ;
259- }
260- } ) ;
261- }
262-
263- return validations ;
264- }
265-
266- function getFieldSchema ( path ) {
267- const fieldInfo = paths [ path ] ;
268-
269- const schema = { field : path , type : getTypeFromMongoose ( fieldInfo ) } ;
270-
271- const ref = detectReference ( fieldInfo ) ;
272- if ( ref ) { schema . reference = ref ; }
273-
274- if ( fieldInfo . enumValues && fieldInfo . enumValues . length ) {
275- schema . enums = fieldInfo . enumValues ;
276- }
277-
278- // NOTICE: Create enums from caster (for ['Enum'] type).
279- if ( fieldInfo . caster && fieldInfo . caster . enumValues && fieldInfo . caster . enumValues . length ) {
280- schema . enums = fieldInfo . caster . enumValues ;
281- }
282-
283- const isRequired = getRequired ( fieldInfo ) ;
284- if ( isRequired ) {
285- schema . isRequired = isRequired ;
286- }
287-
288- if ( path === '_id' ) {
289- schema . isPrimaryKey = true ;
290- }
291-
292- if ( fieldInfo . options && ! _ . isNull ( fieldInfo . options . default )
293- && ! _ . isUndefined ( fieldInfo . options . default )
294- && ! _ . isFunction ( fieldInfo . options . default ) ) {
295- schema . defaultValue = fieldInfo . options . default ;
296- }
297-
298- schema . validations = getValidations ( fieldInfo ) ;
299-
300- if ( schema . validations . length === 0 ) {
301- delete schema . validations ;
302- }
303-
304- return schema ;
305- }
27+ const analyser = new Analyser ( model , opts ) ;
30628
30729 Object . keys ( paths ) . forEach ( async ( path ) => {
30830 if ( path === '__v' ) { return ; }
309- const field = getFieldSchema ( path ) ;
31+ const field = analyser . getFieldSchema ( path , paths [ path ] ) ;
31032 fields . push ( field ) ;
31133 } ) ;
31234
0 commit comments