inspect.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { flags } from '@oclif/command'
  2. import { CLIError } from '@oclif/errors'
  3. import { displayNameValueTable } from '../../helpers/display'
  4. import { ApiPromise } from '@polkadot/api'
  5. import { Codec } from '@polkadot/types/types'
  6. import { ConstantCodec } from '@polkadot/metadata/Decorated/consts/types'
  7. import ExitCodes from '../../ExitCodes'
  8. import chalk from 'chalk'
  9. import { NameValueObj, ApiMethodArg } from '../../Types'
  10. import ApiCommandBase from '../../base/ApiCommandBase'
  11. // Command flags type
  12. type ApiInspectFlags = {
  13. type: string
  14. module: string
  15. method: string
  16. exec: boolean
  17. callArgs: string
  18. }
  19. // Currently "inspectable" api types
  20. const TYPES_AVAILABLE = ['query', 'consts'] as const
  21. // String literals type based on TYPES_AVAILABLE const.
  22. // It works as if we specified: type ApiType = 'query' | 'consts'...;
  23. type ApiType = typeof TYPES_AVAILABLE[number]
  24. export default class ApiInspect extends ApiCommandBase {
  25. static description =
  26. 'Lists available node API modules/methods and/or their description(s), ' +
  27. 'or calls one of the API methods (depending on provided arguments and flags)'
  28. static examples = [
  29. '$ api:inspect',
  30. '$ api:inspect -t=query',
  31. '$ api:inspect -t=query -M=members',
  32. '$ api:inspect -t=query -M=members -m=membershipById',
  33. '$ api:inspect -t=query -M=members -m=membershipById -e',
  34. '$ api:inspect -t=query -M=members -m=membershipById -e -a=1',
  35. ]
  36. static flags = {
  37. type: flags.string({
  38. char: 't',
  39. description:
  40. 'Specifies the type/category of the inspected request (ie. "query", "consts" etc.).\n' +
  41. 'If no "--module" flag is provided then all available modules in that type will be listed.\n' +
  42. 'If this flag is not provided then all available types will be listed.',
  43. }),
  44. module: flags.string({
  45. char: 'M',
  46. description:
  47. 'Specifies the api module, ie. "system", "staking" etc.\n' +
  48. 'If no "--method" flag is provided then all methods in that module will be listed along with the descriptions.',
  49. dependsOn: ['type'],
  50. }),
  51. method: flags.string({
  52. char: 'm',
  53. description: 'Specifies the api method to call/describe.',
  54. dependsOn: ['module'],
  55. }),
  56. exec: flags.boolean({
  57. char: 'e',
  58. description:
  59. 'Provide this flag if you want to execute the actual call, instead of displaying the method description (which is default)',
  60. dependsOn: ['method'],
  61. }),
  62. callArgs: flags.string({
  63. char: 'a',
  64. description:
  65. 'Specifies the arguments to use when calling a method. Multiple arguments can be separated with a comma, ie. "-a=arg1,arg2".\n' +
  66. 'You can omit this flag even if the method requires some aguments.\n' +
  67. 'In that case you will be promted to provide value for each required argument.\n' +
  68. "Ommiting this flag is recommended when input parameters are of more complex types (and it's hard to specify them as just simple comma-separated strings)",
  69. dependsOn: ['exec'],
  70. }),
  71. }
  72. getMethodMeta(apiType: ApiType, apiModule: string, apiMethod: string) {
  73. if (apiType === 'query') {
  74. return this.getOriginalApi().query[apiModule][apiMethod].creator.meta
  75. } else {
  76. // Currently the only other optoin is api.consts
  77. const method: ConstantCodec = this.getOriginalApi().consts[apiModule][apiMethod] as ConstantCodec
  78. return method.meta
  79. }
  80. }
  81. getMethodDescription(apiType: ApiType, apiModule: string, apiMethod: string): string {
  82. const description: string = this.getMethodMeta(apiType, apiModule, apiMethod).documentation.join(' ')
  83. return description || 'No description available.'
  84. }
  85. getQueryMethodParamsTypes(apiModule: string, apiMethod: string): string[] {
  86. const method = this.getOriginalApi().query[apiModule][apiMethod]
  87. const { type } = method.creator.meta
  88. if (type.isDoubleMap) {
  89. return [type.asDoubleMap.key1.toString(), type.asDoubleMap.key2.toString()]
  90. }
  91. if (type.isMap) {
  92. return [type.asMap.key.toString()]
  93. }
  94. return []
  95. }
  96. getMethodReturnType(apiType: ApiType, apiModule: string, apiMethod: string): string {
  97. if (apiType === 'query') {
  98. const method = this.getOriginalApi().query[apiModule][apiMethod]
  99. const {
  100. meta: { type, modifier },
  101. } = method.creator
  102. let typeName = type.toString()
  103. if (type.isDoubleMap) {
  104. typeName = type.asDoubleMap.value.toString()
  105. }
  106. if (type.isMap) {
  107. typeName = type.asMap.value.toString()
  108. }
  109. return modifier.isOptional ? `Option<${typeName}>` : typeName
  110. }
  111. // Fallback for "consts"
  112. return this.getMethodMeta(apiType, apiModule, apiMethod).type.toString()
  113. }
  114. // Validate the flags - throws an error if flags.type, flags.module or flags.method is invalid / does not exist in the api.
  115. // Returns type, module and method which validity we can be sure about (notice they may still be "undefined" if weren't provided).
  116. validateFlags(
  117. api: ApiPromise,
  118. flags: ApiInspectFlags
  119. ): { apiType: ApiType | undefined; apiModule: string | undefined; apiMethod: string | undefined } {
  120. let apiType: ApiType | undefined
  121. const { module: apiModule, method: apiMethod } = flags
  122. if (flags.type !== undefined) {
  123. const availableTypes: readonly string[] = TYPES_AVAILABLE
  124. if (!availableTypes.includes(flags.type)) {
  125. throw new CLIError('Such type is not available', { exit: ExitCodes.InvalidInput })
  126. }
  127. apiType = flags.type as ApiType
  128. if (apiModule !== undefined) {
  129. if (!api[apiType][apiModule]) {
  130. throw new CLIError('Such module was not found', { exit: ExitCodes.InvalidInput })
  131. }
  132. if (apiMethod !== undefined && !api[apiType][apiModule][apiMethod]) {
  133. throw new CLIError('Such method was not found', { exit: ExitCodes.InvalidInput })
  134. }
  135. }
  136. }
  137. return { apiType, apiModule, apiMethod }
  138. }
  139. // Request values for params using array of param types (strings)
  140. async requestParamsValues(paramTypes: string[]): Promise<ApiMethodArg[]> {
  141. const result: ApiMethodArg[] = []
  142. for (const [key, paramType] of Object.entries(paramTypes)) {
  143. this.log(chalk.bold.white(`Parameter no. ${parseInt(key) + 1} (${paramType}):`))
  144. const paramValue = await this.promptForParam(paramType)
  145. result.push(paramValue)
  146. }
  147. return result
  148. }
  149. async run() {
  150. const api: ApiPromise = this.getOriginalApi()
  151. const flags: ApiInspectFlags = this.parse(ApiInspect).flags as ApiInspectFlags
  152. const availableTypes: readonly string[] = TYPES_AVAILABLE
  153. const { apiType, apiModule, apiMethod } = this.validateFlags(api, flags)
  154. // Executing a call
  155. if (apiType && apiModule && apiMethod && flags.exec) {
  156. let result: Codec
  157. if (apiType === 'query') {
  158. // Api query - call with (or without) arguments
  159. let args: (string | ApiMethodArg)[] = flags.callArgs ? flags.callArgs.split(',') : []
  160. const paramsTypes: string[] = this.getQueryMethodParamsTypes(apiModule, apiMethod)
  161. if (args.length < paramsTypes.length) {
  162. this.warn('Some parameters are missing! Please, provide the missing parameters:')
  163. const missingParamsValues = await this.requestParamsValues(paramsTypes.slice(args.length))
  164. args = args.concat(missingParamsValues)
  165. }
  166. result = await api.query[apiModule][apiMethod](...args)
  167. } else {
  168. // Api consts - just assign the value
  169. result = api.consts[apiModule][apiMethod]
  170. }
  171. this.log(chalk.green(result.toString()))
  172. }
  173. // Describing a method
  174. else if (apiType && apiModule && apiMethod) {
  175. this.log(chalk.bold.white(`${apiType}.${apiModule}.${apiMethod}`))
  176. const description: string = this.getMethodDescription(apiType, apiModule, apiMethod)
  177. this.log(`\n${description}\n`)
  178. const typesRows: NameValueObj[] = []
  179. if (apiType === 'query') {
  180. typesRows.push({
  181. name: 'Params:',
  182. value: this.getQueryMethodParamsTypes(apiModule, apiMethod).join(', ') || '-',
  183. })
  184. }
  185. typesRows.push({ name: 'Returns:', value: this.getMethodReturnType(apiType, apiModule, apiMethod) })
  186. displayNameValueTable(typesRows)
  187. }
  188. // Displaying all available methods
  189. else if (apiType && apiModule) {
  190. const module = api[apiType][apiModule]
  191. const rows: NameValueObj[] = Object.keys(module).map((key: string) => {
  192. return { name: key, value: this.getMethodDescription(apiType, apiModule, key) }
  193. })
  194. displayNameValueTable(rows)
  195. }
  196. // Displaying all available modules
  197. else if (apiType) {
  198. this.log(chalk.bold.white('Available modules:'))
  199. this.log(
  200. Object.keys(api[apiType])
  201. .map((key) => chalk.white(key))
  202. .join('\n')
  203. )
  204. }
  205. // Displaying all available types
  206. else {
  207. this.log(chalk.bold.white('Available types:'))
  208. this.log(availableTypes.map((type) => chalk.white(type)).join('\n'))
  209. }
  210. }
  211. }