coderForField method

  1. @override
String? coderForField(
  1. FieldElement field,
  2. SharedChecker<Model> checker, {
  3. required bool wrappedInFuture,
  4. required Sqlite fieldAnnotation,
})

Produces serializing or deserializing method given a field and checker.

The assignment (data['my_field']: in serializers or myField: in deserializers) is automatically injected by the superclass and should not be included in the output of the coder.

To simplify checking, Futures are unwrapped before they get to this method. If the type was originally a future, wrappedInFuture is true. For example, Future<List<int>> will be an iterable according to the checker and wrappedInFuture will be true.

Implementation

@override
String? coderForField(field, checker, {required wrappedInFuture, required fieldAnnotation}) {
  final fieldValue = serdesValueForField(field, fieldAnnotation.name!, checker: checker);
  final defaultValue = SerdesGenerator.defaultValueSuffix(fieldAnnotation);
  if (field.name == InsertTable.PRIMARY_KEY_FIELD) {
    throw InvalidGenerationSourceError(
      'Field `${InsertTable.PRIMARY_KEY_FIELD}` conflicts with reserved `SqliteModel` getter.',
      todo: 'Rename the field from `${InsertTable.PRIMARY_KEY_FIELD}`',
      element: field,
    );
  }

  if (fieldAnnotation.ignoreFrom) return null;

  if (fieldAnnotation.columnType != null) {
    return fieldValue;
  }

  // DateTime
  if (checker.isDateTime) {
    if (checker.isNullable) {
      return '$fieldValue == null ? null : DateTime.tryParse($fieldValue$defaultValue as String)';
    }
    return 'DateTime.parse($fieldValue$defaultValue as String)';

    // bool
  } else if (checker.isBool) {
    return '$fieldValue == 1';

    // double, int, String
  } else if (checker.isDartCoreType) {
    return '$fieldValue as ${field.type}$defaultValue';

    // Iterable
  } else if (checker.isIterable) {
    final argTypeChecker = SharedChecker<SqliteModel>(checker.argType);
    final argType = checker.unFuturedArgType;
    final castIterable = SerdesGenerator.iterableCast(
      argType,
      isSet: checker.isSet,
      isList: checker.isList,
      isFuture: checker.isArgTypeAFuture,
      forceCast: true,
    );

    if (checker.isArgTypeASibling) {
      final awaited = wrappedInFuture ? 'async => await' : '=>';
      final query = '''
        Query.where('${InsertTable.PRIMARY_KEY_FIELD}', ${InsertTable.PRIMARY_KEY_FIELD}, limit1: true),
      ''';
      final argTypeAsString = SharedChecker.withoutNullability(argType);
      final sqlStatement =
          'SELECT DISTINCT `${InsertForeignKey.joinsTableForeignColumnName(argTypeAsString)}` FROM `${InsertForeignKey.joinsTableName(fieldAnnotation.name!, localTableName: fields.element.name)}` WHERE ${InsertForeignKey.joinsTableLocalColumnName(fields.element.name)} = ?';

      final method = '''
        provider
          .rawQuery('$sqlStatement', [data['${InsertTable.PRIMARY_KEY_COLUMN}'] as int])
          .then((results) {
            final ids = results.map((r) => r['${InsertForeignKey.joinsTableForeignColumnName(argTypeAsString)}']);
            return Future.wait<$argType>(
              ids.map((${InsertTable.PRIMARY_KEY_FIELD}) $awaited ${getAssociationMethod(argType, query: query)})
            );
          })
      ''';

      // Future<Iterable<SqliteModel>>
      if (wrappedInFuture) {
        return method;
      }

      // Iterable<Future<SqliteModel>>
      if (checker.isArgTypeAFuture) {
        return 'await $method';

        // Iterable<SqliteModel>
      } else {
        if (checker.isSet) {
          return '(await $method).toSet()';
        }

        return '(await $method)$castIterable';
      }
    }

    // Iterable<DateTime>
    if (argTypeChecker.isDateTime) {
      return "jsonDecode($fieldValue).map((d) => DateTime.tryParse(d ?? ''))$castIterable";
    }

    // Iterable<enum>
    if (argTypeChecker.isEnum) {
      final deserializeFactory = argTypeChecker.enumDeserializeFactory(providerName);
      if (deserializeFactory != null) {
        return 'jsonDecode($fieldValue ?? []).map(${SharedChecker.withoutNullability(argType)}.$deserializeFactory)';
      }

      if (fieldAnnotation.enumAsString) {
        return "jsonDecode($fieldValue ?? []).whereType<String>().map(${SharedChecker.withoutNullability(argType)}.values.byName)$castIterable";
      }

      final discoveredByIndex =
          'jsonDecode($fieldValue).map((d) => d as int > -1 ? ${SharedChecker.withoutNullability(argType)}.values[d] : null)';
      final nullableSuffix = checker.isNullable ? '?' : '';
      return '$discoveredByIndex$nullableSuffix.whereType<${argType.getDisplayString(withNullability: true)}>()$castIterable';
    }

    // Iterable<bool>
    if (argTypeChecker.isBool) {
      return 'jsonDecode($fieldValue).map((d) => d == 1)$castIterable';
    }

    // Iterable<fromJson>
    if (argTypeChecker.fromJsonConstructor != null) {
      final klass = argTypeChecker.targetType.element as ClassElement;
      final parameterType = argTypeChecker.fromJsonConstructor!.parameters.first.type;
      final nullableSuffix = checker.isNullable ? " ?? '[]'" : '';

      return '''jsonDecode($fieldValue$nullableSuffix).map(
        (d) => ${klass.displayName}.fromJson(d as ${parameterType.getDisplayString(withNullability: true)})
      )$castIterable$defaultValue''';
    }

    // Iterable<double>, Iterable<int>, Iterable<num>, Iterable<Map>, Iterable<String>
    return 'jsonDecode($fieldValue)$castIterable';

    // SqliteModel, Future<SqliteModel>
  } else if (checker.isSibling) {
    var repositoryOperator = checker.isUnFuturedTypeNullable ? '?' : '!';
    if (repositoryHasBeenForceCast) repositoryOperator = '';
    if (repositoryOperator == '!') repositoryHasBeenForceCast = true;

    final query = '''
      Query.where('${InsertTable.PRIMARY_KEY_FIELD}', $fieldValue as int, limit1: true),
    ''';

    if (wrappedInFuture) {
      if (checker.isNullable) {
        return '''($fieldValue > -1
            ? ${getAssociationMethod(checker.unFuturedType, query: query)}
            : null)''';
      }
      return getAssociationMethod(checker.unFuturedType, query: query);
    }

    if (checker.isNullable) {
      return '''($fieldValue > -1
            ? (await repository$repositoryOperator.getAssociation<${SharedChecker.withoutNullability(checker.unFuturedType)}>($query))?.first
            : null)''';
    }
    return '(await repository$repositoryOperator.getAssociation<${SharedChecker.withoutNullability(checker.unFuturedType)}>($query))!.first';

    // enum
  } else if (checker.isEnum) {
    final deserializeFactory = checker.enumDeserializeFactory(providerName);
    if (deserializeFactory != null) {
      return '${checker.isNullable ? "$fieldValue == null ? null :" : ""} ${SharedChecker.withoutNullability(field.type)}.$deserializeFactory($fieldValue)';
    }

    if (fieldAnnotation.enumAsString) {
      final nullablePrefix = checker.isNullable
          ? "$fieldValue == null ? ${fieldAnnotation.defaultValue ?? 'null'} : "
          : '';
      return "$nullablePrefix${SharedChecker.withoutNullability(field.type)}.values.byName($fieldValue as String)";
    }

    if (checker.isNullable) {
      return '($fieldValue > -1 ? ${SharedChecker.withoutNullability(field.type)}.values[$fieldValue as int] : null)$defaultValue';
    }
    return '${SharedChecker.withoutNullability(field.type)}.values[$fieldValue as int]';

    // Map
  } else if (checker.isMap) {
    return 'jsonDecode($fieldValue)';
  } else if (checker.fromJsonConstructor != null) {
    final klass = checker.targetType.element as ClassElement;
    final parameterType = checker.fromJsonConstructor!.parameters.first.type;
    return '${klass.displayName}.fromJson(jsonDecode($fieldValue as String) as ${parameterType.getDisplayString(withNullability: true)})';
  }

  return null;
}