cb1f47016b2db2e73cfd69f7fdd77b5c7a2dff7d
[govpp.git] / cmd / binapi-generator / generate.go
1 // Copyright (c) 2017 Cisco and/or its affiliates.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package main
16
17 import (
18         "bytes"
19         "fmt"
20         "io"
21         "path/filepath"
22         "sort"
23         "strings"
24         "unicode"
25 )
26
27 // generatedCodeVersion indicates a version of the generated code.
28 // It is incremented whenever an incompatibility between the generated code and
29 // GoVPP api package is introduced; the generated code references
30 // a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion).
31 const generatedCodeVersion = 1
32
33 const (
34         inputFileExt  = ".api.json" // file extension of the VPP API files
35         outputFileExt = ".ba.go"    // file extension of the Go generated files
36
37         govppApiImportPath = "git.fd.io/govpp.git/api" // import path of the govpp API package
38
39         constModuleName = "ModuleName" // module name constant
40         constAPIVersion = "APIVersion" // API version constant
41         constVersionCrc = "VersionCrc" // version CRC constant
42
43         unionDataField = "XXX_UnionData" // name for the union data field
44
45         serviceApiName    = "RPCService"    // name for the RPC service interface
46         serviceImplName   = "serviceClient" // name for the RPC service implementation
47         serviceClientName = "ServiceClient" // name for the RPC service client
48 )
49
50 // context is a structure storing data for code generation
51 type context struct {
52         inputFile  string // input file with VPP API in JSON
53         outputFile string // output file with generated Go package
54
55         inputData []byte // contents of the input file
56
57         includeAPIVersion  bool // include constant with API version string
58         includeComments    bool // include parts of original source in comments
59         includeBinapiNames bool // include binary API names as struct tag
60         includeServices    bool // include service interface with client implementation
61
62         moduleName  string // name of the source VPP module
63         packageName string // name of the Go package being generated
64
65         packageData *Package // parsed package data
66 }
67
68 // newContext returns context details of the code generation task
69 func newContext(inputFile, outputDir string) (*context, error) {
70         if !strings.HasSuffix(inputFile, inputFileExt) {
71                 return nil, fmt.Errorf("invalid input file name: %q", inputFile)
72         }
73
74         ctx := &context{
75                 inputFile: inputFile,
76         }
77
78         // package name
79         inputFileName := filepath.Base(inputFile)
80         ctx.moduleName = inputFileName[:strings.Index(inputFileName, ".")]
81
82         // alter package names for modules that are reserved keywords in Go
83         switch ctx.moduleName {
84         case "interface":
85                 ctx.packageName = "interfaces"
86         case "map":
87                 ctx.packageName = "maps"
88         default:
89                 ctx.packageName = ctx.moduleName
90         }
91
92         // output file
93         packageDir := filepath.Join(outputDir, ctx.packageName)
94         outputFileName := ctx.packageName + outputFileExt
95         ctx.outputFile = filepath.Join(packageDir, outputFileName)
96
97         return ctx, nil
98 }
99
100 func generatePackage(ctx *context, w io.Writer) error {
101         logf("generating package %q", ctx.packageName)
102
103         fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
104         fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
105         fmt.Fprintln(w)
106
107         generateHeader(ctx, w)
108
109         // generate module desc
110         fmt.Fprintln(w, "const (")
111         fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName)
112         fmt.Fprintf(w, "\t%s = \"%s\"\n", constModuleName, ctx.moduleName)
113
114         if ctx.includeAPIVersion {
115                 if ctx.packageData.Version != "" {
116                         fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion)
117                         fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.packageData.Version)
118                 }
119                 fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc)
120                 fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.packageData.CRC)
121         }
122         fmt.Fprintln(w, ")")
123         fmt.Fprintln(w)
124
125         // generate enums
126         if len(ctx.packageData.Enums) > 0 {
127                 for _, enum := range ctx.packageData.Enums {
128                         generateEnum(ctx, w, &enum)
129                 }
130         }
131
132         // generate aliases
133         if len(ctx.packageData.Aliases) > 0 {
134                 for _, alias := range ctx.packageData.Aliases {
135                         generateAlias(ctx, w, &alias)
136                 }
137         }
138
139         // generate types
140         if len(ctx.packageData.Types) > 0 {
141                 for _, typ := range ctx.packageData.Types {
142                         generateType(ctx, w, &typ)
143                 }
144         }
145
146         // generate unions
147         if len(ctx.packageData.Unions) > 0 {
148                 for _, union := range ctx.packageData.Unions {
149                         generateUnion(ctx, w, &union)
150                 }
151         }
152
153         // generate messages
154         if len(ctx.packageData.Messages) > 0 {
155                 for _, msg := range ctx.packageData.Messages {
156                         generateMessage(ctx, w, &msg)
157                 }
158
159                 // generate message registrations
160                 fmt.Fprintln(w, "func init() {")
161                 for _, msg := range ctx.packageData.Messages {
162                         name := camelCaseName(msg.Name)
163                         fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name)
164                 }
165                 fmt.Fprintln(w, "}")
166                 fmt.Fprintln(w)
167
168                 // generate list of messages
169                 fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n")
170                 fmt.Fprintln(w, "func AllMessages() []api.Message {")
171                 fmt.Fprintln(w, "\treturn []api.Message{")
172                 for _, msg := range ctx.packageData.Messages {
173                         name := camelCaseName(msg.Name)
174                         fmt.Fprintf(w, "\t(*%s)(nil),\n", name)
175                 }
176                 fmt.Fprintln(w, "}")
177                 fmt.Fprintln(w, "}")
178         }
179
180         if ctx.includeServices {
181                 // generate services
182                 if len(ctx.packageData.Services) > 0 {
183                         generateServices(ctx, w, ctx.packageData.Services)
184                 }
185         }
186
187         generateFooter(ctx, w)
188
189         return nil
190 }
191
192 func generateHeader(ctx *context, w io.Writer) {
193         fmt.Fprintln(w, "/*")
194         fmt.Fprintf(w, "Package %s is a generated VPP binary API for '%s' module.\n", ctx.packageName, ctx.moduleName)
195         fmt.Fprintln(w)
196         fmt.Fprintln(w, "It consists of:")
197         printObjNum := func(obj string, num int) {
198                 if num > 0 {
199                         if num > 1 {
200                                 if strings.HasSuffix(obj, "s") {
201
202                                         obj += "es"
203                                 } else {
204                                         obj += "s"
205                                 }
206                         }
207                         fmt.Fprintf(w, "\t%3d %s\n", num, obj)
208                 }
209         }
210         printObjNum("enum", len(ctx.packageData.Enums))
211         printObjNum("alias", len(ctx.packageData.Aliases))
212         printObjNum("type", len(ctx.packageData.Types))
213         printObjNum("union", len(ctx.packageData.Unions))
214         printObjNum("message", len(ctx.packageData.Messages))
215         printObjNum("service", len(ctx.packageData.Services))
216         fmt.Fprintln(w, "*/")
217         fmt.Fprintf(w, "package %s\n", ctx.packageName)
218         fmt.Fprintln(w)
219
220         fmt.Fprintln(w, "import (")
221         fmt.Fprintf(w, "\tapi \"%s\"\n", govppApiImportPath)
222         fmt.Fprintf(w, "\tbytes \"%s\"\n", "bytes")
223         fmt.Fprintf(w, "\tcontext \"%s\"\n", "context")
224         fmt.Fprintf(w, "\tio \"%s\"\n", "io")
225         fmt.Fprintf(w, "\tstrconv \"%s\"\n", "strconv")
226         fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
227         fmt.Fprintln(w, ")")
228         fmt.Fprintln(w)
229 }
230
231 func generateFooter(ctx *context, w io.Writer) {
232         fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
233         fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
234         fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
235         fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
236         fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
237         fmt.Fprintln(w)
238
239         fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
240         fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
241         fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
242         fmt.Fprintf(w, "var _ = context.Background\n")
243         fmt.Fprintf(w, "var _ = io.Copy\n")
244         fmt.Fprintf(w, "var _ = strconv.Itoa\n")
245         fmt.Fprintf(w, "var _ = struc.Pack\n")
246 }
247
248 func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) {
249         if objKind == "service" {
250                 fmt.Fprintf(w, "// %s represents RPC service API for %s module.\n", goName, ctx.moduleName)
251         } else {
252                 fmt.Fprintf(w, "// %s represents VPP binary API %s '%s'.\n", goName, objKind, vppName)
253         }
254
255         if !ctx.includeComments {
256                 return
257         }
258
259         var isNotSpace = func(r rune) bool {
260                 return !unicode.IsSpace(r)
261         }
262
263         // print out the source of the generated object
264         mapType := false
265         objFound := false
266         objTitle := fmt.Sprintf(`"%s",`, vppName)
267         switch objKind {
268         case "alias", "service":
269                 objTitle = fmt.Sprintf(`"%s": {`, vppName)
270                 mapType = true
271         }
272
273         inputBuff := bytes.NewBuffer(ctx.inputData)
274         inputLine := 0
275
276         var trimIndent string
277         var indent int
278         for {
279                 line, err := inputBuff.ReadString('\n')
280                 if err != nil {
281                         break
282                 }
283                 inputLine++
284
285                 noSpaceAt := strings.IndexFunc(line, isNotSpace)
286                 if !objFound {
287                         indent = strings.Index(line, objTitle)
288                         if indent == -1 {
289                                 continue
290                         }
291                         trimIndent = line[:indent]
292                         // If no other non-whitespace character then we are at the message header.
293                         if trimmed := strings.TrimSpace(line); trimmed == objTitle {
294                                 objFound = true
295                                 fmt.Fprintln(w, "//")
296                         }
297                 } else if noSpaceAt < indent {
298                         break // end of the definition in JSON for array types
299                 } else if objFound && mapType && noSpaceAt <= indent {
300                         fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
301                         break // end of the definition in JSON for map types (aliases, services)
302                 }
303                 fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
304         }
305
306         fmt.Fprintln(w, "//")
307 }
308
309 func generateEnum(ctx *context, w io.Writer, enum *Enum) {
310         name := camelCaseName(enum.Name)
311         typ := binapiTypes[enum.Type]
312
313         logf(" writing enum %q (%s) with %d entries", enum.Name, name, len(enum.Entries))
314
315         // generate enum comment
316         generateComment(ctx, w, name, enum.Name, "enum")
317
318         // generate enum definition
319         fmt.Fprintf(w, "type %s %s\n", name, typ)
320         fmt.Fprintln(w)
321
322         // generate enum entries
323         fmt.Fprintln(w, "const (")
324         for _, entry := range enum.Entries {
325                 fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value)
326         }
327         fmt.Fprintln(w, ")")
328         fmt.Fprintln(w)
329
330         // generate enum conversion maps
331         fmt.Fprintf(w, "var %s_name = map[%s]string{\n", name, typ)
332         for _, entry := range enum.Entries {
333                 fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name)
334         }
335         fmt.Fprintln(w, "}")
336         fmt.Fprintln(w)
337
338         fmt.Fprintf(w, "var %s_value = map[string]%s{\n", name, typ)
339         for _, entry := range enum.Entries {
340                 fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value)
341         }
342         fmt.Fprintln(w, "}")
343         fmt.Fprintln(w)
344
345         fmt.Fprintf(w, "func (x %s) String() string {\n", name)
346         fmt.Fprintf(w, "\ts, ok := %s_name[%s(x)]\n", name, typ)
347         fmt.Fprintf(w, "\tif ok { return s }\n")
348         fmt.Fprintf(w, "\treturn strconv.Itoa(int(x))\n")
349         fmt.Fprintln(w, "}")
350         fmt.Fprintln(w)
351 }
352
353 func generateAlias(ctx *context, w io.Writer, alias *Alias) {
354         name := camelCaseName(alias.Name)
355
356         logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length)
357
358         // generate struct comment
359         generateComment(ctx, w, name, alias.Name, "alias")
360
361         // generate struct definition
362         fmt.Fprintf(w, "type %s ", name)
363
364         if alias.Length > 0 {
365                 fmt.Fprintf(w, "[%d]", alias.Length)
366         }
367
368         dataType := convertToGoType(ctx, alias.Type)
369         fmt.Fprintf(w, "%s\n", dataType)
370
371         fmt.Fprintln(w)
372 }
373
374 func generateUnion(ctx *context, w io.Writer, union *Union) {
375         name := camelCaseName(union.Name)
376
377         logf(" writing union %q (%s) with %d fields", union.Name, name, len(union.Fields))
378
379         // generate struct comment
380         generateComment(ctx, w, name, union.Name, "union")
381
382         // generate struct definition
383         fmt.Fprintln(w, "type", name, "struct {")
384
385         // maximum size for union
386         maxSize := getUnionSize(ctx, union)
387
388         // generate data field
389         fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize)
390
391         // generate end of the struct
392         fmt.Fprintln(w, "}")
393
394         // generate name getter
395         generateTypeNameGetter(w, name, union.Name)
396
397         // generate CRC getter
398         if union.CRC != "" {
399                 generateCrcGetter(w, name, union.CRC)
400         }
401
402         // generate getters for fields
403         for _, field := range union.Fields {
404                 fieldName := camelCaseName(field.Name)
405                 fieldType := convertToGoType(ctx, field.Type)
406                 generateUnionGetterSetter(w, name, fieldName, fieldType)
407         }
408
409         // generate union methods
410         //generateUnionMethods(w, name)
411
412         fmt.Fprintln(w)
413 }
414
415 // generateUnionMethods generates methods that implement struc.Custom
416 // interface to allow having XXX_uniondata field unexported
417 // TODO: do more testing when unions are actually used in some messages
418 /*func generateUnionMethods(w io.Writer, structName string) {
419         // generate struc.Custom implementation for union
420         fmt.Fprintf(w, `
421 func (u *%[1]s) Pack(p []byte, opt *struc.Options) (int, error) {
422         var b = new(bytes.Buffer)
423         if err := struc.PackWithOptions(b, u.union_data, opt); err != nil {
424                 return 0, err
425         }
426         copy(p, b.Bytes())
427         return b.Len(), nil
428 }
429 func (u *%[1]s) Unpack(r io.Reader, length int, opt *struc.Options) error {
430         return struc.UnpackWithOptions(r, u.union_data[:], opt)
431 }
432 func (u *%[1]s) Size(opt *struc.Options) int {
433         return len(u.union_data)
434 }
435 func (u *%[1]s) String() string {
436         return string(u.union_data[:])
437 }
438 `, structName)
439 }*/
440
441 func generateUnionGetterSetter(w io.Writer, structName string, getterField, getterStruct string) {
442         fmt.Fprintf(w, `
443 func %[1]s%[2]s(a %[3]s) (u %[1]s) {
444         u.Set%[2]s(a)
445         return
446 }
447 func (u *%[1]s) Set%[2]s(a %[3]s) {
448         var b = new(bytes.Buffer)
449         if err := struc.Pack(b, &a); err != nil {
450                 return
451         }
452         copy(u.%[4]s[:], b.Bytes())
453 }
454 func (u *%[1]s) Get%[2]s() (a %[3]s) {
455         var b = bytes.NewReader(u.%[4]s[:])
456         struc.Unpack(b, &a)
457         return
458 }
459 `, structName, getterField, getterStruct, unionDataField)
460 }
461
462 func generateType(ctx *context, w io.Writer, typ *Type) {
463         name := camelCaseName(typ.Name)
464
465         logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields))
466
467         // generate struct comment
468         generateComment(ctx, w, name, typ.Name, "type")
469
470         // generate struct definition
471         fmt.Fprintf(w, "type %s struct {\n", name)
472
473         // generate struct fields
474         for i, field := range typ.Fields {
475                 // skip internal fields
476                 switch strings.ToLower(field.Name) {
477                 case crcField, msgIdField:
478                         continue
479                 }
480
481                 generateField(ctx, w, typ.Fields, i)
482         }
483
484         // generate end of the struct
485         fmt.Fprintln(w, "}")
486
487         // generate name getter
488         generateTypeNameGetter(w, name, typ.Name)
489
490         // generate CRC getter
491         if typ.CRC != "" {
492                 generateCrcGetter(w, name, typ.CRC)
493         }
494
495         fmt.Fprintln(w)
496 }
497
498 func generateMessage(ctx *context, w io.Writer, msg *Message) {
499         name := camelCaseName(msg.Name)
500
501         logf(" writing message %q (%s) with %d fields", msg.Name, name, len(msg.Fields))
502
503         // generate struct comment
504         generateComment(ctx, w, name, msg.Name, "message")
505
506         // generate struct definition
507         fmt.Fprintf(w, "type %s struct {", name)
508
509         msgType := otherMessage
510         wasClientIndex := false
511
512         // generate struct fields
513         n := 0
514         for i, field := range msg.Fields {
515                 if i == 1 {
516                         if field.Name == clientIndexField {
517                                 // "client_index" as the second member,
518                                 // this might be an event message or a request
519                                 msgType = eventMessage
520                                 wasClientIndex = true
521                         } else if field.Name == contextField {
522                                 // reply needs "context" as the second member
523                                 msgType = replyMessage
524                         }
525                 } else if i == 2 {
526                         if wasClientIndex && field.Name == contextField {
527                                 // request needs "client_index" as the second member
528                                 // and "context" as the third member
529                                 msgType = requestMessage
530                         }
531                 }
532
533                 // skip internal fields
534                 switch strings.ToLower(field.Name) {
535                 case crcField, msgIdField:
536                         continue
537                 case clientIndexField, contextField:
538                         if n == 0 {
539                                 continue
540                         }
541                 }
542                 n++
543                 if n == 1 {
544                         fmt.Fprintln(w)
545                 }
546
547                 generateField(ctx, w, msg.Fields, i)
548         }
549
550         // generate end of the struct
551         fmt.Fprintln(w, "}")
552
553         // generate name getter
554         generateMessageNameGetter(w, name, msg.Name)
555
556         // generate CRC getter
557         generateCrcGetter(w, name, msg.CRC)
558
559         // generate message type getter method
560         generateMessageTypeGetter(w, name, msgType)
561
562         fmt.Fprintln(w)
563 }
564
565 func generateField(ctx *context, w io.Writer, fields []Field, i int) {
566         field := fields[i]
567
568         fieldName := strings.TrimPrefix(field.Name, "_")
569         fieldName = camelCaseName(fieldName)
570
571         dataType := convertToGoType(ctx, field.Type)
572         fieldType := dataType
573
574         // generate length field for strings
575         if field.Type == "string" && field.Length == 0 {
576                 fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName)
577         }
578
579         // check if it is array
580         if field.Length > 0 || field.SizeFrom != "" {
581                 if dataType == "uint8" {
582                         dataType = "byte"
583                 }
584                 if dataType == "string" && field.SpecifiedLen {
585                         fieldType = "string"
586                         dataType = "byte"
587                 } else {
588                         fieldType = "[]" + dataType
589                 }
590         }
591         fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
592
593         fieldTags := map[string]string{}
594
595         if field.Length > 0 && field.SpecifiedLen {
596                 // fixed size array
597                 fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType)
598         } else {
599                 for _, f := range fields {
600                         if f.SizeFrom == field.Name {
601                                 // variable sized array
602                                 sizeOfName := camelCaseName(f.Name)
603                                 fieldTags["struc"] = fmt.Sprintf("sizeof=%s", sizeOfName)
604                         }
605                 }
606         }
607
608         if ctx.includeBinapiNames {
609                 fieldTags["binapi"] = field.Name
610         }
611         if field.Meta.Limit > 0 {
612                 fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], field.Meta.Limit)
613         }
614
615         if len(fieldTags) > 0 {
616                 fmt.Fprintf(w, "\t`")
617                 var keys []string
618                 for k := range fieldTags {
619                         keys = append(keys, k)
620                 }
621                 sort.Strings(keys)
622                 var n int
623                 for _, tt := range keys {
624                         t, ok := fieldTags[tt]
625                         if !ok {
626                                 continue
627                         }
628                         if n > 0 {
629                                 fmt.Fprintf(w, " ")
630                         }
631                         n++
632                         fmt.Fprintf(w, `%s:"%s"`, tt, t)
633                 }
634                 fmt.Fprintf(w, "`")
635         }
636
637         fmt.Fprintln(w)
638 }
639
640 func generateMessageNameGetter(w io.Writer, structName, msgName string) {
641         fmt.Fprintf(w, `func (*%s) GetMessageName() string {
642         return %q
643 }
644 `, structName, msgName)
645 }
646
647 func generateTypeNameGetter(w io.Writer, structName, msgName string) {
648         fmt.Fprintf(w, `func (*%s) GetTypeName() string {
649         return %q
650 }
651 `, structName, msgName)
652 }
653
654 func generateCrcGetter(w io.Writer, structName, crc string) {
655         crc = strings.TrimPrefix(crc, "0x")
656         fmt.Fprintf(w, `func (*%s) GetCrcString() string {
657         return %q
658 }
659 `, structName, crc)
660 }
661
662 func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) {
663         fmt.Fprintln(w, "func (*"+structName+") GetMessageType() api.MessageType {")
664         if msgType == requestMessage {
665                 fmt.Fprintln(w, "\treturn api.RequestMessage")
666         } else if msgType == replyMessage {
667                 fmt.Fprintln(w, "\treturn api.ReplyMessage")
668         } else if msgType == eventMessage {
669                 fmt.Fprintln(w, "\treturn api.EventMessage")
670         } else {
671                 fmt.Fprintln(w, "\treturn api.OtherMessage")
672         }
673         fmt.Fprintln(w, "}")
674 }
675
676 func generateServices(ctx *context, w io.Writer, services []Service) {
677
678         // generate services comment
679         generateComment(ctx, w, serviceApiName, "services", "service")
680
681         // generate service api
682         fmt.Fprintf(w, "type %s interface {\n", serviceApiName)
683         for _, svc := range services {
684                 generateServiceMethod(ctx, w, &svc)
685                 fmt.Fprintln(w)
686         }
687         fmt.Fprintln(w, "}")
688         fmt.Fprintln(w)
689
690         // generate client implementation
691         fmt.Fprintf(w, "type %s struct {\n", serviceImplName)
692         fmt.Fprintf(w, "\tch api.Channel\n")
693         fmt.Fprintln(w, "}")
694         fmt.Fprintln(w)
695
696         // generate client constructor
697         fmt.Fprintf(w, "func New%s(ch api.Channel) %s {\n", serviceClientName, serviceApiName)
698         fmt.Fprintf(w, "\treturn &%s{ch}\n", serviceImplName)
699         fmt.Fprintln(w, "}")
700         fmt.Fprintln(w)
701
702         for _, svc := range services {
703                 method := camelCaseName(svc.RequestType)
704                 if m := strings.TrimSuffix(method, "Dump"); method != m {
705                         method = "Dump" + m
706                 }
707
708                 fmt.Fprintf(w, "func (c *%s) ", serviceImplName)
709                 generateServiceMethod(ctx, w, &svc)
710                 fmt.Fprintln(w, " {")
711                 if svc.Stream {
712                         streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
713                         fmt.Fprintf(w, "\tstream := c.ch.SendMultiRequest(in)\n")
714                         fmt.Fprintf(w, "\tx := &%s{stream}\n", streamImpl)
715                         fmt.Fprintf(w, "\treturn x, nil\n")
716                 } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
717                         fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
718                         fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
719                         fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
720                         fmt.Fprintf(w, "\treturn out, nil\n")
721                 } else {
722                         fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
723                         fmt.Fprintf(w, "\treturn nil\n")
724                 }
725                 fmt.Fprintln(w, "}")
726                 fmt.Fprintln(w)
727
728                 if svc.Stream {
729                         replyTyp := camelCaseName(svc.ReplyType)
730                         method := camelCaseName(svc.RequestType)
731                         if m := strings.TrimSuffix(method, "Dump"); method != m {
732                                 method = "Dump" + m
733                         }
734                         streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, method)
735
736                         fmt.Fprintf(w, "type %s interface {\n", streamApi)
737                         fmt.Fprintf(w, "\tRecv() (*%s, error)\n", replyTyp)
738                         fmt.Fprintln(w, "}")
739                         fmt.Fprintln(w)
740
741                         streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
742                         fmt.Fprintf(w, "type %s struct {\n", streamImpl)
743                         fmt.Fprintf(w, "\tapi.MultiRequestCtx\n")
744                         fmt.Fprintln(w, "}")
745                         fmt.Fprintln(w)
746
747                         fmt.Fprintf(w, "func (c *%s) Recv() (*%s, error) {\n", streamImpl, replyTyp)
748                         fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
749                         fmt.Fprintf(w, "\tstop, err := c.MultiRequestCtx.ReceiveReply(m)\n")
750                         fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
751                         fmt.Fprintf(w, "\tif stop { return nil, io.EOF }\n")
752                         fmt.Fprintf(w, "\treturn m, nil\n")
753                         fmt.Fprintln(w, "}")
754                         fmt.Fprintln(w)
755                 }
756         }
757
758         fmt.Fprintln(w)
759 }
760
761 func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
762         reqTyp := camelCaseName(svc.RequestType)
763
764         // method name is same as parameter type name by default
765         method := reqTyp
766         if svc.Stream {
767                 // use Dump as prefix instead of suffix for stream services
768                 if m := strings.TrimSuffix(method, "Dump"); method != m {
769                         method = "Dump" + m
770                 }
771         }
772
773         params := fmt.Sprintf("in *%s", reqTyp)
774         returns := "error"
775
776         if replyType := camelCaseName(svc.ReplyType); replyType != "" {
777                 var replyTyp string
778                 if svc.Stream {
779                         replyTyp = fmt.Sprintf("%s_%sClient", serviceApiName, method)
780                 } else {
781                         replyTyp = fmt.Sprintf("*%s", replyType)
782                 }
783                 returns = fmt.Sprintf("(%s, error)", replyTyp)
784         }
785
786         fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)
787 }