errors.js

  1. /**
  2. * <p>
  3. * This is the only file that is permitted to create new Error objects.
  4. * Any throw that occurs in this library will be one of the errors listed.
  5. * </p>
  6. *
  7. * <p>
  8. * As such, any try/catch code you write against this library should make
  9. * sure to not blind-catch, but catch intentionally:
  10. * <p>
  11. *
  12. * <pre><code>
  13. * import { Errors } from "use-models-for-data";
  14. * import { MyModel } from "./my-models.js";
  15. *
  16. * try {
  17. * MyModel.load(`someRecordName`);
  18. * } catch (err) {
  19. * const type = err.__proto__.constructor;
  20. * switch(type) {
  21. * case (Errors.RecordDoesNotExist): ...
  22. * case (Errors.RecordAccessError): ...
  23. * ...
  24. * case (TypeError): ...
  25. * default: throw err;
  26. * }
  27. * }
  28. * </code></pre>
  29. *
  30. *
  31. * @namespace Errors
  32. */
  33. export class MissingCreateFunction extends Error {
  34. /**
  35. * Used by the form building code to warn the user that they forgot to provide a create() function for transforming tag/props tuples into nodes.
  36. * @name Errors.MissingCreateFunction
  37. * @member
  38. */
  39. constructor() {
  40. super();
  41. this.message = `No create(tag, props) function provided.`;
  42. }
  43. }
  44. export class NothingToMigrate extends Error {
  45. /**
  46. * Used in migration code to signal that there are no actual diffs to migrate, despite being in a codepath that assumes that there are.
  47. * @name Errors.NothingToMigrate
  48. * @member
  49. */
  50. constructor() {
  51. super();
  52. this.message = `Nothing to migrate.`;
  53. }
  54. }
  55. export class PropertySchemaViolation extends Error {
  56. /**
  57. * Used in schema validation code, specifically the array wrapper, to signal an illegal value assignment.
  58. * @name Errors.PropertySchemaViolation
  59. * @member
  60. */
  61. constructor(errors) {
  62. super();
  63. this.message = `Assignment violates property schema.`;
  64. this.errors = errors;
  65. }
  66. }
  67. export class MissingChoicesArray extends Error {
  68. /**
  69. * Used in the model fields code to signal that an expected <code>choices</code> array is missing from the field properties.
  70. * @name Errors.MissingChoicesArray
  71. * @member
  72. */
  73. constructor() {
  74. super();
  75. this.message = `Missing choices array for choice field.`;
  76. }
  77. }
  78. export class NoStoreFound extends Error {
  79. /**
  80. * Used in the {@link Models} code when a code path assumes that there is a store available when there isn't.
  81. * @name Errors.NoStoreFound
  82. * @member
  83. */
  84. constructor() {
  85. super();
  86. this.message = `Cannot register models until after Models.setStore(store) has been called.`;
  87. }
  88. }
  89. export class StoreNotReady extends Error {
  90. /**
  91. * Used in the {@link Models} code when a code path assumes that a store is ready for use when it isn't.
  92. * @name Errors.StoreNotReady
  93. * @member
  94. */
  95. constructor() {
  96. super();
  97. this.message = `Store is not ready for use yet.`;
  98. }
  99. }
  100. export class TypeNotMatchedToChoices extends Error {
  101. /**
  102. * Used in the model fields code when a choice field with a <code>default</code> value does not contain that value in the available choices.
  103. * @name Errors.TypeNotMatchedToChoices
  104. * @member
  105. * @param {String} type - Field type for model field.
  106. */
  107. constructor(type) {
  108. super();
  109. this.message = `Cannot declare ${type} field with non-${type} value unless that value is exists in [options.choices].`;
  110. this.type = type;
  111. }
  112. }
  113. export class FieldFailedCustomValidation extends Error {
  114. /**
  115. * Used in the model field code to signal that a value did not pass custom validation (even if it passed basic validation).
  116. * @name Errors.FieldFailedCustomValidation
  117. * @member
  118. * @param {String} key - Model field name.
  119. */
  120. constructor(key) {
  121. super();
  122. this.message = `${key} value failed custom validation.`;
  123. this.key = key;
  124. }
  125. }
  126. export class ModelFormDeclarationHasUnknownFields extends Error {
  127. /**
  128. * Used in the model registration process when a model with __meta.form code refers to fields that do not exist on the model.
  129. * @name Errors.ModelFormDeclarationHasUnknownFields
  130. * @member
  131. * @param {String} modelName - Model field name.
  132. * @param {String[]} fields - Array of field names that don't exist
  133. */
  134. constructor(missing) {
  135. super();
  136. const models = missing.map((v) => v.modelName).join(`, `);
  137. this.message = `Model${
  138. models.length > 1 ? `s` : ``
  139. } ${models} had __meta.form declarations with non-existent fields.`;
  140. this.missing = missing;
  141. }
  142. }
  143. export class CouldNotFindModel extends Error {
  144. /**
  145. * Used in the {@link ModelRegistry} to signal that the code tried to access an unknown schema.
  146. * @name Errors.CouldNotFindModel
  147. * @member
  148. * @param {String} modelName - Model class name.
  149. */
  150. constructor(modelName) {
  151. super();
  152. this.message = `Could not retrieve schema for Model ${modelName}, remember to await Models.register(${modelName})`;
  153. this.modelName = modelName;
  154. }
  155. }
  156. export class SchemaMismatchForModel extends Error {
  157. /**
  158. * Used in the {@link ModelRegistry} to signal that the currently loaded version of a schema does not match the stored version of that same schema.
  159. * @name Errors.SchemaMismatchForModel
  160. * @member
  161. * @param {String} modelName - Model class name.
  162. */
  163. constructor(modelName) {
  164. super();
  165. this.message = `Schema mismatch for ${modelName} model, please migrate your data first.`;
  166. this.modelName = modelName;
  167. }
  168. }
  169. export class ModelCreateWithBadData extends Error {
  170. /**
  171. * Used in {@link Model} to signal that .create(data) was passed non-object data
  172. * @name Errors.ModelCreateWithBadData
  173. * @member
  174. * @param {String} modelName - Model class name.
  175. */
  176. constructor(modelName) {
  177. super();
  178. this.message = `${modelName}.create(data) requires that data is an object.`;
  179. this.modelName = modelName;
  180. }
  181. }
  182. export class ModelFromMissingData extends Error {
  183. /**
  184. * Used in {@link Model} to signal that .from(data) was called without a data payload.
  185. * @name Errors.ModelFromMissingData
  186. * @member
  187. * @param {String} modelName - Model class name.
  188. */
  189. constructor(modelName) {
  190. super();
  191. this.message = `${modelName}.from() must be called with a data object.`;
  192. this.modelName = modelName;
  193. }
  194. }
  195. export class IncompleteModelSave extends Error {
  196. /**
  197. * Used in {@link Model} to signal that the user tried to save a model that is missing required values (something which can only be the case if the model was built using the ALLOW_INCOMPLETE symbol)
  198. * @name Errors.IncompleteModelSave
  199. * @member
  200. * @param {String} modelName - Model class name.
  201. * @param {String[]} errors - Array of error strings describing all problems found.
  202. */
  203. constructor(modelName, errors) {
  204. super();
  205. this.message = `Cannot save incomplete ${modelName} model (created with ALLOW_INCOMPLETE).`;
  206. this.modelName = modelName;
  207. this.errors = errors;
  208. }
  209. }
  210. export class DoNotUseModelConstructor extends Error {
  211. /**
  212. * Thrown when code tries to construct a model using the <code>new</code> keyword, rather than the <code>create()</code> function.
  213. * @name Errors.DoNotUseModelConstructor
  214. * @member
  215. * @param {String} modelName - Model class name.
  216. */
  217. constructor(modelName) {
  218. super();
  219. this.message = `Use ${modelName}.create(data?) to build model instances.`;
  220. this.modelName = modelName;
  221. }
  222. }
  223. export class BadModelDataSubmission extends Error {
  224. /**
  225. * Used in {@link Model} to signal that a data update using a form submission object did not succeed due to validation failure.
  226. * @name Errors.BadModelDataSubmission
  227. * @member
  228. * @param {String} modelName - Model class name.
  229. * @param {String[]} errors - Array of error strings describing all problems found.
  230. */
  231. constructor(modelName, errors) {
  232. super();
  233. this.message = `Submitted data did not pass validation for ${modelName} schema.`;
  234. this.modelName = modelName;
  235. this.errors = errors;
  236. }
  237. }
  238. export class UndefinedKey extends Error {
  239. /**
  240. * Used in {@link Model} to signal a missing object property that was expect to exist, based on the model schema.
  241. * @name Errors.UndefinedKey
  242. * @member
  243. * @param {String} key - Model field name.
  244. * @param {String} modelName - Model class name.
  245. */
  246. constructor(key, modelName) {
  247. super();
  248. this.message = `Property [${key}] is not defined for model ${modelName}.`;
  249. this.modelName = modelName;
  250. }
  251. }
  252. export class RequiredFieldsMissing extends Error {
  253. /**
  254. * Used in {@link Models} to signal that something tried to build a {@link Model} without specifying values for all <code>required</code> properties in that model.
  255. * @name Errors.RequiredFieldsMissing
  256. * @member
  257. * @param {String} modelName - Model class name.
  258. * @param {String[]} errors - Array of error strings describing all problems found.
  259. */
  260. constructor(modelName, errors) {
  261. super();
  262. this.message = `Cannot create ${modelName}: missing required fields (without schema-defined default).`;
  263. this.modelName = modelName;
  264. this.errors = errors;
  265. }
  266. }
  267. export class AssignmentMustBeArray extends Error {
  268. /**
  269. * Used in {@link Models} to signal that an assignment to an <code>array</code> property was not an array.
  270. * @name Errors.AssignmentMustBeArray
  271. * @member
  272. * @param {String} key - Model field name.
  273. */
  274. constructor(key) {
  275. super();
  276. this.message = `Assignment for [${key}] must be an array.`;
  277. this.key = key;
  278. }
  279. }
  280. export class InvalidAssignment extends Error {
  281. /**
  282. * Used in {@Models} to signal that a property assignment was not permitted according to the model schema.
  283. * @name Errors.InvalidAssignment
  284. * @member
  285. * @param {String} key - Model field name.
  286. * @param {*} value - Property value for model field.
  287. * @param {String[]} errors - Array of error strings describing all problems found.
  288. */
  289. constructor(key, value, errors) {
  290. super();
  291. this.message = `${key} could not be assigned value [${value}].`;
  292. this.key = key;
  293. this.value = value;
  294. this.errors = errors;
  295. }
  296. }
  297. export class RecordDoesNotExist extends Error {
  298. /**
  299. * Used to signal a record cannot be resolved by a backend
  300. * @name Errors.RecordDoesNotExist
  301. * @member
  302. * @param {String} recordIdentifier - The fully qualified name of the record in question.
  303. * @param {*} details - any number of additional arguments that will be captured as error.details[].
  304. */
  305. constructor(recordIdentifier, ...details) {
  306. super();
  307. this.message = `Record ${recordIdentifier} does not exist.`;
  308. this.recordIdentifier = recordIdentifier;
  309. this.details = details;
  310. }
  311. }
  312. export class RecordAccessError extends Error {
  313. /**
  314. * Used to signal that a backend can find a record, but can't access it.
  315. * @name Errors.RecordAccessError
  316. * @member
  317. * @param {String} recordIdentifier - The fully qualified name of the record in question.
  318. * @param {*} details - any number of additional arguments that will be captured as error.details[].
  319. */
  320. constructor(recordIdentifier, ...details) {
  321. super();
  322. this.message = `Could not read file ${recordIdentifier}.`;
  323. this.recordIdentifier = recordIdentifier;
  324. this.details = details;
  325. }
  326. }
  327. export class RecordParseError extends Error {
  328. /**
  329. * Used to signal a filepath resolves to a file, but its content could not be parse as model record data.
  330. * @name Errors.RecordParseError
  331. * @member
  332. * @param {filepath} path - Path to a file in the filesystem.
  333. */
  334. constructor(path) {
  335. super();
  336. this.message = `Could not parse ${path}.`;
  337. this.path = path;
  338. }
  339. }
  340. export class MissingRecordNameBinding extends Error {
  341. /**
  342. * Used by the schema code to signal that a model could not be saved using an unqualified <code>save()</code> call.
  343. * @name Errors.MissingRecordNameBinding
  344. * @member
  345. * @param {String} modelName - Model class name.
  346. */
  347. constructor(modelName) {
  348. super();
  349. this.message = `${modelName} model lacks a "__meta.recordName" binding for auto-saving model instances.`;
  350. this.modelName = modelName;
  351. }
  352. }
  353. export class MissingImplementation extends Error {
  354. /**
  355. * Thrown by anything that intends to be an abstract superclass, to make sure subclasses implement the necessary methods.
  356. * @name Errors.MissingImplementation
  357. * @member
  358. * @param {String} classAndMethodName - String representation of a class.method(signature).
  359. */
  360. constructor(classAndMethodName) {
  361. super();
  362. this.message = `Missing implementation for ${classAndMethodName}`;
  363. this.classAndMethodName = classAndMethodName;
  364. }
  365. }
  366. export class IncorrectSyncType extends Error {
  367. /**
  368. * Thrown by anything that intends to be an abstract superclass, to make sure subclasses implement the necessary methods.
  369. * @name Errors.IncorrectSyncType
  370. * @member
  371. * @param {String} classAndMethodName - String representation of a class.method(signature).
  372. * @param {String} [syncType] - "AsyncFunction" if this is supposed to be an async function, otherwise undefined.
  373. */
  374. constructor(classAndMethodName, syncType) {
  375. super();
  376. const should = syncType === `AsyncFunction` ? `should` : `should not`;
  377. this.message = `${classAndMethodName} ${should} be marked async.`;
  378. this.classAndMethodName = classAndMethodName;
  379. }
  380. }