///|
pub(all) struct ParseError {
field : String
message : String
} derive(Eq, Debug)
///|
pub fn ParseError::new(field : String, message : String) -> ParseError {
ParseError::{ field, message }
}
///|
pub fn ParseError::field(self : ParseError) -> String {
self.field
}
///|
pub fn ParseError::message(self : ParseError) -> String {
self.message
}
///|
pub(all) enum ParseResult {
ParseOk(LimitSpec)
ParseFailed(ParseError)
} derive(Eq, Debug)
///|
pub fn ParseResult::parse_limit(text : String) -> ParseResult {
let words = split_words(text)
if words.length() == 0 {
return ParseFailed(ParseError::new("rule", "rule must not be empty"))
}
match words[0] {
"token_bucket" => parse_token_bucket_words(words)
"fixed_window" => parse_fixed_window_words(words)
"sliding_log" => parse_sliding_log_words(words)
_ =>
ParseFailed(ParseError::new("kind", "unknown limiter kind: \{words[0]}"))
}
}
///|
pub fn ParseResult::is_ok(self : ParseResult) -> Bool {
match self {
ParseOk(_) => true
ParseFailed(_) => false
}
}
///|
pub fn ParseResult::spec(self : ParseResult) -> LimitSpec {
match self {
ParseOk(spec) => spec
ParseFailed(_) => LimitSpec::token_bucket("invalid", 1, 1, 1000)
}
}
///|
pub fn ParseResult::error_field(self : ParseResult) -> String {
match self {
ParseOk(_) => ""
ParseFailed(error) => error.field()
}
}
///|
pub fn ParseResult::error_message(self : ParseResult) -> String {
match self {
ParseOk(_) => ""
ParseFailed(error) => error.message()
}
}
///|
fn parse_token_bucket_words(words : Array[String]) -> ParseResult {
if words.length() == 4 {
match parse_positive_field(words[2], "capacity") {
ParseIntOk(capacity) =>
match parse_rate_field(words[3], "refill") {
ParseRateOk(rate) =>
ParseOk(
LimitSpec::token_bucket(
words[1],
capacity,
rate.events(),
rate.period_ms(),
),
)
ParseRateFailed(error) => ParseFailed(error)
}
ParseIntFailed(error) => ParseFailed(error)
}
} else if words.length() >= 5 {
match parse_positive_field(words[2], "capacity") {
ParseIntOk(capacity) =>
match parse_positive_field(words[3], "refill_tokens") {
ParseIntOk(refill_tokens) =>
match parse_duration_field(words[4], "refill_period_ms") {
ParseDurationOk(period) =>
ParseOk(
LimitSpec::token_bucket(
words[1],
capacity,
refill_tokens,
period.millis(),
),
)
ParseDurationFailed(error) => ParseFailed(error)
}
ParseIntFailed(error) => ParseFailed(error)
}
ParseIntFailed(error) => ParseFailed(error)
}
} else {
ParseFailed(
ParseError::new(
"token_bucket", "token_bucket requires: name capacity refill_tokens refill_period_ms",
),
)
}
}
///|
fn parse_fixed_window_words(words : Array[String]) -> ParseResult {
if words.length() < 4 {
ParseFailed(
ParseError::new(
"fixed_window", "fixed_window requires: name limit window",
),
)
} else {
match parse_positive_field(words[2], "limit") {
ParseIntOk(limit) =>
match parse_duration_field(words[3], "window") {
ParseDurationOk(window) =>
ParseOk(LimitSpec::fixed_window(words[1], limit, window.millis()))
ParseDurationFailed(error) => ParseFailed(error)
}
ParseIntFailed(error) => ParseFailed(error)
}
}
}
///|
fn parse_sliding_log_words(words : Array[String]) -> ParseResult {
if words.length() < 4 {
ParseFailed(
ParseError::new("sliding_log", "sliding_log requires: name limit window"),
)
} else {
match parse_positive_field(words[2], "limit") {
ParseIntOk(limit) =>
match parse_duration_field(words[3], "window") {
ParseDurationOk(window) =>
ParseOk(LimitSpec::sliding_log(words[1], limit, window.millis()))
ParseDurationFailed(error) => ParseFailed(error)
}
ParseIntFailed(error) => ParseFailed(error)
}
}
}
///|
priv enum ParseIntResult {
ParseIntOk(Int)
ParseIntFailed(ParseError)
}
///|
priv enum ParseDurationResult {
ParseDurationOk(Duration)
ParseDurationFailed(ParseError)
}
///|
priv enum ParseRateResult {
ParseRateOk(Rate)
ParseRateFailed(ParseError)
}
///|
fn parse_positive_field(text : String, field : String) -> ParseIntResult {
let value = parse_required_positive_prefix(text)
if value <= 0 {
ParseIntFailed(
ParseError::new(field, "\{field} must be a positive integer"),
)
} else {
ParseIntOk(value)
}
}
///|
fn parse_duration_field(text : String, field : String) -> ParseDurationResult {
let value = parse_required_positive_prefix(text)
if value <= 0 {
ParseDurationFailed(
ParseError::new(field, "\{field} must be a positive duration"),
)
} else if !is_duration_suffix(suffix_after_digit_prefix(text)) {
ParseDurationFailed(
ParseError::new(field, "\{field} supports duration suffixes: ms, s, m, h"),
)
} else {
ParseDurationOk(Duration::parse(text))
}
}
///|
fn parse_rate_field(text : String, field : String) -> ParseRateResult {
let value = parse_required_positive_prefix(text)
if value <= 0 {
ParseRateFailed(ParseError::new(field, "\{field} must be a positive rate"))
} else if !is_rate_suffix(suffix_after_digit_prefix(text)) {
ParseRateFailed(
ParseError::new(
field,
"\{field} supports rate suffixes: /ms, /s, /min, /h",
),
)
} else {
ParseRateOk(Rate::parse(text))
}
}
///|
fn parse_required_positive_prefix(text : String) -> Int {
let mut value = 0
let mut seen = false
for i in 0.. String {
let mut index = 0
for i in 0.. Bool {
suffix == "" ||
suffix == "ms" ||
suffix == "s" ||
suffix == "m" ||
suffix == "h"
}
///|
fn is_rate_suffix(suffix : String) -> Bool {
suffix == "" ||
suffix == "/ms" ||
suffix == "/s" ||
suffix == "/min" ||
suffix == "/h"
}
///|
fn is_ascii_digit(ch : UInt16) -> Bool {
ch.to_int() >= '0'.to_int() && ch.to_int() <= '9'.to_int()
}