mukan-ignite/ignite/pkg/protoanalysis/package.go
Mukan Erkin Törük 26b204bd04
Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
feat: fork Ignite CLI v29 as Mukan Ignite — remove cosmos-sdk restrictions
2026-05-11 03:31:37 +03:00

244 lines
6.2 KiB
Go

package protoanalysis
import (
"regexp"
"strings"
"golang.org/x/mod/semver"
"github.com/ignite/cli/v29/ignite/pkg/errors"
)
type (
// Packages represents slice of Package.
Packages []Package
PkgName string
// Package represents a proto pkg.
Package struct {
// Name of the proto pkg.
Name string `json:"name,omitempty"`
// Path of the package in the fs.
Path string `json:"path,omitempty"`
// Files is a list of .proto files in the package.
Files Files `json:"files,omitempty"`
// GoImportName is the go package name of proto package.
GoImportName string `json:"go_import_name,omitempty"`
// Messages is a list of proto messages defined in the package.
Messages []Message `json:"messages,omitempty"`
// Services is a list of RPC services.
Services []Service `json:"services,omitempty"`
}
)
var regexBetaVersion = regexp.MustCompile("^v[0-9]+(beta|alpha)[0-9]+")
// ErrMessageNotFound is returned when a proto message cannot be found in a package.
var ErrMessageNotFound = errors.New("no message found")
// ModuleName retrieves the single module name of the package.
func (p Package) ModuleName() (name string) {
names := strings.Split(p.Name, ".")
for i := len(names) - 1; i >= 0; i-- {
name = names[i]
if !semver.IsValid(name) && !regexBetaVersion.MatchString(name) {
break
}
}
return
}
// MessageByName finds a message by its name inside Package.
func (p Package) MessageByName(name string) (Message, error) {
message, ok := p.FindMessageByName(name)
if !ok {
return Message{}, ErrMessageNotFound
}
return message, nil
}
// FindMessageByName finds a message by its name inside Package.
// It accepts plain message names, current-package qualified names and nested message names.
func (p Package) FindMessageByName(name string) (Message, bool) {
for _, candidate := range candidateMessageNames(p.Name, name) {
for _, message := range p.Messages {
if message.Name == candidate {
return message, true
}
}
}
if !strings.Contains(strings.TrimPrefix(name, "."), ".") {
leafName := leafMessageName(name)
var leafMatches []Message
for _, message := range p.Messages {
if leafMessageName(message.Name) == leafName {
leafMatches = append(leafMatches, message)
}
}
if len(leafMatches) == 1 {
return leafMatches[0], true
}
}
return Message{}, false
}
// GoImportPath retrieves the Go import path.
func (p Package) GoImportPath() string {
return strings.Split(p.GoImportName, ";")[0]
}
// Files retrieves the files from the package list.
func (p Packages) Files() Files {
var files []File
for _, pkg := range p {
files = append(files, pkg.Files...)
}
return files
}
type (
Files []File
File struct {
// Path of the file.
Path string `json:"path,omitempty"`
// Dependencies is a list of imported proto packages.
Dependencies []string `json:"dependencies,omitempty"`
}
)
func candidateMessageNames(pkgName, name string) []string {
candidates := []string{name}
canonical := canonicalMessageName(pkgName, name)
if canonical != "" && canonical != name {
candidates = append(candidates, canonical)
}
return candidates
}
func canonicalMessageName(pkgName, name string) string {
name = strings.TrimPrefix(name, ".")
if pkgName != "" {
name = strings.TrimPrefix(name, pkgName+".")
}
return strings.ReplaceAll(name, ".", "_")
}
func leafMessageName(name string) string {
if index := strings.LastIndex(name, "_"); index >= 0 {
return name[index+1:]
}
if index := strings.LastIndex(name, "."); index >= 0 {
return name[index+1:]
}
return name
}
// Paths retrieves the list of paths from the files.
func (f Files) Paths() []string {
var paths []string
for _, ff := range f {
paths = append(paths, ff.Path)
}
return paths
}
type (
// Message represents a proto message.
Message struct {
// Name of the message.
Name string `json:"name,omitempty"`
// Path of the proto file where the message is defined.
Path string `json:"path,omitempty"`
// HighestFieldNumber is the highest field number among fields of the message.
// This allows to determine new field number when writing to proto message.
HighestFieldNumber int `json:"highest_field_number,omitempty"`
// Fields contains message's field names and types.
Fields map[string]string `json:"fields,omitempty"`
}
// Service is an RPC service.
Service struct {
// Name of the services.
Name string `json:"name,omitempty"`
// RPCFuncs is a list of RPC funcs of the service.
RPCFuncs []RPCFunc `json:"functions,omitempty"`
}
// RPCFunc is an RPC func.
RPCFunc struct {
// Name of the RPC func.
Name string `json:"name,omitempty"`
// RequestType is the request type of RPC func.
RequestType string `json:"request_type,omitempty"`
// ReturnsType is the response type of RPC func.
ReturnsType string `json:"return_type,omitempty"`
// HTTPRules keeps info about http rules of an RPC func.
// spec:
// https://github.com/googleapis/googleapis/blob/master/google/api/http.proto.
HTTPRules []HTTPRule `json:"http_rules,omitempty"`
}
// HTTPRule keeps info about a configured http rule of an RPC func.
HTTPRule struct {
// Endpoint is the HTTP endpoint path pattern.
Endpoint string `json:"endpoint,omitempty"`
// Params is a list of parameters defined in the HTTP endpoint itself.
Params []string `json:"params,omitempty"`
// HasQuery indicates if there is a request query.
HasQuery bool `json:"has_query,omitempty"`
// QueryFields is a list of query fields defined in the HTTP endpoint.
QueryFields map[string]string `json:"query_fields,omitempty"`
// HasBody indicates if there is a request payload.
HasBody bool `json:"has_body,omitempty"`
// BodyFields is a list of body fields defined in the HTTP endpoint.
BodyFields map[string]string `json:"body_fields,omitempty"`
}
)
// IsPaginated checks if the HTTPRule is paginated based on its QueryFields.
func (hr HTTPRule) IsPaginated() bool {
if len(hr.QueryFields) == 0 {
return false
}
for _, fieldType := range hr.QueryFields {
// Message field type suffix check to match common pagination types:
// cosmos.base.query.v1beta1.PageRequest
// cosmos.base.query.v1beta1.PageResponse
if strings.HasSuffix(fieldType, "PageRequest") || strings.HasSuffix(fieldType, "PageResponse") {
return true
}
}
return false
}