#![allow(unused_imports)]
use std::collections::BTreeSet;
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};
const UPDATE_VARIABLE: &str = "OXIDEBATCH_UPDATE_FACADE_SNAPSHOT";
const OPTIONAL_FEATURE: &str = "postgres";
mod resolves {
pub use oxide_batch::{
ActorRef, AuthorizationClass, BackoffKind, BackoffOutcome, BackoffPolicy, BackoffSleeper,
BatchStatus, BeforeCallbackOutcome, BlockingTasklet, BlockingTaskletAdapter,
BlockingTaskletContext, BoxFuture, BusinessStatement, BusinessTransaction,
BusinessTransactionError, BusinessValue, BusinessValueKind, BusinessWriteResult,
Checkpoint, ChunkAttemptOutcome, ChunkCommitReceipt, ChunkCompletion,
ChunkCompletionContext, ChunkCompletionError, ChunkCompletionOutcome,
ChunkComponentRevisions, ChunkCount, ChunkCounts, ChunkDeliveryMode, ChunkError,
ChunkExecutionOutcome, ChunkExecutionReport, ChunkFailure, ChunkFaultProgress, ChunkJob,
ChunkLaunchReport, ChunkListener, ChunkListenerContext, ChunkListenerError,
ChunkListenerFailure, ChunkListenerFailureKind, ChunkListenerPhase, ChunkProgress,
ChunkRestartContract, ChunkSize, ChunkStep, ChunkTransaction, ChunkTransactionContext,
ChunkTransactionError, ChunkTransactionManager, ClassifierRevision, Clock,
CompiledExecutionPlan, ComponentRevision, Cursor, CursorError, CursorKey,
DEFAULT_DROP_REPORT_WINDOW, DEFAULT_EXPORT_QUEUE_RECORDS, DEFAULT_MAX_CLOCK_SKEW,
DEFAULT_PAGE_SIZE, DEFAULT_PURGE_AGE, DEFAULT_RETAINED_EVENT_CAPACITY,
DEFAULT_RETAINED_EVENTS_PER_EXECUTION, DEFAULT_SHUTDOWN_DEADLINE, DEFAULT_STALE_THRESHOLD,
DEFAULT_TELEMETRY_FLUSH_DEADLINE, DeciderError, DeciderRevision, DecisionInput,
DecisionInputVersion, DecisionNode, DecisionStepInput, DefinitionDescriptor,
DefinitionError, DefinitionIdentity, DefinitionManifest, DefinitionRevision,
DefinitionTokenKind, DefinitionUpgrade, DefinitionUpgradeKey, DiagnosticField, DomainError,
DrainResult, DropReportWindow, DurableStateKind, EnqueueResult, EventComponent,
EventSeverity, EventTiming, ExecutionAttempt, ExecutionContext, ExecutionControl,
ExecutionCorrelation, ExecutionCounts, ExecutionMetadata, ExecutionTimestamps,
ExecutionVersion, ExitCode, ExitPattern, ExitStatus, ExplorerError, ExplorerQuery,
ExplorerRepository, ExportError, ExportFlushReport, ExportQueueBound,
ExporterConfigurationError, FailureCategory, FailureId, FailureSummary, FaultAction,
FaultClassifier, FaultDecision, FaultDescriptor, FaultEvidence, FaultPhase, FaultPolicy,
FaultPolicyError, FaultProgress, FaultRule, FaultRuntime, FaultStateEntry,
FaultStateEnvelope, FaultStateError, FaultStateFormatError, FaultStateStore, FlowDecision,
FlowDecisionId, FlowDecisionRequest, FlowDecisionSequence, FlowEvent, FlowEventKind,
FlowEventSink, FlowExecutionOutcome, FlowFailure, FlowGraph, FlowJob, FlowJobError,
FlowLaunchReport, FlowLauncher, FlowNode, FlowRuntimeError, FlowSelectionError,
FlowStepState, FlowTarget, FlowTransition, FlowTransitionKind, IdGenerationError,
IdGenerator, IdentifierKind, InFlightPolicy, InMemoryExplorer, InMemoryFaultState,
InMemoryJobRepository, IncidentBufferConfigurationError, IncidentEventBuffer,
InheritedStepProgress, ItemListenerContext, ItemListenerError, ItemListenerFailure,
ItemListenerPhase, ItemListenerSet, ItemProcessor, ItemReader, ItemWriter, JobExecution,
JobExecutionDecider, JobExecutionId, JobExecutionListener, JobExecutionProjection,
JobExplorer, JobInstance, JobInstanceId, JobInstanceKey, JobInstanceProjection,
JobInstanceSelection, JobLauncher, JobName, JobOperator, JobParameter, JobParameters,
JobRepository, JoinNode, LaunchError, LaunchReport, LifecycleError, LifecycleEvent,
LifecycleEventKind, LifecycleEventSink, LifecycleTransition, ListenerContext,
ListenerError, ListenerFailure, ListenerFailureKind, ListenerPhase, LocalFailurePolicy,
MAX_ACTOR_REF_BYTES, MAX_BRANCH_STEPS, MAX_CLOCK_SKEW, MAX_CURSOR_BYTES,
MAX_DROP_REPORT_WINDOW, MAX_EXPORT_QUEUE_RECORDS, MAX_METRIC_NAME_ALLOWLIST, MAX_NODES,
MAX_OPERATION_ID_BYTES, MAX_OUTGOING_TRANSITIONS, MAX_PAGE_SIZE,
MAX_PARTITION_CONTEXT_BYTES, MAX_PARTITION_KEY_BYTES, MAX_PARTITION_WORKERS,
MAX_PARTITIONS, MAX_PATTERN_BYTES, MAX_PURGE_BATCH, MAX_REASON_CODE_BYTES,
MAX_RESPONSE_BYTES, MAX_RETAINED_EVENTS_PER_EXECUTION, MAX_SHUTDOWN_DEADLINE,
MAX_SPLIT_BRANCHES, MAX_STALE_THRESHOLD, MAX_TELEMETRY_FLUSH_DEADLINE, MAX_TRANSITIONS,
METRIC_CARDINALITY_BUDGET, MIN_CLOCK_SKEW, MIN_DROP_REPORT_WINDOW,
MIN_EXPORT_QUEUE_RECORDS, MIN_PURGE_AGE, MIN_SHUTDOWN_DEADLINE, MIN_STALE_THRESHOLD,
MIN_TELEMETRY_FLUSH_DEADLINE, MIN_UNRESOLVED_AGE, ManifestError, MaxClockSkew,
MetricCardinalityGuard, MetricConfigurationError, MetricDimensions, MetricFamily,
MetricLabel, MetricObservation, MetricUnit, MonotonicClock, MonotonicInstant, NameKind,
NodeId, OTHER_LABEL_VALUE, OperationId, OperatorAction, OperatorError, OperatorOutcome,
OperatorOutcomeClass, OperatorRecord, OperatorRecordDraft, OperatorRejection,
OperatorRequest, OperatorRequestId, OwnerObservation, OwnerToken, Page, PageRequest,
PageSize, ParameterDescriptor, ParameterName, ParameterRole, ParameterValue,
ParameterValueKind, PartitionAggregate, PartitionAggregationError, PartitionBudget,
PartitionCount, PartitionFactoryError, PartitionKey, PartitionPlanEntry,
PartitionPlanFactory, PartitionPlanRequest, PartitionResult, PartitionTaskletFactory,
PartitionValueError, PartitionWorkerInput, PartitionedStepNode, PatternSpecificity,
PlanError, ProcessContext, ProcessListener, ProcessOutcome, ProcessorError,
PurgeBatchBound, PurgeCandidate, PurgeCounts, PurgePlan, PurgePlanRequest, PurgeSurvey,
QueryWindow, ReadContext, ReadListener, ReadOutcome, ReaderError, ReasonCode,
RecoveryDecision, RecoveryDecisionId, RecoveryDirective, RecoveryDisposition,
RecoveryError, RecoveryEvidence, RecoveryField, RecoveryMarkers, RecoveryProposal,
RecoveryProposer, RecoveryRepository, RecoveryRequest, RecoveryRequestError,
RecoveryResult, RecoverySnapshot, RecoveryStepEvidence, RepositoryCapability,
RepositoryDescriptor, RepositoryError, RepositoryUnitOfWork, RequestDigest, RequestField,
RequestFieldError, RetentionAction, RetentionActionId, RetentionError, RetentionHold,
RetentionOutcome, RetentionRecord, RetentionRecordDraft, RetentionReport, RetentionService,
RetryCounts, RetryKey, RetryLimit, RetryListener, RetryOrdinal, RetryOutcome,
RetryReservation, RetryStateLimit, RollbackDisposition, SequentialIdGenerator,
ShutdownCoordinator, ShutdownDeadline, ShutdownError, ShutdownHookError,
ShutdownHookStatus, ShutdownReport, ShutdownRequest, ShutdownSignal, ShutdownTaskPhase,
SkipCounts, SkipLimit, SkipListener, SplitBranch, SplitBudget, SplitNode, StaleThreshold,
StartControls, StartLimit, StateCodecError, StateEnvelopeDescriptor, StateError,
StateLimits, StateSchemaId, StateSchemaUpgrade, StateSchemaVersion, StepComponents,
StepDefinitionUpgrade, StepExecution, StepExecutionId, StepExecutionListener,
StepExecutionProjection, StepName, StepNode, StepPartition, StepPartitionId,
StepPartitionProjection, StopPollInterval, StopSource, StopTiming, StopToken, SystemClock,
SystemMonotonicClock, TELEMETRY_EVENT_CATALOG, TELEMETRY_SCHEMA_VERSION,
TELEMETRY_SPAN_CATALOG, TaskJoinDeadline, Tasklet, TaskletContext, TaskletError,
TaskletExecutionOutcome, TaskletFailure, TaskletJob, TaskletOutcome, TaskletStep,
TaskletStepFactory, TelemetryEventKind, TelemetryEventSink, TelemetryExportSink,
TelemetryExporter, TelemetryFlushDeadline, TelemetryFlushStatus, TelemetryQueue,
TelemetryRecord, TelemetrySpanKind, TelemetrySpanStatus, TerminalKind, TerminalStatusSet,
UnjoinedPhase, VERSION, VersionedStateCodec, WriteContext, WriteListener, WriteOutcome,
WriterError, aggregate_step_partitions,
};
#[cfg(feature = "postgres")]
pub use oxide_batch::{
CaCertificate, PostgresChunkStateError, PostgresChunkStateProvider,
PostgresChunkTransactionManager, PostgresConfig, PostgresConfigError,
PostgresDurableStepState, PostgresExplorer, PostgresFaultState, PostgresJobRepository,
PostgresMigrator, TlsMode,
};
}
#[test]
fn public_api_snapshot_is_unchanged_by_extraction() -> Result<(), Box<dyn Error>> {
let rendered = render(&exports(&read(&facade_source())?));
let snapshot = snapshot_path();
if std::env::var_os(UPDATE_VARIABLE).is_some() {
fs::write(&snapshot, &rendered)?;
return Ok(());
}
let committed = read(&snapshot)?;
if committed == rendered {
return Ok(());
}
Err(Box::from(format!(
"the facade surface changed:\n{}\nrerun with {UPDATE_VARIABLE}=1 only \
for a reviewed API change",
difference(&committed, &rendered)
)))
}
#[test]
fn facade_import_paths_resolve_unchanged_after_each_stage() -> Result<(), Box<dyn Error>> {
let declared = exports(&read(&facade_source())?);
let imported = exports(&read(&this_source())?);
if declared == imported {
return Ok(());
}
Err(Box::from(format!(
"the resolves module and the facade disagree:\n{}",
difference(&render(&declared), &render(&imported))
)))
}
fn exports(source: &str) -> BTreeSet<String> {
let gate = format!("#[cfg(feature = \"{OPTIONAL_FEATURE}\")]");
let mut found = BTreeSet::new();
let mut lines = source.lines().map(str::trim).peekable();
let mut gated = false;
while let Some(line) = lines.next() {
if line == gate {
gated = true;
continue;
}
if let Some(rest) = line.strip_prefix("pub use ") {
let mut statement = rest.to_owned();
while !statement.ends_with(';') {
let Some(next) = lines.next() else {
break;
};
statement.push(' ');
statement.push_str(next);
}
for name in names(&statement) {
found.insert(entry(&name, gated));
}
} else if let Some(rest) = line.strip_prefix("pub const ") {
if let Some(name) = rest.split(':').next() {
found.insert(entry(name.trim(), gated));
}
} else if line.is_empty() || line.starts_with("//") {
continue;
}
gated = false;
}
found
}
fn names(statement: &str) -> Vec<String> {
let body = statement.trim_end_matches(';');
let listed = match (body.find('{'), body.rfind('}')) {
(Some(open), Some(close)) if open < close => &body[open + 1..close],
_ => match body.rsplit("::").next() {
Some(last) => last,
None => body,
},
};
listed
.split(',')
.map(str::trim)
.filter(|name| !name.is_empty())
.map(|name| match name.rsplit(" as ").next() {
Some(alias) => alias.trim().to_owned(),
None => name.to_owned(),
})
.collect()
}
fn entry(name: &str, gated: bool) -> String {
if gated {
format!("oxide_batch::{name} [{OPTIONAL_FEATURE}]")
} else {
format!("oxide_batch::{name}")
}
}
fn render(entries: &BTreeSet<String>) -> String {
let mut rendered = String::new();
for line in entries {
rendered.push_str(line);
rendered.push('\n');
}
rendered
}
fn difference(committed: &str, rendered: &str) -> String {
let before: BTreeSet<&str> = committed.lines().collect();
let after: BTreeSet<&str> = rendered.lines().collect();
let mut report = String::new();
for removed in before.difference(&after) {
report.push_str(" - ");
report.push_str(removed);
report.push('\n');
}
for added in after.difference(&before) {
report.push_str(" + ");
report.push_str(added);
report.push('\n');
}
report
}
fn read(path: &Path) -> Result<String, Box<dyn Error>> {
fs::read_to_string(path).map_err(|error| {
Box::<dyn Error>::from(format!("could not read {}: {error}", path.display()))
})
}
fn facade_source() -> PathBuf {
crate_directory().join("src").join("lib.rs")
}
fn this_source() -> PathBuf {
crate_directory().join("tests").join("facade_surface.rs")
}
fn snapshot_path() -> PathBuf {
crate_directory()
.join("tests")
.join("fixtures")
.join("facade")
.join("public-api.txt")
}
fn crate_directory() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}