Merge pull request #975 from spotify/rugvip/create

packages/core: switch to only using createApiRef
This commit is contained in:
Patrik Oldsberg
2020-05-25 12:35:27 +02:00
committed by GitHub
17 changed files with 65 additions and 68 deletions
@@ -15,12 +15,12 @@
*/
import { ApiAggregator } from './ApiAggregator';
import { ApiRef } from './ApiRef';
import { createApiRef } from './ApiRef';
import { ApiRegistry } from './ApiRegistry';
describe('ApiAggregator', () => {
const apiARef = new ApiRef<number>({ id: 'a', description: '' });
const apiBRef = new ApiRef<number>({ id: 'b', description: '' });
const apiARef = createApiRef<number>({ id: 'a', description: '' });
const apiBRef = createApiRef<number>({ id: 'b', description: '' });
it('should forward implementations', () => {
const agg = new ApiAggregator(
@@ -16,14 +16,14 @@
import React from 'react';
import { ApiProvider, useApi, withApis } from './ApiProvider';
import { ApiRef } from './ApiRef';
import { createApiRef } from './ApiRef';
import { ApiRegistry } from './ApiRegistry';
import { render } from '@testing-library/react';
import { withLogCollector } from '@backstage/test-utils-core';
describe('ApiProvider', () => {
type Api = () => string;
const apiRef = new ApiRef<Api>({ id: 'x', description: '' });
const apiRef = createApiRef<Api>({ id: 'x', description: '' });
const registry = ApiRegistry.from([[apiRef, () => 'hello']]);
const MyHookConsumer = () => {
@@ -53,7 +53,7 @@ describe('ApiProvider', () => {
it('should ignore deps in prototype', () => {
// 100% coverage + happy typescript = hasOwnProperty + this atrocity
const xRef = new ApiRef<number>({ id: 'x', description: '' });
const xRef = createApiRef<number>({ id: 'x', description: '' });
const proto = { x: xRef };
const props = { getMessage: { enumerable: true, value: apiRef } };
+7 -7
View File
@@ -14,11 +14,11 @@
* limitations under the License.
*/
import { ApiRef } from './ApiRef';
import { createApiRef } from './ApiRef';
describe('ApiRef', () => {
it('should be created', () => {
const ref = new ApiRef({ id: 'abc', description: '123' });
const ref = createApiRef({ id: 'abc', description: '123' });
expect(ref.id).toBe('abc');
expect(ref.description).toBe('123');
expect(String(ref)).toBe('apiRef{abc}');
@@ -26,13 +26,13 @@ describe('ApiRef', () => {
});
it('should reject invalid ids', () => {
for (const id of ['a', 'abc', 'a.b.c', 'ab.c', 'abc.abc.abc3']) {
expect(new ApiRef({ id, description: '123' }).id).toBe(id);
for (const id of ['a', 'abc', 'ab-c', 'a.b.c', 'a-b.c', 'abc.a-b-c.abc3']) {
expect(createApiRef({ id, description: '123' }).id).toBe(id);
}
for (const id of [
'123',
'ab-c',
'ab-3',
'ab_c',
'.',
'2ac',
@@ -43,8 +43,8 @@ describe('ApiRef', () => {
'',
'_',
]) {
expect(() => new ApiRef({ id, description: '123' }).id).toThrow(
`API id must only contain lowercase alphanums separated by dots, got '${id}'`,
expect(() => createApiRef({ id, description: '123' }).id).toThrow(
`API id must only contain period separated lowercase alphanum tokens with dashes, got '${id}'`,
);
}
});
+15 -5
View File
@@ -19,11 +19,21 @@ export type ApiRefConfig = {
description: string;
};
export class ApiRef<T> {
export type ApiRef<T> = {
id: string;
description: string;
T: T;
};
class ApiRefImpl<T> implements ApiRef<T> {
constructor(private readonly config: ApiRefConfig) {
if (!config.id.match(/^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$/)) {
const valid = config.id
.split('.')
.flatMap(part => part.split('-'))
.every(part => part.match(/^[a-z][a-z0-9]*$/));
if (!valid) {
throw new Error(
`API id must only contain lowercase alphanums separated by dots, got '${config.id}'`,
`API id must only contain period separated lowercase alphanum tokens with dashes, got '${config.id}'`,
);
}
}
@@ -46,6 +56,6 @@ export class ApiRef<T> {
}
}
export function createApiRef<T>(config: ApiRefConfig) {
return new ApiRef<T>(config);
export function createApiRef<T>(config: ApiRefConfig): ApiRef<T> {
return new ApiRefImpl<T>(config);
}
@@ -15,11 +15,11 @@
*/
import { ApiRegistry } from './ApiRegistry';
import { ApiRef } from './ApiRef';
import { createApiRef } from './ApiRef';
describe('ApiRegistry', () => {
const x1Ref = new ApiRef<number>({ id: 'x', description: '' });
const x2Ref = new ApiRef<string>({ id: 'x', description: '' });
const x1Ref = createApiRef<number>({ id: 'x', description: '' });
const x2Ref = createApiRef<string>({ id: 'x', description: '' });
it('should be created', () => {
const registry = ApiRegistry.from([]);
@@ -15,12 +15,12 @@
*/
import { ApiTestRegistry } from './ApiTestRegistry';
import { ApiRef } from './ApiRef';
import { createApiRef } from './ApiRef';
describe('ApiTestRegistry', () => {
const aRef = new ApiRef<number>({ id: 'a', description: '' });
const bRef = new ApiRef<string>({ id: 'b', description: '' });
const cRef = new ApiRef<string>({ id: 'c', description: '' });
const aRef = createApiRef<number>({ id: 'a', description: '' });
const bRef = createApiRef<string>({ id: 'b', description: '' });
const cRef = createApiRef<string>({ id: 'c', description: '' });
it('should be created', () => {
const registry = new ApiTestRegistry();
@@ -31,7 +31,7 @@ describe('ApiTestRegistry', () => {
it('should register a factory', () => {
const registry = new ApiTestRegistry();
registry.register(aRef, () => 3);
registry.register({ implements: aRef, deps: {}, factory: () => 3 });
expect(registry.get(aRef)).toBe(3);
expect(registry.get(bRef)).toBe(undefined);
expect(registry.get(cRef)).toBe(undefined);
@@ -39,7 +39,7 @@ describe('ApiTestRegistry', () => {
it('should remove factories when resetting', () => {
const registry = new ApiTestRegistry();
registry.register(aRef, () => 3);
registry.register({ implements: aRef, deps: {}, factory: () => 3 });
expect(registry.get(aRef)).toBe(3);
registry.reset();
expect(registry.get(aRef)).toBe(undefined);
@@ -47,9 +47,9 @@ describe('ApiTestRegistry', () => {
it('should keep saved factories when resetting', () => {
const registry = new ApiTestRegistry();
registry.register(aRef, () => 3);
registry.register({ implements: aRef, deps: {}, factory: () => 3 });
registry.save();
registry.register(bRef, () => 'x');
registry.register({ implements: bRef, deps: {}, factory: () => 'x' });
expect(registry.get(aRef)).toBe(3);
expect(registry.get(bRef)).toBe('x');
registry.reset();
@@ -127,7 +127,7 @@ describe('ApiTestRegistry', () => {
it('should only call factory func once', () => {
const registry = new ApiTestRegistry();
const factory = jest.fn().mockReturnValue(2);
registry.register(aRef, factory);
registry.register({ implements: aRef, deps: {}, factory });
expect(factory).toHaveBeenCalledTimes(0);
expect(registry.get(aRef)).toBe(2);
@@ -139,7 +139,7 @@ describe('ApiTestRegistry', () => {
it('should call factory again after reset', () => {
const registry = new ApiTestRegistry();
const factory = jest.fn().mockReturnValue(2);
registry.register(aRef, factory);
registry.register({ implements: aRef, deps: {}, factory });
registry.save();
expect(factory).toHaveBeenCalledTimes(0);
+2 -15
View File
@@ -32,21 +32,8 @@ export class ApiTestRegistry implements ApiHolder {
return this.load(ref);
}
register<T>(ref: ApiRef<T>, factoryFunc: () => T): ApiTestRegistry;
register<A, I, D>(factory: ApiFactory<A, I, D>): ApiTestRegistry;
register<A, I, D, T>(
factory: ApiRef<T> | ApiFactory<A, I, D>,
factoryFunc?: () => T,
): ApiTestRegistry {
if (factory instanceof ApiRef) {
this.factories.set(factory, {
implements: factory,
deps: {},
factory: factoryFunc!,
});
} else {
this.factories.set(factory.implements, factory);
}
register<A, I, D>(factory: ApiFactory<A, I, D>): ApiTestRegistry {
this.factories.set(factory.implements, factory);
return this;
}
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ApiRef } from '../ApiRef';
import { createApiRef } from '../ApiRef';
export type AlertMessage = {
message: string;
@@ -32,7 +32,7 @@ export type AlertApi = {
post(alert: AlertMessage): void;
};
export const alertApiRef = new ApiRef<AlertApi>({
export const alertApiRef = createApiRef<AlertApi>({
id: 'core.alert',
description: 'Used to report alerts and forward them to the app',
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef } from '../ApiRef';
import { createApiRef } from '../ApiRef';
import { BackstageTheme } from '@backstage/theme';
import { Observable } from '../../types';
@@ -71,7 +71,7 @@ export type AppThemeApi = {
setActiveThemeId(themeId?: string): void;
};
export const appThemeApiRef = new ApiRef<AppThemeApi>({
export const appThemeApiRef = createApiRef<AppThemeApi>({
id: 'core.apptheme',
description: 'API Used to configure the app theme, and enumerate options',
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef } from '../ApiRef';
import { createApiRef } from '../ApiRef';
/**
* Mirrors the javascript Error class, for the purpose of
@@ -56,7 +56,7 @@ export type ErrorApi = {
post(error: Error, context?: ErrorContext): void;
};
export const errorApiRef = new ApiRef<ErrorApi>({
export const errorApiRef = createApiRef<ErrorApi>({
id: 'core.error',
description: 'Used to report errors and forward them to the app',
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef } from '../ApiRef';
import { createApiRef } from '../ApiRef';
import {
UserFlags,
FeatureFlagsRegistry,
@@ -55,7 +55,7 @@ export interface FeatureFlagsApi {
getRegisteredFlags(): FeatureFlagsRegistry;
}
export const featureFlagsApiRef = new ApiRef<FeatureFlagsApi>({
export const featureFlagsApiRef = createApiRef<FeatureFlagsApi>({
id: 'core.featureflags',
description: 'Used to toggle functionality in features across Backstage',
});
@@ -16,7 +16,7 @@
import { IconComponent } from '../../../icons';
import { Observable } from '../../types';
import { ApiRef } from '../ApiRef';
import { createApiRef } from '../ApiRef';
/**
* Information about the auth provider that we're requesting a login towards.
@@ -127,7 +127,7 @@ export type OAuthRequestApi = {
authRequest$(): Observable<PendingAuthRequest[]>;
};
export const oauthRequestApiRef = new ApiRef<OAuthRequestApi>({
export const oauthRequestApiRef = createApiRef<OAuthRequestApi>({
id: 'core.oauthrequest',
description: 'An API for implementing unified OAuth flows in Backstage',
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef } from '../ApiRef';
import { createApiRef } from '../ApiRef';
/**
* This file contains declarations for common interfaces of auth-related APIs.
@@ -24,7 +24,7 @@ import { ApiRef } from '../ApiRef';
* For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect,
* would be declared as follows:
*
* const googleAuthApiRef = new ApiRef<OAuthApi & OpenIDConnectApi>({ ... })
* const googleAuthApiRef = createApiRef<OAuthApi & OpenIDConnectApi>({ ... })
*/
/**
@@ -148,7 +148,7 @@ export type OpenIdConnectApi = {
* Note that the ID token payload is only guaranteed to contain the user's numerical Google ID,
* email and expiration information. Do not rely on any other fields, as they might not be present.
*/
export const googleAuthApiRef = new ApiRef<OAuthApi & OpenIdConnectApi>({
export const googleAuthApiRef = createApiRef<OAuthApi & OpenIdConnectApi>({
id: 'core.auth.google',
description: 'Provides authentication towards Google APIs and identities',
});
@@ -159,7 +159,7 @@ export const googleAuthApiRef = new ApiRef<OAuthApi & OpenIdConnectApi>({
* See https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
* for a full list of supported scopes.
*/
export const githubAuthApiRef = new ApiRef<OAuthApi>({
export const githubAuthApiRef = createApiRef<OAuthApi>({
id: 'core.auth.github',
description: 'Provides authentication towards Github APIs',
});
+2 -2
View File
@@ -26,11 +26,11 @@ import {
BuildSummary,
GitType,
} from 'circleci-api';
import { ApiRef } from '@backstage/core';
import { createApiRef } from '@backstage/core';
export { BuildWithSteps, BuildStepAction, BuildSummary, GitType };
export const circleCIApiRef = new ApiRef<CircleCIApi>({
export const circleCIApiRef = createApiRef<CircleCIApi>({
id: 'plugin.circleci.service',
description: 'Used by the CircleCI plugin to make requests',
});
+2 -2
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef } from '@backstage/core';
import { createApiRef } from '@backstage/core';
export type GraphQLEndpoint = {
// Will be used as unique key for storing history and query data
@@ -33,7 +33,7 @@ export type GraphQLBrowseApi = {
getEndpoints(): Promise<GraphQLEndpoint[]>;
};
export const graphQlBrowseApiRef = new ApiRef<GraphQLBrowseApi>({
export const graphQlBrowseApiRef = createApiRef<GraphQLBrowseApi>({
id: 'plugin.graphiql.browse',
description: 'Used to supply GraphQL endpoints for browsing',
});
+2 -2
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef } from '@backstage/core';
import { createApiRef } from '@backstage/core';
export type LighthouseCategoryId =
| 'pwa'
@@ -105,7 +105,7 @@ export type LighthouseApi = {
triggerAudit: (payload: TriggerAuditPayload) => Promise<Audit>;
};
export const lighthouseApiRef = new ApiRef<LighthouseApi>({
export const lighthouseApiRef = createApiRef<LighthouseApi>({
id: 'plugin.lighthouse.service',
description: 'Used by the Lighthouse plugin to make requests',
});
+2 -2
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef } from '@backstage/core';
import { createApiRef } from '@backstage/core';
/**
* Types related to the Radar's visualization.
@@ -71,7 +71,7 @@ export interface TechRadarApi extends TechRadarComponentProps {
subtitle?: string;
}
export const techRadarApiRef = new ApiRef<TechRadarApi>({
export const techRadarApiRef = createApiRef<TechRadarApi>({
id: 'plugin.techradar',
description: 'Used by the Tech Radar to render the visualization',
});