Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

docs: create ERRORS.md for x/module #1059

Merged
merged 11 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/check-generated.yml
Original file line number Diff line number Diff line change
@@ -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'

- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1

- name: Check generated error docs
run: |
make error-doc-gen
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 'make error-doc' and update this PR."
echo ">>"
exit 1
fi
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
* (docs) [\#1059](https://github.com/Finschia/finschia-sdk/pull/1059) create ERRORS.md for x/module
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,10 @@ libsodium:
fi
.PHONY: libsodium

error-doc-gen:
cd ./tools/error_doc && go run ./
.PHONY: error-doc-gen

###############################################################################
### release ###
###############################################################################
Expand Down
175 changes: 175 additions & 0 deletions tools/error_doc/generator/error_docs_generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
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
}

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,
errorDocument: make(map[string][]*moduleInfo),
}
}

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
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 by key and codespace
for moduleName := range edg.errorDocument {
edg.modules = append(edg.modules, moduleName)
sort.Sort(sortByCodespace(edg.errorDocument[moduleName]))
}
sort.Strings(edg.modules)
return nil
}

func (edg ErrorDocumentGenerator) outputCategory(file *os.File) {
file.WriteString("<!-- TOC -->\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("<!-- TOC -->\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.parseErrorsFile(); err != nil {
return err
}
if err := mod.parseKeysFile(); 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
}
95 changes: 95 additions & 0 deletions tools/error_doc/generator/errors_file_parser.go
Original file line number Diff line number Diff line change
@@ -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) parseErrorsFile() 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
}
54 changes: 54 additions & 0 deletions tools/error_doc/generator/keys_file_parser.go
Original file line number Diff line number Diff line change
@@ -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) parseKeysFile() 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
}
5 changes: 5 additions & 0 deletions tools/error_doc/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/Finschia/finschia-sdk/tools/error_doc

go 1.20

require golang.org/x/text v0.11.0
2 changes: 2 additions & 0 deletions tools/error_doc/go.sum
Original file line number Diff line number Diff line change
@@ -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=
Loading
Loading