Range Validation — Complete Guide
In this tutorial, you'll learn about Range Validation. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Range validation checks that numeric values, dates, strings, or collections fall within specified minimum and maximum boundaries.
What You'll Learn
By the end of this lesson, you will validate numeric ranges, date ranges, string lengths, collection sizes, and implement inclusive/exclusive boundaries.
Why It Matters
Without range validation, your API accepts age=-5 or price=999999999999. Boundary validation prevents absurd values that cause overflow, storage waste, or business logic errors.
Real-World Use
DodaTech's payment API validates amounts: min 0.50 (to cover processing fees), max 99999.99 (anti-fraud). Values outside are rejected.
Range Validator
# range_validator.py
from typing import Any, Dict, List, Optional, Union
class RangeValidator:
@staticmethod
def number(value: Any, min_val: Optional[float] = None,
max_val: Optional[float] = None,
inclusive: bool = True) -> Optional[str]:
if not isinstance(value, (int, float)):
return "Value must be a number"
if inclusive:
if min_val is not None and value < min_val:
return f"Minimum value is {min_val}"
if max_val is not None and value > max_val:
return f"Maximum value is {max_val}"
else:
if min_val is not None and value <= min_val:
return f"Value must be greater than {min_val}"
if max_val is not None and value >= max_val:
return f"Value must be less than {max_val}"
return None
@staticmethod
def string_length(value: Any, min_len: Optional[int] = None,
max_len: Optional[int] = None) -> Optional[str]:
if not isinstance(value, str):
return "Value must be a string"
if min_len is not None and len(value) < min_len:
return f"Minimum {min_len} characters"
if max_len is not None and len(value) > max_len:
return f"Maximum {max_len} characters"
return None
@staticmethod
def array_size(value: Any, min_items: Optional[int] = None,
max_items: Optional[int] = None) -> Optional[str]:
if not isinstance(value, (list, tuple)):
return "Value must be an array"
if min_items is not None and len(value) < min_items:
return f"Minimum {min_items} items"
if max_items is not None and len(value) > max_items:
return f"Maximum {max_items} items"
return None
@staticmethod
def date_range(value: Any, min_date: Optional[str] = None,
max_date: Optional[str] = None) -> Optional[str]:
from datetime import datetime
if isinstance(value, str):
try:
value = datetime.strptime(value, "%Y-%m-%d")
except ValueError:
return "Invalid date format (YYYY-MM-DD)"
if not isinstance(value, datetime):
return "Value must be a date"
if min_date:
try:
mindt = datetime.strptime(min_date, "%Y-%m-%d")
if value < mindt:
return f"Date must be on or after {min_date}"
except ValueError:
pass
if max_date:
try:
maxdt = datetime.strptime(max_date, "%Y-%m-%d")
if value > maxdt:
return f"Date must be on or before {max_date}"
except ValueError:
pass
return None
rv = RangeValidator()
print(f"Number 5 in [1,10]: {rv.number(5, 1, 10)}")
print(f"Number -1 in [0,]: {rv.number(-1, 0)}")
print(f"String 'ab' len 2-5: {rv.string_length('ab', 2, 5)}")
print(f"Array [] min 1: {rv.array_size([], min_items=1)}")
print(f"Date 2024-01-01: {rv.date_range('2024-01-01', '2023-01-01', '2024-12-31')}")
print(f"Number 10 excl max: {rv.number(10, max_val=10, inclusive=False)}")
Expected output:
Number 5 in [1,10]: None
Number -1 in [0,]: Minimum value is 0
String 'ab' len 2-5: None
Array [] min 1: Minimum 1 items
Date 2024-01-01: None
Number 10 excl max: Value must be less than 10
Multi-Field Range Validation
# multi_range.py
from typing import Any, Dict, List, Optional
class MultiRangeValidator:
@staticmethod
def validate_all(data: Dict, rules: Dict[str, Dict]) -> List[Dict]:
errors = []
for field, rule in rules.items():
value = data.get(field)
if value is None:
continue
if rule.get("type") == "int":
min_v = rule.get("min")
max_v = rule.get("max")
if not isinstance(value, int) or isinstance(value, bool):
errors.append({"field": field, "code": "type"})
else:
if min_v is not None and value < min_v:
errors.append({"field": field, "code": "min", "min": min_v})
if max_v is not None and value > max_v:
errors.append({"field": field, "code": "max", "max": max_v})
elif rule.get("type") == "float":
if not isinstance(value, (int, float)):
errors.append({"field": field, "code": "type"})
else:
if rule.get("min") is not None and value < rule["min"]:
errors.append({"field": field, "code": "min", "min": rule["min"]})
if rule.get("max") is not None and value > rule["max"]:
errors.append({"field": field, "code": "max", "max": rule["max"]})
return errors
rules = {
"age": {"type": "int", "min": 13, "max": 150},
"price": {"type": "float", "min": 0.50, "max": 99999.99},
"quantity": {"type": "int", "min": 1},
}
print(MultiRangeValidator.validate_all({"age": 10, "price": 0.25, "quantity": 0}, rules))
print(MultiRangeValidator.validate_all({"age": 25, "price": 50.0, "quantity": 2}, rules))
Expected output:
[{'field': 'age', 'code': 'min', 'min': 13}, {'field': 'price', 'code': 'min', 'min': 0.5}, {'field': 'quantity', 'code': 'min', 'min': 1}]
[]
Date Range Business Rules
# date_business.py
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional
class DateBusinessRules:
@staticmethod
def future_date(value: str) -> Optional[str]:
try:
dt = datetime.strptime(value, "%Y-%m-%d")
if dt <= datetime.now():
return "Date must be in the future"
except ValueError:
return "Invalid date format"
return None
@staticmethod
def within_days(value: str, days: int = 30) -> Optional[str]:
try:
dt = datetime.strptime(value, "%Y-%m-%d")
if dt > datetime.now() + timedelta(days=days):
return f"Date must be within {days} days from now"
except ValueError:
return "Invalid date format"
return None
@staticmethod
def start_before_end(start: str, end: str) -> Optional[str]:
try:
start_dt = datetime.strptime(start, "%Y-%m-%d")
end_dt = datetime.strptime(end, "%Y-%m-%d")
if start_dt >= end_dt:
return "Start date must be before end date"
except ValueError:
return "Invalid date format"
return None
dbr = DateBusinessRules()
print(f"Future (tomorrow): {dbr.future_date('2028-01-01')}")
print(f"Past: {dbr.future_date('2020-01-01')}")
print(f"Within 30d: {dbr.within_days('2028-06-01', 30)}")
print(f"Start < End: {dbr.start_before_end('2024-06-01', '2024-06-15')}")
print(f"Start >= End: {dbr.start_before_end('2024-06-15', '2024-06-01')}")
Expected output:
Future (tomorrow): None
Past: Date must be in the future
Within 30d: Date must be within 30 days from now
Start < End: None
Start >= End: Start date must be before end date
Common Mistakes
1. Off-by-One in Inclusive/Exclusive
"Must be at least 18" (>= 18) vs "Must be older than 18" (> 18). Use inclusive for age.
2. No Upper Bound
Accepting any number allows overflow attacks. Always set a reasonable maximum.
3. Date Range Without Timezone
"2024-01-01" means different times in different timezones. Use UTC or include timezone.
4. Same Range for All Fields
Different fields need different ranges. Age (0-150) vs Quantity (0-10000) vs Amount (0-999999.99).
5. Not Validating Both Ends
Check both min and max. Only checking one side leaves the other unvalidated.
Practice Questions
1. What is range validation?
Checking that a value falls within specified minimum and maximum boundaries.
2. What is inclusive vs exclusive range?
Inclusive: value >= min and value <= max. Exclusive: value > min and value < max.
3. Why validate both lower and upper bounds?
Lower prevents absurdly small values. Upper prevents overflow or unrealistic values.
4. How do you validate date ranges?
Parse dates and compare with datetime operators. Validate start < end for date pairs.
Challenge
Build a range validation system for a hotel booking API: check-in date (future, within 1 year), check-out date (after check-in, within 30 days of check-in), guests (1-10), room count (1-5).
FAQ
Mini Project: Range Checker
# range_checker.py
from typing import Any, Dict, List, Optional
def in_range(value: Any, minimum: Any, maximum: Any) -> Optional[str]:
if value < minimum:
return f"below minimum {minimum}"
if value > maximum:
return f"above maximum {maximum}"
return None
print(in_range(5, 1, 10))
print(in_range(0, 1, 10))
print(in_range("2024-06-01", "2024-01-01", "2024-12-31"))
Expected output:
None
below minimum 1
None
What's Next
You understand range validation. Next, learn length validation, then regex validation.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro