Skip to content

Commit

Permalink
Merge pull request #164 from Josuto/validation_exception
Browse files Browse the repository at this point in the history
feat: export ValidationException
  • Loading branch information
Josuto authored Dec 21, 2023
2 parents 856314e + 29dad5d commit a120f5a
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 95 deletions.
31 changes: 15 additions & 16 deletions examples/nestjs-mongoose-book-manager/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
This is an example of how to use `monguito` in a NestJS application that uses MongoDB. It is a dummy book
This is an example of how to use `monguito` in a NestJS application that uses MongoDB. It is a dummy book
manager that exposes three simple endpoints i.e., create, update, and delete a book, as well as list all
books. A book may be of type `Book` or any of its subtypes i.e., `PaperBook` and `AudioBook`.

> **Warning**
>
> Some basic knowledge on [NestJS](https://docs.nestjs.com/) is assumed, as well as that you have read the main
> documentation of [monguito](../../README.md). The goal of this documentation is not to provide a comprehensive
guide on `monguito` usage. Thus, you may want to check the [sample application code](./src) as you go reading.
> documentation of [monguito](../../README.md). The goal of this documentation is not to provide a comprehensive
> guide on `monguito` usage. Thus, you may want to check the [sample application code](./src) as you go reading.
# Main Contents

Expand Down Expand Up @@ -35,7 +35,7 @@ MongoDB instance, assuming that Docker Desktop is running.
$ yarn start:dev

# run the NestJS application with no MongoDB Docker container
$ yarn start
$ yarn start
```

# Bottom-up Book Manager Application Implementation
Expand Down Expand Up @@ -115,13 +115,14 @@ directly implement the `Repository` interface. The definition of `MongooseBookRe
@Injectable()
export class MongooseBookRepository
extends MongooseRepository<Book>
implements Repository<Book> {
implements Repository<Book>
{
constructor(@InjectConnection() connection: Connection) {
super(
{
Default: {type: Book, schema: BookSchema},
PaperBook: {type: PaperBook, schema: PaperBookSchema},
AudioBook: {type: AudioBook, schema: AudioBookSchema},
Default: { type: Book, schema: BookSchema },
PaperBook: { type: PaperBook, schema: PaperBookSchema },
AudioBook: { type: AudioBook, schema: AudioBookSchema },
},
connection,
);
Expand All @@ -130,7 +131,7 @@ export class MongooseBookRepository
async deleteById(id: string): Promise<boolean> {
if (!id) throw new IllegalArgumentException('The given ID must be valid');
return this.entityModel
.findByIdAndUpdate(id, {isDeleted: true}, {new: true})
.findByIdAndUpdate(id, { isDeleted: true }, { new: true })
.exec()
.then((book) => !!book);
}
Expand Down Expand Up @@ -174,8 +175,7 @@ export class BookController {
constructor(
@Inject('BOOK_REPOSITORY')
private readonly bookRepository: Repository<Book>,
) {
}
) {}

@Get()
async findAll(): Promise<Book[]> {
Expand All @@ -187,15 +187,15 @@ export class BookController {
@Body({
transform: (plainBook) => deserialise(plainBook),
})
book: Book,
book: Book,
): Promise<Book> {
return this.save(book);
}

@Patch()
async update(
@Body()
book: PartialBook,
book: PartialBook,
): Promise<Book> {
return this.save(book);
}
Expand Down Expand Up @@ -261,8 +261,7 @@ part of the second step: writing the last required class `AppModule`. The defini
],
controllers: [BookController],
})
export class AppModule {
}
export class AppModule {}
```

This class module specifies the Mongoose connection required to instantiate `MongooseBookRepository` at the `imports`
Expand Down Expand Up @@ -321,7 +320,7 @@ export const rootMongooseTestModule = (
});
```

You may perceive that the port and dbName input parameters match those of the database connection
You may perceive that the `port` and `dbName` input parameters match those of the database connection
specified [earlier](#book-manager-module). This is no coincidence.

Finally, you can then use the `mongoServer` instance to perform several explicit Mongoose-based DB operations such as
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "monguito",
"version": "3.3.0",
"version": "3.4.0",
"description": "MongoDB Abstract Repository implementation for Node.js",
"author": {
"name": "Josu Martinez",
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Entity } from './util/entity';
import {
IllegalArgumentException,
UndefinedConstructorException,
ValidationException,
} from './util/exceptions';
import { AuditableSchema, BaseSchema, extendSchema } from './util/schema';

Expand All @@ -18,6 +19,7 @@ export {
MongooseRepository,
Repository,
UndefinedConstructorException,
ValidationException,
extendSchema,
isAuditable,
};
35 changes: 23 additions & 12 deletions src/mongoose.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Entity } from './util/entity';
import {
IllegalArgumentException,
UndefinedConstructorException,
ValidationException,
} from './util/exceptions';
import { SearchOptions } from './util/search-options';

Expand Down Expand Up @@ -103,19 +104,29 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>
): Promise<S> {
if (!entity)
throw new IllegalArgumentException('The given entity must be valid');
let document;
if (!entity.id) {
document = await this.insert(entity as S, userId);
} else {
document = await this.update(
entity as PartialEntityWithIdAndOptionalDiscriminatorKey<S>,
userId,
try {
let document;
if (!entity.id) {
document = await this.insert(entity as S, userId);
} else {
document = await this.update(
entity as PartialEntityWithIdAndOptionalDiscriminatorKey<S>,
userId,
);
}
if (document) return this.instantiateFrom(document) as S;
throw new IllegalArgumentException(
`There is no document matching the given ID ${entity.id}. New entities cannot not specify an ID`,
);
} catch (error) {
if (error.message.includes('validation failed')) {
throw new ValidationException(
`Some fields of the given entity do not specify valid values`,
error,
);
}
throw error;
}
if (document) return this.instantiateFrom(document) as S;
throw new IllegalArgumentException(
`There is no document matching the given ID ${entity.id}. New entities cannot not specify an ID`,
);
}

/**
Expand Down Expand Up @@ -171,7 +182,7 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>
} catch (error) {
if (error.message.includes('duplicate key error')) {
throw new IllegalArgumentException(
`The given entity with ID ${entity.id} includes a field which value is expected to be unique`,
`The given entity includes a field which value is expected to be unique`,
);
}
throw error;
Expand Down
30 changes: 24 additions & 6 deletions src/util/exceptions.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
abstract class Exception extends Error {
readonly error?: Error;

constructor(message: string, error?: Error) {
super(message);
this.error = error;
}
}

/**
* Models a client provided illegal argument exception.
*/
export class IllegalArgumentException extends Error {
constructor(message: string) {
super(message);
export class IllegalArgumentException extends Exception {
constructor(message: string, error?: Error) {
super(message, error);
}
}

/**
* Models an undefined persistable domain object constructor exception.
*/
export class UndefinedConstructorException extends Error {
constructor(message: string) {
super(message);
export class UndefinedConstructorException extends Exception {
constructor(message: string, error?: Error) {
super(message, error);
}
}

/**
* Models a persistable domain object schema validation rule violation exception.
*/
export class ValidationException extends Exception {
constructor(message: string, error?: Error) {
super(message, error);
}
}
Loading

0 comments on commit a120f5a

Please sign in to comment.