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
244 lines
6.2 KiB
Go
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
|
|
}
|