1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import * as _ from "lodash";
6 import { AxiosInstance, AxiosPromise } from "axios";
7 import { Resource } from "src/models/resource";
8 import * as uuid from "uuid/v4";
9 import { ApiActions } from "~/services/api/api-actions";
11 export interface ListArguments {
21 export interface ListResults<T> {
26 itemsAvailable: number;
29 export interface Errors {
34 export enum CommonResourceServiceError {
35 UNIQUE_VIOLATION = 'UniqueViolation',
36 OWNERSHIP_CYCLE = 'OwnershipCycle',
37 MODIFYING_CONTAINER_REQUEST_FINAL_STATE = 'ModifyingContainerRequestFinalState',
38 UNIQUE_PUBLIC_KEY = 'UniquePublicKey',
39 INVALID_PUBLIC_KEY = 'InvalidPublicKey',
44 export class CommonResourceService<T extends Resource> {
46 static mapResponseKeys = (response: { data: any }) =>
47 CommonResourceService.mapKeys(_.camelCase)(response.data)
49 static mapKeys = (mapFn: (key: string) => string) =>
50 (value: any): any => {
52 case _.isPlainObject(value):
55 .map(key => [key, mapFn(key)])
56 .reduce((newValue, [key, newKey]) => ({
58 [newKey]: CommonResourceService.mapKeys(mapFn)(value[key])
60 case _.isArray(value):
61 return value.map(CommonResourceService.mapKeys(mapFn));
67 static defaultResponse<R>(promise: AxiosPromise<R>, actions: ApiActions, mapKeys = true): Promise<R> {
69 actions.progressFn(reqId, true);
72 actions.progressFn(reqId, false);
75 .then((response: { data: any }) => {
76 return mapKeys ? CommonResourceService.mapResponseKeys(response) : response.data;
78 .catch(({ response }) => {
79 actions.progressFn(reqId, false);
80 const errors = CommonResourceService.mapResponseKeys(response) as Errors;
81 actions.errorFn(reqId, errors);
86 protected serverApi: AxiosInstance;
87 protected resourceType: string;
88 protected actions: ApiActions;
90 constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions) {
91 this.serverApi = serverApi;
92 this.resourceType = '/' + resourceType + '/';
93 this.actions = actions;
96 create(data?: Partial<T>) {
97 return CommonResourceService.defaultResponse(
99 .post<T>(this.resourceType, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
104 delete(uuid: string): Promise<T> {
105 return CommonResourceService.defaultResponse(
107 .delete(this.resourceType + uuid),
113 return CommonResourceService.defaultResponse(
115 .get<T>(this.resourceType + uuid),
120 list(args: ListArguments = {}): Promise<ListResults<T>> {
121 const { filters, order, ...other } = args;
124 filters: filters ? `[${filters}]` : undefined,
125 order: order ? order : undefined
127 return CommonResourceService.defaultResponse(
129 .get(this.resourceType, {
130 params: CommonResourceService.mapKeys(_.snakeCase)(params)
136 update(uuid: string, data: Partial<T>) {
137 return CommonResourceService.defaultResponse(
139 .put<T>(this.resourceType + uuid, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
145 export const getCommonResourceServiceError = (errorResponse: any) => {
146 if ('errors' in errorResponse && 'errorToken' in errorResponse) {
147 const error = errorResponse.errors.join('');
149 case /UniqueViolation/.test(error):
150 return CommonResourceServiceError.UNIQUE_VIOLATION;
151 case /ownership cycle/.test(error):
152 return CommonResourceServiceError.OWNERSHIP_CYCLE;
153 case /Mounts cannot be modified in state 'Final'/.test(error):
154 return CommonResourceServiceError.MODIFYING_CONTAINER_REQUEST_FINAL_STATE;
155 case /Public key does not appear to be a valid ssh-rsa or dsa public key/.test(error):
156 return CommonResourceServiceError.INVALID_PUBLIC_KEY;
157 case /Public key already exists in the database, use a different key./.test(error):
158 return CommonResourceServiceError.UNIQUE_PUBLIC_KEY;
160 return CommonResourceServiceError.UNKNOWN;
163 return CommonResourceServiceError.NONE;