xref: /aosp_15_r20/external/golang-protobuf/reflect/protorange/example_test.go (revision 1c12ee1efe575feb122dbf939ff15148a3b3e8f2)
1// Copyright 2020 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package protorange_test
6
7import (
8	"fmt"
9	"strings"
10	"time"
11
12	"google.golang.org/protobuf/encoding/protojson"
13	"google.golang.org/protobuf/internal/detrand"
14	"google.golang.org/protobuf/proto"
15	"google.golang.org/protobuf/reflect/protopath"
16	"google.golang.org/protobuf/reflect/protorange"
17	"google.golang.org/protobuf/reflect/protoreflect"
18	"google.golang.org/protobuf/testing/protopack"
19	"google.golang.org/protobuf/types/known/anypb"
20	"google.golang.org/protobuf/types/known/timestamppb"
21
22	newspb "google.golang.org/protobuf/internal/testprotos/news"
23)
24
25func init() {
26	detrand.Disable()
27}
28
29func mustMarshal(m proto.Message) []byte {
30	b, err := proto.Marshal(m)
31	if err != nil {
32		panic(err)
33	}
34	return b
35}
36
37// Range through every message and clear the unknown fields.
38func Example_discardUnknown() {
39	// Populate the article with unknown fields.
40	m := &newspb.Article{}
41	m.ProtoReflect().SetUnknown(protopack.Message{
42		protopack.Tag{1000, protopack.BytesType}, protopack.String("Hello, world!"),
43	}.Marshal())
44	fmt.Println("has unknown fields?", len(m.ProtoReflect().GetUnknown()) > 0)
45
46	// Range through the message and clear all unknown fields.
47	fmt.Println("clear unknown fields")
48	protorange.Range(m.ProtoReflect(), func(proto protopath.Values) error {
49		m, ok := proto.Index(-1).Value.Interface().(protoreflect.Message)
50		if ok && len(m.GetUnknown()) > 0 {
51			m.SetUnknown(nil)
52		}
53		return nil
54	})
55	fmt.Println("has unknown fields?", len(m.ProtoReflect().GetUnknown()) > 0)
56
57	// Output:
58	// has unknown fields? true
59	// clear unknown fields
60	// has unknown fields? false
61}
62
63// Print the relative paths as Range iterates through a message
64// in a depth-first order.
65func Example_printPaths() {
66	m := &newspb.Article{
67		Author:  "Russ Cox",
68		Date:    timestamppb.New(time.Date(2019, time.November, 8, 0, 0, 0, 0, time.UTC)),
69		Title:   "Go Turns 10",
70		Content: "Happy birthday, Go! This weekend we celebrate the 10th anniversary of the Go release...",
71		Status:  newspb.Article_PUBLISHED,
72		Tags:    []string{"community", "birthday"},
73		Attachments: []*anypb.Any{{
74			TypeUrl: "google.golang.org.BinaryAttachment",
75			Value: mustMarshal(&newspb.BinaryAttachment{
76				Name: "gopher-birthday.png",
77				Data: []byte("<binary data>"),
78			}),
79		}},
80	}
81
82	// Traverse over all reachable values and print the path.
83	protorange.Range(m.ProtoReflect(), func(p protopath.Values) error {
84		fmt.Println(p.Path[1:])
85		return nil
86	})
87
88	// Output:
89	// .author
90	// .date
91	// .date.seconds
92	// .title
93	// .content
94	// .status
95	// .tags
96	// .tags[0]
97	// .tags[1]
98	// .attachments
99	// .attachments[0]
100	// .attachments[0].(google.golang.org.BinaryAttachment)
101	// .attachments[0].(google.golang.org.BinaryAttachment).name
102	// .attachments[0].(google.golang.org.BinaryAttachment).data
103}
104
105// Implement a basic text formatter by ranging through all populated values
106// in a message in depth-first order.
107func Example_formatText() {
108	m := &newspb.Article{
109		Author:  "Brad Fitzpatrick",
110		Date:    timestamppb.New(time.Date(2018, time.February, 16, 0, 0, 0, 0, time.UTC)),
111		Title:   "Go 1.10 is released",
112		Content: "Happy Friday, happy weekend! Today the Go team is happy to announce the release of Go 1.10...",
113		Status:  newspb.Article_PUBLISHED,
114		Tags:    []string{"go1.10", "release"},
115		Attachments: []*anypb.Any{{
116			TypeUrl: "google.golang.org.KeyValueAttachment",
117			Value: mustMarshal(&newspb.KeyValueAttachment{
118				Name: "checksums.txt",
119				Data: map[string]string{
120					"go1.10.src.tar.gz":         "07cbb9d0091b846c6aea40bf5bc0cea7",
121					"go1.10.darwin-amd64.pkg":   "cbb38bb6ff6ea86279e01745984445bf",
122					"go1.10.linux-amd64.tar.gz": "6b3d0e4a5c77352cf4275573817f7566",
123					"go1.10.windows-amd64.msi":  "57bda02030f58f5d2bf71943e1390123",
124				},
125			}),
126		}},
127	}
128
129	// Print a message in a humanly readable format.
130	var indent []byte
131	protorange.Options{
132		Stable: true,
133	}.Range(m.ProtoReflect(),
134		func(p protopath.Values) error {
135			// Print the key.
136			var fd protoreflect.FieldDescriptor
137			last := p.Index(-1)
138			beforeLast := p.Index(-2)
139			switch last.Step.Kind() {
140			case protopath.FieldAccessStep:
141				fd = last.Step.FieldDescriptor()
142				fmt.Printf("%s%s: ", indent, fd.Name())
143			case protopath.ListIndexStep:
144				fd = beforeLast.Step.FieldDescriptor() // lists always appear in the context of a repeated field
145				fmt.Printf("%s%d: ", indent, last.Step.ListIndex())
146			case protopath.MapIndexStep:
147				fd = beforeLast.Step.FieldDescriptor() // maps always appear in the context of a repeated field
148				fmt.Printf("%s%v: ", indent, last.Step.MapIndex().Interface())
149			case protopath.AnyExpandStep:
150				fmt.Printf("%s[%v]: ", indent, last.Value.Message().Descriptor().FullName())
151			case protopath.UnknownAccessStep:
152				fmt.Printf("%s?: ", indent)
153			}
154
155			// Starting printing the value.
156			switch v := last.Value.Interface().(type) {
157			case protoreflect.Message:
158				fmt.Printf("{\n")
159				indent = append(indent, '\t')
160			case protoreflect.List:
161				fmt.Printf("[\n")
162				indent = append(indent, '\t')
163			case protoreflect.Map:
164				fmt.Printf("{\n")
165				indent = append(indent, '\t')
166			case protoreflect.EnumNumber:
167				var ev protoreflect.EnumValueDescriptor
168				if fd != nil {
169					ev = fd.Enum().Values().ByNumber(v)
170				}
171				if ev != nil {
172					fmt.Printf("%v\n", ev.Name())
173				} else {
174					fmt.Printf("%v\n", v)
175				}
176			case string, []byte:
177				fmt.Printf("%q\n", v)
178			default:
179				fmt.Printf("%v\n", v)
180			}
181			return nil
182		},
183		func(p protopath.Values) error {
184			// Finish printing the value.
185			last := p.Index(-1)
186			switch last.Value.Interface().(type) {
187			case protoreflect.Message:
188				indent = indent[:len(indent)-1]
189				fmt.Printf("%s}\n", indent)
190			case protoreflect.List:
191				indent = indent[:len(indent)-1]
192				fmt.Printf("%s]\n", indent)
193			case protoreflect.Map:
194				indent = indent[:len(indent)-1]
195				fmt.Printf("%s}\n", indent)
196			}
197			return nil
198		},
199	)
200
201	// Output:
202	// {
203	// 	author: "Brad Fitzpatrick"
204	// 	date: {
205	// 		seconds: 1518739200
206	// 	}
207	// 	title: "Go 1.10 is released"
208	// 	content: "Happy Friday, happy weekend! Today the Go team is happy to announce the release of Go 1.10..."
209	// 	attachments: [
210	// 		0: {
211	// 			[google.golang.org.KeyValueAttachment]: {
212	// 				name: "checksums.txt"
213	// 				data: {
214	//					go1.10.darwin-amd64.pkg: "cbb38bb6ff6ea86279e01745984445bf"
215	//					go1.10.linux-amd64.tar.gz: "6b3d0e4a5c77352cf4275573817f7566"
216	//					go1.10.src.tar.gz: "07cbb9d0091b846c6aea40bf5bc0cea7"
217	//					go1.10.windows-amd64.msi: "57bda02030f58f5d2bf71943e1390123"
218	// 				}
219	// 			}
220	// 		}
221	// 	]
222	// 	tags: [
223	// 		0: "go1.10"
224	// 		1: "release"
225	// 	]
226	// 	status: PUBLISHED
227	// }
228}
229
230// Scan all protobuf string values for a sensitive word and replace it with
231// a suitable alternative.
232func Example_sanitizeStrings() {
233	m := &newspb.Article{
234		Author:  "Hermione Granger",
235		Date:    timestamppb.New(time.Date(1998, time.May, 2, 0, 0, 0, 0, time.UTC)),
236		Title:   "Harry Potter vanquishes Voldemort once and for all!",
237		Content: "In a final duel between Harry Potter and Lord Voldemort earlier this evening...",
238		Tags:    []string{"HarryPotter", "LordVoldemort"},
239		Attachments: []*anypb.Any{{
240			TypeUrl: "google.golang.org.KeyValueAttachment",
241			Value: mustMarshal(&newspb.KeyValueAttachment{
242				Name: "aliases.txt",
243				Data: map[string]string{
244					"Harry Potter": "The Boy Who Lived",
245					"Tom Riddle":   "Lord Voldemort",
246				},
247			}),
248		}},
249	}
250
251	protorange.Range(m.ProtoReflect(), func(p protopath.Values) error {
252		const (
253			sensitive   = "Voldemort"
254			alternative = "[He-Who-Must-Not-Be-Named]"
255		)
256
257		// Check if there is a sensitive word to redact.
258		last := p.Index(-1)
259		s, ok := last.Value.Interface().(string)
260		if !ok || !strings.Contains(s, sensitive) {
261			return nil
262		}
263		s = strings.Replace(s, sensitive, alternative, -1)
264
265		// Store the redacted string back into the message.
266		beforeLast := p.Index(-2)
267		switch last.Step.Kind() {
268		case protopath.FieldAccessStep:
269			m := beforeLast.Value.Message()
270			fd := last.Step.FieldDescriptor()
271			m.Set(fd, protoreflect.ValueOfString(s))
272		case protopath.ListIndexStep:
273			ls := beforeLast.Value.List()
274			i := last.Step.ListIndex()
275			ls.Set(i, protoreflect.ValueOfString(s))
276		case protopath.MapIndexStep:
277			ms := beforeLast.Value.Map()
278			k := last.Step.MapIndex()
279			ms.Set(k, protoreflect.ValueOfString(s))
280		}
281		return nil
282	})
283
284	fmt.Println(protojson.Format(m))
285
286	// Output:
287	// {
288	//   "author": "Hermione Granger",
289	//   "date": "1998-05-02T00:00:00Z",
290	//   "title": "Harry Potter vanquishes [He-Who-Must-Not-Be-Named] once and for all!",
291	//   "content": "In a final duel between Harry Potter and Lord [He-Who-Must-Not-Be-Named] earlier this evening...",
292	//   "tags": [
293	//     "HarryPotter",
294	//     "Lord[He-Who-Must-Not-Be-Named]"
295	//   ],
296	//   "attachments": [
297	//     {
298	//       "@type": "google.golang.org.KeyValueAttachment",
299	//       "name": "aliases.txt",
300	//       "data": {
301	//         "Harry Potter": "The Boy Who Lived",
302	//         "Tom Riddle": "Lord [He-Who-Must-Not-Be-Named]"
303	//       }
304	//     }
305	//   ]
306	// }
307}
308