Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: correctly infer output type from snake-camel utils #381

Merged
merged 8 commits into from
Jul 20, 2023
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Improvements

- [#381](https://github.com/alleslabs/celatone-frontend/pull/381) Correctly infer output type from snake-camel utils
- [#382](https://github.com/alleslabs/celatone-frontend/pull/382) Add pool manager v15 msgs to tx details
- [#371](https://github.com/alleslabs/celatone-frontend/pull/371) Refactor assign me component and fix color in redelegation page
- [#342](https://github.com/alleslabs/celatone-frontend/pull/342) Add fallback n/a token on asset icon on asset box
Expand Down
4 changes: 2 additions & 2 deletions src/lib/app-provider/hooks/useChainRecordAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const useChainRecordAsset = () => {
(asset) => asset.base === denom
);
return assetInfo
? (snakeToCamel({
? snakeToCamel({
...assetInfo,
precision:
assetInfo?.denom_units.find(
Expand All @@ -31,7 +31,7 @@ export const useChainRecordAsset = () => {
unit.denom ===
(denom[0] === "u" ? denom.slice(1) : "u".concat(denom))
)?.exponent ?? 0,
}) as InternalChainRecordAsset)
})
: null;
},
[currentChainRecord]
Expand Down
11 changes: 2 additions & 9 deletions src/lib/services/proposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ export const fetchGovDepositParams = (
.get<{ deposit_params: DepositParams }>(
`${lcdEndpoint}/cosmos/gov/v1beta1/params/deposit`
)
.then(
({ data }) => snakeToCamel(data.deposit_params) as DepositParamsInternal
);
.then(({ data }) => snakeToCamel(data.deposit_params));

interface ProposalVotingPeriod {
proposal_type: string;
Expand All @@ -48,12 +46,7 @@ export const fetchGovVotingParams = (
.get<{ voting_params: VotingParams }>(
`${lcdEndpoint}/cosmos/gov/v1beta1/params/voting`
)
.then(
({ data }) =>
snakeToCamel(
data.voting_params
) as SnakeToCamelCaseNested<VotingParamsInternal>
);
.then(({ data }) => snakeToCamel(data.voting_params));

export interface UploadAccess {
permission: AccessConfigPermission;
Expand Down
8 changes: 4 additions & 4 deletions src/lib/services/txService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
getBlockTransactionsByHeightQueryDocument,
} from "lib/query";
import { createQueryFnWithTimeout } from "lib/query-utils";
import type { Addr, Option, Transaction, TxFilters, Message } from "lib/types";
import type { Addr, Option, Transaction, TxFilters } from "lib/types";
import { MsgFurtherAction } from "lib/types";
import {
getActionMsgType,
Expand Down Expand Up @@ -89,7 +89,7 @@ export const useTxsByAddressPagination = (
.then<Transaction[]>(({ account_transactions }) =>
account_transactions.map<Transaction>((transaction) => ({
hash: parseTxHash(transaction.transaction.hash),
messages: snakeToCamel(transaction.transaction.messages) as Message[],
messages: snakeToCamel(transaction.transaction.messages),
sender: transaction.transaction.account.address as Addr,
isSigner: transaction.is_signer,
height: transaction.block.height,
Expand Down Expand Up @@ -207,7 +207,7 @@ export const useTxs = (
.then<Transaction[]>(({ transactions }) =>
transactions.map<Transaction>((transaction) => ({
hash: parseTxHash(transaction.hash),
messages: snakeToCamel(transaction.messages) as Message[],
messages: snakeToCamel(transaction.messages),
sender: transaction.account.address as Addr,
isSigner: false,
height: transaction.block.height,
Expand Down Expand Up @@ -267,7 +267,7 @@ export const useTxsByBlockHeightPagination = (
.then<Transaction[]>(({ transactions }) =>
transactions.map<Transaction>((transaction) => ({
hash: parseTxHash(transaction.hash),
messages: snakeToCamel(transaction.messages) as Message[],
messages: snakeToCamel(transaction.messages),
sender: transaction.account.address as Addr,
isSigner: false,
height,
Expand Down
28 changes: 27 additions & 1 deletion src/lib/types/converter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export type SnakeToCamelCase<S extends string> =
S extends `${infer T}_${infer U}`
? `${T}${Capitalize<SnakeToCamelCase<U>>}`
: S;

export type SnakeToCamelCaseNested<T> = T extends object
export type SnakeToCamelCaseNested<T> = T extends (...args: any[]) => any
? T
: T extends Array<infer R>
? Array<SnakeToCamelCaseNested<R>>
: T extends Record<string, any>
? {
[K in keyof T as SnakeToCamelCase<K & string>]: T[K] extends Array<
infer R
Expand All @@ -12,3 +17,24 @@ export type SnakeToCamelCaseNested<T> = T extends object
: SnakeToCamelCaseNested<T[K]>;
}
: T;

export type CamelToSnakeCase<S extends string> =
S extends `${infer First}${infer Rest}`
? `${First extends Uppercase<First>
? "_"
: ""}${Lowercase<First>}${CamelToSnakeCase<Rest>}`
: S;

export type CamelToSnakeCaseNested<T> = T extends (...args: any[]) => any
? T
: T extends Array<infer R>
? Array<CamelToSnakeCaseNested<R>>
: T extends Record<string, any>
? {
[K in keyof T as CamelToSnakeCase<K & string>]: T[K] extends Array<
infer R
>
? Array<CamelToSnakeCaseNested<R>>
: CamelToSnakeCaseNested<T[K]>;
}
: T;
41 changes: 26 additions & 15 deletions src/lib/utils/formatter/camelToSnake.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,48 @@
import mapObject from "map-obj";
import { snakeCase } from "snake-case";

import type { CamelToSnakeCaseNested } from "lib/types";

type Resolver<T> = T extends Array<infer U>
? CamelToSnakeCaseNested<U>[]
: // eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends Record<string, any>
? CamelToSnakeCaseNested<T>
: T;

// Convert camel case to snake case of an object
// Todo - Might have to fix type
export const camelToSnake = (obj: unknown): unknown => {
export const camelToSnake = <T>(obj: T): Resolver<T> => {
// Any falsy, which includes `null` whose typeof is `object`.
if (!obj) {
return obj;
return obj as Resolver<T>;
}

// Ignore Date, whose typeof is `object` too.
if (obj instanceof Date) {
return obj;
return obj as Resolver<T>;
}

// Array of object
if (Array.isArray(obj)) {
return obj.map((element) => {
return camelToSnake(element);
});
}) as Resolver<T>;
}

if (typeof obj === "object") {
return mapObject(obj, (key, value) => {
const newKey = snakeCase(key) || key;
if (key !== newKey && newKey in obj) {
throw new Error(
`Snake case key ${newKey} would overwrite existing key of the given JSON object`
);
return Object.entries(obj).reduce((acc, [key, value]) => {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const newKey = snakeCase(key) || key;
if (key !== newKey && newKey in obj) {
throw new Error(
`Snake case key ${newKey} would overwrite existing key of the given JSON object`
);
}
acc[newKey as keyof CamelToSnakeCaseNested<T>] = camelToSnake(value);
return acc;
}
return [newKey, camelToSnake(value)];
});
return acc;
}, {} as CamelToSnakeCaseNested<T>) as Resolver<T>;
}
// Something else like a String or Number.
return obj;
return obj as Resolver<T>;
};
41 changes: 26 additions & 15 deletions src/lib/utils/formatter/snakeToCamel.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,48 @@
import camelCase from "camelcase";
import mapObject from "map-obj";

import type { SnakeToCamelCaseNested } from "lib/types";

type Resolver<T> = T extends Array<infer U>
? SnakeToCamelCaseNested<U>[]
: // eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends Record<string, any>
? SnakeToCamelCaseNested<T>
: T;

// Convert snake case to camel case of an object
// Todo - Might have to fix type
export const snakeToCamel = (obj: unknown): unknown => {
export const snakeToCamel = <T>(obj: T): Resolver<T> => {
// Any falsy, which includes `null` whose typeof is `object`.
if (!obj) {
return obj;
return obj as Resolver<T>;
}

// Ignore Date, whose typeof is `object` too.
if (obj instanceof Date) {
return obj;
return obj as Resolver<T>;
}
// Array of object
if (Array.isArray(obj)) {
return obj.map((element) => {
return snakeToCamel(element);
});
}) as Resolver<T>;
}

if (typeof obj === "object") {
return mapObject(obj, (key, value) => {
const newKey = camelCase(key) || key;
if (key !== newKey && newKey in obj) {
throw new Error(
`Snake case key ${newKey} would overwrite existing key of the given JSON object`
);
return Object.entries(obj).reduce((acc, [key, value]) => {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const newKey = camelCase(key) || key;
if (key !== newKey && newKey in obj) {
throw new Error(
`Camel case key ${newKey} would overwrite existing key of the given JSON object`
);
}
acc[newKey as keyof SnakeToCamelCaseNested<T>] = snakeToCamel(value);
return acc;
}
return [newKey, snakeToCamel(value)];
});
return acc;
}, {} as SnakeToCamelCaseNested<T>) as Resolver<T>;
}

// Something else like a String or Number.
return obj;
return obj as Resolver<T>;
};