Skip to content

Commit 2aaa8c0

Browse files
committed
feat: URL validation classes 'Url' and 'ReqUrl'
Closes #96
1 parent c2280cb commit 2aaa8c0

File tree

9 files changed

+188
-19
lines changed

9 files changed

+188
-19
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Dart Package Versioning](https://dart.dev/tools/pub
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Url and ReqUrl classes to validate both optional and mandatory Url form fields
13+
[96](https://github.com/dartoos-dev/formdator/issues/96).
14+
1015
## [1.0.0] - 2022-01-09
1116

1217
### Added

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ alt="EO-Color logo" width="101" height="48"/>
2929

3030
## Overview
3131

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

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

141-
Make sure the commands below **passes** before making a Pull Request.
142+
Make sure the command below **passes** before making a Pull Request.
142143

143144
```shell
144145
dart analyze && sudo dart test

analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ analyzer:
99
implicit-dynamic: false
1010
linter:
1111
rules:
12+
always_use_package_imports: false
1213
# Make constructors the first thing in every class
1314
sort_constructors_first: true
1415
# Good packages document everything

example/pubspec.lock

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ packages:
4242
name: collection
4343
url: "https://pub.dartlang.org"
4444
source: hosted
45-
version: "1.15.0"
45+
version: "1.16.0"
4646
fake_async:
4747
dependency: transitive
4848
description:
4949
name: fake_async
5050
url: "https://pub.dartlang.org"
5151
source: hosted
52-
version: "1.2.0"
52+
version: "1.3.0"
5353
flutter:
5454
dependency: "direct main"
5555
description: flutter
@@ -66,7 +66,7 @@ packages:
6666
path: ".."
6767
relative: true
6868
source: path
69-
version: "0.12.3"
69+
version: "1.0.0"
7070
lint:
7171
dependency: "direct dev"
7272
description:
@@ -81,6 +81,13 @@ packages:
8181
url: "https://pub.dartlang.org"
8282
source: hosted
8383
version: "0.12.11"
84+
material_color_utilities:
85+
dependency: transitive
86+
description:
87+
name: material_color_utilities
88+
url: "https://pub.dartlang.org"
89+
source: hosted
90+
version: "0.1.4"
8491
meta:
8592
dependency: transitive
8693
description:
@@ -94,7 +101,7 @@ packages:
94101
name: path
95102
url: "https://pub.dartlang.org"
96103
source: hosted
97-
version: "1.8.0"
104+
version: "1.8.1"
98105
sky_engine:
99106
dependency: transitive
100107
description: flutter
@@ -106,7 +113,7 @@ packages:
106113
name: source_span
107114
url: "https://pub.dartlang.org"
108115
source: hosted
109-
version: "1.8.1"
116+
version: "1.8.2"
110117
stack_trace:
111118
dependency: transitive
112119
description:
@@ -141,20 +148,13 @@ packages:
141148
name: test_api
142149
url: "https://pub.dartlang.org"
143150
source: hosted
144-
version: "0.4.3"
145-
typed_data:
146-
dependency: transitive
147-
description:
148-
name: typed_data
149-
url: "https://pub.dartlang.org"
150-
source: hosted
151-
version: "1.3.0"
151+
version: "0.4.9"
152152
vector_math:
153153
dependency: transitive
154154
description:
155155
name: vector_math
156156
url: "https://pub.dartlang.org"
157157
source: hosted
158-
version: "2.1.1"
158+
version: "2.1.2"
159159
sdks:
160-
dart: ">=2.15.0-7.0.dev <3.0.0"
160+
dart: ">=2.17.0-0 <3.0.0"

lib/net.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ export 'src/net/req_email.dart';
1111
export 'src/net/req_ipv4.dart';
1212
export 'src/net/req_ipv6.dart';
1313
export 'src/net/req_mac_addr.dart';
14+
export 'src/net/req_url.dart';
15+
export 'src/net/url.dart';

lib/src/net/req_url.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import '../../core.dart';
2+
import '../../type.dart';
3+
import 'url.dart';
4+
5+
/// Convenience validator for required URL values.
6+
class ReqUrl {
7+
/// Non-blank and well-formed URL values.
8+
ReqUrl({String blank = 'required URL', String mal = 'malformed URL'})
9+
: _reqUrl = Pair.str2(Req(blank: blank), Url(mal: mal));
10+
11+
final ValObj _reqUrl;
12+
13+
/// Returns null if the value is a valid URL; otherwise it will return the
14+
/// error message for blank fields or the error message for malformed fields.
15+
String? call(String? value) => _reqUrl(value);
16+
}

lib/src/net/url.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import 'package:formdator/formdator.dart';
2+
3+
/// URL Validator.
4+
///
5+
/// Blank field - null value - is a valid input!
6+
///
7+
/// If the URL is required, see [ReqUrl] or [Req].
8+
///
9+
/// Notes on possible differences from a standard/generic validation:
10+
///
11+
/// - utf-8 char class take in consideration the full Unicode range
12+
/// - Top-level domains (TLDs) have been made mandatory so single names like
13+
/// "localhost" fails
14+
/// - protocols have been restricted to ftp, http and https
15+
/// - IP address dotted notation validation, range: 1.0.0.0 - 223.255.255.255
16+
/// first and last IP address of each class is considered invalid (since they
17+
/// are broadcast/network addresses)
18+
///
19+
/// - Made starting path slash optional (http://example.com?foo=bar)
20+
/// - Allow a dot (.) at the end of hostnames (http://example.com.)
21+
/// - Allow an underscore (_) character in host/domain_names
22+
/// - Check dot delimited parts length and total length
23+
/// - Made protocol optional, allowed short syntax //
24+
class Url {
25+
/// Validates URL values using a regular expression.
26+
///
27+
/// If the URL field is mandatory, see [ReqUrl] or [Req].
28+
const Url({this.mal = 'malformed URL'});
29+
30+
/// The error message in case of a malformed URL value.
31+
final String mal;
32+
33+
/// A suitable pattern for URL values.
34+
///
35+
/// Reference: [gist](https://gist.github.com/dperini/729294)
36+
static final RegExp urlPattern = RegExp(
37+
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*)?$',
38+
caseSensitive: false,
39+
unicode: true,
40+
);
41+
42+
/// Returns `null` if [value] is `null` or a valid URL; returns [mal]
43+
/// otherwise.
44+
String? call(String? value) {
45+
return value == null || urlPattern.hasMatch(value) ? null : mal;
46+
}
47+
}

test/net/req_url_test.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import 'package:formdator/src/net/req_url.dart';
2+
import 'package:test/test.dart';
3+
4+
void main() {
5+
group('ReqUrl', () {
6+
const blank = 'required URL value';
7+
const mal = 'malformed URL value';
8+
final reqUrl = ReqUrl(blank: blank, mal: mal);
9+
test('null - blank', () {
10+
expect(reqUrl(null), blank);
11+
});
12+
test('empty - blank', () {
13+
expect(reqUrl(''), blank);
14+
});
15+
test('valid URL', () {
16+
expect(reqUrl('http://10.0.1.1'), null);
17+
});
18+
test('invalid URL', () {
19+
expect(reqUrl('http://10.10.10.256'), mal);
20+
});
21+
});
22+
}

test/net/url_test.dart

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import 'package:formdator/src/net/url.dart';
2+
import 'package:test/test.dart';
3+
4+
void main() {
5+
group('URL', () {
6+
const error = 'malformed URL value';
7+
const url = Url(mal: error);
8+
test('Valid Urls:', () {
9+
expect(url(null), null);
10+
expect(url('//shortsyntax.com'), null);
11+
expect(url('//www.shortsyntax.com'), null);
12+
expect(url('http://www.example.com'), null);
13+
expect(url('http://www.example.com.'), null);
14+
expect(url('http://www.example.com/index.html'), null);
15+
expect(url('http://example.com?foo=bar'), null);
16+
expect(url('https://www.example.com/foo/?bar=baz&inga=42&quux'), null);
17+
expect(url('http://odf.ws/123'), null);
18+
expect(url('http://userid:password@example.com:8080'), null);
19+
expect(url('http://userid:password@example.com:8080/'), null);
20+
expect(url('https://www.youtube.com/watch?v=nmhX3_m84Is'), null);
21+
expect(url('http://foo.com/blah_blah#cite-1'), null);
22+
expect(url('http://userid@example.com:8080/'), null);
23+
expect(url('http://foo.com/blah_(wikipedia)#cite-1'), null);
24+
expect(url('http://foo.com/blah_(wikipedia)_blah#cite-1'), null);
25+
expect(url('http://code.google.com/events/#&product=browser'), null);
26+
expect(url('http://foo.bar/?q=Test%20URL-encoded%20stuff'), null);
27+
expect(url("http://-.~_!&'()*+,;=:%40:80%2f::::::@example.com"), null);
28+
expect(url('https://foo_bar.example.com/'), null);
29+
expect(url('http://1337.net'), null);
30+
expect(url('http://192.168.0.3'), null);
31+
expect(url('http://192.168.0.3/resource'), null);
32+
expect(url('http://223.255.255.254'), null);
33+
});
34+
test('Valid FTP Urls:', () {
35+
expect(url('ftp://example.com'), null);
36+
expect(url('ftp://example.com:8080'), null);
37+
expect(url('ftp://example.com:8080/'), null);
38+
expect(url('ftp://example.com:8080/url-path'), null);
39+
expect(url('ftp://userid:password@example.com:8080/url-path'), null);
40+
});
41+
test('invalid URLs', () {
42+
expect(url(''), error);
43+
expect(url('//'), error);
44+
expect(url('://'), error);
45+
expect(url('//a'), error);
46+
expect(url('http://'), error);
47+
expect(url('http:///a'), error);
48+
expect(url('foo.com'), error);
49+
expect(url('rdar://1234'), error);
50+
expect(url('http://.'), error);
51+
expect(url('http://..'), error);
52+
expect(url('http://?'), error);
53+
expect(url('http://??'), error);
54+
expect(url('http://#'), error);
55+
expect(url('http://##'), error);
56+
expect(url('1'), error);
57+
expect(url('10.10'), error);
58+
expect(url('0.0.0.0'), error);
59+
expect(url('http://10.1.1.255'), error);
60+
expect(url('http://224.1.1.1'), error);
61+
expect(url('http://3628126748'), error);
62+
// the first IP andress (network) of each class is considered invalid.
63+
expect(url('http://192.168.0.0'), error);
64+
expect(url('http://192.168.0.0/resource'), error);
65+
// the last IP andress (broadcast) of each class is considered invalid.
66+
expect(url('http://192.168.0.255/'), error);
67+
expect(url('http://192.168.0.255/resource'), error);
68+
69+
expect(url('http://?df.ws/123'), error);
70+
expect(url('http:// shouldfail.com'), error);
71+
expect(url('http://.www.foo.bar/'), error);
72+
expect(url('http://foo.bar?q=Spaces should be encoded'), error);
73+
});
74+
});
75+
}

0 commit comments

Comments
 (0)