@microsoft/m365-spec-parser 0.2.5-rc.0 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,8 +32,11 @@ var ErrorType;
32
32
  ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
33
33
  ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
34
34
  ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
35
+ ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
35
36
  ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
36
37
  ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
38
+ ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
39
+ ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
37
40
  ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
38
41
  ErrorType["NoParameter"] = "no-parameter";
39
42
  ErrorType["NoAPIInfo"] = "no-api-info";
@@ -53,7 +56,6 @@ var WarningType;
53
56
  WarningType["ConvertSwaggerToOpenAPI"] = "convert-swagger-to-openapi";
54
57
  WarningType["FuncDescriptionTooLong"] = "function-description-too-long";
55
58
  WarningType["OperationIdContainsSpecialCharacters"] = "operationid-contains-special-characters";
56
- WarningType["UnsupportedAuthType"] = "unsupported-auth-type";
57
59
  WarningType["GenerateJsonDataFailed"] = "generate-json-data-failed";
58
60
  WarningType["Unknown"] = "unknown";
59
61
  })(WarningType || (WarningType = {}));
@@ -101,7 +103,6 @@ ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please conve
101
103
  ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
102
104
  ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
103
105
  ConstantString.OperationIdContainsSpecialCharacters = "Operation id '%s' in OpenAPI description document contained special characters and was renamed to '%s'.";
104
- ConstantString.AuthTypeIsNotSupported = "Unsupported authorization type in API '%s'. No authorization will be used.";
105
106
  ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
106
107
  ConstantString.FuncDescriptionTooLong = "The description of the function '%s' is too long. The current length is %s characters, while the maximum allowed length is %s characters.";
107
108
  ConstantString.GenerateJsonDataFailed = "Failed to generate JSON data for api: %s due to %s.";
@@ -111,12 +112,17 @@ ConstantString.WrappedCardResponseLayout = "list";
111
112
  ConstantString.GetMethod = "get";
112
113
  ConstantString.PostMethod = "post";
113
114
  ConstantString.AdaptiveCardVersion = "1.5";
114
- ConstantString.AdaptiveCardSchema = "https://adaptivecards.io/schemas/adaptive-card.json";
115
+ ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
115
116
  ConstantString.AdaptiveCardType = "AdaptiveCard";
116
117
  ConstantString.TextBlockType = "TextBlock";
117
118
  ConstantString.ImageType = "Image";
118
119
  ConstantString.ContainerType = "Container";
119
- ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
120
+ ConstantString.RegistrationIdPostfix = {
121
+ apiKey: "REGISTRATION_ID",
122
+ oauth2: "CONFIGURATION_ID",
123
+ http: "REGISTRATION_ID",
124
+ openIdConnect: "REGISTRATION_ID",
125
+ };
120
126
  ConstantString.ResponseCodeFor20X = [
121
127
  "200",
122
128
  "201",
@@ -128,7 +134,6 @@ ConstantString.ResponseCodeFor20X = [
128
134
  "207",
129
135
  "208",
130
136
  "226",
131
- "2XX",
132
137
  "default",
133
138
  ];
134
139
  ConstantString.AllOperationMethods = [
@@ -186,6 +191,17 @@ ConstantString.PluginManifestSchema = "https://developer.microsoft.com/json-sche
186
191
 
187
192
  // Copyright (c) Microsoft Corporation.
188
193
  class Utils {
194
+ static hasNestedObjectInSchema(schema) {
195
+ if (this.isObjectSchema(schema)) {
196
+ for (const property in schema.properties) {
197
+ const nestedSchema = schema.properties[property];
198
+ if (this.isObjectSchema(nestedSchema)) {
199
+ return true;
200
+ }
201
+ }
202
+ }
203
+ return false;
204
+ }
189
205
  static isObjectSchema(schema) {
190
206
  return schema.type === "object" || (!schema.type && !!schema.properties);
191
207
  }
@@ -198,32 +214,11 @@ class Utils {
198
214
  static isAPIKeyAuth(authScheme) {
199
215
  return authScheme.type === "apiKey";
200
216
  }
201
- static isAPIKeyAuthButNotInCookie(authScheme) {
202
- return authScheme.type === "apiKey" && authScheme.in !== "cookie";
203
- }
204
217
  static isOAuthWithAuthCodeFlow(authScheme) {
205
218
  return !!(authScheme.type === "oauth2" &&
206
219
  authScheme.flows &&
207
220
  authScheme.flows.authorizationCode);
208
221
  }
209
- static isNotSupportedAuth(authSchemeArray) {
210
- if (authSchemeArray.length === 0) {
211
- return false;
212
- }
213
- if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
214
- return true;
215
- }
216
- for (const auths of authSchemeArray) {
217
- if (auths.length === 1) {
218
- if (Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme) ||
219
- Utils.isBearerTokenAuth(auths[0].authScheme) ||
220
- Utils.isAPIKeyAuthButNotInCookie(auths[0].authScheme)) {
221
- return false;
222
- }
223
- }
224
- }
225
- return true;
226
- }
227
222
  static getAuthArray(securities, spec) {
228
223
  var _a;
229
224
  const result = [];
@@ -248,20 +243,6 @@ class Utils {
248
243
  result.sort((a, b) => a[0].name.localeCompare(b[0].name));
249
244
  return result;
250
245
  }
251
- static getAuthMap(spec) {
252
- const authMap = {};
253
- for (const url in spec.paths) {
254
- for (const method in spec.paths[url]) {
255
- const operation = spec.paths[url][method];
256
- const authArray = Utils.getAuthArray(operation.security, spec);
257
- if (authArray && authArray.length > 0) {
258
- const currentAuth = authArray[0][0];
259
- authMap[operation.operationId] = currentAuth;
260
- }
261
- }
262
- }
263
- return authMap;
264
- }
265
246
  static getAuthInfo(spec) {
266
247
  let authInfo = undefined;
267
248
  for (const url in spec.paths) {
@@ -290,32 +271,26 @@ class Utils {
290
271
  let multipleMediaType = false;
291
272
  for (const code of ConstantString.ResponseCodeFor20X) {
292
273
  const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
293
- if (!responseObject) {
294
- continue;
295
- }
296
- multipleMediaType = Utils.containMultipleMediaTypes(responseObject);
297
- if (!allowMultipleMediaType && multipleMediaType) {
298
- json = {};
299
- continue;
300
- }
301
- const mediaObj = Utils.getJsonContentType(responseObject);
302
- if (Object.keys(mediaObj).length > 0) {
303
- json = mediaObj;
304
- return { json, multipleMediaType };
305
- }
306
- }
307
- return { json, multipleMediaType };
308
- }
309
- static getJsonContentType(responseObject) {
310
- if (responseObject.content) {
311
- for (const contentType of Object.keys(responseObject.content)) {
312
- // json media type can also be "application/json; charset=utf-8"
313
- if (contentType.indexOf("application/json") >= 0) {
314
- return responseObject.content[contentType];
274
+ if (responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) {
275
+ for (const contentType of Object.keys(responseObject.content)) {
276
+ // json media type can also be "application/json; charset=utf-8"
277
+ if (contentType.indexOf("application/json") >= 0) {
278
+ multipleMediaType = false;
279
+ json = responseObject.content[contentType];
280
+ if (Utils.containMultipleMediaTypes(responseObject)) {
281
+ multipleMediaType = true;
282
+ if (!allowMultipleMediaType) {
283
+ json = {};
284
+ }
285
+ }
286
+ else {
287
+ return { json, multipleMediaType };
288
+ }
289
+ }
315
290
  }
316
291
  }
317
292
  }
318
- return {};
293
+ return { json, multipleMediaType };
319
294
  }
320
295
  static convertPathToCamelCase(path) {
321
296
  const pathSegments = path.split(/[./{]/);
@@ -352,7 +327,7 @@ class Utils {
352
327
  }
353
328
  return newStr;
354
329
  }
355
- static checkServerUrl(servers, allowHttp = false) {
330
+ static checkServerUrl(servers) {
356
331
  const errors = [];
357
332
  let serverUrl;
358
333
  try {
@@ -375,7 +350,8 @@ class Utils {
375
350
  data: servers,
376
351
  });
377
352
  }
378
- else if (protocol !== "https:" && !(protocol === "http:" && allowHttp)) {
353
+ else if (protocol !== "https:") {
354
+ // Http server url is not supported
379
355
  const protocolString = protocol.slice(0, -1);
380
356
  errors.push({
381
357
  type: ErrorType.UrlProtocolNotSupported,
@@ -391,11 +367,10 @@ class Utils {
391
367
  let hasTopLevelServers = false;
392
368
  let hasPathLevelServers = false;
393
369
  let hasOperationLevelServers = false;
394
- const allowHttp = options.projectType === ProjectType.Copilot;
395
370
  if (spec.servers && spec.servers.length >= 1) {
396
371
  hasTopLevelServers = true;
397
372
  // for multiple server, we only use the first url
398
- const serverErrors = Utils.checkServerUrl(spec.servers, allowHttp);
373
+ const serverErrors = Utils.checkServerUrl(spec.servers);
399
374
  errors.push(...serverErrors);
400
375
  }
401
376
  const paths = spec.paths;
@@ -403,7 +378,7 @@ class Utils {
403
378
  const methods = paths[path];
404
379
  if ((methods === null || methods === void 0 ? void 0 : methods.servers) && methods.servers.length >= 1) {
405
380
  hasPathLevelServers = true;
406
- const serverErrors = Utils.checkServerUrl(methods.servers, allowHttp);
381
+ const serverErrors = Utils.checkServerUrl(methods.servers);
407
382
  errors.push(...serverErrors);
408
383
  }
409
384
  for (const method in methods) {
@@ -411,7 +386,7 @@ class Utils {
411
386
  if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
412
387
  if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
413
388
  hasOperationLevelServers = true;
414
- const serverErrors = Utils.checkServerUrl(operationObject.servers, allowHttp);
389
+ const serverErrors = Utils.checkServerUrl(operationObject.servers);
415
390
  errors.push(...serverErrors);
416
391
  }
417
392
  }
@@ -700,6 +675,22 @@ class Validator {
700
675
  }
701
676
  return result;
702
677
  }
678
+ validateResponse(method, path) {
679
+ const result = { isValid: true, reason: [] };
680
+ const operationObject = this.spec.paths[path][method];
681
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
682
+ if (this.options.projectType === ProjectType.SME) {
683
+ // only support response body only contains “application/json” content type
684
+ if (multipleMediaType) {
685
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
686
+ }
687
+ else if (Object.keys(json).length === 0) {
688
+ // response body should not be empty
689
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
690
+ }
691
+ }
692
+ return result;
693
+ }
703
694
  validateServer(method, path) {
704
695
  const result = { isValid: true, reason: [] };
705
696
  const serverObj = Utils.getServerObject(this.spec, method, path);
@@ -708,8 +699,8 @@ class Validator {
708
699
  result.reason.push(ErrorType.NoServerInformation);
709
700
  }
710
701
  else {
711
- const allowHttp = this.projectType === ProjectType.Copilot;
712
- const serverValidateResult = Utils.checkServerUrl([serverObj], allowHttp);
702
+ // server url should be absolute url with https protocol
703
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
713
704
  result.reason.push(...serverValidateResult.map((item) => item.type));
714
705
  }
715
706
  return result;
@@ -732,9 +723,6 @@ class Validator {
732
723
  reason: [ErrorType.MultipleAuthNotSupported],
733
724
  };
734
725
  }
735
- if (this.projectType === ProjectType.Copilot) {
736
- return { isValid: true, reason: [] };
737
- }
738
726
  for (const auths of authSchemeArray) {
739
727
  if (auths.length === 1) {
740
728
  if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
@@ -747,6 +735,114 @@ class Validator {
747
735
  }
748
736
  return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
749
737
  }
738
+ checkPostBodySchema(schema, isRequired = false) {
739
+ var _a;
740
+ const paramResult = {
741
+ requiredNum: 0,
742
+ optionalNum: 0,
743
+ isValid: true,
744
+ reason: [],
745
+ };
746
+ if (Object.keys(schema).length === 0) {
747
+ return paramResult;
748
+ }
749
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
750
+ const isCopilot = this.projectType === ProjectType.Copilot;
751
+ if (isCopilot && Utils.hasNestedObjectInSchema(schema)) {
752
+ paramResult.isValid = false;
753
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
754
+ return paramResult;
755
+ }
756
+ if (schema.type === "string" ||
757
+ schema.type === "integer" ||
758
+ schema.type === "boolean" ||
759
+ schema.type === "number") {
760
+ if (isRequiredWithoutDefault) {
761
+ paramResult.requiredNum = paramResult.requiredNum + 1;
762
+ }
763
+ else {
764
+ paramResult.optionalNum = paramResult.optionalNum + 1;
765
+ }
766
+ }
767
+ else if (Utils.isObjectSchema(schema)) {
768
+ const { properties } = schema;
769
+ for (const property in properties) {
770
+ let isRequired = false;
771
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
772
+ isRequired = true;
773
+ }
774
+ const result = this.checkPostBodySchema(properties[property], isRequired);
775
+ paramResult.requiredNum += result.requiredNum;
776
+ paramResult.optionalNum += result.optionalNum;
777
+ paramResult.isValid = paramResult.isValid && result.isValid;
778
+ paramResult.reason.push(...result.reason);
779
+ }
780
+ }
781
+ else {
782
+ if (isRequiredWithoutDefault && !isCopilot) {
783
+ paramResult.isValid = false;
784
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
785
+ }
786
+ }
787
+ return paramResult;
788
+ }
789
+ checkParamSchema(paramObject) {
790
+ const paramResult = {
791
+ requiredNum: 0,
792
+ optionalNum: 0,
793
+ isValid: true,
794
+ reason: [],
795
+ };
796
+ if (!paramObject) {
797
+ return paramResult;
798
+ }
799
+ const isCopilot = this.projectType === ProjectType.Copilot;
800
+ for (let i = 0; i < paramObject.length; i++) {
801
+ const param = paramObject[i];
802
+ const schema = param.schema;
803
+ if (isCopilot && Utils.hasNestedObjectInSchema(schema)) {
804
+ paramResult.isValid = false;
805
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
806
+ continue;
807
+ }
808
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
809
+ if (isCopilot) {
810
+ if (isRequiredWithoutDefault) {
811
+ paramResult.requiredNum = paramResult.requiredNum + 1;
812
+ }
813
+ else {
814
+ paramResult.optionalNum = paramResult.optionalNum + 1;
815
+ }
816
+ continue;
817
+ }
818
+ if (param.in === "header" || param.in === "cookie") {
819
+ if (isRequiredWithoutDefault) {
820
+ paramResult.isValid = false;
821
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
822
+ }
823
+ continue;
824
+ }
825
+ if (schema.type !== "boolean" &&
826
+ schema.type !== "string" &&
827
+ schema.type !== "number" &&
828
+ schema.type !== "integer") {
829
+ if (isRequiredWithoutDefault) {
830
+ paramResult.isValid = false;
831
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
832
+ }
833
+ continue;
834
+ }
835
+ if (param.in === "query" || param.in === "path") {
836
+ if (isRequiredWithoutDefault) {
837
+ paramResult.requiredNum = paramResult.requiredNum + 1;
838
+ }
839
+ else {
840
+ paramResult.optionalNum = paramResult.optionalNum + 1;
841
+ }
842
+ }
843
+ }
844
+ return paramResult;
845
+ }
750
846
  }
751
847
 
752
848
  // Copyright (c) Microsoft Corporation.
@@ -756,6 +852,7 @@ class CopilotValidator extends Validator {
756
852
  this.projectType = ProjectType.Copilot;
757
853
  this.options = options;
758
854
  this.spec = spec;
855
+ this.checkCircularReference();
759
856
  }
760
857
  validateSpec() {
761
858
  const result = { errors: [], warnings: [] };
@@ -781,6 +878,10 @@ class CopilotValidator extends Validator {
781
878
  if (!methodAndPathResult.isValid) {
782
879
  return methodAndPathResult;
783
880
  }
881
+ const circularReferenceResult = this.validateCircularReference(method, path);
882
+ if (!circularReferenceResult.isValid) {
883
+ return circularReferenceResult;
884
+ }
784
885
  const operationObject = this.spec.paths[path][method];
785
886
  // validate auth
786
887
  const authCheckResult = this.validateAuth(method, path);
@@ -792,6 +893,24 @@ class CopilotValidator extends Validator {
792
893
  // validate server
793
894
  const validateServerResult = this.validateServer(method, path);
794
895
  result.reason.push(...validateServerResult.reason);
896
+ // validate response
897
+ const validateResponseResult = this.validateResponse(method, path);
898
+ result.reason.push(...validateResponseResult.reason);
899
+ // validate requestBody
900
+ const requestBody = operationObject.requestBody;
901
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
902
+ if (requestJsonBody) {
903
+ const requestBodySchema = requestJsonBody.schema;
904
+ if (!Utils.isObjectSchema(requestBodySchema)) {
905
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
906
+ }
907
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
908
+ result.reason.push(...requestBodyParamResult.reason);
909
+ }
910
+ // validate parameters
911
+ const paramObject = operationObject.parameters;
912
+ const paramResult = this.checkParamSchema(paramObject);
913
+ result.reason.push(...paramResult.reason);
795
914
  if (result.reason.length > 0) {
796
915
  result.isValid = false;
797
916
  }
@@ -883,108 +1002,6 @@ class SMEValidator extends Validator {
883
1002
  }
884
1003
  return result;
885
1004
  }
886
- validateResponse(method, path) {
887
- const result = { isValid: true, reason: [] };
888
- const operationObject = this.spec.paths[path][method];
889
- const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
890
- // only support response body only contains “application/json” content type
891
- if (multipleMediaType) {
892
- result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
893
- }
894
- else if (Object.keys(json).length === 0) {
895
- // response body should not be empty
896
- result.reason.push(ErrorType.ResponseJsonIsEmpty);
897
- }
898
- return result;
899
- }
900
- checkPostBodySchema(schema, isRequired = false) {
901
- var _a;
902
- const paramResult = {
903
- requiredNum: 0,
904
- optionalNum: 0,
905
- isValid: true,
906
- reason: [],
907
- };
908
- if (Object.keys(schema).length === 0) {
909
- return paramResult;
910
- }
911
- const isRequiredWithoutDefault = isRequired && schema.default === undefined;
912
- const isCopilot = this.projectType === ProjectType.Copilot;
913
- if (schema.type === "string" ||
914
- schema.type === "integer" ||
915
- schema.type === "boolean" ||
916
- schema.type === "number") {
917
- if (isRequiredWithoutDefault) {
918
- paramResult.requiredNum = paramResult.requiredNum + 1;
919
- }
920
- else {
921
- paramResult.optionalNum = paramResult.optionalNum + 1;
922
- }
923
- }
924
- else if (Utils.isObjectSchema(schema)) {
925
- const { properties } = schema;
926
- for (const property in properties) {
927
- let isRequired = false;
928
- if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
929
- isRequired = true;
930
- }
931
- const result = this.checkPostBodySchema(properties[property], isRequired);
932
- paramResult.requiredNum += result.requiredNum;
933
- paramResult.optionalNum += result.optionalNum;
934
- paramResult.isValid = paramResult.isValid && result.isValid;
935
- paramResult.reason.push(...result.reason);
936
- }
937
- }
938
- else {
939
- if (isRequiredWithoutDefault && !isCopilot) {
940
- paramResult.isValid = false;
941
- paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
942
- }
943
- }
944
- return paramResult;
945
- }
946
- checkParamSchema(paramObject) {
947
- const paramResult = {
948
- requiredNum: 0,
949
- optionalNum: 0,
950
- isValid: true,
951
- reason: [],
952
- };
953
- if (!paramObject) {
954
- return paramResult;
955
- }
956
- for (let i = 0; i < paramObject.length; i++) {
957
- const param = paramObject[i];
958
- const schema = param.schema;
959
- const isRequiredWithoutDefault = param.required && schema.default === undefined;
960
- if (param.in === "header" || param.in === "cookie") {
961
- if (isRequiredWithoutDefault) {
962
- paramResult.isValid = false;
963
- paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
964
- }
965
- continue;
966
- }
967
- if (schema.type !== "boolean" &&
968
- schema.type !== "string" &&
969
- schema.type !== "number" &&
970
- schema.type !== "integer") {
971
- if (isRequiredWithoutDefault) {
972
- paramResult.isValid = false;
973
- paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
974
- }
975
- continue;
976
- }
977
- if (param.in === "query" || param.in === "path") {
978
- if (isRequiredWithoutDefault) {
979
- paramResult.requiredNum = paramResult.requiredNum + 1;
980
- }
981
- else {
982
- paramResult.optionalNum = paramResult.optionalNum + 1;
983
- }
984
- }
985
- }
986
- return paramResult;
987
- }
988
1005
  validateParamCount(postBodyResult, paramResult) {
989
1006
  const result = { isValid: true, reason: [] };
990
1007
  const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;