From 7585a92162d992b7a8590c5104e01c31a37cc5a8 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 13 Apr 2024 09:07:26 -0700 Subject: [PATCH] tests --- __fixtures__/schemas/assetlist.json | 460 +++++++++++ __fixtures__/schemas/chain.json | 714 +++++++++++++++++ __fixtures__/schemas/ibc.json | 183 +++++ __fixtures__/schemas/memo.json | 42 + .../__snapshots__/assetlist.test.ts.snap | 94 +++ __tests__/__snapshots__/chain.test.ts.snap | 30 +- __tests__/assetlist.test.ts | 5 + __tests__/chain.test.ts | 721 +----------------- src/index.ts | 107 ++- 9 files changed, 1600 insertions(+), 756 deletions(-) create mode 100644 __fixtures__/schemas/assetlist.json create mode 100644 __fixtures__/schemas/chain.json create mode 100644 __fixtures__/schemas/ibc.json create mode 100644 __fixtures__/schemas/memo.json create mode 100644 __tests__/__snapshots__/assetlist.test.ts.snap create mode 100644 __tests__/assetlist.test.ts diff --git a/__fixtures__/schemas/assetlist.json b/__fixtures__/schemas/assetlist.json new file mode 100644 index 0000000..6413aa8 --- /dev/null +++ b/__fixtures__/schemas/assetlist.json @@ -0,0 +1,460 @@ +{ + "$id": "https://osmosis.zone/assetlists.schema.json", + "$schema": "https://json-schema.org/draft-07/schema", + "title": "Asset Lists", + "description": "Asset lists are a similar mechanism to allow frontends and other UIs to fetch metadata associated with Cosmos SDK denoms, especially for assets sent over IBC.", + "type": "object", + "required": [ + "chain_name", + "assets" + ], + "properties": { + "$schema": { + "type": "string", + "pattern": "^(\\.\\./)+assetlist\\.schema\\.json$" + }, + "chain_name": { + "type": "string" + }, + "assets": { + "type": "array", + "items": { + "$ref": "#/$defs/asset" + }, + "minContains": 1 + } + }, + "additionalProperties": false, + "$defs": { + "asset": { + "type": "object", + "required": [ + "denom_units", + "base", + "display", + "name", + "symbol" + ], + "properties": { + "deprecated": { + "type": "boolean", + "description": "[OPTIONAL] Whether the asset has been deprecated for use. For readability, it is best to omit this property unless TRUE." + }, + "description": { + "type": "string", + "description": "[OPTIONAL] A short description of the asset" + }, + "extended_description": { + "type": "string", + "description": "[OPTIONAL] A long description of the asset" + }, + "denom_units": { + "type": "array", + "items": { + "$ref": "#/$defs/denom_unit" + }, + "minContains": 1 + }, + "type_asset": { + "type": "string", + "enum": ["sdk.coin", "cw20", "erc20", "ics20", "snip20", "snip25", "bitcoin-like", "evm-base", "svm-base", "substrate"], + "default": "sdk.coin", + "description": "[OPTIONAL] The potential options for type of asset. By default, assumes sdk.coin" + }, + "address": { + "type": "string", + "description": "[OPTIONAL] The address of the asset. Only required for type_asset : cw20, snip20" + }, + "base": { + "type": "string", + "description": "The base unit of the asset. Must be in denom_units." + }, + "name": { + "type": "string", + "description": "The project name of the asset. For example Bitcoin.", + "maxLength": 42 + }, + "display": { + "type": "string", + "description": "The human friendly unit of the asset. Must be in denom_units." + }, + "symbol": { + "type": "string", + "description": "The symbol of an asset. For example BTC." + }, + "traces": { + "type": "array", + "description": "The origin of the asset, starting with the index, and capturing all transitions in form and location.", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/ibc_transition" + }, + { + "$ref": "#/$defs/ibc_cw20_transition" + }, + { + "$ref": "#/$defs/non_ibc_transition" + } + ] + }, + "minContains": 1 + }, + "ibc": { + "type": "object", + "description": "[OPTIONAL] IBC Channel between src and dst between chain", + "required": [ + "source_channel", + "dst_channel", + "source_denom" + ], + "properties": { + "source_channel": { + "type": "string" + }, + "dst_channel": { + "type": "string" + }, + "source_denom": { + "type": "string" + } + }, + "additionalProperties": false + }, + "logo_URIs": { + "type": "object", + "properties": { + "png": { + "type": "string", + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$" + }, + "svg": { + "type": "string", + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$" + } + }, + "additionalProperties": false + }, + "images": { + "type": "array", + "items": { + "type": "object", + "properties": { + "image_sync": { + "$ref": "#/$defs/pointer" + }, + "png": { + "type": "string", + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$" + }, + "svg": { + "type": "string", + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$" + }, + "theme": { + "type": "object", + "properties": { + "primary_color_hex": { + "type": "string", + "pattern": "^#[0-9a-fA-F]{6}$" + }, + "circle": { + "type": "boolean" + }, + "dark_mode": { + "type": "boolean" + } + }, + "minProperties": 1, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "minItems": 1 + }, + "coingecko_id": { + "type": "string", + "description": "[OPTIONAL] The coingecko id to fetch asset data from coingecko v3 api. See https://api.coingecko.com/api/v3/coins/list" + }, + "keywords": { + "type": "array", + "items": { + "type": "string" + }, + "minContains": 1, + "maxContains": 20 + }, + "socials": { + "type": "object", + "minProperties": 1, + "properties": { + "website": { + "type": "string", + "format": "uri" + }, + "twitter": { + "type": "string", + "format": "uri" + } + } + } + }, + "additionalProperties": false, + "if": { + "required": [ + "type_asset" + ], + "properties": { + "type_asset": { + "enum": [ + "erc20", + "cw20", + "snip20" + ] + } + } + }, + "then": { + "required": [ + "address" + ] + } + }, + "denom_unit": { + "type": "object", + "required": [ + "denom", + "exponent" + ], + "properties": { + "denom": { + "type": "string" + }, + "exponent": { + "type": "integer" + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + }, + "minContains": 1 + } + }, + "additionalProperties": false + }, + "pointer": { + "type": "object", + "description": "The (primary) key used to identify an object within the Chain Registry.", + "required": [ + "chain_name" + ], + "properties": { + "chain_name": { + "type": "string", + "description": "The chain name or platform from which the object resides. E.g., 'cosmoshub', 'ethereum', 'forex', or 'nasdaq'" + }, + "base_denom": { + "type": "string", + "description": "The base denom of the asset from which the object originates. E.g., when describing ATOM from Cosmos Hub, specify 'uatom', NOT 'atom' nor 'ATOM'; base units are unique per platform." + } + }, + "additionalProperties": false + }, + "ibc_transition": { + "type": "object", + "required": [ + "type", + "counterparty", + "chain" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "ibc" + ] + }, + "counterparty": { + "type": "object", + "required": [ + "chain_name", + "base_denom", + "channel_id" + ], + "properties": { + "chain_name": { + "type": "string", + "description": "The name of the counterparty chain. (must match exactly the chain name used in the Chain Registry)" + }, + "base_denom": { + "type": "string", + "description": "The base unit of the asset on its source platform. E.g., when describing ATOM from Cosmos Hub, specify 'uatom', NOT 'atom' nor 'ATOM'; base units are unique per platform." + }, + "channel_id": { + "type": "string", + "pattern": "^channel-(JEnb|\\d+)$", + "description": "The counterparty IBC transfer channel(, e.g., 'channel-1')." + } + }, + "additionalProperties": false + }, + "chain": { + "type": "object", + "required": [ + "channel_id", + "path" + ], + "properties": { + "channel_id": { + "type": "string", + "pattern": "^channel-\\d+$", + "description": "The chain's IBC transfer channel(, e.g., 'channel-1')." + }, + "path": { + "type": "string", + "description": "The port/channel/denom input string that generates the 'ibc/...' denom." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "ibc_cw20_transition": { + "type": "object", + "required": [ + "type", + "counterparty", + "chain" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "ibc-cw20" + ] + }, + "counterparty": { + "type": "object", + "required": [ + "chain_name", + "base_denom", + "port", + "channel_id" + ], + "properties": { + "chain_name": { + "type": "string", + "description": "The name of the counterparty chain. (must match exactly the chain name used in the Chain Registry)" + }, + "base_denom": { + "type": "string", + "description": "The base unit of the asset on its source platform. E.g., when describing ATOM from Cosmos Hub, specify 'uatom', NOT 'atom' nor 'ATOM'; base units are unique per platform." + }, + "port": { + "type": "string", + "description": "The port used to transfer IBC assets; often 'transfer', but sometimes varies, e.g., for outgoing cw20 transfers." + }, + "channel_id": { + "type": "string", + "pattern": "^channel-\\d+$", + "description": "The counterparty IBC transfer channel(, e.g., 'channel-1')." + } + }, + "additionalProperties": false + }, + "chain": { + "type": "object", + "required": [ + "port", + "channel_id", + "path" + ], + "properties": { + "port": { + "type": "string", + "description": "The port used to transfer IBC assets; often 'transfer', but sometimes varies, e.g., for outgoing cw20 transfers." + }, + "channel_id": { + "type": "string", + "pattern": "^channel-\\d+$", + "description": "The chain's IBC transfer channel(, e.g., 'channel-1')." + }, + "path": { + "type": "string", + "description": "The port/channel/denom input string that generates the 'ibc/...' denom." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "non_ibc_transition": { + "type": "object", + "required": [ + "type", + "counterparty", + "provider" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "bridge", + "liquid-stake", + "synthetic", + "wrapped", + "additional-mintage", + "test-mintage" + ] + }, + "counterparty": { + "type": "object", + "required": [ + "chain_name", + "base_denom" + ], + "properties": { + "chain_name": { + "type": "string", + "description": "The chain or platform from which the asset originates. E.g., 'cosmoshub', 'ethereum', 'forex', or 'nasdaq'" + }, + "base_denom": { + "type": "string" + }, + "contract": { + "type": "string", + "description": "The contract address where the transition takes place, where applicable. E.g., The Ethereum contract that locks up the asset while it's minted on another chain." + } + }, + "additionalProperties": false + }, + "chain": { + "type": "object", + "required": [ + "contract" + ], + "properties": { + "contract": { + "type": "string", + "description": "The contract address where the transition takes place, where applicable. E.g., The Ethereum contract that locks up the asset while it's minted on another chain." + } + }, + "additionalProperties": false + }, + "provider": { + "type": "string", + "description": "The entity offering the service. E.g., 'Gravity Bridge' [Network] or 'Tether' [Company]." + } + }, + "additionalProperties": false + } + } + } \ No newline at end of file diff --git a/__fixtures__/schemas/chain.json b/__fixtures__/schemas/chain.json new file mode 100644 index 0000000..d293404 --- /dev/null +++ b/__fixtures__/schemas/chain.json @@ -0,0 +1,714 @@ +{ + "$id": "https://sikka.tech/chain.schema.json", + "$schema": "https://json-schema.org/draft-07/schema", + "title": "Cosmos Chain", + "description": "Cosmos Chain.json is a metadata file that contains information about a cosmos sdk based chain.", + "type": "object", + "required": [ + "chain_name", + "chain_id", + "bech32_prefix" + ], + "properties": { + "$schema": { + "type": "string", + "pattern": "^(\\.\\./)+chain\\.schema\\.json$" + }, + "chain_name": { + "type": "string", + "pattern": "[a-z0-9]+" + }, + "chain_id": { + "type": "string" + }, + "pre_fork_chain_name": { + "type": "string", + "pattern": "[a-z0-9]+" + }, + "pretty_name": { + "type": "string" + }, + "website": { + "type": "string", + "format": "uri" + }, + "update_link": { + "type": "string", + "format": "uri" + }, + "status": { + "enum": [ + "live", + "upcoming", + "killed" + ] + }, + "network_type": { + "enum": [ + "mainnet", + "testnet", + "devnet" + ] + }, + "bech32_prefix": { + "type": "string", + "description": "The default prefix for the human-readable part of addresses that identifies the coin type. Must be registered with SLIP-0173. E.g., 'cosmos'" + }, + "bech32_config": { + "type": "object", + "description": "Used to override the bech32_prefix for specific uses.", + "properties": { + "bech32PrefixAccAddr": { + "type": "string", + "description": "e.g., 'cosmos'" + }, + "bech32PrefixAccPub": { + "type": "string", + "description": "e.g., 'cosmospub'" + }, + "bech32PrefixValAddr": { + "type": "string", + "description": "e.g., 'cosmosvaloper'" + }, + "bech32PrefixValPub": { + "type": "string", + "description": "e.g., 'cosmosvaloperpub'" + }, + "bech32PrefixConsAddr": { + "type": "string", + "description": "e.g., 'cosmosvalcons'" + }, + "bech32PrefixConsPub": { + "type": "string", + "description": "e.g., 'cosmosvalconspub'" + } + }, + "additionalProperties": false, + "minProperties": 1 + }, + "daemon_name": { + "type": "string" + }, + "node_home": { + "type": "string" + }, + "key_algos": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "secp256k1", + "ethsecp256k1", + "ed25519", + "sr25519", + "bn254" + ], + "uniqueItems": true + } + }, + "slip44": { + "type": "number" + }, + "alternative_slip44s": { + "type": "array", + "items": { + "type": "number" + } + }, + "fees": { + "type": "object", + "required": [ + "fee_tokens" + ], + "properties": { + "fee_tokens": { + "type": "array", + "items": { + "$ref": "#/$defs/fee_token" + } + } + }, + "additionalProperties": false + }, + "staking": { + "type": "object", + "required": [ + "staking_tokens" + ], + "properties": { + "staking_tokens": { + "type": "array", + "items": { + "$ref": "#/$defs/staking_token" + } + }, + "lock_duration": { + "type": "object", + "properties": { + "blocks": { + "type": "number", + "description": "The number of blocks for which the staked tokens are locked." + }, + "time": { + "type": "string", + "description": "The approximate time for which the staked tokens are locked." + } + }, + "additionalProperties": false, + "minProperties": 1 + } + }, + "additionalProperties": false + }, + "codebase": { + "type": "object", + "properties": { + "git_repo": { + "type": "string", + "format": "uri" + }, + "recommended_version": { + "type": "string" + }, + "go_version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$", + "description": "Minimum accepted go version to build the binary." + }, + "compatible_versions": { + "type": "array", + "items": { + "type": "string" + } + }, + "binaries": { + "type": "object", + "properties": { + "linux/amd64": { + "type": "string", + "format": "uri" + }, + "linux/arm64": { + "type": "string", + "format": "uri" + }, + "darwin/amd64": { + "type": "string", + "format": "uri" + }, + "darwin/arm64": { + "type": "string", + "format": "uri" + }, + "windows/amd64": { + "type": "string", + "format": "uri" + }, + "windows/arm64": { + "type": "string", + "format": "uri" + } + }, + "additionalProperties": false + }, + "cosmos_sdk_version": { + "type": "string" + }, + "consensus": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "tendermint", + "cometbft", + "sei-tendermint" + ] + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false + }, + "cosmwasm_version": { + "type": "string" + }, + "cosmwasm_enabled": { + "type": "boolean" + }, + "cosmwasm_path": { + "type": "string", + "description": "Relative path to the cosmwasm directory. ex. $HOME/.juno/data/wasm", + "pattern": "^\\$HOME.*$" + }, + "ibc_go_version": { + "type": "string" + }, + "ics_enabled": { + "type": "array", + "description": "List of IBC apps (usually corresponding to a ICS standard) which have been enabled on the network.", + "items": { + "type": "string", + "description": "IBC app or ICS standard.", + "enum": [ + "ics20-1", + "ics27-1", + "mauth" + ] + } + }, + "genesis": { + "type": "object", + "required": [ + "genesis_url" + ], + "properties": { + "name": { + "type": "string" + }, + "genesis_url": { + "type": "string", + "format": "uri" + }, + "ics_ccv_url": { + "type": "string", + "format": "uri" + } + }, + "additionalProperties": false + }, + "versions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Official Upgrade Name" + }, + "tag": { + "type": "string", + "description": "Git Upgrade Tag" + }, + "height": { + "type": "number", + "description": "Block Height" + }, + "proposal": { + "type": "number", + "description": "Proposal that will officially signal community acceptance of the upgrade." + }, + "previous_version_name": { + "type": "string", + "description": "[Optional] Name of the previous version" + }, + "next_version_name": { + "type": "string", + "description": "[Optional] Name of the following version" + }, + "recommended_version": { + "type": "string" + }, + "go_version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$", + "description": "Minimum accepted go version to build the binary." + }, + "compatible_versions": { + "type": "array", + "items": { + "type": "string" + } + }, + "cosmos_sdk_version": { + "type": "string" + }, + "consensus": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "tendermint", + "cometbft", + "sei-tendermint" + ] + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false + }, + "cosmwasm_version": { + "type": "string" + }, + "cosmwasm_enabled": { + "type": "boolean" + }, + "cosmwasm_path": { + "type": "string", + "description": "Relative path to the cosmwasm directory. ex. $HOME/.juno/data/wasm", + "pattern": "^\\$HOME.*$" + }, + "ibc_go_version": { + "type": "string" + }, + "ics_enabled": { + "type": "array", + "description": "List of IBC apps (usually corresponding to a ICS standard) which have been enabled on the network.", + "items": { + "type": "string", + "description": "IBC app or ICS standard.", + "enum": [ + "ics20-1", + "ics27-1", + "mauth" + ] + } + }, + "binaries": { + "type": "object", + "properties": { + "linux/amd64": { + "type": "string", + "format": "uri" + }, + "linux/arm64": { + "type": "string", + "format": "uri" + }, + "darwin/amd64": { + "type": "string", + "format": "uri" + }, + "darwin/arm64": { + "type": "string", + "format": "uri" + }, + "windows/amd64": { + "type": "string", + "format": "uri" + }, + "windows/arm64": { + "type": "string", + "format": "uri" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "images": { + "type": "array", + "items": { + "type": "object", + "properties": { + "image_sync": { + "$ref": "#/$defs/pointer" + }, + "png": { + "type": "string", + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$" + }, + "svg": { + "type": "string", + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$" + }, + "theme": { + "type": "object", + "properties": { + "primary_color_hex": { + "type": "string", + "pattern": "^#[0-9a-fA-F]{6}$" + }, + "circle": { + "type": "boolean" + }, + "dark_mode": { + "type": "boolean" + } + }, + "minProperties": 1, + "additionalProperties": false + }, + "layout": { + "type": "string", + "enum": ["logo", "logomark", "logotype"], + "description": "logomark == icon only; logotype == text only; logo == icon + text." + }, + "text_position": { + "type": "string", + "enum": ["top", "bottom", "left", "right", "integrated"], + "description": "Indicates in which position the text is placed, in case the layout is 'icon' type, it's required only in this case." + } + }, + "if": { + "properties": { + "layout": { "const": "logo" } + }, + "required": ["layout"] + }, + "then": { + "required": ["text_position"] + }, + "anyOf": [ + { + "required": ["png"] + }, + { + "required": ["svg"] + } + ], + "additionalProperties": false + } + }, + "logo_URIs": { + "type": "object", + "properties": { + "png": { + "type": "string", + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$" + }, + "svg": { + "type": "string", + "format": "uri-reference", + "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$" + } + }, + "additionalProperties": false + }, + "description": { + "type": "string", + "maxLength": 3000 + }, + "peers": { + "type": "object", + "properties": { + "seeds": { + "type": "array", + "items": { + "$ref": "#/$defs/peer" + } + }, + "persistent_peers": { + "type": "array", + "items": { + "$ref": "#/$defs/peer" + } + } + }, + "additionalProperties": false + }, + "apis": { + "type": "object", + "properties": { + "rpc": { + "type": "array", + "items": { + "$ref": "#/$defs/endpoint" + } + }, + "rest": { + "type": "array", + "items": { + "$ref": "#/$defs/endpoint" + } + }, + "grpc": { + "type": "array", + "items": { + "$ref": "#/$defs/endpoint" + } + }, + "wss": { + "type": "array", + "items": { + "$ref": "#/$defs/endpoint" + } + }, + "grpc-web": { + "type": "array", + "items": { + "$ref": "#/$defs/endpoint" + } + }, + "evm-http-jsonrpc": { + "type": "array", + "items": { + "$ref": "#/$defs/endpoint" + } + } + }, + "additionalProperties": false + }, + "explorers": { + "type": "array", + "items": { + "$ref": "#/$defs/explorer" + } + }, + "keywords": { + "type": "array", + "maxContains": 20, + "items": { + "type": "string" + } + }, + "extra_codecs": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ethermint", + "injective" + ], + "uniqueItems": true + } + } + }, + "additionalProperties": false, + "$defs": { + "peer": { + "type": "object", + "required": [ + "id", + "address" + ], + "properties": { + "id": { + "type": "string" + }, + "address": { + "type": "string" + }, + "provider": { + "type": "string" + } + }, + "additionalProperties": false + }, + "endpoint": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "archive": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "explorer": { + "type": "object", + "properties": { + "kind": { + "type": "string" + }, + "url": { + "type": "string" + }, + "tx_page": { + "type": "string" + }, + "account_page": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fee_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + }, + "fixed_min_gas_price": { + "type": "number" + }, + "low_gas_price": { + "type": "number" + }, + "average_gas_price": { + "type": "number" + }, + "high_gas_price": { + "type": "number" + }, + "gas_costs": { + "type": "object", + "properties": { + "cosmos_send": { + "type": "number" + }, + "ibc_transfer": { + "type": "number" + } + }, + "additionalProperties": false, + "minProperties": 1 + } + }, + "additionalProperties": false + }, + "staking_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + }, + "pointer": { + "type": "object", + "description": "The (primary) key used to identify an object within the Chain Registry.", + "required": [ + "chain_name" + ], + "properties": { + "chain_name": { + "type": "string", + "description": "The chain name or platform from which the object resides. E.g., 'cosmoshub', 'ethereum', 'forex', or 'nasdaq'" + }, + "base_denom": { + "type": "string", + "description": "The base denom of the asset from which the object originates. E.g., when describing ATOM from Cosmos Hub, specify 'uatom', NOT 'atom' nor 'ATOM'; base units are unique per platform." + } + }, + "additionalProperties": false + } + } + } \ No newline at end of file diff --git a/__fixtures__/schemas/ibc.json b/__fixtures__/schemas/ibc.json new file mode 100644 index 0000000..5a17f07 --- /dev/null +++ b/__fixtures__/schemas/ibc.json @@ -0,0 +1,183 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": [ + "chain_1", + "chain_2", + "channels" + ], + "properties": { + "$schema": { + "type": "string", + "pattern": "^(\\.\\./)+ibc_data\\.schema\\.json$" + }, + "chain_1": { + "type": "object", + "$ref": "#/$defs/chain_info" + }, + "chain_2": { + "type": "object", + "$ref": "#/$defs/chain_info" + }, + "channels": { + "type": "array", + "items": { + "type": "object", + "required": [ + "chain_1", + "chain_2", + "ordering", + "version" + ], + "properties": { + "chain_1": { + "type": "object", + "$ref": "#/$defs/channel_info" + }, + "chain_2": { + "type": "object", + "$ref": "#/$defs/channel_info" + }, + "ordering": { + "enum": [ + "ordered", + "unordered" + ], + "description": "Determines if packets from a sending module must be 'ordered' or 'unordered'." + }, + "version": { + "type": "string", + "description": "IBC Version" + }, + "fee_version": { + "type": "string", + "description": "Fee Version" + }, + "description": { + "type": "string", + "description": "Human readable description of the channel." + }, + "tags": { + "type": "object", + "description": "Human readable key:value pairs that help describe and distinguish channels.", + "properties": { + "status": { + "enum": [ + "live", + "upcoming", + "killed" + ] + }, + "preferred": { + "type": "boolean" + }, + "dex": { + "type": "string" + }, + "properties": { + "type": "string", + "description": "String that helps describe non-dex use cases ex: interchain accounts(ICA)." + } + }, + "additionalProperties": true + } + }, + "additionalProperties": false + } + }, + "operators": { + "type": "array", + "description": "ibc connection operator information.", + "items": { + "type": "object", + "required": [ + "chain_1", + "chain_2", + "memo", + "name" + ], + "properties": { + "chain_1": { + "type": "object", + "$ref": "#/$defs/chain_operator_info" + }, + "chain_2": { + "type": "object", + "$ref": "#/$defs/chain_operator_info" + }, + "memo": { + "type": "string" + }, + "name": { + "type": "string", + "description": "Operator display name" + }, + "discord_handle": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false, + "$defs": { + "chain_operator_info": { + "type": "object", + "description": "Operator information on a specific chain.", + "properties": { + "address": { + "type": "string" + } + } + }, + "chain_info": { + "type": "object", + "description": "Top level IBC data pertaining to the chain. `chain_1` and `chain_2` should be in alphabetical order.", + "required": [ + "chain_name", + "client_id", + "connection_id" + ], + "properties": { + "chain_name": { + "type": "string" + }, + "client_id": { + "type": "string", + "description": "The client ID on the corresponding chain representing the other chain's light client." + }, + "connection_id": { + "type": "string", + "description": "The connection ID on the corresponding chain representing a connection to the other chain." + } + }, + "additionalProperties": false + }, + "channel_info": { + "type": "object", + "required": [ + "channel_id", + "port_id" + ], + "properties": { + "channel_id": { + "type": "string", + "description": "The channel ID on the corresponding chain's connection representing a channel on the other chain." + }, + "port_id": { + "type": "string", + "description": "The IBC port ID which a relevant module binds to on the corresponding chain." + }, + "client_id": { + "type": "string", + "description": "Optional. The client ID on the corresponding chain representing the other chain's light client." + }, + "connection_id": { + "type": "string", + "description": "Optional. The connection ID on the corresponding chain representing a connection to the other chain." + } + }, + "additionalProperties": false + } + } + } \ No newline at end of file diff --git a/__fixtures__/schemas/memo.json b/__fixtures__/schemas/memo.json new file mode 100644 index 0000000..e2f00d5 --- /dev/null +++ b/__fixtures__/schemas/memo.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "required": [ + "memo_keys" + ], + "properties": { + "$schema": { + "type": "string", + "pattern": "^(\\.\\./)+memo_keys\\.schema\\.json$" + }, + "memo_keys": { + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "description", + "git_repo", + "memo" + ], + "properties": { + "key": { + "type": "string" + }, + "description": { + "type": "string" + }, + "git_repo": { + "type": "string", + "format": "uri" + }, + "memo": { + "type": "object" + } + }, + "additionalProperties": true + } + } + }, + "additionalProperties": false + } \ No newline at end of file diff --git a/__tests__/__snapshots__/assetlist.test.ts.snap b/__tests__/__snapshots__/assetlist.test.ts.snap new file mode 100644 index 0000000..7442e53 --- /dev/null +++ b/__tests__/__snapshots__/assetlist.test.ts.snap @@ -0,0 +1,94 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`assetlist 1`] = ` +"interface Asset { + deprecated?: boolean; + description?: string; + extended_description?: string; + denom_units: Denomunit[]; + type_asset?: string; + address?: string; + base: string; + name: string; + display: string; + symbol: string; + traces?: any[]; + ibc?: { + source_channel: string; + dst_channel: string; + source_denom: string; + }; + logo_URIs?: { + png?: string; + svg?: string; + }; + images?: { + image_sync?: Pointer; + png?: string; + svg?: string; + theme?: { + primary_color_hex?: string; + circle?: boolean; + dark_mode?: boolean; + }; + }[]; + coingecko_id?: string; + keywords?: string[]; + socials?: { + website?: string; + twitter?: string; + }; +} +interface Denomunit { + denom: string; + exponent: number; + aliases?: string[]; +} +interface Pointer { + chain_name: string; + base_denom?: string; +} +interface Ibctransition { + type: string; + counterparty: { + chain_name: string; + base_denom: string; + channel_id: string; + }; + chain: { + channel_id: string; + path: string; + }; +} +interface Ibccw20transition { + type: string; + counterparty: { + chain_name: string; + base_denom: string; + port: string; + channel_id: string; + }; + chain: { + port: string; + channel_id: string; + path: string; + }; +} +interface Nonibctransition { + type: string; + counterparty: { + chain_name: string; + base_denom: string; + contract?: string; + }; + chain?: { + contract: string; + }; + provider: string; +} +interface Asset Lists { + $schema?: string; + chain_name: string; + assets: Asset[]; +}" +`; diff --git a/__tests__/__snapshots__/chain.test.ts.snap b/__tests__/__snapshots__/chain.test.ts.snap index 7bb0312..241dede 100644 --- a/__tests__/__snapshots__/chain.test.ts.snap +++ b/__tests__/__snapshots__/chain.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`works 1`] = ` +exports[`chain 1`] = ` "interface Peer { id: string; address: string; @@ -75,12 +75,12 @@ interface Cosmos Chain { go_version?: string; compatible_versions?: string[]; binaries?: { - "\\"linux/amd64\\""?: string; - "\\"linux/arm64\\""?: string; - "\\"darwin/amd64\\""?: string; - "\\"darwin/arm64\\""?: string; - "\\"windows/amd64\\""?: string; - "\\"windows/arm64\\""?: string; + "'linux/amd64'"?: string; + "'linux/arm64'"?: string; + "'darwin/amd64'"?: string; + "'darwin/arm64'"?: string; + "'windows/amd64'"?: string; + "'windows/arm64'"?: string; }; cosmos_sdk_version?: string; consensus?: { @@ -118,12 +118,12 @@ interface Cosmos Chain { ibc_go_version?: string; ics_enabled?: string[]; binaries?: { - "\\"linux/amd64\\""?: string; - "\\"linux/arm64\\""?: string; - "\\"darwin/amd64\\""?: string; - "\\"darwin/arm64\\""?: string; - "\\"windows/amd64\\""?: string; - "\\"windows/arm64\\""?: string; + "'linux/amd64'"?: string; + "'linux/arm64'"?: string; + "'darwin/amd64'"?: string; + "'darwin/arm64'"?: string; + "'windows/amd64'"?: string; + "'windows/arm64'"?: string; }; }[]; }; @@ -153,8 +153,8 @@ interface Cosmos Chain { rest?: Endpoint[]; grpc?: Endpoint[]; wss?: Endpoint[]; - "\\"grpc-web\\""?: Endpoint[]; - "\\"evm-http-jsonrpc\\""?: Endpoint[]; + "'grpc-web'"?: Endpoint[]; + "'evm-http-jsonrpc'"?: Endpoint[]; }; explorers?: Explorer[]; keywords?: string[]; diff --git a/__tests__/assetlist.test.ts b/__tests__/assetlist.test.ts new file mode 100644 index 0000000..4fcfeed --- /dev/null +++ b/__tests__/assetlist.test.ts @@ -0,0 +1,5 @@ +import assetlist from '../__fixtures__/schemas/assetlist.json'; +import { generateTypeScript } from '../src'; +it('assetlist', () => { + expect(generateTypeScript(assetlist as any)).toMatchSnapshot(); +}); diff --git a/__tests__/chain.test.ts b/__tests__/chain.test.ts index 76a168f..8debfd2 100644 --- a/__tests__/chain.test.ts +++ b/__tests__/chain.test.ts @@ -1,720 +1,5 @@ +import chain from '../__fixtures__/schemas/chain.json'; import { generateTypeScript } from '../src'; - -const exampleSchema = { - "$id": "https://sikka.tech/chain.schema.json", - "$schema": "https://json-schema.org/draft-07/schema", - "title": "Cosmos Chain", - "description": "Cosmos Chain.json is a metadata file that contains information about a cosmos sdk based chain.", - "type": "object", - "required": [ - "chain_name", - "chain_id", - "bech32_prefix" - ], - "properties": { - "$schema": { - "type": "string", - "pattern": "^(\\.\\./)+chain\\.schema\\.json$" - }, - "chain_name": { - "type": "string", - "pattern": "[a-z0-9]+" - }, - "chain_id": { - "type": "string" - }, - "pre_fork_chain_name": { - "type": "string", - "pattern": "[a-z0-9]+" - }, - "pretty_name": { - "type": "string" - }, - "website": { - "type": "string", - "format": "uri" - }, - "update_link": { - "type": "string", - "format": "uri" - }, - "status": { - "enum": [ - "live", - "upcoming", - "killed" - ] - }, - "network_type": { - "enum": [ - "mainnet", - "testnet", - "devnet" - ] - }, - "bech32_prefix": { - "type": "string", - "description": "The default prefix for the human-readable part of addresses that identifies the coin type. Must be registered with SLIP-0173. E.g., 'cosmos'" - }, - "bech32_config": { - "type": "object", - "description": "Used to override the bech32_prefix for specific uses.", - "properties": { - "bech32PrefixAccAddr": { - "type": "string", - "description": "e.g., 'cosmos'" - }, - "bech32PrefixAccPub": { - "type": "string", - "description": "e.g., 'cosmospub'" - }, - "bech32PrefixValAddr": { - "type": "string", - "description": "e.g., 'cosmosvaloper'" - }, - "bech32PrefixValPub": { - "type": "string", - "description": "e.g., 'cosmosvaloperpub'" - }, - "bech32PrefixConsAddr": { - "type": "string", - "description": "e.g., 'cosmosvalcons'" - }, - "bech32PrefixConsPub": { - "type": "string", - "description": "e.g., 'cosmosvalconspub'" - } - }, - "additionalProperties": false, - "minProperties": 1 - }, - "daemon_name": { - "type": "string" - }, - "node_home": { - "type": "string" - }, - "key_algos": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "secp256k1", - "ethsecp256k1", - "ed25519", - "sr25519", - "bn254" - ], - "uniqueItems": true - } - }, - "slip44": { - "type": "number" - }, - "alternative_slip44s": { - "type": "array", - "items": { - "type": "number" - } - }, - "fees": { - "type": "object", - "required": [ - "fee_tokens" - ], - "properties": { - "fee_tokens": { - "type": "array", - "items": { - "$ref": "#/$defs/fee_token" - } - } - }, - "additionalProperties": false - }, - "staking": { - "type": "object", - "required": [ - "staking_tokens" - ], - "properties": { - "staking_tokens": { - "type": "array", - "items": { - "$ref": "#/$defs/staking_token" - } - }, - "lock_duration": { - "type": "object", - "properties": { - "blocks": { - "type": "number", - "description": "The number of blocks for which the staked tokens are locked." - }, - "time": { - "type": "string", - "description": "The approximate time for which the staked tokens are locked." - } - }, - "additionalProperties": false, - "minProperties": 1 - } - }, - "additionalProperties": false - }, - "codebase": { - "type": "object", - "properties": { - "git_repo": { - "type": "string", - "format": "uri" - }, - "recommended_version": { - "type": "string" - }, - "go_version": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$", - "description": "Minimum accepted go version to build the binary." - }, - "compatible_versions": { - "type": "array", - "items": { - "type": "string" - } - }, - "binaries": { - "type": "object", - "properties": { - "linux/amd64": { - "type": "string", - "format": "uri" - }, - "linux/arm64": { - "type": "string", - "format": "uri" - }, - "darwin/amd64": { - "type": "string", - "format": "uri" - }, - "darwin/arm64": { - "type": "string", - "format": "uri" - }, - "windows/amd64": { - "type": "string", - "format": "uri" - }, - "windows/arm64": { - "type": "string", - "format": "uri" - } - }, - "additionalProperties": false - }, - "cosmos_sdk_version": { - "type": "string" - }, - "consensus": { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "tendermint", - "cometbft", - "sei-tendermint" - ] - }, - "version": { - "type": "string" - } - }, - "additionalProperties": false - }, - "cosmwasm_version": { - "type": "string" - }, - "cosmwasm_enabled": { - "type": "boolean" - }, - "cosmwasm_path": { - "type": "string", - "description": "Relative path to the cosmwasm directory. ex. $HOME/.juno/data/wasm", - "pattern": "^\\$HOME.*$" - }, - "ibc_go_version": { - "type": "string" - }, - "ics_enabled": { - "type": "array", - "description": "List of IBC apps (usually corresponding to a ICS standard) which have been enabled on the network.", - "items": { - "type": "string", - "description": "IBC app or ICS standard.", - "enum": [ - "ics20-1", - "ics27-1", - "mauth" - ] - } - }, - "genesis": { - "type": "object", - "required": [ - "genesis_url" - ], - "properties": { - "name": { - "type": "string" - }, - "genesis_url": { - "type": "string", - "format": "uri" - }, - "ics_ccv_url": { - "type": "string", - "format": "uri" - } - }, - "additionalProperties": false - }, - "versions": { - "type": "array", - "items": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string", - "description": "Official Upgrade Name" - }, - "tag": { - "type": "string", - "description": "Git Upgrade Tag" - }, - "height": { - "type": "number", - "description": "Block Height" - }, - "proposal": { - "type": "number", - "description": "Proposal that will officially signal community acceptance of the upgrade." - }, - "previous_version_name": { - "type": "string", - "description": "[Optional] Name of the previous version" - }, - "next_version_name": { - "type": "string", - "description": "[Optional] Name of the following version" - }, - "recommended_version": { - "type": "string" - }, - "go_version": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$", - "description": "Minimum accepted go version to build the binary." - }, - "compatible_versions": { - "type": "array", - "items": { - "type": "string" - } - }, - "cosmos_sdk_version": { - "type": "string" - }, - "consensus": { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "tendermint", - "cometbft", - "sei-tendermint" - ] - }, - "version": { - "type": "string" - } - }, - "additionalProperties": false - }, - "cosmwasm_version": { - "type": "string" - }, - "cosmwasm_enabled": { - "type": "boolean" - }, - "cosmwasm_path": { - "type": "string", - "description": "Relative path to the cosmwasm directory. ex. $HOME/.juno/data/wasm", - "pattern": "^\\$HOME.*$" - }, - "ibc_go_version": { - "type": "string" - }, - "ics_enabled": { - "type": "array", - "description": "List of IBC apps (usually corresponding to a ICS standard) which have been enabled on the network.", - "items": { - "type": "string", - "description": "IBC app or ICS standard.", - "enum": [ - "ics20-1", - "ics27-1", - "mauth" - ] - } - }, - "binaries": { - "type": "object", - "properties": { - "linux/amd64": { - "type": "string", - "format": "uri" - }, - "linux/arm64": { - "type": "string", - "format": "uri" - }, - "darwin/amd64": { - "type": "string", - "format": "uri" - }, - "darwin/arm64": { - "type": "string", - "format": "uri" - }, - "windows/amd64": { - "type": "string", - "format": "uri" - }, - "windows/arm64": { - "type": "string", - "format": "uri" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - }, - "images": { - "type": "array", - "items": { - "type": "object", - "properties": { - "image_sync": { - "$ref": "#/$defs/pointer" - }, - "png": { - "type": "string", - "format": "uri-reference", - "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$" - }, - "svg": { - "type": "string", - "format": "uri-reference", - "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$" - }, - "theme": { - "type": "object", - "properties": { - "primary_color_hex": { - "type": "string", - "pattern": "^#[0-9a-fA-F]{6}$" - }, - "circle": { - "type": "boolean" - }, - "dark_mode": { - "type": "boolean" - } - }, - "minProperties": 1, - "additionalProperties": false - }, - "layout": { - "type": "string", - "enum": ["logo", "logomark", "logotype"], - "description": "logomark == icon only; logotype == text only; logo == icon + text." - }, - "text_position": { - "type": "string", - "enum": ["top", "bottom", "left", "right", "integrated"], - "description": "Indicates in which position the text is placed, in case the layout is 'icon' type, it's required only in this case." - } - }, - "if": { - "properties": { - "layout": { "const": "logo" } - }, - "required": ["layout"] - }, - "then": { - "required": ["text_position"] - }, - "anyOf": [ - { - "required": ["png"] - }, - { - "required": ["svg"] - } - ], - "additionalProperties": false - } - }, - "logo_URIs": { - "type": "object", - "properties": { - "png": { - "type": "string", - "format": "uri-reference", - "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$" - }, - "svg": { - "type": "string", - "format": "uri-reference", - "pattern": "^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$" - } - }, - "additionalProperties": false - }, - "description": { - "type": "string", - "maxLength": 3000 - }, - "peers": { - "type": "object", - "properties": { - "seeds": { - "type": "array", - "items": { - "$ref": "#/$defs/peer" - } - }, - "persistent_peers": { - "type": "array", - "items": { - "$ref": "#/$defs/peer" - } - } - }, - "additionalProperties": false - }, - "apis": { - "type": "object", - "properties": { - "rpc": { - "type": "array", - "items": { - "$ref": "#/$defs/endpoint" - } - }, - "rest": { - "type": "array", - "items": { - "$ref": "#/$defs/endpoint" - } - }, - "grpc": { - "type": "array", - "items": { - "$ref": "#/$defs/endpoint" - } - }, - "wss": { - "type": "array", - "items": { - "$ref": "#/$defs/endpoint" - } - }, - "grpc-web": { - "type": "array", - "items": { - "$ref": "#/$defs/endpoint" - } - }, - "evm-http-jsonrpc": { - "type": "array", - "items": { - "$ref": "#/$defs/endpoint" - } - } - }, - "additionalProperties": false - }, - "explorers": { - "type": "array", - "items": { - "$ref": "#/$defs/explorer" - } - }, - "keywords": { - "type": "array", - "maxContains": 20, - "items": { - "type": "string" - } - }, - "extra_codecs": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "ethermint", - "injective" - ], - "uniqueItems": true - } - } - }, - "additionalProperties": false, - "$defs": { - "peer": { - "type": "object", - "required": [ - "id", - "address" - ], - "properties": { - "id": { - "type": "string" - }, - "address": { - "type": "string" - }, - "provider": { - "type": "string" - } - }, - "additionalProperties": false - }, - "endpoint": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - }, - "provider": { - "type": "string" - }, - "archive": { - "type": "boolean", - "default": false - } - }, - "additionalProperties": false - }, - "explorer": { - "type": "object", - "properties": { - "kind": { - "type": "string" - }, - "url": { - "type": "string" - }, - "tx_page": { - "type": "string" - }, - "account_page": { - "type": "string" - } - }, - "additionalProperties": false - }, - "fee_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - }, - "fixed_min_gas_price": { - "type": "number" - }, - "low_gas_price": { - "type": "number" - }, - "average_gas_price": { - "type": "number" - }, - "high_gas_price": { - "type": "number" - }, - "gas_costs": { - "type": "object", - "properties": { - "cosmos_send": { - "type": "number" - }, - "ibc_transfer": { - "type": "number" - } - }, - "additionalProperties": false, - "minProperties": 1 - } - }, - "additionalProperties": false - }, - "staking_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - }, - "pointer": { - "type": "object", - "description": "The (primary) key used to identify an object within the Chain Registry.", - "required": [ - "chain_name" - ], - "properties": { - "chain_name": { - "type": "string", - "description": "The chain name or platform from which the object resides. E.g., 'cosmoshub', 'ethereum', 'forex', or 'nasdaq'" - }, - "base_denom": { - "type": "string", - "description": "The base denom of the asset from which the object originates. E.g., when describing ATOM from Cosmos Hub, specify 'uatom', NOT 'atom' nor 'ATOM'; base units are unique per platform." - } - }, - "additionalProperties": false - } - } - }; - -it('works', () => { - expect(generateTypeScript(exampleSchema as any)).toMatchSnapshot(); +it('chain', () => { + expect(generateTypeScript(chain as any)).toMatchSnapshot(); }); diff --git a/src/index.ts b/src/index.ts index 738e760..67359e2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,25 +19,76 @@ type JSONSchemaProperty = { $ref?: string; }; -export function generateTypeScript(schema: JSONSchema): string { +interface SchemaTSOptions { + useSingleQuotes: boolean; +} + +interface SchemaTSContextI { + options: SchemaTSOptions; + root: JSONSchema; + schema: JSONSchema; + parents: JSONSchema[] +} + + +class SchemaTSContext implements SchemaTSContextI { + options: SchemaTSOptions; + root: JSONSchema; + schema: JSONSchema; + parents: JSONSchema[]; + + constructor( + options: SchemaTSOptions, + root: JSONSchema, + schema: JSONSchema, + parents: JSONSchema[] = [] + ) { + this.options = options; + this.schema = schema; + this.root = root; + this.parents = parents; + } + + // Clone the context with the option to add a new parent + clone(newParent: JSONSchema): Context { + // Create a new array for parents to avoid mutation of the original array + const newParents = [...this.parents, newParent]; + return new SchemaTSContext(this.options, this.root, this.schema, newParents); + } +} + +const defaultOptions: SchemaTSOptions = { useSingleQuotes: true }; + +export function generateTypeScript(schema: JSONSchema, options?: SchemaTSOptions): string { const interfaces = []; - // Process definitions first - if (schema.$defs) { - for (const key in schema.$defs) { - interfaces.push(createInterfaceDeclaration(toPascalCase(key), schema.$defs[key])); + const opts = options || defaultOptions; + const ctx = new SchemaTSContext(opts, schema); + + try { + + // Process definitions first + if (schema.$defs) { + for (const key in schema.$defs) { + // asset requires denom, but is also a definition. + // maybe we just need to do a two-pass and register + interfaces.push(createInterfaceDeclaration(ctx, toPascalCase(key), schema.$defs[key])); + } } + } catch (e) { + console.error('Error processing interfaces'); + throw e; } // Process the main schema - interfaces.push(createInterfaceDeclaration(toPascalCase(schema.title), schema)); + interfaces.push(createInterfaceDeclaration(ctx, toPascalCase(schema.title), schema)); return generate(t.file(t.program(interfaces))).code; } -function createInterfaceDeclaration(name: string, schema: JSONSchema): t.TSInterfaceDeclaration { +function createInterfaceDeclaration(ctx: SchemaTSContext, name: string, schema: JSONSchema): t.TSInterfaceDeclaration { const properties = schema.properties || {}; const required = schema.required || []; const body = Object.keys(properties).map(key => { const prop = properties[key]; - return createPropertySignature(key, prop, required, schema); + return createPropertySignature(ctx, key, prop, required, schema); }); return t.tsInterfaceDeclaration( @@ -48,26 +99,27 @@ function createInterfaceDeclaration(name: string, schema: JSONSchema): t.TSInter ); } +// Determine if the key is a valid JavaScript identifier +function isValidIdentifier(key) { + return /^[$A-Z_][0-9A-Z_$]*$/i.test(key) && !/^[0-9]+$/.test(key); +} + function createPropertySignature( + ctx: SchemaTSContext, key: string, prop: JSONSchemaProperty, required: string[], - schema: JSONSchema, - useSingleQuotes = false // Add a new parameter with a default value + schema: JSONSchema ): t.TSPropertySignature { - // Helper function to determine if the key is a valid JavaScript identifier - function isValidIdentifier(key) { - return /^[$A-Z_][0-9A-Z_$]*$/i.test(key) && !/^[0-9]+$/.test(key); - } // Adjust the quoting style based on the useSingleQuotes flag - function formatKey(key) { - const quote = useSingleQuotes ? "'" : '"'; + function formatKey(ctx: SchemaTSContext) { + const quote = ctx.options.useSingleQuotes ? "'" : '"'; return `${quote}${key}${quote}`; } - const propType = getTypeForProp(prop, required, schema); - const identifier = isValidIdentifier(key) ? t.identifier(key) : t.stringLiteral(formatKey(key)); + const propType = getTypeForProp(ctx, prop, required, schema); + const identifier = isValidIdentifier(key) ? t.identifier(key) : t.stringLiteral(formatKey(ctx, key)); const propSig = t.tsPropertySignature( identifier, t.tsTypeAnnotation(propType) @@ -77,9 +129,9 @@ function createPropertySignature( } -function getTypeForProp(prop: JSONSchemaProperty, required: string[], schema: JSONSchema): t.TSType { +function getTypeForProp(ctx: SchemaTSContext, prop: JSONSchemaProperty, required: string[], schema: JSONSchema): t.TSType { if (prop.$ref) { - return resolveRefType(prop.$ref, schema); + return resolveRefType(ctx, prop.$ref, schema); } switch (prop.type) { @@ -92,7 +144,7 @@ function getTypeForProp(prop: JSONSchemaProperty, required: string[], schema: JS return t.tsBooleanKeyword(); case 'array': if (prop.items) { - return t.tsArrayType(getTypeForProp(prop.items, required, schema)); + return t.tsArrayType(getTypeForProp(ctx, prop.items, required, schema)); } else { throw new Error('Array items specification is missing'); } @@ -102,7 +154,7 @@ function getTypeForProp(prop: JSONSchemaProperty, required: string[], schema: JS const nestedRequired = prop.required || []; const typeElements = Object.keys(nestedProperties).map(nestedKey => { const nestedProp = nestedProperties[nestedKey]; - return createPropertySignature(nestedKey, nestedProp, nestedRequired, schema); + return createPropertySignature(ctx, nestedKey, nestedProp, nestedRequired, schema); }); return t.tsTypeLiteral(typeElements); } else { @@ -113,15 +165,24 @@ function getTypeForProp(prop: JSONSchemaProperty, required: string[], schema: JS } } -function resolveRefType(ref: string, schema: JSONSchema): t.TSType { +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))); } + // now look in root + if (definitionName && ctx.root.$defs && ctx.root.$defs[definitionName]) { + return t.tsTypeReference(t.identifier(toPascalCase(definitionName))); + } + throw new Error(`Reference ${ref} not found in definitions.`); } function toPascalCase(str: string): string { return str.replace(/\w+/g, (w) => w[0].toUpperCase() + w.slice(1).toLowerCase()).replace(/_/g, ''); } + +function toCamelCase(key: string): string { + return key.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : ''); +}