aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/google.golang.org/protobuf/reflect/protodesc/editions.go
blob: 2a6b29d1791c7754567f8963ebed4309feeef04b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package protodesc

import (
	"fmt"
	"os"
	"sync"

	"google.golang.org/protobuf/internal/editiondefaults"
	"google.golang.org/protobuf/internal/filedesc"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/types/descriptorpb"
	gofeaturespb "google.golang.org/protobuf/types/gofeaturespb"
)

const (
	SupportedEditionsMinimum = descriptorpb.Edition_EDITION_PROTO2
	SupportedEditionsMaximum = descriptorpb.Edition_EDITION_2023
)

var defaults = &descriptorpb.FeatureSetDefaults{}
var defaultsCacheMu sync.Mutex
var defaultsCache = make(map[filedesc.Edition]*descriptorpb.FeatureSet)

func init() {
	err := proto.Unmarshal(editiondefaults.Defaults, defaults)
	if err != nil {
		fmt.Fprintf(os.Stderr, "unmarshal editions defaults: %v\n", err)
		os.Exit(1)
	}
}

func fromEditionProto(epb descriptorpb.Edition) filedesc.Edition {
	return filedesc.Edition(epb)
}

func toEditionProto(ed filedesc.Edition) descriptorpb.Edition {
	switch ed {
	case filedesc.EditionUnknown:
		return descriptorpb.Edition_EDITION_UNKNOWN
	case filedesc.EditionProto2:
		return descriptorpb.Edition_EDITION_PROTO2
	case filedesc.EditionProto3:
		return descriptorpb.Edition_EDITION_PROTO3
	case filedesc.Edition2023:
		return descriptorpb.Edition_EDITION_2023
	default:
		panic(fmt.Sprintf("unknown value for edition: %v", ed))
	}
}

func getFeatureSetFor(ed filedesc.Edition) *descriptorpb.FeatureSet {
	defaultsCacheMu.Lock()
	defer defaultsCacheMu.Unlock()
	if def, ok := defaultsCache[ed]; ok {
		return def
	}
	edpb := toEditionProto(ed)
	if defaults.GetMinimumEdition() > edpb || defaults.GetMaximumEdition() < edpb {
		// This should never happen protodesc.(FileOptions).New would fail when
		// initializing the file descriptor.
		// This most likely means the embedded defaults were not updated.
		fmt.Fprintf(os.Stderr, "internal error: unsupported edition %v (did you forget to update the embedded defaults (i.e. the bootstrap descriptor proto)?)\n", edpb)
		os.Exit(1)
	}
	fs := defaults.GetDefaults()[0].GetFeatures()
	// Using a linear search for now.
	// Editions are guaranteed to be sorted and thus we could use a binary search.
	// Given that there are only a handful of editions (with one more per year)
	// there is not much reason to use a binary search.
	for _, def := range defaults.GetDefaults() {
		if def.GetEdition() <= edpb {
			fs = def.GetFeatures()
		} else {
			break
		}
	}
	defaultsCache[ed] = fs
	return fs
}

// mergeEditionFeatures merges the parent and child feature sets. This function
// should be used when initializing Go descriptors from descriptor protos which
// is why the parent is a filedesc.EditionsFeatures (Go representation) while
// the child is a descriptorproto.FeatureSet (protoc representation).
// Any feature set by the child overwrites what is set by the parent.
func mergeEditionFeatures(parentDesc protoreflect.Descriptor, child *descriptorpb.FeatureSet) filedesc.EditionFeatures {
	var parentFS filedesc.EditionFeatures
	switch p := parentDesc.(type) {
	case *filedesc.File:
		parentFS = p.L1.EditionFeatures
	case *filedesc.Message:
		parentFS = p.L1.EditionFeatures
	default:
		panic(fmt.Sprintf("unknown parent type %T", parentDesc))
	}
	if child == nil {
		return parentFS
	}
	if fp := child.FieldPresence; fp != nil {
		parentFS.IsFieldPresence = *fp == descriptorpb.FeatureSet_LEGACY_REQUIRED ||
			*fp == descriptorpb.FeatureSet_EXPLICIT
		parentFS.IsLegacyRequired = *fp == descriptorpb.FeatureSet_LEGACY_REQUIRED
	}
	if et := child.EnumType; et != nil {
		parentFS.IsOpenEnum = *et == descriptorpb.FeatureSet_OPEN
	}

	if rfe := child.RepeatedFieldEncoding; rfe != nil {
		parentFS.IsPacked = *rfe == descriptorpb.FeatureSet_PACKED
	}

	if utf8val := child.Utf8Validation; utf8val != nil {
		parentFS.IsUTF8Validated = *utf8val == descriptorpb.FeatureSet_VERIFY
	}

	if me := child.MessageEncoding; me != nil {
		parentFS.IsDelimitedEncoded = *me == descriptorpb.FeatureSet_DELIMITED
	}

	if jf := child.JsonFormat; jf != nil {
		parentFS.IsJSONCompliant = *jf == descriptorpb.FeatureSet_ALLOW
	}

	if goFeatures, ok := proto.GetExtension(child, gofeaturespb.E_Go).(*gofeaturespb.GoFeatures); ok && goFeatures != nil {
		if luje := goFeatures.LegacyUnmarshalJsonEnum; luje != nil {
			parentFS.GenerateLegacyUnmarshalJSON = *luje
		}
	}

	return parentFS
}

// initFileDescFromFeatureSet initializes editions related fields in fd based
// on fs. If fs is nil it is assumed to be an empty featureset and all fields
// will be initialized with the appropriate default. fd.L1.Edition must be set
// before calling this function.
func initFileDescFromFeatureSet(fd *filedesc.File, fs *descriptorpb.FeatureSet) {
	dfs := getFeatureSetFor(fd.L1.Edition)
	// initialize the featureset with the defaults
	fd.L1.EditionFeatures = mergeEditionFeatures(fd, dfs)
	// overwrite any options explicitly specified
	fd.L1.EditionFeatures = mergeEditionFeatures(fd, fs)
}