prove_derive 0.1.5

prove struct
Documentation
// Copyright 2019 YechaoLi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[derive(Debug, Clone)]
pub enum ValidatorKind {
    Length(prove::length::Length),
    Match(prove::r#match::Match),
    Url(prove::url::Url),
    Ip(prove::ip::Ip),
    IpV4(prove::ip::IpV4),
    IpV6(prove::ip::IpV6),
    Email(prove::email::Email),
    Nested,
}

#[derive(Debug)]
pub enum SimpleValidatorKind {
    Url,
    Ip,
    IpV4,
    IpV6,
    Email,
}

#[derive(Debug)]
pub struct Validator {
    pub validator: ValidatorKind,
    pub message: String,
    pub code: u64,
}

pub fn extract_nested_validator() -> Validator {
    Validator {
        validator: ValidatorKind::Nested,
        message: "Nested validator error".to_owned(),
        code: 0,
    }
}

pub fn extract_length_validator(field_name: &str, metas: &Vec<syn::NestedMeta>) -> Validator {
    let mut min = None;
    let mut max = None;
    let mut eq = None;

    let (message, code) = extract_message_and_code("length", field_name, metas);

    let error = |msg: &str| -> ! {
        panic!(
            "Invalid attribute #[prove] on field `{}`: {}",
            field_name, msg
        );
    };

    for meta in metas {
        if let syn::NestedMeta::Meta(ref item) = *meta {
            if let syn::Meta::NameValue(syn::MetaNameValue {
                ref ident, ref lit, ..
            }) = *item
            {
                match ident.to_string().as_ref() {
                    "message" | "code" => continue,
                    "min" => {
                        min = match *lit {
                            syn::Lit::Int(ref min) => Some(min.value()),
                            _ => error("invalid argument type for `min` of `length` validator: only u64 are allowed"),
                        };
                    },
                    "max" => {
                        max = match *lit {
                            syn::Lit::Int(ref max) => Some(max.value()),
                            _ => error("invalid argument type for `max` of `length` validator: only u64 are allowed"),
                        };
                    },
                    "eq" => {
                        eq= match *lit {
                            syn::Lit::Int(ref eq) => Some(eq.value()),
                            _ => error("invalid argument type for `eq` of `length` validator: only u64 are allowed"),
                        };
                    },
                    v => error(&format!(
                        "unknown argument `{}` for validator `length` (it only has `message`, `code`, `min`, `max`, `eq`)",
                        v
                    ))
                }
            } else {
                panic!(
                    "unexpected item {:?} while parsing `length` validator of field {}",
                    item, field_name
                )
            }
        }
    }

    // eq with min and max cannot exist simultaneously.
    if eq.is_some() && (min.is_some() || max.is_some()) {
        error(
            "both `eq` and `min` or `max` have been set in `length` validator: probably a mistake",
        );
    }
    // eq with min or max must have at least one.
    if min.is_none() && max.is_none() && eq.is_none() {
        error("Validator `length` requires at least 1 argument out of `message`, `min`, `max` and `eq`");
    }
    // min must less then or eq max.
    if min.is_some() && max.is_some() {
        if min.unwrap() > max.unwrap() {
            error("min must less then or equal max");
        }
    }

    Validator {
        validator: ValidatorKind::Length(prove::length::Length { min, max, eq }),
        message: message.unwrap_or("Invalid length".to_owned()),
        code: code.unwrap_or(0),
    }
}

pub fn extract_match_validator(field_name: &str, metas: &Vec<syn::NestedMeta>) -> Validator {
    let mut regex = None;
    let (message, code) = extract_message_and_code("match", field_name, metas);

    let error = |msg: &str| -> ! {
        panic!(
            "Invalid attribute #[prove] on field `{}`: {}",
            field_name, msg
        );
    };

    for meta in metas {
        if let syn::NestedMeta::Meta(ref item) = *meta {
            if let syn::Meta::NameValue(syn::MetaNameValue {
                ref ident, ref lit, ..
            }) = *item
            {
                match ident.to_string().as_ref() {
                    "message" | "code" => continue,
                    "regex" => {
                        regex = match *lit {
                            syn::Lit::Str(ref min) => Some(min.value()),
                            _ => error("invalid argument type for `regex` of `match` validator: only regex string are allowed"),
                        };
                    },
                    v => error(&format!(
                        "unknown argument `{}` for validator `match` (it only has `message`, `code`, `regex`)",
                        v
                    ))
                }
            } else {
                panic!(
                    "unexpected item {:?} while parsing `match` validator of field {}",
                    item, field_name
                )
            }
        }
    }

    match regex {
        Some(regex) => Validator {
            validator: ValidatorKind::Match(prove::r#match::Match { regex }),
            message: message.unwrap_or("Must match".to_owned()),
            code: code.unwrap_or(0),
        },
        _ => error("Validator `match` require regex parameter"),
    }
}

pub fn extract_simple_validator(
    validator_kind: SimpleValidatorKind,
    field_name: &str,
    metas: &Vec<syn::NestedMeta>,
) -> Validator {
    let (message, code) =
        extract_message_and_code(&format!("{:?}", validator_kind), field_name, metas);

    let error = |msg: &str| -> ! {
        panic!(
            "Invalid attribute #[prove] on field `{}`: {}",
            field_name, msg
        );
    };

    for meta in metas {
        if let syn::NestedMeta::Meta(ref item) = *meta {
            if let syn::Meta::NameValue(syn::MetaNameValue { ref ident, .. }) = *item {
                match ident.to_string().as_ref() {
                    "message" | "code" => continue,
                    v => error(&format!(
                        "unknown argument `{}` for validator `{:?}` (it only has `message`, `code`)",
                        v, validator_kind
                    ))
                }
            } else {
                panic!(
                    "unexpected item {:?} while parsing `{:?}` validator of field {}",
                    item, validator_kind, field_name
                )
            }
        }
    }

    Validator {
        validator: match validator_kind {
            SimpleValidatorKind::Url => ValidatorKind::Url(prove::url::Url),
            SimpleValidatorKind::Ip => ValidatorKind::Ip(prove::ip::Ip),
            SimpleValidatorKind::IpV4 => ValidatorKind::IpV4(prove::ip::IpV4),
            SimpleValidatorKind::IpV6 => ValidatorKind::IpV6(prove::ip::IpV6),
            SimpleValidatorKind::Email => ValidatorKind::Email(prove::email::Email),
        },
        message: message.unwrap_or(format!("Invalid {:?}", validator_kind)),
        code: code.unwrap_or(0),
    }
}

fn extract_message_and_code(
    validator_name: &str,
    field_name: &str,
    metas: &Vec<syn::NestedMeta>,
) -> (Option<String>, Option<u64>) {
    let mut message = None;
    let mut code = None;

    for meta_item in metas {
        if let syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
            ref ident,
            ref lit,
            ..
        })) = *meta_item
        {
            match ident.to_string().as_ref() {
                "message" => {
                    message = match lit {
                        syn::Lit::Str(s) => Some(s.value()),
                        _ => panic!(
                            "Invalid argument type for `message` for validator `{}` on field `{}`: only a string is allowed",
                            validator_name, field_name
                        ),
                    };
                }
                "code" => {
                    code = match lit {
                        syn::Lit::Int(s) => Some(s.value()),
                        _ => panic!(
                            "Invalid argument type for `code` for validator `{}` on field `{}`: only a u64 is allowed",
                            validator_name, field_name
                        ),
                    }
                }
                _ => continue,
            }
        }
    }

    (message, code)
}