| /** |
| * @license |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| import {HttpMethod} from '../../../constants/constants'; |
| import {RequestPayload} from '../../../types/common'; |
| import {appContext} from '../../../services/app-context'; |
| import {ErrorCallback, RestPluginApi} from '../../../api/rest'; |
| import {PluginApi} from '../../../api/plugin'; |
| |
| async function getErrorMessage(response: Response): Promise<string> { |
| const text = await response.text(); |
| return text || `${response.status}`; |
| } |
| |
| // This is an internal error, that must never be visible outside of this |
| // file. It is used only inside GrPluginRestApi.send method. See detailed |
| // explanation in the GrPluginRestApi.send method. |
| class ResponseError extends Error { |
| public constructor(readonly response: Response) { |
| super(); |
| } |
| } |
| |
| export class GrPluginRestApi implements RestPluginApi { |
| private readonly restApi = appContext.restApiService; |
| |
| private readonly reporting = appContext.reportingService; |
| |
| constructor(readonly plugin: PluginApi, private readonly prefix = '') { |
| this.reporting.trackApi(this.plugin, 'rest', 'constructor'); |
| } |
| |
| getLoggedIn() { |
| this.reporting.trackApi(this.plugin, 'rest', 'getLoggedIn'); |
| return this.restApi.getLoggedIn(); |
| } |
| |
| getVersion() { |
| this.reporting.trackApi(this.plugin, 'rest', 'getVersion'); |
| return this.restApi.getVersion(); |
| } |
| |
| getConfig() { |
| this.reporting.trackApi(this.plugin, 'rest', 'getConfig'); |
| return this.restApi.getConfig(); |
| } |
| |
| invalidateReposCache() { |
| this.reporting.trackApi(this.plugin, 'rest', 'invalidateReposCache'); |
| this.restApi.invalidateReposCache(); |
| } |
| |
| getAccount() { |
| this.reporting.trackApi(this.plugin, 'rest', 'getAccount'); |
| return this.restApi.getAccount(); |
| } |
| |
| getAccountCapabilities(capabilities: string[]) { |
| this.reporting.trackApi(this.plugin, 'rest', 'getAccountCapabilities'); |
| return this.restApi.getAccountCapabilities(capabilities); |
| } |
| |
| getRepos(filter: string, reposPerPage: number, offset?: number) { |
| this.reporting.trackApi(this.plugin, 'rest', 'getRepos'); |
| return this.restApi.getRepos(filter, reposPerPage, offset); |
| } |
| |
| fetch( |
| method: HttpMethod, |
| url: string, |
| payload?: RequestPayload, |
| errFn?: undefined, |
| contentType?: string |
| ): Promise<Response>; |
| |
| fetch( |
| method: HttpMethod, |
| url: string, |
| payload: RequestPayload | undefined, |
| errFn: ErrorCallback, |
| contentType?: string |
| ): Promise<Response | void>; |
| |
| fetch( |
| method: HttpMethod, |
| url: string, |
| payload: RequestPayload | undefined, |
| errFn?: ErrorCallback, |
| contentType?: string |
| ): Promise<Response | void>; |
| |
| /** |
| * Fetch and return native browser REST API Response. |
| */ |
| fetch( |
| method: HttpMethod, |
| url: string, |
| payload?: RequestPayload, |
| errFn?: ErrorCallback, |
| contentType?: string |
| ): Promise<Response | void> { |
| this.reporting.trackApi(this.plugin, 'rest', 'fetch'); |
| return this.restApi.send( |
| method, |
| this.prefix + url, |
| payload, |
| errFn, |
| contentType |
| ); |
| } |
| |
| /** |
| * Fetch and parse REST API response, if request succeeds. |
| */ |
| send<T>( |
| method: HttpMethod, |
| url: string, |
| payload?: RequestPayload, |
| errFn?: ErrorCallback, |
| contentType?: string |
| ) { |
| this.reporting.trackApi(this.plugin, 'rest', 'send'); |
| // Plugins typically don't want Gerrit to show error dialogs for failed |
| // requests. So we are defining a default errFn here, even if it is not |
| // explicitly set by the caller. |
| // TODO: We are soon getting rid of the `errFn` altogether. There are only |
| // 2 known usages of errFn in plugins: delete-project and verify-status. |
| errFn = |
| errFn ?? |
| ((response: Response | null | undefined, error?: Error) => { |
| if (error) throw error; |
| // Some plugins show an error message if send is failed, smth like: |
| // pluginApi.send(...).catch(err => showError(err)); |
| // The response can contain an error text, but getting this text is |
| // an asynchronous operation. At the same time, the errFn must be a |
| // synchronous function. |
| // As a workaround, we throw an ResponseError here and then catch |
| // it inside a catch block below and read the message. |
| if (response) throw new ResponseError(response); |
| throw new Error('Generic REST API error.'); |
| }); |
| return this.fetch(method, url, payload, errFn, contentType) |
| .then(response => { |
| // Will typically not happen. The response can only be unset, if the |
| // errFn handles the error and then returns void or undefined or null. |
| // But the errFn above always throws. |
| if (!response) { |
| throw new Error('plugin rest-api call failed'); |
| } |
| // Will typically not happen. errFn will have dealt with that and the |
| // caller will get a rejected promise already. |
| if (response.status < 200 || response.status >= 300) { |
| return getErrorMessage(response).then(msg => |
| Promise.reject(new Error(msg)) |
| ); |
| } else { |
| return this.restApi.getResponseObject(response) as Promise<T>; |
| } |
| }) |
| .catch(err => { |
| if (err instanceof ResponseError) { |
| return getErrorMessage(err.response).then(msg => { |
| throw new Error(msg); |
| }); |
| } |
| throw err; |
| }); |
| } |
| |
| get<T>(url: string) { |
| this.reporting.trackApi(this.plugin, 'rest', 'get'); |
| return this.send<T>(HttpMethod.GET, url); |
| } |
| |
| post<T>( |
| url: string, |
| payload?: RequestPayload, |
| errFn?: ErrorCallback, |
| contentType?: string |
| ) { |
| this.reporting.trackApi(this.plugin, 'rest', 'post'); |
| return this.send<T>(HttpMethod.POST, url, payload, errFn, contentType); |
| } |
| |
| put<T>( |
| url: string, |
| payload?: RequestPayload, |
| errFn?: ErrorCallback, |
| contentType?: string |
| ) { |
| this.reporting.trackApi(this.plugin, 'rest', 'put'); |
| return this.send<T>(HttpMethod.PUT, url, payload, errFn, contentType); |
| } |
| |
| delete(url: string) { |
| this.reporting.trackApi(this.plugin, 'rest', 'delete'); |
| return this.fetch(HttpMethod.DELETE, url).then(response => { |
| if (response.status !== 204) { |
| return response.text().then(text => { |
| if (text) { |
| return Promise.reject(new Error(text)); |
| } else { |
| return Promise.reject(new Error(`${response.status}`)); |
| } |
| }); |
| } |
| return response; |
| }); |
| } |
| } |