Skip to content

Commit 9c3c9d1

Browse files
authored
feat: add allowed-visibilities option (#192)
1 parent 67edeb4 commit 9c3c9d1

File tree

12 files changed

+294
-3
lines changed

12 files changed

+294
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ protoc-gen-connect-openapi also has support for the [OpenAPI v3 annotations](htt
176176
| path | `{filepath}` | Output filepath, defaults to per-proto file output if not given. When using [buf](https://github.com/bufbuild/buf), generating multiple files to the same path requires additional configuration to avoid overwriting files. See [#159](https://github.com/sudorandom/protoc-gen-connect-openapi/issues/159). |
177177
| path-prefix | `{path}` | Prefixes the given string to the beginning of each HTTP path. |
178178
| features | `{feature1};{feature2};[...]` | Semicolon-separated list of features to enable. Options: `connectrpc`, `google.api.http`, `twirp`, `gnostic`, `protovalidate`; Default: `connectrpc;google.api.http;gnostic;protovalidate`. If this option is used, only the specified features will be enabled. |
179+
| allowed-visibilities | `{visibility1};{visibility2};[...]` | Semicolon-separated list of visibility labels to include. If an element (service, method, message, enum, enum value, or field) has a `google.api.visibility` rule, it will only be included in the generated OpenAPI specification if its visibility label is in this list. If this option is omitted, all elements are included regardless of their visibility. |
179180
| proto | - | Generate requests/responses with the protobuf content type |
180181
| services | `{service_name}` | Specifies which services to include in the generated OpenAPI specification. If omitted, all services are included. The service name must be fully qualified (e.g., "package.name.ServiceName"). Wildcards (`*` and `**`) are supported; `*` matches a single package segment, while `**` matches multiple. This option can be provided multiple times to include multiple services. |
181182
| short-operation-ids | - | Set the operationId to shortServiceName + "_" + method short name instead of the full method name. |

internal/converter/converter.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/gnostic"
2727
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/options"
2828
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/util"
29+
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/visibility"
2930
)
3031

3132
func ConvertFrom(rd io.Reader) (*pluginpb.CodeGeneratorResponse, error) {
@@ -286,13 +287,21 @@ func appendToSpec(opts options.Options, spec *v3.Document, fd protoreflect.FileD
286287
// Files can have enums
287288
enums := fd.Enums()
288289
for i := 0; i < enums.Len(); i++ {
289-
AddEnumToSchema(opts, enums.Get(i), spec)
290+
enum := enums.Get(i)
291+
if visibility.ShouldBeFiltered(visibility.GetVisibilityRule(enum), opts.AllowedVisibilities) {
292+
continue
293+
}
294+
AddEnumToSchema(opts, enum, spec)
290295
}
291296

292297
// Files can have messages
293298
messages := fd.Messages()
294299
for i := 0; i < messages.Len(); i++ {
295-
AddMessageSchemas(opts, messages.Get(i), spec)
300+
message := messages.Get(i)
301+
if visibility.ShouldBeFiltered(visibility.GetVisibilityRule(message), opts.AllowedVisibilities) {
302+
continue
303+
}
304+
AddMessageSchemas(opts, message, spec)
296305
}
297306
}
298307

@@ -338,6 +347,11 @@ func appendServiceDocs(opts options.Options, spec *v3.Document, fd protoreflect.
338347
if !opts.HasService(service.FullName()) {
339348
continue
340349
}
350+
// Add visibility filtering for services
351+
if visibility.ShouldBeFiltered(visibility.GetVisibilityRule(service), opts.AllowedVisibilities) {
352+
opts.Logger.Debug("Filtering service due to visibility", slog.String("service", string(service.FullName())), slog.Any("restriction_selectors", opts.AllowedVisibilities))
353+
continue
354+
}
341355

342356
builder.WriteString("## ")
343357
builder.WriteString(string(service.FullName()))

internal/converter/converter_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ var scenarios = []Scenario{
3939
{Name: "with_google_error_detail", Options: "with-google-error-detail"},
4040
{Name: "twirp", Options: "features=google.api.http;twirp;gnostic;protovalidate"},
4141
{Name: "twirp_only", Options: "features=twirp"},
42+
{Name: "visibility", Options: "features=google.api.http;gnostic;protovalidate,allowed-visibilities=INTERNAL;PREVIEW"},
4243
{Name: "disable_default_response", Options: "disable-default-response"},
4344
}
4445

internal/converter/options/options.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ type Options struct {
7373
DisableDefaultResponse bool
7474
// EnabledFeatures is a map of enabled features.
7575
EnabledFeatures map[Feature]bool
76+
// AllowedVisibilities is a map of visibility strings to include. If an element has a `google.api.visibility` rule with a `restriction` that is not in this map, it will be excluded.
77+
AllowedVisibilities map[string]bool
7678

7779
MessageAnnotator MessageAnnotator
7880
FieldAnnotator FieldAnnotator
@@ -253,6 +255,12 @@ func FromString(s string) (Options, error) {
253255
return opts, err
254256
}
255257
opts.Services = append(opts.Services, patterns...)
258+
case strings.HasPrefix(param, "allowed-visibilities="):
259+
selectors := strings.Split(param[len("allowed-visibilities="):], ";")
260+
opts.AllowedVisibilities = make(map[string]bool)
261+
for _, selector := range selectors {
262+
opts.AllowedVisibilities[selector] = true
263+
}
256264
default:
257265
return opts, fmt.Errorf("invalid parameter: %s", param)
258266
}

internal/converter/paths.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package converter
22

33
import (
4+
"log/slog"
5+
46
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
57
"github.com/pb33f/libopenapi/orderedmap"
68
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/connectrpc"
@@ -9,6 +11,7 @@ import (
911
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/options"
1012
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/twirp"
1113
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/util"
14+
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/visibility"
1215
"google.golang.org/protobuf/reflect/protoreflect"
1316
)
1417

@@ -23,6 +26,11 @@ func addPathItemsFromFile(opts options.Options, fd protoreflect.FileDescriptor,
2326
for j := 0; j < methods.Len(); j++ {
2427
method := methods.Get(j)
2528

29+
if visibility.ShouldBeFiltered(visibility.GetVisibilityRule(method), opts.AllowedVisibilities) {
30+
opts.Logger.Debug("Filtering method due to visibility", slog.String("method", string(method.FullName())), slog.Any("restriction_selectors", opts.AllowedVisibilities))
31+
continue
32+
}
33+
2634
// No matter what, we add the schemas for the method input/output
2735
AddMessageSchemas(opts, method.Input(), doc)
2836
AddMessageSchemas(opts, method.Output(), doc)
@@ -73,7 +81,6 @@ func addPathItemsFromFile(opts options.Options, fd protoreflect.FileDescriptor,
7381

7482
return nil
7583
}
76-
7784
func mergePathItems(existing, new *v3.PathItem) {
7885
// Merge operations
7986
operations := []struct {

internal/converter/schema.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ import (
1313
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/options"
1414
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/schema"
1515
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/util"
16+
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/visibility"
1617
)
1718

1819
func AddMessageSchemas(opts options.Options, md protoreflect.MessageDescriptor, doc *v3.Document) {
1920
if md == nil {
2021
return
2122
}
23+
if visibility.ShouldBeFiltered(visibility.GetVisibilityRule(md), opts.AllowedVisibilities) {
24+
opts.Logger.Debug("Filtering message due to visibility", slog.String("message", string(md.FullName())), slog.Any("restriction_selectors", opts.AllowedVisibilities))
25+
return
26+
}
2227
if _, ok := doc.Components.Schemas.Get(string(md.FullName())); ok {
2328
return
2429
}
@@ -60,6 +65,10 @@ func AddEnumToSchema(opts options.Options, ed protoreflect.EnumDescriptor, doc *
6065
if ed == nil {
6166
return
6267
}
68+
if visibility.ShouldBeFiltered(visibility.GetVisibilityRule(ed), opts.AllowedVisibilities) {
69+
opts.Logger.Debug("Filtering enum due to visibility", slog.String("enum", string(ed.FullName())), slog.Any("restriction_selectors", opts.AllowedVisibilities))
70+
return
71+
}
6372
if _, ok := doc.Components.Schemas.Get(string(ed.FullName())); ok {
6473
return
6574
}
@@ -75,6 +84,10 @@ func enumToSchema(opts options.Options, tt protoreflect.EnumDescriptor) (string,
7584
values := tt.Values()
7685
for i := 0; i < values.Len(); i++ {
7786
value := values.Get(i)
87+
if visibility.ShouldBeFiltered(visibility.GetVisibilityRule(value), opts.AllowedVisibilities) {
88+
opts.Logger.Debug("Filtering enum value due to visibility", slog.String("enum_value", string(value.FullName())), slog.Any("restriction_selectors", opts.AllowedVisibilities))
89+
continue // Skip this enum value
90+
}
7891
children = append(children, utils.CreateStringNode(string(value.Name())))
7992
if opts.IncludeNumberEnumValues {
8093
children = append(children, utils.CreateIntNode(strconv.FormatInt(int64(value.Number()), 10)))

internal/converter/schema/schema.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/pb33f/libopenapi/utils"
1212
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/options"
1313
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/util"
14+
"github.com/sudorandom/protoc-gen-connect-openapi/internal/converter/visibility"
1415
"go.yaml.in/yaml/v4"
1516
"google.golang.org/protobuf/reflect/protoreflect"
1617
)
@@ -42,6 +43,10 @@ func MessageToSchema(opts options.Options, tt protoreflect.MessageDescriptor) (s
4243
fields := tt.Fields()
4344
for i := 0; i < fields.Len(); i++ {
4445
field := fields.Get(i)
46+
if visibility.ShouldBeFiltered(visibility.GetVisibilityRule(field), opts.AllowedVisibilities) {
47+
opts.Logger.Debug("Filtering field due to visibility", slog.String("field", string(field.FullName())), slog.Any("restriction_selectors", opts.AllowedVisibilities))
48+
continue
49+
}
4550
if oneOf := field.ContainingOneof(); oneOf != nil && !oneOf.IsSynthetic() {
4651
oneOneGroups[oneOf.FullName()] = append(oneOneGroups[oneOf.FullName()], field)
4752
continue
5.46 KB
Binary file not shown.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
"openapi": "3.1.0",
3+
"info": {
4+
"title": "visibility"
5+
},
6+
"paths": {
7+
"/v1/messages/{message_id}": {
8+
"get": {
9+
"tags": [
10+
"visibility.VisibilityService"
11+
],
12+
"summary": "GetMessage",
13+
"operationId": "visibility.VisibilityService.GetMessage",
14+
"parameters": [
15+
{
16+
"name": "message_id",
17+
"in": "path",
18+
"required": true,
19+
"schema": {
20+
"type": "string",
21+
"title": "message_id"
22+
}
23+
}
24+
],
25+
"responses": {
26+
"200": {
27+
"description": "Success",
28+
"content": {
29+
"application/json": {
30+
"schema": {
31+
"$ref": "#/components/schemas/visibility.Message"
32+
}
33+
}
34+
}
35+
}
36+
}
37+
}
38+
}
39+
},
40+
"components": {
41+
"schemas": {
42+
"visibility.GetMessageRequest": {
43+
"type": "object",
44+
"properties": {
45+
"messageId": {
46+
"type": "string",
47+
"title": "message_id"
48+
}
49+
},
50+
"title": "GetMessageRequest",
51+
"additionalProperties": false
52+
},
53+
"visibility.Message": {
54+
"type": "object",
55+
"properties": {
56+
"publicField": {
57+
"type": "string",
58+
"title": "public_field"
59+
},
60+
"internalField": {
61+
"type": "string",
62+
"title": "internal_field"
63+
},
64+
"previewField": {
65+
"type": "string",
66+
"title": "preview_field"
67+
},
68+
"anotherPublicField": {
69+
"type": "string",
70+
"title": "another_public_field"
71+
}
72+
},
73+
"title": "Message",
74+
"additionalProperties": false
75+
}
76+
}
77+
},
78+
"security": [],
79+
"tags": [
80+
{
81+
"name": "visibility.VisibilityService"
82+
}
83+
]
84+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
openapi: 3.1.0
2+
info:
3+
title: visibility
4+
paths:
5+
/v1/messages/{message_id}:
6+
get:
7+
tags:
8+
- visibility.VisibilityService
9+
summary: GetMessage
10+
operationId: visibility.VisibilityService.GetMessage
11+
parameters:
12+
- name: message_id
13+
in: path
14+
required: true
15+
schema:
16+
type: string
17+
title: message_id
18+
responses:
19+
"200":
20+
description: Success
21+
content:
22+
application/json:
23+
schema:
24+
$ref: '#/components/schemas/visibility.Message'
25+
components:
26+
schemas:
27+
visibility.GetMessageRequest:
28+
type: object
29+
properties:
30+
messageId:
31+
type: string
32+
title: message_id
33+
title: GetMessageRequest
34+
additionalProperties: false
35+
visibility.Message:
36+
type: object
37+
properties:
38+
publicField:
39+
type: string
40+
title: public_field
41+
internalField:
42+
type: string
43+
title: internal_field
44+
previewField:
45+
type: string
46+
title: preview_field
47+
anotherPublicField:
48+
type: string
49+
title: another_public_field
50+
title: Message
51+
additionalProperties: false
52+
security: []
53+
tags:
54+
- name: visibility.VisibilityService

0 commit comments

Comments
 (0)