diff --git a/__fixtures__/output/assetlist.camel.ts b/__fixtures__/output/assetlist.camel.ts index 917b044..d0b6114 100644 --- a/__fixtures__/output/assetlist.camel.ts +++ b/__fixtures__/output/assetlist.camel.ts @@ -9,7 +9,7 @@ export interface Asset { name: string; display: string; symbol: string; - traces?: any[]; + traces?: (IbcTransition | IbcCw20Transition | NonIbcTransition)[]; ibc?: { sourceChannel: string; dstChannel: string; diff --git a/__fixtures__/output/assetlist.ts b/__fixtures__/output/assetlist.ts index 7b80f6c..bb49d19 100644 --- a/__fixtures__/output/assetlist.ts +++ b/__fixtures__/output/assetlist.ts @@ -9,7 +9,7 @@ export interface Asset { name: string; display: string; symbol: string; - traces?: any[]; + traces?: (IbcTransition | IbcCw20Transition | NonIbcTransition)[]; ibc?: { source_channel: string; dst_channel: string; diff --git a/__fixtures__/output/chain.camel.ts b/__fixtures__/output/chain.camel.ts index cedfc98..96720c0 100644 --- a/__fixtures__/output/chain.camel.ts +++ b/__fixtures__/output/chain.camel.ts @@ -40,8 +40,8 @@ export interface CosmosChain { prettyName?: string; website?: string; updateLink?: string; - status?: any; - networkType?: any; + status?: "live" | "upcoming" | "killed"; + networkType?: "mainnet" | "testnet" | "devnet"; bech32Prefix: string; bech32Config?: { bech32PrefixAccAddr?: string; diff --git a/__fixtures__/output/chain.ts b/__fixtures__/output/chain.ts index 6c19269..4c967bd 100644 --- a/__fixtures__/output/chain.ts +++ b/__fixtures__/output/chain.ts @@ -40,8 +40,8 @@ export interface CosmosChain { pretty_name?: string; website?: string; update_link?: string; - status?: any; - network_type?: any; + status?: "live" | "upcoming" | "killed"; + network_type?: "mainnet" | "testnet" | "devnet"; bech32_prefix: string; bech32_config?: { bech32PrefixAccAddr?: string; diff --git a/__fixtures__/output/contract.camel.ts b/__fixtures__/output/contract.camel.ts new file mode 100644 index 0000000..7f9c8d5 --- /dev/null +++ b/__fixtures__/output/contract.camel.ts @@ -0,0 +1,112 @@ +export type Addr = string; +export interface Asset { + amount: Uint128; + info: AssetInfo; +} +export type AssetInfo = { + token: { + contractAddr: Addr; + }; +} | { + nativeToken: { + denom: string; + }; +}; +export type Binary = string; +export interface Cw20ReceiveMsg { + amount: Uint128; + msg: Binary; + sender: string; +} +export type Decimal = string; +export interface FeeInfo { + devFeePercent: number; + developerAddr?: Addr | any; + protocolFeePercent: number; + totalFeeBps: number; +} +export interface PoolConfig { + codeId: number; + feeInfo: FeeInfo; + isDisabled: boolean; + isGeneratorDisabled: boolean; + poolType: PoolType; +} +export type PoolType = { + xyk: any; +} | { + stable2Pool: any; +} | { + stable3Pool: any; +} | { + weighted: any; +} | { + custom: string; +}; +export interface SingleSwapRequest { + amount: Uint128; + assetIn: AssetInfo; + assetOut: AssetInfo; + beliefPrice?: Decimal | any; + maxSpread?: Decimal | any; + poolId: Uint128; + swapType: SwapType; +} +export type SwapType = { + giveIn: any; +} | { + giveOut: any; +} | { + custom: string; +}; +export type Uint128 = string; +export type ExecuteMsg = { + receive: Cw20ReceiveMsg; +} | { + updateConfig: { + feeCollector?: any; + generatorAddress?: any; + lpTokenCodeId?: any; + }; +} | { + updatePoolConfig: { + isDisabled?: any; + newFeeInfo?: FeeInfo | any; + poolType: PoolType; + }; +} | { + addToRegistery: { + newPoolConfig: PoolConfig; + }; +} | { + createPoolInstance: { + assetInfos: AssetInfo[]; + initParams?: Binary | any; + lpTokenName?: any; + lpTokenSymbol?: any; + poolType: PoolType; + }; +} | { + joinPool: { + assets?: any; + autoStake?: any; + lpToMint?: Uint128 | any; + poolId: Uint128; + recipient?: any; + slippageTolerance?: Decimal | any; + }; +} | { + swap: { + recipient?: any; + swapRequest: SingleSwapRequest; + }; +} | { + proposeNewOwner: { + expiresIn: number; + owner: string; + }; +} | { + dropOwnershipProposal: any; +} | { + claimOwnership: any; +}; \ No newline at end of file diff --git a/__fixtures__/output/contract.ts b/__fixtures__/output/contract.ts new file mode 100644 index 0000000..9443f4b --- /dev/null +++ b/__fixtures__/output/contract.ts @@ -0,0 +1,112 @@ +export type Addr = string; +export interface Asset { + amount: Uint128; + info: AssetInfo; +} +export type AssetInfo = { + token: { + contract_addr: Addr; + }; +} | { + native_token: { + denom: string; + }; +}; +export type Binary = string; +export interface Cw20ReceiveMsg { + amount: Uint128; + msg: Binary; + sender: string; +} +export type Decimal = string; +export interface FeeInfo { + dev_fee_percent: number; + developer_addr?: Addr | any; + protocol_fee_percent: number; + total_fee_bps: number; +} +export interface PoolConfig { + code_id: number; + fee_info: FeeInfo; + is_disabled: boolean; + is_generator_disabled: boolean; + pool_type: PoolType; +} +export type PoolType = { + xyk: any; +} | { + stable2_pool: any; +} | { + stable3_pool: any; +} | { + weighted: any; +} | { + custom: string; +}; +export interface SingleSwapRequest { + amount: Uint128; + asset_in: AssetInfo; + asset_out: AssetInfo; + belief_price?: Decimal | any; + max_spread?: Decimal | any; + pool_id: Uint128; + swap_type: SwapType; +} +export type SwapType = { + give_in: any; +} | { + give_out: any; +} | { + custom: string; +}; +export type Uint128 = string; +export type ExecuteMsg = { + receive: Cw20ReceiveMsg; +} | { + update_config: { + fee_collector?: any; + generator_address?: any; + lp_token_code_id?: any; + }; +} | { + update_pool_config: { + is_disabled?: any; + new_fee_info?: FeeInfo | any; + pool_type: PoolType; + }; +} | { + add_to_registery: { + new_pool_config: PoolConfig; + }; +} | { + create_pool_instance: { + asset_infos: AssetInfo[]; + init_params?: Binary | any; + lp_token_name?: any; + lp_token_symbol?: any; + pool_type: PoolType; + }; +} | { + join_pool: { + assets?: any; + auto_stake?: any; + lp_to_mint?: Uint128 | any; + pool_id: Uint128; + recipient?: any; + slippage_tolerance?: Decimal | any; + }; +} | { + swap: { + recipient?: any; + swap_request: SingleSwapRequest; + }; +} | { + propose_new_owner: { + expires_in: number; + owner: string; + }; +} | { + drop_ownership_proposal: any; +} | { + claim_ownership: any; +}; \ No newline at end of file diff --git a/__fixtures__/schemas/contract.json b/__fixtures__/schemas/contract.json new file mode 100644 index 0000000..af6f24c --- /dev/null +++ b/__fixtures__/schemas/contract.json @@ -0,0 +1,645 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "description": "Executable only by `config.owner`. Facilitates updating `config.fee_collector`, `config.generator_address`, `config.lp_token_code_id` parameters.", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "fee_collector": { + "type": [ + "string", + "null" + ] + }, + "generator_address": { + "type": [ + "string", + "null" + ] + }, + "lp_token_code_id": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Executable only by `pool_config.fee_info.developer_addr` or `config.owner` if its not set. Facilitates enabling / disabling new pool instances creation (`pool_config.is_disabled`) , and updating Fee (` pool_config.fee_info`) for new pool instances", + "type": "object", + "required": [ + "update_pool_config" + ], + "properties": { + "update_pool_config": { + "type": "object", + "required": [ + "pool_type" + ], + "properties": { + "is_disabled": { + "type": [ + "boolean", + "null" + ] + }, + "new_fee_info": { + "anyOf": [ + { + "$ref": "#/definitions/FeeInfo" + }, + { + "type": "null" + } + ] + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Adds a new pool with a new [`PoolType`] Key.", + "type": "object", + "required": [ + "add_to_registery" + ], + "properties": { + "add_to_registery": { + "type": "object", + "required": [ + "new_pool_config" + ], + "properties": { + "new_pool_config": { + "$ref": "#/definitions/PoolConfig" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Creates a new pool with the specified parameters in the `asset_infos` variable.", + "type": "object", + "required": [ + "create_pool_instance" + ], + "properties": { + "create_pool_instance": { + "type": "object", + "required": [ + "asset_infos", + "pool_type" + ], + "properties": { + "asset_infos": { + "type": "array", + "items": { + "$ref": "#/definitions/AssetInfo" + } + }, + "init_params": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "lp_token_name": { + "type": [ + "string", + "null" + ] + }, + "lp_token_symbol": { + "type": [ + "string", + "null" + ] + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "join_pool" + ], + "properties": { + "join_pool": { + "type": "object", + "required": [ + "pool_id" + ], + "properties": { + "assets": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Asset" + } + }, + "auto_stake": { + "type": [ + "boolean", + "null" + ] + }, + "lp_to_mint": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "pool_id": { + "$ref": "#/definitions/Uint128" + }, + "recipient": { + "type": [ + "string", + "null" + ] + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "swap" + ], + "properties": { + "swap": { + "type": "object", + "required": [ + "swap_request" + ], + "properties": { + "recipient": { + "type": [ + "string", + "null" + ] + }, + "swap_request": { + "$ref": "#/definitions/SingleSwapRequest" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "ProposeNewOwner creates an offer for a new owner. The validity period of the offer is set in the `expires_in` variable.", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "DropOwnershipProposal removes the existing offer for the new owner.", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Used to claim(approve) new owner proposal, thus changing contract's owner", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "title": "Description - This enum describes a asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + } + }, + "AssetInfo": { + "description": "This enum describes available Token types.", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "FeeInfo": { + "title": "Description - This struct describes the Fee configuration supported by a particular pool type.", + "type": "object", + "required": [ + "dev_fee_percent", + "protocol_fee_percent", + "total_fee_bps" + ], + "properties": { + "dev_fee_percent": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "developer_addr": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "protocol_fee_percent": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "total_fee_bps": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + } + }, + "PoolConfig": { + "description": "This struct stores a pool type's configuration.", + "type": "object", + "required": [ + "code_id", + "fee_info", + "is_disabled", + "is_generator_disabled", + "pool_type" + ], + "properties": { + "code_id": { + "description": "ID of contract which is used to create pools of this type", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "fee_info": { + "$ref": "#/definitions/FeeInfo" + }, + "is_disabled": { + "description": "Whether a pool type is disabled or not. If it is disabled, new pools cannot be created, but existing ones can still read the pool configuration", + "type": "boolean" + }, + "is_generator_disabled": { + "description": "Setting this to true means that pools of this type will not be able to get added to generator", + "type": "boolean" + }, + "pool_type": { + "description": "The pools type (provided in a [`PoolType`])", + "allOf": [ + { + "$ref": "#/definitions/PoolType" + } + ] + } + } + }, + "PoolType": { + "description": "This enum describes the key for the different Pool types supported by Dexter ## Available pool types ``` Xyk Stable2Pool Weighted Stable3Pool Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pool type", + "type": "object", + "required": [ + "xyk" + ], + "properties": { + "xyk": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Stable pool type", + "type": "object", + "required": [ + "stable2_pool" + ], + "properties": { + "stable2_pool": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Stable pool type", + "type": "object", + "required": [ + "stable3_pool" + ], + "properties": { + "stable3_pool": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Weighted pool type", + "type": "object", + "required": [ + "weighted" + ], + "properties": { + "weighted": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Custom pool type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "SingleSwapRequest": { + "type": "object", + "required": [ + "amount", + "asset_in", + "asset_out", + "pool_id", + "swap_type" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "asset_in": { + "$ref": "#/definitions/AssetInfo" + }, + "asset_out": { + "$ref": "#/definitions/AssetInfo" + }, + "belief_price": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "max_spread": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "pool_id": { + "$ref": "#/definitions/Uint128" + }, + "swap_type": { + "$ref": "#/definitions/SwapType" + } + } + }, + "SwapType": { + "description": "This enum describes available Swap types. ## Available swap types ``` GiveIn :: When we have the number of tokens being provided by the user to the pool in the swap request GiveOut :: When we have the number of tokens to be sent to the user from the pool in the swap request ```", + "oneOf": [ + { + "type": "object", + "required": [ + "give_in" + ], + "properties": { + "give_in": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "give_out" + ], + "properties": { + "give_out": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Custom swap type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} \ No newline at end of file diff --git a/__tests__/__snapshots__/assetlist.test.ts.snap b/__tests__/__snapshots__/assetlist.test.ts.snap index 8954605..47b4bde 100644 --- a/__tests__/__snapshots__/assetlist.test.ts.snap +++ b/__tests__/__snapshots__/assetlist.test.ts.snap @@ -12,7 +12,7 @@ exports[`assetlist 1`] = ` name: string; display: string; symbol: string; - traces?: any[]; + traces?: (IbcTransition | IbcCw20Transition | NonIbcTransition)[]; ibc?: { source_channel: string; dst_channel: string; @@ -105,7 +105,7 @@ exports[`assetlist camelCase 1`] = ` name: string; display: string; symbol: string; - traces?: any[]; + traces?: (IbcTransition | IbcCw20Transition | NonIbcTransition)[]; ibc?: { sourceChannel: string; dstChannel: string; diff --git a/__tests__/__snapshots__/chain.test.ts.snap b/__tests__/__snapshots__/chain.test.ts.snap index c1384db..fa3eefd 100644 --- a/__tests__/__snapshots__/chain.test.ts.snap +++ b/__tests__/__snapshots__/chain.test.ts.snap @@ -43,8 +43,8 @@ export interface CosmosChain { pretty_name?: string; website?: string; update_link?: string; - status?: any; - network_type?: any; + status?: "live" | "upcoming" | "killed"; + network_type?: "mainnet" | "testnet" | "devnet"; bech32_prefix: string; bech32_config?: { bech32PrefixAccAddr?: string; @@ -205,8 +205,8 @@ export interface CosmosChain { prettyName?: string; website?: string; updateLink?: string; - status?: any; - networkType?: any; + status?: "live" | "upcoming" | "killed"; + networkType?: "mainnet" | "testnet" | "devnet"; bech32Prefix: string; bech32Config?: { bech32PrefixAccAddr?: string; diff --git a/__tests__/__snapshots__/contract.test.ts.snap b/__tests__/__snapshots__/contract.test.ts.snap new file mode 100644 index 0000000..1a14f8f --- /dev/null +++ b/__tests__/__snapshots__/contract.test.ts.snap @@ -0,0 +1,231 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`contract 1`] = ` +"export type Addr = string; +export interface Asset { + amount: Uint128; + info: AssetInfo; +} +export type AssetInfo = { + token: { + contract_addr: Addr; + }; +} | { + native_token: { + denom: string; + }; +}; +export type Binary = string; +export interface Cw20ReceiveMsg { + amount: Uint128; + msg: Binary; + sender: string; +} +export type Decimal = string; +export interface FeeInfo { + dev_fee_percent: number; + developer_addr?: Addr | any; + protocol_fee_percent: number; + total_fee_bps: number; +} +export interface PoolConfig { + code_id: number; + fee_info: FeeInfo; + is_disabled: boolean; + is_generator_disabled: boolean; + pool_type: PoolType; +} +export type PoolType = { + xyk: any; +} | { + stable2_pool: any; +} | { + stable3_pool: any; +} | { + weighted: any; +} | { + custom: string; +}; +export interface SingleSwapRequest { + amount: Uint128; + asset_in: AssetInfo; + asset_out: AssetInfo; + belief_price?: Decimal | any; + max_spread?: Decimal | any; + pool_id: Uint128; + swap_type: SwapType; +} +export type SwapType = { + give_in: any; +} | { + give_out: any; +} | { + custom: string; +}; +export type Uint128 = string; +export type ExecuteMsg = { + receive: Cw20ReceiveMsg; +} | { + update_config: { + fee_collector?: any; + generator_address?: any; + lp_token_code_id?: any; + }; +} | { + update_pool_config: { + is_disabled?: any; + new_fee_info?: FeeInfo | any; + pool_type: PoolType; + }; +} | { + add_to_registery: { + new_pool_config: PoolConfig; + }; +} | { + create_pool_instance: { + asset_infos: AssetInfo[]; + init_params?: Binary | any; + lp_token_name?: any; + lp_token_symbol?: any; + pool_type: PoolType; + }; +} | { + join_pool: { + assets?: any; + auto_stake?: any; + lp_to_mint?: Uint128 | any; + pool_id: Uint128; + recipient?: any; + slippage_tolerance?: Decimal | any; + }; +} | { + swap: { + recipient?: any; + swap_request: SingleSwapRequest; + }; +} | { + propose_new_owner: { + expires_in: number; + owner: string; + }; +} | { + drop_ownership_proposal: any; +} | { + claim_ownership: any; +};" +`; + +exports[`contract camelCase 1`] = ` +"export type Addr = string; +export interface Asset { + amount: Uint128; + info: AssetInfo; +} +export type AssetInfo = { + token: { + contractAddr: Addr; + }; +} | { + nativeToken: { + denom: string; + }; +}; +export type Binary = string; +export interface Cw20ReceiveMsg { + amount: Uint128; + msg: Binary; + sender: string; +} +export type Decimal = string; +export interface FeeInfo { + devFeePercent: number; + developerAddr?: Addr | any; + protocolFeePercent: number; + totalFeeBps: number; +} +export interface PoolConfig { + codeId: number; + feeInfo: FeeInfo; + isDisabled: boolean; + isGeneratorDisabled: boolean; + poolType: PoolType; +} +export type PoolType = { + xyk: any; +} | { + stable2Pool: any; +} | { + stable3Pool: any; +} | { + weighted: any; +} | { + custom: string; +}; +export interface SingleSwapRequest { + amount: Uint128; + assetIn: AssetInfo; + assetOut: AssetInfo; + beliefPrice?: Decimal | any; + maxSpread?: Decimal | any; + poolId: Uint128; + swapType: SwapType; +} +export type SwapType = { + giveIn: any; +} | { + giveOut: any; +} | { + custom: string; +}; +export type Uint128 = string; +export type ExecuteMsg = { + receive: Cw20ReceiveMsg; +} | { + updateConfig: { + feeCollector?: any; + generatorAddress?: any; + lpTokenCodeId?: any; + }; +} | { + updatePoolConfig: { + isDisabled?: any; + newFeeInfo?: FeeInfo | any; + poolType: PoolType; + }; +} | { + addToRegistery: { + newPoolConfig: PoolConfig; + }; +} | { + createPoolInstance: { + assetInfos: AssetInfo[]; + initParams?: Binary | any; + lpTokenName?: any; + lpTokenSymbol?: any; + poolType: PoolType; + }; +} | { + joinPool: { + assets?: any; + autoStake?: any; + lpToMint?: Uint128 | any; + poolId: Uint128; + recipient?: any; + slippageTolerance?: Decimal | any; + }; +} | { + swap: { + recipient?: any; + swapRequest: SingleSwapRequest; + }; +} | { + proposeNewOwner: { + expiresIn: number; + owner: string; + }; +} | { + dropOwnershipProposal: any; +} | { + claimOwnership: any; +};" +`; diff --git a/__tests__/__snapshots__/items.test.ts.snap b/__tests__/__snapshots__/items.test.ts.snap new file mode 100644 index 0000000..932c09c --- /dev/null +++ b/__tests__/__snapshots__/items.test.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`works 1`] = ` +"export interface Dog { + name: string; + breed: string; + age?: number; +} +export interface Cat { + name: string; + breed: string; + age?: number; +} +export interface Person { + traces?: (Dog | Cat)[]; + profile?: Dog & { + microchipped?: boolean; + }; + identity?: { + passportNumber?: string; + } | { + driverLicense?: string; + }; +}" +`; diff --git a/__tests__/arrays.test.ts b/__tests__/arrays.test.ts index 9d53f85..6a417d8 100644 --- a/__tests__/arrays.test.ts +++ b/__tests__/arrays.test.ts @@ -1,6 +1,6 @@ import { generateTypeScript } from '../src'; -const exampleSchema = { +const schema = { "$id": "https://example.com/person.schema.json", "$schema": "https://json-schema.org/draft-07/schema#", "title": "Person", @@ -64,5 +64,5 @@ const exampleSchema = { }; it('works', () => { - expect(generateTypeScript(exampleSchema as any)).toMatchSnapshot(); + expect(generateTypeScript(schema as any)).toMatchSnapshot(); }); diff --git a/__tests__/contract.test.ts b/__tests__/contract.test.ts new file mode 100644 index 0000000..17d2bac --- /dev/null +++ b/__tests__/contract.test.ts @@ -0,0 +1,19 @@ +import { writeFileSync } from 'fs'; + +import schema from '../__fixtures__/schemas/contract.json'; +import { generateTypeScript } from '../src'; + +it('contract', () => { + const code = generateTypeScript(schema as any); + expect(code).toMatchSnapshot(); + writeFileSync(__dirname + '/../__fixtures__/output/contract.ts', code); +}); + +it('contract camelCase', () => { + const code = generateTypeScript(schema as any, { + useSingleQuotes: true, + useCamelCase: true + }); + expect(code).toMatchSnapshot(); + writeFileSync(__dirname + '/../__fixtures__/output/contract.camel.ts', code); +}); diff --git a/__tests__/items.test.ts b/__tests__/items.test.ts new file mode 100644 index 0000000..739150a --- /dev/null +++ b/__tests__/items.test.ts @@ -0,0 +1,90 @@ +import { generateTypeScript } from '../src'; + +const schema = { + "$id": "https://example.com/person.schema.json", + "$schema": "https://json-schema.org/draft-07/schema#", + "title": "Person", + "type": "object", + "properties": { + "traces": { + "type": "array", + "description": "The origin of the asset, capturing all transitions in form and location.", + "items": { + "anyOf": [ + { "$ref": "#/$defs/dog" }, + { "$ref": "#/$defs/cat" } + ] + }, + "minContains": 1 + }, + "profile": { + "allOf": [ + { "$ref": "#/$defs/dog" }, + { + "type": "object", + "properties": { + "microchipped": { + "type": "boolean" + } + } + } + ] + }, + "identity": { + "oneOf": [ + { + "type": "object", + "properties": { + "passportNumber": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "driverLicense": { + "type": "string" + } + } + } + ] + } + }, + "$defs": { + "dog": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "breed": { + "type": "string" + }, + "age": { + "type": "integer" + } + }, + "required": ["name", "breed"] + }, + "cat": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "breed": { + "type": "string" + }, + "age": { + "type": "integer" + } + }, + "required": ["name", "breed"] + } + } +}; + +it('works', () => { + expect(generateTypeScript(schema as any)).toMatchSnapshot(); +}); diff --git a/__tests__/nested.test.ts b/__tests__/nested.test.ts index c8632ab..fbaa2c1 100644 --- a/__tests__/nested.test.ts +++ b/__tests__/nested.test.ts @@ -1,6 +1,6 @@ import { generateTypeScript } from '../src'; -const exampleSchema = { +const schema = { "$id": "https://example.com/person.schema.json", "$schema": "https://json-schema.org/draft-07/schema#", "title": "Person", @@ -35,5 +35,5 @@ const exampleSchema = { }; it('works', () => { - expect(generateTypeScript(exampleSchema)).toMatchSnapshot() + expect(generateTypeScript(schema)).toMatchSnapshot() }) \ No newline at end of file diff --git a/__tests__/readme.test.ts b/__tests__/readme.test.ts index 6794388..fd11961 100644 --- a/__tests__/readme.test.ts +++ b/__tests__/readme.test.ts @@ -1,7 +1,7 @@ import { generateTypeScript } from '../src'; -const exampleSchema = { +const schema = { "$id": "https://example.com/person.schema.json", "$schema": "https://json-schema.org/draft-07/schema#", "title": "Person", @@ -27,5 +27,5 @@ const exampleSchema = { }; it('works', () => { - expect(generateTypeScript(exampleSchema as any)).toMatchSnapshot(); + expect(generateTypeScript(schema as any)).toMatchSnapshot(); }); diff --git a/__tests__/simple.test.ts b/__tests__/simple.test.ts index 4e25945..79b2007 100644 --- a/__tests__/simple.test.ts +++ b/__tests__/simple.test.ts @@ -1,6 +1,6 @@ import { generateTypeScript } from '../src'; -const exampleSchema = { +const schema = { "$id": "https://example.com/person.schema.json", "$schema": "https://json-schema.org/draft-07/schema#", "title": "Person", @@ -20,5 +20,5 @@ const exampleSchema = { }; it('works', () => { - expect(generateTypeScript(exampleSchema)).toMatchSnapshot() + expect(generateTypeScript(schema)).toMatchSnapshot() }) \ No newline at end of file diff --git a/__tests__/types.test.ts b/__tests__/types.test.ts index db51210..8a7a4ca 100644 --- a/__tests__/types.test.ts +++ b/__tests__/types.test.ts @@ -1,6 +1,6 @@ import { generateTypeScript } from '../src'; -const exampleSchema = { +const schema = { "$id": "https://example.com/person.schema.json", "$schema": "https://json-schema.org/draft-07/schema#", "title": "Person", @@ -40,5 +40,5 @@ const exampleSchema = { }; it('works', () => { - expect(generateTypeScript(exampleSchema as any)).toMatchSnapshot(); + expect(generateTypeScript(schema as any)).toMatchSnapshot(); }); diff --git a/src/index.ts b/src/index.ts index b20dffb..11bb0f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,27 +5,28 @@ import { defaultOptions, SchemaTSContext, type SchemaTSOptions } from "./context import type { JSONSchema } from "./types"; import { isValidIdentifier, toCamelCase, toPascalCase } from "./utils"; + export function generateTypeScript(schema: JSONSchema, options?: SchemaTSOptions): string { const interfaces = []; const opts = options || defaultOptions; const ctx = new SchemaTSContext(opts, schema, schema, []); try { - - // Process definitions first - if (schema.$defs) { - for (const key in schema.$defs) { - interfaces.push(createInterfaceDeclaration(ctx, toPascalCase(key), schema.$defs[key])); - } + // Process both $defs and definitions + const definitions = schema.$defs || schema.definitions || {}; + for (const key in definitions) { + interfaces.push(createInterfaceDeclaration(ctx, toPascalCase(key), definitions[key])); } } catch (e) { console.error('Error processing interfaces'); throw e; } + // Process the main schema const title = schema.title; if (!title) { console.error('schema or options require a title'); + return ''; // Ensure there's a return on error condition } interfaces.push(createInterfaceDeclaration(ctx, toPascalCase(title), schema)); return generate(t.file(t.program(interfaces))).code; @@ -36,18 +37,21 @@ function createInterfaceDeclaration( name: string, schema: JSONSchema ): t.ExportNamedDeclaration { - const properties = schema.properties || {}; - const required = schema.required || []; - const bodyElements: (t.TSIndexSignature | t.TSPropertySignature)[] = Object.keys(properties).map(key => { - const prop = properties[key]; - return createPropertySignature(ctx, key, prop, required, schema); - }); - - // Handling additionalProperties + // Handle standard properties if they exist + let bodyElements: any = []; + if (schema.properties) { + const properties = schema.properties; + const required = schema.required || []; + bodyElements = Object.keys(properties).map(key => { + const prop = properties[key]; + return createPropertySignature(ctx, key, prop, required, schema); + }); + } + + // Handling additionalProperties if they exist if (schema.additionalProperties) { const additionalType = typeof schema.additionalProperties === 'boolean' ? t.tsStringKeyword() : getTypeForProp(ctx, schema.additionalProperties, [], schema); - const indexSignature = t.tsIndexSignature( [t.identifier("key")], // index name, can be any valid name t.tsTypeAnnotation(additionalType) @@ -56,17 +60,48 @@ function createInterfaceDeclaration( bodyElements.push(indexSignature); } - const interfaceDeclaration = t.tsInterfaceDeclaration( - t.identifier(name), - null, - [], - t.tsInterfaceBody(bodyElements) - ); + // Handling oneOf, anyOf, allOf if properties are not defined + if (!schema.properties && (schema.oneOf || schema.anyOf || schema.allOf)) { + const types = []; + if (schema.oneOf) { + types.push(getTypeForProp(ctx, { oneOf: schema.oneOf }, [], schema)); + } + if (schema.anyOf) { + types.push(getTypeForProp(ctx, { anyOf: schema.anyOf }, [], schema)); + } + if (schema.allOf) { + types.push(getTypeForProp(ctx, { allOf: schema.allOf }, [], schema)); + } + + // Create a union type if multiple types are generated + const combinedType = types.length > 1 ? t.tsUnionType(types) : types[0]; + + // Create a type alias instead of an interface if we're only handling these constructs + const typeAlias = t.tsTypeAliasDeclaration(t.identifier(name), null, combinedType); + return t.exportNamedDeclaration(typeAlias); + } - // Make the interface exportable - return t.exportNamedDeclaration(interfaceDeclaration); + // Finally, create the interface declaration if there are any body elements + if (bodyElements.length > 0) { + const interfaceDeclaration = t.tsInterfaceDeclaration( + t.identifier(name), + null, + [], + t.tsInterfaceBody(bodyElements) + ); + return t.exportNamedDeclaration(interfaceDeclaration); + } + + if (schema.type) { + return t.exportNamedDeclaration(t.tsTypeAliasDeclaration(t.identifier(name), null, getTypeForProp(ctx, schema, [], schema))); + } + + // Fallback to exporting a basic type if nothing else is possible + console.warn(`No properties or type definitions found for ${name}, defaulting to 'any'.`); + return t.exportNamedDeclaration(t.tsTypeAliasDeclaration(t.identifier(name), null, t.tsAnyKeyword())); } + function createPropertySignature( ctx: SchemaTSContext, key: string, @@ -87,62 +122,89 @@ function createPropertySignature( return propSig; } - function getTypeForProp(ctx: SchemaTSContext, prop: JSONSchema, required: string[], schema: JSONSchema): t.TSType { if (prop.$ref) { return resolveRefType(ctx, prop.$ref, schema); } - switch (prop.type) { - case 'string': { - if (prop.enum) { - // Convert each string in the enum to a TypeScript literal type and join them into a union - const enumType = prop.enum.map(enumValue => t.tsLiteralType(t.stringLiteral(enumValue))); - return t.tsUnionType(enumType); - } - return t.tsStringKeyword(); + if (prop.enum) { + const enumType = prop.enum.map(enumValue => t.tsLiteralType(t.stringLiteral(enumValue))); + return t.tsUnionType(enumType); + } + + if (prop.type) { + switch (prop.type) { + case 'string': + return t.tsStringKeyword(); + case 'number': + case 'integer': + return t.tsNumberKeyword(); + case 'boolean': + return t.tsBooleanKeyword(); + case 'array': + if (prop.items) { + return t.tsArrayType(getTypeForProp(ctx, prop.items, required, schema)); + } else { + throw new Error('Array items specification is missing'); + } + case 'object': + if (prop.properties) { + const nestedProperties = prop.properties; + const nestedRequired = prop.required || []; + const typeElements = Object.keys(nestedProperties).map(nestedKey => { + const nestedProp = nestedProperties[nestedKey]; + return createPropertySignature(ctx, nestedKey, nestedProp, nestedRequired, schema); + }); + return t.tsTypeLiteral(typeElements); + } else { + return t.tsAnyKeyword(); + } + default: + return t.tsAnyKeyword(); } - case 'number': - case 'integer': - return t.tsNumberKeyword(); - case 'boolean': - return t.tsBooleanKeyword(); - case 'array': - if (prop.items) { - return t.tsArrayType(getTypeForProp(ctx, prop.items, required, schema)); - } else { - throw new Error('Array items specification is missing'); - } - case 'object': - if (prop.properties) { - const nestedProperties = prop.properties; - const nestedRequired = prop.required || []; - const typeElements = Object.keys(nestedProperties).map(nestedKey => { - const nestedProp = nestedProperties[nestedKey]; - return createPropertySignature(ctx, nestedKey, nestedProp, nestedRequired, schema); - }); - return t.tsTypeLiteral(typeElements); - // } else { - // throw new Error('Object must have properties'); - } else { - return t.tsAnyKeyword(); - } - break; - default: - return t.tsAnyKeyword(); } + + if (prop.anyOf) { + const types = prop.anyOf.map((subProp) => getTypeForProp(ctx, subProp, required, schema)); + return t.tsUnionType(types); + } + + if (prop.allOf) { + const types = prop.allOf.map((subProp) => getTypeForProp(ctx, subProp, required, schema)); + return t.tsIntersectionType(types); + } + + if (prop.oneOf) { + const types = prop.oneOf.map((subProp) => getTypeForProp(ctx, subProp, required, schema)); + return t.tsUnionType(types); + } + + console.error(prop); + throw new Error('Unsupported property type or keyword'); } function resolveRefType(ctx: SchemaTSContext, ref: string, schema: JSONSchema): t.TSType { const path = ref.split('/'); const definitionName = path.pop(); - if (definitionName && schema.$defs && schema.$defs[definitionName]) { - return t.tsTypeReference(t.identifier(toPascalCase(definitionName))); + + // Check both $defs and definitions in the local schema + if (definitionName) { + if (schema.$defs && schema.$defs[definitionName]) { + return t.tsTypeReference(t.identifier(toPascalCase(definitionName))); + } else if (schema.definitions && schema.definitions[definitionName]) { + return t.tsTypeReference(t.identifier(toPascalCase(definitionName))); + } } - // now look in root - if (definitionName && ctx.root.$defs && ctx.root.$defs[definitionName]) { - return t.tsTypeReference(t.identifier(toPascalCase(definitionName))); + + // Check both $defs and definitions in the root schema + if (definitionName) { + if (ctx.root.$defs && ctx.root.$defs[definitionName]) { + return t.tsTypeReference(t.identifier(toPascalCase(definitionName))); + } else if (ctx.root.definitions && ctx.root.definitions[definitionName]) { + return t.tsTypeReference(t.identifier(toPascalCase(definitionName))); + } } - throw new Error(`Reference ${ref} not found in definitions.`); + // If no definitions are found, throw an error + throw new Error(`Reference ${ref} not found in definitions or $defs.`); } diff --git a/src/types.ts b/src/types.ts index ff47359..614943d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,13 @@ export interface JSONSchema { type?: string; enum?: string[]; items?: JSONSchema; - $defs?: { [key: string]: JSONSchema }; + $defs?: { [key: string]: JSONSchema }; // (JSON Schema Draft 2019-09 and later) + definitions?: { [key: string]: JSONSchema }; // (JSON Schema Draft-04 to Draft-07) additionalProperties?: boolean | JSONSchema; + anyOf?: JSONSchema[]; + allOf?: JSONSchema[]; + oneOf?: JSONSchema[]; + description?: string; + default?: any; + format?: string; } \ No newline at end of file