0.0.2-alpha.5
This commit is contained in:
19
crates/knowledge-app-lib/Cargo.toml
Normal file
19
crates/knowledge-app-lib/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
# file: crates/knowledge-app-lib/Cargo.toml
|
||||
# version: 1
|
||||
|
||||
[package]
|
||||
name = "knowledge-app-lib"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
knowledge-git-api.workspace = true
|
||||
knowledge-sync-api.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
knowledge-domain-lib.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
14
crates/knowledge-app-lib/src/lib.rs
Normal file
14
crates/knowledge-app-lib/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
// file: crates/knowledge-app-lib/src/lib.rs
|
||||
// version: 1
|
||||
|
||||
//! Shared application use cases for SaseDev Knowledge.
|
||||
//!
|
||||
//! This crate composes provider-neutral application contracts. It owns no Git process, scheduler,
|
||||
//! database, HTTP runtime or forge integration.
|
||||
|
||||
mod repository_sync;
|
||||
|
||||
/// Result of reconciling one repository-backed content source.
|
||||
pub use self::repository_sync::RepositorySyncOutcome;
|
||||
/// Reconcile one source through the provider-neutral Git repository contract.
|
||||
pub use self::repository_sync::synchronize_repository;
|
||||
89
crates/knowledge-app-lib/src/repository_sync.rs
Normal file
89
crates/knowledge-app-lib/src/repository_sync.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
// file: crates/knowledge-app-lib/src/repository_sync.rs
|
||||
// version: 1
|
||||
|
||||
/// Result of reconciling one repository-backed content source.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum RepositorySyncOutcome {
|
||||
/// The observed revision matches the revision already known by the caller.
|
||||
Unchanged {
|
||||
/// Authoritative revision observed after refresh and resolution.
|
||||
revision: knowledge_git_api::GitRevision,
|
||||
},
|
||||
/// The observed revision differs and a read snapshot has been prepared.
|
||||
Changed {
|
||||
/// Authoritative revision observed after refresh and resolution.
|
||||
revision: knowledge_git_api::GitRevision,
|
||||
/// Read-only snapshot prepared for the new authoritative revision.
|
||||
snapshot: knowledge_git_api::GitSnapshot,
|
||||
},
|
||||
}
|
||||
|
||||
impl crate::RepositorySyncOutcome {
|
||||
/// Return the authoritative revision observed by reconciliation.
|
||||
pub fn revision(&self) -> &knowledge_git_api::GitRevision {
|
||||
match self {
|
||||
crate::RepositorySyncOutcome::Unchanged { revision } => return revision,
|
||||
crate::RepositorySyncOutcome::Changed { revision, snapshot: _ } => return revision,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the prepared snapshot when reconciliation detected a change.
|
||||
pub fn snapshot(&self) -> Option<&knowledge_git_api::GitSnapshot> {
|
||||
match self {
|
||||
crate::RepositorySyncOutcome::Unchanged { revision: _ } => return None,
|
||||
crate::RepositorySyncOutcome::Changed { revision: _, snapshot } => return Some(snapshot),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return whether reconciliation detected a new authoritative revision.
|
||||
pub fn is_changed(&self) -> bool {
|
||||
match self {
|
||||
crate::RepositorySyncOutcome::Unchanged { revision: _ } => return false,
|
||||
crate::RepositorySyncOutcome::Changed { revision: _, snapshot: _ } => return true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconcile one source through the provider-neutral Git repository contract.
|
||||
///
|
||||
/// The signal cause does not change the reconciliation path: explicit, periodic and external
|
||||
/// signals all request the same authoritative refresh/resolve/compare operation. The caller owns
|
||||
/// persistence of the previous revision and any scheduling policy.
|
||||
pub fn synchronize_repository<R>(
|
||||
repository: &mut R,
|
||||
signal: &knowledge_sync_api::RepositorySyncSignal,
|
||||
reference: &knowledge_git_api::GitReference,
|
||||
previous_revision: Option<&knowledge_git_api::GitRevision>,
|
||||
) -> Result<crate::RepositorySyncOutcome, R::Error>
|
||||
where
|
||||
R: knowledge_git_api::GitRepository,
|
||||
{
|
||||
let refresh_result = knowledge_git_api::GitRepository::refresh(repository, signal.source_id());
|
||||
match refresh_result {
|
||||
Ok(()) => (),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
let revision_result = knowledge_git_api::GitRepository::resolve_revision(repository, signal.source_id(), reference);
|
||||
let revision = match revision_result {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
match previous_revision {
|
||||
Some(previous) => {
|
||||
if previous == &revision {
|
||||
return Ok(crate::RepositorySyncOutcome::Unchanged { revision });
|
||||
}
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
let snapshot_result = knowledge_git_api::GitRepository::prepare_snapshot(repository, signal.source_id(), &revision);
|
||||
let snapshot = match snapshot_result {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
return Ok(crate::RepositorySyncOutcome::Changed { revision, snapshot });
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/repository_sync.rs"]
|
||||
mod tests;
|
||||
121
crates/knowledge-app-lib/unit_tests/repository_sync.rs
Normal file
121
crates/knowledge-app-lib/unit_tests/repository_sync.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
// file: crates/knowledge-app-lib/unit_tests/repository_sync.rs
|
||||
// version: 1
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
struct FakeRepositoryError;
|
||||
|
||||
struct FakeRepository {
|
||||
observed_revision: knowledge_git_api::GitRevision,
|
||||
reject_snapshot: bool,
|
||||
}
|
||||
|
||||
impl FakeRepository {
|
||||
fn new(observed_revision: &str, reject_snapshot: bool) -> FakeRepository {
|
||||
return FakeRepository { observed_revision: knowledge_git_api::GitRevision::new(observed_revision), reject_snapshot };
|
||||
}
|
||||
}
|
||||
|
||||
impl knowledge_git_api::GitRepository for FakeRepository {
|
||||
type Error = FakeRepositoryError;
|
||||
|
||||
fn refresh(&mut self, _source_id: &knowledge_domain_lib::SourceId) -> Result<(), Self::Error> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn resolve_revision(
|
||||
&self,
|
||||
_source_id: &knowledge_domain_lib::SourceId,
|
||||
_reference: &knowledge_git_api::GitReference,
|
||||
) -> Result<knowledge_git_api::GitRevision, Self::Error> {
|
||||
return Ok(self.observed_revision.clone());
|
||||
}
|
||||
|
||||
fn prepare_snapshot(
|
||||
&self,
|
||||
_source_id: &knowledge_domain_lib::SourceId,
|
||||
revision: &knowledge_git_api::GitRevision,
|
||||
) -> Result<knowledge_git_api::GitSnapshot, Self::Error> {
|
||||
if self.reject_snapshot {
|
||||
return Err(FakeRepositoryError);
|
||||
}
|
||||
return Ok(knowledge_git_api::GitSnapshot::new(revision.clone(), std::path::PathBuf::from("/controlled/snapshot")));
|
||||
}
|
||||
}
|
||||
|
||||
fn source_id() -> Option<knowledge_domain_lib::SourceId> {
|
||||
let result = knowledge_domain_lib::SourceId::new("snippets");
|
||||
match result {
|
||||
Ok(value) => return Some(value),
|
||||
Err(_) => return None,
|
||||
}
|
||||
}
|
||||
|
||||
fn signal(cause: knowledge_sync_api::RepositorySyncCause) -> Option<knowledge_sync_api::RepositorySyncSignal> {
|
||||
let id = match source_id() {
|
||||
Some(value) => value,
|
||||
None => return None,
|
||||
};
|
||||
return Some(knowledge_sync_api::RepositorySyncSignal::new(id, cause));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changed_revision_prepares_snapshot_and_returns_observed_revision() {
|
||||
let mut repository = FakeRepository::new("bbbb", false);
|
||||
let request = match signal(knowledge_sync_api::RepositorySyncCause::Explicit) {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
};
|
||||
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
|
||||
let previous = knowledge_git_api::GitRevision::new("aaaa");
|
||||
let result = crate::synchronize_repository(&mut repository, &request, &reference, Some(&previous));
|
||||
assert!(result.is_ok());
|
||||
let outcome = match result {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
assert!(outcome.is_changed());
|
||||
assert_eq!(outcome.revision().as_str(), "bbbb");
|
||||
assert!(outcome.snapshot().is_some());
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unchanged_revision_does_not_prepare_snapshot() {
|
||||
let mut repository = FakeRepository::new("bbbb", true);
|
||||
let request = match signal(knowledge_sync_api::RepositorySyncCause::Explicit) {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
};
|
||||
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
|
||||
let previous = knowledge_git_api::GitRevision::new("bbbb");
|
||||
let result = crate::synchronize_repository(&mut repository, &request, &reference, Some(&previous));
|
||||
assert!(result.is_ok());
|
||||
let outcome = match result {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
assert!(!outcome.is_changed());
|
||||
assert_eq!(outcome.revision().as_str(), "bbbb");
|
||||
assert!(outcome.snapshot().is_none());
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn periodic_signal_uses_the_same_reconciliation_path() {
|
||||
let mut repository = FakeRepository::new("cccc", false);
|
||||
let request = match signal(knowledge_sync_api::RepositorySyncCause::Periodic) {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
};
|
||||
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
|
||||
let result = crate::synchronize_repository(&mut repository, &request, &reference, None);
|
||||
assert!(result.is_ok());
|
||||
let outcome = match result {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
assert!(outcome.is_changed());
|
||||
assert_eq!(outcome.revision().as_str(), "cccc");
|
||||
assert!(outcome.snapshot().is_some());
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user