summaryrefslogtreecommitdiff
path: root/plugins/grpc/parser/parse.go
blob: d59b092700c0955720e07fc5bd69e398e71cddcf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package parser

import (
	"bytes"
	"io"
	"os"

	pp "github.com/emicklei/proto"
)

// Service contains information about singular GRPC service.
type Service struct {
	// Package defines service namespace.
	Package string

	// Name defines service name.
	Name string

	// Methods list.
	Methods []Method
}

// Method describes singular RPC method.
type Method struct {
	// Name is method name.
	Name string

	// StreamsRequest defines if method accept stream input.
	StreamsRequest bool

	// RequestType defines message name (from the same package) of method input.
	RequestType string

	// StreamsReturns defines if method streams result.
	StreamsReturns bool

	// ReturnsType defines message name (from the same package) of method return value.
	ReturnsType string
}

// File parses given proto file or returns error.
func File(file string, importPath string) ([]Service, error) {
	reader, _ := os.Open(file)
	defer reader.Close()

	return parse(reader, importPath)
}

// Bytes parses string into proto definition.
func Bytes(data []byte) ([]Service, error) {
	return parse(bytes.NewBuffer(data), "")
}

func parse(reader io.Reader, importPath string) ([]Service, error) {
	proto, err := pp.NewParser(reader).Parse()
	if err != nil {
		return nil, err
	}

	return parseServices(
		proto,
		parsePackage(proto),
		importPath,
	)
}

func parsePackage(proto *pp.Proto) string {
	for _, e := range proto.Elements {
		if p, ok := e.(*pp.Package); ok {
			return p.Name
		}
	}

	return ""
}

func parseServices(proto *pp.Proto, pkg string, importPath string) ([]Service, error) {
	services := make([]Service, 0)

	pp.Walk(proto, pp.WithService(func(service *pp.Service) {
		services = append(services, Service{
			Package: pkg,
			Name:    service.Name,
			Methods: parseMethods(service),
		})
	}))

	pp.Walk(proto, func(v pp.Visitee) {
		if i, ok := v.(*pp.Import); ok {
			if im, err := File(importPath+"/"+i.Filename, importPath); err == nil {
				services = append(services, im...)
			}
		}
	})

	return services, nil
}

func parseMethods(s *pp.Service) []Method {
	methods := make([]Method, 0)
	for _, e := range s.Elements {
		if m, ok := e.(*pp.RPC); ok {
			methods = append(methods, Method{
				Name:           m.Name,
				StreamsRequest: m.StreamsRequest,
				RequestType:    m.RequestType,
				StreamsReturns: m.StreamsReturns,
				ReturnsType:    m.ReturnsType,
			})
		}
	}

	return methods
}