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
33pub struct OrmliteSchema {
36 pub tables: Vec<ModelMeta>,
37 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}