Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion source/utilities/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,42 @@ import { logger } from './logger.js';
import type { ErrorObject } from 'ajv';
import type { Configuration, Options, NodeError } from '../types.js';

const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === 'object' && value !== null;

const schemaWithNullableHeaderValue = (() => {
const staticSchema = JSON.parse(JSON.stringify(schema)) as Record<
string,
unknown
>;
const properties = staticSchema.properties;
if (!isRecord(properties)) return staticSchema;

const headers = properties.headers;
if (!isRecord(headers)) return staticSchema;

const headerItems = headers.items;
if (!isRecord(headerItems)) return staticSchema;

const headerProperties = headerItems.properties;
if (!isRecord(headerProperties)) return staticSchema;

const headerEntries = headerProperties.headers;
if (!isRecord(headerEntries)) return staticSchema;

const headerEntryItems = headerEntries.items;
if (!isRecord(headerEntryItems)) return staticSchema;

const headerEntryProperties = headerEntryItems.properties;
if (!isRecord(headerEntryProperties)) return staticSchema;

const headerValue = headerEntryProperties.value;
if (!isRecord(headerValue)) return staticSchema;

headerValue.type = ['string', 'null'];
return staticSchema;
})();

/**
* Parses and returns a configuration object from the designated locations.
*
Expand Down Expand Up @@ -122,7 +158,7 @@ export const loadConfiguration = async (
// If the configuration isn't empty, validate it against the AJV schema.
if (Object.keys(config).length !== 0) {
const ajv = new Ajv({ allowUnionTypes: true });
const validate = ajv.compile(schema as object);
const validate = ajv.compile(schemaWithNullableHeaderValue as object);

if (!validate(config) && validate.errors) {
const defaultMessage = 'The configuration you provided is invalid:';
Expand Down
23 changes: 23 additions & 0 deletions tests/__fixtures__/config/header-removal/serve.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"public": "app/",
"headers": [
{
"source": "**",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=86400"
}
]
},
{
"source": "service-worker.js",
"headers": [
{
"key": "Cache-Control",
"value": null
}
]
}
]
}
19 changes: 18 additions & 1 deletion tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Options } from '../source/types.js';
const fixtures = 'tests/__fixtures__/config/';
// A helper function to load the configuration for a certain fixture.
const loadConfig = (
name: 'valid' | 'invalid' | 'non-existent' | 'deprecated',
name: 'valid' | 'invalid' | 'non-existent' | 'deprecated' | 'header-removal',
args?: Partial<Options> = {},
) => loadConfiguration(process.cwd(), `${fixtures}/${name}`, args);

Expand Down Expand Up @@ -65,4 +65,21 @@ describe('utilities/config', () => {
expect.stringContaining('deprecated'),
);
});

// `serve-handler` supports `null` to remove previously defined headers.
// This should pass config validation as well.
test('accept null header values for header removal rules', async () => {
const configuration = await loadConfig('header-removal');

expect(configuration.headers).toEqual([
{
source: '**',
headers: [{ key: 'Cache-Control', value: 'max-age=86400' }],
},
{
source: 'service-worker.js',
headers: [{ key: 'Cache-Control', value: null }],
},
]);
});
});