1 |
|
2 | const { kebabCase, standardize } = require('@src/utils/string-utils');
|
3 | const CliError = require('@src/exceptions/cli-error');
|
4 | const { customizationMap } = require('@src/commands/smapi/customizations/parameters-map');
|
5 |
|
6 | const BODY_PATH_DELIMITER = '>>>';
|
7 | const ARRAY_SPLIT_DELIMITER = ',';
|
8 | const MAX_NESTED_PROPERTIES = 10;
|
9 |
|
10 | class CliCustomizationProcessor {
|
11 | |
12 |
|
13 |
|
14 |
|
15 | processOperationName(operationName) {
|
16 | return kebabCase(operationName.substring(0, operationName.length - 2));
|
17 | }
|
18 |
|
19 | |
20 |
|
21 |
|
22 |
|
23 |
|
24 | processOperation(operationName, operation) {
|
25 | operation.customizationMetadata.flatParamsMap = new Map();
|
26 | }
|
27 |
|
28 | |
29 |
|
30 |
|
31 |
|
32 |
|
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;
|
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 |
|
187 | module.exports = { CliCustomizationProcessor, BODY_PATH_DELIMITER, ARRAY_SPLIT_DELIMITER, MAX_NESTED_PROPERTIES };
|