diff --git a/.changeset/stale-jobs-tease.md b/.changeset/stale-jobs-tease.md new file mode 100644 index 0000000000..e62f87afe7 --- /dev/null +++ b/.changeset/stale-jobs-tease.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Change the response status of metadata endpoints in case a documentation is not +available to `404 NOT FOUND`. This also introduces the JSON based error messages +used by other backends. diff --git a/plugins/techdocs-backend/src/service/helpers.test.ts b/plugins/techdocs-backend/src/service/helpers.test.ts deleted file mode 100644 index 507373107d..0000000000 --- a/plugins/techdocs-backend/src/service/helpers.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 { getEntityNameFromUrlPath } from './helpers'; - -describe('getEntityNameFromUrlPath', () => { - it('should parse correctly', () => { - const path = 'default/Component/documented-component'; - const parsedEntity = getEntityNameFromUrlPath(path); - expect(parsedEntity).toHaveProperty('namespace', 'default'); - expect(parsedEntity).toHaveProperty('kind', 'Component'); - expect(parsedEntity).toHaveProperty('name', 'documented-component'); - }); -}); diff --git a/plugins/techdocs-backend/src/service/helpers.ts b/plugins/techdocs-backend/src/service/helpers.ts deleted file mode 100644 index fc80ab717d..0000000000 --- a/plugins/techdocs-backend/src/service/helpers.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { EntityName } from '@backstage/catalog-model'; -/** - * Using the path of the TechDocs page URL, return a structured EntityName type object with namespace, - * kind and name of the Entity. - * @param {string} path Example: default/Component/documented-component - */ -export const getEntityNameFromUrlPath = (path: string): EntityName => { - const [namespace, kind, name] = path.split('/'); - - return { - namespace, - kind, - name, - }; -}; diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 9456e8a277..121ddddef5 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -14,8 +14,9 @@ * limitations under the License. */ import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; +import { NotFoundError } from '@backstage/errors'; import { GeneratorBuilder, getLocationForEntity, @@ -30,8 +31,6 @@ import { Knex } from 'knex'; import { Logger } from 'winston'; import { DocsBuilder } from '../DocsBuilder'; import { shouldCheckForUpdate } from '../DocsBuilder/BuildMetadataStorage'; -import { getEntityNameFromUrlPath } from './helpers'; -import { NotFoundError } from '@backstage/errors'; type RouterOptions = { preparers: PreparerBuilder; @@ -55,10 +54,9 @@ export async function createRouter({ }: RouterOptions): Promise { const router = Router(); - router.get('/metadata/techdocs/*', async (req, res) => { - // path is `:namespace/:kind:/:name` - const { '0': path } = req.params; - const entityName = getEntityNameFromUrlPath(path); + router.get('/metadata/techdocs/:namespace/:kind/:name', async (req, res) => { + const { kind, namespace, name } = req.params; + const entityName = { kind, namespace, name }; try { const techdocsMetadata = await publisher.fetchTechDocsMetadata( @@ -67,14 +65,15 @@ export async function createRouter({ res.json(techdocsMetadata); } catch (err) { - logger.error( - `Unable to get metadata for ${entityName.namespace}/${entityName.name} with error ${err}`, + logger.info( + `Unable to get metadata for '${stringifyEntityRef( + entityName, + )}' with error ${err}`, + ); + throw new NotFoundError( + `Unable to get metadata for '${stringifyEntityRef(entityName)}'`, + err, ); - res - .status(500) - .send( - `Unable to get metadata for $${entityName.namespace}/${entityName.name}, reason: ${err}`, - ); } }); @@ -82,9 +81,11 @@ export async function createRouter({ const catalogUrl = await discovery.getBaseUrl('catalog'); const { kind, namespace, name } = req.params; + const entityName = { kind, namespace, name }; try { const token = getBearerToken(req.headers.authorization); + // TODO: Consider using the catalog client here const entity = (await ( await fetch( `${catalogUrl}/entities/by-name/${kind}/${namespace}/${name}`, @@ -98,10 +99,13 @@ export async function createRouter({ res.json({ ...entity, locationMetadata }); } catch (err) { logger.info( - `Unable to get metadata for ${kind}/${namespace}/${name} with error ${err}`, + `Unable to get metadata for '${stringifyEntityRef( + entityName, + )}' with error ${err}`, ); - throw new Error( - `Unable to get metadata for ${kind}/${namespace}/${name} with error ${err}`, + throw new NotFoundError( + `Unable to get metadata for '${stringifyEntityRef(entityName)}'`, + err, ); } });