protoc-gen-go-micro/graphql/utils.go
Vasiliy Tolstov 6d17a74ffb add graphql
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-06-11 20:45:45 +03:00

292 lines
8.2 KiB
Go

package generator
import (
"strings"
"unicode"
"unicode/utf8"
"github.com/jhump/protoreflect/desc"
"google.golang.org/protobuf/proto"
descriptor "google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/pluginpb"
gqlpb "go.unistack.org/micro-proto/v3/graphql"
)
func GraphqlMethodOptions(opts proto.Message) *gqlpb.Rpc {
if opts != nil {
v := proto.GetExtension(opts, gqlpb.E_Rpc)
if v != nil {
return v.(*gqlpb.Rpc)
}
}
return nil
}
func GraphqlServiceOptions(opts proto.Message) *gqlpb.Svc {
if opts != nil {
v := proto.GetExtension(opts, gqlpb.E_Svc)
if v != nil {
return v.(*gqlpb.Svc)
}
}
return nil
}
func GraphqlFieldOptions(opts proto.Message) *gqlpb.Field {
if opts != nil {
v := proto.GetExtension(opts, gqlpb.E_Field)
if v != nil && v.(*gqlpb.Field) != nil {
return v.(*gqlpb.Field)
}
}
return nil
}
func GraphqlOneofOptions(opts proto.Message) *gqlpb.Oneof {
if opts != nil {
v := proto.GetExtension(opts, gqlpb.E_Oneof)
if v != nil && v.(*gqlpb.Oneof) != nil {
return v.(*gqlpb.Oneof)
}
}
return nil
}
// GoCamelCase camel-cases a protobuf name for use as a Go identifier.
//
// If there is an interior underscore followed by a lower case letter,
// drop the underscore and convert the letter to upper case.
func GoCamelCase(s string) string {
// Invariant: if the next letter is lower case, it must be converted
// to upper case.
// That is, we process a word at a time, where words are marked by _ or
// upper case letter. Digits are treated as words.
var b []byte
for i := 0; i < len(s); i++ {
c := s[i]
switch {
case c == '.' && i+1 < len(s) && isASCIILower(s[i+1]):
// Skip over '.' in ".{{lowercase}}".
case c == '.':
b = append(b, '_') // convert '.' to '_'
case c == '_' && (i == 0 || s[i-1] == '.'):
// Convert initial '_' to ensure we start with a capital letter.
// Do the same for '_' after '.' to match historic behavior.
b = append(b, 'X') // convert '_' to 'X'
case c == '_' && i+1 < len(s) && isASCIILower(s[i+1]):
// Skip over '_' in "_{{lowercase}}".
case isASCIIDigit(c):
b = append(b, c)
default:
// Assume we have a letter now - if not, it's a bogus identifier.
// The next word is a sequence of characters that must start upper case.
if isASCIILower(c) {
c -= 'a' - 'A' // convert lowercase to uppercase
}
b = append(b, c)
// Accept lower case sequence that follows.
for ; i+1 < len(s) && isASCIILower(s[i+1]); i++ {
b = append(b, s[i+1])
}
}
}
return string(b)
}
func GetRequestType(rpcOpts *gqlpb.Rpc, svcOpts *gqlpb.Svc) gqlpb.Type {
if rpcOpts != nil && rpcOpts.Type != nil {
return *rpcOpts.Type
}
if svcOpts != nil && svcOpts.Type != nil {
return *svcOpts.Type
}
return gqlpb.Type_DEFAULT
}
func CreateDescriptorsFromProto(req *pluginpb.CodeGeneratorRequest) (descs []*desc.FileDescriptor, err error) {
dd, err := desc.CreateFileDescriptorsFromSet(&descriptor.FileDescriptorSet{
File: req.GetProtoFile(),
})
if err != nil {
return nil, err
}
for _, d := range dd {
for _, filename := range req.FileToGenerate {
if filename == d.GetName() {
descs = append(descs, d)
}
}
}
return
}
func ResolveProtoFilesRecursively(descs []*desc.FileDescriptor) (files FileDescriptors) {
for _, f := range descs {
files = append(files, ResolveProtoFilesRecursively(f.GetDependencies())...)
files = append(files, f)
}
return files
}
type FileDescriptors []*desc.FileDescriptor
func (ds FileDescriptors) AsFileDescriptorProto() (files []*descriptor.FileDescriptorProto) {
for _, d := range ds {
files = append(files, d.AsFileDescriptorProto())
}
return
}
// Split splits the camelcase word and returns a list of words. It also
// supports digits. Both lower camel case and upper camel case are supported.
// For more info please check: http://en.wikipedia.org/wiki/CamelCase
//
// Examples
//
// "" => [""]
// "lowercase" => ["lowercase"]
// "Class" => ["Class"]
// "MyClass" => ["My", "Class"]
// "MyC" => ["My", "C"]
// "HTML" => ["HTML"]
// "PDFLoader" => ["PDF", "Loader"]
// "AString" => ["A", "String"]
// "SimpleXMLParser" => ["Simple", "XML", "Parser"]
// "vimRPCPlugin" => ["vim", "RPC", "Plugin"]
// "GL11Version" => ["GL", "11", "Version"]
// "99Bottles" => ["99", "Bottles"]
// "May5" => ["May", "5"]
// "BFG9000" => ["BFG", "9000"]
// "BöseÜberraschung" => ["Böse", "Überraschung"]
// "Two spaces" => ["Two", " ", "spaces"]
// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
//
// Splitting rules
//
// 1. If string is not valid UTF-8, return it without splitting as
// single item array.
// 2. Assign all unicode characters into one of 4 sets: lower case
// letters, upper case letters, numbers, and all other characters.
// 3. Iterate through characters of string, introducing splits
// between adjacent characters that belong to different sets.
// 4. Iterate through array of split strings, and if a given string
// is upper case:
// if subsequent string is lower case:
// move last character of upper case string to beginning of
// lower case string
func SplitCamelCase(src string) (entries []string) {
// don't split invalid utf8
if !utf8.ValidString(src) {
return []string{src}
}
entries = []string{}
var runes [][]rune
lastClass := 0
class := 0
// split into fields based on class of unicode character
for _, r := range src {
switch true {
case unicode.IsLower(r):
class = 1
case unicode.IsUpper(r):
class = 2
case unicode.IsDigit(r):
class = 3
default:
class = 4
}
if class == lastClass {
runes[len(runes)-1] = append(runes[len(runes)-1], r)
} else {
runes = append(runes, []rune{r})
}
lastClass = class
}
// handle upper case -> lower case sequences, e.g.
// "PDFL", "oader" -> "PDF", "Loader"
for i := 0; i < len(runes)-1; i++ {
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
runes[i] = runes[i][:len(runes[i])-1]
}
}
// construct []string from results
for _, s := range runes {
if len(s) > 0 {
entries = append(entries, string(s))
}
}
return
}
// CamelCase returns the CamelCased name.
// If there is an interior underscore followed by a lower case letter,
// drop the underscore and convert the letter to upper case.
// There is a remote possibility of this rewrite causing a name collision,
// but it's so remote we're prepared to pretend it's nonexistent - since the
// C++ generator lowercases names, it's extremely unlikely to have two fields
// with different capitalizations.
// In short, _my_field_name_2 becomes XMyFieldName_2.
func CamelCase(s string) string {
if s == "" {
return ""
}
t := make([]byte, 0, 32)
i := 0
if s[0] == '_' {
// Need a capital letter; drop the '_'.
t = append(t, 'X')
i++
}
// Invariant: if the next letter is lower case, it must be converted
// to upper case.
// That is, we process a word at a time, where words are marked by _ or
// upper case letter. Digits are treated as words.
for ; i < len(s); i++ {
c := s[i]
if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) {
continue // Skip the underscore in s.
}
if isASCIIDigit(c) {
t = append(t, c)
continue
}
// Assume we have a letter now - if not, it's a bogus identifier.
// The next word is a sequence of characters that must start upper case.
if isASCIILower(c) {
c ^= ' ' // Make it a capital letter.
}
t = append(t, c) // Guaranteed not lower case.
// Accept lower case sequence that follows.
for i+1 < len(s) && isASCIILower(s[i+1]) {
i++
t = append(t, s[i])
}
}
return string(t)
}
// CamelCaseSlice is like CamelCase, but the argument is a slice of strings to
// be joined with "_".
func CamelCaseSlice(elem []string) string { return CamelCase(strings.Join(elem, "_")) }
// Is c an ASCII lower-case letter?
func isASCIILower(c byte) bool {
return 'a' <= c && c <= 'z'
}
// Is c an ASCII digit?
func isASCIIDigit(c byte) bool {
return '0' <= c && c <= '9'
}
func ToLowerFirst(s string) string {
if len(s) > 0 {
return string(unicode.ToLower(rune(s[0]))) + s[1:]
}
return ""
}