blob: dbdd7d0ee6796a685a2ceebfa82cec35df6688a4 [file]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of 'internal.dart';
/// An object representing a protobuf message field.
class FieldInfo<T> {
/// Cached read-only empty list for this field type. For non-repeated fields
/// this is always `null`. Otherwise it starts as `null` and gets initialized
/// in `readonlyDefault`.
PbList<T>? _emptyList;
/// Name of this field as the `json_name` reported by protoc.
///
/// Example:
///
/// ```proto
/// message Msg {
/// int32 foo_name = 1 [json_name = "barName"];
/// }
/// ```
///
/// Here `name` of the field is `barName`. When `json_name` is not specified
/// in the proto definition, this is the camelCase version of the field name.
/// In the example above, without the `json_name` field option, `name` would
/// be `"fooName"`.
final String name;
/// Name of this field as written in the proto definition.
///
/// Example:
///
/// ```proto
/// message SearchRequest {
/// ...
/// int32 result_per_page = 3;
/// }
/// ```
///
/// `protoName` for the `result_per_page` field above is `"result_per_page"`.
/// The name typically consist of words separated with underscores.
String get protoName {
return _protoName ??= _unCamelCase(name);
}
String? _protoName;
/// Field number as specified in the proto definition.
///
/// Example:
///
/// ```proto
/// message SearchRequest {
/// ...
/// int32 result_per_page = 3;
/// }
/// ```
///
/// `tagNumber` of `result_per_page` field is 3.
final int tagNumber;
/// Index of the field in [FieldSet._values] list of this field's message.
///
/// The value is `null` for extension fields.
final int? index;
/// Type of this field. See [PbFieldType].
final int type;
/// Constructs the default value of a field.
///
/// For repeated fields, only used when the [check] property is `null`.
final MakeDefaultFunc? makeDefault;
/// Creates an empty message or group when decoding a message.
///
/// Only available in fields with message type.
final CreateBuilderFunc? subBuilder;
/// List of all enum values.
///
/// Only available in enum fields.
final List<ProtobufEnum>? enumValues;
/// Default enum value.
///
/// Only available in enum fields.
final ProtobufEnum? defaultEnumValue;
/// Mapping from enum integer values to enum values.
///
/// Only available in enum fields.
final ValueOfFunc? valueOf;
/// Function to verify items when adding to a repeated field.
///
/// Only available in repeated fields.
final CheckFunc<T>? check;
FieldInfo(
this.name,
this.tagNumber,
this.index,
this.type, {
dynamic defaultOrMaker,
this.subBuilder,
this.valueOf,
this.enumValues,
this.defaultEnumValue,
String? protoName,
}) : makeDefault = findMakeDefault(type, defaultOrMaker),
check = null,
_protoName = protoName,
assert(type != 0),
assert(
!PbFieldType.isGroupOrMessage(type) ||
subBuilder != null ||
PbFieldType.isMapField(type),
),
assert(!PbFieldType.isEnum(type) || valueOf != null);
// Represents a field that has been removed by a program transformation.
FieldInfo.dummy(this.index)
: name = '<removed field>',
_protoName = '<removed field>',
tagNumber = 0,
type = 0,
makeDefault = null,
valueOf = null,
check = null,
enumValues = null,
defaultEnumValue = null,
subBuilder = null;
FieldInfo.repeated(
this.name,
this.tagNumber,
this.index,
this.type,
CheckFunc<T> this.check,
this.subBuilder, {
this.valueOf,
this.enumValues,
this.defaultEnumValue,
String? protoName,
}) : makeDefault = (() => PbList<T>(check: check)),
_protoName = protoName,
assert(PbFieldType.isRepeated(type)),
assert(!PbFieldType.isEnum(type) || valueOf != null);
static MakeDefaultFunc? findMakeDefault(int type, dynamic defaultOrMaker) {
if (defaultOrMaker == null) return PbFieldType.defaultForType(type);
if (defaultOrMaker is MakeDefaultFunc) return defaultOrMaker;
return () => defaultOrMaker;
}
/// Whether this represents a dummy field standing in for a field that has
/// been removed by a program transformation.
bool get _isDummy => tagNumber == 0;
bool get isRequired => PbFieldType.isRequired(type);
bool get isRepeated => PbFieldType.isRepeated(type);
bool get isGroupOrMessage => PbFieldType.isGroupOrMessage(type);
bool get isEnum => PbFieldType.isEnum(type);
bool get isMapField => PbFieldType.isMapField(type);
/// Returns a read-only default value for a field. Unlike
/// [GeneratedMessage.getField], doesn't create a repeated field.
dynamic get readonlyDefault {
if (isRepeated) {
return _emptyList ??= PbList.unmodifiable();
}
return makeDefault!();
}
/// Returns true if the field's value is okay to transmit.
/// That is, it doesn't contain any required fields that aren't initialized.
bool _hasRequiredValues(dynamic value) {
if (value == null) return !isRequired; // missing is okay if optional
if (!PbFieldType.isGroupOrMessage(type)) {
return true; // primitive and present
}
if (!isRepeated) {
// A required message: recurse.
final GeneratedMessage message = value;
return message._fieldSet._hasRequiredValues();
}
final List<GeneratedMessage> list = value;
if (list.isEmpty) return true;
// For message types that (recursively) contain no required fields,
// short-circuit the loop.
if (!list[0]._fieldSet._hasRequiredFields) return true;
// Recurse on each item in the list.
return list.every((GeneratedMessage m) => m._fieldSet._hasRequiredValues());
}
/// Appends the dotted path to each required field that's missing a value.
void _appendInvalidFields(List<String> problems, value, String prefix) {
if (value == null) {
if (isRequired) problems.add('$prefix$name');
} else if (!PbFieldType.isGroupOrMessage(type)) {
// primitive and present
} else if (!isRepeated) {
// Required message/group: recurse.
final GeneratedMessage message = value;
message._fieldSet._appendInvalidFields(problems, '$prefix$name.');
} else {
final List<GeneratedMessage> list = value;
if (list.isEmpty) return;
// For message types that (recursively) contain no required fields,
// short-circuit the loop.
if (!list[0]._fieldSet._hasRequiredFields) return;
// Recurse on each item in the list.
var position = 0;
for (final message in list) {
message._fieldSet._appendInvalidFields(
problems,
'$prefix$name[$position].',
);
position++;
}
}
}
/// Creates a repeated field.
PbList<T> _createRepeatedField() {
assert(isRepeated);
return PbList<T>(check: check!);
}
/// Same as above, but allow a tighter typed [PbList] to be created.
PbList<S> _createRepeatedFieldWithType<S extends T>() {
assert(isRepeated);
return PbList<S>(check: check!);
}
/// Convenience method to thread this FieldInfo's reified type parameter to
/// `FieldSet._ensureRepeatedField`.
PbList<T> _ensureRepeatedField(BuilderInfo meta, FieldSet fs) {
return fs._ensureRepeatedField<T>(meta, this);
}
@override
String toString() => name;
}
extension FieldInfoInternalExtension<T> on FieldInfo<T> {
List<T> ensureRepeatedField(BuilderInfo meta, FieldSet fs) =>
_ensureRepeatedField(meta, fs);
}
final RegExp _upperCase = RegExp('[A-Z]');
String _unCamelCase(String name) {
return name.replaceAllMapped(
_upperCase,
(match) => '_${match.group(0)!.toLowerCase()}',
);
}
/// A [FieldInfo] subclass for protobuf `map` fields.
class MapFieldInfo<K, V> extends FieldInfo<PbMap<K, V>?> {
/// Key type of the map. Per proto2 and proto3 specs, this needs to be an
/// integer type or `string`, and the type cannot be `repeated`.
///
/// The `int` value is interpreted the same way as [FieldInfo.type].
final int keyFieldType;
/// Value type of the map. Per proto2 and proto3 specs, this can be any type
/// other than `map`, and the type cannot be `repeated`.
///
/// The `int` value is interpreted the same way as [FieldInfo.type].
final int valueFieldType;
/// Creates a new empty instance of the value type.
///
/// `null` if the value type is not a Message type.
final CreateBuilderFunc? valueCreator;
final BuilderInfo mapEntryBuilderInfo;
MapFieldInfo(
String name,
int tagNumber,
int index,
int type,
this.keyFieldType,
this.valueFieldType,
this.mapEntryBuilderInfo,
this.valueCreator, {
ProtobufEnum? defaultEnumValue,
String? protoName,
}) : assert(PbFieldType.isMapField(type)),
super(
name,
tagNumber,
index,
type,
defaultOrMaker: () => PbMap<K, V>(keyFieldType, valueFieldType),
defaultEnumValue: defaultEnumValue,
protoName: protoName,
) {
assert(!PbFieldType.isEnum(type) || valueOf != null);
}
FieldInfo get valueFieldInfo =>
mapEntryBuilderInfo.fieldInfo[PbMap._valueFieldNumber]!;
PbMap<K, V> _ensureMapField(BuilderInfo meta, FieldSet fs) {
return fs._ensureMapField<K, V>(meta, this);
}
PbMap<K, V> _createMapField() {
assert(isMapField);
return PbMap<K, V>(keyFieldType, valueFieldType);
}
}
extension MapFieldInfoInternalExtension<K, V> on MapFieldInfo<K, V> {
Map<K, V> ensureMapField(BuilderInfo meta, FieldSet fs) =>
_ensureMapField(meta, fs);
}