14096-move-to-process
[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     MODIFYING_CONTAINER_REQUEST_FINAL_STATE = 'ModifyingFinalState',
36     UNKNOWN = 'Unknown',
37     NONE = 'None'
38 }
39
40 export class CommonResourceService<T extends Resource> {
41
42     static mapResponseKeys = (response: { data: any }): Promise<any> =>
43         CommonResourceService.mapKeys(_.camelCase)(response.data)
44
45     static mapKeys = (mapFn: (key: string) => string) =>
46         (value: any): any => {
47             switch (true) {
48                 case _.isPlainObject(value):
49                     return Object
50                         .keys(value)
51                         .map(key => [key, mapFn(key)])
52                         .reduce((newValue, [key, newKey]) => ({
53                             ...newValue,
54                             [newKey]: CommonResourceService.mapKeys(mapFn)(value[key])
55                         }), {});
56                 case _.isArray(value):
57                     return value.map(CommonResourceService.mapKeys(mapFn));
58                 default:
59                     return value;
60             }
61         }
62
63     static defaultResponse<R>(promise: AxiosPromise<R>): Promise<R> {
64         return promise
65             .then(CommonResourceService.mapResponseKeys)
66             .catch(({ response }) => Promise.reject<Errors>(CommonResourceService.mapResponseKeys(response)));
67     }
68
69     protected serverApi: AxiosInstance;
70     protected resourceType: string;
71
72     constructor(serverApi: AxiosInstance, resourceType: string) {
73         this.serverApi = serverApi;
74         this.resourceType = '/' + resourceType + '/';
75     }
76
77     create(data?: Partial<T> | any) {
78         return CommonResourceService.defaultResponse(
79             this.serverApi
80                 .post<T>(this.resourceType, data && CommonResourceService.mapKeys(_.snakeCase)(data)));
81     }
82
83     delete(uuid: string): Promise<T> {
84         return CommonResourceService.defaultResponse(
85             this.serverApi
86                 .delete(this.resourceType + uuid));
87     }
88
89     get(uuid: string) {
90         return CommonResourceService.defaultResponse(
91             this.serverApi
92                 .get<T>(this.resourceType + uuid));
93     }
94
95     list(args: ListArguments = {}): Promise<ListResults<T>> {
96         const { filters, order, ...other } = args;
97         const params = {
98             ...other,
99             filters: filters ? `[${filters}]` : undefined,
100             order: order ? order : undefined
101         };
102         return CommonResourceService.defaultResponse(
103             this.serverApi
104                 .get(this.resourceType, {
105                     params: CommonResourceService.mapKeys(_.snakeCase)(params)
106                 }));
107     }
108
109     update(uuid: string, data: Partial<T>) {
110         return CommonResourceService.defaultResponse(
111             this.serverApi
112                 .put<T>(this.resourceType + uuid, data && CommonResourceService.mapKeys(_.snakeCase)(data)));
113
114     }
115 }
116
117 export const getCommonResourceServiceError = (errorResponse: any) => {
118     if ('errors' in errorResponse && 'errorToken' in errorResponse) {
119         const error = errorResponse.errors.join('');
120         switch (true) {
121             case /UniqueViolation/.test(error):
122                 return CommonResourceServiceError.UNIQUE_VIOLATION;
123             case /ownership cycle/.test(error):
124                 return CommonResourceServiceError.OWNERSHIP_CYCLE;
125             case /Mounts cannot be modified in state 'Final'/.test(error):
126                 return CommonResourceServiceError.MODIFYING_CONTAINER_REQUEST_FINAL_STATE;
127             default:
128                 return CommonResourceServiceError.UNKNOWN;
129         }
130     }
131     return CommonResourceServiceError.NONE;
132 };
133
134