ts-sql-helper-derive 0.6.2

Derives for ts-sql-helper-lib
Documentation
use postgres_types::Type;
use quote::format_ident;
use syn::parse_quote;

pub struct Parameter {
    pub r#type: Type,
    pub is_optional: bool,
    pub number: usize,
}
impl Parameter {
    pub fn to_syn_type(&self) -> syn::Type {
        let raw_type = match &self.r#type {
            &Type::BOOL => parse_quote!(&'a bool),
            &Type::BOOL_ARRAY => parse_quote!(&'a [bool]),
            &Type::BYTEA => parse_quote!(&'a [u8]),
            &Type::BYTEA_ARRAY => parse_quote!(&'a [Vec<u8>]),
            &Type::CHAR => parse_quote!(&'a i8),
            &Type::CHAR_ARRAY => parse_quote!(&'a [i8]),
            &Type::INT8 => parse_quote!(&'a i64),
            &Type::INT8_ARRAY => parse_quote!(&'a [i64]),
            &Type::INT4 => parse_quote!(&'a i32),
            &Type::INT4_ARRAY => parse_quote!(&'a [i32]),
            &Type::INT2 => parse_quote!(&'a i16),
            &Type::INT2_ARRAY => parse_quote!(&'a [i16]),
            &Type::FLOAT8 => parse_quote!(&'a f64),
            &Type::FLOAT8_ARRAY => parse_quote!(&'a [f64]),
            &Type::FLOAT4 => parse_quote!(&'a f32),
            &Type::FLOAT4_ARRAY => parse_quote!(&'a [f32]),
            &Type::UUID => parse_quote!(&'a uuid::Uuid),
            &Type::UUID_ARRAY => parse_quote!(&'a [uuid::Uuid]),
            &Type::TEXT | &Type::VARCHAR => parse_quote!(&'a str),
            &Type::VARCHAR_ARRAY | &Type::TEXT_ARRAY => parse_quote!(&'a [String]),
            &Type::TIMESTAMP => parse_quote!(&'a ts_sql_helper_lib::SqlDateTime),
            &Type::TIMESTAMP_ARRAY => parse_quote!(&'a [ts_sql_helper_lib::SqlDateTime]),
            &Type::TIMESTAMPTZ => parse_quote!(&'a ts_sql_helper_lib::SqlTimestamp),
            &Type::TIMESTAMPTZ_ARRAY => parse_quote!(&'a [ts_sql_helper_lib::SqlTimestamp]),
            &Type::DATE => parse_quote!(&'a ts_sql_helper_lib::SqlDate),
            &Type::DATE_ARRAY => parse_quote!(&'a [ts_sql_helper_lib::SqlDate]),
            &Type::TIME => parse_quote!(&'a ts_sql_helper_lib::SqlTime),
            &Type::TIME_ARRAY => parse_quote!(&'a [ts_sql_helper_lib::SqlTime]),
            _ => unreachable!(),
        };
        if self.is_optional {
            parse_quote!(Option<#raw_type>)
        } else {
            raw_type
        }
    }

    pub fn name(&self) -> syn::Ident {
        format_ident!("p{}", self.number)
    }
}

pub fn get_parameters(sql: &str, optional_parameters: Vec<usize>) -> Vec<Parameter> {
    let mut parameter_types: Vec<(usize, String)> = vec![];

    enum State {
        Neutral,
        ConsumingNumber { number: String },
        ConsumingTypeSeparator { number: usize },
        ConsumingType { number: usize, type_string: String },
    }

    let mut state = State::Neutral;
    for character in sql.chars() {
        match &mut state {
            State::Neutral => {
                if character == '$' {
                    state = State::ConsumingNumber {
                        number: String::new(),
                    };
                }
            }
            State::ConsumingNumber { number } => {
                if character.is_ascii_digit() {
                    number.push(character);
                } else if character == ':' {
                    state = State::ConsumingTypeSeparator {
                        number: number.parse().expect("variable should be a number"),
                    };
                } else {
                    if !number.is_empty() {
                        panic!("variable {number} must have a type");
                    }
                    state = State::Neutral;
                }
            }
            State::ConsumingTypeSeparator { number } => {
                if character.is_ascii_alphabetic() {
                    state = State::ConsumingType {
                        number: *number,
                        type_string: character.to_string(),
                    };
                } else if character != ':' {
                    panic!("variable {number} must have a type");
                }
            }
            State::ConsumingType {
                number,
                type_string,
            } => {
                if character.is_ascii_alphanumeric() || character == '[' || character == ']' {
                    type_string.push(character);
                } else if type_string.is_empty() {
                    panic!("variable {number} must have a type");
                } else {
                    parameter_types.push((*number, type_string.to_uppercase()));
                    state = State::Neutral;
                }
            }
        }
    }
    match state {
        State::Neutral => {}
        State::ConsumingNumber { number } => {
            if !number.is_empty() {
                panic!("variable {number} must have a type");
            }
        }
        State::ConsumingTypeSeparator { number } => {
            panic!("variable {number} must have a type");
        }
        State::ConsumingType {
            number,
            type_string,
        } => {
            if type_string.is_empty() {
                panic!("variable {number} must have a type");
            } else {
                parameter_types.push((number, type_string.to_uppercase()));
            }
        }
    }

    parameter_types.sort_by_key(|v| v.0);
    parameter_types.dedup();

    let mut seen_variables = vec![];
    for (number, _) in parameter_types.iter() {
        if seen_variables.contains(number) {
            panic!("variable {number} is defined with multiple types")
        }
        if let Some(variable) = seen_variables.last()
            && number - 1 != *variable
        {
            panic!("variable {} is missing", number - 1)
        }
        seen_variables.push(*number);
    }

    parameter_types
        .into_iter()
        .map(|(number, type_string)| {
            let r#type = match type_string.as_str() {
                "BOOL" => Type::BOOL,
                "BOOL[]" => Type::BOOL_ARRAY,
                "BYTEA" => Type::BYTEA,
                "BYTEA[]" => Type::BYTEA_ARRAY,
                "CHAR" => Type::CHAR,
                "CHAR[]" => Type::CHAR_ARRAY,
                "INT8" => Type::INT8,
                "INT8[]" => Type::INT8_ARRAY,
                "INT4" => Type::INT4,
                "INT4[]" => Type::INT4_ARRAY,
                "INT2" => Type::INT2,
                "INT2[]" => Type::INT2_ARRAY,
                "FLOAT8" => Type::FLOAT8,
                "FLOAT8[]" => Type::FLOAT8_ARRAY,
                "FLOAT4" => Type::FLOAT4,
                "FLOAT4[]" => Type::FLOAT4_ARRAY,
                "UUID" => Type::UUID,
                "UUID[]" => Type::UUID_ARRAY,
                "TEXT" => Type::TEXT,
                "VARCHAR" => Type::VARCHAR,
                "VARCHAR[]" => Type::VARCHAR_ARRAY,
                "TEXT[]" => Type::TEXT_ARRAY,
                "TIMESTAMP" => Type::TIMESTAMP,
                "TIMESTAMP[]" => Type::TIMESTAMP_ARRAY,
                "TIMESTAMPTZ" => Type::TIMESTAMPTZ,
                "TIMESTAMPTZ[]" => Type::TIMESTAMPTZ_ARRAY,
                "DATE" => Type::DATE,
                "DATE[]" => Type::DATE_ARRAY,
                "TIME" => Type::TIME,
                "TIME[]" => Type::TIME_ARRAY,
                type_string => {
                    panic!("unsupported type `{type_string}`")
                }
            };
            Parameter {
                r#type,
                is_optional: optional_parameters.contains(&number),
                number,
            }
        })
        .collect()
}