Skip to content

Commit c5f7cfc

Browse files
authored
Add MergeSpecs and use variadic args for NewSpec (#5)
1 parent e69704c commit c5f7cfc

File tree

8 files changed

+157
-32
lines changed

8 files changed

+157
-32
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ export GOBIN := $(abspath $(BIN))
1212
COPYRIGHT_YEARS := 2024
1313
LICENSE_IGNORE := --ignore /testdata/
1414

15-
BUF_VERSION := v1.39.0
16-
GO_MOD_GOTOOLCHAIN := go1.23.0
15+
BUF_VERSION := v1.42.0
16+
GO_MOD_GOTOOLCHAIN := go1.23.1
1717
GOLANGCI_LINT_VERSION := v1.60.1
1818
# https://github.com/golangci/golangci-lint/issues/4837
1919
GOLANGCI_LINT_GOTOOLCHAIN := $(GO_MOD_GOTOOLCHAIN)

cmd/protoc-gen-pluginrpc-go/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ func generateSpecBuilder(g *protogen.GeneratedFile, service *protogen.Service, n
300300
g.P("}")
301301
g.P("procedures = append(procedures, procedure)")
302302
}
303-
g.P("return ", pluginrpcPackage.Ident("NewSpec"), "(procedures)")
303+
g.P("return ", pluginrpcPackage.Ident("NewSpec"), "(procedures...)")
304304
g.P("}")
305305
g.P()
306306
}

internal/example/gen/pluginrpc/example/v1/examplev1pluginrpc/example.pluginrpc.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pluginrpc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ const (
2121

2222
// IsAtLeastVersion0_1_0 is used in compile-time handshake's with pluginrpc's generated code.
2323
IsAtLeastVersion0_1_0 = true
24+
// IsAtLeastVersion0_4_0 is used in compile-time handshake's with pluginrpc's generated code.
25+
IsAtLeastVersion0_4_0 = true
2426
)

procedure.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"net/url"
2121
"regexp"
2222
"slices"
23+
"strings"
2324

2425
pluginrpcv1 "buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go/pluginrpc/v1"
2526
)
@@ -115,6 +116,29 @@ func newProcedureOptions() *procedureOptions {
115116
return &procedureOptions{}
116117
}
117118

119+
func validateProcedures(procedures []Procedure) error {
120+
usedPathMap := make(map[string]struct{})
121+
usedArgsMap := make(map[string]struct{})
122+
for _, procedure := range procedures {
123+
path := procedure.Path()
124+
if _, ok := usedPathMap[path]; ok {
125+
return fmt.Errorf("duplicate procedure path: %q", path)
126+
}
127+
usedPathMap[path] = struct{}{}
128+
args := procedure.Args()
129+
if len(args) > 0 {
130+
// We can do this given that we have a valid Spec where
131+
// args do not contain spaces.
132+
joinedArgs := strings.Join(args, " ")
133+
if _, ok := usedArgsMap[joinedArgs]; ok {
134+
return fmt.Errorf("duplicate procedure args: %q", joinedArgs)
135+
}
136+
usedArgsMap[joinedArgs] = struct{}{}
137+
}
138+
}
139+
return nil
140+
}
141+
118142
func validateProcedure(procedure *procedure) error {
119143
if procedure.path == "" {
120144
return errors.New("procedure path is empty")

procedure_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2024 Buf Technologies, Inc.
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 pluginrpc
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/require"
21+
)
22+
23+
func TestProcedureBasic(t *testing.T) {
24+
t.Parallel()
25+
26+
procedure, err := NewProcedure("/foo/bar")
27+
require.NoError(t, err)
28+
require.Equal(t, "/foo/bar", procedure.Path())
29+
require.Empty(t, procedure.Args())
30+
31+
procedure, err = NewProcedure("/foo/bar", ProcedureWithArgs("foo", "bar"))
32+
require.NoError(t, err)
33+
require.Equal(t, "/foo/bar", procedure.Path())
34+
require.Equal(t, []string{"foo", "bar"}, procedure.Args())
35+
36+
_, err = NewProcedure("foo/bar")
37+
require.Error(t, err)
38+
_, err = NewProcedure("\\foo\\bar")
39+
require.Error(t, err)
40+
_, err = NewProcedure("/foo/bar", ProcedureWithArgs("f"))
41+
require.Error(t, err)
42+
}

spec.go

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
package pluginrpc
1616

1717
import (
18-
"fmt"
1918
"slices"
20-
"strings"
2119

2220
pluginrpcv1 "buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go/pluginrpc/v1"
2321
)
@@ -40,7 +38,7 @@ type Spec interface {
4038
}
4139

4240
// NewSpec returns a new validated Spec for the given Procedures.
43-
func NewSpec(procedures []Procedure) (Spec, error) {
41+
func NewSpec(procedures ...Procedure) (Spec, error) {
4442
return newSpec(procedures)
4543
}
4644

@@ -54,7 +52,7 @@ func NewSpecForProto(protoSpec *pluginrpcv1.Spec) (Spec, error) {
5452
}
5553
procedures[i] = procedure
5654
}
57-
return NewSpec(procedures)
55+
return NewSpec(procedures...)
5856
}
5957

6058
// NewProtoSpec returns a new pluginrpcv1.Spec for the given Spec.
@@ -69,6 +67,17 @@ func NewProtoSpec(spec Spec) *pluginrpcv1.Spec {
6967
}
7068
}
7169

70+
// MergeSpecs merges the given Specs.
71+
//
72+
// Returns error if any Procedures overlap by Path or Args.
73+
func MergeSpecs(specs ...Spec) (Spec, error) {
74+
var procedures []Procedure
75+
for _, spec := range specs {
76+
procedures = append(procedures, spec.Procedures()...)
77+
}
78+
return NewSpec(procedures...)
79+
}
80+
7281
// *** PRIVATE ***
7382

7483
type spec struct {
@@ -77,7 +86,7 @@ type spec struct {
7786
}
7887

7988
func newSpec(procedures []Procedure) (*spec, error) {
80-
if err := validateSpecProcedures(procedures); err != nil {
89+
if err := validateProcedures(procedures); err != nil {
8190
return nil, err
8291
}
8392
pathToProcedure := make(map[string]Procedure)
@@ -99,26 +108,3 @@ func (s *spec) Procedures() []Procedure {
99108
}
100109

101110
func (*spec) isSpec() {}
102-
103-
func validateSpecProcedures(procedures []Procedure) error {
104-
usedPathMap := make(map[string]struct{})
105-
usedArgsMap := make(map[string]struct{})
106-
for _, procedure := range procedures {
107-
path := procedure.Path()
108-
if _, ok := usedPathMap[path]; ok {
109-
return fmt.Errorf("duplicate procedure path: %q", path)
110-
}
111-
usedPathMap[path] = struct{}{}
112-
args := procedure.Args()
113-
if len(args) > 0 {
114-
// We can do this given that we have a valid Spec where
115-
// args do not contain spaces.
116-
joinedArgs := strings.Join(args, " ")
117-
if _, ok := usedArgsMap[joinedArgs]; ok {
118-
return fmt.Errorf("duplicate procedure args: %q", joinedArgs)
119-
}
120-
usedArgsMap[joinedArgs] = struct{}{}
121-
}
122-
}
123-
return nil
124-
}

spec_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2024 Buf Technologies, Inc.
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 pluginrpc
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/require"
21+
)
22+
23+
func TestMergeSpecsSuccess(t *testing.T) {
24+
t.Parallel()
25+
26+
procedure1, err := NewProcedure("/foo/bar")
27+
require.NoError(t, err)
28+
procedure2, err := NewProcedure("/foo/baz")
29+
require.NoError(t, err)
30+
spec1, err := NewSpec(procedure1)
31+
require.NoError(t, err)
32+
spec2, err := NewSpec(procedure2)
33+
require.NoError(t, err)
34+
spec, err := MergeSpecs(spec1, spec2)
35+
require.NoError(t, err)
36+
require.Equal(
37+
t,
38+
[]Procedure{procedure1, procedure2},
39+
spec.Procedures(),
40+
)
41+
}
42+
43+
func TestMergeSpecsErrorOverlappingPaths(t *testing.T) {
44+
t.Parallel()
45+
46+
procedure1, err := NewProcedure("/foo/bar")
47+
require.NoError(t, err)
48+
procedure2, err := NewProcedure("/foo/bar")
49+
require.NoError(t, err)
50+
spec1, err := NewSpec(procedure1)
51+
require.NoError(t, err)
52+
spec2, err := NewSpec(procedure2)
53+
require.NoError(t, err)
54+
_, err = MergeSpecs(spec1, spec2)
55+
require.Error(t, err)
56+
}
57+
58+
func TestMergeSpecsErrorOverlappingArgs(t *testing.T) {
59+
t.Parallel()
60+
61+
procedure1, err := NewProcedure("/foo/bar", ProcedureWithArgs("foo", "bar"))
62+
require.NoError(t, err)
63+
procedure2, err := NewProcedure("/foo/baz", ProcedureWithArgs("foo", "bar"))
64+
require.NoError(t, err)
65+
spec1, err := NewSpec(procedure1)
66+
require.NoError(t, err)
67+
spec2, err := NewSpec(procedure2)
68+
require.NoError(t, err)
69+
_, err = MergeSpecs(spec1, spec2)
70+
require.Error(t, err)
71+
}

0 commit comments

Comments
 (0)