0.0.1-alpha.3
This commit is contained in:
13
crates/knowledge-domain-lib/src/content_family.rs
Normal file
13
crates/knowledge-domain-lib/src/content_family.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
// file: crates/knowledge-domain-lib/src/content_family.rs
|
||||
// version: 1
|
||||
|
||||
//! Content-family identities owned by the Knowledge domain.
|
||||
|
||||
/// Semantic family of knowledge content.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum ContentFamily {
|
||||
Pgr,
|
||||
Howto,
|
||||
Snippet,
|
||||
ProjectLocal,
|
||||
}
|
||||
@@ -1,8 +1,16 @@
|
||||
// file: crates/knowledge-domain-lib/src/lib.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Domain boundary for SaseDev Knowledge.
|
||||
//! Shared domain contracts for SaseDev Knowledge.
|
||||
//!
|
||||
//! The crate is intentionally minimal in `0.0.1-alpha.2`: it establishes ownership of shared
|
||||
//! domain contracts without introducing the Git/synchronization contracts reserved for the next
|
||||
//! tranche.
|
||||
//! The crate owns content-family and source identities that are independent of HTTP, PostgreSQL,
|
||||
//! forge providers and concrete Git implementations.
|
||||
|
||||
mod content_family;
|
||||
mod source;
|
||||
|
||||
pub use crate::content_family::ContentFamily;
|
||||
pub use crate::source::{ContentSource, SourceId, SourceIdError};
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests;
|
||||
|
||||
68
crates/knowledge-domain-lib/src/source.rs
Normal file
68
crates/knowledge-domain-lib/src/source.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
// file: crates/knowledge-domain-lib/src/source.rs
|
||||
// version: 1
|
||||
|
||||
//! Source identities independent of repository and forge providers.
|
||||
|
||||
/// Error returned when constructing an invalid source identifier.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum SourceIdError {
|
||||
/// The identifier is empty or contains only whitespace.
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for crate::SourceIdError {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
crate::SourceIdError::Empty => return formatter.write_str("source identifier must not be empty"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for crate::SourceIdError {}
|
||||
|
||||
/// Stable logical identifier of one configured content source.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct SourceId(String);
|
||||
|
||||
impl crate::SourceId {
|
||||
/// Build a source identifier without imposing a fixed repository count or provider syntax.
|
||||
pub fn new(value: &str) -> Result<crate::SourceId, crate::SourceIdError> {
|
||||
if value.trim().is_empty() {
|
||||
return Err(crate::SourceIdError::Empty);
|
||||
}
|
||||
return Ok(crate::SourceId(value.to_owned()));
|
||||
}
|
||||
|
||||
/// Return the identifier as text.
|
||||
pub fn as_str(&self) -> &str {
|
||||
return self.0.as_str();
|
||||
}
|
||||
}
|
||||
|
||||
/// One logical content source associated with a semantic family.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ContentSource {
|
||||
id: crate::SourceId,
|
||||
family: crate::ContentFamily,
|
||||
}
|
||||
|
||||
impl crate::ContentSource {
|
||||
/// Build a content source from a textual source identifier.
|
||||
pub fn new(id: &str, family: crate::ContentFamily) -> Result<crate::ContentSource, crate::SourceIdError> {
|
||||
let source_id = match crate::SourceId::new(id) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
return Ok(crate::ContentSource { id: source_id, family });
|
||||
}
|
||||
|
||||
/// Return the logical source identifier.
|
||||
pub fn id(&self) -> &crate::SourceId {
|
||||
return &self.id;
|
||||
}
|
||||
|
||||
/// Return the semantic content family.
|
||||
pub fn family(&self) -> crate::ContentFamily {
|
||||
return self.family;
|
||||
}
|
||||
}
|
||||
20
crates/knowledge-domain-lib/src/unit_tests.rs
Normal file
20
crates/knowledge-domain-lib/src/unit_tests.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
// file: crates/knowledge-domain-lib/src/unit_tests.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn source_id_rejects_empty_values() {
|
||||
assert_eq!(crate::SourceId::new(" "), Err(crate::SourceIdError::Empty));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn content_sources_are_not_limited_to_three_repositories() {
|
||||
let sources = [
|
||||
crate::ContentSource::new("pgr", crate::ContentFamily::Pgr),
|
||||
crate::ContentSource::new("howtos", crate::ContentFamily::Howto),
|
||||
crate::ContentSource::new("snippets", crate::ContentFamily::Snippet),
|
||||
crate::ContentSource::new("project-extra", crate::ContentFamily::ProjectLocal),
|
||||
];
|
||||
assert!(sources.iter().all(Result::is_ok));
|
||||
return;
|
||||
}
|
||||
15
crates/knowledge-git-api/Cargo.toml
Normal file
15
crates/knowledge-git-api/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
# file: crates/knowledge-git-api/Cargo.toml
|
||||
# version: 1
|
||||
|
||||
[package]
|
||||
name = "knowledge-git-api"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
knowledge-domain-lib.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
57
crates/knowledge-git-api/src/lib.rs
Normal file
57
crates/knowledge-git-api/src/lib.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
// file: crates/knowledge-git-api/src/lib.rs
|
||||
// version: 1
|
||||
|
||||
//! Provider-neutral contracts for Git-backed Knowledge sources.
|
||||
//!
|
||||
//! This crate deliberately contains no forge API, process runner, filesystem checkout or database
|
||||
//! implementation. Concrete Git backends depend on this contract, not the reverse.
|
||||
|
||||
/// Opaque Git reference requested by a caller, such as a branch, tag or object expression.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct GitReference(String);
|
||||
|
||||
impl crate::GitReference {
|
||||
/// Create an opaque Git reference without imposing one provider's reference grammar.
|
||||
pub fn new(value: &str) -> crate::GitReference {
|
||||
return crate::GitReference(value.to_owned());
|
||||
}
|
||||
|
||||
/// Return the reference text.
|
||||
pub fn as_str(&self) -> &str {
|
||||
return self.0.as_str();
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolved authoritative Git revision identity.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct GitRevision(String);
|
||||
|
||||
impl crate::GitRevision {
|
||||
/// Create a revision identity returned by a concrete Git implementation.
|
||||
pub fn new(value: &str) -> crate::GitRevision {
|
||||
return crate::GitRevision(value.to_owned());
|
||||
}
|
||||
|
||||
/// Return the revision identity as text.
|
||||
pub fn as_str(&self) -> &str {
|
||||
return self.0.as_str();
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider-neutral operations required from a Git repository backend.
|
||||
///
|
||||
/// Implementations may use a local Git executable, a Rust Git library or another generic Git
|
||||
/// mechanism. Forge-only capabilities do not belong to this trait.
|
||||
pub trait GitRepository {
|
||||
/// Implementation-specific recoverable error.
|
||||
type Error;
|
||||
|
||||
/// Refresh the local view of one configured source when the backend requires it.
|
||||
fn refresh(&mut self, source_id: &knowledge_domain_lib::SourceId) -> Result<(), Self::Error>;
|
||||
|
||||
/// Resolve one opaque reference to the authoritative revision used by synchronization.
|
||||
fn resolve_revision(&self, source_id: &knowledge_domain_lib::SourceId, reference: &crate::GitReference) -> Result<crate::GitRevision, Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests;
|
||||
35
crates/knowledge-git-api/src/unit_tests.rs
Normal file
35
crates/knowledge-git-api/src/unit_tests.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
// file: crates/knowledge-git-api/src/unit_tests.rs
|
||||
// version: 1
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct FakeGitError;
|
||||
|
||||
struct FakeGitRepository;
|
||||
|
||||
impl crate::GitRepository for FakeGitRepository {
|
||||
type Error = FakeGitError;
|
||||
|
||||
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: &crate::GitReference) -> Result<crate::GitRevision, Self::Error> {
|
||||
return Ok(crate::GitRevision::new(&format!("{}:{}", source_id.as_str(), reference.as_str())));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_repository_contract_does_not_require_a_forge() {
|
||||
let source_result = knowledge_domain_lib::SourceId::new("local-pgr");
|
||||
assert!(source_result.is_ok());
|
||||
let source_id = match source_result {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
let reference = crate::GitReference::new("refs/heads/master");
|
||||
let mut repository = FakeGitRepository;
|
||||
assert!(crate::GitRepository::refresh(&mut repository, &source_id).is_ok());
|
||||
let revision_result = crate::GitRepository::resolve_revision(&repository, &source_id, &reference);
|
||||
assert_eq!(revision_result, Ok(crate::GitRevision::new("local-pgr:refs/heads/master")));
|
||||
return;
|
||||
}
|
||||
15
crates/knowledge-sync-api/Cargo.toml
Normal file
15
crates/knowledge-sync-api/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
# file: crates/knowledge-sync-api/Cargo.toml
|
||||
# version: 1
|
||||
|
||||
[package]
|
||||
name = "knowledge-sync-api"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
knowledge-domain-lib.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
57
crates/knowledge-sync-api/src/lib.rs
Normal file
57
crates/knowledge-sync-api/src/lib.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
// file: crates/knowledge-sync-api/src/lib.rs
|
||||
// version: 1
|
||||
|
||||
//! Contracts for provider-neutral repository synchronization signals.
|
||||
//!
|
||||
//! A signal requests reconciliation of a source. It is not authoritative content and does not
|
||||
//! replace resolution of the target Git revision by the Git repository contract.
|
||||
|
||||
/// Coarse origin of a synchronization request.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum RepositorySyncCause {
|
||||
/// An explicit request from an operator or application use case.
|
||||
Explicit,
|
||||
/// A periodic reconciliation independent of external hooks.
|
||||
Periodic,
|
||||
/// A non-authoritative external signal such as a Git hook, webhook or filesystem watcher.
|
||||
ExternalSignal,
|
||||
}
|
||||
|
||||
/// Request to reconcile one logical content source.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RepositorySyncSignal {
|
||||
source_id: knowledge_domain_lib::SourceId,
|
||||
cause: crate::RepositorySyncCause,
|
||||
}
|
||||
|
||||
impl crate::RepositorySyncSignal {
|
||||
/// Build a synchronization signal for one source.
|
||||
pub fn new(source_id: knowledge_domain_lib::SourceId, cause: crate::RepositorySyncCause) -> crate::RepositorySyncSignal {
|
||||
return crate::RepositorySyncSignal { source_id, cause };
|
||||
}
|
||||
|
||||
/// Return the source that must be reconciled.
|
||||
pub fn source_id(&self) -> &knowledge_domain_lib::SourceId {
|
||||
return &self.source_id;
|
||||
}
|
||||
|
||||
/// Return the coarse trigger origin.
|
||||
pub fn cause(&self) -> crate::RepositorySyncCause {
|
||||
return self.cause;
|
||||
}
|
||||
}
|
||||
|
||||
/// Producer of synchronization signals.
|
||||
///
|
||||
/// Concrete implementations may represent explicit queues, polling, hooks, webhooks or other
|
||||
/// event mechanisms. The scheduling/runtime model remains outside this passive contract.
|
||||
pub trait RepositoryEventSource {
|
||||
/// Implementation-specific recoverable error.
|
||||
type Error;
|
||||
|
||||
/// Return the next available signal, or `None` when no signal is currently available.
|
||||
fn next_signal(&mut self) -> Result<Option<crate::RepositorySyncSignal>, Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests;
|
||||
45
crates/knowledge-sync-api/src/unit_tests.rs
Normal file
45
crates/knowledge-sync-api/src/unit_tests.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
// file: crates/knowledge-sync-api/src/unit_tests.rs
|
||||
// version: 1
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct FakeEventError;
|
||||
|
||||
struct OneShotEventSource {
|
||||
signal: Option<crate::RepositorySyncSignal>,
|
||||
}
|
||||
|
||||
impl crate::RepositoryEventSource for OneShotEventSource {
|
||||
type Error = FakeEventError;
|
||||
|
||||
fn next_signal(&mut self) -> Result<Option<crate::RepositorySyncSignal>, Self::Error> {
|
||||
return Ok(self.signal.take());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_signal_is_only_a_request_to_reconcile_a_source() {
|
||||
let source_result = knowledge_domain_lib::SourceId::new("snippets-extra");
|
||||
assert!(source_result.is_ok());
|
||||
let source_id = match source_result {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
let signal = crate::RepositorySyncSignal::new(source_id, crate::RepositorySyncCause::ExternalSignal);
|
||||
let mut event_source = OneShotEventSource { signal: Some(signal) };
|
||||
let first_result = crate::RepositoryEventSource::next_signal(&mut event_source);
|
||||
assert!(first_result.is_ok());
|
||||
let first = match first_result {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
assert!(first.is_some());
|
||||
let signal = match first {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
};
|
||||
assert_eq!(signal.source_id().as_str(), "snippets-extra");
|
||||
assert_eq!(signal.cause(), crate::RepositorySyncCause::ExternalSignal);
|
||||
let second_result = crate::RepositoryEventSource::next_signal(&mut event_source);
|
||||
assert_eq!(second_result, Ok(None));
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user