validate method

Validates record and returns a MetadataValidationResult.

Never throws; failures are expressed as MetadataValidationResult.invalid.

Implementation

MetadataValidationResult validate(RemoteTemplateRecord record) {
  // ID
  if (!_idPattern.hasMatch(record.id)) {
    return MetadataValidationResult.invalid(
        'Invalid template ID "${record.id}". '
        'Must match ^[a-z][a-z0-9_]{0,63}\$.');
  }

  // Version
  try {
    TemplateSemVer.parse(record.version);
  } catch (_) {
    return MetadataValidationResult.invalid(
        'Invalid semantic version "${record.version}" for template "${record.id}".');
  }

  // displayName
  if (record.displayName.isEmpty) {
    return MetadataValidationResult.invalid(
        'Template "${record.id}" has an empty displayName.');
  }
  if (record.displayName.length > 128) {
    return MetadataValidationResult.invalid(
        'Template "${record.id}" displayName exceeds 128 characters.');
  }

  // description
  if (record.description.length > 512) {
    return MetadataValidationResult.invalid(
        'Template "${record.id}" description exceeds 512 characters.');
  }

  // projectType
  if (!allowedProjectTypes.contains(record.projectType)) {
    return MetadataValidationResult.invalid(
        'Template "${record.id}" has unsupported projectType '
        '"${record.projectType}". Allowed: $allowedProjectTypes.');
  }

  // category — remote cannot claim builtin
  if (!allowedRemoteCategories.contains(record.category)) {
    return MetadataValidationResult.invalid(
        'Template "${record.id}" has invalid or disallowed category '
        '"${record.category}". Remote registries may only use: '
        '$allowedRemoteCategories.');
  }

  // maturity
  if (record.maturity != null &&
      !allowedMaturityValues.contains(record.maturity)) {
    return MetadataValidationResult.invalid(
        'Template "${record.id}" has invalid maturity "${record.maturity}". '
        'Allowed: $allowedMaturityValues.');
  }

  // tags
  for (final tag in record.tags) {
    if (!_tagPattern.hasMatch(tag)) {
      return MetadataValidationResult.invalid(
          'Template "${record.id}" has invalid tag "$tag". '
          'Tags must match ^[a-z][a-z0-9_-]{0,63}\$.');
    }
  }

  // capabilities
  for (final cap in record.capabilities) {
    if (!_tagPattern.hasMatch(cap)) {
      return MetadataValidationResult.invalid(
          'Template "${record.id}" has invalid capability "$cap".');
    }
  }

  // dependencies
  for (final dep in record.dependencies) {
    if (!_idPattern.hasMatch(dep)) {
      return MetadataValidationResult.invalid(
          'Template "${record.id}" has invalid dependency ID "$dep". '
          'Must match ^[a-z][a-z0-9_]{0,63}\$.');
    }
  }

  // documentationUrl — must be HTTPS if present
  if (record.documentationUrl != null &&
      record.documentationUrl!.isNotEmpty) {
    try {
      final uri = Uri.parse(record.documentationUrl!);
      if (uri.scheme != 'https') {
        return MetadataValidationResult.invalid(
            'Template "${record.id}" documentationUrl must use HTTPS '
            '(got "${uri.scheme}").');
      }
    } catch (_) {
      return MetadataValidationResult.invalid(
          'Template "${record.id}" has a malformed documentationUrl.');
    }
  }

  // downloadCount
  if (record.downloadCount < 0) {
    return MetadataValidationResult.invalid(
        'Template "${record.id}" has negative downloadCount.');
  }

  // rating
  if (record.rating < 0.0 || record.rating > 5.0) {
    return MetadataValidationResult.invalid(
        'Template "${record.id}" rating must be between 0.0 and 5.0 '
        '(got ${record.rating}).');
  }

  // minimumDartSdk — must look like a constraint
  if (record.minimumDartSdk.isEmpty) {
    return MetadataValidationResult.invalid(
        'Template "${record.id}" has an empty minimumDartSdk.');
  }
  if (!_sdkConstraintPattern.hasMatch(record.minimumDartSdk)) {
    return MetadataValidationResult.invalid(
        'Template "${record.id}" minimumDartSdk '
        '"${record.minimumDartSdk}" does not look like a valid SDK constraint.');
  }

  return MetadataValidationResult.valid(record);
}