1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use proc_macro_error::abort;
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{Ident, LitInt, LitStr};

use util::{parse_eq, unknown_attribute, POSTGRES_TYPE_NOTE, POSTGRES_TYPE_NOTE_ID};

enum Attr {
    Oid(Ident, LitInt),
    ArrayOid(Ident, LitInt),
    Name(Ident, LitStr),
    Schema(Ident, LitStr),
}

impl Parse for Attr {
    fn parse(input: ParseStream) -> Result<Self> {
        let name: Ident = input.parse()?;
        let name_str = name.to_string();

        match &*name_str {
            "oid" => Ok(Attr::Oid(name, parse_eq(input, POSTGRES_TYPE_NOTE_ID)?)),
            "array_oid" => Ok(Attr::ArrayOid(
                name,
                parse_eq(input, POSTGRES_TYPE_NOTE_ID)?,
            )),
            "name" => Ok(Attr::Name(name, parse_eq(input, POSTGRES_TYPE_NOTE)?)),
            "schema" => Ok(Attr::Schema(name, parse_eq(input, POSTGRES_TYPE_NOTE)?)),

            _ => unknown_attribute(&name, &["oid", "array_oid", "name", "schema"]),
        }
    }
}

pub enum PostgresType {
    Fixed(LitInt, LitInt),
    Lookup(LitStr, Option<LitStr>),
}

impl Parse for PostgresType {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut oid = None;
        let mut array_oid = None;
        let mut name = None;
        let mut schema = None;

        for attr in Punctuated::<Attr, Comma>::parse_terminated(input)? {
            match attr {
                Attr::Oid(ident, value) => oid = Some((ident, value)),
                Attr::ArrayOid(ident, value) => array_oid = Some((ident, value)),
                Attr::Name(ident, value) => name = Some((ident, value)),
                Attr::Schema(ident, value) => schema = Some((ident, value)),
            }
        }

        Self::validate_and_build(input, oid, array_oid, name, schema)
    }
}

impl PostgresType {
    pub fn validate_and_build(
        input: ParseStream,
        oid: Option<(Ident, LitInt)>,
        array_oid: Option<(Ident, LitInt)>,
        name: Option<(Ident, LitStr)>,
        schema: Option<(Ident, LitStr)>,
    ) -> Result<Self> {
        let help = format!(
            "The correct format looks like either `#[diesel({})]` or `#[diesel({})]`",
            POSTGRES_TYPE_NOTE, POSTGRES_TYPE_NOTE_ID
        );

        if let Some((_, name)) = name {
            if let Some((oid, _)) = oid {
                abort!(
                    oid, "unexpected `oid` when `name` is present";
                    help = "{}", help
                );
            } else if let Some((array_oid, _)) = array_oid {
                abort!(
                    array_oid, "unexpected `array_oid` when `name` is present";
                    help = "{}", help
                );
            }

            Ok(PostgresType::Lookup(name, schema.map(|s| s.1)))
        } else if let Some((schema, lit)) = schema {
            abort!(
                schema, "expected `name` to be also present";
                help = "make sure `name` is present, `#[diesel(postgres_type(name = \"...\", schema = \"{}\"))]`", lit.value()
            );
        } else if let (Some((_, oid)), Some((_, array_oid))) = (oid, array_oid) {
            Ok(PostgresType::Fixed(oid, array_oid))
        } else {
            abort!(
                input.span(),
                "expected `oid` and `array_oid` attribute or `name` attribute";
                help = "{}", help
            );
        }
    }
}