7b36b71cf42d7ce6ba289712eac1f2955b047ec4
[arvados-workbench2.git] / src / services / common-service / common-resource-service.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as _ from "lodash";
6 import { AxiosInstance, AxiosPromise } from "axios";
7 import { Resource } from "src/models/resource";
8
9 export interface ListArguments {
10     limit?: number;
11     offset?: number;
12     filters?: string;
13     order?: string;
14     select?: string[];
15     distinct?: boolean;
16     count?: string;
17 }
18
19 export interface ListResults<T> {
20     kind: string;
21     offset: number;
22     limit: number;
23     items: T[];
24     itemsAvailable: number;
25 }
26
27 export interface Errors {
28     errors: string[];
29     errorToken: string;
30 }
31
32 export enum CommonResourceServiceError {
33     UNIQUE_VIOLATION = 'UniqueViolation',
34     OWNERSHIP_CYCLE = 'OwnershipCycle',
35     UNKNOWN = 'Unknown',
36     NONE = 'None'
37 }
38
39 export class CommonResourceService<T extends Resource> {
40
41     static mapResponseKeys = (response: { data: any }): Promise<any> =>
42         CommonResourceService.mapKeys(_.camelCase)(response.data)
43
44     static mapKeys = (mapFn: (key: string) => string) =>
45         (value: any): any => {
46             switch (true) {
47                 case _.isPlainObject(value):
48                     return Object
49                         .keys(value)
50                         .map(key => [key, mapFn(key)])
51                         .reduce((newValue, [key, newKey]) => ({
52                             ...newValue,
53                             [newKey]: CommonResourceService.mapKeys(mapFn)(value[key])
54                         }), {});
55                 case _.isArray(value):
56                     return value.map(CommonResourceService.mapKeys(mapFn));
57                 default:
58                     return value;
59             }
60         }
61
62     static defaultResponse<R>(promise: AxiosPromise<R>): Promise<R> {
63         return promise
64             .then(CommonResourceService.mapResponseKeys)
65             .catch(({ response }) => Promise.reject<Errors>(CommonResourceService.mapResponseKeys(response)));
66     }
67
68     protected serverApi: AxiosInstance;
69     protected resourceType: string;
70
71     constructor(serverApi: AxiosInstance, resourceType: string) {
72         this.serverApi = serverApi;
73         this.resourceType = '/' + resourceType + '/';
74     }
75
76     create(data?: Partial<T> | any) {
77         return CommonResourceService.defaultResponse(
78             this.serverApi
79                 .post<T>(this.resourceType, data && CommonResourceService.mapKeys(_.snakeCase)(data)));
80     }
81
82     delete(uuid: string): Promise<T> {
83         return CommonResourceService.defaultResponse(
84             this.serverApi
85                 .delete(this.resourceType + uuid));
86     }
87
88     get(uuid: string) {
89         return CommonResourceService.defaultResponse(
90             this.serverApi
91                 .get<T>(this.resourceType + uuid));
92     }
93
94     list(args: ListArguments = {}): Promise<ListResults<T>> {
95         const { filters, order, ...other } = args;
96         const params = {
97             ...other,
98             filters: filters ? `[${filters}]` : undefined,
99             order: order ? order : undefined
100         };
101         return CommonResourceService.defaultResponse(
102             this.serverApi
103                 .get(this.resourceType, {
104                     params: CommonResourceService.mapKeys(_.snakeCase)(params)
105                 }));
106     }
107
108     update(uuid: string, data: Partial<T>) {
109         return CommonResourceService.defaultResponse(
110             this.serverApi
111                 .put<T>(this.resourceType + uuid, data && CommonResourceService.mapKeys(_.snakeCase)(data)));
112
113     }
114 }
115
116 export const getCommonResourceServiceError = (errorResponse: any) => {
117     if ('errors' in errorResponse && 'errorToken' in errorResponse) {
118         const error = errorResponse.errors.join('');
119         switch (true) {
120             case /UniqueViolation/.test(error):
121                 return CommonResourceServiceError.UNIQUE_VIOLATION;
122             case /ownership cycle/.test(error):
123                 return CommonResourceServiceError.OWNERSHIP_CYCLE;
124             default:
125                 return CommonResourceServiceError.UNKNOWN;
126         }
127     }
128     return CommonResourceServiceError.NONE;
129 };
130
131