Skip to content

Commit 49df9e9

Browse files
authored
Merge pull request #9 from denehoffman/rust-crate
feat: expose Rust API
2 parents f014235 + f31b7ee commit 49df9e9

File tree

8 files changed

+371
-37
lines changed

8 files changed

+371
-37
lines changed

.github/workflows/release-plz.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Release-plz
2+
3+
permissions:
4+
pull-requests: write
5+
contents: write
6+
7+
on:
8+
push:
9+
branches:
10+
- main
11+
12+
jobs:
13+
14+
# Release unpublished packages.
15+
release-plz-release:
16+
name: Release-plz release
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout repository
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
- name: Install Rust toolchain
24+
uses: dtolnay/rust-toolchain@stable
25+
- name: Run release-plz
26+
uses: release-plz/[email protected]
27+
with:
28+
command: release
29+
env:
30+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31+
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
32+
33+
# Create a PR with the new versions and changelog, preparing the next release.
34+
release-plz-pr:
35+
name: Release-plz PR
36+
runs-on: ubuntu-latest
37+
concurrency:
38+
group: release-plz-${{ github.ref }}
39+
cancel-in-progress: false
40+
steps:
41+
- name: Checkout repository
42+
uses: actions/checkout@v4
43+
with:
44+
fetch-depth: 0
45+
- name: Install Rust toolchain
46+
uses: dtolnay/rust-toolchain@stable
47+
- name: Run release-plz
48+
uses: release-plz/[email protected]
49+
with:
50+
command: release-pr
51+
env:
52+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53+
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@ name = "emval"
33
version = "0.1.4"
44
description = "emval is a blazingly fast email validator"
55
edition = "2021"
6+
license-file = "LICENSE"
7+
homepage = "https://github.com/bnkc/emval"
8+
repository = "https://github.com/bnkc/emval"
9+
readme = "README.md"
10+
keywords = ["email", "validation"]
11+
categories = ["email"]
612

713
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
814
[lib]
9-
name = "_emval"
10-
crate-type = ["cdylib"]
15+
name = "emval"
16+
crate-type = ["cdylib", "rlib"]
1117

1218
[dependencies]
1319
pyo3 = "0.23.0"

README.md

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 📬 emval
22

3-
`emval` is a blazingly fast Python email validator written in Rust, offering performance improvements of 100-1000x over traditional validators.
3+
`emval` is a blazingly fast email validator written in Rust with Python bindings, offering performance improvements of 100-1000x over traditional validators.
44

55
![performance image](https://raw.githubusercontent.com/bnkc/emval/b90cc4a0ae24e329702872c4fb1cccf212d556a6/perf.svg)
66

@@ -23,11 +23,16 @@ Install `emval` from PyPI:
2323
pip install emval
2424
```
2525

26+
or use `emval` in a Rust project:
27+
```sh
28+
cargo add emval
29+
```
30+
2631
## Usage
2732

2833
### Quick Start
2934

30-
To validate an email address:
35+
To validate an email address in Python:
3136

3237
```python
3338
from emval import validate_email, EmailValidator
@@ -44,6 +49,18 @@ except Exception as e:
4449
print(str(e))
4550
```
4651

52+
The same code in Rust:
53+
```rust
54+
use emval::{validate_email, ValidationError};
55+
56+
fn main() -> Result<(), ValidationError> {
57+
let email = "[email protected]";
58+
let val_email = validate_email(email)?;
59+
let normalized_email = val_email.normalized;
60+
Ok(())
61+
}
62+
```
63+
4764
### Configurations
4865

4966
Customize email validation behavior using the `EmailValidator` class:
@@ -68,6 +85,25 @@ except Exception as e:
6885
print(str(e))
6986
```
7087

88+
The same code in Rust:
89+
```rust
90+
use emval::{EmailValidator, ValidationError};
91+
92+
fn main() -> Result<(), ValidationError> {
93+
let emval = EmailValidator {
94+
allow_smtputf8: false,
95+
allow_empty_local: true,
96+
allow_quoted_local: true,
97+
allow_domain_literal: true,
98+
deliverable_address: false,
99+
};
100+
101+
let email = "[email protected]";
102+
let validated_email = emval.validate_email(email)?;
103+
Ok(())
104+
}
105+
```
106+
71107
### Options
72108

73109
- `allow_smtputf8`: Allows internationalized email addresses.

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ classifiers = [
2323
]
2424
dynamic = ["version"]
2525

26+
[project.optional-dependencies]
27+
tests = ["pytest"]
28+
2629
[tool.maturin]
2730
module-name = "emval._emval"
2831
features = ["pyo3/extension-module"]

src/errors.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use pyo3::exceptions::{PySyntaxError, PyValueError};
22
use pyo3::prelude::*;
33

4+
/// An error enum for email validation.
45
#[derive(Debug)]
56
pub enum ValidationError {
7+
/// A syntax error.
68
SyntaxError(String),
9+
/// An error involving some input value.
710
ValueError(String),
811
}
912

src/lib.rs

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,174 @@
1+
//! # 📬 emval
2+
//!
3+
//! `emval` is a blazingly fast email validator written in Rust with Python bindings, offering performance improvements of 100-1000x over traditional validators.
4+
//!
5+
//! ![performance image](https://raw.githubusercontent.com/bnkc/emval/b90cc4a0ae24e329702872c4fb1cccf212d556a6/perf.svg)
6+
7+
//! ## Features
8+
9+
//! - Drop-in replacement for popular email validators like `python-email-validator`, `verify-email`, and `pyIsEmail`.
10+
//! - 100-1000x faster than [python-email-validator](https://github.com/JoshData/python-email-validator).
11+
//! - Validates email address syntax according to [RFC 5322](https://www.rfc-editor.org/rfc/rfc5322.html) and [RFC 6531](https://www.rfc-editor.org/rfc/rfc6531.html).
12+
//! - Checks domain deliverability (coming soon).
13+
//! - Supports internationalized domain names (IDN) and local parts.
14+
//! - Provides user-friendly syntax errors.
15+
//! - Normalizes addresses.
16+
//! - Rejects invalid and unsafe Unicode characters.
17+
//!
18+
//! ## Getting Started
19+
//!
20+
//! Install `emval` from PyPI:
21+
//!
22+
//! ```sh
23+
//! pip install emval
24+
//! ```
25+
//!
26+
//! or use `emval` in a Rust project:
27+
//! ```sh
28+
//! cargo add emval
29+
//! ```
30+
//!
31+
//! ## Usage
32+
//!
33+
//! ### Quick Start
34+
//!
35+
//! To validate an email address in Python:
36+
//!
37+
//! ```python
38+
//! from emval import validate_email, EmailValidator
39+
//!
40+
//! email = "[email protected]"
41+
//!
42+
//! try:
43+
//! # Check if the email is valid.
44+
//! val_email = validate_email(email)
45+
//! # Utilize the normalized form for storage.
46+
//! normalized_email = val_email.normalized
47+
//! except Exception as e:
48+
//! # Example: "Invalid Local Part: Quoting the local part before the '@' sign is not permitted in this context."
49+
//! print(str(e))
50+
//! ```
51+
//!
52+
//! The same code in Rust:
53+
//! ```rust
54+
//! use emval::{validate_email, ValidationError};
55+
//!
56+
//! fn main() -> Result<(), ValidationError> {
57+
//! let email = "[email protected]";
58+
//! let val_email = validate_email(email)?;
59+
//! let normalized_email = val_email.normalized;
60+
//! Ok(())
61+
//! }
62+
//! ```
63+
//!
64+
//! ### Configurations
65+
//!
66+
//! Customize email validation behavior using the `EmailValidator` class:
67+
//!
68+
//! ```python
69+
//! from emval import EmailValidator
70+
//!
71+
//! emval = EmailValidator(
72+
//! allow_smtputf8=False,
73+
//! allow_empty_local=True,
74+
//! allow_quoted_local=True,
75+
//! allow_domain_literal=True,
76+
//! deliverable_address=False,
77+
//! )
78+
//!
79+
//! email = "user@[192.168.1.1]"
80+
//!
81+
//! try:
82+
//! validated_email = emval.validate_email(email)
83+
//! print(validated_email)
84+
//! except Exception as e:
85+
//! print(str(e))
86+
//! ```
87+
//!
88+
//! The same code in Rust:
89+
//! ```rust
90+
//! use emval::{EmailValidator, ValidationError};
91+
//!
92+
//! fn main() -> Result<(), ValidationError> {
93+
//! let emval = EmailValidator {
94+
//! allow_smtputf8: false,
95+
//! allow_empty_local: true,
96+
//! allow_quoted_local: true,
97+
//! allow_domain_literal: true,
98+
//! deliverable_address: false,
99+
//! };
100+
//!
101+
//! let email = "[email protected]";
102+
//! let validated_email = emval.validate_email(email)?;
103+
//! Ok(())
104+
//! }
105+
//! ```
106+
//!
107+
//! ### Options
108+
//!
109+
//! - `allow_smtputf8`: Allows internationalized email addresses.
110+
//! - `allow_empty_local`: Allows an empty local part (e.g., `@domain.com`).
111+
//! - `allow_quoted_local`: Allows quoted local parts (e.g., `"user name"@domain.com`).
112+
//! - `allow_domain_literal`: Allows domain literals (e.g., `[192.168.0.1]`).
113+
//! - `deliverable_address`: Checks if the email address is deliverable by verifying the domain's MX records.
114+
//!
115+
//! ## Technical Details
116+
//!
117+
//! ### Email Address Syntax
118+
//!
119+
//! emval adheres to the syntax rules defined in [RFC 5322](https://www.rfc-editor.org/rfc/rfc5322.html) and [RFC 6531](https://www.rfc-editor.org/rfc/rfc6531.html). It supports both ASCII and internationalized characters.
120+
//!
121+
//! ### Internationalized Email Addresses
122+
//!
123+
//! #### Domain Names
124+
//!
125+
//! emval converts non-ASCII domain names into their ASCII "Punycode" form according to [IDNA 2008](https://www.rfc-editor.org/rfc/rfc5891.html). This ensures compatibility with systems that do not support Unicode.
126+
//!
127+
//! #### Local Parts
128+
//!
129+
//! emval allows international characters in the local part of email addresses, following [RFC 6531](https://www.rfc-editor.org/rfc/rfc6531.html). It offers options to handle environments without SMTPUTF8 support.
130+
//!
131+
//! ### Unsafe Unicode Characters
132+
//!
133+
//! emval rejects unsafe Unicode characters to enhance security, preventing display and interpretation issues.
134+
//!
135+
//! ### Normalization
136+
//!
137+
//! emval normalizes email addresses to ensure consistency:
138+
//!
139+
//! - **Lowercasing domains:** Domain names are standardized to lowercase.
140+
//! - **Unicode NFC normalization:** Characters are transformed into their precomposed forms.
141+
//! - **Removing unnecessary characters:** Quotes and backslashes in the local part are removed.
142+
//!
143+
//! ## Acknowledgements
144+
//!
145+
//! This project draws inspiration from [python-email-validator](https://github.com/JoshData/python-email-validator). While `python-email-validator` is more comprehensive, `emval` aims to provide a faster solution.
146+
//!
147+
//! ## Getting Help
148+
//!
149+
//! For questions and issues, please open an issue in the [GitHub issue tracker](https://github.com/bnkc/emval/issues).
150+
//!
151+
//! ## License
152+
//!
153+
//! emval is licensed under the [MIT License](https://opensource.org/licenses/MIT). See the [LICENSE](https://github.com/bnkc/emval/blob/main/LICENSE) file for more details.
154+
1155
#![feature(ip)]
2156
#[macro_use]
3157
extern crate lazy_static;
4158
mod consts;
5-
mod errors;
159+
pub mod errors;
6160
mod models;
7161
mod validators;
8162

163+
pub use crate::errors::ValidationError;
164+
pub use crate::models::{EmailValidator, ValidatedEmail};
165+
166+
/// Validate an email with default validator settings.
167+
pub fn validate_email<T: AsRef<str>>(email: T) -> Result<ValidatedEmail, ValidationError> {
168+
let validator = EmailValidator::default();
169+
validator.validate_email(email.as_ref())
170+
}
171+
9172
use pyo3::prelude::*;
10173

11174
#[pymodule]

src/models.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,52 @@
11
use pyo3::prelude::*;
22
use std::net::IpAddr;
33

4+
/// A structure representing a validated email address with various components and normalized forms.
45
#[pyclass]
56
pub struct ValidatedEmail {
7+
/// The email address provided to validate_email.
68
#[pyo3(get)]
79
pub original: String,
10+
/// The normalized email address should be used instead of the original. It converts IDNA ASCII domain names to Unicode and normalizes both the local part and domain. The normalized address combines the local part and domain name with an '@' sign.
811
#[pyo3(get)]
912
pub normalized: String,
13+
/// The local part of the email address (the part before the '@' sign) after it has been Unicode normalized.
1014
#[pyo3(get)]
1115
pub local_part: String,
16+
/// If the domain part is a domain literal, it will be an IPv4Address or IPv6Address object.
1217
#[pyo3(get)]
1318
pub domain_address: Option<IpAddr>,
19+
/// The domain part of the email address (the part after the '@' sign) after Unicode normalization.
1420
#[pyo3(get)]
1521
pub domain_name: String,
22+
/// Whether the email address is deliverable.
1623
#[pyo3(get)]
1724
pub is_deliverable: bool,
1825
}
1926

20-
#[derive(Default)]
27+
/// A structure for customizing email validation.
2128
#[pyclass]
2229
pub struct EmailValidator {
30+
/// Whether to allow SMTPUTF8. [Default: true]
2331
pub allow_smtputf8: bool,
32+
/// Whether to allow empty local part. [Default: false]
2433
pub allow_empty_local: bool,
34+
/// Whether to allow quoted local part. [Default: false]
2535
pub allow_quoted_local: bool,
36+
/// Whether to allow domain literals. [Default: false]
2637
pub allow_domain_literal: bool,
38+
/// Whether to check if the email address is deliverable. [Default: true]
2739
pub deliverable_address: bool,
2840
}
41+
42+
impl Default for EmailValidator {
43+
fn default() -> Self {
44+
Self {
45+
allow_smtputf8: true,
46+
allow_empty_local: false,
47+
allow_quoted_local: false,
48+
allow_domain_literal: false,
49+
deliverable_address: true,
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)