applyConstraints method

String applyConstraints(
  1. String schema,
  2. Element declaration,
  3. DartType type
)

Applies constraint annotations declared on a field or parameter.

Implementation

String applyConstraints(String schema, Element declaration, DartType type) {
  var output = schema;
  final isCollection =
      type is InterfaceType && (type.isDartCoreList || type.isDartCoreSet);
  final isSet = type is InterfaceType && type.isDartCoreSet;
  final scalarType = isCollection && type.typeArguments.length == 1
      ? type.typeArguments.single
      : type;
  final isNumeric =
      scalarType is InterfaceType &&
      (_core(scalarType, 'int') ||
          _core(scalarType, 'double') ||
          _core(scalarType, 'num'));
  final isString = scalarType is InterfaceType && _core(scalarType, 'String');
  final itemConstraints = <String>[];
  final setConstraints = <String>[];
  var hasUniqueConstraint = false;

  void addScalarConstraint(String constraint) {
    if (isCollection) {
      itemConstraints.add(constraint);
    } else {
      output = '$output$constraint';
    }
  }

  void addCollectionConstraint(String constraint, {bool isUnique = false}) {
    if (isSet) {
      if (isUnique) hasUniqueConstraint = true;
      setConstraints.add(constraint);
    } else {
      output = '$output$constraint';
    }
  }

  for (final metadata in declaration.metadata.annotations) {
    final value = metadata.computeConstantValue();
    final valueType = value?.type;
    if (value == null || valueType is! InterfaceType) continue;
    if (!_rule.isAssignableFromType(valueType)) continue;
    final rule = valueType.element.name!;
    final annotation = '@$rule';
    switch (rule) {
      case 'IsOptional' || 'IsRequired' || 'IsNotNull':
        break;
      case 'HasMin':
        _require(declaration, type, annotation, isNumeric, '@HasMinLength');
        addScalarConstraint(
          '.min(${_number(declaration, annotation, value, 'min')})',
        );
      case 'HasMax':
        _require(declaration, type, annotation, isNumeric, '@HasMaxLength');
        addScalarConstraint(
          '.max(${_number(declaration, annotation, value, 'max')})',
        );
      case 'IsMultipleOf':
        _require(declaration, type, annotation, isNumeric, 'numeric field');
        addScalarConstraint(
          '.multipleOf(${_number(declaration, annotation, value, 'divisor')})',
        );
      case 'IsPositive':
        _require(declaration, type, annotation, isNumeric, 'numeric field');
        addScalarConstraint('.positive()');
      case 'IsNegative':
        _require(declaration, type, annotation, isNumeric, 'numeric field');
        addScalarConstraint('.negative()');
      case 'HasMinLength':
        _require(declaration, type, annotation, isString, '@HasMin');
        addScalarConstraint('.minLength(${_int(value, 'length')})');
      case 'HasMaxLength':
        _require(declaration, type, annotation, isString, '@HasMax');
        addScalarConstraint('.maxLength(${_int(value, 'length')})');
      case 'HasMatch':
        _require(declaration, type, annotation, isString, 'String field');
        addScalarConstraint(
          '.matches(${dartStringLiteral(value.getField('pattern')!.toStringValue()!)})',
        );
      case 'IsNotEmpty':
        _require(
          declaration,
          type,
          annotation,
          isString || isCollection,
          'String, List, or Set field',
        );
        if (isCollection) {
          addCollectionConstraint('.notEmpty()');
        } else {
          output = '$output.notEmpty()';
        }
      case 'IsEmail':
        _require(declaration, type, annotation, isString, 'String field');
        addScalarConstraint('.email()');
      case 'IsUrl':
        _require(declaration, type, annotation, isString, 'String field');
        addScalarConstraint('.url()');
      case 'IsUri':
        _require(declaration, type, annotation, isString, 'String field');
        addScalarConstraint('.uri()');
      case 'IsUuid':
        _require(declaration, type, annotation, isString, 'String field');
        addScalarConstraint('.uuid()');
      case 'IsDate':
        _require(declaration, type, annotation, isString, 'String field');
        addScalarConstraint('.date()');
      case 'IsDateTime':
        _require(declaration, type, annotation, isString, 'String field');
        addScalarConstraint('.datetime()');
      case 'IsIp':
        _require(declaration, type, annotation, isString, 'String field');
        final version = value.getField('version')?.toIntValue();
        if (version != null) {
          if (version != 4 && version != 6) {
            throw InvalidGenerationSource(
              '${_qualifiedName(declaration)} has @IsIp(version: '
              '$version); version must be 4 or 6.',
              element: declaration,
            );
          }
          addScalarConstraint('.ip(version: $version)');
        } else {
          addScalarConstraint('.ip()');
        }
      case 'HasMinItems':
        _require(
          declaration,
          type,
          annotation,
          isCollection,
          'List or Set field',
        );
        addCollectionConstraint('.minItems(${_int(value, 'count')})');
      case 'HasMaxItems':
        _require(
          declaration,
          type,
          annotation,
          isCollection,
          'List or Set field',
        );
        addCollectionConstraint('.maxItems(${_int(value, 'count')})');
      case 'HasUniqueItems':
        _require(
          declaration,
          type,
          annotation,
          isCollection,
          'List or Set field',
        );
        addCollectionConstraint('.unique()', isUnique: true);
      default:
        throw InvalidGenerationSource(
          '${_qualifiedName(declaration)} has unsupported rule $annotation.',
          element: declaration,
        );
    }
  }
  if (itemConstraints.isNotEmpty) {
    output = _appendListItemConstraints(output, itemConstraints.join());
  }
  if (setConstraints.isNotEmpty) {
    if (isSet && !hasUniqueConstraint) {
      // A Set cannot retain duplicate values after decoding. Validate the
      // boundary with the same cardinality before the codec runs.
      setConstraints.add('.unique()');
    }
    output = _beforeOuterCodec(output, setConstraints.join());
  }
  return output;
}