Skip to content

Commit

Permalink
feat: URL validation classes 'Url' and 'ReqUrl'
Browse files Browse the repository at this point in the history
Closes #96
  • Loading branch information
rafamizes committed Jun 20, 2022
1 parent c2280cb commit 2aaa8c0
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 19 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Dart Package Versioning](https://dart.dev/tools/pub

## [Unreleased]

### Added

- Url and ReqUrl classes to validate both optional and mandatory Url form fields
[96](https://github.com/dartoos-dev/formdator/issues/96).

## [1.0.0] - 2022-01-09

### Added
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ alt="EO-Color logo" width="101" height="48"/>

## Overview

**Form** Vali**dator** — Formdator is a fully object-oriented package for
validating Flutter form fields. Its main benefits, compared to all other similar
packages, include:
**Formdator****Form**idable Vali**dator**.

Formdator is a fully object-oriented package for validating Flutter form fields.
Its main benefits, compared to all other similar packages, include:

- **Dependency-free**: there is only pure Dart code.
- **Object-oriented mindset**: the elements for validation are **immutable
Expand Down Expand Up @@ -138,7 +139,7 @@ Contributors are welcome!
branch and make a _Pull Request_.
3. After review and acceptance, the _Pull Request_ is merged and closed.

Make sure the commands below **passes** before making a Pull Request.
Make sure the command below **passes** before making a Pull Request.

```shell
dart analyze && sudo dart test
Expand Down
1 change: 1 addition & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ analyzer:
implicit-dynamic: false
linter:
rules:
always_use_package_imports: false
# Make constructors the first thing in every class
sort_constructors_first: true
# Good packages document everything
Expand Down
30 changes: 15 additions & 15 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ packages:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
version: "1.16.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.0"
flutter:
dependency: "direct main"
description: flutter
Expand All @@ -66,7 +66,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.12.3"
version: "1.0.0"
lint:
dependency: "direct dev"
description:
Expand All @@ -81,6 +81,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
meta:
dependency: transitive
description:
Expand All @@ -94,7 +101,7 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.8.1"
sky_engine:
dependency: transitive
description: flutter
Expand All @@ -106,7 +113,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.8.2"
stack_trace:
dependency: transitive
description:
Expand Down Expand Up @@ -141,20 +148,13 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.3"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "0.4.9"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
version: "2.1.2"
sdks:
dart: ">=2.15.0-7.0.dev <3.0.0"
dart: ">=2.17.0-0 <3.0.0"
2 changes: 2 additions & 0 deletions lib/net.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ export 'src/net/req_email.dart';
export 'src/net/req_ipv4.dart';
export 'src/net/req_ipv6.dart';
export 'src/net/req_mac_addr.dart';
export 'src/net/req_url.dart';
export 'src/net/url.dart';
16 changes: 16 additions & 0 deletions lib/src/net/req_url.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import '../../core.dart';
import '../../type.dart';
import 'url.dart';

/// Convenience validator for required URL values.
class ReqUrl {
/// Non-blank and well-formed URL values.
ReqUrl({String blank = 'required URL', String mal = 'malformed URL'})
: _reqUrl = Pair.str2(Req(blank: blank), Url(mal: mal));

final ValObj _reqUrl;

/// Returns null if the value is a valid URL; otherwise it will return the
/// error message for blank fields or the error message for malformed fields.
String? call(String? value) => _reqUrl(value);
}
47 changes: 47 additions & 0 deletions lib/src/net/url.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:formdator/formdator.dart';

/// URL Validator.
///
/// Blank field - null value - is a valid input!
///
/// If the URL is required, see [ReqUrl] or [Req].
///
/// Notes on possible differences from a standard/generic validation:
///
/// - utf-8 char class take in consideration the full Unicode range
/// - Top-level domains (TLDs) have been made mandatory so single names like
/// "localhost" fails
/// - protocols have been restricted to ftp, http and https
/// - IP address dotted notation validation, range: 1.0.0.0 - 223.255.255.255
/// first and last IP address of each class is considered invalid (since they
/// are broadcast/network addresses)
///
/// - Made starting path slash optional (http://example.com?foo=bar)
/// - Allow a dot (.) at the end of hostnames (http://example.com.)
/// - Allow an underscore (_) character in host/domain_names
/// - Check dot delimited parts length and total length
/// - Made protocol optional, allowed short syntax //
class Url {
/// Validates URL values using a regular expression.
///
/// If the URL field is mandatory, see [ReqUrl] or [Req].
const Url({this.mal = 'malformed URL'});

/// The error message in case of a malformed URL value.
final String mal;

/// A suitable pattern for URL values.
///
/// Reference: [gist](https://gist.github.com/dperini/729294)
static final RegExp urlPattern = RegExp(
r'^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$',
caseSensitive: false,
unicode: true,
);

/// Returns `null` if [value] is `null` or a valid URL; returns [mal]
/// otherwise.
String? call(String? value) {
return value == null || urlPattern.hasMatch(value) ? null : mal;
}
}
22 changes: 22 additions & 0 deletions test/net/req_url_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:formdator/src/net/req_url.dart';
import 'package:test/test.dart';

void main() {
group('ReqUrl', () {
const blank = 'required URL value';
const mal = 'malformed URL value';
final reqUrl = ReqUrl(blank: blank, mal: mal);
test('null - blank', () {
expect(reqUrl(null), blank);
});
test('empty - blank', () {
expect(reqUrl(''), blank);
});
test('valid URL', () {
expect(reqUrl('http://10.0.1.1'), null);
});
test('invalid URL', () {
expect(reqUrl('http://10.10.10.256'), mal);
});
});
}
75 changes: 75 additions & 0 deletions test/net/url_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import 'package:formdator/src/net/url.dart';
import 'package:test/test.dart';

void main() {
group('URL', () {
const error = 'malformed URL value';
const url = Url(mal: error);
test('Valid Urls:', () {
expect(url(null), null);
expect(url('//shortsyntax.com'), null);
expect(url('//www.shortsyntax.com'), null);
expect(url('http://www.example.com'), null);
expect(url('http://www.example.com.'), null);
expect(url('http://www.example.com/index.html'), null);
expect(url('http://example.com?foo=bar'), null);
expect(url('https://www.example.com/foo/?bar=baz&inga=42&quux'), null);
expect(url('http://odf.ws/123'), null);
expect(url('http://userid:[email protected]:8080'), null);
expect(url('http://userid:[email protected]:8080/'), null);
expect(url('https://www.youtube.com/watch?v=nmhX3_m84Is'), null);
expect(url('http://foo.com/blah_blah#cite-1'), null);
expect(url('http://[email protected]:8080/'), null);
expect(url('http://foo.com/blah_(wikipedia)#cite-1'), null);
expect(url('http://foo.com/blah_(wikipedia)_blah#cite-1'), null);
expect(url('http://code.google.com/events/#&product=browser'), null);
expect(url('http://foo.bar/?q=Test%20URL-encoded%20stuff'), null);
expect(url("http://-.~_!&'()*+,;=:%40:80%2f::::::@example.com"), null);
expect(url('https://foo_bar.example.com/'), null);
expect(url('http://1337.net'), null);
expect(url('http://192.168.0.3'), null);
expect(url('http://192.168.0.3/resource'), null);
expect(url('http://223.255.255.254'), null);
});
test('Valid FTP Urls:', () {
expect(url('ftp://example.com'), null);
expect(url('ftp://example.com:8080'), null);
expect(url('ftp://example.com:8080/'), null);
expect(url('ftp://example.com:8080/url-path'), null);
expect(url('ftp://userid:[email protected]:8080/url-path'), null);
});
test('invalid URLs', () {
expect(url(''), error);
expect(url('//'), error);
expect(url('://'), error);
expect(url('//a'), error);
expect(url('http://'), error);
expect(url('http:///a'), error);
expect(url('foo.com'), error);
expect(url('rdar://1234'), error);
expect(url('http://.'), error);
expect(url('http://..'), error);
expect(url('http://?'), error);
expect(url('http://??'), error);
expect(url('http://#'), error);
expect(url('http://##'), error);
expect(url('1'), error);
expect(url('10.10'), error);
expect(url('0.0.0.0'), error);
expect(url('http://10.1.1.255'), error);
expect(url('http://224.1.1.1'), error);
expect(url('http://3628126748'), error);
// the first IP andress (network) of each class is considered invalid.
expect(url('http://192.168.0.0'), error);
expect(url('http://192.168.0.0/resource'), error);
// the last IP andress (broadcast) of each class is considered invalid.
expect(url('http://192.168.0.255/'), error);
expect(url('http://192.168.0.255/resource'), error);

expect(url('http://?df.ws/123'), error);
expect(url('http:// shouldfail.com'), error);
expect(url('http://.www.foo.bar/'), error);
expect(url('http://foo.bar?q=Spaces should be encoded'), error);
});
});
}

0 comments on commit 2aaa8c0

Please sign in to comment.