mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ContentManagement] Use natural number for version (#153239)
This commit is contained in:
parent
15b1dd2e45
commit
92a6a1a7ea
36 changed files with 219 additions and 247 deletions
|
@ -22,7 +22,7 @@ export default {
|
|||
const todosClient = new TodosClient();
|
||||
|
||||
const contentTypeRegistry = new ContentTypeRegistry();
|
||||
contentTypeRegistry.register({ id: 'todos', version: { latest: 'v1' } });
|
||||
contentTypeRegistry.register({ id: 'todos', version: { latest: 1 } });
|
||||
|
||||
const contentClient = new ContentClient((contentType: string) => {
|
||||
switch (contentType) {
|
||||
|
|
|
@ -34,7 +34,7 @@ export class ContentManagementExamplesPlugin
|
|||
contentManagement.registry.register({
|
||||
id: 'todos',
|
||||
version: {
|
||||
latest: 'v1',
|
||||
latest: 1,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ export const registerTodoContentType = ({
|
|||
id: TODO_CONTENT_ID,
|
||||
storage: new TodosStorage(),
|
||||
version: {
|
||||
latest: 'v1',
|
||||
latest: 1,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
export { initTransform } from './object_transform';
|
||||
|
||||
export {
|
||||
getTransforms as getContentManagmentServicesTransforms,
|
||||
compile as compileServiceDefinitions,
|
||||
|
|
|
@ -12,6 +12,8 @@ describe('utils', () => {
|
|||
describe('validateVersion()', () => {
|
||||
[
|
||||
{ input: '123', isValid: true, expected: 123 },
|
||||
{ input: '1111111111111111111111111', isValid: true, expected: 1111111111111111111111111 },
|
||||
{ input: '111111111111.1111111111111', isValid: false, expected: null },
|
||||
{ input: 123, isValid: true, expected: 123 },
|
||||
{ input: 1.23, isValid: false, expected: null },
|
||||
{ input: '123a', isValid: false, expected: null },
|
||||
|
|
|
@ -29,7 +29,9 @@ export const validateObj = (obj: unknown, objSchema?: Type<any>): ValidationErro
|
|||
}
|
||||
};
|
||||
|
||||
export const validateVersion = (version: unknown): { result: boolean; value: Version | null } => {
|
||||
export const validateVersion = (
|
||||
version: unknown
|
||||
): { result: true; value: Version } | { result: false; value: null } => {
|
||||
if (typeof version === 'string') {
|
||||
const isValid = /^\d+$/.test(version);
|
||||
if (isValid) {
|
||||
|
@ -42,9 +44,15 @@ export const validateVersion = (version: unknown): { result: boolean; value: Ver
|
|||
return { result: false, value: null };
|
||||
} else {
|
||||
const isValid = Number.isInteger(version);
|
||||
if (isValid) {
|
||||
return {
|
||||
result: true,
|
||||
value: version as Version,
|
||||
};
|
||||
}
|
||||
return {
|
||||
result: isValid,
|
||||
value: isValid ? (version as Version) : null,
|
||||
result: false,
|
||||
value: null,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,5 +18,3 @@ export type {
|
|||
DeleteIn,
|
||||
SearchIn,
|
||||
} from './rpc';
|
||||
|
||||
export type { Version } from './types';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { Version } from '../types';
|
||||
import type { Version } from '@kbn/object-versioning';
|
||||
import { versionSchema } from './constants';
|
||||
|
||||
import type { ProcedureSchemas } from './types';
|
||||
|
|
|
@ -6,19 +6,17 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { validateVersion } from '../utils';
|
||||
import { validateVersion } from '@kbn/object-versioning/lib/utils';
|
||||
|
||||
export const procedureNames = ['get', 'bulkGet', 'create', 'update', 'delete', 'search'] as const;
|
||||
|
||||
export type ProcedureName = typeof procedureNames[number];
|
||||
|
||||
export const versionSchema = schema.string({
|
||||
export const versionSchema = schema.number({
|
||||
validate: (value) => {
|
||||
try {
|
||||
validateVersion(value);
|
||||
} catch (e) {
|
||||
return 'must follow the pattern [v${number}]';
|
||||
const { result } = validateVersion(value);
|
||||
if (!result) {
|
||||
return 'must be an integer';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { Version } from '../types';
|
||||
import type { Version } from '@kbn/object-versioning';
|
||||
import { versionSchema } from './constants';
|
||||
|
||||
import type { ProcedureSchemas } from './types';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { Version } from '../types';
|
||||
import type { Version } from '@kbn/object-versioning';
|
||||
import { versionSchema } from './constants';
|
||||
|
||||
import type { ProcedureSchemas } from './types';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { Version } from '../types';
|
||||
import type { Version } from '@kbn/object-versioning';
|
||||
import { versionSchema } from './constants';
|
||||
|
||||
import type { ProcedureSchemas } from './types';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { Version } from '../types';
|
||||
import type { Version } from '@kbn/object-versioning';
|
||||
import { versionSchema } from './constants';
|
||||
|
||||
import type { ProcedureSchemas } from './types';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { Version } from '../types';
|
||||
import type { Version } from '@kbn/object-versioning';
|
||||
import { versionSchema } from './constants';
|
||||
|
||||
import type { ProcedureSchemas } from './types';
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type Version = `v${number}`;
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { validateVersion } from './utils';
|
||||
|
||||
describe('utils', () => {
|
||||
describe('validateVersion', () => {
|
||||
const isValid = (version: unknown): boolean => {
|
||||
try {
|
||||
validateVersion(version);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
[
|
||||
{
|
||||
version: 'v1',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
version: 'v689584563',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
version: 'v0', // Invalid: must be >= 1
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
version: 'av0',
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
version: '1',
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
version: 'vv1',
|
||||
expected: false,
|
||||
},
|
||||
].forEach(({ version, expected }) => {
|
||||
test(`should validate [${version}] version`, () => {
|
||||
expect(isValid(version)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
test('should return the version number', () => {
|
||||
expect(validateVersion('v7')).toBe(7);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/** Utility to validate that a content version follows the pattern `v${number}` */
|
||||
export const validateVersion = (version: unknown): number => {
|
||||
if (typeof version !== 'string') {
|
||||
throw new Error(`Invalid version [${version}]. Must follow the pattern [v$\{number\}]`);
|
||||
}
|
||||
|
||||
if (/^v\d+$/.test(version) === false) {
|
||||
throw new Error(`Invalid version [${version}]. Must follow the pattern [v$\{number\}]`);
|
||||
}
|
||||
|
||||
const versionNumber = parseInt(version.substring(1), 10);
|
||||
|
||||
if (versionNumber < 1) {
|
||||
throw new Error(`Version must be >= 1`);
|
||||
}
|
||||
|
||||
return versionNumber;
|
||||
};
|
|
@ -18,7 +18,7 @@ const setup = () => {
|
|||
const contentTypeRegistry = new ContentTypeRegistry();
|
||||
contentTypeRegistry.register({
|
||||
id: 'testType',
|
||||
version: { latest: 'v3' },
|
||||
version: { latest: 3 },
|
||||
});
|
||||
const contentClient = new ContentClient(() => crudClient, contentTypeRegistry);
|
||||
return { crudClient, contentClient, contentTypeRegistry };
|
||||
|
@ -31,27 +31,28 @@ describe('#get', () => {
|
|||
const output = { test: 'test' };
|
||||
crudClient.get.mockResolvedValueOnce(output);
|
||||
expect(await contentClient.get(input)).toEqual(output);
|
||||
expect(crudClient.get).toBeCalledWith({ ...input, version: 'v3' }); // latest version added
|
||||
expect(crudClient.get).toBeCalledWith({ ...input, version: 3 }); // latest version added
|
||||
});
|
||||
|
||||
it('does not add the latest version if one is passed', async () => {
|
||||
const { crudClient, contentClient } = setup();
|
||||
const input: GetIn = { id: 'test', contentTypeId: 'testType', version: 'v1' };
|
||||
const input: GetIn = { id: 'test', contentTypeId: 'testType', version: 1 };
|
||||
await contentClient.get(input);
|
||||
expect(crudClient.get).toBeCalledWith(input);
|
||||
});
|
||||
|
||||
it('throws if version is not valid', async () => {
|
||||
const { contentClient } = setup();
|
||||
let input = { id: 'test', contentTypeId: 'testType', version: 'vv' }; // Invalid format
|
||||
let input = { id: 'test', contentTypeId: 'testType', version: 'foo' }; // Invalid format
|
||||
await expect(async () => {
|
||||
contentClient.get(input as any);
|
||||
}).rejects.toThrowError('Invalid version [vv]. Must follow the pattern [v${number}]');
|
||||
}).rejects.toThrowError('Invalid version [foo]. Must be an integer.');
|
||||
|
||||
input = { id: 'test', contentTypeId: 'testType', version: 'v4' }; // Latest version is v3
|
||||
// @ts-expect-error
|
||||
input = { id: 'test', contentTypeId: 'testType', version: 4 }; // Latest version is 3
|
||||
await expect(async () => {
|
||||
contentClient.get(input as any);
|
||||
}).rejects.toThrowError('Invalid version [v4]. Latest version is [v3]');
|
||||
}).rejects.toThrowError('Invalid version [4]. Latest version is [3]');
|
||||
});
|
||||
|
||||
it('calls rpcClient.get$ with input and returns output', async () => {
|
||||
|
@ -84,12 +85,12 @@ describe('#create', () => {
|
|||
crudClient.create.mockResolvedValueOnce(output);
|
||||
|
||||
expect(await contentClient.create(input)).toEqual(output);
|
||||
expect(crudClient.create).toBeCalledWith({ ...input, version: 'v3' }); // latest version added
|
||||
expect(crudClient.create).toBeCalledWith({ ...input, version: 3 }); // latest version added
|
||||
});
|
||||
|
||||
it('does not add the latest version if one is passed', async () => {
|
||||
const { crudClient, contentClient } = setup();
|
||||
const input: CreateIn = { contentTypeId: 'testType', data: { foo: 'bar' }, version: 'v1' };
|
||||
const input: CreateIn = { contentTypeId: 'testType', data: { foo: 'bar' }, version: 1 };
|
||||
await contentClient.create(input);
|
||||
expect(crudClient.create).toBeCalledWith(input);
|
||||
});
|
||||
|
@ -107,7 +108,7 @@ describe('#update', () => {
|
|||
crudClient.update.mockResolvedValueOnce(output);
|
||||
|
||||
expect(await contentClient.update(input)).toEqual(output);
|
||||
expect(crudClient.update).toBeCalledWith({ ...input, version: 'v3' }); // latest version added
|
||||
expect(crudClient.update).toBeCalledWith({ ...input, version: 3 }); // latest version added
|
||||
});
|
||||
|
||||
it('does not add the latest version if one is passed', async () => {
|
||||
|
@ -117,7 +118,7 @@ describe('#update', () => {
|
|||
contentTypeId: 'testType',
|
||||
id: 'test',
|
||||
data: { foo: 'bar' },
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
};
|
||||
await contentClient.update(input);
|
||||
expect(crudClient.update).toBeCalledWith(input);
|
||||
|
@ -132,12 +133,12 @@ describe('#delete', () => {
|
|||
crudClient.delete.mockResolvedValueOnce(output);
|
||||
|
||||
expect(await contentClient.delete(input)).toEqual(output);
|
||||
expect(crudClient.delete).toBeCalledWith({ ...input, version: 'v3' }); // latest version added
|
||||
expect(crudClient.delete).toBeCalledWith({ ...input, version: 3 }); // latest version added
|
||||
});
|
||||
|
||||
it('does not add the latest version if one is passed', async () => {
|
||||
const { crudClient, contentClient } = setup();
|
||||
const input: DeleteIn = { contentTypeId: 'testType', id: 'test', version: 'v1' };
|
||||
const input: DeleteIn = { contentTypeId: 'testType', id: 'test', version: 1 };
|
||||
await contentClient.delete(input);
|
||||
expect(crudClient.delete).toBeCalledWith(input);
|
||||
});
|
||||
|
@ -150,12 +151,12 @@ describe('#search', () => {
|
|||
const output = { hits: [{ id: 'test' }] };
|
||||
crudClient.search.mockResolvedValueOnce(output);
|
||||
expect(await contentClient.search(input)).toEqual(output);
|
||||
expect(crudClient.search).toBeCalledWith({ ...input, version: 'v3' }); // latest version added
|
||||
expect(crudClient.search).toBeCalledWith({ ...input, version: 3 }); // latest version added
|
||||
});
|
||||
|
||||
it('does not add the latest version if one is passed', async () => {
|
||||
const { crudClient, contentClient } = setup();
|
||||
const input: SearchIn = { contentTypeId: 'testType', query: {}, version: 'v1' };
|
||||
const input: SearchIn = { contentTypeId: 'testType', query: {}, version: 1 };
|
||||
await contentClient.search(input);
|
||||
expect(crudClient.search).toBeCalledWith(input);
|
||||
});
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
*/
|
||||
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { validateVersion } from '@kbn/object-versioning/lib/utils';
|
||||
import type { Version } from '@kbn/object-versioning';
|
||||
import { createQueryObservable } from './query_observable';
|
||||
import type { CrudClient } from '../crud_client';
|
||||
import type { CreateIn, GetIn, UpdateIn, DeleteIn, SearchIn, Version } from '../../common';
|
||||
import { validateVersion } from '../../common/utils';
|
||||
import type { CreateIn, GetIn, UpdateIn, DeleteIn, SearchIn } from '../../common';
|
||||
import type { ContentTypeRegistry } from '../registry';
|
||||
|
||||
export const queryKeyBuilder = {
|
||||
|
@ -35,9 +36,13 @@ const addVersion = <I extends { contentTypeId: string; version?: Version }>(
|
|||
|
||||
const version = input.version ?? contentType.version.latest;
|
||||
|
||||
const versionNumber = validateVersion(version);
|
||||
const { result, value } = validateVersion(version);
|
||||
|
||||
if (versionNumber > parseInt(contentType.version.latest.substring(1), 10)) {
|
||||
if (!result) {
|
||||
throw new Error(`Invalid version [${version}]. Must be an integer.`);
|
||||
}
|
||||
|
||||
if (value > contentType.version.latest) {
|
||||
throw new Error(
|
||||
`Invalid version [${version}]. Latest version is [${contentType.version.latest}]`
|
||||
);
|
||||
|
|
|
@ -24,7 +24,7 @@ const setup = () => {
|
|||
const contentTypeRegistry = new ContentTypeRegistry();
|
||||
contentTypeRegistry.register({
|
||||
id: 'testType',
|
||||
version: { latest: 'v3' },
|
||||
version: { latest: 3 },
|
||||
});
|
||||
const contentClient = new ContentClient(() => crudClient, contentTypeRegistry);
|
||||
|
||||
|
@ -42,7 +42,7 @@ const setup = () => {
|
|||
describe('useCreateContentMutation', () => {
|
||||
test('should call rpcClient.create with input and resolve with output', async () => {
|
||||
const { Wrapper, crudClient } = setup();
|
||||
const input: CreateIn = { contentTypeId: 'testType', data: { foo: 'bar' }, version: 'v2' };
|
||||
const input: CreateIn = { contentTypeId: 'testType', data: { foo: 'bar' }, version: 2 };
|
||||
const output = { test: 'test' };
|
||||
crudClient.create.mockResolvedValueOnce(output);
|
||||
const { result, waitFor } = renderHook(() => useCreateContentMutation(), { wrapper: Wrapper });
|
||||
|
@ -61,7 +61,7 @@ describe('useUpdateContentMutation', () => {
|
|||
contentTypeId: 'testType',
|
||||
id: 'test',
|
||||
data: { foo: 'bar' },
|
||||
version: 'v2',
|
||||
version: 2,
|
||||
};
|
||||
const output = { test: 'test' };
|
||||
crudClient.update.mockResolvedValueOnce(output);
|
||||
|
@ -77,7 +77,7 @@ describe('useUpdateContentMutation', () => {
|
|||
describe('useDeleteContentMutation', () => {
|
||||
test('should call rpcClient.delete with input and resolve with output', async () => {
|
||||
const { Wrapper, crudClient } = setup();
|
||||
const input: DeleteIn = { contentTypeId: 'testType', id: 'test', version: 'v2' };
|
||||
const input: DeleteIn = { contentTypeId: 'testType', id: 'test', version: 2 };
|
||||
const output = { test: 'test' };
|
||||
crudClient.delete.mockResolvedValueOnce(output);
|
||||
const { result, waitFor } = renderHook(() => useDeleteContentMutation(), { wrapper: Wrapper });
|
||||
|
|
|
@ -20,7 +20,7 @@ const setup = () => {
|
|||
const contentTypeRegistry = new ContentTypeRegistry();
|
||||
contentTypeRegistry.register({
|
||||
id: 'testType',
|
||||
version: { latest: 'v2' },
|
||||
version: { latest: 2 },
|
||||
});
|
||||
const contentClient = new ContentClient(() => crudClient, contentTypeRegistry);
|
||||
|
||||
|
@ -38,7 +38,7 @@ const setup = () => {
|
|||
describe('useGetContentQuery', () => {
|
||||
test('should call rpcClient.get with input and resolve with output', async () => {
|
||||
const { crudClient, Wrapper } = setup();
|
||||
const input: GetIn = { id: 'test', contentTypeId: 'testType', version: 'v2' };
|
||||
const input: GetIn = { id: 'test', contentTypeId: 'testType', version: 2 };
|
||||
const output = { test: 'test' };
|
||||
crudClient.get.mockResolvedValueOnce(output);
|
||||
const { result, waitFor } = renderHook(() => useGetContentQuery(input), { wrapper: Wrapper });
|
||||
|
@ -50,7 +50,7 @@ describe('useGetContentQuery', () => {
|
|||
describe('useSearchContentQuery', () => {
|
||||
test('should call rpcClient.search with input and resolve with output', async () => {
|
||||
const { crudClient, Wrapper } = setup();
|
||||
const input: SearchIn = { contentTypeId: 'testType', query: {}, version: 'v2' };
|
||||
const input: SearchIn = { contentTypeId: 'testType', query: {}, version: 2 };
|
||||
const output = { hits: [{ id: 'test' }] };
|
||||
crudClient.search.mockResolvedValueOnce(output);
|
||||
const { result, waitFor } = renderHook(() => useSearchContentQuery(input), {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { ContentType } from './content_type';
|
|||
import type { ContentTypeDefinition } from './content_type_definition';
|
||||
|
||||
test('create a content type with just an id', () => {
|
||||
const type = new ContentType({ id: 'test', version: { latest: 'v1' } });
|
||||
const type = new ContentType({ id: 'test', version: { latest: 1 } });
|
||||
|
||||
expect(type.id).toBe('test');
|
||||
expect(type.name).toBe('test');
|
||||
|
@ -24,7 +24,7 @@ test('create a content type with all the full definition', () => {
|
|||
name: 'Test',
|
||||
icon: 'test',
|
||||
description: 'Test description',
|
||||
version: { latest: 'v1' },
|
||||
version: { latest: 1 },
|
||||
};
|
||||
const type = new ContentType(definition);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { Version } from '../../common';
|
||||
import type { Version } from '@kbn/object-versioning';
|
||||
import type { CrudClient } from '../crud_client';
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,7 +15,7 @@ beforeEach(() => {
|
|||
});
|
||||
|
||||
const versionInfo = {
|
||||
latest: 'v2',
|
||||
latest: 2,
|
||||
} as const;
|
||||
|
||||
test('registering a content type', () => {
|
||||
|
@ -45,12 +45,21 @@ test('registering already registered content type throws', () => {
|
|||
).toThrowErrorMatchingInlineSnapshot(`"Content type with id \\"test\\" already registered."`);
|
||||
});
|
||||
|
||||
test('registering string number version converts it to number', () => {
|
||||
registry.register({
|
||||
id: 'test',
|
||||
version: { latest: '123' },
|
||||
} as any);
|
||||
|
||||
expect(registry.get('test')?.version).toEqual({ latest: 123 });
|
||||
});
|
||||
|
||||
test('registering without version throws', () => {
|
||||
expect(() => {
|
||||
registry.register({
|
||||
id: 'test',
|
||||
} as any);
|
||||
}).toThrowError('Invalid version [undefined]. Must follow the pattern [v${number}]');
|
||||
}).toThrowError('Invalid version [undefined]. Must be an integer.');
|
||||
});
|
||||
|
||||
test('registering invalid version throws', () => {
|
||||
|
@ -61,13 +70,13 @@ test('registering invalid version throws', () => {
|
|||
latest: 'bad',
|
||||
},
|
||||
} as any);
|
||||
}).toThrowError('Invalid version [bad]. Must follow the pattern [v${number}]');
|
||||
}).toThrowError('Invalid version [bad]. Must be an integer.');
|
||||
|
||||
expect(() => {
|
||||
registry.register({
|
||||
id: 'test',
|
||||
version: {
|
||||
latest: 'v0',
|
||||
latest: 0,
|
||||
},
|
||||
});
|
||||
}).toThrowError('Version must be >= 1');
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { validateVersion } from '@kbn/object-versioning/lib/utils';
|
||||
|
||||
import type { ContentTypeDefinition } from './content_type_definition';
|
||||
import { ContentType } from './content_type';
|
||||
import { validateVersion } from '../../common/utils';
|
||||
|
||||
export class ContentTypeRegistry {
|
||||
private readonly types: Map<string, ContentType> = new Map();
|
||||
|
@ -18,9 +19,19 @@ export class ContentTypeRegistry {
|
|||
throw new Error(`Content type with id "${definition.id}" already registered.`);
|
||||
}
|
||||
|
||||
validateVersion(definition.version?.latest);
|
||||
const { result, value } = validateVersion(definition.version?.latest);
|
||||
if (!result) {
|
||||
throw new Error(`Invalid version [${definition.version?.latest}]. Must be an integer.`);
|
||||
}
|
||||
|
||||
const type = new ContentType(definition);
|
||||
if (value < 1) {
|
||||
throw new Error(`Version must be >= 1`);
|
||||
}
|
||||
|
||||
const type = new ContentType({
|
||||
...definition,
|
||||
version: { ...definition.version, latest: value },
|
||||
});
|
||||
this.types.set(type.id, type);
|
||||
|
||||
return type;
|
||||
|
|
|
@ -36,4 +36,8 @@ export class ContentType {
|
|||
public get crud() {
|
||||
return this.contentCrud;
|
||||
}
|
||||
|
||||
public get version() {
|
||||
return this._definition.version;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,8 +40,8 @@ const setup = ({ registerFooType = false }: { registerFooType?: boolean } = {})
|
|||
const ctx: StorageContext = {
|
||||
requestHandlerContext: {} as any,
|
||||
version: {
|
||||
latest: 'v1',
|
||||
request: 'v1',
|
||||
latest: 1,
|
||||
request: 1,
|
||||
},
|
||||
utils: {
|
||||
getTransforms: jest.fn(),
|
||||
|
@ -54,7 +54,7 @@ const setup = ({ registerFooType = false }: { registerFooType?: boolean } = {})
|
|||
id: FOO_CONTENT_ID,
|
||||
storage: createMemoryStorage(),
|
||||
version: {
|
||||
latest: 'v2',
|
||||
latest: 2,
|
||||
},
|
||||
};
|
||||
const cleanUp = () => {
|
||||
|
@ -107,7 +107,24 @@ describe('Content Core', () => {
|
|||
// Make sure the "register" exposed by the api is indeed registring
|
||||
// the content into our "contentRegistry" instance
|
||||
expect(contentRegistry.isContentRegistered(FOO_CONTENT_ID)).toBe(true);
|
||||
expect(contentRegistry.getDefinition(FOO_CONTENT_ID)).toBe(contentDefinition);
|
||||
expect(contentRegistry.getDefinition(FOO_CONTENT_ID)).toEqual(contentDefinition);
|
||||
|
||||
cleanUp();
|
||||
});
|
||||
|
||||
test('should convert the latest version to number if string is passed', () => {
|
||||
const { coreSetup, cleanUp, contentDefinition } = setup();
|
||||
|
||||
const {
|
||||
contentRegistry,
|
||||
api: { register },
|
||||
} = coreSetup;
|
||||
|
||||
register({ ...contentDefinition, version: { latest: '123' } } as any);
|
||||
|
||||
expect(contentRegistry.getContentType(contentDefinition.id).version).toEqual({
|
||||
latest: 123,
|
||||
});
|
||||
|
||||
cleanUp();
|
||||
});
|
||||
|
@ -124,10 +141,10 @@ describe('Content Core', () => {
|
|||
|
||||
expect(() => {
|
||||
register({ ...contentDefinition, version: undefined } as any);
|
||||
}).toThrowError('Invalid version [undefined]. Must follow the pattern [v${number}]');
|
||||
}).toThrowError('Invalid version [undefined]. Must be an integer.');
|
||||
|
||||
expect(() => {
|
||||
register({ ...contentDefinition, version: { latest: 'v0' } });
|
||||
register({ ...contentDefinition, version: { latest: 0 } });
|
||||
}).toThrowError('Version must be >= 1');
|
||||
|
||||
cleanUp();
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { validateVersion } from '../../common/utils';
|
||||
import { validateVersion } from '@kbn/object-versioning/lib/utils';
|
||||
import { ContentType } from './content_type';
|
||||
import { EventBus } from './event_bus';
|
||||
import type { ContentStorage, ContentTypeDefinition } from './types';
|
||||
|
@ -27,9 +27,19 @@ export class ContentRegistry {
|
|||
throw new Error(`Content [${definition.id}] is already registered`);
|
||||
}
|
||||
|
||||
validateVersion(definition.version?.latest);
|
||||
const { result, value } = validateVersion(definition.version?.latest);
|
||||
if (!result) {
|
||||
throw new Error(`Invalid version [${definition.version?.latest}]. Must be an integer.`);
|
||||
}
|
||||
|
||||
const contentType = new ContentType(definition, this.eventBus);
|
||||
if (value < 1) {
|
||||
throw new Error(`Version must be >= 1`);
|
||||
}
|
||||
|
||||
const contentType = new ContentType(
|
||||
{ ...definition, version: { ...definition.version, latest: value } },
|
||||
this.eventBus
|
||||
);
|
||||
|
||||
this.types.set(contentType.id, contentType);
|
||||
}
|
||||
|
|
|
@ -7,15 +7,14 @@
|
|||
*/
|
||||
|
||||
import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server';
|
||||
import type { ContentManagementGetTransformsFn } from '@kbn/object-versioning';
|
||||
import type { Version as LegacyVersion } from '../../common';
|
||||
import type { ContentManagementGetTransformsFn, Version } from '@kbn/object-versioning';
|
||||
|
||||
/** Context that is sent to all storage instance methods */
|
||||
export interface StorageContext {
|
||||
requestHandlerContext: RequestHandlerContext;
|
||||
version: {
|
||||
request: LegacyVersion;
|
||||
latest: LegacyVersion;
|
||||
request: Version;
|
||||
latest: Version;
|
||||
};
|
||||
utils: {
|
||||
getTransforms: ContentManagementGetTransformsFn;
|
||||
|
@ -48,6 +47,6 @@ export interface ContentTypeDefinition<S extends ContentStorage = ContentStorage
|
|||
/** The storage layer for the content. It must implment the ContentStorage interface. */
|
||||
storage: S;
|
||||
version: {
|
||||
latest: LegacyVersion;
|
||||
latest: Version;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ const FOO_CONTENT_ID = 'foo';
|
|||
describe('RPC -> bulkGet()', () => {
|
||||
describe('Input/Output validation', () => {
|
||||
const ids = ['123', '456'];
|
||||
const validInput = { contentTypeId: 'foo', ids, version: 'v1' };
|
||||
const validInput = { contentTypeId: 'foo', ids, version: 1 };
|
||||
|
||||
/**
|
||||
* These tests are for the procedure call itself. Every RPC needs to declare in/out schema
|
||||
|
@ -50,11 +50,10 @@ describe('RPC -> bulkGet()', () => {
|
|||
},
|
||||
{
|
||||
input: omit(validInput, 'version'),
|
||||
expectedError: '[version]: expected value of type [string] but got [undefined]',
|
||||
expectedError: '[version]: expected value of type [number] but got [undefined]',
|
||||
},
|
||||
{
|
||||
input: { ...validInput, version: '1' }, // invalid version format
|
||||
expectedError: '[version]: must follow the pattern [v${number}]',
|
||||
input: { ...validInput, version: '1' }, // string number is OK
|
||||
},
|
||||
{
|
||||
input: omit(validInput, 'ids'),
|
||||
|
@ -96,7 +95,7 @@ describe('RPC -> bulkGet()', () => {
|
|||
{
|
||||
contentTypeId: 'foo',
|
||||
ids: ['123'],
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
options: { any: 'object' },
|
||||
},
|
||||
inputSchema
|
||||
|
@ -107,7 +106,7 @@ describe('RPC -> bulkGet()', () => {
|
|||
error = validate(
|
||||
{
|
||||
contentTypeId: 'foo',
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
ids: ['123'],
|
||||
options: 123, // Not an object
|
||||
},
|
||||
|
@ -157,7 +156,7 @@ describe('RPC -> bulkGet()', () => {
|
|||
id: FOO_CONTENT_ID,
|
||||
storage,
|
||||
version: {
|
||||
latest: 'v2',
|
||||
latest: 2,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -179,7 +178,7 @@ describe('RPC -> bulkGet()', () => {
|
|||
|
||||
const result = await fn(ctx, {
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
ids: ['123', '456'],
|
||||
});
|
||||
|
||||
|
@ -192,8 +191,8 @@ describe('RPC -> bulkGet()', () => {
|
|||
{
|
||||
requestHandlerContext: ctx.requestHandlerContext,
|
||||
version: {
|
||||
request: 'v1',
|
||||
latest: 'v2', // from the registry
|
||||
request: 1,
|
||||
latest: 2, // from the registry
|
||||
},
|
||||
utils: {
|
||||
getTransforms: expect.any(Function),
|
||||
|
@ -218,16 +217,16 @@ describe('RPC -> bulkGet()', () => {
|
|||
fn(ctx, {
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
ids: ['123', '456'],
|
||||
version: 'v7',
|
||||
version: 7,
|
||||
})
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [v2].'));
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [2].'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('object versioning', () => {
|
||||
test('should expose a utility to transform and validate services objects', () => {
|
||||
const { ctx, storage } = setup();
|
||||
fn(ctx, { contentTypeId: FOO_CONTENT_ID, ids: ['1234'], version: 'v1' });
|
||||
fn(ctx, { contentTypeId: FOO_CONTENT_ID, ids: ['1234'], version: 1 });
|
||||
const [[storageContext]] = storage.bulkGet.mock.calls;
|
||||
|
||||
// getTransforms() utils should be available from context
|
||||
|
|
|
@ -34,7 +34,7 @@ const FOO_CONTENT_ID = 'foo';
|
|||
|
||||
describe('RPC -> create()', () => {
|
||||
describe('Input/Output validation', () => {
|
||||
const validInput = { contentTypeId: 'foo', version: 'v1', data: { title: 'hello' } };
|
||||
const validInput = { contentTypeId: 'foo', version: 1, data: { title: 'hello' } };
|
||||
|
||||
test('should validate the input', () => {
|
||||
[
|
||||
|
@ -45,11 +45,10 @@ describe('RPC -> create()', () => {
|
|||
},
|
||||
{
|
||||
input: omit(validInput, 'version'),
|
||||
expectedError: '[version]: expected value of type [string] but got [undefined]',
|
||||
expectedError: '[version]: expected value of type [number] but got [undefined]',
|
||||
},
|
||||
{
|
||||
input: { ...validInput, version: '1' }, // invalid version format
|
||||
expectedError: '[version]: must follow the pattern [v${number}]',
|
||||
input: { ...validInput, version: '1' }, // string number is OK
|
||||
},
|
||||
{
|
||||
input: omit(validInput, 'data'),
|
||||
|
@ -83,7 +82,7 @@ describe('RPC -> create()', () => {
|
|||
{
|
||||
contentTypeId: 'foo',
|
||||
data: { title: 'hello' },
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
options: { any: 'object' },
|
||||
},
|
||||
inputSchema
|
||||
|
@ -95,7 +94,7 @@ describe('RPC -> create()', () => {
|
|||
{
|
||||
contentTypeId: 'foo',
|
||||
data: { title: 'hello' },
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
options: 123, // Not an object
|
||||
},
|
||||
inputSchema
|
||||
|
@ -130,7 +129,7 @@ describe('RPC -> create()', () => {
|
|||
id: FOO_CONTENT_ID,
|
||||
storage,
|
||||
version: {
|
||||
latest: 'v2',
|
||||
latest: 2,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -152,7 +151,7 @@ describe('RPC -> create()', () => {
|
|||
|
||||
const result = await fn(ctx, {
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
data: { title: 'Hello' },
|
||||
});
|
||||
|
||||
|
@ -165,8 +164,8 @@ describe('RPC -> create()', () => {
|
|||
{
|
||||
requestHandlerContext: ctx.requestHandlerContext,
|
||||
version: {
|
||||
request: 'v1',
|
||||
latest: 'v2', // from the registry
|
||||
request: 1,
|
||||
latest: 2, // from the registry
|
||||
},
|
||||
utils: {
|
||||
getTransforms: expect.any(Function),
|
||||
|
@ -191,16 +190,16 @@ describe('RPC -> create()', () => {
|
|||
fn(ctx, {
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
data: { title: 'Hello' },
|
||||
version: 'v7',
|
||||
version: 7,
|
||||
})
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [v2].'));
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [2].'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('object versioning', () => {
|
||||
test('should expose a utility to transform and validate services objects', () => {
|
||||
const { ctx, storage } = setup();
|
||||
fn(ctx, { contentTypeId: FOO_CONTENT_ID, data: { title: 'Hello' }, version: 'v1' });
|
||||
fn(ctx, { contentTypeId: FOO_CONTENT_ID, data: { title: 'Hello' }, version: 1 });
|
||||
const [[storageContext]] = storage.create.mock.calls;
|
||||
|
||||
// getTransforms() utils should be available from context
|
||||
|
|
|
@ -34,7 +34,7 @@ const FOO_CONTENT_ID = 'foo';
|
|||
|
||||
describe('RPC -> delete()', () => {
|
||||
describe('Input/Output validation', () => {
|
||||
const validInput = { contentTypeId: 'foo', id: '123', version: 'v1' };
|
||||
const validInput = { contentTypeId: 'foo', id: '123', version: 1 };
|
||||
|
||||
test('should validate that a contentTypeId and an id is passed', () => {
|
||||
[
|
||||
|
@ -53,11 +53,10 @@ describe('RPC -> delete()', () => {
|
|||
},
|
||||
{
|
||||
input: omit(validInput, 'version'),
|
||||
expectedError: '[version]: expected value of type [string] but got [undefined]',
|
||||
expectedError: '[version]: expected value of type [number] but got [undefined]',
|
||||
},
|
||||
{
|
||||
input: { ...validInput, version: '1' }, // invalid version format
|
||||
expectedError: '[version]: must follow the pattern [v${number}]',
|
||||
input: { ...validInput, version: '1' }, // string number is OK
|
||||
},
|
||||
].forEach(({ input, expectedError }) => {
|
||||
const error = validate(input, inputSchema);
|
||||
|
@ -79,7 +78,7 @@ describe('RPC -> delete()', () => {
|
|||
{
|
||||
contentTypeId: 'foo',
|
||||
id: '123',
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
options: { any: 'object' },
|
||||
},
|
||||
inputSchema
|
||||
|
@ -91,7 +90,7 @@ describe('RPC -> delete()', () => {
|
|||
{
|
||||
contentTypeId: 'foo',
|
||||
id: '123',
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
options: 123, // Not an object
|
||||
},
|
||||
inputSchema
|
||||
|
@ -126,7 +125,7 @@ describe('RPC -> delete()', () => {
|
|||
id: FOO_CONTENT_ID,
|
||||
storage,
|
||||
version: {
|
||||
latest: 'v2',
|
||||
latest: 2,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -146,7 +145,7 @@ describe('RPC -> delete()', () => {
|
|||
const expected = 'DeleteResult';
|
||||
storage.delete.mockResolvedValueOnce(expected);
|
||||
|
||||
const result = await fn(ctx, { contentTypeId: FOO_CONTENT_ID, version: 'v1', id: '1234' });
|
||||
const result = await fn(ctx, { contentTypeId: FOO_CONTENT_ID, version: 1, id: '1234' });
|
||||
|
||||
expect(result).toEqual({
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
|
@ -157,8 +156,8 @@ describe('RPC -> delete()', () => {
|
|||
{
|
||||
requestHandlerContext: ctx.requestHandlerContext,
|
||||
version: {
|
||||
request: 'v1',
|
||||
latest: 'v2', // from the registry
|
||||
request: 1,
|
||||
latest: 2, // from the registry
|
||||
},
|
||||
utils: {
|
||||
getTransforms: expect.any(Function),
|
||||
|
@ -183,16 +182,16 @@ describe('RPC -> delete()', () => {
|
|||
fn(ctx, {
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
id: '1234',
|
||||
version: 'v7',
|
||||
version: 7,
|
||||
})
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [v2].'));
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [2].'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('object versioning', () => {
|
||||
test('should expose a utility to transform and validate services objects', () => {
|
||||
const { ctx, storage } = setup();
|
||||
fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 'v1' });
|
||||
fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 1 });
|
||||
const [[storageContext]] = storage.delete.mock.calls;
|
||||
|
||||
// getTransforms() utils should be available from context
|
||||
|
|
|
@ -34,7 +34,7 @@ const FOO_CONTENT_ID = 'foo';
|
|||
|
||||
describe('RPC -> get()', () => {
|
||||
describe('Input/Output validation', () => {
|
||||
const validInput = { contentTypeId: 'foo', id: '123', version: 'v1' };
|
||||
const validInput = { contentTypeId: 'foo', id: '123', version: 1 };
|
||||
|
||||
test('should validate that a contentTypeId and an id is passed', () => {
|
||||
[
|
||||
|
@ -53,11 +53,10 @@ describe('RPC -> get()', () => {
|
|||
},
|
||||
{
|
||||
input: omit(validInput, 'version'),
|
||||
expectedError: '[version]: expected value of type [string] but got [undefined]',
|
||||
expectedError: '[version]: expected value of type [number] but got [undefined]',
|
||||
},
|
||||
{
|
||||
input: { ...validInput, version: '1' }, // invalid version format
|
||||
expectedError: '[version]: must follow the pattern [v${number}]',
|
||||
input: { ...validInput, version: '1' }, // string number is OK
|
||||
},
|
||||
].forEach(({ input, expectedError }) => {
|
||||
const error = validate(input, inputSchema);
|
||||
|
@ -79,7 +78,7 @@ describe('RPC -> get()', () => {
|
|||
{
|
||||
contentTypeId: 'foo',
|
||||
id: '123',
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
options: { any: 'object' },
|
||||
},
|
||||
inputSchema
|
||||
|
@ -91,7 +90,7 @@ describe('RPC -> get()', () => {
|
|||
{
|
||||
contentTypeId: 'foo',
|
||||
id: '123',
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
options: 123, // Not an object
|
||||
},
|
||||
inputSchema
|
||||
|
@ -126,7 +125,7 @@ describe('RPC -> get()', () => {
|
|||
id: FOO_CONTENT_ID,
|
||||
storage,
|
||||
version: {
|
||||
latest: 'v2',
|
||||
latest: 2,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -146,7 +145,7 @@ describe('RPC -> get()', () => {
|
|||
const expected = 'GetResult';
|
||||
storage.get.mockResolvedValueOnce(expected);
|
||||
|
||||
const result = await fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 'v1' });
|
||||
const result = await fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 1 });
|
||||
|
||||
expect(result).toEqual({
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
|
@ -157,8 +156,8 @@ describe('RPC -> get()', () => {
|
|||
{
|
||||
requestHandlerContext: ctx.requestHandlerContext,
|
||||
version: {
|
||||
request: 'v1',
|
||||
latest: 'v2', // from the registry
|
||||
request: 1,
|
||||
latest: 2, // from the registry
|
||||
},
|
||||
utils: {
|
||||
getTransforms: expect.any(Function),
|
||||
|
@ -183,16 +182,16 @@ describe('RPC -> get()', () => {
|
|||
fn(ctx, {
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
id: '1234',
|
||||
version: 'v7',
|
||||
version: 7,
|
||||
})
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [v2].'));
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [2].'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('object versioning', () => {
|
||||
test('should expose a utility to transform and validate services objects', () => {
|
||||
const { ctx, storage } = setup();
|
||||
fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 'v1' });
|
||||
fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 1 });
|
||||
const [[storageContext]] = storage.get.mock.calls;
|
||||
|
||||
// getTransforms() utils should be available from context
|
||||
|
|
|
@ -35,7 +35,7 @@ const FOO_CONTENT_ID = 'foo';
|
|||
describe('RPC -> search()', () => {
|
||||
describe('Input/Output validation', () => {
|
||||
const query = { title: 'hello' };
|
||||
const validInput = { contentTypeId: 'foo', version: 'v1', query };
|
||||
const validInput = { contentTypeId: 'foo', version: 1, query };
|
||||
|
||||
test('should validate that a contentTypeId and "query" object is passed', () => {
|
||||
[
|
||||
|
@ -46,11 +46,14 @@ describe('RPC -> search()', () => {
|
|||
},
|
||||
{
|
||||
input: omit(validInput, 'version'),
|
||||
expectedError: '[version]: expected value of type [string] but got [undefined]',
|
||||
expectedError: '[version]: expected value of type [number] but got [undefined]',
|
||||
},
|
||||
{
|
||||
input: { ...validInput, version: '1' }, // invalid version format
|
||||
expectedError: '[version]: must follow the pattern [v${number}]',
|
||||
input: { ...validInput, version: '1' }, // string number is OK
|
||||
},
|
||||
{
|
||||
input: { ...validInput, version: 'foo' }, // invalid version format
|
||||
expectedError: '[version]: expected value of type [number] but got [string]',
|
||||
},
|
||||
{
|
||||
input: omit(validInput, 'query'),
|
||||
|
@ -84,7 +87,7 @@ describe('RPC -> search()', () => {
|
|||
{
|
||||
contentTypeId: 'foo',
|
||||
query: { title: 'hello' },
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
options: { any: 'object' },
|
||||
},
|
||||
inputSchema
|
||||
|
@ -95,7 +98,7 @@ describe('RPC -> search()', () => {
|
|||
error = validate(
|
||||
{
|
||||
contentTypeId: 'foo',
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
query: { title: 'hello' },
|
||||
options: 123, // Not an object
|
||||
},
|
||||
|
@ -145,7 +148,7 @@ describe('RPC -> search()', () => {
|
|||
id: FOO_CONTENT_ID,
|
||||
storage,
|
||||
version: {
|
||||
latest: 'v2',
|
||||
latest: 2,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -167,7 +170,7 @@ describe('RPC -> search()', () => {
|
|||
|
||||
const result = await fn(ctx, {
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
version: 'v1', // version in request
|
||||
version: 1, // version in request
|
||||
query: { title: 'Hello' },
|
||||
});
|
||||
|
||||
|
@ -180,8 +183,8 @@ describe('RPC -> search()', () => {
|
|||
{
|
||||
requestHandlerContext: ctx.requestHandlerContext,
|
||||
version: {
|
||||
request: 'v1',
|
||||
latest: 'v2', // from the registry
|
||||
request: 1,
|
||||
latest: 2, // from the registry
|
||||
},
|
||||
utils: {
|
||||
getTransforms: expect.any(Function),
|
||||
|
@ -206,16 +209,16 @@ describe('RPC -> search()', () => {
|
|||
fn(ctx, {
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
query: { title: 'Hello' },
|
||||
version: 'v7',
|
||||
version: 7,
|
||||
})
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [v2].'));
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [2].'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('object versioning', () => {
|
||||
test('should expose a utility to transform and validate services objects', () => {
|
||||
const { ctx, storage } = setup();
|
||||
fn(ctx, { contentTypeId: FOO_CONTENT_ID, query: { title: 'Hello' }, version: 'v1' });
|
||||
fn(ctx, { contentTypeId: FOO_CONTENT_ID, query: { title: 'Hello' }, version: 1 });
|
||||
const [[storageContext]] = storage.search.mock.calls;
|
||||
|
||||
// getTransforms() utils should be available from context
|
||||
|
|
|
@ -35,7 +35,7 @@ const FOO_CONTENT_ID = 'foo';
|
|||
describe('RPC -> update()', () => {
|
||||
describe('Input/Output validation', () => {
|
||||
const data = { title: 'hello' };
|
||||
const validInput = { contentTypeId: 'foo', id: '123', version: 'v1', data };
|
||||
const validInput = { contentTypeId: 'foo', id: '123', version: 1, data };
|
||||
|
||||
test('should validate that a "contentTypeId", an "id" and "data" object is passed', () => {
|
||||
[
|
||||
|
@ -54,11 +54,10 @@ describe('RPC -> update()', () => {
|
|||
},
|
||||
{
|
||||
input: omit(validInput, 'version'),
|
||||
expectedError: '[version]: expected value of type [string] but got [undefined]',
|
||||
expectedError: '[version]: expected value of type [number] but got [undefined]',
|
||||
},
|
||||
{
|
||||
input: { ...validInput, version: '1' }, // invalid version format
|
||||
expectedError: '[version]: must follow the pattern [v${number}]',
|
||||
input: { ...validInput, version: '1' }, // string number is OK
|
||||
},
|
||||
{
|
||||
input: omit(validInput, 'data'),
|
||||
|
@ -88,7 +87,7 @@ describe('RPC -> update()', () => {
|
|||
{
|
||||
contentTypeId: 'foo',
|
||||
id: '123',
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
data: { title: 'hello' },
|
||||
options: { any: 'object' },
|
||||
},
|
||||
|
@ -102,7 +101,7 @@ describe('RPC -> update()', () => {
|
|||
contentTypeId: 'foo',
|
||||
data: { title: 'hello' },
|
||||
id: '123',
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
options: 123, // Not an object
|
||||
},
|
||||
inputSchema
|
||||
|
@ -137,7 +136,7 @@ describe('RPC -> update()', () => {
|
|||
id: FOO_CONTENT_ID,
|
||||
storage,
|
||||
version: {
|
||||
latest: 'v2',
|
||||
latest: 2,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -160,7 +159,7 @@ describe('RPC -> update()', () => {
|
|||
const result = await fn(ctx, {
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
id: '123',
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
data: { title: 'Hello' },
|
||||
});
|
||||
|
||||
|
@ -173,8 +172,8 @@ describe('RPC -> update()', () => {
|
|||
{
|
||||
requestHandlerContext: ctx.requestHandlerContext,
|
||||
version: {
|
||||
request: 'v1',
|
||||
latest: 'v2', // from the registry
|
||||
request: 1,
|
||||
latest: 2, // from the registry
|
||||
},
|
||||
utils: {
|
||||
getTransforms: expect.any(Function),
|
||||
|
@ -201,9 +200,9 @@ describe('RPC -> update()', () => {
|
|||
contentTypeId: FOO_CONTENT_ID,
|
||||
id: '123',
|
||||
data: { title: 'Hello' },
|
||||
version: 'v7',
|
||||
version: 7,
|
||||
})
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [v2].'));
|
||||
).rejects.toEqual(new Error('Invalid version. Latest version is [2].'));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -213,7 +212,7 @@ describe('RPC -> update()', () => {
|
|||
fn(ctx, {
|
||||
contentTypeId: FOO_CONTENT_ID,
|
||||
id: '123',
|
||||
version: 'v1',
|
||||
version: 1,
|
||||
data: { title: 'Hello' },
|
||||
});
|
||||
const [[storageContext]] = storage.update.mock.calls;
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { validateVersion } from '../../../common/utils';
|
||||
import type { Version } from '../../../common';
|
||||
import { validateVersion } from '@kbn/object-versioning/lib/utils';
|
||||
import type { Version } from '@kbn/object-versioning';
|
||||
|
||||
export const validateRequestVersion = (
|
||||
requestVersion: Version | undefined,
|
||||
|
@ -18,12 +18,15 @@ export const validateRequestVersion = (
|
|||
throw new Error('Request version missing');
|
||||
}
|
||||
|
||||
const requestVersionNumber = validateVersion(requestVersion);
|
||||
const latestVersionNumber = parseInt(latestVersion.substring(1), 10);
|
||||
const { result, value: requestVersionNumber } = validateVersion(requestVersion);
|
||||
|
||||
if (requestVersionNumber > latestVersionNumber) {
|
||||
if (!result) {
|
||||
throw new Error(`Invalid version [${requestVersion}]. Must be an integer.`);
|
||||
}
|
||||
|
||||
if (requestVersionNumber > latestVersion) {
|
||||
throw new Error(`Invalid version. Latest version is [${latestVersion}].`);
|
||||
}
|
||||
|
||||
return requestVersion;
|
||||
return requestVersionNumber;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue