From 9ce590954359820a159704d7f45049f64faf7834 Mon Sep 17 00:00:00 2001 From: 170210 Date: Tue, 1 Aug 2023 17:59:26 +0900 Subject: [PATCH 01/11] chore: create an automatic error document generation module Signed-off-by: 170210 --- tools/error_doc/const_info.go | 18 +++++ tools/error_doc/error_info.go | 98 +++++++++++++++++++++++ tools/error_doc/main.go | 142 +++++++++++++++++++++++++++++++++ tools/error_doc/module_info.go | 52 ++++++++++++ 4 files changed, 310 insertions(+) create mode 100644 tools/error_doc/const_info.go create mode 100644 tools/error_doc/error_info.go create mode 100644 tools/error_doc/main.go create mode 100644 tools/error_doc/module_info.go diff --git a/tools/error_doc/const_info.go b/tools/error_doc/const_info.go new file mode 100644 index 0000000000..12d6cbd6b4 --- /dev/null +++ b/tools/error_doc/const_info.go @@ -0,0 +1,18 @@ +package main + +import ( + "errors" + "strings" +) + +func getConst(line string) (string, string, error) { + line = strings.Replace(line, "const", "", 1) + parts := strings.Split(line, "=") + if len(parts) == 2 { + i := strings.TrimSpace(parts[0]) + val := strings.Trim(strings.TrimSpace(parts[1]), `"`) + return i, val, nil + } else { + return "", "", errors.New("failed to get the value in: " + line) + } +} diff --git a/tools/error_doc/error_info.go b/tools/error_doc/error_info.go new file mode 100644 index 0000000000..37a80f50b4 --- /dev/null +++ b/tools/error_doc/error_info.go @@ -0,0 +1,98 @@ +package main + +import ( + "bufio" + "errors" + "fmt" + "os" + "regexp" + "strings" +) + +type errorInfo struct { + errorName string + codeSpace string + code string + description string +} + +func (err errorInfo) toString(moduleName string) (string, error) { + errorInfoTemplate := "|%s|%s|%s|%s|\n" + if err.codeSpace == "ModuleName" { + if moduleName == "" { + return "", errors.New("failed to find moduleName") + } else { + return fmt.Sprintf(errorInfoTemplate, err.errorName, moduleName, err.code, err.description), nil + } + } else { + return fmt.Sprintf(errorInfoTemplate, err.errorName, err.codeSpace, err.code, err.description), nil + } +} + +func addError(line string, errorDict map[string]string) (errorInfo, error) { + parts := strings.SplitN(line, "=", 2) + errName := strings.TrimSpace(parts[0]) + errBody := strings.TrimSpace(parts[1]) + // error info is like as sdkerrors.Register(...) + pattern := regexp.MustCompile(`sdkerrors\.Register\((.*)\)`) + match := pattern.FindStringSubmatch(errBody) + + if len(match) == 2 { + parts := strings.SplitN(match[1], ",", 3) + + if len(parts) == 3 { + codeSpace := strings.TrimSpace(parts[0]) + code := strings.TrimSpace(parts[1]) + description := strings.Trim(strings.TrimSpace(parts[2]), `"`) + + if constValue, found := errorDict[codeSpace]; found { + codeSpace = constValue + } + + return errorInfo{ + errorName: errName, + codeSpace: codeSpace, + code: code, + description: description, + }, nil + } else { + return errorInfo{}, errors.New("failed to get error info in: " + line) + } + } else { + return errorInfo{}, errors.New("failed to parse error info in: " + line) + } + +} + +func getErrors(p string) ([]errorInfo, error) { + var errorDict []errorInfo + constDict := make(map[string]string) + + file, err := os.Open(p) + if err != nil { + return nil, err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + // get const + if strings.Contains(line, "=") { + if !strings.Contains(line, "sdkerrors.Register") { + identifier, value, err := getConst(line) + if err != nil { + return nil, err + } + constDict[identifier] = value + } else { + errInfo, err := addError(line, constDict) + if err != nil { + return nil, err + } + errorDict = append(errorDict, errInfo) + } + } + } + return errorDict, nil +} diff --git a/tools/error_doc/main.go b/tools/error_doc/main.go new file mode 100644 index 0000000000..413e1ae5b0 --- /dev/null +++ b/tools/error_doc/main.go @@ -0,0 +1,142 @@ +package main + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "sort" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +func findFilesWithName(startPath, fileName string) ([]string, error) { + var foundFiles []string + + err := filepath.Walk(startPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() && info.Name() == fileName { + foundFiles = append(foundFiles, path) + } + return nil + }) + if err != nil { + return nil, err + } + + return foundFiles, nil +} + +func findModuleWithFiles(targetPath string) (map[string][]string, []string, error) { + + // get all errors.go in x folder + errorFile := "errors.go" + filePaths, err := findFilesWithName(targetPath, errorFile) + if len(filePaths) == 0 || err != nil { + return nil, nil, errors.New("Not find target files in x folder") + } + + // get each module name and bind it to paths (one module may have multiple errors.go) + moduleWithPaths := make(map[string][]string) + for _, filePath := range filePaths { + moduleName := findModuleName(filePath) + if moduleName == "" { + return nil, nil, errors.New("Failed to get module name for " + filePath) + } + moduleWithPaths[moduleName] = append(moduleWithPaths[moduleName], filePath) + } + + // sort keys and filepaths + var modules []string + for moduleName := range moduleWithPaths { + modules = append(modules, moduleName) + sort.Strings(moduleWithPaths[moduleName]) + } + sort.Strings(modules) + + return moduleWithPaths, modules, nil +} + +func autoGenerate(targetPath string, moduleWithPaths map[string][]string, modules []string) error { + filePath := targetPath + "/ERRORS.md" + file, err := os.Create(filePath) + if err != nil { + return err + } + defer file.Close() + + // generate category + file.WriteString("\n") + file.WriteString("# Category\n") + columnTemplate := " * [%s](#%s)\n" + for _, moduleName := range modules { + file.WriteString(fmt.Sprintf(columnTemplate, cases.Title(language.Und).String(moduleName), moduleName)) + } + file.WriteString("\n") + + extraInfoTemplate := " * [%s](%s)\n" + // errors in each module + for _, moduleName := range modules { + + // table header + file.WriteString("\n") + file.WriteString("## " + cases.Title(language.Und).String(moduleName) + "\n") + file.WriteString("\n") + file.WriteString("|Error Name|Codespace|Code|Description|\n") + file.WriteString("|:-|:-|:-|:-|\n") + + filePaths := moduleWithPaths[moduleName] + for _, filePath := range filePaths { + errDict, err := getErrors(filePath) + if err != nil { + return err + } + moduleName, err := getModuleNameValue(filePath) + if err != nil { + return err + } + for _, errInfo := range errDict { + column, err := errInfo.toString(moduleName) + if err != nil { + return err + } + file.WriteString(column) + } + } + + file.WriteString("\n>You can also find detailed information in the following Errors.go files:\n") + for _, filePath := range filePaths { + relPath, err := filepath.Rel(targetPath, filePath) + if err != nil { + return err + } + file.WriteString(fmt.Sprintf(extraInfoTemplate, relPath, relPath)) + } + + } + return nil +} + +func main() { + currentPath, err := os.Getwd() + if err != nil { + fmt.Println("Error getting current directory:", err) + os.Exit(1) + } + targetPath := filepath.Join(currentPath, "..", "..", "x") + + moduleWithPaths, modules, err := findModuleWithFiles(targetPath) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := autoGenerate(targetPath, moduleWithPaths, modules); err != nil { + fmt.Println(err) + os.Exit(1) + } + +} diff --git a/tools/error_doc/module_info.go b/tools/error_doc/module_info.go new file mode 100644 index 0000000000..f5ef3073a4 --- /dev/null +++ b/tools/error_doc/module_info.go @@ -0,0 +1,52 @@ +package main + +import ( + "bufio" + "errors" + "os" + "strings" +) + +func findModuleName(s string) string { + startIndex := strings.Index(s, "/x/") + len("/x/") + endIndex := strings.Index(s[startIndex:], "/") + + if startIndex != -1 && endIndex != -1 { + return s[startIndex : startIndex+endIndex] + } + return "" +} + +func getModuleNameValue(filePath string) (string, error) { + possibleFileNames := []string{"keys.go", "key.go"} + var keyFilePath string + for _, fileName := range possibleFileNames { + paramPath := strings.Replace(filePath, "errors.go", fileName, 1) + if _, err := os.Stat(paramPath); err == nil { + keyFilePath = paramPath + break + } + } + + if keyFilePath != "" { + file, err := os.Open(keyFilePath) + if err != nil { + return "", errors.New(keyFilePath + " cannot be opened") + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + // get module name + if strings.Contains(line, "ModuleName = ") { + _, val, err := getConst(line) + if err != nil { + return "", err + } + return val, nil + } + } + } + + return "", nil +} From d22b47dd2b000aff8085d30c5c22d80631173c48 Mon Sep 17 00:00:00 2001 From: 170210 Date: Tue, 1 Aug 2023 18:00:32 +0900 Subject: [PATCH 02/11] docs: generate ERRORS.md for x/module Signed-off-by: 170210 --- x/ERRORS.md | 298 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 x/ERRORS.md diff --git a/x/ERRORS.md b/x/ERRORS.md new file mode 100644 index 0000000000..32ed9ca1b2 --- /dev/null +++ b/x/ERRORS.md @@ -0,0 +1,298 @@ + +# Category + * [Authz](#authz) + * [Bank](#bank) + * [Capability](#capability) + * [Collection](#collection) + * [Crisis](#crisis) + * [Distribution](#distribution) + * [Evidence](#evidence) + * [Feegrant](#feegrant) + * [Foundation](#foundation) + * [Gov](#gov) + * [Params](#params) + * [Slashing](#slashing) + * [Staking](#staking) + * [Token](#token) + + +## Authz + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrInvalidExpirationTime|authz|3|expiration time of authorization should be more than current time| + +>You can also find detailed information in the following Errors.go files: + * [authz/errors.go](authz/errors.go) + +## Bank + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrNoInputs|bank|2|no inputs to send transaction| +|ErrNoOutputs|bank|3|no outputs to send transaction| +|ErrInputOutputMismatch|bank|4|sum inputs != sum outputs| +|ErrSendDisabled|bank|5|send transactions are disabled| +|ErrDenomMetadataNotFound|bank|6|client denom metadata not found| +|ErrInvalidKey|bank|7|invalid key| + +>You can also find detailed information in the following Errors.go files: + * [bank/types/errors.go](bank/types/errors.go) + +## Capability + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrInvalidCapabilityName|capability|2|capability name not valid| +|ErrNilCapability|capability|3|provided capability is nil| +|ErrCapabilityTaken|capability|4|capability name already taken| +|ErrOwnerClaimed|capability|5|given owner already claimed capability| +|ErrCapabilityNotOwned|capability|6|capability not owned by module| +|ErrCapabilityNotFound|capability|7|capability not found| +|ErrCapabilityOwnersNotFound|capability|8|owners not found for capability| + +>You can also find detailed information in the following Errors.go files: + * [capability/types/errors.go](capability/types/errors.go) + +## Collection + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrTokenNotExist|collection|2|token symbol, token-id does not exist| +|ErrTokenNotMintable|collection|3|token symbol, token-id is not mintable| +|ErrInvalidTokenName|collection|4|token name should not be empty| +|ErrInvalidTokenID|collection|5|invalid token id| +|ErrInvalidTokenDecimals|collection|6|token decimals should be within the range in 0 ~ 18| +|ErrInvalidIssueFT|collection|7|Issuing token with amount[1], decimals[0], mintable[false] prohibited. Issue nft token instead.| +|ErrInvalidAmount|collection|8|invalid token amount| +|ErrInvalidBaseImgURILength|collection|9|invalid base_img_uri length| +|ErrInvalidNameLength|collection|10|invalid name length| +|ErrInvalidTokenType|collection|11|invalid token type pattern found| +|ErrInvalidTokenIndex|collection|12|invalid token index pattern found| +|ErrCollectionExist|collection|13|collection already exists| +|ErrCollectionNotExist|collection|14|collection does not exists| +|ErrTokenTypeExist|collection|15|token type for contract_id, token-type already exists| +|ErrTokenTypeNotExist|collection|16|token type for contract_id, token-type does not exist| +|ErrTokenTypeFull|collection|17|all token type for contract_id are occupied| +|ErrTokenIndexFull|collection|18|all non-fungible token index for contract_id, token-type are occupied| +|ErrTokenIDFull|collection|19|all fungible token-id for contract_id are occupied| +|ErrTokenNoPermission|collection|20|account does not have the permission| +|ErrTokenAlreadyAChild|collection|21|token is already a child of some other| +|ErrTokenNotAChild|collection|22|token is not a child of some other| +|ErrTokenNotOwnedBy|collection|23|token is being not owned by| +|ErrTokenCannotTransferChildToken|collection|24|cannot transfer a child token| +|ErrTokenNotNFT|collection|25|token is not a NFT| +|ErrCannotAttachToItself|collection|26|cannot attach token to itself| +|ErrCannotAttachToADescendant|collection|27|cannot attach token to a descendant| +|ErrApproverProxySame|collection|28|approver is same with proxy| +|ErrCollectionNotApproved|collection|29|proxy is not approved on the collection| +|ErrCollectionAlreadyApproved|collection|30|proxy is already approved on the collection| +|ErrAccountExist|collection|31|account already exists| +|ErrAccountNotExist|collection|32|account does not exists| +|ErrInsufficientSupply|collection|33|insufficient supply| +|ErrInvalidCoin|collection|34|invalid coin| +|ErrInvalidChangesFieldCount|collection|35|invalid count of field changes| +|ErrEmptyChanges|collection|36|changes is empty| +|ErrInvalidChangesField|collection|37|invalid field of changes| +|ErrTokenIndexWithoutType|collection|38|There is a token index but no token type| +|ErrTokenTypeFTWithoutIndex|collection|39|There is a token type of ft but no token index| +|ErrInsufficientToken|collection|40|insufficient token| +|ErrDuplicateChangesField|collection|41|duplicate field of changes| +|ErrInvalidMetaLength|collection|42|invalid meta length| +|ErrSupplyOverflow|collection|43|supply for collection reached maximum| +|ErrEmptyField|collection|44|required field cannot be empty| +|ErrCompositionTooDeep|collection|45|cannot attach token (composition too deep)| +|ErrCompositionTooWide|collection|46|cannot attach token (composition too wide)| +|ErrBurnNonRootNFT|collection|47|cannot burn non-root NFTs| + +>You can also find detailed information in the following Errors.go files: + * [collection/errors.go](collection/errors.go) + +## Crisis + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrNoSender|crisis|2|sender address is empty| +|ErrUnknownInvariant|crisis|3|unknown invariant| + +>You can also find detailed information in the following Errors.go files: + * [crisis/types/errors.go](crisis/types/errors.go) + +## Distribution + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrEmptyDelegatorAddr|distribution|2|delegator address is empty| +|ErrEmptyWithdrawAddr|distribution|3|withdraw address is empty| +|ErrEmptyValidatorAddr|distribution|4|validator address is empty| +|ErrEmptyDelegationDistInfo|distribution|5|no delegation distribution info| +|ErrNoValidatorDistInfo|distribution|6|no validator distribution info| +|ErrNoValidatorCommission|distribution|7|no validator commission to withdraw| +|ErrSetWithdrawAddrDisabled|distribution|8|set withdraw address disabled| +|ErrBadDistribution|distribution|9|community pool does not have sufficient coins to distribute| +|ErrInvalidProposalAmount|distribution|10|invalid community pool spend proposal amount| +|ErrEmptyProposalRecipient|distribution|11|invalid community pool spend proposal recipient| +|ErrNoValidatorExists|distribution|12|validator does not exist| +|ErrNoDelegationExists|distribution|13|delegation does not exist| + +>You can also find detailed information in the following Errors.go files: + * [distribution/types/errors.go](distribution/types/errors.go) + +## Evidence + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrNoEvidenceHandlerExists|evidence|2|unregistered handler for evidence type| +|ErrInvalidEvidence|evidence|3|invalid evidence| +|ErrNoEvidenceExists|evidence|4|evidence does not exist| +|ErrEvidenceExists|evidence|5|evidence already exists| + +>You can also find detailed information in the following Errors.go files: + * [evidence/types/errors.go](evidence/types/errors.go) + +## Feegrant + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrFeeLimitExceeded|feegrant|2|fee limit exceeded| +|ErrFeeLimitExpired|feegrant|3|fee allowance expired| +|ErrInvalidDuration|feegrant|4|invalid duration| +|ErrNoAllowance|feegrant|5|no allowance| +|ErrNoMessages|feegrant|6|allowed messages are empty| +|ErrMessageNotAllowed|feegrant|7|message not allowed| + +>You can also find detailed information in the following Errors.go files: + * [feegrant/errors.go](feegrant/errors.go) + +## Foundation + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| + +>You can also find detailed information in the following Errors.go files: + * [foundation/errors.go](foundation/errors.go) + +## Gov + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrUnknownProposal|gov|2|unknown proposal| +|ErrInactiveProposal|gov|3|inactive proposal| +|ErrAlreadyActiveProposal|gov|4|proposal already active| +|ErrInvalidProposalContent|gov|5|invalid proposal content| +|ErrInvalidProposalType|gov|6|invalid proposal type| +|ErrInvalidVote|gov|7|invalid vote option| +|ErrInvalidGenesis|gov|8|invalid genesis state| +|ErrNoProposalHandlerExists|gov|9|no handler exists for proposal type| + +>You can also find detailed information in the following Errors.go files: + * [gov/types/errors.go](gov/types/errors.go) + +## Params + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrUnknownSubspace|params|2|unknown subspace| +|ErrSettingParameter|params|3|failed to set parameter| +|ErrEmptyChanges|params|4|submitted parameter changes are empty| +|ErrEmptySubspace|params|5|parameter subspace is empty| +|ErrEmptyKey|params|6|parameter key is empty| +|ErrEmptyValue|params|7|parameter value is empty| + +>You can also find detailed information in the following Errors.go files: + * [params/types/proposal/errors.go](params/types/proposal/errors.go) + +## Slashing + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrNoValidatorForAddress|slashing|2|address is not associated with any known validator| +|ErrBadValidatorAddr|slashing|3|validator does not exist for that address| +|ErrValidatorJailed|slashing|4|validator still jailed; cannot be unjailed| +|ErrValidatorNotJailed|slashing|5|validator not jailed; cannot be unjailed| +|ErrMissingSelfDelegation|slashing|6|validator has no self-delegation; cannot be unjailed| +|ErrSelfDelegationTooLowToUnjail|slashing|7|validator's self delegation less than minimum; cannot be unjailed| +|ErrNoSigningInfoFound|slashing|8|no validator signing info found| + +>You can also find detailed information in the following Errors.go files: + * [slashing/types/errors.go](slashing/types/errors.go) + +## Staking + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrEmptyValidatorAddr|staking|2|empty validator address| +|ErrNoValidatorFound|staking|3|validator does not exist| +|ErrValidatorOwnerExists|staking|4|validator already exist for this operator address; must use new validator operator address| +|ErrValidatorPubKeyExists|staking|5|validator already exist for this pubkey; must use new validator pubkey| +|ErrValidatorPubKeyTypeNotSupported|staking|6|validator pubkey type is not supported| +|ErrValidatorJailed|staking|7|validator for this address is currently jailed| +|ErrBadRemoveValidator|staking|8|failed to remove validator| +|ErrCommissionNegative|staking|9|commission must be positive| +|ErrCommissionHuge|staking|10|commission cannot be more than 100%| +|ErrCommissionGTMaxRate|staking|11|commission cannot be more than the max rate| +|ErrCommissionUpdateTime|staking|12|commission cannot be changed more than once in 24h| +|ErrCommissionChangeRateNegative|staking|13|commission change rate must be positive| +|ErrCommissionChangeRateGTMaxRate|staking|14|commission change rate cannot be more than the max rate| +|ErrCommissionGTMaxChangeRate|staking|15|commission cannot be changed more than max change rate| +|ErrSelfDelegationBelowMinimum|staking|16|validator's self delegation must be greater than their minimum self delegation| +|ErrMinSelfDelegationDecreased|staking|17|minimum self delegation cannot be decrease| +|ErrEmptyDelegatorAddr|staking|18|empty delegator address| +|ErrNoDelegation|staking|19|no delegation for (address, validator) tuple| +|ErrBadDelegatorAddr|staking|20|delegator does not exist with address| +|ErrNoDelegatorForAddress|staking|21|delegator does not contain delegation| +|ErrInsufficientShares|staking|22|insufficient delegation shares| +|ErrDelegationValidatorEmpty|staking|23|cannot delegate to an empty validator| +|ErrNotEnoughDelegationShares|staking|24|not enough delegation shares| +|ErrNotMature|staking|25|entry not mature| +|ErrNoUnbondingDelegation|staking|26|no unbonding delegation found| +|ErrMaxUnbondingDelegationEntries|staking|27|too many unbonding delegation entries for (delegator, validator) tuple| +|ErrNoRedelegation|staking|28|no redelegation found| +|ErrSelfRedelegation|staking|29|cannot redelegate to the same validator| +|ErrTinyRedelegationAmount|staking|30|too few tokens to redelegate (truncates to zero tokens)| +|ErrBadRedelegationDst|staking|31|redelegation destination validator not found| +|ErrTransitiveRedelegation|staking|32|redelegation to this validator already in progress; first redelegation to this validator must complete before next redelegation| +|ErrMaxRedelegationEntries|staking|33|too many redelegation entries for (delegator, src-validator, dst-validator) tuple| +|ErrDelegatorShareExRateInvalid|staking|34|cannot delegate to validators with invalid (zero) ex-rate| +|ErrBothShareMsgsGiven|staking|35|both shares amount and shares percent provided| +|ErrNeitherShareMsgsGiven|staking|36|neither shares amount nor shares percent provided| +|ErrInvalidHistoricalInfo|staking|37|invalid historical info| +|ErrNoHistoricalInfo|staking|38|no historical info found| +|ErrEmptyValidatorPubKey|staking|39|empty validator public key| + +>You can also find detailed information in the following Errors.go files: + * [staking/types/errors.go](staking/types/errors.go) + +## Token + +|Error Name|Codespace|Code|Description| +|:-|:-|:-|:-| +|ErrInvalidContractID|contract|2|invalid contractID| +|ErrContractNotExist|contract|3|contract does not exist| +|ErrTokenNotExist|token|2|token does not exist| +|ErrTokenNotMintable|token|3|token is not mintable| +|ErrInvalidTokenName|token|4|token name should not be empty| +|ErrInvalidTokenDecimals|token|5|token decimals should be within the range in 0 ~ 18| +|ErrInvalidAmount|token|6|invalid token amount| +|ErrInvalidImageURILength|token|7|invalid token uri length| +|ErrInvalidNameLength|token|8|invalid name length| +|ErrInvalidTokenSymbol|token|9|invalid token symbol| +|ErrTokenNoPermission|token|10|account does not have the permission| +|ErrAccountExist|token|11|account already exists| +|ErrAccountNotExist|token|12|account does not exists| +|ErrInsufficientBalance|token|13|insufficient balance| +|ErrSupplyExist|token|14|supply for token already exists| +|ErrInsufficientSupply|token|15|insufficient supply| +|ErrInvalidChangesFieldCount|token|16|invalid count of field changes| +|ErrEmptyChanges|token|17|changes is empty| +|ErrInvalidChangesField|token|18|invalid field of changes| +|ErrDuplicateChangesField|token|19|invalid field of changes| +|ErrInvalidMetaLength|token|20|invalid meta length| +|ErrSupplyOverflow|token|21|supply for token reached maximum| +|ErrApproverProxySame|token|22|approver is same with proxy| +|ErrTokenNotApproved|token|23|proxy is not approved on the token| +|ErrTokenAlreadyApproved|token|24|proxy is already approved on the token| + +>You can also find detailed information in the following Errors.go files: + * [token/class/errors.go](token/class/errors.go) + * [token/errors.go](token/errors.go) From d4f38a6f4dcf02973a6525a87c84a94bc9625692 Mon Sep 17 00:00:00 2001 From: 170210 Date: Tue, 1 Aug 2023 18:07:09 +0900 Subject: [PATCH 03/11] chore: add a ci to check generated error docs up-to-date Signed-off-by: 170210 --- .github/workflows/check-generated.yml | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/check-generated.yml diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml new file mode 100644 index 0000000000..8cf4518441 --- /dev/null +++ b/.github/workflows/check-generated.yml @@ -0,0 +1,37 @@ +# Verify that generated code is up-to-date. + +name: Check generated code +on: + workflow_dispatch: + pull_request: + branches: + - '*' + +permissions: + contents: read + +jobs: + check-error-doc: + runs-on: ubuntu-latest + steps: + - name: Setup Golang + uses: actions/setup-go@v4 + with: + go-version: '1.20' + + - uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Check generated error docs + run: | + cd ./tools/error_doc + go run ./ + if ! git diff --stat --exit-code ; then + echo ">> ERROR:" + echo ">>" + echo ">> Error documents require update (source files in x folder may have changed)." + echo ">> Ensure your tools are up-to-date, re-run 'go run ./' in tools/error_doc and update this PR." + echo ">>" + exit 1 + fi \ No newline at end of file From 8d056a0f7a54e3baf0bcec333b1c8716efd48411 Mon Sep 17 00:00:00 2001 From: 170210 Date: Tue, 1 Aug 2023 18:09:03 +0900 Subject: [PATCH 04/11] chore: update changelog Signed-off-by: 170210 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 635aa422d8..b8f0af83d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,3 +58,4 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Document Updates * (readme) [\#997](https://github.com/finschia/finschia-sdk/pull/997) fix swagger url +* (x/ERRORS.md) [\#1059](https://github.com/Finschia/finschia-sdk/pull/1059) create ERRORS.md for x/module \ No newline at end of file From a640923fb890676734912d0971aa9509dd08fa61 Mon Sep 17 00:00:00 2001 From: 170210 Date: Tue, 1 Aug 2023 18:20:06 +0900 Subject: [PATCH 05/11] refactor: fix for lint Signed-off-by: 170210 --- tools/error_doc/const_info.go | 3 +-- tools/error_doc/error_info.go | 13 ++++--------- tools/error_doc/main.go | 8 +++++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/tools/error_doc/const_info.go b/tools/error_doc/const_info.go index 12d6cbd6b4..3f4ce99386 100644 --- a/tools/error_doc/const_info.go +++ b/tools/error_doc/const_info.go @@ -12,7 +12,6 @@ func getConst(line string) (string, string, error) { i := strings.TrimSpace(parts[0]) val := strings.Trim(strings.TrimSpace(parts[1]), `"`) return i, val, nil - } else { - return "", "", errors.New("failed to get the value in: " + line) } + return "", "", errors.New("failed to get the value in: " + line) } diff --git a/tools/error_doc/error_info.go b/tools/error_doc/error_info.go index 37a80f50b4..4bbf8e482c 100644 --- a/tools/error_doc/error_info.go +++ b/tools/error_doc/error_info.go @@ -21,12 +21,10 @@ func (err errorInfo) toString(moduleName string) (string, error) { if err.codeSpace == "ModuleName" { if moduleName == "" { return "", errors.New("failed to find moduleName") - } else { - return fmt.Sprintf(errorInfoTemplate, err.errorName, moduleName, err.code, err.description), nil } - } else { - return fmt.Sprintf(errorInfoTemplate, err.errorName, err.codeSpace, err.code, err.description), nil + return fmt.Sprintf(errorInfoTemplate, err.errorName, moduleName, err.code, err.description), nil } + return fmt.Sprintf(errorInfoTemplate, err.errorName, err.codeSpace, err.code, err.description), nil } func addError(line string, errorDict map[string]string) (errorInfo, error) { @@ -55,13 +53,10 @@ func addError(line string, errorDict map[string]string) (errorInfo, error) { code: code, description: description, }, nil - } else { - return errorInfo{}, errors.New("failed to get error info in: " + line) } - } else { - return errorInfo{}, errors.New("failed to parse error info in: " + line) + return errorInfo{}, errors.New("failed to get error info in: " + line) } - + return errorInfo{}, errors.New("failed to parse error info in: " + line) } func getErrors(p string) ([]errorInfo, error) { diff --git a/tools/error_doc/main.go b/tools/error_doc/main.go index 413e1ae5b0..1929848d91 100644 --- a/tools/error_doc/main.go +++ b/tools/error_doc/main.go @@ -36,7 +36,7 @@ func findModuleWithFiles(targetPath string) (map[string][]string, []string, erro errorFile := "errors.go" filePaths, err := findFilesWithName(targetPath, errorFile) if len(filePaths) == 0 || err != nil { - return nil, nil, errors.New("Not find target files in x folder") + return nil, nil, errors.New("not find target files in x folder") } // get each module name and bind it to paths (one module may have multiple errors.go) @@ -44,13 +44,15 @@ func findModuleWithFiles(targetPath string) (map[string][]string, []string, erro for _, filePath := range filePaths { moduleName := findModuleName(filePath) if moduleName == "" { - return nil, nil, errors.New("Failed to get module name for " + filePath) + return nil, nil, errors.New("failed to get module name for " + filePath) } moduleWithPaths[moduleName] = append(moduleWithPaths[moduleName], filePath) } // sort keys and filepaths - var modules []string + n := len(moduleWithPaths) + modules := make([]string, 0, n) + for moduleName := range moduleWithPaths { modules = append(modules, moduleName) sort.Strings(moduleWithPaths[moduleName]) From a45bc3098dd8e75d111dbbc9356b845314bb1cac Mon Sep 17 00:00:00 2001 From: 170210 Date: Tue, 1 Aug 2023 18:48:15 +0900 Subject: [PATCH 06/11] style: format yml file Signed-off-by: 170210 --- .github/workflows/check-generated.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml index 8cf4518441..0e95638b1a 100644 --- a/.github/workflows/check-generated.yml +++ b/.github/workflows/check-generated.yml @@ -34,4 +34,4 @@ jobs: echo ">> Ensure your tools are up-to-date, re-run 'go run ./' in tools/error_doc and update this PR." echo ">>" exit 1 - fi \ No newline at end of file + fi From e9c5da910e9e8154b04d01440308e2f568b895fe Mon Sep 17 00:00:00 2001 From: 170210 Date: Wed, 2 Aug 2023 11:19:46 +0900 Subject: [PATCH 07/11] fixup: fix for review Signed-off-by: 170210 --- .github/workflows/check-generated.yml | 5 ++--- Makefile | 4 ++++ tools/error_doc/go.mod | 5 +++++ tools/error_doc/go.sum | 2 ++ x/README.md | 2 ++ 5 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 tools/error_doc/go.mod create mode 100644 tools/error_doc/go.sum diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml index 0e95638b1a..324936ab00 100644 --- a/.github/workflows/check-generated.yml +++ b/.github/workflows/check-generated.yml @@ -25,13 +25,12 @@ jobs: - name: Check generated error docs run: | - cd ./tools/error_doc - go run ./ + make error-doc if ! git diff --stat --exit-code ; then echo ">> ERROR:" echo ">>" echo ">> Error documents require update (source files in x folder may have changed)." - echo ">> Ensure your tools are up-to-date, re-run 'go run ./' in tools/error_doc and update this PR." + echo ">> Ensure your tools are up-to-date, re-run 'make error-doc' and update this PR." echo ">>" exit 1 fi diff --git a/Makefile b/Makefile index 61e1b781b8..9e808cdc50 100644 --- a/Makefile +++ b/Makefile @@ -570,6 +570,10 @@ libsodium: fi .PHONY: libsodium +error-doc: + cd ./tools/error_doc && go run ./ +.PHONY: error-doc + ############################################################################### ### release ### ############################################################################### diff --git a/tools/error_doc/go.mod b/tools/error_doc/go.mod new file mode 100644 index 0000000000..6ca1dbad7f --- /dev/null +++ b/tools/error_doc/go.mod @@ -0,0 +1,5 @@ +module github.com/Finschia/finschia-sdk/tools/error_doc + +go 1.20 + +require golang.org/x/text v0.11.0 diff --git a/tools/error_doc/go.sum b/tools/error_doc/go.sum new file mode 100644 index 0000000000..5f53cd0902 --- /dev/null +++ b/tools/error_doc/go.sum @@ -0,0 +1,2 @@ +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= diff --git a/x/README.md b/x/README.md index 9579cebc3c..41120f07a8 100644 --- a/x/README.md +++ b/x/README.md @@ -26,3 +26,5 @@ Here are some production-grade modules that can be used in Cosmos SDK applicatio - [Upgrade](upgrade/spec/README.md) - Software upgrades handling and coordination. To learn more about the process of building modules, visit the [building modules reference documentation](../docs/building-modules/README.md). + +To learn more about the error information of modules, visit the [error documentation](./ERRORS.md). From 6932d6ef0b727299ec7411708b77eb1255890e5e Mon Sep 17 00:00:00 2001 From: 170210 Date: Wed, 2 Aug 2023 14:45:24 +0900 Subject: [PATCH 08/11] fixup: fix for review Signed-off-by: 170210 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8f0af83d2..cd3a5ab8a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,4 +58,4 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Document Updates * (readme) [\#997](https://github.com/finschia/finschia-sdk/pull/997) fix swagger url -* (x/ERRORS.md) [\#1059](https://github.com/Finschia/finschia-sdk/pull/1059) create ERRORS.md for x/module \ No newline at end of file +* (docs) [\#1059](https://github.com/Finschia/finschia-sdk/pull/1059) create ERRORS.md for x/module \ No newline at end of file From 384316e37d448b89cbf55c37cc076367a86a5038 Mon Sep 17 00:00:00 2001 From: 170210 Date: Mon, 7 Aug 2023 11:32:04 +0900 Subject: [PATCH 09/11] fixup: refactor by comment Signed-off-by: 170210 --- .github/workflows/check-generated.yml | 3 +- tools/error_doc/const_info.go | 17 -- tools/error_doc/error_info.go | 93 ----------- .../error_doc/generator/errors_file_parser.go | 95 +++++++++++ .../generator/generate_error_docs.go | 153 ++++++++++++++++++ tools/error_doc/generator/keys_file_parser.go | 54 +++++++ .../generator/module_directory_walker.go | 22 +++ tools/error_doc/main.go | 126 +-------------- tools/error_doc/module_info.go | 52 ------ 9 files changed, 329 insertions(+), 286 deletions(-) delete mode 100644 tools/error_doc/const_info.go delete mode 100644 tools/error_doc/error_info.go create mode 100644 tools/error_doc/generator/errors_file_parser.go create mode 100644 tools/error_doc/generator/generate_error_docs.go create mode 100644 tools/error_doc/generator/keys_file_parser.go create mode 100644 tools/error_doc/generator/module_directory_walker.go delete mode 100644 tools/error_doc/module_info.go diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml index 324936ab00..f613b5e993 100644 --- a/.github/workflows/check-generated.yml +++ b/.github/workflows/check-generated.yml @@ -19,7 +19,8 @@ jobs: with: go-version: '1.20' - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 with: fetch-depth: 1 diff --git a/tools/error_doc/const_info.go b/tools/error_doc/const_info.go deleted file mode 100644 index 3f4ce99386..0000000000 --- a/tools/error_doc/const_info.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "errors" - "strings" -) - -func getConst(line string) (string, string, error) { - line = strings.Replace(line, "const", "", 1) - parts := strings.Split(line, "=") - if len(parts) == 2 { - i := strings.TrimSpace(parts[0]) - val := strings.Trim(strings.TrimSpace(parts[1]), `"`) - return i, val, nil - } - return "", "", errors.New("failed to get the value in: " + line) -} diff --git a/tools/error_doc/error_info.go b/tools/error_doc/error_info.go deleted file mode 100644 index 4bbf8e482c..0000000000 --- a/tools/error_doc/error_info.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "bufio" - "errors" - "fmt" - "os" - "regexp" - "strings" -) - -type errorInfo struct { - errorName string - codeSpace string - code string - description string -} - -func (err errorInfo) toString(moduleName string) (string, error) { - errorInfoTemplate := "|%s|%s|%s|%s|\n" - if err.codeSpace == "ModuleName" { - if moduleName == "" { - return "", errors.New("failed to find moduleName") - } - return fmt.Sprintf(errorInfoTemplate, err.errorName, moduleName, err.code, err.description), nil - } - return fmt.Sprintf(errorInfoTemplate, err.errorName, err.codeSpace, err.code, err.description), nil -} - -func addError(line string, errorDict map[string]string) (errorInfo, error) { - parts := strings.SplitN(line, "=", 2) - errName := strings.TrimSpace(parts[0]) - errBody := strings.TrimSpace(parts[1]) - // error info is like as sdkerrors.Register(...) - pattern := regexp.MustCompile(`sdkerrors\.Register\((.*)\)`) - match := pattern.FindStringSubmatch(errBody) - - if len(match) == 2 { - parts := strings.SplitN(match[1], ",", 3) - - if len(parts) == 3 { - codeSpace := strings.TrimSpace(parts[0]) - code := strings.TrimSpace(parts[1]) - description := strings.Trim(strings.TrimSpace(parts[2]), `"`) - - if constValue, found := errorDict[codeSpace]; found { - codeSpace = constValue - } - - return errorInfo{ - errorName: errName, - codeSpace: codeSpace, - code: code, - description: description, - }, nil - } - return errorInfo{}, errors.New("failed to get error info in: " + line) - } - return errorInfo{}, errors.New("failed to parse error info in: " + line) -} - -func getErrors(p string) ([]errorInfo, error) { - var errorDict []errorInfo - constDict := make(map[string]string) - - file, err := os.Open(p) - if err != nil { - return nil, err - } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - // get const - if strings.Contains(line, "=") { - if !strings.Contains(line, "sdkerrors.Register") { - identifier, value, err := getConst(line) - if err != nil { - return nil, err - } - constDict[identifier] = value - } else { - errInfo, err := addError(line, constDict) - if err != nil { - return nil, err - } - errorDict = append(errorDict, errInfo) - } - } - } - return errorDict, nil -} diff --git a/tools/error_doc/generator/errors_file_parser.go b/tools/error_doc/generator/errors_file_parser.go new file mode 100644 index 0000000000..502ad2558c --- /dev/null +++ b/tools/error_doc/generator/errors_file_parser.go @@ -0,0 +1,95 @@ +package generator + +import ( + "bufio" + "errors" + "fmt" + "os" + "regexp" + "strings" +) + +type errorInfo struct { + errorName string + codespace string + code string + description string +} + +func (ei errorInfo) toString(cs string) (string, error) { + errorInfoTemplate := "|%s|%s|%s|%s|\n" + if ei.codespace == "ModuleName" { + if cs == "" { + return "", errors.New("failed to find moduleName") + } + ei.codespace = cs + } + return fmt.Sprintf(errorInfoTemplate, ei.errorName, ei.codespace, ei.code, ei.description), nil +} + +func (ei *errorInfo) getError(line string, constDict map[string]string) error { + parts := strings.SplitN(line, "=", 2) + ei.errorName = strings.TrimSpace(parts[0]) + errBody := strings.TrimSpace(parts[1]) + // error info is like as sdkerrors.Register(...) + pattern := regexp.MustCompile(`sdkerrors\.Register\((.*)\)`) + match := pattern.FindStringSubmatch(errBody) + if len(match) == 2 { + parts := strings.SplitN(match[1], ",", 3) + if len(parts) == 3 { + ei.codespace = strings.TrimSpace(parts[0]) + ei.code = strings.TrimSpace(parts[1]) + ei.description = strings.Trim(strings.TrimSpace(parts[2]), `"`) + if constValue, found := constDict[ei.codespace]; found { + ei.codespace = constValue + } + return nil + } + return errors.New("failed to get error info in: " + line) + } + return errors.New("failed to parse error info in: " + line) +} + +func getConst(line string) (string, string, error) { + line = strings.Replace(line, "const", "", 1) + parts := strings.Split(line, "=") + if len(parts) == 2 { + i := strings.TrimSpace(parts[0]) + val := strings.Trim(strings.TrimSpace(parts[1]), `"`) + return i, val, nil + } + return "", "", errors.New("failed to get the value in: " + line) +} + +func (mi *moduleInfo) errorsFileParse() error { + // var errorDict []errorInfo + // constDict := make(map[string]string) + file, err := os.Open(mi.filepath) + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, "=") { + // get const + if !strings.Contains(line, "sdkerrors.Register") { + identifier, value, err := getConst(line) + if err != nil { + return err + } + mi.constDict[identifier] = value + } else { + // get error + var errInfo errorInfo + if err := errInfo.getError(line, mi.constDict); err != nil { + return err + } + mi.errorDict = append(mi.errorDict, errInfo) + } + } + } + return nil +} diff --git a/tools/error_doc/generator/generate_error_docs.go b/tools/error_doc/generator/generate_error_docs.go new file mode 100644 index 0000000000..73e0fdb6a8 --- /dev/null +++ b/tools/error_doc/generator/generate_error_docs.go @@ -0,0 +1,153 @@ +package generator + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +type ErrorDocumentGenerator struct { + targetPath string + errorsFiles []string + modules []string + errorDocument map[string][]*moduleInfo +} + +type moduleInfo struct { + filepath string + codespace string + constDict map[string]string + errorDict []errorInfo +} + +func NewErrorDocumentGenerator(p string) *ErrorDocumentGenerator { + return &ErrorDocumentGenerator{ + targetPath: p, + errorDocument: make(map[string][]*moduleInfo), + } +} + +func (edg *ErrorDocumentGenerator) extractModuleName() error { + for _, filepath := range edg.errorsFiles { + var moduleName string + startIndex := strings.Index(filepath, "/x/") + len("/x/") + endIndex := strings.Index(filepath[startIndex:], "/") + if startIndex != -1 && endIndex != -1 { + moduleName = filepath[startIndex : startIndex+endIndex] + } + if moduleName == "" { + return errors.New("failed to get module name for " + filepath) + } + edg.errorDocument[moduleName] = append(edg.errorDocument[moduleName], &moduleInfo{ + filepath: filepath, + codespace: "", + constDict: make(map[string]string), + errorDict: []errorInfo{}, + }) + } + // sort keys and filepaths + for moduleName := range edg.errorDocument { + edg.modules = append(edg.modules, moduleName) + // sort.Strings(edg.errorDocument[moduleName]) + } + sort.Strings(edg.modules) + return nil +} + +func (edg ErrorDocumentGenerator) outputCategory(file *os.File) { + file.WriteString("\n") + file.WriteString("# Category\n") + columnTemplate := " * [%s](#%s)\n" + for _, moduleName := range edg.modules { + file.WriteString(fmt.Sprintf(columnTemplate, cases.Title(language.Und).String(moduleName), moduleName)) + } + file.WriteString("\n") +} + +func (edg *ErrorDocumentGenerator) generateContent() error { + // generate errors in each module + for _, moduleName := range edg.modules { + mods := edg.errorDocument[moduleName] + for _, mod := range mods { + if err := mod.errorsFileParse(); err != nil { + return err + } + if err := mod.keysFileParse(); err != nil { + return err + } + } + } + return nil +} + +func (edg ErrorDocumentGenerator) outputContent(file *os.File) error { + extraInfoTemplate := " * [%s](%s)\n" + for _, moduleName := range edg.modules { + // module name + file.WriteString("\n") + file.WriteString("## " + cases.Title(language.Und).String(moduleName) + "\n") + // table header + file.WriteString("\n") + file.WriteString("|Error Name|Codespace|Code|Description|\n") + file.WriteString("|:-|:-|:-|:-|\n") + // table contents + mods := edg.errorDocument[moduleName] + for _, mod := range mods { + for _, errInfo := range mod.errorDict { + // assign value to field "codespace" + if s, err := errInfo.toString(mod.codespace); err != nil { + return err + } else { + file.WriteString(s) + } + } + } + // extract infomation + file.WriteString("\n>You can also find detailed information in the following Errors.go files:\n") + for _, mod := range mods { + relPath, err := filepath.Rel(edg.targetPath, mod.filepath) + if err != nil { + return err + } + file.WriteString(fmt.Sprintf(extraInfoTemplate, relPath, relPath)) + } + } + return nil +} + +func (edg ErrorDocumentGenerator) AutoGenerate() error { + // get all errors.go in x folder + errorsFileName := "errors.go" + err := edg.listUpErrorsGoFiles(edg.targetPath, errorsFileName) + if len(edg.errorsFiles) == 0 || err != nil { + return errors.New("not find target files in x folder") + } + // get each module name and bind it to paths (one module may have multiple errors.go) + if err := edg.extractModuleName(); err != nil { + return err + } + // generate content + if err := edg.generateContent(); err != nil { + return err + } + // prepare the file for writing + filepath := edg.targetPath + "/ERRORS.md" + file, err := os.Create(filepath) + if err != nil { + return err + } + defer file.Close() + // output category + edg.outputCategory(file) + // output content + if err := edg.outputContent(file); err != nil { + return err + } + return nil +} diff --git a/tools/error_doc/generator/keys_file_parser.go b/tools/error_doc/generator/keys_file_parser.go new file mode 100644 index 0000000000..19fd739f8e --- /dev/null +++ b/tools/error_doc/generator/keys_file_parser.go @@ -0,0 +1,54 @@ +package generator + +import ( + "bufio" + "errors" + "os" + "strings" +) + +func getCodeSpace(line string) (string, string, error) { + line = strings.Replace(line, "const", "", 1) + parts := strings.Split(line, "=") + if len(parts) == 2 { + i := strings.TrimSpace(parts[0]) + val := strings.Trim(strings.TrimSpace(parts[1]), `"`) + return i, val, nil + } + return "", "", errors.New("failed to get the value in: " + line) +} + +func (mi *moduleInfo) keysFileParse() error { + // find keys.go or key.go + possibleFileNames := []string{"keys.go", "key.go"} + var keyFilePath string + for _, fileName := range possibleFileNames { + paramPath := strings.Replace(mi.filepath, "errors.go", fileName, 1) + if _, err := os.Stat(paramPath); err == nil { + keyFilePath = paramPath + break + } + } + // if keys.go or key.go is exist + if keyFilePath != "" { + file, err := os.Open(keyFilePath) + if err != nil { + return errors.New(keyFilePath + " cannot be opened") + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + // get module name + if strings.Contains(line, "ModuleName = ") { + _, val, err := getCodeSpace(line) + if err != nil { + return err + } + mi.codespace = val + } + } + } + + return nil +} diff --git a/tools/error_doc/generator/module_directory_walker.go b/tools/error_doc/generator/module_directory_walker.go new file mode 100644 index 0000000000..429a333c9b --- /dev/null +++ b/tools/error_doc/generator/module_directory_walker.go @@ -0,0 +1,22 @@ +package generator + +import ( + "os" + "path/filepath" +) + +func (edg *ErrorDocumentGenerator) listUpErrorsGoFiles(startPath, errorsFileName string) error { + err := filepath.Walk(startPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() && info.Name() == errorsFileName { + edg.errorsFiles = append(edg.errorsFiles, path) + } + return nil + }) + if err != nil { + return err + } + return nil +} diff --git a/tools/error_doc/main.go b/tools/error_doc/main.go index 1929848d91..8342308263 100644 --- a/tools/error_doc/main.go +++ b/tools/error_doc/main.go @@ -1,127 +1,13 @@ package main import ( - "errors" "fmt" "os" "path/filepath" - "sort" - "golang.org/x/text/cases" - "golang.org/x/text/language" + "github.com/Finschia/finschia-sdk/tools/error_doc/generator" ) -func findFilesWithName(startPath, fileName string) ([]string, error) { - var foundFiles []string - - err := filepath.Walk(startPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if !info.IsDir() && info.Name() == fileName { - foundFiles = append(foundFiles, path) - } - return nil - }) - if err != nil { - return nil, err - } - - return foundFiles, nil -} - -func findModuleWithFiles(targetPath string) (map[string][]string, []string, error) { - - // get all errors.go in x folder - errorFile := "errors.go" - filePaths, err := findFilesWithName(targetPath, errorFile) - if len(filePaths) == 0 || err != nil { - return nil, nil, errors.New("not find target files in x folder") - } - - // get each module name and bind it to paths (one module may have multiple errors.go) - moduleWithPaths := make(map[string][]string) - for _, filePath := range filePaths { - moduleName := findModuleName(filePath) - if moduleName == "" { - return nil, nil, errors.New("failed to get module name for " + filePath) - } - moduleWithPaths[moduleName] = append(moduleWithPaths[moduleName], filePath) - } - - // sort keys and filepaths - n := len(moduleWithPaths) - modules := make([]string, 0, n) - - for moduleName := range moduleWithPaths { - modules = append(modules, moduleName) - sort.Strings(moduleWithPaths[moduleName]) - } - sort.Strings(modules) - - return moduleWithPaths, modules, nil -} - -func autoGenerate(targetPath string, moduleWithPaths map[string][]string, modules []string) error { - filePath := targetPath + "/ERRORS.md" - file, err := os.Create(filePath) - if err != nil { - return err - } - defer file.Close() - - // generate category - file.WriteString("\n") - file.WriteString("# Category\n") - columnTemplate := " * [%s](#%s)\n" - for _, moduleName := range modules { - file.WriteString(fmt.Sprintf(columnTemplate, cases.Title(language.Und).String(moduleName), moduleName)) - } - file.WriteString("\n") - - extraInfoTemplate := " * [%s](%s)\n" - // errors in each module - for _, moduleName := range modules { - - // table header - file.WriteString("\n") - file.WriteString("## " + cases.Title(language.Und).String(moduleName) + "\n") - file.WriteString("\n") - file.WriteString("|Error Name|Codespace|Code|Description|\n") - file.WriteString("|:-|:-|:-|:-|\n") - - filePaths := moduleWithPaths[moduleName] - for _, filePath := range filePaths { - errDict, err := getErrors(filePath) - if err != nil { - return err - } - moduleName, err := getModuleNameValue(filePath) - if err != nil { - return err - } - for _, errInfo := range errDict { - column, err := errInfo.toString(moduleName) - if err != nil { - return err - } - file.WriteString(column) - } - } - - file.WriteString("\n>You can also find detailed information in the following Errors.go files:\n") - for _, filePath := range filePaths { - relPath, err := filepath.Rel(targetPath, filePath) - if err != nil { - return err - } - file.WriteString(fmt.Sprintf(extraInfoTemplate, relPath, relPath)) - } - - } - return nil -} - func main() { currentPath, err := os.Getwd() if err != nil { @@ -130,15 +16,9 @@ func main() { } targetPath := filepath.Join(currentPath, "..", "..", "x") - moduleWithPaths, modules, err := findModuleWithFiles(targetPath) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - if err := autoGenerate(targetPath, moduleWithPaths, modules); err != nil { + errorDocumentGenerator := generator.NewErrorDocumentGenerator(targetPath) + if err := errorDocumentGenerator.AutoGenerate(); err != nil { fmt.Println(err) os.Exit(1) } - } diff --git a/tools/error_doc/module_info.go b/tools/error_doc/module_info.go deleted file mode 100644 index f5ef3073a4..0000000000 --- a/tools/error_doc/module_info.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "bufio" - "errors" - "os" - "strings" -) - -func findModuleName(s string) string { - startIndex := strings.Index(s, "/x/") + len("/x/") - endIndex := strings.Index(s[startIndex:], "/") - - if startIndex != -1 && endIndex != -1 { - return s[startIndex : startIndex+endIndex] - } - return "" -} - -func getModuleNameValue(filePath string) (string, error) { - possibleFileNames := []string{"keys.go", "key.go"} - var keyFilePath string - for _, fileName := range possibleFileNames { - paramPath := strings.Replace(filePath, "errors.go", fileName, 1) - if _, err := os.Stat(paramPath); err == nil { - keyFilePath = paramPath - break - } - } - - if keyFilePath != "" { - file, err := os.Open(keyFilePath) - if err != nil { - return "", errors.New(keyFilePath + " cannot be opened") - } - defer file.Close() - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - // get module name - if strings.Contains(line, "ModuleName = ") { - _, val, err := getConst(line) - if err != nil { - return "", err - } - return val, nil - } - } - } - - return "", nil -} From d5e2d852ed1216be04d2497f9ac9afebbe017f2c Mon Sep 17 00:00:00 2001 From: 170210 Date: Tue, 8 Aug 2023 11:08:15 +0900 Subject: [PATCH 10/11] fixup: fix for comment Signed-off-by: 170210 --- .github/workflows/check-generated.yml | 2 +- Makefile | 4 ++-- ..._error_docs.go => error_docs_generator.go} | 20 +++++++++++++++-- .../error_doc/generator/errors_file_parser.go | 2 +- tools/error_doc/generator/keys_file_parser.go | 2 +- .../generator/module_directory_walker.go | 22 ------------------- 6 files changed, 23 insertions(+), 29 deletions(-) rename tools/error_doc/generator/{generate_error_docs.go => error_docs_generator.go} (89%) delete mode 100644 tools/error_doc/generator/module_directory_walker.go diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml index f613b5e993..1aa1b4f375 100644 --- a/.github/workflows/check-generated.yml +++ b/.github/workflows/check-generated.yml @@ -26,7 +26,7 @@ jobs: - name: Check generated error docs run: | - make error-doc + make error-doc-gen if ! git diff --stat --exit-code ; then echo ">> ERROR:" echo ">>" diff --git a/Makefile b/Makefile index 9e808cdc50..3acde110b2 100644 --- a/Makefile +++ b/Makefile @@ -570,9 +570,9 @@ libsodium: fi .PHONY: libsodium -error-doc: +error-doc-gen: cd ./tools/error_doc && go run ./ -.PHONY: error-doc +.PHONY: error-doc-gen ############################################################################### ### release ### diff --git a/tools/error_doc/generator/generate_error_docs.go b/tools/error_doc/generator/error_docs_generator.go similarity index 89% rename from tools/error_doc/generator/generate_error_docs.go rename to tools/error_doc/generator/error_docs_generator.go index 73e0fdb6a8..0f814ef3b1 100644 --- a/tools/error_doc/generator/generate_error_docs.go +++ b/tools/error_doc/generator/error_docs_generator.go @@ -33,6 +33,22 @@ func NewErrorDocumentGenerator(p string) *ErrorDocumentGenerator { } } +func (edg *ErrorDocumentGenerator) listUpErrorsGoFiles(startPath, errorsFileName string) error { + err := filepath.Walk(startPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() && info.Name() == errorsFileName { + edg.errorsFiles = append(edg.errorsFiles, path) + } + return nil + }) + if err != nil { + return err + } + return nil +} + func (edg *ErrorDocumentGenerator) extractModuleName() error { for _, filepath := range edg.errorsFiles { var moduleName string @@ -75,10 +91,10 @@ func (edg *ErrorDocumentGenerator) generateContent() error { for _, moduleName := range edg.modules { mods := edg.errorDocument[moduleName] for _, mod := range mods { - if err := mod.errorsFileParse(); err != nil { + if err := mod.parseErrorsFile(); err != nil { return err } - if err := mod.keysFileParse(); err != nil { + if err := mod.parseKeysFile(); err != nil { return err } } diff --git a/tools/error_doc/generator/errors_file_parser.go b/tools/error_doc/generator/errors_file_parser.go index 502ad2558c..258eef75e1 100644 --- a/tools/error_doc/generator/errors_file_parser.go +++ b/tools/error_doc/generator/errors_file_parser.go @@ -61,7 +61,7 @@ func getConst(line string) (string, string, error) { return "", "", errors.New("failed to get the value in: " + line) } -func (mi *moduleInfo) errorsFileParse() error { +func (mi *moduleInfo) parseErrorsFile() error { // var errorDict []errorInfo // constDict := make(map[string]string) file, err := os.Open(mi.filepath) diff --git a/tools/error_doc/generator/keys_file_parser.go b/tools/error_doc/generator/keys_file_parser.go index 19fd739f8e..5a42c87500 100644 --- a/tools/error_doc/generator/keys_file_parser.go +++ b/tools/error_doc/generator/keys_file_parser.go @@ -18,7 +18,7 @@ func getCodeSpace(line string) (string, string, error) { return "", "", errors.New("failed to get the value in: " + line) } -func (mi *moduleInfo) keysFileParse() error { +func (mi *moduleInfo) parseKeysFile() error { // find keys.go or key.go possibleFileNames := []string{"keys.go", "key.go"} var keyFilePath string diff --git a/tools/error_doc/generator/module_directory_walker.go b/tools/error_doc/generator/module_directory_walker.go deleted file mode 100644 index 429a333c9b..0000000000 --- a/tools/error_doc/generator/module_directory_walker.go +++ /dev/null @@ -1,22 +0,0 @@ -package generator - -import ( - "os" - "path/filepath" -) - -func (edg *ErrorDocumentGenerator) listUpErrorsGoFiles(startPath, errorsFileName string) error { - err := filepath.Walk(startPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if !info.IsDir() && info.Name() == errorsFileName { - edg.errorsFiles = append(edg.errorsFiles, path) - } - return nil - }) - if err != nil { - return err - } - return nil -} From 42c625c4f1d784c3fda91dc87741253151d088f9 Mon Sep 17 00:00:00 2001 From: 170210 Date: Wed, 9 Aug 2023 11:05:28 +0900 Subject: [PATCH 11/11] fixup: fix for comment Signed-off-by: 170210 --- tools/error_doc/generator/error_docs_generator.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/error_doc/generator/error_docs_generator.go b/tools/error_doc/generator/error_docs_generator.go index 0f814ef3b1..4faf47298a 100644 --- a/tools/error_doc/generator/error_docs_generator.go +++ b/tools/error_doc/generator/error_docs_generator.go @@ -26,6 +26,12 @@ type moduleInfo struct { errorDict []errorInfo } +type sortByCodespace []*moduleInfo + +func (b sortByCodespace) Len() int { return len(b) } +func (b sortByCodespace) Less(i, j int) bool { return b[i].codespace < b[j].codespace } +func (b sortByCodespace) Swap(i, j int) { b[i], b[j] = b[j], b[i] } + func NewErrorDocumentGenerator(p string) *ErrorDocumentGenerator { return &ErrorDocumentGenerator{ targetPath: p, @@ -67,10 +73,10 @@ func (edg *ErrorDocumentGenerator) extractModuleName() error { errorDict: []errorInfo{}, }) } - // sort keys and filepaths + // sort by key and codespace for moduleName := range edg.errorDocument { edg.modules = append(edg.modules, moduleName) - // sort.Strings(edg.errorDocument[moduleName]) + sort.Sort(sortByCodespace(edg.errorDocument[moduleName])) } sort.Strings(edg.modules) return nil