1pub use buffa::editions::{
14 EnumType, FieldPresence, JsonFormat, MessageEncoding, RepeatedFieldEncoding, ResolvedFeatures,
15 Utf8Validation,
16};
17
18use crate::generated::descriptor::feature_set::{
19 EnumType as FeatureSetEnumType, FieldPresence as FeatureSetFieldPresence,
20 JsonFormat as FeatureSetJsonFormat, MessageEncoding as FeatureSetMessageEncoding,
21 RepeatedFieldEncoding as FeatureSetRepeatedFieldEncoding,
22 Utf8Validation as FeatureSetUtf8Validation,
23};
24use crate::generated::descriptor::{
25 DescriptorProto, Edition, EnumDescriptorProto, FeatureSet, FieldDescriptorProto,
26 FileDescriptorProto, OneofDescriptorProto,
27};
28
29#[must_use]
32pub fn for_file(file: &FileDescriptorProto) -> ResolvedFeatures {
33 match file.syntax.as_deref() {
34 Some("proto3") => {
35 let base = ResolvedFeatures::proto3_defaults();
36 merge(base, file_features(file))
37 }
38 Some("editions") => {
39 let edition = file.edition.unwrap_or(Edition::EDITION_UNKNOWN);
40 let base = for_edition(edition);
41 merge(base, file_features(file))
42 }
43 _ => {
45 let base = ResolvedFeatures::proto2_defaults();
46 merge(base, file_features(file))
47 }
48 }
49}
50
51#[must_use]
56pub fn resolve_child(
57 parent: &ResolvedFeatures,
58 child_features: Option<&FeatureSet>,
59) -> ResolvedFeatures {
60 merge(*parent, child_features)
61}
62
63fn for_edition(edition: Edition) -> ResolvedFeatures {
65 match edition {
66 Edition::EDITION_PROTO2 | Edition::EDITION_LEGACY => ResolvedFeatures::proto2_defaults(),
67 Edition::EDITION_PROTO3 => ResolvedFeatures::proto3_defaults(),
68 Edition::EDITION_2023 | Edition::EDITION_2024 => ResolvedFeatures::edition_2023_defaults(),
73 _ => ResolvedFeatures::edition_2023_defaults(),
77 }
78}
79
80fn merge(parent: ResolvedFeatures, features: Option<&FeatureSet>) -> ResolvedFeatures {
84 let Some(fs) = features else {
85 return parent;
86 };
87 ResolvedFeatures {
88 field_presence: fs
89 .field_presence
90 .and_then(convert_field_presence)
91 .unwrap_or(parent.field_presence),
92 enum_type: fs
93 .enum_type
94 .and_then(convert_enum_type)
95 .unwrap_or(parent.enum_type),
96 repeated_field_encoding: fs
97 .repeated_field_encoding
98 .and_then(convert_repeated_field_encoding)
99 .unwrap_or(parent.repeated_field_encoding),
100 utf8_validation: fs
101 .utf8_validation
102 .and_then(convert_utf8_validation)
103 .unwrap_or(parent.utf8_validation),
104 message_encoding: fs
105 .message_encoding
106 .and_then(convert_message_encoding)
107 .unwrap_or(parent.message_encoding),
108 json_format: fs
109 .json_format
110 .and_then(convert_json_format)
111 .unwrap_or(parent.json_format),
112 }
113}
114
115fn file_features(file: &FileDescriptorProto) -> Option<&FeatureSet> {
119 file.options
120 .as_option()
121 .and_then(|o| o.features.as_option())
122}
123
124#[must_use]
126pub fn message_features(msg: &DescriptorProto) -> Option<&FeatureSet> {
127 msg.options.as_option().and_then(|o| o.features.as_option())
128}
129
130#[must_use]
132pub fn field_features(field: &FieldDescriptorProto) -> Option<&FeatureSet> {
133 field
134 .options
135 .as_option()
136 .and_then(|o| o.features.as_option())
137}
138
139#[must_use]
141pub fn enum_features(e: &EnumDescriptorProto) -> Option<&FeatureSet> {
142 e.options.as_option().and_then(|o| o.features.as_option())
143}
144
145#[must_use]
147pub fn oneof_features(o: &OneofDescriptorProto) -> Option<&FeatureSet> {
148 o.options.as_option().and_then(|o| o.features.as_option())
149}
150
151fn convert_field_presence(v: FeatureSetFieldPresence) -> Option<FieldPresence> {
158 match v {
159 FeatureSetFieldPresence::EXPLICIT => Some(FieldPresence::Explicit),
160 FeatureSetFieldPresence::IMPLICIT => Some(FieldPresence::Implicit),
161 FeatureSetFieldPresence::LEGACY_REQUIRED => Some(FieldPresence::LegacyRequired),
162 FeatureSetFieldPresence::FIELD_PRESENCE_UNKNOWN => None,
163 }
164}
165
166fn convert_enum_type(v: FeatureSetEnumType) -> Option<EnumType> {
167 match v {
168 FeatureSetEnumType::OPEN => Some(EnumType::Open),
169 FeatureSetEnumType::CLOSED => Some(EnumType::Closed),
170 FeatureSetEnumType::ENUM_TYPE_UNKNOWN => None,
171 }
172}
173
174fn convert_repeated_field_encoding(
175 v: FeatureSetRepeatedFieldEncoding,
176) -> Option<RepeatedFieldEncoding> {
177 match v {
178 FeatureSetRepeatedFieldEncoding::PACKED => Some(RepeatedFieldEncoding::Packed),
179 FeatureSetRepeatedFieldEncoding::EXPANDED => Some(RepeatedFieldEncoding::Expanded),
180 FeatureSetRepeatedFieldEncoding::REPEATED_FIELD_ENCODING_UNKNOWN => None,
181 }
182}
183
184fn convert_utf8_validation(v: FeatureSetUtf8Validation) -> Option<Utf8Validation> {
185 match v {
186 FeatureSetUtf8Validation::VERIFY => Some(Utf8Validation::Verify),
187 FeatureSetUtf8Validation::NONE => Some(Utf8Validation::None),
188 FeatureSetUtf8Validation::UTF8_VALIDATION_UNKNOWN => None,
189 }
190}
191
192fn convert_message_encoding(v: FeatureSetMessageEncoding) -> Option<MessageEncoding> {
193 match v {
194 FeatureSetMessageEncoding::LENGTH_PREFIXED => Some(MessageEncoding::LengthPrefixed),
195 FeatureSetMessageEncoding::DELIMITED => Some(MessageEncoding::Delimited),
196 FeatureSetMessageEncoding::MESSAGE_ENCODING_UNKNOWN => None,
197 }
198}
199
200fn convert_json_format(v: FeatureSetJsonFormat) -> Option<JsonFormat> {
201 match v {
202 FeatureSetJsonFormat::ALLOW => Some(JsonFormat::Allow),
203 FeatureSetJsonFormat::LEGACY_BEST_EFFORT => Some(JsonFormat::LegacyBestEffort),
204 FeatureSetJsonFormat::JSON_FORMAT_UNKNOWN => None,
205 }
206}
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::generated::descriptor::FeatureSetDefaults;
211
212 #[test]
213 fn proto2_file_returns_proto2_defaults() {
214 let file = FileDescriptorProto {
215 name: Some("test.proto".into()),
216 ..Default::default()
217 };
218 let f = for_file(&file);
219 assert_eq!(f, ResolvedFeatures::proto2_defaults());
220 }
221
222 #[test]
223 fn proto3_file_returns_proto3_defaults() {
224 let file = FileDescriptorProto {
225 name: Some("test.proto".into()),
226 syntax: Some("proto3".into()),
227 ..Default::default()
228 };
229 let f = for_file(&file);
230 assert_eq!(f, ResolvedFeatures::proto3_defaults());
231 }
232
233 #[test]
234 fn editions_2023_file_returns_edition_defaults() {
235 let file = FileDescriptorProto {
236 name: Some("test.proto".into()),
237 syntax: Some("editions".into()),
238 edition: Some(Edition::EDITION_2023),
239 ..Default::default()
240 };
241 let f = for_file(&file);
242 assert_eq!(f, ResolvedFeatures::edition_2023_defaults());
243 }
244
245 #[test]
246 fn editions_2024_shares_2023_defaults() {
247 let file = FileDescriptorProto {
248 name: Some("test.proto".into()),
249 syntax: Some("editions".into()),
250 edition: Some(Edition::EDITION_2024),
251 ..Default::default()
252 };
253 let f = for_file(&file);
254 assert_eq!(f, ResolvedFeatures::edition_2023_defaults());
255 }
256
257 #[test]
258 fn edition_proto2_maps_to_proto2_defaults() {
259 let file = FileDescriptorProto {
260 name: Some("test.proto".into()),
261 syntax: Some("editions".into()),
262 edition: Some(Edition::EDITION_PROTO2),
263 ..Default::default()
264 };
265 let f = for_file(&file);
266 assert_eq!(f, ResolvedFeatures::proto2_defaults());
267 }
268
269 #[test]
270 fn edition_legacy_maps_to_proto2_defaults() {
271 assert_eq!(
272 for_edition(Edition::EDITION_LEGACY),
273 ResolvedFeatures::proto2_defaults()
274 );
275 }
276
277 #[test]
278 fn child_inherits_parent_when_no_override() {
279 let parent = ResolvedFeatures::proto2_defaults();
280 let child = resolve_child(&parent, None);
281 assert_eq!(parent, child);
282 }
283
284 #[test]
285 fn child_partial_override() {
286 let parent = ResolvedFeatures::proto3_defaults();
287 let override_fs = FeatureSet {
288 enum_type: Some(FeatureSetEnumType::CLOSED),
289 ..Default::default()
290 };
291 let child = resolve_child(&parent, Some(&override_fs));
292 assert_eq!(child.enum_type, EnumType::Closed);
293 assert_eq!(child.field_presence, FieldPresence::Implicit);
294 assert_eq!(child.repeated_field_encoding, RepeatedFieldEncoding::Packed);
295 }
296
297 #[test]
298 fn child_full_override() {
299 let parent = ResolvedFeatures::proto3_defaults();
300 let override_fs = FeatureSet {
301 field_presence: Some(FeatureSetFieldPresence::EXPLICIT),
302 enum_type: Some(FeatureSetEnumType::CLOSED),
303 repeated_field_encoding: Some(FeatureSetRepeatedFieldEncoding::EXPANDED),
304 utf8_validation: Some(FeatureSetUtf8Validation::NONE),
305 message_encoding: Some(FeatureSetMessageEncoding::DELIMITED),
306 json_format: Some(FeatureSetJsonFormat::LEGACY_BEST_EFFORT),
307 ..Default::default()
308 };
309 let child = resolve_child(&parent, Some(&override_fs));
310 assert_eq!(child.field_presence, FieldPresence::Explicit);
311 assert_eq!(child.enum_type, EnumType::Closed);
312 assert_eq!(
313 child.repeated_field_encoding,
314 RepeatedFieldEncoding::Expanded
315 );
316 assert_eq!(child.utf8_validation, Utf8Validation::None);
317 assert_eq!(child.message_encoding, MessageEncoding::Delimited);
318 assert_eq!(child.json_format, JsonFormat::LegacyBestEffort);
319 }
320
321 #[test]
322 fn unknown_enum_values_are_treated_as_unset() {
323 let parent = ResolvedFeatures::edition_2023_defaults();
324 let override_fs = FeatureSet {
325 field_presence: Some(FeatureSetFieldPresence::FIELD_PRESENCE_UNKNOWN),
326 enum_type: Some(FeatureSetEnumType::ENUM_TYPE_UNKNOWN),
327 ..Default::default()
328 };
329 let child = resolve_child(&parent, Some(&override_fs));
330 assert_eq!(child.field_presence, parent.field_presence);
331 assert_eq!(child.enum_type, parent.enum_type);
332 }
333
334 #[test]
335 fn editions_file_with_file_level_override() {
336 let mut file = FileDescriptorProto {
337 name: Some("test.proto".into()),
338 syntax: Some("editions".into()),
339 edition: Some(Edition::EDITION_2023),
340 ..Default::default()
341 };
342 file.options
343 .get_or_insert_default()
344 .features
345 .get_or_insert_default()
346 .enum_type = Some(FeatureSetEnumType::CLOSED);
347
348 let f = for_file(&file);
349 assert_eq!(f.enum_type, EnumType::Closed);
350 assert_eq!(f.field_presence, FieldPresence::Explicit);
351 assert_eq!(f.repeated_field_encoding, RepeatedFieldEncoding::Packed);
352 }
353
354 #[test]
355 fn multi_level_hierarchy() {
356 let file_features = for_edition(Edition::EDITION_2023);
358 assert_eq!(file_features.enum_type, EnumType::Open);
359
360 let msg_override = FeatureSet {
362 enum_type: Some(FeatureSetEnumType::CLOSED),
363 ..Default::default()
364 };
365 let msg_features = resolve_child(&file_features, Some(&msg_override));
366 assert_eq!(msg_features.enum_type, EnumType::Closed);
367 assert_eq!(msg_features.field_presence, FieldPresence::Explicit);
368
369 let field_override = FeatureSet {
371 field_presence: Some(FeatureSetFieldPresence::IMPLICIT),
372 ..Default::default()
373 };
374 let field_features = resolve_child(&msg_features, Some(&field_override));
375 assert_eq!(field_features.field_presence, FieldPresence::Implicit);
377 assert_eq!(field_features.enum_type, EnumType::Closed);
379 assert_eq!(
381 field_features.repeated_field_encoding,
382 RepeatedFieldEncoding::Packed
383 );
384 }
385
386 fn resolve_protoc_defaults(
401 fixed: Option<&FeatureSet>,
402 overridable: Option<&FeatureSet>,
403 ) -> ResolvedFeatures {
404 let sentinel = ResolvedFeatures {
407 field_presence: FieldPresence::LegacyRequired,
408 enum_type: EnumType::Closed,
409 repeated_field_encoding: RepeatedFieldEncoding::Expanded,
410 utf8_validation: Utf8Validation::None,
411 message_encoding: MessageEncoding::Delimited,
412 json_format: JsonFormat::LegacyBestEffort,
413 };
414 let after_fixed = merge(sentinel, fixed);
415 let result = merge(after_fixed, overridable);
416
417 assert_ne!(
420 result.field_presence,
421 FieldPresence::LegacyRequired,
422 "protoc did not set field_presence in either fixed or overridable features"
423 );
424 assert_ne!(
425 result.message_encoding,
426 MessageEncoding::Delimited,
427 "protoc did not set message_encoding in either fixed or overridable features"
428 );
429
430 result
431 }
432
433 #[test]
445 fn hardcoded_defaults_match_protoc_edition_defaults() {
446 use buffa::Message;
447
448 let binpb = include_bytes!("../../conformance/edition_defaults.binpb");
449 let defaults = FeatureSetDefaults::decode_from_slice(binpb)
450 .expect("failed to parse edition_defaults.binpb");
451
452 let expected_mappings: &[(Edition, ResolvedFeatures)] = &[
457 (Edition::EDITION_LEGACY, ResolvedFeatures::proto2_defaults()),
458 (Edition::EDITION_PROTO2, ResolvedFeatures::proto2_defaults()),
459 (Edition::EDITION_PROTO3, ResolvedFeatures::proto3_defaults()),
460 (
461 Edition::EDITION_2023,
462 ResolvedFeatures::edition_2023_defaults(),
463 ),
464 (
465 Edition::EDITION_2024,
466 ResolvedFeatures::edition_2023_defaults(),
467 ),
468 ];
469
470 for &(target_edition, ref expected) in expected_mappings {
471 let entry = defaults
475 .defaults
476 .iter()
477 .rfind(|d| {
478 d.edition
479 .is_some_and(|e| (e as i32) <= (target_edition as i32))
480 })
481 .unwrap_or_else(|| panic!("no defaults entry for edition {target_edition:?}"));
482
483 let resolved = resolve_protoc_defaults(
484 entry.fixed_features.as_option(),
485 entry.overridable_features.as_option(),
486 );
487
488 assert_eq!(
489 &resolved, expected,
490 "defaults mismatch for edition {target_edition:?}: \
491 protoc emitted {resolved:?}, we hardcode {expected:?}"
492 );
493 }
494 }
495}