marshalField function

List<int> marshalField(
  1. List<int> buffer,
  2. int fieldNumber,
  3. String type,
  4. Object? value, {
  5. bool repeated = false,
  6. bool explicitPresence = false,
  7. bool delimited = false,
  8. List<Map<String, Object?>>? messageDescriptor,
})

Marshals a single scalar field value and appends it to buffer.

Applies proto3 default-value elision: values equal to the type's default (0, 0.0, false, empty string, empty bytes) are not written unless repeated is true (packed encoding handles its own elision).

For TYPE_MESSAGE, value must be either a pre-encoded List<int> or will be treated as such.

Returns buffer with the field bytes appended.

Implementation

List<int> marshalField(
  List<int> buffer,
  int fieldNumber,
  String type,
  Object? value, {
  bool repeated = false,
  bool explicitPresence = false,
  bool delimited = false,
  List<Map<String, Object?>>? messageDescriptor,
}) {
  if (value == null) return buffer;

  // Explicit / required presence: a present value is serialized even when it
  // equals the type default. The low-level encoders below bake in proto3
  // default elision, so route singular scalars through the forced writer.
  // (Message fields track presence separately — handled by the normal path.)
  if (explicitPresence && !repeated && type != 'TYPE_MESSAGE') {
    return _writeScalarForced(buffer, fieldNumber, type, value);
  }

  switch (type) {
    case 'TYPE_INT32':
      final v = _toInt(value);
      if (!repeated && v == 0) return buffer;
      encodeInt32Field(buffer, fieldNumber, v);
    case 'TYPE_INT64':
      final v = _toInt(value);
      if (!repeated && v == 0) return buffer;
      encodeInt64Field(buffer, fieldNumber, v);
    case 'TYPE_UINT32':
      final v = _toInt(value);
      if (!repeated && v == 0) return buffer;
      encodeUint32Field(buffer, fieldNumber, v);
    case 'TYPE_UINT64':
      final v = _toInt(value);
      if (!repeated && v == 0) return buffer;
      encodeUint64Field(buffer, fieldNumber, v);
    case 'TYPE_SINT32':
      final v = _toInt(value);
      if (!repeated && v == 0) return buffer;
      encodeSint32Field(buffer, fieldNumber, v);
    case 'TYPE_SINT64':
      final v = _toInt(value);
      if (!repeated && v == 0) return buffer;
      encodeSint64Field(buffer, fieldNumber, v);
    case 'TYPE_BOOL':
      final v = value as bool;
      if (!repeated && !v) return buffer;
      encodeBoolField(buffer, fieldNumber, v);
    case 'TYPE_ENUM':
      final v = _toInt(value);
      if (!repeated && v == 0) return buffer;
      encodeEnumField(buffer, fieldNumber, v);
    case 'TYPE_FIXED32':
      final v = _toInt(value);
      if (!repeated && v == 0) return buffer;
      encodeFixed32Field(buffer, fieldNumber, v);
    case 'TYPE_FIXED64':
      final v = _toInt(value);
      if (!repeated && v == 0) return buffer;
      encodeFixed64Field(buffer, fieldNumber, v);
    case 'TYPE_SFIXED32':
      final v = _toInt(value);
      if (!repeated && v == 0) return buffer;
      encodeSfixed32Field(buffer, fieldNumber, v);
    case 'TYPE_SFIXED64':
      final v = _toInt(value);
      if (!repeated && v == 0) return buffer;
      encodeSfixed64Field(buffer, fieldNumber, v);
    case 'TYPE_FLOAT':
      final v = _toDouble(value);
      // `identical` (not `==`) so -0.0 is preserved; see encodeFloatField.
      if (!repeated && identical(v, 0.0)) return buffer;
      encodeFloatField(buffer, fieldNumber, v);
    case 'TYPE_DOUBLE':
      final v = _toDouble(value);
      // `identical` (not `==`) so -0.0 is preserved; see encodeDoubleField.
      if (!repeated && identical(v, 0.0)) return buffer;
      encodeDoubleField(buffer, fieldNumber, v);
    case 'TYPE_STRING':
      final v = value as String;
      if (!repeated && v.isEmpty) return buffer;
      encodeStringField(buffer, fieldNumber, v);
    case 'TYPE_BYTES':
      final v = value as List<int>;
      if (!repeated && v.isEmpty) return buffer;
      encodeBytesField(buffer, fieldNumber, v);
    case 'TYPE_MESSAGE':
      // Value is either pre-encoded bytes (List<int>) or a map that needs
      // marshaling via a messageDescriptor.
      final List<int> v;
      if (value is List<int>) {
        v = value;
      } else if (value is Map<String, Object?> && messageDescriptor != null) {
        v = marshal(value, messageDescriptor);
      } else if (value is Map<String, Object?>) {
        throw ArgumentError(
          'TYPE_MESSAGE field $fieldNumber received a Map but no '
          'messageDescriptor was provided for auto-marshaling',
        );
      } else {
        throw ArgumentError(
          'TYPE_MESSAGE field $fieldNumber expected List<int> or '
          'Map<String, Object?>, got ${value.runtimeType}',
        );
      }
      if (delimited) {
        // DELIMITED (group): a present submessage is always emitted, including
        // an empty one (START_GROUP/END_GROUP carries presence on its own).
        encodeGroupField(buffer, fieldNumber, v);
      } else {
        // A present singular message is emitted even when empty (tag + length 0)
        // — message fields carry presence, and an absent field was already
        // elided by the null check at the call site. Eliding a present empty
        // message would drop it on round-trip (ValidDataScalar.MESSAGE).
        encodeTag(buffer, fieldNumber, _wtLen);
        encodeBytes(buffer, v);
      }
    default:
      throw ArgumentError('Unknown protobuf field type: $type');
  }

  return buffer;
}