ormlite_attr/
lib.rs

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#![allow(non_snake_case)]

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::{env, fs};

use anyhow::Context;
use ignore::Walk;
use syn::{DeriveInput, Item};

use crate::derive::DeriveParser;
use crate::repr::Repr;
pub use error::*;
pub use ext::*;
pub use ident::*;
pub use metadata::*;
pub use ttype::*;

mod cfg_attr;
mod derive;
mod error;
mod ext;
mod ident;
mod metadata;
mod repr;
pub mod ttype;

#[derive(Default, Debug)]
pub struct LoadOptions {
    pub verbose: bool,
}

/// This is an intermediate representation of the schema.
///
pub struct OrmliteSchema {
    pub tables: Vec<ModelMeta>,
    // map of rust structs (e.g. enums) to database encodings.
    // note that these are not bona fide postgres types.
    pub type_reprs: HashMap<String, String>,
}

struct Intermediate {
    model_structs: Vec<syn::ItemStruct>,
    type_structs: Vec<(syn::ItemStruct, Option<Repr>)>,
    type_enums: Vec<(syn::ItemEnum, Option<Repr>)>,
}

impl Intermediate {
    fn into_models_and_types(
        self,
    ) -> (
        impl Iterator<Item = syn::ItemStruct>,
        impl Iterator<Item = (String, Option<Repr>)>,
    ) {
        let models = self.model_structs.into_iter();
        let types = self
            .type_structs
            .into_iter()
            .map(|(s, a)| (s.ident.to_string(), a))
            .chain(self.type_enums.into_iter().map(|(e, a)| (e.ident.to_string(), a)));
        (models, types)
    }

    fn from_file(value: syn::File) -> Self {
        let mut model_structs = Vec::new();
        let mut type_structs = Vec::new();
        let mut type_enums = Vec::new();
        for item in value.items {
            match item {
                Item::Struct(s) => {
                    let attrs = DeriveParser::from_attributes(&s.attrs);
                    if attrs.has_derive("ormlite", "Model") {
                        tracing::debug!(model=%s.ident.to_string(), "Found");
                        model_structs.push(s);
                    } else if attrs.has_any_derive(&["ormlite", "sqlx"], "Type") {
                        tracing::debug!(r#type=%s.ident.to_string(), "Found");
                        let repr = Repr::from_attributes(&s.attrs);
                        type_structs.push((s, repr));
                    } else if attrs.has_derive("ormlite", "ManualType") {
                        tracing::debug!(r#type=%s.ident.to_string(), "Found");
                        let repr = Repr::from_attributes(&s.attrs);
                        type_structs.push((s, repr));
                    }
                }
                Item::Enum(e) => {
                    let attrs = DeriveParser::from_attributes(&e.attrs);
                    if attrs.has_derive("ormlite", "Type") || attrs.has_derive("ormlite", "ManualType") {
                        tracing::debug!(r#type=%e.ident.to_string(), "Found");
                        let repr = Repr::from_attributes(&e.attrs);
                        type_enums.push((e, repr));
                    }
                }
                _ => {}
            }
        }
        Self {
            model_structs,
            type_structs,
            type_enums,
        }
    }
}

pub fn schema_from_filepaths(paths: &[&Path]) -> anyhow::Result<OrmliteSchema> {
    let cwd = env::var("CARGO_MANIFEST_DIR")
        .map(PathBuf::from)
        .or_else(|_| env::current_dir())
        .expect("Failed to get current directory for schema");
    let paths = paths.iter().map(|p| cwd.join(p)).collect::<Vec<_>>();
    let invalid_paths = paths.iter().filter(|p| fs::metadata(p).is_err()).collect::<Vec<_>>();
    if !invalid_paths.is_empty() {
        for path in &invalid_paths {
            tracing::error!(path = path.display().to_string(), "Does not exist");
        }
        let paths = invalid_paths
            .iter()
            .map(|p| p.display().to_string())
            .collect::<Vec<_>>()
            .join(", ");
        anyhow::bail!("Provided paths that did not exist: {}", paths);
    }

    let walk = paths.iter().flat_map(Walk::new);

    let walk = walk
        .map(|e| e.unwrap())
        .filter(|e| e.path().extension().map(|e| e == "rs").unwrap_or(false))
        .map(|e| e.into_path())
        .chain(paths.iter().filter(|p| p.ends_with(".rs")).map(|p| p.to_path_buf()));

    let mut tables = vec![];
    let mut type_aliases = HashMap::new();
    for entry in walk {
        let contents = fs::read_to_string(&entry).context(format!("failed to read file: {}", entry.display()))?;
        tracing::debug!(
            file = entry.display().to_string(),
            "Checking for Model, Type, ManualType derive attrs"
        );
        if !(contents.contains("Model") || contents.contains("Type") || contents.contains("ManualType")) {
            continue;
        }
        let ast = syn::parse_file(&contents).context(format!("Failed to parse file: {}", entry.display()))?;
        let intermediate = Intermediate::from_file(ast);
        let (models, types) = intermediate.into_models_and_types();

        for item in models {
            let derive: DeriveInput = item.into();
            tables.push(ModelMeta::from_derive(&derive));
        }

        for (name, repr) in types {
            let ty = repr.map(|s| s.to_string()).unwrap_or_else(|| "String".to_string());
            type_aliases.insert(name, ty);
        }
    }
    Ok(OrmliteSchema {
        tables,
        type_reprs: type_aliases,
    })
}