Files
gerrit/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api.ts
Ben Rohlfs d121f72b1d Define a default errFn for REST API calls from plugins
The REST API service was recently fixed to fire error events on the
document instead of on itself. This is a change in behavior for plugins,
because they were using a component instance that was detached from the
DOM.

We want to revert back to the behavior where plugins would typically
not expect Gerrit to handle errors by showing error dialogs. So we are
defining a default errFn at least for the known case of a Chrome plugin
that would otherwise show a distracting dialog when no config was found.

We are swapping the catch() and then() clauses in the rest-api-helper
such that errFn can throw an error without the catch() clause catching
it. errFn throwing an error is the only situation where the swap
matters.

We are planning a lot of further clean-ups and want to ultimately remove
the errFn support. There are a lot of follow-up changes expected, but
here we just want to fix Gerrit and bring it into a releasable state.

Change-Id: I3f441a0a7587b791223c4ae89fe0547cb2ea090c
2020-12-03 08:39:31 +01:00

197 lines
5.1 KiB
TypeScript

/**
* @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 {
ErrorCallback,
RestApiService,
} from '../../../services/services/gr-rest-api/gr-rest-api';
import {HttpMethod} from '../../../constants/constants';
import {RequestPayload} from '../../../types/common';
let restApi: RestApiService | null = null;
export function _testOnlyResetRestApi() {
restApi = null;
}
function getRestApi(): RestApiService {
if (!restApi) {
restApi = (document.createElement(
'gr-rest-api-interface'
) as unknown) as RestApiService;
}
return restApi;
}
export class GrPluginRestApi {
constructor(private readonly prefix = '') {}
getLoggedIn() {
return getRestApi().getLoggedIn();
}
getVersion() {
return getRestApi().getVersion();
}
getConfig() {
return getRestApi().getConfig();
}
invalidateReposCache() {
getRestApi().invalidateReposCache();
}
getAccount() {
return getRestApi().getAccount();
}
getAccountCapabilities(capabilities: string[]) {
return getRestApi().getAccountCapabilities(capabilities);
}
getRepos(filter: string, reposPerPage: number, offset?: number) {
return getRestApi().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> {
return getRestApi().send(
method,
this.prefix + url,
payload,
errFn,
contentType
);
}
/**
* Fetch and parse REST API response, if request succeeds.
*/
send(
method: HttpMethod,
url: string,
payload?: RequestPayload,
errFn?: ErrorCallback,
contentType?: string
) {
// 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;
if (response) throw new Error(`${response.status}`);
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 response.text().then(text => {
if (text) {
return Promise.reject(new Error(text));
} else {
return Promise.reject(new Error(`${response.status}`));
}
});
} else {
return getRestApi().getResponseObject(response);
}
}
);
}
get(url: string) {
return this.send(HttpMethod.GET, url);
}
post(
url: string,
payload?: RequestPayload,
errFn?: ErrorCallback,
contentType?: string
) {
return this.send(HttpMethod.POST, url, payload, errFn, contentType);
}
put(
url: string,
payload?: RequestPayload,
errFn?: ErrorCallback,
contentType?: string
) {
return this.send(HttpMethod.PUT, url, payload, errFn, contentType);
}
delete(url: string) {
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;
});
}
}