Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion benches/generated/Cargo.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion docs/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ name = "furinapp-queries"
version = "1.0.0"
description = "Today I wanted to eat a *quaso*."
license = "MIT"
edition = "2021"

[manifest.dependencies]
serde = { version = "1.0", features = ["derive"] }
Expand All @@ -89,6 +88,9 @@ my_custom_types = { path = "../types" }

This gives you complete control over the generated Cargo.toml. Cornucopia will automatically merge your configuration with the required PostgreSQL dependencies based on the types found in your SQL queries.

### Rust edition and version
The `edition` and `rust-version` values on the generated crate are fixed by Cornucopia since they depend on the generated code itself. Setting these values in `[manifest.package]` has no effect.

### Dependency merging
Cornucopia automatically adds dependencies based on your PostgreSQL schema:
- Core dependencies: `postgres-types`, `postgres-protocol`, `postgres`
Expand Down
3 changes: 2 additions & 1 deletion examples/auto_build/auto_build_codegen/Cargo.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion examples/basic_async/basic_async_codegen/Cargo.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion examples/basic_sync/basic_sync_codegen/Cargo.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion examples/custom_types/db/custom_types_codegen/Cargo.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 71 additions & 22 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use std::{
str::FromStr,
};

use crate::error::Warning;

#[derive(Debug, Deserialize, Clone)]
#[serde(default, deny_unknown_fields)]
#[non_exhaustive]
Expand Down Expand Up @@ -57,15 +59,10 @@ impl Config {
let contents = fs::read_to_string(path)?;
let mut config: Config = toml::from_str(&contents)?;

if config.manifest.package.is_none() {
config.manifest.package = default_manifest().package;
}
warn_on_ignored_package_fields(&contents);

if let Some(manifest) = &mut config.manifest.package
&& manifest.edition == cargo_toml::Inheritable::Set(cargo_toml::Edition::E2015)
{
manifest.edition = cargo_toml::Inheritable::Set(cargo_toml::Edition::E2021);
}
let package = config.manifest.package.get_or_insert_with(default_package);
enforce_cornucopia_controlled_fields(package);

config.check_deprecated_fields();

Expand Down Expand Up @@ -265,14 +262,42 @@ impl TypeMapping {
}
}

#[allow(deprecated)]
fn default_manifest() -> cargo_toml::Manifest {
/// Overwrite the fields on the generated crate's `[package]` that are
/// determined by cornucopia rather than the user.
fn enforce_cornucopia_controlled_fields(package: &mut cargo_toml::Package) {
package.edition = cargo_toml::Inheritable::Set(cargo_toml::Edition::E2024);
package.rust_version = Some(cargo_toml::Inheritable::Set("1.85".into()));
}

/// Warn when the user has set fields in `[manifest.package]` that cornucopia
/// will silently override. We re-parse the raw TOML because the deserialized
/// `cargo_toml` types fill in defaults that look identical to explicit values.
fn warn_on_ignored_package_fields(contents: &str) {
let Ok(raw) = toml::from_str::<toml::Value>(contents) else {
return;
};
let Some(package) = raw.get("manifest").and_then(|m| m.get("package")) else {
return;
};
if package.get("edition").is_some() {
Warning::IgnoredManifestEdition.emit();
}
if package.get("rust-version").is_some() {
Warning::IgnoredManifestRustVersion.emit();
}
}

fn default_package() -> cargo_toml::Package {
let mut package = cargo_toml::Package::new("cornucopia", "0.0.0");
package.edition = cargo_toml::Inheritable::Set(cargo_toml::Edition::E2021);
enforce_cornucopia_controlled_fields(&mut package);
package.publish = cargo_toml::Inheritable::Set(cargo_toml::Publish::Flag(false));
package
}

#[allow(deprecated)]
fn default_manifest() -> cargo_toml::Manifest {
cargo_toml::Manifest {
package: Some(package),
package: Some(default_package()),
workspace: None,
dependencies: Default::default(),
dev_dependencies: Default::default(),
Expand Down Expand Up @@ -333,14 +358,12 @@ impl ConfigBuilder {

/// Set just the package name, keeping other package defaults
pub fn name(mut self, name: impl Into<String>) -> Self {
if let Some(package) = &mut self.config.manifest.package {
package.name = name.into();
} else {
let mut package = cargo_toml::Package::new(name.into(), "0.1.0");
package.edition = cargo_toml::Inheritable::Set(cargo_toml::Edition::E2021);
package.publish = cargo_toml::Inheritable::Set(cargo_toml::Publish::Flag(false));
self.config.manifest.package = Some(package);
}
let package = self.config.manifest.package.get_or_insert_with(|| {
let mut package = default_package();
package.version = cargo_toml::Inheritable::Set("0.1.0".to_string());
package
});
package.name = name.into();
self
}

Expand Down Expand Up @@ -551,14 +574,13 @@ version = "0.2"
}

#[test]
fn explicit_manifest_package_is_respected() {
fn user_controlled_package_fields_are_respected() {
let toml_content = r#"
queries = "db/queries"

[manifest.package]
name = "custom-name"
version = "1.0.0"
edition = "2021"
publish = false
"#;

Expand All @@ -574,4 +596,31 @@ publish = false
assert_eq!(package.name, "custom-name");
assert_eq!(package.version(), "1.0.0");
}

#[test]
fn cornucopia_controlled_package_fields_are_overridden() {
let toml_content = r#"
queries = "db/queries"

[manifest.package]
name = "custom-name"
edition = "2021"
rust-version = "1.70"
"#;

let mut tmpfile = tempfile::NamedTempFile::new().unwrap();
tmpfile.write_all(toml_content.as_bytes()).unwrap();

let config = Config::from_file(tmpfile.path()).unwrap();

let package = config
.manifest
.package
.expect("package section should exist");
assert_eq!(
package.edition,
cargo_toml::Inheritable::Set(cargo_toml::Edition::E2024)
);
assert_eq!(package.rust_version(), Some("1.85"));
}
}
18 changes: 18 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ pub(crate) enum Warning {
)
)]
NoQueries,
/// User set `manifest.package.edition`, which cornucopia controls.
#[error("`manifest.package.edition` is ignored")]
#[diagnostic(
severity(Warning),
help(
"Cornucopia controls the edition of the generated crate because the emitted code's syntax is tied to it. Remove this key from your config."
)
)]
IgnoredManifestEdition,
/// User set `manifest.package.rust-version`, which cornucopia controls.
#[error("`manifest.package.rust-version` is ignored")]
#[diagnostic(
severity(Warning),
help(
"Cornucopia controls the MSRV of the generated crate because it is tied to the edition cornucopia emits for. Remove this key from your config."
)
)]
IgnoredManifestRustVersion,
}

impl Warning {
Expand Down
8 changes: 4 additions & 4 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ pub(crate) fn db_err(err: &tokio_postgres::Error) -> Option<(u32, String, Option
pub(crate) const STRICT_KEYWORD: [&str; 5] = ["Self", "_", "crate", "self", "super"];

/// Sorted list of rust reserved keywords
pub(crate) const KEYWORD: [&str; 53] = [
pub(crate) const KEYWORD: [&str; 54] = [
"Self", "_", "abstract", "as", "async", "await", "become", "box", "break", "const", "continue",
"crate", "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl",
"in", "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub", "ref",
"return", "self", "static", "struct", "super", "trait", "true", "try", "type", "typeof",
"crate", "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "gen", "if",
"impl", "in", "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub",
"ref", "return", "self", "static", "struct", "super", "trait", "true", "try", "type", "typeof",
"union", "unsafe", "unsized", "use", "virtual", "where", "while", "yield",
];
3 changes: 2 additions & 1 deletion tests/codegen/codegen/Cargo.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions tests/codegen/codegen/src/types.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion tests/codegen/cornucopia.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ async = true
[manifest.package]
name = "codegen"
version = "0.0.0"
edition = "2021"
publish = false

[manifest.dependencies]
Expand Down
3 changes: 2 additions & 1 deletion tests/codegen/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ CREATE TABLE schema.nightmare (
-- Syntax

CREATE TYPE syntax_composite AS (
async INT
async INT,
gen INT
);
CREATE TYPE syntax_enum AS Enum('async', 'box', 'I Love Chocolate');
CREATE TABLE Syntax (
Expand Down
5 changes: 4 additions & 1 deletion tests/codegen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,10 @@ pub fn test_stress(client: &mut Client) {
// Test keyword escaping
pub fn test_keyword_escaping(client: &mut Client) {
let params = TrickySql10Params {
r#async: SyntaxComposite { r#async: 34 },
r#async: SyntaxComposite {
r#async: 34,
r#gen: 42,
},
r#enum: SyntaxEnum::r#box,
};
tricky_sql10().params(client, &params).unwrap();
Expand Down