convertToJson function

Expression? convertToJson(
  1. ServerType type,
  2. Expression result, {
  3. bool handleNull = true,
})

Implementation

Expression? convertToJson(
  ServerType type,
  Expression result, {
  bool handleNull = true,
}) {
  if (type.isStream) {
    if (type.typeArguments.length != 1) {
      throw Exception('Unsupported stream type: $type');
    }

    final typeArg = type.typeArguments.first;

    final toJson = convertToJson(typeArg, refer('e'));

    final shouldNest = shouldNestJsonInData(typeArg);

    if (toJson == null && !shouldNest) {
      return null;
    }

    return result.property('map').call([
      Method(
        (b) => b
          ..lambda = true
          ..requiredParameters.add(Parameter((p) => p.name = 'e'))
          ..body = switch ((shouldNest, toJson)) {
            (false, final json?) => json.code,
            (true, _) => literalMap({'data': toJson?.code ?? refer('e')}).code,
            (false, null) => throw Exception('Expected toJson to be non-null'),
          },
      ).closure,
    ]);
  }

  if (type.isNullable && handleNull) {
    final toJson = convertToJson(type, result, handleNull: false);

    if (toJson == null) {
      return null;
    }

    if (type case ServerType(isStringContent: true, isNullable: true)) {
      return createSwitchPattern(result, {
        literalNull: literal(''),
        const Code('_'): toJson,
      });
    }

    if (type case ServerType(isRecord: true, isNullable: true)) {
      return createSwitchPattern(result, {
        literalNull: literalNull,
        const Code('_'): toJson,
      });
    }

    return toJson;
  }

  if (type.isFuture) {
    if (type.typeArguments.length != 1) {
      throw Exception('Unsupported future type: $type');
    }

    return convertToJson(type.typeArguments.first, result);
  }

  if (type.iterableType case final IterableType iterableType) {
    if (type.typeArguments.length != 1) {
      throw Exception('Unsupported iterable type: ${type.iterableType}');
    }

    final typeArg = type.typeArguments.first;

    final toJson = convertToJson(typeArg, refer('e'));

    if (toJson == null) {
      return switch (iterableType) {
        IterableType.set => result.safeProperty(type, 'toList').call([]),
        IterableType.iterable => result.safeProperty(type, 'toList').call([]),
        IterableType.list => null,
      };
    }

    final iterates = Method(
      (p) => p
        ..requiredParameters.add(Parameter((b) => b..name = 'e'))
        ..lambda = true
        ..body = toJson.code,
    ).closure;

    return result
        .safeProperty(type, 'map')
        .call([iterates])
        .property('toList')
        .call([]);
  }

  if (type.isMap) {
    if (type.typeArguments.length != 2) {
      throw Exception('Unsupported map type');
    }

    final [keyType, valueType] = type.typeArguments;

    if (!(keyType.hasToJsonMember || valueType.hasToJsonMember)) {
      return null;
    }

    final keyMethodBody = convertToJson(keyType, refer('key'));
    final valueMethodBody = convertToJson(valueType, refer('value'));

    if (keyMethodBody == null && valueMethodBody == null) {
      return null;
    }

    final iterates = Method(
      (p) => p
        ..requiredParameters.addAll([
          Parameter((b) => b.name = 'key'),
          Parameter((b) => b.name = 'value'),
        ])
        ..lambda = true
        ..body = refer((MapEntry).name).call([
          keyMethodBody ?? refer('key'),
          valueMethodBody ?? refer('value'),
        ]).code,
    ).closure;

    return result.safeProperty(type, 'map').call([iterates]);
  }

  if (type.recordProps case final props?
      when type.isRecord && props.isNotEmpty) {
    Expression extract(ServerRecordProp prop, String access) {
      var extract = result.safeProperty(prop.type, access);
      if (prop.type.hasToJsonMember) {
        extract = extract.safeProperty(prop.type, 'toJson').call([]);
      }

      return extract;
    }

    Expression namedProps() {
      final named = props.where((e) => e.isNamed).toList();

      if (named.isEmpty) {
        return const CodeExpression(Code(''));
      }

      return CodeExpression(
        Block.of([
          const Code('{'),
          for (final prop in named)
            if (prop.name case final String name when prop.isNamed) ...[
              literal(name).code,
              const Code(':'),
              extract(prop, name).code,
              const Code(','),
            ],
          const Code('}'),
        ]),
      );
    }

    if (props.first.isNamed) {
      // all props are named
      return namedProps();
    } else {
      Iterable<Expression> positionedProps() sync* {
        for (final (index, prop) in props.indexed) {
          if (prop.isNamed) continue;

          yield extract(
            prop,
            r'$'
            '${index + 1}',
          );
        }
      }

      return CodeExpression(
        Block.of([
          const Code('['),
          for (final value in positionedProps()) ...[
            value.code,
            const Code(','),
          ],
          namedProps().code,
          const Code(']'),
        ]),
      );
    }
  }

  if (type.hasToJsonMember) {
    return result.safeProperty(type, 'toJson').call([]);
  }

  if (type.isStringContent) {
    return result.safeProperty(type, 'value');
  }

  if (type.isEnum) {
    return result.safeProperty(type, 'name');
  }

  return null;
}