getargs-derive-macros 0.1.0

Internal implementation detail of getargs-derive. Not meant for direct use; depend on getargs-derive instead.
Documentation
use super::{DefaultValue, FieldKind, RawField};
use crate::model::attr::{require_value, set_once};
use crate::model::errors::Errors;
use crate::model::types::is_count_type;
use proc_macro2::Span;
use std::collections::HashSet;
use syn::{Expr, ExprLit, ExprRange, Lit, LitStr, Type, meta::ParseNestedMeta, spanned::Spanned};

const VALUE_PARSER_EXAMPLE: &str = "`value_parser = parse_port` or `value_parser = 1..=64`";

pub(crate) enum ValueParser {
    Function(syn::Path),
    Range(ExprRange),
}

pub(crate) struct PossibleValues {
    pub(crate) values: Vec<String>,
    span: Span,
}

pub(super) fn value_parser(meta: &ParseNestedMeta, raw: &mut RawField) -> syn::Result<()> {
    let expr: Expr = require_value(meta, "value_parser", "value_parser = 1..=64")?.parse()?;
    let parser = match expr {
        Expr::Path(path) if path.qself.is_none() && path.attrs.is_empty() => {
            ValueParser::Function(path.path)
        }
        Expr::Range(range) if range.start.is_none() && range.end.is_none() => {
            return Err(syn::Error::new_spanned(
                range,
                "`value_parser` range needs at least one bound, e.g. `value_parser = 1..=64`",
            ));
        }
        Expr::Range(range) => ValueParser::Range(range),
        other => {
            return Err(syn::Error::new_spanned(
                other,
                format!(
                    "`value_parser` expects a function path or a range, e.g. {VALUE_PARSER_EXAMPLE}"
                ),
            ));
        }
    };
    set_once(&mut raw.value_parser, parser, meta)
}

fn parse_possible_values(meta: &ParseNestedMeta) -> syn::Result<PossibleValues> {
    let example = "possible_values = [\"fast\", \"slow\"]";
    let expr: Expr = require_value(meta, "possible_values", example)?.parse()?;
    let Expr::Array(array) = expr else {
        return Err(syn::Error::new_spanned(
            expr,
            format!("`possible_values` expects an array of string literals, e.g. `{example}`"),
        ));
    };
    if array.elems.is_empty() {
        return Err(syn::Error::new_spanned(
            array,
            "`possible_values` cannot be empty",
        ));
    }
    let mut values = Vec::new();
    let mut seen = HashSet::new();
    let mut errors = Errors::default();
    for element in &array.elems {
        let Expr::Lit(ExprLit {
            lit: Lit::Str(literal),
            ..
        }) = element
        else {
            errors.push(syn::Error::new_spanned(
                element,
                format!("`possible_values` expects string literals, e.g. `{example}`"),
            ));
            continue;
        };
        let value = literal.value();
        if value.is_empty() {
            errors.push(syn::Error::new(
                literal.span(),
                "a possible value cannot be empty",
            ));
        } else if !seen.insert(value.clone()) {
            errors.push(syn::Error::new(
                literal.span(),
                format!("duplicate possible value \"{value}\""),
            ));
        }
        values.push(value);
    }
    errors.finish(PossibleValues {
        values,
        span: array.span(),
    })
}

pub(super) fn possible_values(meta: &ParseNestedMeta, raw: &mut RawField) -> syn::Result<()> {
    let values = parse_possible_values(meta)?;
    set_once(&mut raw.possible_values, values, meta)
}

fn key_error(
    value_parser: Option<&ValueParser>,
    possible_values: Option<&PossibleValues>,
    message: impl Fn(&str) -> String,
) -> Option<syn::Error> {
    match (value_parser, possible_values) {
        (Some(ValueParser::Function(path)), _) => {
            Some(syn::Error::new_spanned(path, message("value_parser")))
        }
        (Some(ValueParser::Range(range)), _) => {
            Some(syn::Error::new_spanned(range, message("value_parser")))
        }
        (None, Some(values)) => Some(syn::Error::new(values.span, message("possible_values"))),
        (None, None) => None,
    }
}

pub(super) fn check(
    value_parser: Option<&ValueParser>,
    possible_values: Option<&PossibleValues>,
    kind: &FieldKind,
    value_ty: &Type,
    default: Option<&DefaultValue>,
    value_enum: bool,
    errors: &mut Errors,
) {
    if let FieldKind::Flag(_) = kind
        && let Some(error) = key_error(value_parser, possible_values, |key| {
            format!("`{key}` cannot be used on flags because they do not take a value")
        })
    {
        errors.push(error);
        return;
    }

    if value_enum
        && let Some(error) = key_error(value_parser, possible_values, |key| {
            format!(
                "`{key}` cannot be combined with `value_enum`, which already defines the accepted values"
            )
        })
    {
        errors.push(error);
        return;
    }

    if let Some(ValueParser::Range(range)) = value_parser
        && !is_count_type(value_ty)
    {
        errors.push(syn::Error::new_spanned(
            range,
            "a `value_parser` range requires an integer field type, e.g. `u8`; use a function for other types",
        ));
    }

    if let (Some(values), Some(DefaultValue::Literal(literal))) = (possible_values, default) {
        check_default(values, literal, errors);
    }
}

fn check_default(values: &PossibleValues, literal: &LitStr, errors: &mut Errors) {
    let default = literal.value();
    if !values.values.contains(&default) {
        errors.push(syn::Error::new(
            literal.span(),
            format!(
                "`default_value` \"{default}\" is not one of the possible values: {}",
                values.values.join(", ")
            ),
        ));
    }
}