Skip to content

Commit 0330c7f

Browse files
committed
pluginrpc-gen: align generator with generated
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
1 parent 8601b22 commit 0330c7f

File tree

8 files changed

+220
-29
lines changed

8 files changed

+220
-29
lines changed

.golangci.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,6 @@ linters:
171171
http-status-code: true
172172

173173
exclusions:
174-
paths:
175-
- volume/drivers/proxy.go # TODO: this is a generated file but with an invalid header, see https://github.com/moby/moby/pull/46274
176-
177174
rules:
178175
# We prefer to use an "linters.exclusions.rules" so that new "default" exclusions are not
179176
# automatically inherited. We can decide whether or not to follow upstream

pkg/plugins/pluginrpc-gen/README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type volumeDriver interface {
2323
Run the generator:
2424

2525
```bash
26-
$ pluginrpc-gen --type volumeDriver --name VolumeDriver -i volumes/drivers/extpoint.go -o volumes/drivers/proxy.go
26+
$ pluginrpc-gen --type volumeDriver --name VolumeDriver -i volume/drivers/extpoint.go -o volume/drivers/proxy.go
2727
```
2828

2929
Where:
@@ -41,6 +41,40 @@ This flag can be specified multiple times.
4141
You can also add build tags that should be prepended to the generated code by
4242
supplying `--tag`. This flag can be specified multiple times.
4343

44+
45+
## Annotations
46+
47+
`pluginrpc-gen` supports annotations to customize the behavior of the generated code. These annotations are added as comments directly above the interface methods.
48+
49+
### Supported Annotations
50+
51+
1. **`pluginrpc-gen:timeout-type=<value>`**
52+
- Specifies the timeout type to use for the method in the generated proxy code.
53+
- The `<value>` can be:
54+
- `short`: Uses the `shortTimeout` constant (default: 1 minute).
55+
- `long`: Uses the `longTimeout` constant (default: 2 minutes).
56+
57+
### Usage
58+
59+
To use the `pluginrpc-gen:timeout-type` annotation, place it directly above the method definition in the interface.
60+
To use the timeout value annotations, place them directly above the interface definition.
61+
62+
#### Example
63+
64+
```go
65+
type volumeDriver interface {
66+
// pluginrpc-gen:timeout-type=long
67+
Create(name string, opts map[string]string) error
68+
69+
// pluginrpc-gen:timeout-type=short
70+
Remove(name string) error
71+
}
72+
```
73+
74+
### Default Behavior
75+
76+
- If no `pluginrpc-gen:timeout-type` annotation is provided, the `shortTimeout` value is used by default.
77+
4478
## Known issues
4579

4680
## go-generate

pkg/plugins/pluginrpc-gen/fixtures/foo.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,16 @@ type Fooer11 interface {
8181
type Fooer12 interface {
8282
Foo(a aliasedio.Reader)
8383
}
84+
85+
type FooerWithTimeout interface {
86+
// pluginrpc-gen:timeout-type=long
87+
WithTimeout() (err error)
88+
}
89+
90+
// pluginrpc-gen:long-timeout=5m
91+
// pluginrpc-gen:short-timeout=10s
92+
type FooerWithMultilineAnnotation interface {
93+
// Foo is a method that does something
94+
// pluginrpc-gen:timeout-type=long
95+
WithMultilineAnnotation() (err error)
96+
}

pkg/plugins/pluginrpc-gen/parser.go

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"path"
1010
"reflect"
1111
"strings"
12+
"time"
1213
)
1314

1415
var errBadReturn = errors.New("found return arg with no name: all args must be named")
@@ -25,16 +26,19 @@ func (e errUnexpectedType) Error() string {
2526
// ParsedPkg holds information about a package that has been parsed,
2627
// its name and the list of functions.
2728
type ParsedPkg struct {
28-
Name string
29-
Functions []function
30-
Imports []importSpec
29+
Name string
30+
Functions []function
31+
Imports []importSpec
32+
LongTimeout string // e.g. "2 * time.Minute"
33+
ShortTimeout string // e.g. "1 * time.Minute"
3134
}
3235

3336
type function struct {
34-
Name string
35-
Args []fnArg
36-
Returns []fnArg
37-
Doc string
37+
Name string
38+
Args []fnArg
39+
Returns []fnArg
40+
Doc string
41+
TimeoutType string
3842
}
3943

4044
type fnArg struct {
@@ -64,11 +68,11 @@ func (s *importSpec) String() string {
6468
// Parse parses the given file for an interface definition with the given name.
6569
func Parse(filePath string, objName string) (*ParsedPkg, error) {
6670
fs := token.NewFileSet()
67-
pkg, err := parser.ParseFile(fs, filePath, nil, parser.AllErrors)
71+
pkg, err := parser.ParseFile(fs, filePath, nil, parser.ParseComments)
6872
if err != nil {
6973
return nil, err
7074
}
71-
p := &ParsedPkg{}
75+
p := &ParsedPkg{ LongTimeout : "2 * time.Minute", ShortTimeout = "1 * time.Minute"}
7276
p.Name = pkg.Name.Name
7377
obj, exists := pkg.Scope.Objects[objName]
7478
if !exists {
@@ -85,6 +89,21 @@ func Parse(filePath string, objName string) (*ParsedPkg, error) {
8589
if !ok {
8690
return nil, errUnexpectedType{"*ast.InterfaceType", spec.Type}
8791
}
92+
p.LongTimeout = "2 * time.Minute"
93+
p.ShortTimeout = "1 * time.Minute"
94+
if spec.Doc != nil {
95+
for _, c := range spec.Doc.List {
96+
txt := strings.TrimSpace(strings.TrimPrefix(c.Text, "//"))
97+
if strings.HasPrefix(txt, "pluginrpc-gen:long-timeout=") {
98+
val := strings.TrimPrefix(txt, "pluginrpc-gen:long-timeout=")
99+
p.LongTimeout = durationToGoCode(val)
100+
}
101+
if strings.HasPrefix(txt, "pluginrpc-gen:short-timeout=") {
102+
val := strings.TrimPrefix(txt, "pluginrpc-gen:short-timeout=")
103+
p.ShortTimeout = durationToGoCode(val)
104+
}
105+
}
106+
}
88107

89108
p.Functions, err = parseInterface(iface)
90109
if err != nil {
@@ -169,11 +188,15 @@ func parseInterface(iface *ast.InterfaceType) ([]function, error) {
169188

170189
func parseFunc(field *ast.Field) (*function, error) {
171190
f := field.Type.(*ast.FuncType)
172-
method := &function{Name: field.Names[0].Name}
191+
method := &function{Name: field.Names[0].Name, TimeoutType: "short"}
173192
if _, exists := skipFuncs[method.Name]; exists {
174193
fmt.Println("skipping:", method.Name)
175194
return nil, nil
176195
}
196+
if field.Doc != nil {
197+
method.Doc = extractDocumentation(field.Doc.List)
198+
method.TimeoutType = parseRequestTimeout(field.Doc.List)
199+
}
177200
if f.Params != nil {
178201
args, err := parseArgs(f.Params.List)
179202
if err != nil {
@@ -261,3 +284,55 @@ func parseExpr(e ast.Expr) (parsedExpr, error) {
261284
}
262285
return parsed, nil
263286
}
287+
288+
func extractDocumentation(comments []*ast.Comment) string {
289+
var docLines []string
290+
291+
for _, comment := range comments {
292+
text := strings.TrimSpace(comment.Text)
293+
// Ignore lines that contains "pluginrpc-gen:"
294+
if strings.Contains(text, "pluginrpc-gen:") {
295+
continue
296+
}
297+
docLines = append(docLines, text)
298+
}
299+
300+
return strings.Join(docLines, "\n")
301+
}
302+
303+
func parseRequestTimeout(comments []*ast.Comment) string {
304+
var commentText string
305+
306+
// Concatenate all comment lines into a single string
307+
for _, comment := range comments {
308+
commentText += strings.TrimSpace(comment.Text) + " "
309+
}
310+
311+
// Look for the timeout annotation
312+
if strings.Contains(commentText, "pluginrpc-gen:timeout-type=") {
313+
parts := strings.Split(commentText, "pluginrpc-gen:timeout-type=")
314+
if len(parts) > 1 {
315+
// Extract the timeout value
316+
return strings.Fields(parts[1])[0]
317+
}
318+
}
319+
320+
return "short"
321+
}
322+
323+
func durationToGoCode(d string) string {
324+
parsed, err := time.ParseDuration(d)
325+
if err != nil {
326+
// fallback or default
327+
return "2 * time.Minute"
328+
}
329+
secs := int64(parsed.Seconds())
330+
switch {
331+
case secs%3600 == 0:
332+
return fmt.Sprintf("%d * time.Hour", secs/3600)
333+
case secs%60 == 0:
334+
return fmt.Sprintf("%d * time.Minute", secs/60)
335+
default:
336+
return fmt.Sprintf("%d * time.Second", secs)
337+
}
338+
}

pkg/plugins/pluginrpc-gen/parser_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@ func TestParseWithMultipleFuncs(t *testing.T) {
5353
assertName(t, "Foo", f.Name)
5454
assertNum(t, 0, len(f.Args))
5555
assertNum(t, 0, len(f.Returns))
56+
assertName(t, "short", f.TimeoutType)
5657

5758
f = pkg.Functions[1]
5859
assertName(t, "Bar", f.Name)
5960
assertNum(t, 1, len(f.Args))
6061
assertNum(t, 0, len(f.Returns))
62+
assertName(t, "short", f.TimeoutType)
6163
arg := f.Args[0]
6264
assertName(t, "a", arg.Name)
6365
assertName(t, "string", arg.ArgType)
@@ -66,6 +68,7 @@ func TestParseWithMultipleFuncs(t *testing.T) {
6668
assertName(t, "Baz", f.Name)
6769
assertNum(t, 1, len(f.Args))
6870
assertNum(t, 1, len(f.Returns))
71+
assertName(t, "short", f.TimeoutType)
6972
arg = f.Args[0]
7073
assertName(t, "a", arg.Name)
7174
assertName(t, "string", arg.ArgType)
@@ -77,6 +80,7 @@ func TestParseWithMultipleFuncs(t *testing.T) {
7780
assertName(t, "Qux", f.Name)
7881
assertNum(t, 2, len(f.Args))
7982
assertNum(t, 2, len(f.Returns))
83+
assertName(t, "short", f.TimeoutType)
8084
arg = f.Args[0]
8185
assertName(t, "a", arg.Name)
8286
assertName(t, "string", arg.ArgType)
@@ -94,6 +98,7 @@ func TestParseWithMultipleFuncs(t *testing.T) {
9498
assertName(t, "Wobble", f.Name)
9599
assertNum(t, 0, len(f.Args))
96100
assertNum(t, 1, len(f.Returns))
101+
assertName(t, "short", f.TimeoutType)
97102
arg = f.Returns[0]
98103
assertName(t, "w", arg.Name)
99104
assertName(t, "*wobble", arg.ArgType)
@@ -102,6 +107,7 @@ func TestParseWithMultipleFuncs(t *testing.T) {
102107
assertName(t, "Wiggle", f.Name)
103108
assertNum(t, 0, len(f.Args))
104109
assertNum(t, 1, len(f.Returns))
110+
assertName(t, "short", f.TimeoutType)
105111
arg = f.Returns[0]
106112
assertName(t, "w", arg.Name)
107113
assertName(t, "wobble", arg.ArgType)
@@ -110,6 +116,7 @@ func TestParseWithMultipleFuncs(t *testing.T) {
110116
assertName(t, "WiggleWobble", f.Name)
111117
assertNum(t, 6, len(f.Args))
112118
assertNum(t, 6, len(f.Returns))
119+
assertName(t, "short", f.TimeoutType)
113120
expectedArgs := [][]string{
114121
{"a", "[]*wobble"},
115122
{"b", "[]wobble"},
@@ -156,11 +163,13 @@ func TestEmbeddedInterface(t *testing.T) {
156163
assertName(t, "Foo", f.Name)
157164
assertNum(t, 0, len(f.Args))
158165
assertNum(t, 0, len(f.Returns))
166+
assertName(t, "short", f.TimeoutType)
159167

160168
f = pkg.Functions[1]
161169
assertName(t, "Boo", f.Name)
162170
assertNum(t, 2, len(f.Args))
163171
assertNum(t, 2, len(f.Returns))
172+
assertName(t, "short", f.TimeoutType)
164173

165174
arg := f.Args[0]
166175
assertName(t, "a", arg.Name)
@@ -204,6 +213,41 @@ func TestAliasedImports(t *testing.T) {
204213
assertName(t, "aliasedio", pkg.Imports[0].Name)
205214
}
206215

216+
func TestParseWithMethodTimeoutAnnotation(t *testing.T) {
217+
pkg, err := Parse(testFixture, "FooerWithTimeout")
218+
if err != nil {
219+
t.Fatal(err)
220+
}
221+
222+
assertName(t, "foo", pkg.Name)
223+
assertNum(t, 1, len(pkg.Functions))
224+
225+
f := pkg.Functions[0]
226+
assertName(t, "WithTimeout", f.Name)
227+
assertNum(t, 0, len(f.Args))
228+
assertNum(t, 1, len(f.Returns))
229+
assertName(t, "long", f.TimeoutType)
230+
}
231+
232+
func TestParseWithMultilineAnnotation(t *testing.T) {
233+
pkg, err := Parse(testFixture, "FooerWithMultilineAnnotation")
234+
if err != nil {
235+
t.Fatal(err)
236+
}
237+
238+
assertName(t, "foo", pkg.Name)
239+
assertName(t, "5 * time.Minute", pkg.LongTimeout)
240+
assertName(t, "10 * time.Second", pkg.ShortTimeout)
241+
assertNum(t, 1, len(pkg.Functions))
242+
243+
f := pkg.Functions[0]
244+
assertName(t, "WithMultilineAnnotation", f.Name)
245+
assertNum(t, 0, len(f.Args))
246+
assertNum(t, 1, len(f.Returns))
247+
assertName(t, "long", f.TimeoutType)
248+
assertName(t, "// Foo is a method that does something", f.Doc)
249+
}
250+
207251
func assertName(t *testing.T, expected, actual string) {
208252
if expected != actual {
209253
fatalOut(t, fmt.Sprintf("expected name to be `%s`, got: %s", expected, actual))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy