Skip to content

Commit

Permalink
Add shouldNotify() and de-duplicate buildServiceOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
hudson-newey committed Mar 19, 2024
1 parent d4ce617 commit 5725858
Showing 1 changed file with 49 additions and 67 deletions.
116 changes: 49 additions & 67 deletions src/app/services/baw-api/baw-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const multiPartApiHeaders = new HttpHeaders({
// eslint-disable-next-line @typescript-eslint/naming-convention
Accept: "application/json",
});
export const defaultBawServiceOptions: BawServiceOptions = {
export const defaultBawServiceOptions: Required<BawServiceOptions> = {
disableNotification: false,
withCredentials: true,
cacheOptions: { cache: false },
Expand Down Expand Up @@ -111,16 +111,30 @@ export class BawApiService<
};

// because users can create a partial options object, we need to merge the partial options with the default options
// so that we don't have "undefined" values being passed as objects
// so that we don't have "undefined" values being passed as options
private buildServiceOptions(
options: Partial<BawServiceOptions>
): BawServiceOptions {
): Required<BawServiceOptions> {
return {
...defaultBawServiceOptions,
...options,
};
}

private buildHttpContext(options: Required<BawServiceOptions>): HttpContext {
return new HttpContext().set(CREDENTIALS_CONTEXT, options.withCredentials);
}

// because users can input a partial options object, it is possible for the disableNotification property to be undefined
// this can be a problem because it will default to a falsy value, not the default option
// therefore, this method should be used to check if we should show a toast notification if the request fails
private shouldNotify(options: Partial<BawServiceOptions>): boolean {
return (
options?.disableNotification ??
defaultBawServiceOptions.disableNotification
);
}

public constructor(
@Inject(API_ROOT) protected apiRoot: string,
@Inject(IS_SERVER_PLATFORM) protected isServer: boolean,
Expand Down Expand Up @@ -187,14 +201,12 @@ export class BawApiService<
public list(
classBuilder: ClassBuilder,
path: string,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<Model[]> {
const fullOptions = this.buildServiceOptions(options);

return this.session.authTrigger.pipe(
switchMap(() => this.httpGet(path, defaultApiHeaders, fullOptions)),
switchMap(() => this.httpGet(path, defaultApiHeaders, options)),
map(this.handleCollectionResponse(classBuilder)),
catchError((err) => this.handleError(err, fullOptions.disableNotification))
catchError((err) => this.handleError(err, this.shouldNotify(options)))
);
}

Expand All @@ -209,14 +221,12 @@ export class BawApiService<
classBuilder: ClassBuilder,
path: string,
filters: Filters<Model>,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<Model[]> {
const fullOptions = this.buildServiceOptions(options);

return this.session.authTrigger.pipe(
switchMap(() => this.httpPost(path, filters, undefined, fullOptions)),
switchMap(() => this.httpPost(path, filters, undefined, options)),
map(this.handleCollectionResponse(classBuilder)),
catchError((err) => this.handleError(err, fullOptions.disableNotification))
catchError((err) => this.handleError(err, this.shouldNotify(options)))
);
}

Expand All @@ -231,16 +241,12 @@ export class BawApiService<
classBuilder: ClassBuilder,
path: string,
filters: Filters<Model>,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<Model> {
const fullOptions = this.buildServiceOptions(options);

return this.session.authTrigger.pipe(
switchMap(() => this.httpPost(path, filters, undefined, fullOptions)),
switchMap(() => this.httpPost(path, filters, undefined, options)),
map(this.handleSingleResponse(classBuilder)),
catchError((err) =>
this.handleError(err, fullOptions.disableNotification)
)
catchError((err) => this.handleError(err, this.shouldNotify(options)))
);
}

Expand All @@ -253,14 +259,12 @@ export class BawApiService<
public show(
classBuilder: ClassBuilder,
path: string,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<Model> {
const fullOptions = this.buildServiceOptions(options);

return this.session.authTrigger.pipe(
switchMap(() => this.httpGet(path, defaultApiHeaders, fullOptions)),
switchMap(() => this.httpGet(path, defaultApiHeaders, options)),
map(this.handleSingleResponse(classBuilder)),
catchError((err) => this.handleError(err, fullOptions.disableNotification))
catchError((err) => this.handleError(err, this.shouldNotify(options)))
);
}

Expand All @@ -278,24 +282,22 @@ export class BawApiService<
createPath: string,
updatePath: (model: Model) => string,
model: AbstractModel,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<Model> {
const jsonData = model.getJsonAttributes?.({ create: true });
const formData = model.getFormDataOnlyAttributes({ create: true });
const body = model.kind
? { [model.kind]: jsonData ?? model }
: jsonData ?? model;

const fullOptions = this.buildServiceOptions(options);

// as part of the multi part request, if there is only a JSON body, we want to return the output of the JSON POST request
// if there is only a formData body, we want to return the output of the formData PUT request
// if there is both a JSON body and formData, we want to return the output of the last request sent (formData PUT request)
// we default to returning null if there is no JSON or formData body
return of(null).pipe(
concatMap(
model.hasJsonOnlyAttributes({ create: true })
? () => this.httpPost(createPath, body, undefined, fullOptions).pipe()
? () => this.httpPost(createPath, body, undefined, options).pipe()
: (data) => of(data)
),
// we create a class from the POST response so that we can construct an update route for the formData PUT request
Expand All @@ -312,16 +314,14 @@ export class BawApiService<
updatePath(data),
formData,
multiPartApiHeaders,
fullOptions
options
).pipe(map(this.handleSingleResponse(classBuilder))),
of(data)
)
),
// there is no map function here, because the handleSingleResponse method is invoked on the POST and PUT requests
// individually. Moving the handleSingleResponse mapping here would result in the response object being instantiated twice
catchError((err) =>
this.handleError(err, fullOptions.disableNotification)
)
catchError((err) => this.handleError(err, this.shouldNotify(options)))
);
}

Expand All @@ -337,16 +337,14 @@ export class BawApiService<
classBuilder: ClassBuilder,
path: string,
model: AbstractModel,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<Model> {
const jsonData = model.getJsonAttributes?.({ update: true });
const formData = model.getFormDataOnlyAttributes({ update: true });
const body = model.kind
? { [model.kind]: jsonData ?? model }
: jsonData ?? model;

const fullOptions = this.buildServiceOptions(options);

// as part of the multi part request, if there is only a JSON body, we want to return the output of the JSON PATCH request
// if there is only a formData body, we want to return the output of the formData PUT request
// if there is both a JSON body and formData, we want to return the output of the last request sent (formData PUT request)
Expand All @@ -357,16 +355,16 @@ export class BawApiService<
// returns a value, and not an observable. Because we use concatMap below, we need the existing
// value to be emitted as an observable instead. Therefore, we create a static observable using of()
model.hasJsonOnlyAttributes({ update: true })
? () => this.httpPatch(path, body, undefined, fullOptions)
? () => this.httpPatch(path, body, undefined, options)
: (data) => of(data)
),
concatMap(
model.hasFormDataOnlyAttributes({ update: true })
? () => this.httpPut(path, formData, multiPartApiHeaders, fullOptions)
? () => this.httpPut(path, formData, multiPartApiHeaders, options)
: (data) => of(data)
),
map(this.handleSingleResponse(classBuilder)),
catchError((err) => this.handleError(err, fullOptions.disableNotification))
catchError((err) => this.handleError(err, this.shouldNotify(options)))
);
}

Expand All @@ -377,16 +375,12 @@ export class BawApiService<
*/
public destroy(
path: string,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<null> {
const fullOptions = this.buildServiceOptions(options);

return this.httpDelete(path, undefined, fullOptions).pipe(
return this.httpDelete(path, undefined, options).pipe(
map(this.handleEmptyResponse),
tap(() => this.clearCache(path)),
catchError((err) =>
this.handleError(err, fullOptions.disableNotification)
)
catchError((err) => this.handleError(err, this.shouldNotify(options)))
);
}

Expand All @@ -404,7 +398,7 @@ export class BawApiService<
public httpGet(
path: string,
headers: HttpHeaders = defaultApiHeaders,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<ApiResponse<Model | Model[]>> {
const fullOptions = this.buildServiceOptions(options);

Expand Down Expand Up @@ -443,14 +437,11 @@ export class BawApiService<
public httpDelete(
path: string,
headers: HttpHeaders = defaultApiHeaders,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<ApiResponse<Model | void>> {
const fullOptions = this.buildServiceOptions(options);

const context = new HttpContext().set(
CREDENTIALS_CONTEXT,
fullOptions.withCredentials
);
const context = this.buildHttpContext(fullOptions);

return this.http.delete<ApiResponse<null>>(this.getPath(path), {
responseType: "json",
Expand All @@ -475,14 +466,11 @@ export class BawApiService<
path: string,
body?: any,
headers: HttpHeaders = defaultApiHeaders,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<ApiResponse<Model | Model[]>> {
const fullOptions = this.buildServiceOptions(options);

const context = new HttpContext().set(
CREDENTIALS_CONTEXT,
fullOptions.withCredentials
);
const context = this.buildHttpContext(fullOptions);

return this.http.post<ApiResponse<Model | Model[]>>(
this.getPath(path),
Expand Down Expand Up @@ -511,14 +499,11 @@ export class BawApiService<
path: string,
body?: any,
headers: HttpHeaders = defaultApiHeaders,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<ApiResponse<Model>> {
const fullOptions = this.buildServiceOptions(options);

const context = new HttpContext().set(
CREDENTIALS_CONTEXT,
fullOptions.withCredentials
);
const context = this.buildHttpContext(fullOptions);

return this.http.put<ApiResponse<Model>>(this.getPath(path), body, {
responseType: "json",
Expand All @@ -543,14 +528,11 @@ export class BawApiService<
path: string,
body?: any,
headers: HttpHeaders = defaultApiHeaders,
options: BawServiceOptions = defaultBawServiceOptions
options: BawServiceOptions = {}
): Observable<ApiResponse<Model>> {
const fullOptions = this.buildServiceOptions(options);

const context = new HttpContext().set(
CREDENTIALS_CONTEXT,
fullOptions.withCredentials
);
const context = this.buildHttpContext(fullOptions);

return this.http.patch<ApiResponse<Model>>(this.getPath(path), body, {
responseType: "json",
Expand Down

0 comments on commit 5725858

Please sign in to comment.