mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
Add multi-database data layer
Introduce aether-data-schema and driver-specific schema generation for Postgres, MySQL, and SQLite. Split data backends, lifecycle, repositories, and gateway runtime integration across database drivers. Verified with cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, and cargo test --workspace.
This commit is contained in:
244
crates/aether-data/tests/public_entrypoints.rs
Normal file
244
crates/aether-data/tests/public_entrypoints.rs
Normal file
@@ -0,0 +1,244 @@
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
const OLD_ENTRYPOINTS: &[&str] = &[
|
||||
"backends", "backfill", "export", "migrate", "mysql", "postgres", "redis", "sqlite",
|
||||
];
|
||||
const SELF_TEST_PATH: &str = "crates/aether-data/tests/public_entrypoints.rs";
|
||||
|
||||
#[test]
|
||||
fn deprecated_aether_data_entrypoints_are_not_exposed_or_used() {
|
||||
let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.and_then(Path::parent)
|
||||
.expect("aether-data should live under crates/");
|
||||
let mut violations = Vec::new();
|
||||
|
||||
for relative_dir in ["apps", "crates"] {
|
||||
scan_rust_files(
|
||||
&workspace_root.join(relative_dir),
|
||||
workspace_root,
|
||||
&mut violations,
|
||||
);
|
||||
}
|
||||
|
||||
assert!(
|
||||
violations.is_empty(),
|
||||
"deprecated aether-data entrypoints should not be exposed or used inside the workspace:\n{}",
|
||||
violations.join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
fn scan_rust_files(dir: &Path, workspace_root: &Path, violations: &mut Vec<String>) {
|
||||
let entries = fs::read_dir(dir).unwrap_or_else(|err| panic!("failed to read {dir:?}: {err}"));
|
||||
for entry in entries {
|
||||
let path = entry
|
||||
.unwrap_or_else(|err| panic!("failed to read entry under {dir:?}: {err}"))
|
||||
.path();
|
||||
|
||||
if path.is_dir() {
|
||||
scan_rust_files(&path, workspace_root, violations);
|
||||
continue;
|
||||
}
|
||||
if path.extension().and_then(|ext| ext.to_str()) != Some("rs") {
|
||||
continue;
|
||||
}
|
||||
if should_skip_file(&path, workspace_root) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let source = fs::read_to_string(&path)
|
||||
.unwrap_or_else(|err| panic!("failed to read {path:?}: {err}"));
|
||||
collect_file_violations(&path, workspace_root, &source, violations);
|
||||
}
|
||||
}
|
||||
|
||||
fn should_skip_file(path: &Path, workspace_root: &Path) -> bool {
|
||||
let relative = relative_path(path, workspace_root);
|
||||
relative == Path::new(SELF_TEST_PATH)
|
||||
}
|
||||
|
||||
fn collect_file_violations(
|
||||
path: &Path,
|
||||
workspace_root: &Path,
|
||||
source: &str,
|
||||
violations: &mut Vec<String>,
|
||||
) {
|
||||
let relative = relative_path(path, workspace_root);
|
||||
|
||||
collect_alias_violations(&relative, source, "aether_data", violations);
|
||||
|
||||
if relative.starts_with("crates/aether-data/src") {
|
||||
collect_alias_violations(&relative, source, "crate", violations);
|
||||
if relative == Path::new("crates/aether-data/src/lib.rs") {
|
||||
collect_public_module_violations(&relative, source, violations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_alias_violations(
|
||||
relative: &Path,
|
||||
source: &str,
|
||||
prefix: &str,
|
||||
violations: &mut Vec<String>,
|
||||
) {
|
||||
for entrypoint in OLD_ENTRYPOINTS {
|
||||
let direct = format!("{prefix}::{entrypoint}");
|
||||
for (byte_index, _) in source.match_indices(&direct) {
|
||||
push_violation(relative, source, byte_index, &direct, violations);
|
||||
}
|
||||
}
|
||||
|
||||
collect_grouped_alias_violations(relative, source, prefix, violations);
|
||||
}
|
||||
|
||||
fn collect_grouped_alias_violations(
|
||||
relative: &Path,
|
||||
source: &str,
|
||||
prefix: &str,
|
||||
violations: &mut Vec<String>,
|
||||
) {
|
||||
let marker = format!("{prefix}::{{");
|
||||
let mut offset = 0;
|
||||
|
||||
while let Some(local_start) = source[offset..].find(&marker) {
|
||||
let marker_start = offset + local_start;
|
||||
let body_start = marker_start + marker.len();
|
||||
let Some((body_end, body)) = grouped_import_body(source, body_start) else {
|
||||
break;
|
||||
};
|
||||
|
||||
for segment in top_level_import_segments(body) {
|
||||
for entrypoint in OLD_ENTRYPOINTS {
|
||||
if import_matches(segment, entrypoint) {
|
||||
let imported = format!("{prefix}::{entrypoint}");
|
||||
push_violation(relative, source, marker_start, &imported, violations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset = body_end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_public_module_violations(relative: &Path, source: &str, violations: &mut Vec<String>) {
|
||||
for entrypoint in OLD_ENTRYPOINTS {
|
||||
for marker in [format!("pub mod {entrypoint}"), format!("mod {entrypoint}")] {
|
||||
for (byte_index, _) in source.match_indices(&marker) {
|
||||
push_violation(relative, source, byte_index, &marker, violations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn grouped_import_body(source: &str, body_start: usize) -> Option<(usize, &str)> {
|
||||
let mut depth = 1usize;
|
||||
for (relative_index, ch) in source[body_start..].char_indices() {
|
||||
match ch {
|
||||
'{' => depth += 1,
|
||||
'}' => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
let body_end = body_start + relative_index;
|
||||
return Some((body_end, &source[body_start..body_end]));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn top_level_import_segments(body: &str) -> Vec<&str> {
|
||||
let mut segments = Vec::new();
|
||||
let mut depth = 0usize;
|
||||
let mut segment_start = 0usize;
|
||||
|
||||
for (index, ch) in body.char_indices() {
|
||||
match ch {
|
||||
'{' => depth += 1,
|
||||
'}' => depth = depth.saturating_sub(1),
|
||||
',' if depth == 0 => {
|
||||
segments.push(&body[segment_start..index]);
|
||||
segment_start = index + ch.len_utf8();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
segments.push(&body[segment_start..]);
|
||||
segments
|
||||
}
|
||||
|
||||
fn import_matches(part: &str, entrypoint: &str) -> bool {
|
||||
let trimmed = part.trim();
|
||||
trimmed == entrypoint
|
||||
|| trimmed.strip_prefix(entrypoint).is_some_and(|suffix| {
|
||||
suffix.starts_with("::") || suffix.trim_start().starts_with("as ")
|
||||
})
|
||||
}
|
||||
|
||||
fn push_violation(
|
||||
relative: &Path,
|
||||
source: &str,
|
||||
byte_index: usize,
|
||||
imported: &str,
|
||||
violations: &mut Vec<String>,
|
||||
) {
|
||||
violations.push(format!(
|
||||
"{}:{}: deprecated {imported}",
|
||||
relative.display(),
|
||||
line_number(source, byte_index)
|
||||
));
|
||||
}
|
||||
|
||||
fn line_number(source: &str, byte_index: usize) -> usize {
|
||||
source[..byte_index]
|
||||
.bytes()
|
||||
.filter(|byte| *byte == b'\n')
|
||||
.count()
|
||||
+ 1
|
||||
}
|
||||
|
||||
fn relative_path(path: &Path, workspace_root: &Path) -> PathBuf {
|
||||
path.strip_prefix(workspace_root)
|
||||
.unwrap_or(path)
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grouped_import_scanner_allows_nested_new_paths() {
|
||||
let source = r#"
|
||||
use aether_data::{
|
||||
driver::{postgres::PostgresPool, redis::RedisKvRunner},
|
||||
lifecycle::{backfill::PendingBackfillInfo, migrate::PendingMigrationInfo},
|
||||
};
|
||||
"#;
|
||||
let mut violations = Vec::new();
|
||||
collect_alias_violations(
|
||||
Path::new("crates/example/src/lib.rs"),
|
||||
source,
|
||||
"aether_data",
|
||||
&mut violations,
|
||||
);
|
||||
assert!(violations.is_empty(), "{violations:#?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grouped_import_scanner_rejects_top_level_old_paths() {
|
||||
let source = r#"
|
||||
use aether_data::{
|
||||
postgres::PostgresPool,
|
||||
redis::{RedisKvRunner, RedisLockRunner},
|
||||
};
|
||||
"#;
|
||||
let mut violations = Vec::new();
|
||||
collect_alias_violations(
|
||||
Path::new("crates/example/src/lib.rs"),
|
||||
source,
|
||||
"aether_data",
|
||||
&mut violations,
|
||||
);
|
||||
assert_eq!(violations.len(), 2, "{violations:#?}");
|
||||
}
|
||||
Reference in New Issue
Block a user