Skip to content

Commit

Permalink
chore: move bridge-api response validators to a separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
micaelae committed Oct 28, 2024
1 parent 31e9121 commit 304a039
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 125 deletions.
133 changes: 8 additions & 125 deletions ui/pages/bridge/bridge.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
} from '../../../shared/constants/bridge';
import { MINUTE } from '../../../shared/constants/time';
import fetchWithCache from '../../../shared/lib/fetch-with-cache';
import { validateData } from '../../../shared/lib/swaps-utils';
import {
decimalToHex,
hexToDecimal,
Expand All @@ -26,32 +25,20 @@ import {
isSwapsDefaultTokenSymbol,
} from '../../../shared/modules/swaps.utils';
import {
BridgeAsset,
BridgeFlag,
FeatureFlagResponse,
Quote,
QuoteRequest,
QuoteResponse,
TxData,
} from './types';
import {
FEATURE_FLAG_VALIDATORS,
QUOTE_VALIDATORS,
validateResponse,
} from './utils/validators';

const CLIENT_ID_HEADER = { 'X-Client-Id': BRIDGE_CLIENT_ID };
const CACHE_REFRESH_TEN_MINUTES = 10 * MINUTE;

type Validator<ExpectedResponse, DataToValidate> = {
property: keyof ExpectedResponse | string;
type: string;
validator: (value: DataToValidate) => boolean;
};

const validateResponse = <ExpectedResponse, DataToValidate>(
validators: Validator<ExpectedResponse, DataToValidate>[],
data: unknown,
urlUsed: string,
): data is ExpectedResponse => {
return validateData(validators, data, urlUsed);
};

export async function fetchBridgeFeatureFlags(): Promise<BridgeFeatureFlags> {
const url = `${BRIDGE_API_BASE_URL}/getAllFeatureFlags`;
const rawFeatureFlags = await fetchWithCache({
Expand All @@ -62,30 +49,8 @@ export async function fetchBridgeFeatureFlags(): Promise<BridgeFeatureFlags> {
});

if (
validateResponse<FeatureFlagResponse, unknown>(
[
{
property: BridgeFlag.EXTENSION_SUPPORT,
type: 'boolean',
validator: (v) => typeof v === 'boolean',
},
{
property: BridgeFlag.NETWORK_SRC_ALLOWLIST,
type: 'object',
validator: (v): v is number[] =>
Object.values(v as { [s: string]: unknown }).every(
(i) => typeof i === 'number',
),
},
{
property: BridgeFlag.NETWORK_DEST_ALLOWLIST,
type: 'object',
validator: (v): v is number[] =>
Object.values(v as { [s: string]: unknown }).every(
(i) => typeof i === 'number',
),
},
],
validateResponse<FeatureFlagResponse>(
FEATURE_FLAG_VALIDATORS,
rawFeatureFlags,
url,
)
Expand Down Expand Up @@ -179,89 +144,7 @@ export async function fetchBridgeQuotes(
});

const filteredQuotes = quotes.filter((quote: QuoteResponse) =>
validateResponse<QuoteResponse, unknown>(
[
{
property: 'quote',
type: 'object',
validator: (v): v is Quote =>
typeof v === 'object' &&
v !== null &&
v !== undefined &&
[
'requestId',
'srcTokenAmount',
'destTokenAmount',
'bridgeId',
].every(
(k) => k in v && typeof v[k as keyof typeof v] === 'string',
) &&
['srcTokenAmount', 'destTokenAmount'].every(
(k) =>
k in v &&
typeof v[k as keyof typeof v] === 'string' &&
/^\d+$/u.test(v[k as keyof typeof v] as string),
) &&
['srcAsset', 'destAsset'].every(
(k) =>
k in v &&
typeof v[k as keyof typeof v] === 'object' &&
'address' in v[k as keyof typeof v] &&
typeof (v[k as keyof typeof v] as BridgeAsset).address ===
'string' &&
'decimals' in v[k as keyof typeof v] &&
typeof (v[k as keyof typeof v] as BridgeAsset).decimals ===
'number',
),
},
{
property: 'approval',
type: 'object|undefined',
validator: (v): v is TxData | undefined =>
v === undefined ||
(v
? typeof v === 'object' &&
'gasLimit' in v &&
typeof v.gasLimit === 'number' &&
'to' in v &&
typeof v.to === 'string' &&
'from' in v &&
typeof v.from === 'string' &&
'data' in v &&
typeof v.data === 'string'
: false),
},
{
property: 'trade',
type: 'object',
validator: (v): v is TxData =>
v
? typeof v === 'object' &&
'gasLimit' in v &&
typeof v.gasLimit === 'number' &&
'to' in v &&
typeof v.to === 'string' &&
'from' in v &&
typeof v.from === 'string' &&
'data' in v &&
typeof v.data === 'string' &&
'value' in v &&
typeof v.value === 'string' &&
v.value.startsWith('0x')
: false,
},
{
property: 'estimatedProcessingTimeInSeconds',
type: 'number',
validator: (v): v is number[] =>
Object.values(v as { [s: string]: unknown }).every(
(i) => typeof i === 'number',
),
},
],
quote,
url,
),
validateResponse<QuoteResponse, object>(QUOTE_VALIDATORS, quote, url),
);
return filteredQuotes;
}
113 changes: 113 additions & 0 deletions ui/pages/bridge/utils/validators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { validateData } from '../../../../shared/lib/swaps-utils';
import { BridgeAsset, BridgeFlag, Quote, TxData } from '../types';

type Validator<ExpectedResponse, ResponseDataType> = {
property: keyof ExpectedResponse | string;
type: string;
validator: (value: ResponseDataType) => boolean;
};

export const validateResponse = <ExpectedResponse, ResponseDataType = unknown>(
validators: Validator<ExpectedResponse, ResponseDataType>[],
data: unknown,
urlUsed: string,
): data is ExpectedResponse => {
return validateData(validators, data, urlUsed);
};

export const QUOTE_VALIDATORS = [
{
property: 'quote',
type: 'object',
validator: (v: unknown): v is Quote =>
typeof v === 'object' &&
v !== null &&
v !== undefined &&
['requestId', 'srcTokenAmount', 'destTokenAmount', 'bridgeId'].every(
(k) => k in v && typeof v[k as keyof typeof v] === 'string',
) &&
['srcTokenAmount', 'destTokenAmount'].every(
(k) =>
k in v &&
typeof v[k as keyof typeof v] === 'string' &&
/^\d+$/u.test(v[k as keyof typeof v] as string),
) &&
['srcAsset', 'destAsset'].every(
(k) =>
k in v &&
typeof v[k as keyof typeof v] === 'object' &&
'address' in v[k as keyof typeof v] &&
typeof (v[k as keyof typeof v] as BridgeAsset).address === 'string' &&
'decimals' in v[k as keyof typeof v] &&
typeof (v[k as keyof typeof v] as BridgeAsset).decimals === 'number',
),
},
{
property: 'approval',
type: 'object|undefined',
validator: (v: unknown): v is TxData | undefined =>
v === undefined ||
(v
? typeof v === 'object' &&
'gasLimit' in v &&
typeof v.gasLimit === 'number' &&
'to' in v &&
typeof v.to === 'string' &&
'from' in v &&
typeof v.from === 'string' &&
'data' in v &&
typeof v.data === 'string'
: false),
},
{
property: 'trade',
type: 'object',
validator: (v: unknown): v is TxData =>
v
? typeof v === 'object' &&
'gasLimit' in v &&
typeof v.gasLimit === 'number' &&
'to' in v &&
typeof v.to === 'string' &&
'from' in v &&
typeof v.from === 'string' &&
'data' in v &&
typeof v.data === 'string' &&
'value' in v &&
typeof v.value === 'string' &&
v.value.startsWith('0x')
: false,
},
{
property: 'estimatedProcessingTimeInSeconds',
type: 'number',
validator: (v: unknown): v is number[] =>
Object.values(v as { [s: string]: unknown }).every(
(i) => typeof i === 'number',
),
},
];

export const FEATURE_FLAG_VALIDATORS = [
{
property: BridgeFlag.EXTENSION_SUPPORT,
type: 'boolean',
validator: (v: unknown) => typeof v === 'boolean',
},
{
property: BridgeFlag.NETWORK_SRC_ALLOWLIST,
type: 'object',
validator: (v: unknown): v is number[] =>
Object.values(v as { [s: string]: unknown }).every(
(i) => typeof i === 'number',
),
},
{
property: BridgeFlag.NETWORK_DEST_ALLOWLIST,
type: 'object',
validator: (v: unknown): v is number[] =>
Object.values(v as { [s: string]: unknown }).every(
(i) => typeof i === 'number',
),
},
];

0 comments on commit 304a039

Please sign in to comment.