diesel_derives/parsers/
postgres_type.rs

1use syn::parse::{Parse, ParseStream, Result};
2use syn::punctuated::Punctuated;
3use syn::token::Comma;
4use syn::{Ident, LitInt, LitStr};
5
6use crate::util::{parse_eq, unknown_attribute, POSTGRES_TYPE_NOTE, POSTGRES_TYPE_NOTE_ID};
7
8enum Attr {
9    Oid(Ident, LitInt),
10    ArrayOid(Ident, LitInt),
11    Name(Ident, LitStr),
12    Schema(Ident, LitStr),
13}
14
15impl Parse for Attr {
16    fn parse(input: ParseStream) -> Result<Self> {
17        let name: Ident = input.parse()?;
18        let name_str = name.to_string();
19
20        match &*name_str {
21            "oid" => Ok(Attr::Oid(name, parse_eq(input, POSTGRES_TYPE_NOTE_ID)?)),
22            "array_oid" => Ok(Attr::ArrayOid(
23                name,
24                parse_eq(input, POSTGRES_TYPE_NOTE_ID)?,
25            )),
26            "name" => Ok(Attr::Name(name, parse_eq(input, POSTGRES_TYPE_NOTE)?)),
27            "schema" => Ok(Attr::Schema(name, parse_eq(input, POSTGRES_TYPE_NOTE)?)),
28
29            _ => Err(unknown_attribute(
30                &name,
31                &["oid", "array_oid", "name", "schema"],
32            )),
33        }
34    }
35}
36
37pub enum PostgresType {
38    Fixed(LitInt, LitInt),
39    Lookup(LitStr, Option<LitStr>),
40}
41
42impl Parse for PostgresType {
43    fn parse(input: ParseStream) -> Result<Self> {
44        let mut oid = None;
45        let mut array_oid = None;
46        let mut name = None;
47        let mut schema = None;
48
49        for attr in Punctuated::<Attr, Comma>::parse_terminated(input)? {
50            match attr {
51                Attr::Oid(ident, value) => oid = Some((ident, value)),
52                Attr::ArrayOid(ident, value) => array_oid = Some((ident, value)),
53                Attr::Name(ident, value) => name = Some((ident, value)),
54                Attr::Schema(ident, value) => schema = Some((ident, value)),
55            }
56        }
57
58        Self::validate_and_build(input, oid, array_oid, name, schema)
59    }
60}
61
62impl PostgresType {
63    pub fn validate_and_build(
64        input: ParseStream,
65        oid: Option<(Ident, LitInt)>,
66        array_oid: Option<(Ident, LitInt)>,
67        name: Option<(Ident, LitStr)>,
68        schema: Option<(Ident, LitStr)>,
69    ) -> Result<Self> {
70        let help = format!(
71            "The correct format looks like either `#[diesel({POSTGRES_TYPE_NOTE})]` or `#[diesel({POSTGRES_TYPE_NOTE_ID})]`"
72        );
73
74        if let Some((_, name)) = name {
75            if let Some((oid, _)) = oid {
76                Err(syn::Error::new(
77                    oid.span(),
78                    format!("unexpected `oid` when `name` is present\nhelp: {help}"),
79                ))
80            } else if let Some((array_oid, _)) = array_oid {
81                Err(syn::Error::new(
82                    array_oid.span(),
83                    format!("unexpected `array_oid` when `name` is present\nhelp: {help}"),
84                ))
85            } else {
86                Ok(PostgresType::Lookup(name, schema.map(|s| s.1)))
87            }
88        } else if let Some((schema, lit)) = schema {
89            Err(syn::Error::new(
90                schema.span(),
91                format!(
92                    "expected `name` to be also present\n\
93                     help: make sure `name` is present, `#[diesel(postgres_type(name = \"...\", schema = \"{}\"))]`", lit.value()
94                ),
95            ))
96        } else if let (Some((_, oid)), Some((_, array_oid))) = (oid, array_oid) {
97            Ok(PostgresType::Fixed(oid, array_oid))
98        } else {
99            Err(syn::Error::new(
100                input.span(),
101                format!(
102                    "expected `oid` and `array_oid` attribute or `name` attribute\nhelp: {help}"
103                ),
104            ))
105        }
106    }
107}