Skip to content

Commit 38ac99c

Browse files
committed
Add support for type mapping
Similar to Kanel's customTypeMap. One use case for this is allowing one to treat PG extension types as some other types that they behave as (e.g., map public.ulid to String). Fixes #246
1 parent d1229ae commit 38ac99c

File tree

10 files changed

+165
-39
lines changed

10 files changed

+165
-39
lines changed

benches/codegen.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ fn bench(c: &mut Criterion) {
1717
gen_sync: true,
1818
gen_async: false,
1919
derive_ser: true,
20+
config: Default::default(),
2021
},
2122
)
2223
.unwrap()
@@ -32,6 +33,7 @@ fn bench(c: &mut Criterion) {
3233
gen_sync: true,
3334
gen_async: false,
3435
derive_ser: true,
36+
config: Default::default(),
3537
},
3638
)
3739
.unwrap()

benches/execution/diesel_benches.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,7 @@ pub fn bench_insert(b: &mut Bencher, conn: &mut PgConnection, size: usize) {
164164
};
165165
let insert = &insert;
166166

167-
b.iter(|| {
168-
let insert = insert;
169-
insert(conn)
170-
})
167+
b.iter(|| insert(conn))
171168
}
172169

173170
pub fn loading_associations_sequentially(b: &mut Bencher, conn: &mut PgConnection) {

crates/cornucopia/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,7 @@ heck = "0.4.0"
3333

3434
# Order-preserving map to work around borrowing issues
3535
indexmap = "2.0.2"
36+
37+
# Config handling
38+
serde = { version = "1.0.203", features = ["derive"] }
39+
toml = "0.8.14"

crates/cornucopia/src/cli.rs

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
use std::path::PathBuf;
1+
use miette::Diagnostic;
2+
use std::{fs, path::PathBuf};
3+
use thiserror::Error as ThisError;
24

35
use clap::{Parser, Subcommand};
46

5-
use crate::{conn, container, error::Error, generate_live, generate_managed, CodegenSettings};
7+
use crate::{
8+
config::Config, conn, container, error::Error, generate_live, generate_managed, CodegenSettings,
9+
};
610

711
/// Command line interface to interact with Cornucopia SQL.
812
#[derive(Parser, Debug)]
@@ -28,6 +32,13 @@ struct Args {
2832
/// Derive serde's `Serialize` trait for generated types.
2933
#[clap(long)]
3034
serialize: bool,
35+
/// The location of the configuration file.
36+
#[clap(short, long, default_value = default_config_path())]
37+
config: PathBuf,
38+
}
39+
40+
const fn default_config_path() -> &'static str {
41+
"cornucopia.toml"
3142
}
3243

3344
#[derive(Debug, Subcommand)]
@@ -44,8 +55,26 @@ enum Action {
4455
},
4556
}
4657

58+
/// Enumeration of the errors reported by the CLI.
59+
#[derive(ThisError, Debug, Diagnostic)]
60+
pub enum CliError {
61+
/// An error occurred while loading the configuration file.
62+
#[error("Could not load config `{path}`: ({err})")]
63+
MissingConfig { path: String, err: std::io::Error },
64+
/// An error occurred while parsing the configuration file.
65+
#[error("Could not parse config `{path}`: ({err})")]
66+
ConfigContents {
67+
path: String,
68+
err: Box<dyn std::error::Error + Send + Sync>,
69+
},
70+
/// An error occurred while running the CLI.
71+
#[error(transparent)]
72+
#[diagnostic(transparent)]
73+
Internal(#[from] Error),
74+
}
75+
4776
// Main entrypoint of the CLI. Parses the args and calls the appropriate routines.
48-
pub fn run() -> Result<(), Error> {
77+
pub fn run() -> Result<(), CliError> {
4978
let Args {
5079
podman,
5180
queries_path,
@@ -54,17 +83,40 @@ pub fn run() -> Result<(), Error> {
5483
sync,
5584
r#async,
5685
serialize,
86+
config,
5787
} = Args::parse();
5888

89+
let config = match fs::read_to_string(config.as_path()) {
90+
Ok(contents) => match toml::from_str(&contents) {
91+
Ok(config) => config,
92+
Err(err) => {
93+
return Err(CliError::ConfigContents {
94+
path: config.to_string_lossy().into_owned(),
95+
err: err.into(),
96+
});
97+
}
98+
},
99+
Err(err) => {
100+
if config.as_path().as_os_str() != default_config_path() {
101+
return Err(CliError::MissingConfig {
102+
path: config.to_string_lossy().into_owned(),
103+
err,
104+
});
105+
} else {
106+
Config::default()
107+
}
108+
}
109+
};
59110
let settings = CodegenSettings {
60111
gen_async: r#async || !sync,
61112
gen_sync: sync,
62113
derive_ser: serialize,
114+
config,
63115
};
64116

65117
match action {
66118
Action::Live { url } => {
67-
let mut client = conn::from_url(&url)?;
119+
let mut client = conn::from_url(&url).map_err(|e| CliError::Internal(e.into()))?;
68120
generate_live(&mut client, &queries_path, Some(&destination), settings)?;
69121
}
70122
Action::Schema { schema_files } => {
@@ -77,7 +129,7 @@ pub fn run() -> Result<(), Error> {
77129
settings,
78130
) {
79131
container::cleanup(podman).ok();
80-
return Err(e);
132+
return Err(CliError::Internal(e));
81133
}
82134
}
83135
};

crates/cornucopia/src/config.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//! Configuration for Cornucopia.
2+
3+
use std::collections::HashMap;
4+
5+
use serde::Deserialize;
6+
7+
/// Configuration for Cornucopia.
8+
#[derive(Clone, Deserialize, Default, Debug)]
9+
pub struct Config {
10+
/// Contains a map of what given type should map to.
11+
pub custom_type_map: HashMap<String, String>,
12+
}

crates/cornucopia/src/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod cli;
22
mod codegen;
3+
mod config;
34
mod error;
45
mod load_schema;
56
mod parser;
@@ -16,6 +17,7 @@ pub mod container;
1617

1718
use std::path::Path;
1819

20+
use config::Config;
1921
use postgres::Client;
2022

2123
use codegen::generate as generate_internal;
@@ -31,11 +33,12 @@ pub use error::Error;
3133
pub use load_schema::load_schema;
3234

3335
/// Struct containing the settings for code generation.
34-
#[derive(Clone, Copy)]
36+
#[derive(Clone)]
3537
pub struct CodegenSettings {
3638
pub gen_async: bool,
3739
pub gen_sync: bool,
3840
pub derive_ser: bool,
41+
pub config: Config,
3942
}
4043

4144
/// Generates Rust queries from PostgreSQL queries located at `queries_path`,
@@ -54,7 +57,7 @@ pub fn generate_live<P: AsRef<Path>>(
5457
.map(parse_query_module)
5558
.collect::<Result<_, parser::error::Error>>()?;
5659
// Generate
57-
let prepared_modules = prepare(client, modules)?;
60+
let prepared_modules = prepare(client, modules, settings.clone())?;
5861
let generated_code = generate_internal(prepared_modules, settings);
5962
// Write
6063
if let Some(d) = destination {
@@ -86,7 +89,7 @@ pub fn generate_managed<P: AsRef<Path>>(
8689
container::setup(podman)?;
8790
let mut client = conn::cornucopia_conn()?;
8891
load_schema(&mut client, schema_files)?;
89-
let prepared_modules = prepare(&mut client, modules)?;
92+
let prepared_modules = prepare(&mut client, modules, settings.clone())?;
9093
let generated_code = generate_internal(prepared_modules, settings);
9194
container::cleanup(podman)?;
9295

crates/cornucopia/src/prepare_queries.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ use crate::{
99
codegen::GenCtx,
1010
parser::{Module, NullableIdent, Query, Span, TypeAnnotation},
1111
read_queries::ModuleInfo,
12-
type_registrar::CornucopiaType,
13-
type_registrar::TypeRegistrar,
12+
type_registrar::{CornucopiaType, TypeRegistrar},
1413
utils::KEYWORD,
15-
validation,
14+
validation, CodegenSettings,
1615
};
1716

1817
use self::error::Error;
@@ -226,8 +225,12 @@ impl PreparedModule {
226225
}
227226

228227
/// Prepares all modules
229-
pub(crate) fn prepare(client: &mut Client, modules: Vec<Module>) -> Result<Preparation, Error> {
230-
let mut registrar = TypeRegistrar::default();
228+
pub(crate) fn prepare(
229+
client: &mut Client,
230+
modules: Vec<Module>,
231+
settings: CodegenSettings,
232+
) -> Result<Preparation, Error> {
233+
let mut registrar = TypeRegistrar::new(settings.config.custom_type_map);
231234
let mut tmp = Preparation {
232235
modules: Vec::new(),
233236
types: IndexMap::new(),
@@ -244,16 +247,12 @@ pub(crate) fn prepare(client: &mut Client, modules: Vec<Module>) -> Result<Prepa
244247
}
245248

246249
// Prepare types grouped by schema
247-
for ((schema, name), ty) in &registrar.types {
248-
if let Some(ty) = prepare_type(&registrar, name, ty, &declared) {
249-
match tmp.types.entry(schema.clone()) {
250-
Entry::Occupied(mut entry) => {
251-
entry.get_mut().push(ty);
252-
}
253-
Entry::Vacant(entry) => {
254-
entry.insert(vec![ty]);
255-
}
256-
}
250+
for (schema_key, ty) in registrar.types() {
251+
if let Some(ty) = prepare_type(&registrar, schema_key.name, ty, &declared) {
252+
tmp.types
253+
.entry(schema_key.schema.to_owned())
254+
.or_default()
255+
.push(ty);
257256
}
258257
}
259258
Ok(tmp)
@@ -301,7 +300,9 @@ fn prepare_type(
301300
})
302301
.collect(),
303302
),
304-
_ => unreachable!(),
303+
_ => {
304+
return None;
305+
}
305306
};
306307
Some(PreparedType {
307308
name: name.to_string(),

0 commit comments

Comments
 (0)