isValidUrlAsync<T> function

Future<T> isValidUrlAsync<T>(
  1. String url, {
  2. bool allowRelative = false,
  3. List<String>? allowedSchemes,
  4. List<String>? allowedDomains,
  5. bool normalize = true,
  6. bool checkDomainExists = false,
})

Validates URLs with format checking and domain restrictions. Returns String/bool/UrlValidationResult based on generic type T.

Implementation

Future<T> isValidUrlAsync<T>(
  String url, {
  bool allowRelative = false,
  List<String>? allowedSchemes,
  List<String>? allowedDomains,
  bool normalize = true,
  bool checkDomainExists = false,
}) async {
  UrlValidationResult result;

  if (url.trim().isEmpty) {
    result = UrlValidationResult(isValid: false, message: "URL is required");
    return _castResult<T>(result);
  }

  String trimmedUrl = url.trim();

  try {
    Uri uri = Uri.parse(trimmedUrl);

    // Allow relative paths
    if (allowRelative && !uri.hasScheme && uri.path.isNotEmpty) {
      result = UrlValidationResult(
        isValid: true,
        message: "Relative URL is valid",
      );
      return _castResult<T>(result);
    }

    // Scheme check
    if (uri.scheme.isEmpty) {
      result = UrlValidationResult(
        isValid: false,
        message: "URL must start with http:// or https://",
      );
      return _castResult<T>(result);
    }

    if (allowedSchemes != null &&
        !allowedSchemes.contains(uri.scheme.toLowerCase())) {
      result = UrlValidationResult(
        isValid: false,
        message: "Scheme '${uri.scheme}' is not allowed",
      );
      return _castResult<T>(result);
    }

    if (!uri.hasAuthority || uri.host.isEmpty) {
      result = UrlValidationResult(
        isValid: false,
        message: "URL must have a valid domain",
      );
      return _castResult<T>(result);
    }

    // Domain whitelist check
    if (allowedDomains != null &&
        !allowedDomains.contains(uri.host.toLowerCase())) {
      result = UrlValidationResult(
        isValid: false,
        message: "Domain '${uri.host}' is not allowed",
      );
      return _castResult<T>(result);
    }

    // Optional DNS check (not supported on web platforms)
    if (checkDomainExists && !kIsWeb) {
      try {
        final lookup = await InternetAddress.lookup(uri.host);
        if (lookup.isEmpty || lookup.first.address.isEmpty) {
          result = UrlValidationResult(
            isValid: false,
            message: "Domain does not exist",
          );
          return _castResult<T>(result);
        }
      } catch (e) {
        result = UrlValidationResult(
          isValid: false,
          message: "Domain lookup failed",
        );
        return _castResult<T>(result);
      }
    }

    UrlData urlData = UrlData(
      protocol: uri.scheme.toLowerCase(),
      host: uri.host.toLowerCase(),
      domain: _extractDomain(uri.host),
      port: uri.hasPort ? uri.port : null,
      path: uri.path.isEmpty ? "/" : uri.path,
      query: uri.query.isEmpty ? null : uri.query,
      fragment: uri.fragment.isEmpty ? null : uri.fragment,
      fullUrl: normalize ? _normalizeUrl(uri) : trimmedUrl,
    );

    result = UrlValidationResult(
      isValid: true,
      message: "URL is valid",
      data: urlData,
    );
    return _castResult<T>(result);
  } catch (e) {
    result = UrlValidationResult(
      isValid: false,
      message: "URL format is invalid",
    );
    return _castResult<T>(result);
  }
}