properties-definition-validator.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. import {Service} from '@e22m4u/js-service';
  2. import {DataType as Type} from './data-type.js';
  3. import {capitalize} from '../../../utils/index.js';
  4. import {PropertyUniqueness} from './property-uniqueness.js';
  5. import {InvalidArgumentError} from '../../../errors/index.js';
  6. import {PropertyValidatorRegistry} from './property-validator/index.js';
  7. import {PropertyTransformerRegistry} from './property-transformer/index.js';
  8. import {PrimaryKeysDefinitionValidator} from './primary-keys-definition-validator.js';
  9. /**
  10. * Properties definition validator.
  11. */
  12. export class PropertiesDefinitionValidator extends Service {
  13. /**
  14. * Validate.
  15. *
  16. * @param {string} modelName
  17. * @param {object} propDefs
  18. */
  19. validate(modelName, propDefs) {
  20. if (!modelName || typeof modelName !== 'string')
  21. throw new InvalidArgumentError(
  22. 'The first argument of PropertiesDefinitionValidator.validate ' +
  23. 'should be a non-empty String, but %v given.',
  24. modelName,
  25. );
  26. if (!propDefs || typeof propDefs !== 'object' || Array.isArray(propDefs)) {
  27. throw new InvalidArgumentError(
  28. 'The provided option "properties" of the model %v ' +
  29. 'should be an Object, but %v given.',
  30. modelName,
  31. propDefs,
  32. );
  33. }
  34. const propNames = Object.keys(propDefs);
  35. propNames.forEach(propName => {
  36. const propDef = propDefs[propName];
  37. this._validateProperty(modelName, propName, propDef);
  38. });
  39. this.getService(PrimaryKeysDefinitionValidator).validate(
  40. modelName,
  41. propDefs,
  42. );
  43. }
  44. /**
  45. * Validate property.
  46. *
  47. * @param {string} modelName
  48. * @param {string} propName
  49. * @param {object} propDef
  50. */
  51. _validateProperty(modelName, propName, propDef) {
  52. if (!modelName || typeof modelName !== 'string')
  53. throw new InvalidArgumentError(
  54. 'The first argument of PropertiesDefinitionValidator._validateProperty ' +
  55. 'should be a non-empty String, but %v given.',
  56. modelName,
  57. );
  58. if (!propName || typeof propName !== 'string')
  59. throw new InvalidArgumentError(
  60. 'The property name of the model %v should be ' +
  61. 'a non-empty String, but %v given.',
  62. modelName,
  63. propName,
  64. );
  65. if (!propDef)
  66. throw new InvalidArgumentError(
  67. 'The property %v of the model %v should have ' +
  68. 'a property definition, but %v given.',
  69. propName,
  70. modelName,
  71. propDef,
  72. );
  73. if (typeof propDef === 'string') {
  74. if (!Object.values(Type).includes(propDef))
  75. throw new InvalidArgumentError(
  76. 'In case of a short property definition, the property %v ' +
  77. 'of the model %v should have one of data types: %l, but %v given.',
  78. propName,
  79. modelName,
  80. Object.values(Type),
  81. propDef,
  82. );
  83. return;
  84. }
  85. if (!propDef || typeof propDef !== 'object' || Array.isArray(propDef)) {
  86. throw new InvalidArgumentError(
  87. 'In case of a full property definition, the property %v ' +
  88. 'of the model %v should be an Object, but %v given.',
  89. propName,
  90. modelName,
  91. propDef,
  92. );
  93. }
  94. if (!propDef.type || !Object.values(Type).includes(propDef.type))
  95. throw new InvalidArgumentError(
  96. 'The property %v of the model %v requires the option "type" ' +
  97. 'to have one of data types: %l, but %v given.',
  98. propName,
  99. modelName,
  100. Object.values(Type),
  101. propDef.type,
  102. );
  103. if (propDef.itemType && !Object.values(Type).includes(propDef.itemType)) {
  104. throw new InvalidArgumentError(
  105. 'The provided option "itemType" of the property %v in the model %v ' +
  106. 'should have one of data types: %l, but %v given.',
  107. propName,
  108. modelName,
  109. Object.values(Type),
  110. propDef.itemType,
  111. );
  112. }
  113. if (propDef.model && typeof propDef.model !== 'string')
  114. throw new InvalidArgumentError(
  115. 'The provided option "model" of the property %v in the model %v ' +
  116. 'should be a String, but %v given.',
  117. propName,
  118. modelName,
  119. propDef.model,
  120. );
  121. if (propDef.primaryKey && typeof propDef.primaryKey !== 'boolean')
  122. throw new InvalidArgumentError(
  123. 'The provided option "primaryKey" of the property %v in the model %v ' +
  124. 'should be a Boolean, but %v given.',
  125. propName,
  126. modelName,
  127. propDef.primaryKey,
  128. );
  129. if (propDef.columnName && typeof propDef.columnName !== 'string')
  130. throw new InvalidArgumentError(
  131. 'The provided option "columnName" of the property %v in the model %v ' +
  132. 'should be a String, but %v given.',
  133. propName,
  134. modelName,
  135. propDef.columnName,
  136. );
  137. if (propDef.columnType && typeof propDef.columnType !== 'string')
  138. throw new InvalidArgumentError(
  139. 'The provided option "columnType" of the property %v in the model %v ' +
  140. 'should be a String, but %v given.',
  141. propName,
  142. modelName,
  143. propDef.columnType,
  144. );
  145. if (propDef.required && typeof propDef.required !== 'boolean')
  146. throw new InvalidArgumentError(
  147. 'The provided option "required" of the property %v in the model %v ' +
  148. 'should be a Boolean, but %v given.',
  149. propName,
  150. modelName,
  151. propDef.required,
  152. );
  153. if (propDef.required && propDef.default !== undefined)
  154. throw new InvalidArgumentError(
  155. 'The property %v of the model %v is a required property, ' +
  156. 'so it should not have the option "default" to be provided.',
  157. propName,
  158. modelName,
  159. );
  160. if (propDef.primaryKey && propDef.required)
  161. throw new InvalidArgumentError(
  162. 'The property %v of the model %v is a primary key, ' +
  163. 'so it should not have the option "required" to be provided.',
  164. propName,
  165. modelName,
  166. );
  167. if (propDef.primaryKey && propDef.default !== undefined)
  168. throw new InvalidArgumentError(
  169. 'The property %v of the model %v is a primary key, ' +
  170. 'so it should not have the option "default" to be provided.',
  171. propName,
  172. modelName,
  173. );
  174. if (propDef.itemType && propDef.type !== Type.ARRAY)
  175. throw new InvalidArgumentError(
  176. 'The property %v of the model %v has the non-array type, ' +
  177. 'so it should not have the option "itemType" to be provided.',
  178. propName,
  179. modelName,
  180. propDef.type,
  181. );
  182. if (
  183. propDef.model &&
  184. propDef.type !== Type.OBJECT &&
  185. propDef.itemType !== Type.OBJECT
  186. ) {
  187. if (propDef.type !== Type.ARRAY) {
  188. throw new InvalidArgumentError(
  189. 'The option "model" is not supported for %s property type, ' +
  190. 'so the property %v of the model %v should not have ' +
  191. 'the option "model" to be provided.',
  192. capitalize(propDef.type),
  193. propName,
  194. modelName,
  195. );
  196. } else {
  197. throw new InvalidArgumentError(
  198. 'The option "model" is not supported for Array property type of %s, ' +
  199. 'so the property %v of the model %v should not have ' +
  200. 'the option "model" to be provided.',
  201. capitalize(propDef.itemType),
  202. propName,
  203. modelName,
  204. );
  205. }
  206. }
  207. if (propDef.validate != null) {
  208. const propertyValidatorRegistry = this.getService(
  209. PropertyValidatorRegistry,
  210. );
  211. if (propDef.validate && typeof propDef.validate === 'string') {
  212. if (!propertyValidatorRegistry.hasValidator(propDef.validate))
  213. throw new InvalidArgumentError(
  214. 'The property validator %v is not found.',
  215. propDef.validate,
  216. );
  217. } else if (Array.isArray(propDef.validate)) {
  218. for (const validatorName of propDef.validate) {
  219. if (typeof validatorName !== 'string')
  220. throw new InvalidArgumentError(
  221. 'The provided option "validate" of the property %v in the model %v ' +
  222. 'has an Array value that should have a non-empty String, ' +
  223. 'but %v given.',
  224. propName,
  225. modelName,
  226. validatorName,
  227. );
  228. if (!propertyValidatorRegistry.hasValidator(validatorName))
  229. throw new InvalidArgumentError(
  230. 'The property validator %v is not found.',
  231. validatorName,
  232. );
  233. }
  234. } else if (typeof propDef.validate === 'object') {
  235. for (const validatorName in propDef.validate) {
  236. if (!propertyValidatorRegistry.hasValidator(validatorName))
  237. throw new InvalidArgumentError(
  238. 'The property validator %v is not found.',
  239. validatorName,
  240. );
  241. }
  242. } else {
  243. throw new InvalidArgumentError(
  244. 'The provided option "validate" of the property %v in the model %v ' +
  245. 'should be a non-empty String, an Array of String or an Object, ' +
  246. 'but %v given.',
  247. propName,
  248. modelName,
  249. propDef.validate,
  250. );
  251. }
  252. }
  253. if (propDef.transform != null) {
  254. const propertyTransformerRegistry = this.getService(
  255. PropertyTransformerRegistry,
  256. );
  257. if (propDef.transform && typeof propDef.transform === 'string') {
  258. if (!propertyTransformerRegistry.hasTransformer(propDef.transform))
  259. throw new InvalidArgumentError(
  260. 'The property transformer %v is not found.',
  261. propDef.transform,
  262. );
  263. } else if (Array.isArray(propDef.transform)) {
  264. for (const transformerName of propDef.transform) {
  265. if (typeof transformerName !== 'string')
  266. throw new InvalidArgumentError(
  267. 'The provided option "transform" of the property %v in the model %v ' +
  268. 'has an Array value that should have a non-empty String, ' +
  269. 'but %v given.',
  270. propName,
  271. modelName,
  272. transformerName,
  273. );
  274. if (!propertyTransformerRegistry.hasTransformer(transformerName))
  275. throw new InvalidArgumentError(
  276. 'The property transformer %v is not found.',
  277. transformerName,
  278. );
  279. }
  280. } else if (typeof propDef.transform === 'object') {
  281. for (const transformerName in propDef.transform) {
  282. if (!propertyTransformerRegistry.hasTransformer(transformerName))
  283. throw new InvalidArgumentError(
  284. 'The property transformer %v is not found.',
  285. transformerName,
  286. );
  287. }
  288. } else {
  289. throw new InvalidArgumentError(
  290. 'The provided option "transform" of the property %v in the model %v ' +
  291. 'should be a non-empty String, an Array of String or an Object, ' +
  292. 'but %v given.',
  293. propName,
  294. modelName,
  295. propDef.transform,
  296. );
  297. }
  298. }
  299. if (
  300. propDef.unique &&
  301. !Object.values(PropertyUniqueness).includes(propDef.unique)
  302. ) {
  303. throw new InvalidArgumentError(
  304. 'The provided option "unique" of the property %v in the model %v ' +
  305. 'should be one of values: %l, but %v given.',
  306. propName,
  307. modelName,
  308. Object.values(PropertyUniqueness),
  309. propDef.unique,
  310. );
  311. }
  312. if (propDef.unique && propDef.primaryKey)
  313. throw new InvalidArgumentError(
  314. 'The property %v of the model %v is a primary key, ' +
  315. 'so it should not have the option "unique" to be provided.',
  316. propName,
  317. modelName,
  318. );
  319. }
  320. }