Some checks failed
Docs Deploy / build_and_deploy (push) Has been cancelled
Generate Docs / cli (push) Has been cancelled
Generate Config Doc / cli (push) Has been cancelled
Go formatting / go-formatting (push) Has been cancelled
Check links / markdown-link-check (push) Has been cancelled
Integration / pre-test (push) Has been cancelled
Integration / test on (push) Has been cancelled
Integration / status (push) Has been cancelled
Lint / Lint Go code (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
209 lines
5 KiB
Go
209 lines
5 KiB
Go
package cosmosgen
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/blang/semver/v4"
|
|
"github.com/iancoleman/strcase"
|
|
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/cache"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/cosmosbuf"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/dirchange"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/errors"
|
|
swaggercombine "git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/swagger-combine"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/xos"
|
|
)
|
|
|
|
const (
|
|
specCacheNamespace = "generate.openapi.spec"
|
|
specFilename = "swagger.config.json"
|
|
)
|
|
|
|
func (g *generator) openAPITemplate() string {
|
|
return filepath.Join(g.appPath, g.protoDir, "buf.gen.swagger.yaml")
|
|
}
|
|
|
|
func (g *generator) generateOpenAPISpec(ctx context.Context, excludeList ...string) error {
|
|
var (
|
|
specDirs = make([]string, 0)
|
|
conf = swaggercombine.New("HTTP API Console", g.goModPath)
|
|
)
|
|
defer func() {
|
|
for _, dir := range specDirs {
|
|
_ = os.RemoveAll(dir)
|
|
}
|
|
}()
|
|
|
|
specCache := cache.New[[]byte](g.cacheStorage, specCacheNamespace)
|
|
|
|
var hasAnySpecChanged bool
|
|
|
|
// gen generates a spec for a module where it's source code resides at src.
|
|
// and adds needed swaggercombine configure for it.
|
|
gen := func(appPath, protoDir, name string) error {
|
|
name = strcase.ToCamel(name)
|
|
protoPath := filepath.Join(appPath, protoDir)
|
|
|
|
// check if directory exists
|
|
if _, err := os.Stat(protoPath); os.IsNotExist(err) {
|
|
var err error
|
|
protoPath, err = findInnerProtoFolder(appPath)
|
|
if err != nil {
|
|
// if proto directory does not exist, we just skip it
|
|
log.Print(err.Error())
|
|
return nil
|
|
}
|
|
}
|
|
|
|
dir, err := os.MkdirTemp("", "gen-openapi-module-spec")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
specDirs = append(specDirs, dir)
|
|
|
|
var noChecksum bool
|
|
checksum, err := dirchange.ChecksumFromPaths(appPath, protoDir)
|
|
if errors.Is(err, dirchange.ErrNoFile) {
|
|
noChecksum = true
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
cacheKey := fmt.Sprintf("%x", checksum)
|
|
if !noChecksum {
|
|
existingSpec, err := specCache.Get(cacheKey)
|
|
if err != nil && !errors.Is(err, cache.ErrorNotFound) {
|
|
return err
|
|
}
|
|
|
|
if !errors.Is(err, cache.ErrorNotFound) {
|
|
specPath := filepath.Join(dir, specFilename)
|
|
if err := os.WriteFile(specPath, existingSpec, 0o600); err != nil {
|
|
return err
|
|
}
|
|
return conf.AddSpec(name, specPath, true)
|
|
}
|
|
}
|
|
|
|
hasAnySpecChanged = true
|
|
if err = g.buf.Generate(
|
|
ctx,
|
|
protoPath,
|
|
dir,
|
|
g.openAPITemplate(),
|
|
cosmosbuf.ExcludeFiles(
|
|
append(excludeList, []string{
|
|
"*/strangelove_ventures/poa/*",
|
|
"*/osmosis-labs/fee-abstraction/*",
|
|
"*/module.proto",
|
|
"*/testutil/*",
|
|
"*/testdata/*",
|
|
"*/cosmos/orm/*",
|
|
"*/cosmos/reflection/*",
|
|
"*/cosmos/app/v1alpha1/*",
|
|
"*/cosmos/tx/config/v1/config.proto",
|
|
"*/cosmos/msg/textual/v1/textual.proto",
|
|
"*/cosmos/vesting/v1beta1/vesting.proto",
|
|
}...)...,
|
|
),
|
|
cosmosbuf.FileByFile(),
|
|
); err != nil {
|
|
return errors.Wrapf(err, "failed to generate openapi spec %s, probably you need to exclude some proto files", protoPath)
|
|
}
|
|
|
|
specs, err := xos.FindFiles(dir, xos.WithExtension(xos.JSONFile))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, spec := range specs {
|
|
f, err := os.ReadFile(spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if no checksum, the cacheKey is wrong, so we do not save it
|
|
if !noChecksum {
|
|
if err := specCache.Put(cacheKey, f); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := conf.AddSpec(name, spec, true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// generate specs for each module and persist them in the file system
|
|
// after add their path and config to swaggercombine.Config so we can combine them
|
|
// into a single spec.
|
|
|
|
// protoc openapi generator acts weird on concurrent run, so do not use goroutines here.
|
|
if err := gen(g.appPath, g.protoDir, g.goModPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
doneMods := make(map[string]struct{})
|
|
for _, modules := range g.thirdModules {
|
|
for _, m := range modules {
|
|
path := extractRootModulePath(m.Pkg.Path)
|
|
|
|
if _, ok := doneMods[path]; ok {
|
|
continue
|
|
}
|
|
doneMods[path] = struct{}{}
|
|
|
|
if err := gen(path, "proto", m.Name); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
out := g.opts.openAPISpecOut
|
|
|
|
if !hasAnySpecChanged {
|
|
// In case the generated output has been changed
|
|
changed, err := dirchange.HasDirChecksumChanged(specCache, out, g.appPath, out)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !changed {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// combine specs into one and save to out.
|
|
if err := conf.Combine(out); err != nil {
|
|
return err
|
|
}
|
|
|
|
return dirchange.SaveDirChecksum(specCache, out, g.appPath, out)
|
|
}
|
|
|
|
func extractRootModulePath(fullPath string) string {
|
|
var (
|
|
segments = strings.Split(fullPath, "/")
|
|
modulePath = "/"
|
|
)
|
|
|
|
for _, segment := range segments {
|
|
modulePath = filepath.Join(modulePath, segment)
|
|
segmentName := strings.Split(segment, "@")
|
|
if len(segmentName) > 1 {
|
|
if _, err := semver.ParseTolerant(segmentName[1]); err == nil {
|
|
return modulePath
|
|
}
|
|
}
|
|
}
|
|
return fullPath
|
|
}
|