From 575bcf244a465f72b07190b86aef236371460c89 Mon Sep 17 00:00:00 2001
From: Jay McDoniel <jmcdo29@gmail.com>
Date: Wed, 7 Feb 2024 13:18:55 -0800
Subject: [PATCH 1/2] feat: allow for and guard guards to be ran sequentially

---
 .vscode/extensions.json                   |  3 +--
 packages/or-guard/README.md               | 11 +++++++----
 packages/or-guard/src/lib/and.guard.ts    | 17 +++++++++--------
 packages/or-guard/test/app.controller.ts  | 16 +++++++++++++++-
 packages/or-guard/test/app.module.ts      |  4 ++++
 packages/or-guard/test/or.guard.spec.ts   | 22 ++++++++++++++++++++--
 packages/or-guard/test/read-user.guard.ts | 12 ++++++++++++
 packages/or-guard/test/set-user.guard.ts  | 12 ++++++++++++
 8 files changed, 80 insertions(+), 17 deletions(-)
 create mode 100644 packages/or-guard/test/read-user.guard.ts
 create mode 100644 packages/or-guard/test/set-user.guard.ts

diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 6a302fe..462e29b 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -2,7 +2,6 @@
   "recommendations": [
     "nrwl.angular-console",
     "esbenp.prettier-vscode",
-    "dbaeumer.vscode-eslint",
-    "firsttris.vscode-jest-runner"
+    "dbaeumer.vscode-eslint"
   ]
 }
diff --git a/packages/or-guard/README.md b/packages/or-guard/README.md
index f4376c9..60b5139 100644
--- a/packages/or-guard/README.md
+++ b/packages/or-guard/README.md
@@ -90,17 +90,20 @@ And this library will set up the handling of the logic for
 under the hood.
 
 ```ts
-AndGuard(guards: Array<Type<CanActivate> | InjectionToken>, orGuardOptions?: OrGuardOptions): CanActivate
+AndGuard(guards: Array<Type<CanActivate> | InjectionToken>, andGuardOptions?: AndGuardOptions): CanActivate
 ```
 
 - `guards`: an array of guards or injection tokens for the `AndGuard` to resolve
   and test
-- `orGuardOptions`: an optional object with properties to modify how the
-  `OrGuard` functions
+- `andGuardOptions`: an optional object with properties to modify how the
+  `AndGuard` functions
 
 ```ts
-interface OrGuardOptions {
+interface AndGuardOptions {
+  // immediately stop all other guards and throw an error
   throwOnFirstError?: boolean;
+  // run the guards in order they are declared in the array rather than in parallel
+  sequential?: boolean;
 }
 ```
 
diff --git a/packages/or-guard/src/lib/and.guard.ts b/packages/or-guard/src/lib/and.guard.ts
index 23aa477..c1cc699 100644
--- a/packages/or-guard/src/lib/and.guard.ts
+++ b/packages/or-guard/src/lib/and.guard.ts
@@ -15,27 +15,28 @@ import {
   OperatorFunction,
   throwError,
 } from 'rxjs';
-import { catchError, last, mergeMap, every } from 'rxjs/operators';
+import { catchError, last, mergeMap, every, concatMap } from 'rxjs/operators';
 
-interface OrGuardOptions {
+interface AndGuardOptions {
   throwOnFirstError?: boolean;
+  sequential?: boolean;
 }
 
 export function AndGuard(
   guards: Array<Type<CanActivate> | InjectionToken>,
-  orGuardOptions?: OrGuardOptions
+  orGuardOptions?: AndGuardOptions
 ) {
   class AndMixinGuard implements CanActivate {
     private guards: CanActivate[] = [];
     constructor(@Inject(ModuleRef) private readonly modRef: ModuleRef) {}
     canActivate(context: ExecutionContext): Observable<boolean> {
       this.guards = guards.map((guard) => this.modRef.get(guard));
-      const canActivateReturns: Array<Observable<boolean>> = this.guards.map(
-        (guard) => this.deferGuard(guard, context)
-      );
+      const canActivateReturns: Array<() => Observable<boolean>> =
+        this.guards.map((guard) => () => this.deferGuard(guard, context));
+      const mapOperator = orGuardOptions?.sequential ? concatMap : mergeMap;
       return from(canActivateReturns).pipe(
-        mergeMap((obs) => {
-          return obs.pipe(this.handleError());
+        mapOperator((obs) => {
+          return obs().pipe(this.handleError());
         }),
         every((val) => val === true),
         last()
diff --git a/packages/or-guard/test/app.controller.ts b/packages/or-guard/test/app.controller.ts
index 0acfe5e..87fe4dd 100644
--- a/packages/or-guard/test/app.controller.ts
+++ b/packages/or-guard/test/app.controller.ts
@@ -1,8 +1,10 @@
 import { Controller, Get, UseGuards } from '@nestjs/common';
 
-import { OrGuard } from '../src';
+import { AndGuard, OrGuard } from '../src';
 import { ObsGuard } from './obs.guard';
 import { PromGuard } from './prom.guard';
+import { ReadUserGuard } from './read-user.guard';
+import { SetUserGuard } from './set-user.guard';
 import { SyncGuard } from './sync.guard';
 import { ThrowGuard } from './throw.guard';
 
@@ -32,4 +34,16 @@ export class AppController {
   getLogicalAnd() {
     return this.message;
   }
+
+  @UseGuards(AndGuard([SetUserGuard, ReadUserGuard]))
+  @Get('set-user-fail')
+  getSetUserFail() {
+    return this.message;
+  }
+
+  @UseGuards(AndGuard([SetUserGuard, ReadUserGuard], { sequential: true }))
+  @Get('set-user-pass')
+  getSetUserPass() {
+    return this.message;
+  }
 }
diff --git a/packages/or-guard/test/app.module.ts b/packages/or-guard/test/app.module.ts
index 861ee34..2a91b5d 100644
--- a/packages/or-guard/test/app.module.ts
+++ b/packages/or-guard/test/app.module.ts
@@ -6,6 +6,8 @@ import { ObsGuard } from './obs.guard';
 import { PromGuard } from './prom.guard';
 import { SyncGuard } from './sync.guard';
 import { ThrowGuard } from './throw.guard';
+import { SetUserGuard } from './set-user.guard';
+import { ReadUserGuard } from './read-user.guard';
 
 @Module({
   controllers: [AppController],
@@ -14,6 +16,8 @@ import { ThrowGuard } from './throw.guard';
     SyncGuard,
     PromGuard,
     ThrowGuard,
+    SetUserGuard,
+    ReadUserGuard,
     {
       provide: 'SyncAndProm',
       useClass: AndGuard([SyncGuard, PromGuard]),
diff --git a/packages/or-guard/test/or.guard.spec.ts b/packages/or-guard/test/or.guard.spec.ts
index 69ee16a..50a0666 100644
--- a/packages/or-guard/test/or.guard.spec.ts
+++ b/packages/or-guard/test/or.guard.spec.ts
@@ -8,7 +8,7 @@ import { ObsGuard } from './obs.guard';
 import { PromGuard } from './prom.guard';
 import { SyncGuard } from './sync.guard';
 
-describe('Or Guard Integration Test', () => {
+describe('OrGuard and AndGuard Integration Test', () => {
   let moduleConfig: TestingModuleBuilder;
 
   beforeEach(() => {
@@ -55,7 +55,7 @@ describe('Or Guard Integration Test', () => {
                 await app.close();
               });
               /**
-               * OrGuard([SyncGuard, PromGuard, ObsGuard])
+               * OrGuard([AndGuard([SyncGuard, PromGuard]), ObsGuard])
                *
                * | Sync | Prom | Obs | Final |
                * |  - | - | - | - |
@@ -185,4 +185,22 @@ describe('Or Guard Integration Test', () => {
       });
     }
   );
+
+  describe('AndGuard with options', () => {
+    let app: INestApplication;
+    beforeAll(async () => {
+      const testMod = await moduleConfig.compile();
+      app = testMod.createNestApplication();
+      await app.init();
+    });
+    afterAll(async () => {
+      await app.close();
+    });
+    it('should throw an error if not ran sequentially', async () => {
+      return supertest(app.getHttpServer()).get('/set-user-fail').expect(403);
+    });
+    it('should not throw an error if ran sequentially', async () => {
+      return supertest(app.getHttpServer()).get('/set-user-pass').expect(200);
+    });
+  });
 });
diff --git a/packages/or-guard/test/read-user.guard.ts b/packages/or-guard/test/read-user.guard.ts
new file mode 100644
index 0000000..5a13ddf
--- /dev/null
+++ b/packages/or-guard/test/read-user.guard.ts
@@ -0,0 +1,12 @@
+import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
+import { Observable } from 'rxjs';
+
+@Injectable()
+export class ReadUserGuard implements CanActivate {
+  canActivate(
+    context: ExecutionContext
+  ): boolean | Promise<boolean> | Observable<boolean> {
+    const req = context.switchToHttp().getRequest();
+    return !!req.user;
+  }
+}
diff --git a/packages/or-guard/test/set-user.guard.ts b/packages/or-guard/test/set-user.guard.ts
new file mode 100644
index 0000000..c9b7425
--- /dev/null
+++ b/packages/or-guard/test/set-user.guard.ts
@@ -0,0 +1,12 @@
+import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
+import { setTimeout } from 'timers/promises';
+
+@Injectable()
+export class SetUserGuard implements CanActivate {
+  async canActivate(context: ExecutionContext): Promise<boolean> {
+    await setTimeout(500);
+    const req = context.switchToHttp().getRequest();
+    req.user = 'set';
+    return true;
+  }
+}

From 3a767b5ce9d22194b3de3d745f56ef7426153173 Mon Sep 17 00:00:00 2001
From: Jay McDoniel <jmcdo29@gmail.com>
Date: Wed, 7 Feb 2024 13:19:49 -0800
Subject: [PATCH 2/2] chore: changeset for @nest-lab/or-guard@2.4.0

---
 .changeset/late-nails-brush.md | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 .changeset/late-nails-brush.md

diff --git a/.changeset/late-nails-brush.md b/.changeset/late-nails-brush.md
new file mode 100644
index 0000000..664d4d8
--- /dev/null
+++ b/.changeset/late-nails-brush.md
@@ -0,0 +1,5 @@
+---
+'@nest-lab/or-guard': minor
+---
+
+Allow for AndGuard guards to be ran in sequential order