UNPKG

8.97 kBJavaScriptView Raw
1
2const { kebabCase, standardize } = require('@src/utils/string-utils');
3const CliError = require('@src/exceptions/cli-error');
4const { customizationMap } = require('@src/commands/smapi/customizations/parameters-map');
5
6const BODY_PATH_DELIMITER = '>>>';
7const ARRAY_SPLIT_DELIMITER = ',';
8const MAX_NESTED_PROPERTIES = 10;
9
10class CliCustomizationProcessor {
11 /**
12 * Processes each operation name.
13 * @param {string} operationName Operation name.
14 */
15 processOperationName(operationName) {
16 return kebabCase(operationName.substring(0, operationName.length - 2));
17 }
18
19 /**
20 * Processes each operation.
21 * @param {string} operationName Operation name.
22 * @param {Object} operation Operation object.
23 */
24 processOperation(operationName, operation) {
25 operation.customizationMetadata.flatParamsMap = new Map();
26 }
27
28 /**
29 * Processes each parameter.
30 * @param {Object} parameter Parameter object.
31 * @param {Object} parentOperation Parent object of the parameter.
32 * @param {Map} definitions Map with all Swagger definitions.
33 */
34 processParameter(parameter, parentOperation, definitions) {
35 if (parameter.in === 'body') {
36 this._processBodyParameter(parameter, parentOperation, definitions);
37 } else {
38 this._processNonBodyParameter(parameter, parentOperation, definitions);
39 }
40 }
41
42 _processBodyParameter(parameter, parentOperation, definitions) {
43 const rootName = parameter.name;
44 const { description, required } = parameter;
45 const { properties } = this._getDefinitionSchema(parameter.schema.$ref, definitions);
46 const customization = customizationMap.get(parameter.name);
47 if (!properties || (customization && customization.skipUnwrap)) {
48 const customizedParameter = { name: parameter.name, description, required, json: true };
49 this._addCustomizedParameter(parentOperation.customizationMetadata, customizedParameter);
50 } else if (customization && customization.customParameters) {
51 customization.customParameters.forEach(customParameter => {
52 this._addCustomizedParameter(parentOperation.customizationMetadata, customParameter);
53 });
54 } else {
55 this._processNestedBodyParam(parameter, properties, parentOperation.customizationMetadata, rootName, definitions);
56 }
57 }
58
59 _processNonBodyParameter(parameter, parentOperation, definitions) {
60 let customizedParameter = { ...parameter };
61 const { type } = parameter;
62 let enumOptions = parameter.enum;
63 let { description } = parameter;
64 if (parameter.items && parameter.items.$ref) {
65 const schema = this._getDefinitionSchema(parameter.items.$ref, definitions);
66 const enumValue = this._mapEnum(parameter.description, schema);
67 enumOptions = enumValue.enumOptions;
68 description = enumValue.description;
69 }
70 customizedParameter = { ...customizedParameter, description, type, enum: enumOptions };
71 this._addCustomizedParameter(parentOperation.customizationMetadata, customizedParameter);
72 }
73
74 _mapEnum(parentDescription, schema) {
75 const description = schema.description || parentDescription;
76 const enumOptions = schema.enum;
77 return { description, enumOptions };
78 }
79
80 _addCustomizedParameter(customizationMetadata, customizedParameter) {
81 const customization = customizationMap.get(customizedParameter.name) || {};
82 const skip = customization.skip || false;
83 if (skip) return;
84
85 const isArray = customizedParameter.type === 'array' && !customizedParameter.json; // the array of object is treated as json type
86 const isNumber = ['number', 'integer'].includes(customizedParameter.type);
87 const isBoolean = customizedParameter.type === 'boolean';
88 const flags = { isArray, isNumber, isBoolean };
89 Object.keys(flags).forEach(key => {
90 if (flags[key]) {
91 customizedParameter[key] = true;
92 }
93 });
94 customizationMetadata.flatParamsMap.set(standardize(customizedParameter.name), customizedParameter);
95 }
96
97 _getDefinitionSchema(ref, definitions) {
98 const schema = ref.replace('#/definitions/', '');
99 return definitions.get(schema);
100 }
101
102 _shouldParseAsJson(property, definitions) {
103 if (property.type === 'object') return true;
104 if (property.type === 'array' && '$ref' in property.items) {
105 const schema = this._getDefinitionSchema(property.items.$ref, definitions);
106 if (schema.type === 'object') return true;
107 }
108 return false;
109 }
110
111 _isRequired(definition, key, parentRequired) {
112 if (definition.required) {
113 return definition.required.includes(key);
114 }
115 return !!parentRequired;
116 }
117
118 _appendSecondLevelProperty(customizationMetadata, parentName, rootName, secondLevelDefinition, parentRequired, definitions) {
119 let customizedParameter;
120 const parentDescription = secondLevelDefinition.description;
121 this._ensureNumberOfProperties(rootName, secondLevelDefinition.properties);
122 Object.keys(secondLevelDefinition.properties || []).forEach(key => {
123 const property = secondLevelDefinition.properties[key];
124 let enumOptions = null;
125 const { type } = property;
126 let description = property.description || parentDescription;
127 if (property.$ref) {
128 const schema = this._getDefinitionSchema(property.$ref, definitions);
129 if (!schema.enum) {
130 throw new CliError('Cannot unwrap more then 2 levels deep. Please use customParameters or '
131 + `set skipUnwrap for parameter ${rootName} in lib/commands/smapi/customizations/parameters.json.`);
132 }
133 enumOptions = schema.enum;
134 description = schema.description || description;
135 }
136 const json = this._shouldParseAsJson(property, definitions);
137 const bodyPath = `${parentName}${BODY_PATH_DELIMITER}${key}`;
138 const required = this._isRequired(secondLevelDefinition, key, parentRequired);
139 customizedParameter = { name: `${parentName} ${key}`, description, rootName, required, bodyPath, enum: enumOptions, json, type };
140 this._addCustomizedParameter(customizationMetadata, customizedParameter);
141 });
142 if (secondLevelDefinition.enum) {
143 const { type } = secondLevelDefinition;
144 const { description, enumOptions } = this._mapEnum(parentDescription, secondLevelDefinition);
145 customizedParameter = {
146 name: parentName,
147 description,
148 rootName,
149 required: parentRequired,
150 bodyPath: parentName,
151 enum: enumOptions,
152 type
153 };
154 this._addCustomizedParameter(customizationMetadata, customizedParameter);
155 }
156 }
157
158 _processNestedBodyParam(param, properties, customizationMetadata, rootName, definitions) {
159 this._ensureNumberOfProperties(rootName, properties);
160 const parentRequired = this._getDefinitionSchema(param.schema.$ref, definitions).required;
161 Object.keys(properties).forEach(key => {
162 const property = properties[key];
163 const isParentRequired = parentRequired && parentRequired.includes(key);
164 if (property.$ref) {
165 const secondLevelDefinition = this._getDefinitionSchema(property.$ref, definitions);
166 const isRequired = this._isRequired(secondLevelDefinition, key, isParentRequired);
167 this._appendSecondLevelProperty(customizationMetadata, key, rootName, secondLevelDefinition, isRequired, definitions);
168 } else {
169 const { description, type } = property;
170 const json = this._shouldParseAsJson(property, definitions);
171 const parentDefinition = this._getDefinitionSchema(param.schema.$ref, definitions);
172 const required = this._isRequired(parentDefinition, key, isParentRequired);
173 const customizedParameter = { name: key, description, rootName, required, bodyPath: key, json, type };
174 this._addCustomizedParameter(customizationMetadata, customizedParameter);
175 }
176 });
177 }
178
179 _ensureNumberOfProperties(parameterName, properties) {
180 if (properties && Object.keys(properties).length >= MAX_NESTED_PROPERTIES) {
181 throw new CliError(`${parameterName} - number of body properties `
182 + `exceeds ${MAX_NESTED_PROPERTIES}. Please use customParameters.`);
183 }
184 }
185}
186
187module.exports = { CliCustomizationProcessor, BODY_PATH_DELIMITER, ARRAY_SPLIT_DELIMITER, MAX_NESTED_PROPERTIES };