use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};
mod surface {
#![allow(unused_imports)]
pub mod atomic {
pub use hayson_engine::atomic::{open_temporary, write_atomic, AtomicTemporaryFileError};
}
pub mod buffer {
pub use hayson_engine::buffer::{
apply_clean_entries, format_conflict_markers, merge, parse_buffer_entry, parse_buffer_folder,
parse_buffer_folders, resolve, serialize_buffer_entry, BufferEntry, BufferOp, BufferParseError, Conflict,
MergeResult, MergeSession, MergeSessionError, MergeSessionResult, Resolution,
};
}
pub mod catalog {
pub use hayson_engine::catalog::{
assert_catalog_readable, catalog_document, check_integrity, create_table, declare_index, declare_trigger,
drop_field, empty_catalog, expand_indexes, format_1_schema_files, migrate_format_1_to_2, parse_catalog,
write_catalog, Catalog, CatalogCompaction, CatalogFieldSpec, CatalogIndexKind, CatalogLayout,
CatalogPredicate, CatalogSyncMode, CatalogValidation, CheckEntry, ExpandedIndex, HistoryEntry, IndexEntry,
IntegrityReport, LinkParsing, OrphanRecord, Relation, RelationAction, RelationCardinality, RelationEnd,
TableEntry, TableOptions, TriggerEffect, TriggerEntry, TriggerEvent, TriggerFieldMapping,
TriggerValueSource, ViewEntry,
};
}
pub mod compaction {
pub use hayson_engine::compaction::CompactionResult;
}
pub mod crypto {
pub use hayson_engine::crypto::{CipherOptions, FieldCipher, MissingKeyPolicy};
}
pub mod database {
pub use hayson_engine::database::{
migrate_layout, Database, DatabaseError, FollowOption, Layout, LayoutKind, LayoutMigrationOptions,
LayoutMigrationResult, LayoutMove, LayoutOptions, LockMode, OpenOptions, RestoreOutcome,
DEFAULT_METADATA_FILE,
};
}
pub mod errors {
pub use hayson_engine::errors::{
HaysonCipherError, HaysonCorruptLogError, HaysonIndexMismatchError, HaysonNotFoundError,
HaysonPartitionError, HaysonPendingConflictError, HaysonQueryParseError, HaysonRelationRestrictError,
HaysonRelationViolationError, HaysonSchemaError, HaysonUniqueViolationError, HaysonValidationError,
ValidationIssue,
};
}
pub mod lock {
pub use hayson_engine::lock::{
ChallengeOutcome, DirLock, DirLockError, GraceServed, LockHolder, LockOptions, LockState, LockedError,
LOCK_FILE,
};
}
pub mod query {
pub use hayson_engine::query::{
parse_query, AccessPathSummary, AggregateResult, AggregateSpec, Combine, ComparisonOp, CorpusDelta,
GroupedAggregateResult, OrderBy, OverlayCandidates, OverlayStrategy, PlanBranch, PlanOverlay, Predicate,
PredicateGroupBuilder, PredicateNode, QueryBuilder, QueryOverlay, QueryResult, QuerySpec, QueryStrategy,
RecordSourceError, SearchMode, SearchSpec, SortDirection,
};
}
pub mod schema {
pub use hayson_engine::schema::{
is_partitioned, parse_schema, partition_of, primary_key_of, schema_document, validate_record, DefaultValue,
FieldSpec, FieldType, HaysonRecord, IndexKind, TableSchema, ValidateOptions, META_PREFIX, SCHEMA_VERSION,
UNPARTITIONED,
};
}
pub mod table {
pub use hayson_engine::table::{
BrokenLink, CachePolicy, CommitError, FollowPolicy, HistoryEntry, LogOp, PreparedTxnEntry, PutOutcome,
RefreshOutcome, RotationResult, StageError, Table, TableError, TableHistory, TableStats, TxnOp,
};
}
pub mod txn {
pub use hayson_engine::txn::{PreparedUnit, StagedView, TxnError, UnitOfWork, UnitOfWorkResult, JOURNAL_FILE};
}
}
const PUBLIC_MODULES: &[&str] = &[
"atomic",
"buffer",
"catalog",
"compaction",
"crypto",
"database",
"errors",
"lock",
"query",
"schema",
"table",
"txn",
];
const PRIVATE_MODULES: &[&str] = &["checkpoint", "queue", "registry", "storage"];
const EXPECTED_EXPORTS: &[(&str, &[&str])] = &[
(
"atomic",
&["AtomicTemporaryFileError", "open_temporary", "write_atomic"],
),
(
"buffer",
&[
"BufferEntry",
"BufferOp",
"BufferParseError",
"Conflict",
"MergeResult",
"MergeSession",
"MergeSessionError",
"MergeSessionResult",
"Resolution",
"apply_clean_entries",
"format_conflict_markers",
"merge",
"parse_buffer_entry",
"parse_buffer_folder",
"parse_buffer_folders",
"resolve",
"serialize_buffer_entry",
],
),
(
"catalog",
&[
"Catalog",
"CatalogCompaction",
"CatalogFieldSpec",
"CatalogIndexKind",
"CatalogLayout",
"CatalogPredicate",
"CatalogSyncMode",
"CatalogValidation",
"CheckEntry",
"ExpandedIndex",
"HistoryEntry",
"IndexEntry",
"IntegrityReport",
"LinkParsing",
"MigrateFormat1Error",
"MigrateOutcome",
"OrphanRecord",
"Relation",
"RelationAction",
"RelationCardinality",
"RelationEnd",
"TableEntry",
"TableOptions",
"TriggerEffect",
"TriggerEntry",
"TriggerEvent",
"TriggerFieldMapping",
"TriggerValueSource",
"ViewEntry",
"assert_catalog_readable",
"catalog_document",
"check_integrity",
"create_table",
"declare_index",
"declare_relation",
"declare_trigger",
"drop_field",
"empty_catalog",
"expand_indexes",
"format_1_schema_files",
"is_format_one_directory",
"migrate_format1",
"migrate_format_1_to_2",
"parse_catalog",
"sugar_index_name",
"write_catalog",
],
),
("compaction", &["CompactionResult"]),
(
"crypto",
&[
"CipherOptions",
"FieldCipher",
"MissingKeyPolicy",
"cypher_fields_of",
"is_envelope",
],
),
(
"database",
&[
"DEFAULT_METADATA_FILE",
"Database",
"DatabaseError",
"FollowOption",
"Layout",
"LayoutKind",
"LayoutMigrationOptions",
"LayoutMigrationResult",
"LayoutMove",
"LayoutOptions",
"LockMode",
"OpenOptions",
"RestoreOutcome",
"migrate_layout",
],
),
(
"errors",
&[
"HaysonCipherError",
"HaysonCorruptLogError",
"HaysonIndexMismatchError",
"HaysonNotFoundError",
"HaysonPartitionError",
"HaysonPendingConflictError",
"HaysonQueryParseError",
"HaysonRelationRestrictError",
"HaysonRelationViolationError",
"HaysonSchemaError",
"HaysonUniqueViolationError",
"HaysonValidationError",
"ValidationIssue",
],
),
(
"lock",
&[
"ChallengeOutcome",
"DirLock",
"DirLockError",
"GraceServed",
"LOCK_FILE",
"LockHolder",
"LockOptions",
"LockState",
"LockedError",
],
),
(
"query",
&[
"AccessPathSummary",
"AggregateResult",
"AggregateSpec",
"Combine",
"ComparisonOp",
"CorpusDelta",
"GroupedAggregateResult",
"OrderBy",
"OverlayCandidates",
"OverlayStrategy",
"PlanBranch",
"PlanOverlay",
"Predicate",
"PredicateGroupBuilder",
"PredicateNode",
"QueryBuilder",
"QueryOverlay",
"QueryResult",
"QuerySpec",
"QueryStrategy",
"RecordSourceError",
"SearchMode",
"SearchSpec",
"SortDirection",
"parse_query",
],
),
(
"schema",
&[
"DefaultValue",
"FieldSpec",
"FieldType",
"HaysonRecord",
"IndexKind",
"META_PREFIX",
"SCHEMA_VERSION",
"TableSchema",
"UNPARTITIONED",
"ValidateOptions",
"assert_partition_value",
"is_partitioned",
"parse_schema",
"partition_of",
"primary_key_of",
"schema_document",
"validate_record",
],
),
(
"table",
&[
"BrokenLink",
"CachePolicy",
"CommitError",
"FollowPolicy",
"HistoryEntry",
"LOG_SUFFIX",
"LogOp",
"PreparedTxnEntry",
"PutOutcome",
"RefreshOutcome",
"RotationResult",
"StageError",
"Table",
"TableError",
"TableHistory",
"TableStats",
"TxnOp",
"partition_names_in",
],
),
(
"txn",
&[
"JOURNAL_FILE",
"PreparedUnit",
"StagedView",
"TxnError",
"UnitOfWork",
"UnitOfWorkResult",
],
),
];
fn src_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("src")
}
fn read(path: &Path) -> String {
fs::read_to_string(path).unwrap_or_else(|error| panic!("cannot read {}: {error}", path.display()))
}
fn rust_files(dir: &Path, into: &mut Vec<PathBuf>) {
let mut entries: Vec<PathBuf> = fs::read_dir(dir)
.unwrap_or_else(|error| panic!("cannot list {}: {error}", dir.display()))
.map(|entry| entry.expect("directory entry").path())
.collect();
entries.sort();
for path in entries {
if path.is_dir() {
rust_files(&path, into);
} else if path.extension().is_some_and(|extension| extension == "rs") {
into.push(path);
}
}
}
fn module_declarations(source: &str) -> Vec<(String, &'static str)> {
let mut found = Vec::new();
for line in source.lines() {
if line.starts_with(char::is_whitespace) {
continue;
}
let (visibility, rest) = if let Some(rest) = line.strip_prefix("pub mod ") {
("pub", rest)
} else if let Some(rest) = line.strip_prefix("pub(crate) mod ") {
("pub(crate)", rest)
} else if let Some(rest) = line.strip_prefix("mod ") {
("private", rest)
} else {
continue;
};
if let Some(name) = rest.strip_suffix(';') {
found.push((name.to_string(), visibility));
}
}
found
}
fn public_exports(source: &str) -> BTreeSet<String> {
let mut names = BTreeSet::new();
let mut rest = source;
while let Some(start) = rest.find("\npub use ") {
let after = &rest[start + "\npub use ".len()..];
let end = after.find(';').expect("a `pub use` statement ends in a semicolon");
let (statement, tail) = after.split_at(end);
names.extend(exported_names(statement));
rest = tail;
}
names
}
fn exported_names(statement: &str) -> Vec<String> {
let listed = match statement.find('{') {
Some(open) => {
let close = statement.rfind('}').expect("a braced `pub use` list closes");
statement[open + 1..close].to_string()
}
None => statement.rsplit("::").next().expect("a path segment").to_string(),
};
listed
.split(',')
.map(|part| part.trim())
.filter(|part| !part.is_empty())
.map(|part| part.rsplit(" as ").next().expect("a name").trim().to_string())
.collect()
}
#[test]
fn lib_declares_exactly_the_curated_public_modules() {
let declared = module_declarations(&read(&src_dir().join("lib.rs")));
let public: Vec<&str> = declared
.iter()
.filter(|(_, visibility)| *visibility == "pub")
.map(|(name, _)| name.as_str())
.collect();
assert_eq!(public, PUBLIC_MODULES, "lib.rs public module list drifted");
let mut private: Vec<&str> = declared
.iter()
.filter(|(_, visibility)| *visibility != "pub")
.map(|(name, _)| name.as_str())
.collect();
private.sort_unstable();
assert_eq!(private, PRIVATE_MODULES, "lib.rs private module list drifted");
}
#[test]
fn no_module_below_lib_is_declared_public() {
let src = src_dir();
let mut files = Vec::new();
rust_files(&src, &mut files);
let mut leaked: Vec<String> = Vec::new();
for path in files.iter().filter(|path| !path.ends_with("lib.rs")) {
let relative = path
.strip_prefix(&src)
.expect("a path under src/")
.display()
.to_string();
for (name, _) in module_declarations(&read(path))
.into_iter()
.filter(|(_, visibility)| *visibility == "pub")
{
leaked.push(format!("{relative}: pub mod {name}"));
}
}
assert!(
leaked.is_empty(),
"a submodule became reachable from outside the crate: {leaked:?}"
);
}
#[test]
fn every_public_module_exports_exactly_its_curated_list() {
let src = src_dir();
for (module, expected) in EXPECTED_EXPORTS.iter().filter(|(module, _)| *module != "errors") {
let path = src.join(module).join("mod.rs");
let actual = public_exports(&read(&path));
let expected: BTreeSet<String> = expected.iter().map(|name| (*name).to_string()).collect();
let added: Vec<&String> = actual.difference(&expected).collect();
let removed: Vec<&String> = expected.difference(&actual).collect();
assert!(
added.is_empty() && removed.is_empty(),
"{module}/mod.rs surface drifted — added {added:?}, removed {removed:?}"
);
}
}
#[test]
fn errors_module_exports_exactly_its_curated_list() {
let source = read(&src_dir().join("errors.rs"));
let actual: BTreeSet<String> = source
.lines()
.filter_map(|line| {
let rest = line
.strip_prefix("pub struct ")
.or_else(|| line.strip_prefix("pub enum "))?;
Some(
rest.split(|c: char| !c.is_alphanumeric() && c != '_')
.next()
.expect("a type name")
.to_string(),
)
})
.collect();
let expected: BTreeSet<String> = EXPECTED_EXPORTS
.iter()
.find(|(module, _)| *module == "errors")
.expect("errors is a curated module")
.1
.iter()
.map(|name| (*name).to_string())
.collect();
assert_eq!(actual, expected, "errors.rs surface drifted");
}