core-api: add DiscoveryApi + UrlPatternDiscovery implementation
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { createApiRef } from '../ApiRef';
|
||||
|
||||
/**
|
||||
* The discovery API is used to provide a mechanism for plugins to
|
||||
* discover the endpoint to use to talk to their backend counterpart.
|
||||
*
|
||||
* The purpose of the discovery API is to allow for many different deployment
|
||||
* setups and routing methods through a central configuration, instead
|
||||
* of letting each individual plugin manage that configuration.
|
||||
*
|
||||
* Implementations of the discovery API can be a simple as a URL pattern
|
||||
* using the pluginId, but could also have overrides for individual plugins,
|
||||
* or query a separate discovery service.
|
||||
*/
|
||||
export type DiscoveryApi = {
|
||||
/**
|
||||
* Returns the HTTP base backend URL for a given plugin, without a trailing slash.
|
||||
*
|
||||
* This method must always be called just before making a request. as opposed to
|
||||
* fetching the URL when constructing an API client. That is to ensure that more
|
||||
* flexible routing patterns can be supported.
|
||||
*
|
||||
* For example, asking for the URL for `auth` may return something
|
||||
* like `https://backstage.example.com/api/auth`
|
||||
*/
|
||||
getBaseUrl(pluginId: string): Promise<string>;
|
||||
};
|
||||
|
||||
export const discoveryApiRef = createApiRef<DiscoveryApi>({
|
||||
id: 'core.discovery',
|
||||
description: 'Provides service discovery of backend plugins',
|
||||
});
|
||||
@@ -27,6 +27,7 @@ export * from './AppThemeApi';
|
||||
export * from './ConfigApi';
|
||||
export * from './ErrorApi';
|
||||
export * from './FeatureFlagsApi';
|
||||
export * from './DiscoveryApi';
|
||||
export * from './IdentityApi';
|
||||
export * from './OAuthRequestApi';
|
||||
export * from './StorageApi';
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { UrlPatternDiscovery } from './UrlPatternDiscovery';
|
||||
|
||||
describe('UrlPatternDiscovery', () => {
|
||||
it('should not require interpolation', async () => {
|
||||
const discoveryApi = UrlPatternDiscovery.compile('http://example.com');
|
||||
await expect(discoveryApi.getBaseUrl('my-plugin')).resolves.toBe(
|
||||
'http://example.com',
|
||||
);
|
||||
});
|
||||
|
||||
it('should use a plain pattern', async () => {
|
||||
const discoveryApi = UrlPatternDiscovery.compile(
|
||||
'http://localhost:7000/{{ pluginId }}',
|
||||
);
|
||||
await expect(discoveryApi.getBaseUrl('my-plugin')).resolves.toBe(
|
||||
'http://localhost:7000/my-plugin',
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow for multiple interpolation points', async () => {
|
||||
const discoveryApi = UrlPatternDiscovery.compile(
|
||||
'https://{{pluginId }}.example.com/api/{{ pluginId}}',
|
||||
);
|
||||
await expect(discoveryApi.getBaseUrl('my-plugin')).resolves.toBe(
|
||||
'https://my-plugin.example.com/api/my-plugin',
|
||||
);
|
||||
});
|
||||
|
||||
it('should validate that the pattern is a valid URL', () => {
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('example.com');
|
||||
}).toThrow('Invalid discovery URL pattern, Invalid URL: example.com');
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('http://');
|
||||
}).toThrow('Invalid discovery URL pattern, Invalid URL: http://');
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('abc123');
|
||||
}).toThrow('Invalid discovery URL pattern, Invalid URL: abc123');
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('http://example.com:{{pluginId}}');
|
||||
}).toThrow(
|
||||
'Invalid discovery URL pattern, Invalid URL: http://example.com:pluginId',
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('/{{pluginId}}');
|
||||
}).toThrow('Invalid discovery URL pattern, Invalid URL: /pluginId');
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('http://localhost/{{pluginId}}?forbidden');
|
||||
}).toThrow('Invalid discovery URL pattern, URL must not have a query');
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('http://localhost/{{pluginId}}#forbidden');
|
||||
}).toThrow('Invalid discovery URL pattern, URL must not have a hash');
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('http://localhost/{{pluginId}}/');
|
||||
}).toThrow('Invalid discovery URL pattern, URL must not end with a slash');
|
||||
|
||||
expect(() => {
|
||||
UrlPatternDiscovery.compile('http://localhost/');
|
||||
}).toThrow('Invalid discovery URL pattern, URL must not end with a slash');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { DiscoveryApi } from '../../definitions/DiscoveryApi';
|
||||
|
||||
/**
|
||||
* UrlPatternDiscovery is a lightweight DiscoveryApi implementation.
|
||||
* It uses a single template string to construct URLs for each plugin.
|
||||
*/
|
||||
export class UrlPatternDiscovery implements DiscoveryApi {
|
||||
/**
|
||||
* Creates a new UrlPatternDiscovery given a template. The the only
|
||||
* interpolation done for the template is to replace instances of `{{pluginId}}`
|
||||
* with the ID of the plugin being requested.
|
||||
*
|
||||
* Example pattern: `http://localhost:7000/api/{{ pluginId }}`
|
||||
*/
|
||||
static compile(pattern: string): UrlPatternDiscovery {
|
||||
const parts = pattern.split(/\{\{\s*pluginId\s*\}\}/);
|
||||
|
||||
try {
|
||||
const urlStr = parts.join('pluginId');
|
||||
const url = new URL(urlStr);
|
||||
if (url.hash) {
|
||||
throw new Error('URL must not have a hash');
|
||||
}
|
||||
if (url.search) {
|
||||
throw new Error('URL must not have a query');
|
||||
}
|
||||
if (urlStr.endsWith('/')) {
|
||||
throw new Error('URL must not end with a slash');
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Invalid discovery URL pattern, ${error.message}`);
|
||||
}
|
||||
|
||||
return new UrlPatternDiscovery(parts);
|
||||
}
|
||||
|
||||
private constructor(private readonly parts: string[]) {}
|
||||
|
||||
async getBaseUrl(pluginId: string): Promise<string> {
|
||||
return this.parts.join(pluginId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// This folder contains implementations for all core APIs.
|
||||
//
|
||||
// Plugins should rely on these APIs for functionality as much as possible.
|
||||
|
||||
export { UrlPatternDiscovery } from './UrlPatternDiscovery';
|
||||
@@ -24,5 +24,6 @@ export * from './AlertApi';
|
||||
export * from './AppThemeApi';
|
||||
export * from './ConfigApi';
|
||||
export * from './ErrorApi';
|
||||
export * from './DiscoveryApi';
|
||||
export * from './OAuthRequestApi';
|
||||
export * from './StorageApi';
|
||||
|
||||
Reference in New Issue
Block a user