blob: 1d92ac0ebf7c9e5fffec2545857061c105860de0 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package golang implements Go code generation from compiled VDL packages.
package golang
import (
"bytes"
"fmt"
"go/format"
"path"
"strings"
"text/template"
"v.io/v23/vdl"
"v.io/v23/vdlroot/vdltool"
"v.io/x/ref/lib/vdl/compile"
"v.io/x/ref/lib/vdl/parse"
"v.io/x/ref/lib/vdl/vdlutil"
)
type goData struct {
File *compile.File
Env *compile.Env
Imports *goImports
}
// testingMode is only set to true in tests, to make testing simpler.
var testingMode = false
func (data goData) Pkg(pkgPath string) string {
if testingMode {
return path.Base(pkgPath) + "."
}
// Special-case to avoid adding package qualifiers if we're generating code
// for that package.
if data.File.Package.GenPath == pkgPath {
return ""
}
if local := data.Imports.LookupLocal(pkgPath); local != "" {
return local + "."
}
data.Env.Errorf(data.File, parse.Pos{}, "missing package %q", pkgPath)
return ""
}
// Generate takes a populated compile.File and returns a byte slice containing
// the generated Go source code.
func Generate(file *compile.File, env *compile.Env) []byte {
validateGoConfig(file, env)
data := goData{
File: file,
Env: env,
Imports: newImports(file, env),
}
// The implementation uses the template mechanism from text/template and
// executes the template against the goData instance.
var buf bytes.Buffer
if err := goTemplate.Execute(&buf, data); err != nil {
// We shouldn't see an error; it means our template is buggy.
panic(fmt.Errorf("vdl: couldn't execute template: %v", err))
}
// Use gofmt to format the generated source.
pretty, err := format.Source(buf.Bytes())
if err != nil {
// We shouldn't see an error; it means we generated invalid code.
fmt.Printf("%s", buf.Bytes())
panic(fmt.Errorf("vdl: generated invalid Go code: %v", err))
}
return pretty
}
// The native types feature is hard to use correctly. E.g. the package
// containing the wire type must be imported into your Go binary in order for
// the wire<->native registration to work, which is hard to ensure. E.g.
//
// package base // VDL package
// type Wire int // has native type native.Int
//
// package dep // VDL package
// import "base"
// type Foo struct {
// X base.Wire
// }
//
// The Go code for package "dep" imports "native", rather than "base":
//
// package dep // Go package generated from VDL package
// import "native"
// type Foo struct {
// X native.Int
// }
//
// Note that when you import the "dep" package in your own code, you always use
// native.Int, rather than base.Wire; the base.Wire representation is only used
// as the wire format, but doesn't appear in generated code. But in order for
// this to work correctly, the "base" package must imported. This is tricky.
//
// Restrict the feature to these whitelisted VDL packages for now.
var nativeTypePackageWhitelist = map[string]bool{
"time": true,
"v.io/x/ref/lib/vdl/testdata/nativetest": true,
"v.io/v23/security": true,
}
func validateGoConfig(file *compile.File, env *compile.Env) {
pkg := file.Package
vdlconfig := path.Join(pkg.GenPath, "vdl.config")
// Validate native type configuration. Since native types are hard to use, we
// restrict them to a built-in whitelist of packages for now.
if len(pkg.Config.Go.WireToNativeTypes) > 0 && !nativeTypePackageWhitelist[pkg.Path] {
env.Errors.Errorf("%s: Go.WireToNativeTypes is restricted to whitelisted VDL packages", vdlconfig)
}
// Make sure each wire type is actually defined in the package, and required
// fields are all filled in.
for wire, native := range pkg.Config.Go.WireToNativeTypes {
if def := pkg.ResolveType(wire); def == nil {
env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes undefined", vdlconfig, wire)
}
if native.Type == "" {
env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes invalid (empty GoType.Type)", vdlconfig, wire)
}
for _, imp := range native.Imports {
if imp.Path == "" || imp.Name == "" {
env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes invalid (empty GoImport.Path or Name)", vdlconfig, wire)
continue
}
importPrefix := imp.Name + "."
if !strings.Contains(native.Type, importPrefix) {
env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes invalid (native type %q doesn't contain import prefix %q)", vdlconfig, wire, native.Type, importPrefix)
}
}
}
}
var goTemplate *template.Template
// The template mechanism is great at high-level formatting and simple
// substitution, but is bad at more complicated logic. We define some functions
// that we can use in the template so that when things get complicated we back
// off to a regular function.
func init() {
funcMap := template.FuncMap{
"firstRuneToExport": vdlutil.FirstRuneToExportCase,
"firstRuneToUpper": vdlutil.FirstRuneToUpper,
"firstRuneToLower": vdlutil.FirstRuneToLower,
"errorName": errorName,
"nativeIdent": nativeIdent,
"typeGo": typeGo,
"typeDefGo": typeDefGo,
"constDefGo": constDefGo,
"tagValue": tagValue,
"embedGo": embedGo,
"isStreamingMethod": isStreamingMethod,
"hasStreamingMethods": hasStreamingMethods,
"docBreak": docBreak,
"quoteStripDoc": parse.QuoteStripDoc,
"argNames": argNames,
"argTypes": argTypes,
"argNameTypes": argNameTypes,
"argParens": argParens,
"uniqueName": uniqueName,
"uniqueNameImpl": uniqueNameImpl,
"serverCallVar": serverCallVar,
"serverCallStubVar": serverCallStubVar,
"outArgsClient": outArgsClient,
"clientStubImpl": clientStubImpl,
"clientFinishImpl": clientFinishImpl,
"serverStubImpl": serverStubImpl,
"reInitStreamValue": reInitStreamValue,
"nativeConversionsInFile": nativeConversionsInFile,
}
goTemplate = template.Must(template.New("genGo").Funcs(funcMap).Parse(genGo))
}
func errorName(def *compile.ErrorDef, file *compile.File) string {
switch {
case def.Exported:
return "Err" + def.Name
default:
return "err" + vdlutil.FirstRuneToUpper(def.Name)
}
}
func isStreamingMethod(method *compile.Method) bool {
return method.InStream != nil || method.OutStream != nil
}
func hasStreamingMethods(methods []*compile.Method) bool {
for _, method := range methods {
if isStreamingMethod(method) {
return true
}
}
return false
}
// docBreak adds a "//\n" break to separate previous comment lines and doc. If
// doc is empty it returns the empty string.
func docBreak(doc string) string {
if doc == "" {
return ""
}
return "//\n" + doc
}
// argTypes returns a comma-separated list of each type from args.
func argTypes(first, last string, data goData, args []*compile.Field) string {
var result []string
if first != "" {
result = append(result, first)
}
for _, arg := range args {
result = append(result, typeGo(data, arg.Type))
}
if last != "" {
result = append(result, last)
}
return strings.Join(result, ", ")
}
// argNames returns a comma-separated list of each name from args. If argPrefix
// is empty, the name specified in args is used; otherwise the name is prefixD,
// where D is the position of the argument.
func argNames(boxPrefix, argPrefix, first, second, last string, args []*compile.Field) string {
var result []string
if first != "" {
result = append(result, first)
}
if second != "" {
result = append(result, second)
}
for ix, arg := range args {
name := arg.Name
if argPrefix != "" {
name = fmt.Sprintf("%s%d", argPrefix, ix)
}
if arg.Type == vdl.ErrorType {
// TODO(toddw): Also need to box user-defined external interfaces. Or can
// we remove this special-case now?
name = boxPrefix + name
}
result = append(result, name)
}
if last != "" {
result = append(result, last)
}
return strings.Join(result, ", ")
}
// argNameTypes returns a comma-separated list of "name type" from args. If
// argPrefix is empty, the name specified in args is used; otherwise the name is
// prefixD, where D is the position of the argument. If argPrefix is empty and
// no names are specified in args, no names will be output.
func argNameTypes(argPrefix, first, second, last string, data goData, args []*compile.Field) string {
noNames := argPrefix == "" && !hasArgNames(args)
var result []string
if first != "" {
result = append(result, maybeStripArgName(first, noNames))
}
if second != "" {
result = append(result, maybeStripArgName(second, noNames))
}
for ax, arg := range args {
var name string
switch {
case noNames:
break
case argPrefix == "":
name = arg.Name + " "
default:
name = fmt.Sprintf("%s%d ", argPrefix, ax)
}
result = append(result, name+typeGo(data, arg.Type))
}
if last != "" {
result = append(result, maybeStripArgName(last, noNames))
}
return strings.Join(result, ", ")
}
func hasArgNames(args []*compile.Field) bool {
// VDL guarantees that either all args are named, or none of them are.
return len(args) > 0 && args[0].Name != ""
}
// maybeStripArgName strips away the first space-terminated token from arg, only
// if strip is true.
func maybeStripArgName(arg string, strip bool) string {
if index := strings.Index(arg, " "); index != -1 && strip {
return arg[index+1:]
}
return arg
}
// argParens takes a list of 0 or more arguments, and adds parens only when
// necessary; if args contains any commas or spaces, we must add parens.
func argParens(argList string) string {
if strings.IndexAny(argList, ", ") > -1 {
return "(" + argList + ")"
}
return argList
}
// uniqueName returns a unique name based on the interface, method and suffix.
func uniqueName(iface *compile.Interface, method *compile.Method, suffix string) string {
return iface.Name + method.Name + suffix
}
// uniqueNameImpl returns uniqueName with an "impl" prefix.
func uniqueNameImpl(iface *compile.Interface, method *compile.Method, suffix string) string {
return "impl" + uniqueName(iface, method, suffix)
}
// The first arg of every server method is a context; the type is either a typed
// context for streams, or rpc.ServerCall for non-streams.
func serverCallVar(data goData, iface *compile.Interface, method *compile.Method) string {
if isStreamingMethod(method) {
return "call " + uniqueName(iface, method, "ServerCall")
}
return "call " + data.Pkg("v.io/v23/rpc") + "ServerCall"
}
// The first arg of every server stub method is a context; the type is either a
// typed context stub for streams, or rpc.ServerCall for non-streams.
func serverCallStubVar(data goData, iface *compile.Interface, method *compile.Method) string {
if isStreamingMethod(method) {
return "call *" + uniqueName(iface, method, "ServerCallStub")
}
return "call " + data.Pkg("v.io/v23/rpc") + "ServerCall"
}
// outArgsClient returns the out args of an interface method on the client,
// wrapped in parens if necessary. The client side always returns a final
// error, in addition to the regular out-args.
func outArgsClient(argPrefix string, data goData, iface *compile.Interface, method *compile.Method) string {
first, args := "", method.OutArgs
if isStreamingMethod(method) {
first, args = "ocall "+uniqueName(iface, method, "ClientCall"), nil
}
return argParens(argNameTypes(argPrefix, first, "", "err error", data, args))
}
// clientStubImpl returns the interface method client stub implementation.
func clientStubImpl(data goData, iface *compile.Interface, method *compile.Method) string {
var buf bytes.Buffer
inargs := "nil"
if len(method.InArgs) > 0 {
inargs = "[]interface{}{" + argNames("&", "i", "", "", "", method.InArgs) + "}"
}
switch {
case isStreamingMethod(method):
fmt.Fprint(&buf, "\tvar call "+data.Pkg("v.io/v23/rpc")+"ClientCall\n")
fmt.Fprintf(&buf, "\tif call, err = "+data.Pkg("v.io/v23")+"GetClient(ctx).StartCall(ctx, c.name, %q, %s, opts...); err != nil {\n\t\treturn\n\t}\n", method.Name, inargs)
fmt.Fprintf(&buf, "ocall = &%s{ClientCall: call}\n", uniqueNameImpl(iface, method, "ClientCall"))
default:
outargs := "nil"
if len(method.OutArgs) > 0 {
outargs = "[]interface{}{" + argNames("", "&o", "", "", "", method.OutArgs) + "}"
}
fmt.Fprintf(&buf, "\terr = "+data.Pkg("v.io/v23")+"GetClient(ctx).Call(ctx, c.name, %q, %s, %s, opts...)\n", method.Name, inargs, outargs)
}
fmt.Fprint(&buf, "\treturn")
return buf.String() // the caller writes the trailing newline
}
// clientFinishImpl returns the client finish implementation for method.
func clientFinishImpl(varname string, method *compile.Method) string {
outargs := argNames("", "&o", "", "", "", method.OutArgs)
return fmt.Sprintf("\terr = %s.Finish(%s)", varname, outargs)
}
// serverStubImpl returns the interface method server stub implementation.
func serverStubImpl(data goData, iface *compile.Interface, method *compile.Method) string {
var buf bytes.Buffer
inargs := argNames("", "i", "ctx", "call", "", method.InArgs)
fmt.Fprintf(&buf, "\treturn s.impl.%s(%s)", method.Name, inargs)
return buf.String() // the caller writes the trailing newline
}
func reInitStreamValue(data goData, t *vdl.Type, name string) string {
switch t.Kind() {
case vdl.Struct:
return name + " = " + typeGo(data, t) + "{}\n"
case vdl.Any:
return name + " = nil\n"
}
return ""
}
// nativeConversionsInFile returns the map between wire and native types for
// wire types defined in file.
func nativeConversionsInFile(file *compile.File) map[string]vdltool.GoType {
all := file.Package.Config.Go.WireToNativeTypes
infile := make(map[string]vdltool.GoType)
for wire, gotype := range all {
for _, tdef := range file.TypeDefs {
if tdef.Name == wire {
infile[wire] = gotype
break
}
}
}
return infile
}
// The template that we execute against a goData instance to generate our
// code. Most of this is fairly straightforward substitution and ranges; more
// complicated logic is delegated to the helper functions above.
//
// We try to generate code that has somewhat reasonable formatting, and leave
// the fine-tuning to the go/format package. Note that go/format won't fix
// some instances of spurious newlines, so we try to keep it reasonable.
const genGo = `
{{$data := .}}
{{$file := $data.File}}
{{$file.Package.FileDoc}}
// This file was auto-generated by the vanadium vdl tool.
// Source: {{$file.BaseName}}
{{$file.PackageDef.Doc}}package {{$file.PackageDef.Name}}{{$file.PackageDef.DocSuffix}}
{{if or $data.Imports.System $data.Imports.User}}
import ( {{if $data.Imports.System}}
// VDL system imports{{range $imp := $data.Imports.System}}
{{if $imp.Name}}{{$imp.Name}} {{end}}"{{$imp.Path}}"{{end}}{{end}}
{{if $data.Imports.User}}
// VDL user imports{{range $imp := $data.Imports.User}}
{{if $imp.Name}}{{$imp.Name}} {{end}}"{{$imp.Path}}"{{end}}{{end}}
){{end}}
{{if $file.TypeDefs}}
{{range $tdef := $file.TypeDefs}}
{{typeDefGo $data $tdef}}
{{end}}
{{$nativeConversions := nativeConversionsInFile $file}}
func init() { {{range $wire, $native := $nativeConversions}}{{$lwire := firstRuneToLower $wire}}
{{$data.Pkg "v.io/v23/vdl"}}RegisterNative({{$lwire}}ToNative, {{$lwire}}FromNative){{end}}{{range $tdef := $file.TypeDefs}}
{{$data.Pkg "v.io/v23/vdl"}}Register((*{{$tdef.Name}})(nil)){{end}}
}
{{range $wire, $native := $nativeConversions}}{{$lwire := firstRuneToLower $wire}}{{$nat := nativeIdent $data $native}}
// Type-check {{$wire}} conversion functions.
var _ func({{$wire}}, *{{$nat}}) error = {{$lwire}}ToNative
var _ func(*{{$wire}}, {{$nat}}) error = {{$lwire}}FromNative
{{end}}
{{end}}
{{range $cdef := $file.ConstDefs}}
{{constDefGo $data $cdef}}
{{end}}
{{if $file.ErrorDefs}}var ( {{range $edef := $file.ErrorDefs}}
{{$edef.Doc}}{{errorName $edef $file}} = {{$data.Pkg "v.io/v23/verror"}}Register("{{$edef.ID}}", {{$data.Pkg "v.io/v23/verror"}}{{$edef.RetryCode}}, "{{$edef.English}}"){{end}}
)
{{/* TODO(toddw): Don't set "en-US" or "en" again, since it's already set by Register */}}
func init() { {{range $edef := $file.ErrorDefs}}{{range $lf := $edef.Formats}}
{{$data.Pkg "v.io/v23/i18n"}}Cat().SetWithBase({{$data.Pkg "v.io/v23/i18n"}}LangID("{{$lf.Lang}}"), {{$data.Pkg "v.io/v23/i18n"}}MsgID({{errorName $edef $file}}.ID), "{{$lf.Fmt}}"){{end}}{{end}}
}
{{range $edef := $file.ErrorDefs}}
{{$errName := errorName $edef $file}}
{{$newErr := print (firstRuneToExport "New" $edef.Exported) (firstRuneToUpper $errName)}}
// {{$newErr}} returns an error with the {{$errName}} ID.
func {{$newErr}}(ctx {{argNameTypes "" (print "*" ($data.Pkg "v.io/v23/context") "T") "" "" $data $edef.Params}}) error {
return {{$data.Pkg "v.io/v23/verror"}}New({{$errName}}, {{argNames "" "" "ctx" "" "" $edef.Params}})
}
{{end}}{{end}}
{{range $iface := $file.Interfaces}}
{{$ifaceStreaming := hasStreamingMethods $iface.AllMethods}}
{{$rpc_ := $data.Pkg "v.io/v23/rpc"}}
{{$optsVar := print "opts ..." $rpc_ "CallOpt"}}
{{$ctxVar := print "ctx *" ($data.Pkg "v.io/v23/context") "T"}}
// {{$iface.Name}}ClientMethods is the client interface
// containing {{$iface.Name}} methods.
{{docBreak $iface.Doc}}type {{$iface.Name}}ClientMethods interface { {{range $embed := $iface.Embeds}}
{{$embed.Doc}}{{embedGo $data $embed}}ClientMethods{{$embed.DocSuffix}}{{end}}{{range $method := $iface.Methods}}
{{$method.Doc}}{{$method.Name}}({{argNameTypes "" $ctxVar "" $optsVar $data $method.InArgs}}) {{outArgsClient "" $data $iface $method}}{{$method.DocSuffix}}{{end}}
}
// {{$iface.Name}}ClientStub adds universal methods to {{$iface.Name}}ClientMethods.
type {{$iface.Name}}ClientStub interface {
{{$iface.Name}}ClientMethods
{{$rpc_}}UniversalServiceMethods
}
// {{$iface.Name}}Client returns a client stub for {{$iface.Name}}.
func {{$iface.Name}}Client(name string) {{$iface.Name}}ClientStub {
return impl{{$iface.Name}}ClientStub{name{{range $embed := $iface.Embeds}}, {{embedGo $data $embed}}Client(name){{end}} }
}
type impl{{$iface.Name}}ClientStub struct {
name string
{{range $embed := $iface.Embeds}}
{{embedGo $data $embed}}ClientStub{{end}}
}
{{range $method := $iface.Methods}}
func (c impl{{$iface.Name}}ClientStub) {{$method.Name}}({{argNameTypes "i" $ctxVar "" $optsVar $data $method.InArgs}}) {{outArgsClient "o" $data $iface $method}} {
{{clientStubImpl $data $iface $method}}
}
{{end}}
{{range $method := $iface.Methods}}{{if isStreamingMethod $method}}
{{$clientStream := uniqueName $iface $method "ClientStream"}}
{{$clientCall := uniqueName $iface $method "ClientCall"}}
{{$clientCallImpl := uniqueNameImpl $iface $method "ClientCall"}}
{{$clientRecvImpl := uniqueNameImpl $iface $method "ClientCallRecv"}}
{{$clientSendImpl := uniqueNameImpl $iface $method "ClientCallSend"}}
// {{$clientStream}} is the client stream for {{$iface.Name}}.{{$method.Name}}.
type {{$clientStream}} interface { {{if $method.OutStream}}
// RecvStream returns the receiver side of the {{$iface.Name}}.{{$method.Name}} client stream.
RecvStream() interface {
// Advance stages an item so that it may be retrieved via Value. Returns
// true iff there is an item to retrieve. Advance must be called before
// Value is called. May block if an item is not available.
Advance() bool
// Value returns the item that was staged by Advance. May panic if Advance
// returned false or was not called. Never blocks.
Value() {{typeGo $data $method.OutStream}}
// Err returns any error encountered by Advance. Never blocks.
Err() error
} {{end}}{{if $method.InStream}}
// SendStream returns the send side of the {{$iface.Name}}.{{$method.Name}} client stream.
SendStream() interface {
// Send places the item onto the output stream. Returns errors
// encountered while sending, or if Send is called after Close or
// the stream has been canceled. Blocks if there is no buffer
// space; will unblock when buffer space is available or after
// the stream has been canceled.
Send(item {{typeGo $data $method.InStream}}) error
// Close indicates to the server that no more items will be sent;
// server Recv calls will receive io.EOF after all sent items.
// This is an optional call - e.g. a client might call Close if it
// needs to continue receiving items from the server after it's
// done sending. Returns errors encountered while closing, or if
// Close is called after the stream has been canceled. Like Send,
// blocks if there is no buffer space available.
Close() error
} {{end}}
}
// {{$clientCall}} represents the call returned from {{$iface.Name}}.{{$method.Name}}.
type {{$clientCall}} interface {
{{$clientStream}} {{if $method.InStream}}
// Finish performs the equivalent of SendStream().Close, then blocks until
// the server is done, and returns the positional return values for the call.{{else}}
// Finish blocks until the server is done, and returns the positional return
// values for call.{{end}}
//
// Finish returns immediately if the call has been canceled; depending on the
// timing the output could either be an error signaling cancelation, or the
// valid positional return values from the server.
//
// Calling Finish is mandatory for releasing stream resources, unless the call
// has been canceled or any of the other methods return an error. Finish should
// be called at most once.
Finish() {{argParens (argNameTypes "" "" "" "err error" $data $method.OutArgs)}}
}
type {{$clientCallImpl}} struct {
{{$rpc_}}ClientCall{{if $method.OutStream}}
valRecv {{typeGo $data $method.OutStream}}
errRecv error{{end}}
}
{{if $method.OutStream}}func (c *{{$clientCallImpl}}) RecvStream() interface {
Advance() bool
Value() {{typeGo $data $method.OutStream}}
Err() error
} {
return {{$clientRecvImpl}}{c}
}
type {{$clientRecvImpl}} struct {
c *{{$clientCallImpl}}
}
func (c {{$clientRecvImpl}}) Advance() bool {
{{reInitStreamValue $data $method.OutStream "c.c.valRecv"}}c.c.errRecv = c.c.Recv(&c.c.valRecv)
return c.c.errRecv == nil
}
func (c {{$clientRecvImpl}}) Value() {{typeGo $data $method.OutStream}} {
return c.c.valRecv
}
func (c {{$clientRecvImpl}}) Err() error {
if c.c.errRecv == {{$data.Pkg "io"}}EOF {
return nil
}
return c.c.errRecv
}
{{end}}{{if $method.InStream}}func (c *{{$clientCallImpl}}) SendStream() interface {
Send(item {{typeGo $data $method.InStream}}) error
Close() error
} {
return {{$clientSendImpl}}{c}
}
type {{$clientSendImpl}} struct {
c *{{$clientCallImpl}}
}
func (c {{$clientSendImpl}}) Send(item {{typeGo $data $method.InStream}}) error {
return c.c.Send(item)
}
func (c {{$clientSendImpl}}) Close() error {
return c.c.CloseSend()
}
{{end}}func (c *{{$clientCallImpl}}) Finish() {{argParens (argNameTypes "o" "" "" "err error" $data $method.OutArgs)}} {
{{clientFinishImpl "c.ClientCall" $method}}
return
}
{{end}}{{end}}
// {{$iface.Name}}ServerMethods is the interface a server writer
// implements for {{$iface.Name}}.
{{docBreak $iface.Doc}}type {{$iface.Name}}ServerMethods interface { {{range $embed := $iface.Embeds}}
{{$embed.Doc}}{{embedGo $data $embed}}ServerMethods{{$embed.DocSuffix}}{{end}}{{range $method := $iface.Methods}}
{{$method.Doc}}{{$method.Name}}({{argNameTypes "" $ctxVar (serverCallVar $data $iface $method) "" $data $method.InArgs}}) {{argParens (argNameTypes "" "" "" "err error" $data $method.OutArgs)}}{{$method.DocSuffix}}{{end}}
}
// {{$iface.Name}}ServerStubMethods is the server interface containing
// {{$iface.Name}} methods, as expected by rpc.Server.{{if $ifaceStreaming}}
// The only difference between this interface and {{$iface.Name}}ServerMethods
// is the streaming methods.{{else}}
// There is no difference between this interface and {{$iface.Name}}ServerMethods
// since there are no streaming methods.{{end}}
type {{$iface.Name}}ServerStubMethods {{if $ifaceStreaming}}interface { {{range $embed := $iface.Embeds}}
{{$embed.Doc}}{{embedGo $data $embed}}ServerStubMethods{{$embed.DocSuffix}}{{end}}{{range $method := $iface.Methods}}
{{$method.Doc}}{{$method.Name}}({{argNameTypes "" $ctxVar (serverCallStubVar $data $iface $method) "" $data $method.InArgs}}) {{argParens (argNameTypes "" "" "" "err error" $data $method.OutArgs)}}{{$method.DocSuffix}}{{end}}
}
{{else}}{{$iface.Name}}ServerMethods
{{end}}
// {{$iface.Name}}ServerStub adds universal methods to {{$iface.Name}}ServerStubMethods.
type {{$iface.Name}}ServerStub interface {
{{$iface.Name}}ServerStubMethods
// Describe the {{$iface.Name}} interfaces.
Describe__() []{{$rpc_}}InterfaceDesc
}
// {{$iface.Name}}Server returns a server stub for {{$iface.Name}}.
// It converts an implementation of {{$iface.Name}}ServerMethods into
// an object that may be used by rpc.Server.
func {{$iface.Name}}Server(impl {{$iface.Name}}ServerMethods) {{$iface.Name}}ServerStub {
stub := impl{{$iface.Name}}ServerStub{
impl: impl,{{range $embed := $iface.Embeds}}
{{$embed.Name}}ServerStub: {{embedGo $data $embed}}Server(impl),{{end}}
}
// Initialize GlobState; always check the stub itself first, to handle the
// case where the user has the Glob method defined in their VDL source.
if gs := {{$rpc_}}NewGlobState(stub); gs != nil {
stub.gs = gs
} else if gs := {{$rpc_}}NewGlobState(impl); gs != nil {
stub.gs = gs
}
return stub
}
type impl{{$iface.Name}}ServerStub struct {
impl {{$iface.Name}}ServerMethods{{range $embed := $iface.Embeds}}
{{embedGo $data $embed}}ServerStub{{end}}
gs *{{$rpc_}}GlobState
}
{{range $method := $iface.Methods}}
func (s impl{{$iface.Name}}ServerStub) {{$method.Name}}({{argNameTypes "i" $ctxVar (serverCallStubVar $data $iface $method) "" $data $method.InArgs}}) {{argParens (argTypes "" "error" $data $method.OutArgs)}} {
{{serverStubImpl $data $iface $method}}
}
{{end}}
func (s impl{{$iface.Name}}ServerStub) Globber() *{{$rpc_}}GlobState {
return s.gs
}
func (s impl{{$iface.Name}}ServerStub) Describe__() []{{$rpc_}}InterfaceDesc {
return []{{$rpc_}}InterfaceDesc{ {{$iface.Name}}Desc{{range $embed := $iface.TransitiveEmbeds}}, {{embedGo $data $embed}}Desc{{end}} }
}
// {{$iface.Name}}Desc describes the {{$iface.Name}} interface.
var {{$iface.Name}}Desc {{$rpc_}}InterfaceDesc = desc{{$iface.Name}}
// desc{{$iface.Name}} hides the desc to keep godoc clean.
var desc{{$iface.Name}} = {{$rpc_}}InterfaceDesc{ {{if $iface.Name}}
Name: "{{$iface.Name}}",{{end}}{{if $iface.File.Package.Path}}
PkgPath: "{{$iface.File.Package.Path}}",{{end}}{{if $iface.Doc}}
Doc: {{quoteStripDoc $iface.Doc}},{{end}}{{if $iface.Embeds}}
Embeds: []{{$rpc_}}EmbedDesc{ {{range $embed := $iface.Embeds}}
{ "{{$embed.Name}}", "{{$embed.File.Package.Path}}", {{quoteStripDoc $embed.Doc}} },{{end}}
},{{end}}{{if $iface.Methods}}
Methods: []{{$rpc_}}MethodDesc{ {{range $method := $iface.Methods}}
{ {{if $method.Name}}
Name: "{{$method.Name}}",{{end}}{{if $method.Doc}}
Doc: {{quoteStripDoc $method.Doc}},{{end}}{{if $method.InArgs}}
InArgs: []{{$rpc_}}ArgDesc{ {{range $arg := $method.InArgs}}
{ "{{$arg.Name}}", {{quoteStripDoc $arg.Doc}} }, // {{typeGo $data $arg.Type}}{{end}}
},{{end}}{{if $method.OutArgs}}
OutArgs: []{{$rpc_}}ArgDesc{ {{range $arg := $method.OutArgs}}
{ "{{$arg.Name}}", {{quoteStripDoc $arg.Doc}} }, // {{typeGo $data $arg.Type}}{{end}}
},{{end}}{{if $method.Tags}}
Tags: []*{{$data.Pkg "v.io/v23/vdl"}}Value{ {{range $tag := $method.Tags}}{{tagValue $data $tag}} ,{{end}} },{{end}}
},{{end}}
},{{end}}
}
{{range $method := $iface.Methods}}
{{if isStreamingMethod $method}}
{{$serverStream := uniqueName $iface $method "ServerStream"}}
{{$serverCall := uniqueName $iface $method "ServerCall"}}
{{$serverCallStub := uniqueName $iface $method "ServerCallStub"}}
{{$serverRecvImpl := uniqueNameImpl $iface $method "ServerCallRecv"}}
{{$serverSendImpl := uniqueNameImpl $iface $method "ServerCallSend"}}
// {{$serverStream}} is the server stream for {{$iface.Name}}.{{$method.Name}}.
type {{$serverStream}} interface { {{if $method.InStream}}
// RecvStream returns the receiver side of the {{$iface.Name}}.{{$method.Name}} server stream.
RecvStream() interface {
// Advance stages an item so that it may be retrieved via Value. Returns
// true iff there is an item to retrieve. Advance must be called before
// Value is called. May block if an item is not available.
Advance() bool
// Value returns the item that was staged by Advance. May panic if Advance
// returned false or was not called. Never blocks.
Value() {{typeGo $data $method.InStream}}
// Err returns any error encountered by Advance. Never blocks.
Err() error
} {{end}}{{if $method.OutStream}}
// SendStream returns the send side of the {{$iface.Name}}.{{$method.Name}} server stream.
SendStream() interface {
// Send places the item onto the output stream. Returns errors encountered
// while sending. Blocks if there is no buffer space; will unblock when
// buffer space is available.
Send(item {{typeGo $data $method.OutStream}}) error
} {{end}}
}
// {{$serverCall}} represents the context passed to {{$iface.Name}}.{{$method.Name}}.
type {{$serverCall}} interface {
{{$rpc_}}ServerCall
{{$serverStream}}
}
// {{$serverCallStub}} is a wrapper that converts rpc.StreamServerCall into
// a typesafe stub that implements {{$serverCall}}.
type {{$serverCallStub}} struct {
{{$rpc_}}StreamServerCall{{if $method.InStream}}
valRecv {{typeGo $data $method.InStream}}
errRecv error{{end}}
}
// Init initializes {{$serverCallStub}} from rpc.StreamServerCall.
func (s *{{$serverCallStub}}) Init(call {{$rpc_}}StreamServerCall) {
s.StreamServerCall = call
}
{{if $method.InStream}}// RecvStream returns the receiver side of the {{$iface.Name}}.{{$method.Name}} server stream.
func (s *{{$serverCallStub}}) RecvStream() interface {
Advance() bool
Value() {{typeGo $data $method.InStream}}
Err() error
} {
return {{$serverRecvImpl}}{s}
}
type {{$serverRecvImpl}} struct {
s *{{$serverCallStub}}
}
func (s {{$serverRecvImpl}}) Advance() bool {
{{reInitStreamValue $data $method.InStream "s.s.valRecv"}}s.s.errRecv = s.s.Recv(&s.s.valRecv)
return s.s.errRecv == nil
}
func (s {{$serverRecvImpl}}) Value() {{typeGo $data $method.InStream}} {
return s.s.valRecv
}
func (s {{$serverRecvImpl}}) Err() error {
if s.s.errRecv == {{$data.Pkg "io"}}EOF {
return nil
}
return s.s.errRecv
}
{{end}}{{if $method.OutStream}}// SendStream returns the send side of the {{$iface.Name}}.{{$method.Name}} server stream.
func (s *{{$serverCallStub}}) SendStream() interface {
Send(item {{typeGo $data $method.OutStream}}) error
} {
return {{$serverSendImpl}}{s}
}
type {{$serverSendImpl}} struct {
s *{{$serverCallStub}}
}
func (s {{$serverSendImpl}}) Send(item {{typeGo $data $method.OutStream}}) error {
return s.s.Send(item)
}
{{end}}{{end}}{{end}}
{{end}}
`