oa-document-scope.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import {joinPath} from './utils/index.js';
  2. import {InvalidArgumentError} from '@e22m4u/js-format';
  3. import {OADocumentBuilder} from './oa-document-builder.js';
  4. import {OAOperationMethod} from './document-specification.js';
  5. /**
  6. * Document scope.
  7. */
  8. export class OADocumentScope {
  9. /**
  10. * @param {object} rootBuilder
  11. * @param {object} [options]
  12. */
  13. constructor(rootBuilder, options = {}) {
  14. if (!(rootBuilder instanceof OADocumentBuilder)) {
  15. throw new InvalidArgumentError(
  16. 'Parameter "rootBuilder" must be an instance of OADocumentBuilder, ' +
  17. 'but %v was given.',
  18. rootBuilder,
  19. );
  20. }
  21. if (!options || typeof options !== 'object' || Array.isArray(options)) {
  22. throw new InvalidArgumentError(
  23. 'Parameter "options" must be an Object, but %v was given.',
  24. options,
  25. );
  26. }
  27. if (options.pathPrefix !== undefined) {
  28. if (!options.pathPrefix || typeof options.pathPrefix !== 'string') {
  29. throw new InvalidArgumentError(
  30. 'Parameter "pathPrefix" must be a non-empty String, ' +
  31. 'but %v was given.',
  32. options.pathPrefix,
  33. );
  34. }
  35. }
  36. if (options.tags !== undefined) {
  37. if (!Array.isArray(options.tags)) {
  38. throw new InvalidArgumentError(
  39. 'Parameter "tags" must be an Array, ' + 'but %v was given.',
  40. options.tags,
  41. );
  42. }
  43. options.tags.forEach((tag, index) => {
  44. if (!tag || typeof tag !== 'string') {
  45. throw new InvalidArgumentError(
  46. 'Element "tags[%d]" must be a non-empty String, ' +
  47. 'but %v was given.',
  48. index,
  49. tag,
  50. );
  51. }
  52. });
  53. }
  54. this.rootBuilder = rootBuilder;
  55. this.pathPrefix = options.pathPrefix || '/';
  56. this.tags = options.tags || [];
  57. }
  58. /**
  59. * Define operation.
  60. *
  61. * @param {object} operationDef
  62. * @returns {this}
  63. */
  64. defineOperation(operationDef) {
  65. if (
  66. !operationDef ||
  67. typeof operationDef !== 'object' ||
  68. Array.isArray(operationDef)
  69. ) {
  70. throw new InvalidArgumentError(
  71. 'Operation Definition must be an Object, but %v was given.',
  72. operationDef,
  73. );
  74. }
  75. // path
  76. if (!operationDef.path || typeof operationDef.path !== 'string') {
  77. throw new InvalidArgumentError(
  78. 'Property "path" must be a non-empty String, but %v was given.',
  79. operationDef.path,
  80. );
  81. }
  82. if (operationDef.path[0] !== '/') {
  83. throw new InvalidArgumentError(
  84. 'Property "path" must start with forward slash "/", but %v was given.',
  85. operationDef.path,
  86. );
  87. }
  88. // method
  89. if (!operationDef.method || typeof operationDef.method !== 'string') {
  90. throw new InvalidArgumentError(
  91. 'Property "method" must be a non-empty String, but %v was given.',
  92. operationDef.method,
  93. );
  94. }
  95. if (!Object.values(OAOperationMethod).includes(operationDef.method)) {
  96. throw new InvalidArgumentError(
  97. 'Property "method" must be one of values: %l, but %v was given.',
  98. Object.values(OAOperationMethod),
  99. );
  100. }
  101. // operation
  102. if (
  103. !operationDef.operation ||
  104. typeof operationDef.operation !== 'object' ||
  105. Array.isArray(operationDef.operation)
  106. ) {
  107. throw new InvalidArgumentError(
  108. 'Property "operation" must be an Object, but %v was given.',
  109. operationDef.operation,
  110. );
  111. }
  112. // склеивание пути
  113. const fullPath = joinPath(this.pathPrefix, operationDef.path);
  114. // создание копии схемы операции
  115. // чтобы избежать мутацию аргумента
  116. const operation = structuredClone(operationDef.operation);
  117. // объединение тегов текущей области
  118. // с тегами текущей операции и удаление
  119. // дубликатов
  120. if (this.tags.length > 0) {
  121. operation.tags = [...this.tags, ...(operation.tags || [])];
  122. operation.tags = [...new Set(operation.tags)];
  123. }
  124. // регистрация операции в родительском
  125. // экземпляре сборщика документа
  126. this.rootBuilder.defineOperation({
  127. ...operationDef,
  128. path: fullPath,
  129. operation,
  130. });
  131. return this;
  132. }
  133. /**
  134. * Create scope.
  135. *
  136. * @param {object} [options]
  137. * @returns {OADocumentScope}
  138. */
  139. createScope(options = {}) {
  140. if (!options || typeof options !== 'object' || Array.isArray(options)) {
  141. throw new InvalidArgumentError(
  142. 'Parameter "options" must be an Object, but %v was given.',
  143. options,
  144. );
  145. }
  146. if (options.pathPrefix !== undefined) {
  147. if (!options.pathPrefix || typeof options.pathPrefix !== 'string') {
  148. throw new InvalidArgumentError(
  149. 'Parameter "pathPrefix" must be a non-empty String, ' +
  150. 'but %v was given.',
  151. options.pathPrefix,
  152. );
  153. }
  154. }
  155. if (options.tags !== undefined) {
  156. if (!Array.isArray(options.tags)) {
  157. throw new InvalidArgumentError(
  158. 'Parameter "tags" must be an Array, ' + 'but %v was given.',
  159. options.tags,
  160. );
  161. }
  162. options.tags.forEach((tag, index) => {
  163. if (!tag || typeof tag !== 'string') {
  164. throw new InvalidArgumentError(
  165. 'Element "tags[%d]" must be a non-empty String, ' +
  166. 'but %v was given.',
  167. index,
  168. tag,
  169. );
  170. }
  171. });
  172. }
  173. return new OADocumentScope(this.rootBuilder, {
  174. pathPrefix: joinPath(this.pathPrefix, options.pathPrefix),
  175. tags: [...this.tags, ...(options.tags || [])],
  176. });
  177. }
  178. /**
  179. * Build.
  180. *
  181. * @returns {object}
  182. */
  183. build() {
  184. return this.rootBuilder.build();
  185. }
  186. }