Skip to content

Commit

Permalink
feat: add validate and constraints
Browse files Browse the repository at this point in the history
Adds a validate method and a basic set of constraints.
  • Loading branch information
tirithen committed May 11, 2020
1 parent a16137d commit e5dcd2c
Show file tree
Hide file tree
Showing 7 changed files with 402 additions and 22 deletions.
23 changes: 1 addition & 22 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,24 +1,3 @@
# Compiled Object files
*.o
*.obj

# Compiled Dynamic libraries
*.so
*.dylib
*.dll

# Compiled Static libraries
dutils-validation-test-library
*.a
*.lib

# Executables
*.exe

# DUB
.dub
docs.json
__dummy.html
docs/

# Code coverage
*.lst
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: d

branches:
only:
- master
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,34 @@
# validation

[![DUB Package](https://img.shields.io/dub/v/dutils-validation.svg)](https://code.dlang.org/packages/dutils-validation)
[![Posix Build Status](https://travis-ci.org/d-utils/validation.svg?branch=master)](https://travis-ci.org/d-utils/validation)

Validation annotations for dlang structs

## example

import dutils.validation.constraints : ValidateRequired, ValidateEmail

struct Email {
@ValidateRequired()
@ValidateEmail()
string to;

@ValidateRequired()
@ValidateEmail()
string from;

@ValidateMinimumLength(3)
@ValidateMaximumLength(100)
string subject;

string body;
}

auto email = Email("badto.address", "name@example.com", "no", "some body");

validate(email); // throws an instance of ValidationErrors

## TODO

- [ ] Support for nested structs and arrays
11 changes: 11 additions & 0 deletions dub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "dutils-validation",
"description": "Validation annotations for dlang structs",
"homepage": "https://github.com/d-utils/validation",
"authors": [
"Fredrik Söderström"
],
"copyright": "Copyright © 2020, Fredrik Söderström",
"license": "MIT",
"targetType": "library"
}
96 changes: 96 additions & 0 deletions source/dutils/validation/constraints.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
module dutils.validation.constraints;

import std.conv : to;
import std.typecons : Nullable;

import dutils.validation.validate : ValidationError;

struct ValidateRequired {
Nullable!ValidationError getError(T)(T value, string path) {
if (value) {
return Nullable!ValidationError.init;
}

return Nullable!ValidationError(ValidationError(path,
ValidationErrorTypes.required, "Required to have a value"));
}
}

struct ValidateEmail {
Nullable!ValidationError getError(string value, string path) {
import dutils.validation.email : isValidEmail;

if (isValidEmail(value)) {
return Nullable!ValidationError.init;
}

return Nullable!ValidationError(ValidationError(path,
ValidationErrorTypes.email, "Value must be an email string"));
}
}

struct ValidateMinimum(T) {
T minimum;

Nullable!ValidationError getError(T value, string path) {
if (value.to!T >= this.minimum) {
return Nullable!ValidationError.init;
}

return Nullable!ValidationError(ValidationError(path, ValidationErrorTypes.minimum,
"Value cannot be less than " ~ this.minimum.to!string,
["minimum": this.minimum.to!double]));
}
}

struct ValidateMaximum(T) {
T maximum;

Nullable!ValidationError getError(T value, string path) {
if (value.to!T <= this.maximum) {
return Nullable!ValidationError.init;
}

return Nullable!ValidationError(ValidationError(path, ValidationErrorTypes.maximum,
"Value cannot be greater isBuiltinTypethan " ~ this.maximum.to!string,
["maximum": this.maximum.to!double]));
}
}

struct ValidateMinimumLength {
uint minimumLength;

Nullable!ValidationError getError(string value, string path) {
if (value.length >= this.minimumLength) {
return Nullable!ValidationError.init;
}

return Nullable!ValidationError(ValidationError(path, ValidationErrorTypes.minimumLength,
"Value cannot be shorter than " ~ this.minimumLength.to!string,
["minimumLength": this.minimumLength.to!double]));
}
}

struct ValidateMaximumLength {
uint maximumLength;

Nullable!ValidationError getError(string value, string path) {
if (value.length <= this.maximumLength) {
return Nullable!ValidationError.init;
}

return Nullable!ValidationError(ValidationError(path, ValidationErrorTypes.maximumLength,
"Value cannot be longer than " ~ this.maximumLength.to!string,
["maximumLength": this.maximumLength.to!double]));
}
}

enum ValidationErrorTypes {
type = "type",
required = "required",
email = "email",
minimum = "minimum",
maximum = "maximum",
minimumLength = "minimumLength",
maximumLength = "maximumLength"
}
54 changes: 54 additions & 0 deletions source/dutils/validation/email.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module dutils.validation.email;

import std.regex : ctRegex, replace;

bool isValidEmail(string email) {
import std.typecons : No;
import std.net.isemail : isEmail, EmailStatusCode;

const result = isEmail(extractEmail(email), No.checkDns, EmailStatusCode.none);
return result.statusCode == EmailStatusCode.valid;
}

const extractEmailPattern = ctRegex!r"^(.*?<\s*)?([^@]+@[^>]+).*$";

string extractEmail(string email) {
return replace(email, extractEmailPattern, "$2");
}

/**
* extractEmail - Don't modify string without email
*/
unittest {
assert(extractEmail("this is not an email<noat>") == "this is not an email<noat>");
}

/**
* extractEmail - Don't modify string with only email
*/
unittest {
assert(extractEmail("anna.andersson@example.com") == "anna.andersson@example.com");
}

/**
* extractEmail - Extract email from string
*/
unittest {
assert(extractEmail(
"\"Anna Andersson\" <anna.andersson@example.com>") == "anna.andersson@example.com");
}

/**
* isValidEmail - Valid email should return true
*/
unittest {
assert(isValidEmail("\"Anna Andersson\" <anna.andersson@example.com>"));
}

/**
* isValidEmail - Invalid email should return false
*/
unittest {
assert(!isValidEmail("\"Anna Andersson\" <anna.anderssonexample.com>"));
assert(!isValidEmail("Anna Andersson"));
}

0 comments on commit e5dcd2c

Please sign in to comment.