Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: URL validation classes 'Url' and 'ReqUrl' #159

Merged
merged 1 commit into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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:password@example.com:8080'), null);
expect(url('http://userid:password@example.com: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://userid@example.com: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:password@example.com: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);
});
});
}