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,
  7. SahihLanguage? lang,
})

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,
  SahihLanguage? lang,
}) async {
  UrlValidationResult result;

  if (url.trim().isEmpty) {
    result = UrlValidationResult(
        isValid: false,
        message: SahihLocalization.get('url_required', lang: lang));
    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: SahihLocalization.get('url_relative_valid', lang: lang),
      );
      return _castResult<T>(result);
    }

    // Scheme check
    if (uri.scheme.isEmpty) {
      result = UrlValidationResult(
        isValid: false,
        message: SahihLocalization.get('url_invalid', lang: lang),
      );
      return _castResult<T>(result);
    }

    if (allowedSchemes != null &&
        !allowedSchemes.contains(uri.scheme.toLowerCase())) {
      result = UrlValidationResult(
        isValid: false,
        message: SahihLocalization.get('url_scheme_not_allowed',
            lang: lang, args: {'scheme': uri.scheme}),
      );
      return _castResult<T>(result);
    }

    if (!uri.hasAuthority || uri.host.isEmpty) {
      result = UrlValidationResult(
        isValid: false,
        message: SahihLocalization.get('url_no_domain', lang: lang),
      );
      return _castResult<T>(result);
    }

    // Domain whitelist check
    if (allowedDomains != null &&
        !allowedDomains.contains(uri.host.toLowerCase())) {
      result = UrlValidationResult(
        isValid: false,
        message: SahihLocalization.get('url_domain_not_allowed',
            lang: lang, args: {'domain': uri.host}),
      );
      return _castResult<T>(result);
    }

    // Optional DNS check (conditional bridge for WASM compatibility)
    if (checkDomainExists && !kIsWeb) {
      try {
        final exists = await dns.checkDomainExists(uri.host);
        if (!exists) {
          result = UrlValidationResult(
            isValid: false,
            message: SahihLocalization.get('url_domain_not_found', lang: lang),
          );
          return _castResult<T>(result);
        }
      } catch (e) {
        result = UrlValidationResult(
          isValid: false,
          message: SahihLocalization.get('url_lookup_failed', lang: lang),
        );
        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: SahihLocalization.get('url_valid', lang: lang),
      data: urlData,
    );
    return _castResult<T>(result);
  } catch (e) {
    result = UrlValidationResult(
      isValid: false,
      message: SahihLocalization.get('url_invalid', lang: lang),
    );
    return _castResult<T>(result);
  }
}