From 83f6938060f383a41708623a80a90ec909e42d62 Mon Sep 17 00:00:00 2001 From: Josu Martinez Date: Sat, 7 Dec 2024 19:06:44 +0100 Subject: [PATCH 1/2] fix: improve exception handling Add entity validation exception handling at the functions insert and update since they are part of Monguito Application Protected Interface. Also, create new exception to handle any error thrown at any entity constructor. --- src/mongoose.repository.ts | 72 ++++++++++++++++++++++++-------------- src/util/exceptions.ts | 14 ++++++++ 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/mongoose.repository.ts b/src/mongoose.repository.ts index 768c61c..4089b64 100644 --- a/src/mongoose.repository.ts +++ b/src/mongoose.repository.ts @@ -12,6 +12,7 @@ import { DomainModel, DomainTree } from './util/domain-model'; import { Entity } from './util/entity'; import { IllegalArgumentException, + InstantiationException, UndefinedConstructorException, ValidationException, } from './util/exceptions'; @@ -129,19 +130,10 @@ export abstract class MongooseRepository> throw new IllegalArgumentException( 'The given entity cannot be null or undefined', ); - try { - if (!entity.id) { - return await this.insert(entity as S, options); - } else { - return await this.update(entity as PartialEntityWithId, options); - } - } catch (error) { - if (error instanceof mongoose.Error.ValidationError) { - throw new ValidationException( - 'One or more fields of the given entity do not specify valid values', - error, - ); - } else throw error; + if (!entity.id) { + return await this.insert(entity as S, options); + } else { + return await this.update(entity as PartialEntityWithId, options); } } @@ -160,11 +152,20 @@ export abstract class MongooseRepository> throw new IllegalArgumentException( 'The given entity cannot be null or undefined', ); - const document = this.createDocumentForInsertion(entity, options); - const insertedDocument = (await document.save({ - session: options?.session, - })) as HydratedDocument; - return this.instantiateFrom(insertedDocument) as S; + try { + const document = this.createDocumentForInsertion(entity, options); + const insertedDocument = (await document.save({ + session: options?.session, + })) as HydratedDocument; + return this.instantiateFrom(insertedDocument) as S; + } catch (error) { + if (error instanceof mongoose.Error.ValidationError) { + throw new ValidationException( + 'One or more fields of the given entity do not specify valid values', + error, + ); + } else throw error; + } } private createDocumentForInsertion( @@ -221,12 +222,21 @@ export abstract class MongooseRepository> .findById>(entity.id) .session(options?.session ?? null); if (document) { - document.set(entity); - this.setAuditableDataOnDocumentToUpdate(document, options?.userId); - const updatedDocument = (await document.save({ - session: options?.session, - })) as HydratedDocument; - return this.instantiateFrom(updatedDocument) as S; + try { + document.set(entity); + this.setAuditableDataOnDocumentToUpdate(document, options?.userId); + const updatedDocument = (await document.save({ + session: options?.session, + })) as HydratedDocument; + return this.instantiateFrom(updatedDocument) as S; + } catch (error) { + if (error instanceof mongoose.Error.ValidationError) { + throw new ValidationException( + 'One or more fields of the given entity do not specify valid values', + error, + ); + } else throw error; + } } throw new IllegalArgumentException( `There is no document matching the given ID '${entity.id}'`, @@ -268,11 +278,19 @@ export abstract class MongooseRepository> ? this.domainTree.getSubtypeConstructor(entityKey) : this.domainTree.getSupertypeConstructor(); if (constructor) { - // safe instantiation as no abstract class instance can be stored in the first place - return new constructor(document.toObject()) as S; + try { + // safe instantiation as no abstract class instance can be stored in the first place + return new constructor(document.toObject()) as S; + } catch (error) { + // still, the constructor of some entities may throw an error + throw new InstantiationException( + `An error occurred while instantiating an entity with ID ${document.id}`, + error, + ); + } } throw new UndefinedConstructorException( - `There is no registered instance constructor for the document with ID ${document.id} or the constructor is abstract`, + `There is no registered instance constructor for the document with ID ${document.id} or the corresponding entity type is abstract`, ); } } diff --git a/src/util/exceptions.ts b/src/util/exceptions.ts index 03c5232..97d49da 100644 --- a/src/util/exceptions.ts +++ b/src/util/exceptions.ts @@ -24,6 +24,20 @@ export class IllegalArgumentException extends Exception { } } +/** + * Models an entity instantiation exception. + */ +export class InstantiationException extends Exception { + /** + * Creates an `InstantiationException`, optionally wrapping an error. + * @param {string} message the message of the exception. + * @param {Error} cause (optional) the wrapped error. + */ + constructor(message: string, cause?: Error) { + super(message, cause); + } +} + /** * Models an undefined persistable domain object constructor exception. */ From a64cf033589f1ae006af80c507336ec223e46e29 Mon Sep 17 00:00:00 2001 From: Josu Martinez Date: Sat, 7 Dec 2024 19:14:03 +0100 Subject: [PATCH 2/2] docs: improve dependency installation comments Also add missing license file. --- examples/nestjs-mongoose-book-manager/LICENSE | 21 +++++++++++++++++++ .../nestjs-mongoose-book-manager/README.md | 14 ++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 examples/nestjs-mongoose-book-manager/LICENSE diff --git a/examples/nestjs-mongoose-book-manager/LICENSE b/examples/nestjs-mongoose-book-manager/LICENSE new file mode 100644 index 0000000..0538f01 --- /dev/null +++ b/examples/nestjs-mongoose-book-manager/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Josu Martinez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/nestjs-mongoose-book-manager/README.md b/examples/nestjs-mongoose-book-manager/README.md index c94af32..13f08c3 100644 --- a/examples/nestjs-mongoose-book-manager/README.md +++ b/examples/nestjs-mongoose-book-manager/README.md @@ -15,11 +15,19 @@ A book may be of type `Book` or any of its subtypes i.e., `PaperBook` and `Audio ## Installation -Assuming that you have already [installed NestJS](https://docs.nestjs.com/first-steps) in your local machine, first you -need to install of the project dependencies by running the following command: +This example depends on NestJS and the `monguito` package included in the root folder of the Monguito repository. Hence, before +installing any dependency, (1) make sure that you have [NestJS installed in your machine](https://docs.nestjs.com/first-steps) +and (2) build `monguito` from the root folder of your local repository: ```bash -$ yarn install +$ yarn build +``` + +Then, you can install of the NestJS sample project dependencies by running the following command (from +`/examples/nestjs-mongoose-book-manager`): + +```bash +$ yarn install --force ``` ## Execution