Skip to main content

alembic_engine/
lib.rs

1//! engine orchestration: load, validate, plan, apply.
2
3mod adapter_ops;
4mod apply_retry;
5mod errors;
6pub mod external;
7mod extract;
8mod loader;
9pub mod mapping;
10mod pipeline;
11mod planner;
12mod retort;
13mod state;
14mod types;
15
16use alembic_core::{key_string, validate_inventory, Inventory, Object, ValidationReport};
17use anyhow::{anyhow, Result};
18
19#[cfg(test)]
20mod tests;
21
22pub use adapter_ops::{
23    build_key_from_schema, build_request_body, query_filters_from_key, resolve_value_for_type,
24};
25pub use apply_retry::{apply_non_delete_with_retries, RetryApplyDriver, RetryApplyResult};
26pub use errors::AdapterApplyError;
27pub use external::{
28    run_external_adapter, ExternalAdapter, ExternalEnvelope, ExternalObject, ExternalRequest,
29    ExternalResponse, EXTERNAL_PROTOCOL_VERSION,
30};
31pub use extract::{import_inventory, ImportReport};
32pub use loader::load_brew;
33pub use planner::{plan, sort_ops_for_apply};
34pub use retort::{compile_retort, is_brew_format, load_raw_yaml, load_retort, Retort};
35pub use state::{PostgresTlsMode, StateData, StateStore};
36pub use types::{
37    Adapter, AppliedOp, ApplyReport, BackendId, FieldChange, ObservedObject, ObservedState, Op,
38    Plan, PlanSummary, ProvisionReport,
39};
40
41/// validate an inventory and return the report.
42pub fn validate(inventory: &Inventory) -> ValidationReport {
43    validate_inventory(inventory)
44}
45
46/// helper to format a validation report into a Result.
47pub fn report_to_result(report: ValidationReport) -> Result<()> {
48    report_to_result_with_sources(report, &[])
49}
50
51/// helper to format a validation report with source locations into a Result.
52pub fn report_to_result_with_sources(report: ValidationReport, objects: &[Object]) -> Result<()> {
53    if report.is_ok() {
54        return Ok(());
55    }
56
57    let located_errors = report.with_sources(objects);
58    let mut message = String::from("validation failed:\n");
59    for error in located_errors {
60        message.push_str(&format!("- {error}\n"));
61    }
62    Err(anyhow!(message))
63}
64
65/// observe backend state and produce a deterministic plan.
66pub async fn build_plan(
67    adapter: &(dyn Adapter + '_),
68    inventory: &Inventory,
69    state: &mut StateStore,
70    allow_delete: bool,
71) -> Result<Plan> {
72    let observed = pipeline::observe(adapter, inventory, state).await?;
73    Ok(plan(
74        &inventory.objects,
75        &observed,
76        state,
77        &inventory.schema,
78        allow_delete,
79    ))
80}
81
82pub(crate) fn bootstrap_state_from_observed(
83    state: &mut StateStore,
84    desired: &[Object],
85    observed: &ObservedState,
86) -> bool {
87    let mut updated = false;
88    for object in desired {
89        if state
90            .backend_id(object.type_name.clone(), object.uid)
91            .is_some()
92        {
93            continue;
94        }
95        if let Some(obs) = observed
96            .by_key
97            .get(&(object.type_name.clone(), key_string(&object.key)))
98        {
99            if obs.type_name != object.type_name {
100                continue;
101            }
102            if let Some(backend_id) = &obs.backend_id {
103                state.set_backend_id(object.type_name.clone(), object.uid, backend_id.clone());
104                updated = true;
105            }
106        }
107    }
108    updated
109}
110
111/// apply a plan and update the state store.
112pub async fn apply_plan(
113    adapter: &(dyn Adapter + '_),
114    plan: &Plan,
115    state: &mut StateStore,
116    allow_delete: bool,
117) -> Result<ApplyReport> {
118    pipeline::apply(adapter, plan, state, allow_delete).await
119}