ormlite_attr/
lib.rs

1#![allow(non_snake_case)]
2
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5use std::{env, fs};
6
7use anyhow::Context;
8use ignore::Walk;
9use syn::{DeriveInput, Item};
10
11use crate::derive::DeriveParser;
12use crate::repr::Repr;
13pub use error::*;
14pub use ext::*;
15pub use ident::*;
16pub use metadata::*;
17pub use ttype::*;
18
19mod cfg_attr;
20mod derive;
21mod error;
22mod ext;
23mod ident;
24mod metadata;
25mod repr;
26pub mod ttype;
27
28#[derive(Default, Debug)]
29pub struct LoadOptions {
30    pub verbose: bool,
31}
32
33/// This is an intermediate representation of the schema.
34///
35pub struct OrmliteSchema {
36    pub tables: Vec<ModelMeta>,
37    // map of rust structs (e.g. enums) to database encodings.
38    // note that these are not bona fide postgres types.
39    pub type_reprs: HashMap<String, String>,
40}
41
42struct Intermediate {
43    model_structs: Vec<syn::ItemStruct>,
44    type_structs: Vec<(syn::ItemStruct, Option<Repr>)>,
45    type_enums: Vec<(syn::ItemEnum, Option<Repr>)>,
46}
47
48impl Intermediate {
49    fn into_models_and_types(
50        self,
51    ) -> (
52        impl Iterator<Item = syn::ItemStruct>,
53        impl Iterator<Item = (String, Option<Repr>)>,
54    ) {
55        let models = self.model_structs.into_iter();
56        let types = self
57            .type_structs
58            .into_iter()
59            .map(|(s, a)| (s.ident.to_string(), a))
60            .chain(self.type_enums.into_iter().map(|(e, a)| (e.ident.to_string(), a)));
61        (models, types)
62    }
63
64    fn from_file(value: syn::File) -> Self {
65        let mut model_structs = Vec::new();
66        let mut type_structs = Vec::new();
67        let mut type_enums = Vec::new();
68        for item in value.items {
69            match item {
70                Item::Struct(s) => {
71                    let attrs = DeriveParser::from_attributes(&s.attrs);
72                    if attrs.has_derive("ormlite", "Model") {
73                        tracing::debug!(model=%s.ident.to_string(), "Found");
74                        model_structs.push(s);
75                    } else if attrs.has_any_derive(&["ormlite", "sqlx"], "Type") {
76                        tracing::debug!(r#type=%s.ident.to_string(), "Found");
77                        let repr = Repr::from_attributes(&s.attrs);
78                        type_structs.push((s, repr));
79                    } else if attrs.has_derive("ormlite", "ManualType") {
80                        tracing::debug!(r#type=%s.ident.to_string(), "Found");
81                        let repr = Repr::from_attributes(&s.attrs);
82                        type_structs.push((s, repr));
83                    }
84                }
85                Item::Enum(e) => {
86                    let attrs = DeriveParser::from_attributes(&e.attrs);
87                    if attrs.has_derive("ormlite", "Type") || attrs.has_derive("ormlite", "ManualType") {
88                        tracing::debug!(r#type=%e.ident.to_string(), "Found");
89                        let repr = Repr::from_attributes(&e.attrs);
90                        type_enums.push((e, repr));
91                    }
92                }
93                _ => {}
94            }
95        }
96        Self {
97            model_structs,
98            type_structs,
99            type_enums,
100        }
101    }
102}
103
104pub fn schema_from_filepaths(paths: &[&Path]) -> anyhow::Result<OrmliteSchema> {
105    let cwd = env::var("CARGO_RUSTC_CURRENT_DIR")
106        .or_else(|_| env::var("CARGO_MANIFEST_DIR"))
107        .map(PathBuf::from)
108        .or_else(|_| env::current_dir())
109        .expect("Failed to get current directory for schema");
110
111    let paths = paths.iter().map(|p| cwd.join(p)).collect::<Vec<_>>();
112    let invalid_paths = paths.iter().filter(|p| fs::metadata(p).is_err()).collect::<Vec<_>>();
113    if !invalid_paths.is_empty() {
114        for path in &invalid_paths {
115            tracing::error!(path = path.display().to_string(), "Does not exist");
116        }
117        let paths = invalid_paths
118            .iter()
119            .map(|p| p.display().to_string())
120            .collect::<Vec<_>>()
121            .join(", ");
122        anyhow::bail!("Provided paths that did not exist: {}", paths);
123    }
124
125    let walk = paths.iter().flat_map(Walk::new);
126
127    let walk = walk
128        .map(|e| e.unwrap())
129        .filter(|e| e.path().extension().map(|e| e == "rs").unwrap_or(false))
130        .map(|e| e.into_path())
131        .chain(paths.iter().filter(|p| p.ends_with(".rs")).map(|p| p.to_path_buf()));
132
133    let mut tables = vec![];
134    let mut type_aliases = HashMap::new();
135    for entry in walk {
136        let contents = fs::read_to_string(&entry).context(format!("failed to read file: {}", entry.display()))?;
137        tracing::debug!(
138            file = entry.display().to_string(),
139            "Checking for Model, Type, ManualType derive attrs"
140        );
141        if !(contents.contains("Model") || contents.contains("Type") || contents.contains("ManualType")) {
142            continue;
143        }
144        let ast = syn::parse_file(&contents).context(format!("Failed to parse file: {}", entry.display()))?;
145        let intermediate = Intermediate::from_file(ast);
146        let (models, types) = intermediate.into_models_and_types();
147
148        for item in models {
149            let derive: DeriveInput = item.into();
150            tables.push(ModelMeta::from_derive(&derive));
151        }
152
153        for (name, repr) in types {
154            let ty = repr.map(|s| s.to_string()).unwrap_or_else(|| "String".to_string());
155            type_aliases.insert(name, ty);
156        }
157    }
158    Ok(OrmliteSchema {
159        tables,
160        type_reprs: type_aliases,
161    })
162}