Skip to content

Commit 8b0d081

Browse files
authored
Add bufpolicyapi package (#3794)
1 parent 8f9f45a commit 8b0d081

File tree

8 files changed

+1095
-0
lines changed

8 files changed

+1095
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2020-2025 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 bufpolicyapi
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Copyright 2020-2025 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 bufpolicyapi
16+
17+
import (
18+
"fmt"
19+
20+
policyv1beta1 "buf.build/gen/go/bufbuild/registry/protocolbuffers/go/buf/registry/policy/v1beta1"
21+
"buf.build/go/bufplugin/option"
22+
"buf.build/go/standard/xslices"
23+
"github.com/bufbuild/buf/private/bufpkg/bufcas"
24+
"github.com/bufbuild/buf/private/bufpkg/bufconfig"
25+
"github.com/bufbuild/buf/private/bufpkg/bufparse"
26+
"github.com/bufbuild/buf/private/bufpkg/bufpolicy"
27+
)
28+
29+
var (
30+
v1beta1ProtoDigestTypeToDigestType = map[policyv1beta1.DigestType]bufpolicy.DigestType{
31+
policyv1beta1.DigestType_DIGEST_TYPE_O1: bufpolicy.DigestTypeO1,
32+
}
33+
)
34+
35+
// V1Beta1ProtoToDigest converts the given proto Digest to a Digest.
36+
//
37+
// Validation is performed to ensure the DigestType is known, and the value
38+
// is a valid digest value for the given DigestType.
39+
func V1Beta1ProtoToDigest(protoDigest *policyv1beta1.Digest) (bufpolicy.Digest, error) {
40+
digestType, err := v1beta1ProtoToDigestType(protoDigest.Type)
41+
if err != nil {
42+
return nil, err
43+
}
44+
bufcasDigest, err := bufcas.NewDigest(protoDigest.Value)
45+
if err != nil {
46+
return nil, err
47+
}
48+
return bufpolicy.NewDigest(digestType, bufcasDigest)
49+
}
50+
51+
// V1Beta1ProtoToPolicyConfig converts the given proto PolicyConfig to a PolicyConfig.
52+
// The registry is used to resolve plugin references.
53+
func V1Beta1ProtoToPolicyConfig(registry string, protoPolicyConfig *policyv1beta1.PolicyConfig) (bufpolicy.PolicyConfig, error) {
54+
return newPolicyConfig(registry, protoPolicyConfig)
55+
}
56+
57+
// *** PRIVATE ***
58+
59+
func policyVisibilityToV1Beta1Proto(policyVisibility bufpolicy.PolicyVisibility) (policyv1beta1.PolicyVisibility, error) {
60+
switch policyVisibility {
61+
case bufpolicy.PolicyVisibilityPublic:
62+
return policyv1beta1.PolicyVisibility_POLICY_VISIBILITY_PUBLIC, nil
63+
case bufpolicy.PolicyVisibilityPrivate:
64+
return policyv1beta1.PolicyVisibility_POLICY_VISIBILITY_PRIVATE, nil
65+
default:
66+
return 0, fmt.Errorf("unknown PolicyVisibility: %v", policyVisibility)
67+
}
68+
}
69+
70+
func v1beta1ProtoToDigestType(protoDigestType policyv1beta1.DigestType) (bufpolicy.DigestType, error) {
71+
digestType, ok := v1beta1ProtoDigestTypeToDigestType[protoDigestType]
72+
if !ok {
73+
return 0, fmt.Errorf("unknown policyv1beta1.DigestType: %v", protoDigestType)
74+
}
75+
return digestType, nil
76+
}
77+
78+
// policyConfig implements bufpolicy.PolicyConfig.
79+
type policyConfig struct {
80+
lintConfig bufconfig.LintConfig
81+
breakingConfig bufconfig.BreakingConfig
82+
pluginConfigs []bufconfig.PluginConfig
83+
}
84+
85+
func newPolicyConfig(
86+
registry string,
87+
policyConfigV1Beta1 *policyv1beta1.PolicyConfig,
88+
) (*policyConfig, error) {
89+
lintConfig, err := getLintConfigForV1Beta1LintConfig(policyConfigV1Beta1.Lint)
90+
if err != nil {
91+
return nil, err
92+
}
93+
breakingConfig, err := getBreakingConfigForV1Beta1BreakingConfig(policyConfigV1Beta1.Breaking)
94+
if err != nil {
95+
return nil, err
96+
}
97+
pluginConfigs, err := xslices.MapError(
98+
policyConfigV1Beta1.Plugins,
99+
func(pluginConfigV1Beta1 *policyv1beta1.PolicyConfig_CheckPluginConfig) (bufconfig.PluginConfig, error) {
100+
return getPluginConfigForV1Beta1PluginConfig(registry, pluginConfigV1Beta1)
101+
},
102+
)
103+
if err != nil {
104+
return nil, err
105+
}
106+
return &policyConfig{
107+
lintConfig: lintConfig,
108+
breakingConfig: breakingConfig,
109+
pluginConfigs: pluginConfigs,
110+
}, nil
111+
}
112+
113+
// LintConfig returns the LintConfig for the File.
114+
func (p *policyConfig) LintConfig() bufconfig.LintConfig {
115+
return p.lintConfig
116+
}
117+
118+
// BreakingConfig returns the BreakingConfig for the File.
119+
func (p *policyConfig) BreakingConfig() bufconfig.BreakingConfig {
120+
return p.breakingConfig
121+
}
122+
123+
// PluginConfigs returns the PluginConfigs for the File.
124+
func (p *policyConfig) PluginConfigs() []bufconfig.PluginConfig {
125+
return p.pluginConfigs
126+
}
127+
128+
func getLintConfigForV1Beta1LintConfig(
129+
lintConfigV1Beta1 *policyv1beta1.PolicyConfig_LintConfig,
130+
) (bufconfig.LintConfig, error) {
131+
checkConfig, err := bufconfig.NewEnabledCheckConfig(
132+
bufconfig.FileVersionV2,
133+
lintConfigV1Beta1.Use,
134+
lintConfigV1Beta1.Except,
135+
nil,
136+
nil,
137+
false,
138+
)
139+
if err != nil {
140+
return nil, err
141+
}
142+
return bufconfig.NewLintConfig(
143+
checkConfig,
144+
lintConfigV1Beta1.EnumZeroValueSuffix,
145+
lintConfigV1Beta1.RpcAllowSameRequestResponse,
146+
lintConfigV1Beta1.RpcAllowGoogleProtobufEmptyRequests,
147+
lintConfigV1Beta1.RpcAllowGoogleProtobufEmptyResponses,
148+
lintConfigV1Beta1.ServiceSuffix,
149+
false, // Comment ignores are not allowed in Policy files.
150+
), nil
151+
}
152+
153+
func getBreakingConfigForV1Beta1BreakingConfig(
154+
breakingConfigV1Beta1 *policyv1beta1.PolicyConfig_BreakingConfig,
155+
) (bufconfig.BreakingConfig, error) {
156+
checkConfig, err := bufconfig.NewEnabledCheckConfig(
157+
bufconfig.FileVersionV2,
158+
breakingConfigV1Beta1.Use,
159+
breakingConfigV1Beta1.Except,
160+
nil,
161+
nil,
162+
false,
163+
)
164+
if err != nil {
165+
return nil, err
166+
}
167+
return bufconfig.NewBreakingConfig(
168+
checkConfig,
169+
breakingConfigV1Beta1.IgnoreUnstablePackages,
170+
), nil
171+
}
172+
173+
func getPluginConfigForV1Beta1PluginConfig(
174+
registry string,
175+
pluginConfigV1Beta1 *policyv1beta1.PolicyConfig_CheckPluginConfig,
176+
) (bufconfig.PluginConfig, error) {
177+
nameV1Beta1 := pluginConfigV1Beta1.Name
178+
pluginRef, err := bufparse.NewRef(
179+
registry,
180+
nameV1Beta1.Owner,
181+
nameV1Beta1.Plugin,
182+
nameV1Beta1.Ref,
183+
)
184+
if err != nil {
185+
return nil, err
186+
}
187+
options, err := option.OptionsForProtoOptions(pluginConfigV1Beta1.Options)
188+
if err != nil {
189+
return nil, err
190+
}
191+
optionsMap := make(map[string]any)
192+
options.Range(func(key string, value any) {
193+
optionsMap[key] = value
194+
})
195+
return bufconfig.NewRemoteWasmPluginConfig(
196+
pluginRef,
197+
optionsMap,
198+
pluginConfigV1Beta1.Args,
199+
)
200+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright 2020-2025 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 bufpolicyapi
16+
17+
import (
18+
"context"
19+
"log/slog"
20+
21+
policyv1beta1 "buf.build/gen/go/bufbuild/registry/protocolbuffers/go/buf/registry/policy/v1beta1"
22+
"buf.build/go/standard/xslices"
23+
"connectrpc.com/connect"
24+
"github.com/bufbuild/buf/private/bufpkg/bufparse"
25+
"github.com/bufbuild/buf/private/bufpkg/bufpolicy"
26+
"github.com/bufbuild/buf/private/bufpkg/bufregistryapi/bufregistryapipolicy"
27+
"github.com/bufbuild/buf/private/pkg/syserror"
28+
"github.com/bufbuild/buf/private/pkg/uuidutil"
29+
"github.com/google/uuid"
30+
)
31+
32+
// NewPolicyDataProvider returns a new PolicyDataProvider for the given API client.
33+
//
34+
// A warning is printed to the logger if a given Policy is deprecated.
35+
func NewPolicyDataProvider(
36+
logger *slog.Logger,
37+
clientProvider interface {
38+
bufregistryapipolicy.V1Beta1DownloadServiceClientProvider
39+
},
40+
) bufpolicy.PolicyDataProvider {
41+
return newPolicyDataProvider(logger, clientProvider)
42+
}
43+
44+
// *** PRIVATE ***
45+
46+
type policyDataProvider struct {
47+
logger *slog.Logger
48+
clientProvider interface {
49+
bufregistryapipolicy.V1Beta1DownloadServiceClientProvider
50+
}
51+
}
52+
53+
func newPolicyDataProvider(
54+
logger *slog.Logger,
55+
clientProvider interface {
56+
bufregistryapipolicy.V1Beta1DownloadServiceClientProvider
57+
},
58+
) *policyDataProvider {
59+
return &policyDataProvider{
60+
logger: logger,
61+
clientProvider: clientProvider,
62+
}
63+
}
64+
65+
func (p *policyDataProvider) GetPolicyDatasForPolicyKeys(
66+
ctx context.Context,
67+
policyKeys []bufpolicy.PolicyKey,
68+
) ([]bufpolicy.PolicyData, error) {
69+
if len(policyKeys) == 0 {
70+
return nil, nil
71+
}
72+
digestType, err := bufpolicy.UniqueDigestTypeForPolicyKeys(policyKeys)
73+
if err != nil {
74+
return nil, err
75+
}
76+
if digestType != bufpolicy.DigestTypeO1 {
77+
return nil, syserror.Newf("unsupported digest type: %v", digestType)
78+
}
79+
if _, err := bufparse.FullNameStringToUniqueValue(policyKeys); err != nil {
80+
return nil, err
81+
}
82+
83+
registryToIndexedPolicyKeys := xslices.ToIndexedValuesMap(
84+
policyKeys,
85+
func(policyKey bufpolicy.PolicyKey) string {
86+
return policyKey.FullName().Registry()
87+
},
88+
)
89+
indexedPolicyDatas := make([]xslices.Indexed[bufpolicy.PolicyData], 0, len(policyKeys))
90+
for registry, indexedPolicyKeys := range registryToIndexedPolicyKeys {
91+
indexedRegistryPolicyDatas, err := p.getIndexedPolicyDatasForRegistryAndIndexedPolicyKeys(
92+
ctx,
93+
registry,
94+
indexedPolicyKeys,
95+
)
96+
if err != nil {
97+
return nil, err
98+
}
99+
indexedPolicyDatas = append(indexedPolicyDatas, indexedRegistryPolicyDatas...)
100+
}
101+
return xslices.IndexedToSortedValues(indexedPolicyDatas), nil
102+
}
103+
104+
func (p *policyDataProvider) getIndexedPolicyDatasForRegistryAndIndexedPolicyKeys(
105+
ctx context.Context,
106+
registry string,
107+
indexedPolicyKeys []xslices.Indexed[bufpolicy.PolicyKey],
108+
) ([]xslices.Indexed[bufpolicy.PolicyData], error) {
109+
values := xslices.Map(indexedPolicyKeys, func(indexedPolicyKey xslices.Indexed[bufpolicy.PolicyKey]) *policyv1beta1.DownloadRequest_Value {
110+
return &policyv1beta1.DownloadRequest_Value{
111+
ResourceRef: &policyv1beta1.ResourceRef{
112+
Value: &policyv1beta1.ResourceRef_Id{
113+
Id: uuidutil.ToDashless(indexedPolicyKey.Value.CommitID()),
114+
},
115+
},
116+
}
117+
})
118+
119+
policyResponse, err := p.clientProvider.V1Beta1DownloadServiceClient(registry).Download(
120+
ctx,
121+
connect.NewRequest(&policyv1beta1.DownloadRequest{
122+
Values: values,
123+
}),
124+
)
125+
if err != nil {
126+
return nil, err
127+
}
128+
policyContents := policyResponse.Msg.Contents
129+
if len(policyContents) != len(indexedPolicyKeys) {
130+
return nil, syserror.New("did not get the expected number of policy datas")
131+
}
132+
133+
commitIDToIndexedPolicyKeys, err := xslices.ToUniqueValuesMapError(
134+
indexedPolicyKeys,
135+
func(indexedPolicyKey xslices.Indexed[bufpolicy.PolicyKey]) (uuid.UUID, error) {
136+
return indexedPolicyKey.Value.CommitID(), nil
137+
},
138+
)
139+
if err != nil {
140+
return nil, err
141+
}
142+
143+
indexedPolicyDatas := make([]xslices.Indexed[bufpolicy.PolicyData], 0, len(indexedPolicyKeys))
144+
for _, policyContent := range policyContents {
145+
commitID, err := uuid.Parse(policyContent.Commit.Id)
146+
if err != nil {
147+
return nil, err
148+
}
149+
indexedPolicyKey, ok := commitIDToIndexedPolicyKeys[commitID]
150+
if !ok {
151+
return nil, syserror.Newf("did not get policy key from store with commitID %q", commitID)
152+
}
153+
getContent := func() (bufpolicy.PolicyConfig, error) {
154+
return newPolicyConfig(registry, policyContent.GetConfig())
155+
}
156+
policyData, err := bufpolicy.NewPolicyData(ctx, indexedPolicyKey.Value, getContent)
157+
if err != nil {
158+
return nil, err
159+
}
160+
indexedPolicyDatas = append(
161+
indexedPolicyDatas,
162+
xslices.Indexed[bufpolicy.PolicyData]{
163+
Value: policyData,
164+
Index: indexedPolicyKey.Index,
165+
},
166+
)
167+
}
168+
return indexedPolicyDatas, nil
169+
}

0 commit comments

Comments
 (0)