Skip to content

Commit

Permalink
feat: load metadata from mapped path (#298)
Browse files Browse the repository at this point in the history
* feat: load metadata from mapped path

* fix: always provide a default in `state.mappedMetadata`

* fix: add surrogate key

* fix: no mapped metadata when target is in code-bus

* fix: getModifiers missing
  • Loading branch information
dominique-pfister authored Apr 24, 2023
1 parent e51b14f commit a1fc7b0
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/html-pipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { PipelineStatusError } from './PipelineStatusError.js';
import { PipelineResponse } from './PipelineResponse.js';
import { validatePathInfo } from './utils/path.js';
import { initAuthRoute } from './utils/auth.js';
import fetchMappedMetadata from './steps/fetch-mapped-metadata.js';

/**
* Runs the default pipeline and returns the response.
Expand Down Expand Up @@ -88,6 +89,7 @@ export async function htmlPipe(state, req) {
await Promise.all([
fetchConfigAll(state, req, res),
fetchContent(state, req, res),
fetchMappedMetadata(state),
]);

await requireProject(state, req, res);
Expand Down
1 change: 1 addition & 0 deletions src/steps/extract-metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export default function extractMetaData(state, req) {
// with local metadata from document
const metaConfig = Object.assign(
state.metadata.getModifiers(state.info.unmappedPath || state.info.path),
state.mappedMetadata.getModifiers(state.info.path),
getLocalMetadata(hast),
);

Expand Down
4 changes: 2 additions & 2 deletions src/steps/fetch-config-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ async function fetchMetadata(state, req, res) {
try {
json = JSON.parse(ret.body);
} catch (e) {
throw new PipelineStatusError(400, `failed parsing of /metadata.json: ${e.message}`);
throw new PipelineStatusError(500, `failed parsing of /metadata.json: ${e.message}`);
}

const { data } = json.default ?? json;
if (!Array.isArray(data)) {
throw new PipelineStatusError(400, 'failed loading of /metadata.json: data must be an array');
throw new PipelineStatusError(500, 'failed loading of /metadata.json: data must be an array');
}

state.metadata = Modifiers.fromModifierSheet(
Expand Down
55 changes: 55 additions & 0 deletions src/steps/fetch-mapped-metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2021 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { PipelineStatusError } from '../PipelineStatusError.js';
import { Modifiers } from '../utils/modifiers.js';

/**
* Loads metadata for a mapped path and puts it into `state.mappedMetadata`. If path is not
* mapped or there is no `metadata.json` at that path, puts an `EMPTY` Modifiers object into
* `state.mappedMetadata`.
*
* @type PipelineStep
* @param {PipelineState} state
* @returns {Promise<void>}
*/
export default async function fetchMappedMetadata(state) {
state.mappedMetadata = Modifiers.EMPTY;
if (!state.mapped) {
return;
}
const { contentBusId, partition } = state;
const metadataPath = `${state.info.path}/metadata.json`;
const key = `${contentBusId}/${partition}${metadataPath}`;
const ret = await state.s3Loader.getObject('helix-content-bus', key);
if (ret.status === 200) {
let json;
try {
json = JSON.parse(ret.body);
} catch (e) {
throw new PipelineStatusError(500, `failed parsing of ${metadataPath}: ${e.message}`);
}

const { data } = json.default ?? json;
if (!Array.isArray(data)) {
throw new PipelineStatusError(500, `failed loading of ${metadataPath}: data must be an array`);
}

state.mappedMetadata = Modifiers.fromModifierSheet(
data,
);
return;
}
if (ret.status !== 404) {
throw new PipelineStatusError(502, `failed to load ${metadataPath}: ${ret.status}`);
}
}
1 change: 1 addition & 0 deletions src/steps/folder-mapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default function folderMapping(state) {
state.info.resourcePath = mapped;
state.log.info(`mapped ${path} to ${state.info.resourcePath} (code-bus)`);
} else {
state.mapped = true;
state.log.info(`mapped ${path} to ${state.info.path} (content-bus)`);
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/steps/set-x-surrogate-key-header.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,15 @@ export default async function setXSurrogateKeyHeader(state, req, res) {
} else if (path.endsWith('.plain.html')) {
path = path.substring(0, path.length - '.plain.html'.length);
}
keys.push(await computeSurrogateKey(`${contentBusId}${path}`));
const hash = await computeSurrogateKey(`${contentBusId}${path}`);
keys.push(hash);

keys.push(`${contentBusId}_metadata`);
keys.push(`${ref}--${repo}--${owner}_head`);

if (state.mapped) {
keys.push(`${hash}_metadata`);
}

res.headers.set('x-surrogate-key', keys.join(' '));
}
4 changes: 2 additions & 2 deletions test/steps/fetch-config-all.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ describe('Fetch Metadata fallback', () => {
headers: new Map(),
}),
});
await assert.rejects(promise, new PipelineStatusError(400, 'failed parsing of /metadata.json: Unexpected token h in JSON at position 1'));
await assert.rejects(promise, new PipelineStatusError(500, 'failed parsing of /metadata.json: Unexpected token h in JSON at position 1'));
});

it('throws error on metadata with no data array', async () => {
Expand All @@ -225,7 +225,7 @@ describe('Fetch Metadata fallback', () => {
headers: new Map(),
}),
});
await assert.rejects(promise, new PipelineStatusError(400, 'failed loading of /metadata.json: data must be an array'));
await assert.rejects(promise, new PipelineStatusError(500, 'failed loading of /metadata.json: data must be an array'));
});

it('throws error on generic error', async () => {
Expand Down
75 changes: 75 additions & 0 deletions test/steps/fetch-mapped-metadata.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2018 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
/* eslint-env mocha */
import assert from 'assert';
import { PipelineStatusError } from '../../src/index.js';
import { StaticS3Loader } from '../StaticS3Loader.js';
import fetchMappedMetadata from '../../src/steps/fetch-mapped-metadata.js';

describe('Fetch Mapped Metadata', () => {
it('throws error on invalid json', async () => {
const promise = fetchMappedMetadata({
log: console,
contentBusId: 'foo-id',
partition: 'live',
mapped: true,
info: {
path: '/mapped',
},
s3Loader: new StaticS3Loader()
.reply('helix-content-bus', 'foo-id/live/mapped/metadata.json', {
status: 200,
body: 'this is no json!',
headers: new Map(),
}),
});
await assert.rejects(promise, new PipelineStatusError(500, 'failed parsing of /mapped/metadata.json: Unexpected token h in JSON at position 1'));
});

it('throws error on metadata with no data array', async () => {
const promise = fetchMappedMetadata({
log: console,
contentBusId: 'foo-id',
partition: 'live',
mapped: true,
info: {
path: '/mapped',
},
s3Loader: new StaticS3Loader()
.reply('helix-content-bus', 'foo-id/live/mapped/metadata.json', {
status: 200,
body: '{}',
headers: new Map(),
}),
});
await assert.rejects(promise, new PipelineStatusError(500, 'failed loading of /mapped/metadata.json: data must be an array'));
});

it('throws error on generic error', async () => {
const promise = fetchMappedMetadata({
log: console,
contentBusId: 'foo-id',
partition: 'live',
mapped: true,
info: {
path: '/mapped',
},
s3Loader: new StaticS3Loader()
.reply('helix-content-bus', 'foo-id/live/mapped/metadata.json', {
status: 500,
body: '',
headers: new Map(),
}),
});
await assert.rejects(promise, new PipelineStatusError(502, 'failed to load /mapped/metadata.json: 500'));
});
});

0 comments on commit a1fc7b0

Please sign in to comment.