diff --git a/keyserver/src/utils/validation-utils.js b/keyserver/src/utils/validation-utils.js --- a/keyserver/src/utils/validation-utils.js +++ b/keyserver/src/utils/validation-utils.js @@ -8,6 +8,11 @@ hasMinStateVersion, } from 'lib/shared/version-utils.js'; import { type PlatformDetails } from 'lib/types/device-types.js'; +import { + convertClientIDsToServerIDs, + convertObject, + convertServerIDsToClientIDs, +} from 'lib/utils/conversion-utils.js'; import { ServerError } from 'lib/utils/errors.js'; import { tCookie, @@ -16,9 +21,6 @@ tPlatformDetails, assertWithValidator, ashoatKeyserverID, - convertClientIDsToServerIDs, - convertObject, - convertServerIDsToClientIDs, } from 'lib/utils/validation-utils.js'; import { fetchNotAcknowledgedPolicies } from '../fetchers/policy-acknowledgment-fetchers.js'; diff --git a/lib/selectors/socket-selectors.js b/lib/selectors/socket-selectors.js --- a/lib/selectors/socket-selectors.js +++ b/lib/selectors/socket-selectors.js @@ -40,13 +40,10 @@ userInfosValidator, } from '../types/user-types.js'; import { getConfig } from '../utils/config.js'; +import { convertClientIDsToServerIDs } from '../utils/conversion-utils.js'; import { minimumOneTimeKeysRequired } from '../utils/crypto-utils.js'; import { values, hash } from '../utils/objects.js'; -import { - tID, - convertClientIDsToServerIDs, - ashoatKeyserverID, -} from '../utils/validation-utils.js'; +import { tID, ashoatKeyserverID } from '../utils/validation-utils.js'; const queuedReports: ( state: AppState, diff --git a/lib/utils/validation-utils.js b/lib/utils/conversion-utils.js copy from lib/utils/validation-utils.js copy to lib/utils/conversion-utils.js --- a/lib/utils/validation-utils.js +++ b/lib/utils/conversion-utils.js @@ -1,109 +1,10 @@ // @flow -import invariant from 'invariant'; import _mapKeys from 'lodash/fp/mapKeys.js'; import _mapValues from 'lodash/fp/mapValues.js'; -import t from 'tcomb'; -import type { - TStructProps, - TIrreducible, - TRefinement, - TEnums, - TInterface, - TUnion, - TType, -} from 'tcomb'; +import type { TInterface, TType } from 'tcomb'; -import { - validEmailRegex, - oldValidUsernameRegex, - validHexColorRegex, -} from '../shared/account-utils.js'; -import type { PlatformDetails } from '../types/device-types'; -import type { - MediaMessageServerDBContent, - PhotoMessageServerDBContent, - VideoMessageServerDBContent, -} from '../types/messages/media'; - -function tBool(value: boolean): TIrreducible { - return t.irreducible(value.toString(), x => x === value); -} - -function tString(value: string): TIrreducible { - return t.irreducible(`'${value}'`, x => x === value); -} - -function tNumber(value: number): TIrreducible { - return t.irreducible(value.toString(), x => x === value); -} - -function tShape(spec: TStructProps): TInterface { - return t.interface(spec, { strict: true }); -} - -type TRegex = TRefinement; -function tRegex(regex: RegExp): TRegex { - return t.refinement(t.String, val => regex.test(val)); -} - -function tNumEnum(nums: $ReadOnlyArray): TRefinement { - return t.refinement(t.Number, (input: number) => { - for (const num of nums) { - if (input === num) { - return true; - } - } - return false; - }); -} -const tNull: TIrreducible = t.irreducible('null', x => x === null); -const tDate: TRegex = tRegex(/^[0-9]{4}-[0-1][0-9]-[0-3][0-9]$/); -const tColor: TRegex = tRegex(validHexColorRegex); // we don't include # char -const tPlatform: TEnums = t.enums.of([ - 'ios', - 'android', - 'web', - 'windows', - 'macos', -]); -const tDeviceType: TEnums = t.enums.of(['ios', 'android']); -const tPlatformDetails: TInterface = tShape({ - platform: tPlatform, - codeVersion: t.maybe(t.Number), - stateVersion: t.maybe(t.Number), -}); -const tPassword: TRefinement = t.refinement( - t.String, - (password: string) => !!password, -); -const tCookie: TRegex = tRegex(/^(user|anonymous)=[0-9]+:[0-9a-f]+$/); -const tEmail: TRegex = tRegex(validEmailRegex); -const tOldValidUsername: TRegex = tRegex(oldValidUsernameRegex); -const tID: TRefinement = t.refinement(t.String, (id: string) => !!id); - -const tMediaMessagePhoto: TInterface = tShape({ - type: tString('photo'), - uploadID: tID, -}); - -const tMediaMessageVideo: TInterface = tShape({ - type: tString('video'), - uploadID: tID, - thumbnailUploadID: tID, -}); - -const tMediaMessageMedia: TUnion = t.union([ - tMediaMessagePhoto, - tMediaMessageVideo, -]); - -function assertWithValidator(data: mixed, validator: TType): T { - invariant(validator.is(data), "data isn't of type T"); - return (data: any); -} - -const ashoatKeyserverID = '256'; +import { assertWithValidator, tID } from './validation-utils.js'; function convertServerIDsToClientIDs( serverPrefixID: string, @@ -219,33 +120,8 @@ return input; } -const idSchemaRegex = '(?:[0-9]+\\|)?[0-9]+'; - export { - tBool, - tString, - tNumber, - tShape, - tRegex, - tNumEnum, - tNull, - tDate, - tColor, - tPlatform, - tDeviceType, - tPlatformDetails, - tPassword, - tCookie, - tEmail, - tOldValidUsername, - tID, - tMediaMessagePhoto, - tMediaMessageVideo, - tMediaMessageMedia, - assertWithValidator, - ashoatKeyserverID, convertClientIDsToServerIDs, convertServerIDsToClientIDs, convertObject, - idSchemaRegex, }; diff --git a/lib/utils/conversion-utils.test.js b/lib/utils/conversion-utils.test.js new file mode 100644 --- /dev/null +++ b/lib/utils/conversion-utils.test.js @@ -0,0 +1,69 @@ +// @flow + +import invariant from 'invariant'; +import t from 'tcomb'; + +import { + convertServerIDsToClientIDs, + convertClientIDsToServerIDs, +} from './conversion-utils.js'; +import { tShape, tID, idSchemaRegex } from './validation-utils.js'; + +describe('id conversion', () => { + it('should convert string id', () => { + const validator = tShape({ id: tID }); + const serverData = { id: '1' }; + const clientData = { id: '0|1' }; + + expect( + convertServerIDsToClientIDs('0', validator, serverData), + ).toStrictEqual(clientData); + expect( + convertClientIDsToServerIDs('0', validator, clientData), + ).toStrictEqual(serverData); + }); + + it('should convert a complex type', () => { + const validator = tShape({ ids: t.dict(tID, t.list(tID)) }); + const serverData = { ids: { '1': ['11', '12'], '2': [], '3': ['13'] } }; + const clientData = { + ids: { '0|1': ['0|11', '0|12'], '0|2': [], '0|3': ['0|13'] }, + }; + + expect( + convertServerIDsToClientIDs('0', validator, serverData), + ).toStrictEqual(clientData); + expect( + convertClientIDsToServerIDs('0', validator, clientData), + ).toStrictEqual(serverData); + }); + + it('should convert a refinement', () => { + const validator = t.refinement(tID, () => true); + const serverData = '1'; + const clientData = '0|1'; + + expect( + convertServerIDsToClientIDs('0', validator, serverData), + ).toStrictEqual(clientData); + expect( + convertClientIDsToServerIDs('0', validator, clientData), + ).toStrictEqual(serverData); + }); +}); + +describe('idSchemaRegex tests', () => { + it('should capture ids', () => { + const regex = new RegExp(`^(${idSchemaRegex})$`); + const ids = ['123|123', '0|0', '123', '0']; + + for (const id of ids) { + const result = regex.exec(id); + expect(result).not.toBeNull(); + invariant(result, 'result is not null'); + const matches = [...result]; + expect(matches).toHaveLength(2); + expect(matches[1]).toBe(id); + } + }); +}); diff --git a/lib/utils/validation-utils.js b/lib/utils/validation-utils.js --- a/lib/utils/validation-utils.js +++ b/lib/utils/validation-utils.js @@ -1,8 +1,6 @@ // @flow import invariant from 'invariant'; -import _mapKeys from 'lodash/fp/mapKeys.js'; -import _mapValues from 'lodash/fp/mapValues.js'; import t from 'tcomb'; import type { TStructProps, @@ -105,120 +103,6 @@ const ashoatKeyserverID = '256'; -function convertServerIDsToClientIDs( - serverPrefixID: string, - outputValidator: TType, - data: T, -): T { - const conversionFunction = id => { - if (id.indexOf('|') !== -1) { - console.warn(`Server id '${id}' already has a prefix`); - return id; - } - return `${serverPrefixID}|${id}`; - }; - - return convertObject(outputValidator, data, [tID], conversionFunction); -} - -function convertClientIDsToServerIDs( - serverPrefixID: string, - outputValidator: TType, - data: T, -): T { - const prefix = serverPrefixID + '|'; - const conversionFunction = id => { - if (id.startsWith(prefix)) { - return id.substr(prefix.length); - } - - throw new Error('invalid_client_id_prefix'); - }; - - return convertObject(outputValidator, data, [tID], conversionFunction); -} - -function convertObject( - validator: TType, - input: I, - typesToConvert: $ReadOnlyArray>, - conversionFunction: T => T, -): I { - if (input === null || input === undefined) { - return input; - } - - // While they should be the same runtime object, - // `TValidator` is `TType` and `validator` is `TType`. - // Having them have different types allows us to use `assertWithValidator` - // to change `input` flow type - const TValidator = typesToConvert[typesToConvert.indexOf(validator)]; - if (TValidator && TValidator.is(input)) { - const TInput = assertWithValidator(input, TValidator); - const converted = conversionFunction(TInput); - return assertWithValidator(converted, validator); - } - - if (validator.meta.kind === 'maybe' || validator.meta.kind === 'subtype') { - return convertObject( - validator.meta.type, - input, - typesToConvert, - conversionFunction, - ); - } - if (validator.meta.kind === 'interface' && typeof input === 'object') { - const recastValidator: TInterface = (validator: any); - const result = {}; - for (const key in input) { - const innerValidator = recastValidator.meta.props[key]; - result[key] = convertObject( - innerValidator, - input[key], - typesToConvert, - conversionFunction, - ); - } - return assertWithValidator(result, recastValidator); - } - if (validator.meta.kind === 'union') { - for (const innerValidator of validator.meta.types) { - if (innerValidator.is(input)) { - return convertObject( - innerValidator, - input, - typesToConvert, - conversionFunction, - ); - } - } - return input; - } - if (validator.meta.kind === 'list' && Array.isArray(input)) { - const innerValidator = validator.meta.type; - return (input.map(value => - convertObject(innerValidator, value, typesToConvert, conversionFunction), - ): any); - } - if (validator.meta.kind === 'dict' && typeof input === 'object') { - const domainValidator = validator.meta.domain; - const codomainValidator = validator.meta.codomain; - if (typesToConvert.includes(domainValidator)) { - input = _mapKeys(key => conversionFunction(key))(input); - } - return _mapValues(value => - convertObject( - codomainValidator, - value, - typesToConvert, - conversionFunction, - ), - )(input); - } - - return input; -} - const idSchemaRegex = '(?:[0-9]+\\|)?[0-9]+'; export { @@ -244,8 +128,5 @@ tMediaMessageMedia, assertWithValidator, ashoatKeyserverID, - convertClientIDsToServerIDs, - convertServerIDsToClientIDs, - convertObject, idSchemaRegex, }; diff --git a/lib/utils/validation-utils.test.js b/lib/utils/validation-utils.test.js --- a/lib/utils/validation-utils.test.js +++ b/lib/utils/validation-utils.test.js @@ -1,17 +1,9 @@ // @flow -import invariant from 'invariant'; -import t from 'tcomb'; - import { tMediaMessagePhoto, tMediaMessageVideo, tNumEnum, - tShape, - tID, - convertServerIDsToClientIDs, - convertClientIDsToServerIDs, - idSchemaRegex, } from './validation-utils.js'; import { threadTypes } from '../types/thread-types-enum.js'; import { values } from '../utils/objects.js'; @@ -148,62 +140,3 @@ }); }); }); - -describe('id conversion', () => { - it('should convert string id', () => { - const validator = tShape({ id: tID }); - const serverData = { id: '1' }; - const clientData = { id: '0|1' }; - - expect( - convertServerIDsToClientIDs('0', validator, serverData), - ).toStrictEqual(clientData); - expect( - convertClientIDsToServerIDs('0', validator, clientData), - ).toStrictEqual(serverData); - }); - - it('should convert a complex type', () => { - const validator = tShape({ ids: t.dict(tID, t.list(tID)) }); - const serverData = { ids: { '1': ['11', '12'], '2': [], '3': ['13'] } }; - const clientData = { - ids: { '0|1': ['0|11', '0|12'], '0|2': [], '0|3': ['0|13'] }, - }; - - expect( - convertServerIDsToClientIDs('0', validator, serverData), - ).toStrictEqual(clientData); - expect( - convertClientIDsToServerIDs('0', validator, clientData), - ).toStrictEqual(serverData); - }); - - it('should convert a refinement', () => { - const validator = t.refinement(tID, () => true); - const serverData = '1'; - const clientData = '0|1'; - - expect( - convertServerIDsToClientIDs('0', validator, serverData), - ).toStrictEqual(clientData); - expect( - convertClientIDsToServerIDs('0', validator, clientData), - ).toStrictEqual(serverData); - }); -}); - -describe('idSchemaRegex tests', () => { - it('should capture ids', () => { - const regex = new RegExp(`^(${idSchemaRegex})$`); - const ids = ['123|123', '0|0', '123', '0']; - - for (const id of ids) { - const result = regex.exec(id); - expect(result).not.toBeNull(); - invariant(result, 'result is not null'); - const matches = [...result]; - expect(matches).toHaveLength(2); - expect(matches[1]).toBe(id); - } - }); -});