Skip to content

Commit

Permalink
feat(repo): enforce schema validation on api return
Browse files Browse the repository at this point in the history
  • Loading branch information
sh977218 committed Dec 27, 2024
1 parent 3ac5912 commit d16e3cd
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 28 deletions.
18 changes: 18 additions & 0 deletions client/simple-web-site/src/app/model/hero.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
import {z} from 'zod'

export const MemberSchema = z.object({
age: z.number(),
name: z.string(),
powers: [z.string()],
secretIdentity: z.string(),
})
export const HeroSchema = z.object({
homeTown: z.string(),
secretBase: z.string(),
content: z.string(),
squadName: z.number(),
members: [MemberSchema]
});

export const HeroResponseSchema = z.object([HeroSchema])

export type Hero = {
homeTown: string;
secretBase: string;
Expand Down
4 changes: 2 additions & 2 deletions client/simple-web-site/src/app/search/search.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
color="primary"
togglePosition="before"
*ngFor="let shoe of typesOfShoes"
>{{ shoe }}</mat-list-option
>{{ shoe }}</mat-list-option
>
</mat-selection-list>
</div>
Expand All @@ -16,7 +16,7 @@
color="accent"
togglePosition="before"
*ngFor="let season of typeOfSeasons"
>{{ season }}</mat-list-option
>{{ season }}</mat-list-option
>
</mat-selection-list>
</div>
Expand Down
30 changes: 17 additions & 13 deletions client/simple-web-site/src/app/search/search.component.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { AsyncPipe, CommonModule, NgFor } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import {AsyncPipe, CommonModule, NgFor} from '@angular/common';
import {HttpClient} from '@angular/common/http';
import {
Component,
CUSTOM_ELEMENTS_SCHEMA,
inject,
NO_ERRORS_SCHEMA,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatSidenavModule } from '@angular/material/sidenav';
import { RouterLink, RouterOutlet } from '@angular/router';
import { catchError, map } from 'rxjs';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatIconModule} from '@angular/material/icon';
import {MatListModule} from '@angular/material/list';
import {MatSidenavModule} from '@angular/material/sidenav';
import {RouterLink, RouterOutlet} from '@angular/router';
import {catchError, map} from 'rxjs';

import { HeroComponent } from 'app/hero/hero.component';
import { Hero } from 'app/model/hero';
import {HeroComponent} from 'app/hero/hero.component';
import {Hero, HeroResponseSchema} from "app/model/hero";
import {verifyResponse} from "app/verifyResponse";

@Component({
selector: 'app-search',
Expand Down Expand Up @@ -58,7 +59,10 @@ export class SearchComponent {

heroesFromMongo$ = this.http
.get<Hero[]>('http://localhost:3000/api/heroes/100')
.pipe(catchError(() => []));
.pipe(
verifyResponse(HeroResponseSchema),
catchError(() => [])
);

/*
cards$ = defer(() => from((this.client.search<Hero>({
Expand All @@ -72,7 +76,7 @@ export class SearchComponent {
}));*/
constructor() {
this.http
.get('http://localhost:3000/api/information', { responseType: 'text' })
.get('http://localhost:3000/api/information', {responseType: 'text'})
.subscribe();
}
}
18 changes: 18 additions & 0 deletions client/simple-web-site/src/app/verifyResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {isDevMode} from '@angular/core';
import {map, pipe} from 'rxjs';
import {z} from 'zod';

export function verifyResponse<T extends z.ZodTypeAny>(zodObj: T) {
return pipe(
map((response) => {
if (isDevMode()) {
const result = zodObj.safeParse(response);

if (!result.success) {
console.error(result.error);
}
}
return response as z.infer<T>;
})
);
}
24 changes: 12 additions & 12 deletions client/simple-web-site/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { VERSION as CDK_VERSION } from '@angular/cdk';
import { provideHttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { VERSION as MAT_VERSION } from '@angular/material/core';
import { bootstrapApplication, Title } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import {VERSION as CDK_VERSION} from '@angular/cdk';
import {provideHttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {VERSION as MAT_VERSION} from '@angular/material/core';
import {bootstrapApplication, Title} from '@angular/platform-browser';
import {provideAnimations} from '@angular/platform-browser/animations';
import {
provideRouter,
RouterStateSnapshot,
Routes,
TitleStrategy,
} from '@angular/router';

import { AppComponent } from 'app/app.component';
import { HomeComponent } from 'app/home/home.component';
import { SearchComponent } from 'app/search/search.component';
import { ThreeJsComponent } from 'app/three-js/three-js.component';
import {AppComponent} from 'app/app.component';
import {HomeComponent} from 'app/home/home.component';
import {SearchComponent} from 'app/search/search.component';
import {ThreeJsComponent} from 'app/three-js/three-js.component';

/* eslint-disable no-console */
console.info('Angular CDK version', CDK_VERSION.full);
Expand Down Expand Up @@ -43,7 +43,7 @@ const routes: Routes = [
},
];

@Injectable({ providedIn: 'root' })
@Injectable({providedIn: 'root'})
class TemplatePageTitleStrategy extends TitleStrategy {
constructor(private readonly title: Title) {
super();
Expand All @@ -60,6 +60,6 @@ bootstrapApplication(AppComponent, {
provideAnimations(),
provideHttpClient(),
provideRouter(routes),
{ provide: TitleStrategy, useClass: TemplatePageTitleStrategy },
{provide: TitleStrategy, useClass: TemplatePageTitleStrategy},
],
}).catch((err) => console.error(err));
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"tslib": "^2.3.0",
"typescript": "^5.1.3",
"winston": "^3.17.0",
"zod": "^3.24.1",
"zone.js": "~0.14.10"
},
"devDependencies": {
Expand Down
11 changes: 10 additions & 1 deletion test/heroes.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
[
{
"squadName": "Super hero squad",
"homeTown": "Metro City",
"formed": 2016,
Expand Down Expand Up @@ -38,4 +39,12 @@
]
}
]
},
{
"squadName": "Lonely Super hero",
"homeTown": "Lonely City",
"formed": 2025,
"secretBase": "Lonely tower",
"active": false
}
]

0 comments on commit d16e3cd

Please sign in to comment.