Skip to main content

llm_bucket/
contract.rs

1#![allow(unused)]
2
3//! # uploader: Universal interface for external source/item upload
4//!
5//! This module defines a single trait (`Uploader`) and concrete supporting types
6//! for uploading external sources (e.g. repositories, document spaces)
7//! and their items (files, documents) into a project knowledge bucket via
8//! an external API, local system, or a mock/test implementation.
9//!
10//! ## Interface & Extensibility
11//! - Implement the [`Uploader`] trait to create new upload clients (e.g. API, file-based).
12//! - All methods are async, returning results and using boxed error types.
13//! - Error handling is uniform: all API/caller errors return boxed trait objects.
14//! - Meant for both production code and robust mocking in tests.
15//!
16//! ## Mocking & Testing
17//! - The trait is annotated for `mockall` so consumers can generate deterministic mocks for unit/integration tests.
18//!
19//! ## Type Sources
20//! - Request and response types (e.g., `NewExternalSource`, `ExternalSource`, `NewExternalItem`, `ExternalItem`) are plain data; see docs for field descriptions.
21//!
22//! ## Example Usage
23//! - See the core binary crate or test suite for concrete implementors—API client, test-mock, etc.
24//!
25//! ## Adding New Upload Destinations
26//! - Implement the trait for your destination.
27//! - Ensure methods are infallible in their contract: convert all meaningful upstream errors to a boxed error.
28//! - Return concrete, understandable error variants on user/config/connection issues.
29
30use async_trait::async_trait;
31
32use mockall::{automock, predicate::*};
33
34/// Represents the bare minimum data needed to create an external source.
35pub struct NewExternalSource<'a> {
36    /// Human-readable name for the external source (e.g., the repository name).
37    pub name: &'a str,
38    /// The bucket this source belongs to.
39    pub bucket_id: i32,
40}
41
42/// Represents the returned external source after creation.
43#[derive(Clone)]
44pub struct ExternalSource {
45    pub bucket_id: i32,
46    pub external_source_id: i32,
47    pub external_source_name: String,
48    pub updated_by: i32,
49    pub updated_datetime: Option<String>,
50}
51
52/// Represents the minimal data needed to upload a new item (file/document) to a source.
53pub struct NewExternalItem<'a> {
54    /// The raw file contents, typically UTF-8 text.
55    pub content: &'a str,
56    /// URL that must identify the item uniquely (can be a VCS or filesystem URL).
57    pub url: &'a str,
58    /// The parent bucket id.
59    pub bucket_id: i64,
60    /// The id of the external source to which this item belongs.
61    pub external_source_id: i64,
62    /// Optional state for processing. (Leave unpopulated to use default.)
63    pub processing_state: Option<&'a str>,
64}
65
66/// Represents the created/returned item.
67#[derive(Debug, Clone, serde::Serialize)]
68pub struct ExternalItem {
69    pub content_hash: String,
70    pub external_item_id: i64,
71    pub external_source_id: i64,
72    pub processing_state: String,
73    pub state: String,
74    pub updated_datetime: Option<String>,
75    pub url: String,
76}
77
78/// Error type for Downloader trait (simple boxed error for now)
79pub type DownloadError = Box<dyn std::error::Error + Send + Sync>;
80
81/// Manifest returned from a download operation, describing exactly what was downloaded and where.
82#[derive(Debug, Clone)]
83pub struct DownloadedManifest {
84    pub sources: Vec<DownloadedSource>,
85}
86
87/// Describes a successfully downloaded source in the manifest.
88#[derive(Debug, Clone)]
89pub struct DownloadedSource {
90    /// Human-readable logical name (e.g., repo URL or space name)
91    pub logical_name: String,
92    /// Filesystem path to the downloaded/extracted source directory
93    pub local_path: std::path::PathBuf,
94    /// Original declared source action (for audit)
95    pub original_source: crate::download::SourceAction,
96}
97
98/// Trait for downloading all sources as specified in configuration.
99/// Allows plugging in real, test, or mockable downloaders (like with Uploader).
100#[cfg_attr(any(test, feature = "test-export-mocks"), automock)]
101#[async_trait]
102pub trait Downloader: Send + Sync {
103    /// Download all sources from the downloader's config into the configured output directory,
104    /// returning a manifest of what was downloaded and where.
105    async fn download_all(&self) -> Result<DownloadedManifest, DownloadError>;
106}
107
108/// Trait for uploading and managing external sources/items in a bucket.
109/// The implementor is responsible for connecting to a backing service or storage API.
110///
111/// *NOTE:* This file acts as the *interface* only. Types referenced here
112/// (e.g. NewExternalSource, ExternalSource, etc.) must be imported by
113/// dependents from their public sources.
114/// The trait is implemented by real clients and by test mocks.
115///
116/// The trait is `Send` + `Sync` + `'static` and intended for async/await usage.
117#[cfg_attr(any(test, feature = "test-export-mocks"), automock)]
118#[async_trait]
119pub trait Uploader: Send + Sync {
120    /// Create a new external source (such as a repository or a folder).
121    async fn create_source<'a>(
122        &self,
123        req: NewExternalSource<'a>,
124    ) -> Result<ExternalSource, Box<dyn std::error::Error + Send + Sync>>;
125
126    /// Create a new item (such as a file) in an external source.
127    ///
128    /// Implementor is responsible for content handling and required API fields.
129    async fn create_item<'a>(
130        &self,
131        req: NewExternalItem<'a>,
132    ) -> Result<ExternalItem, Box<dyn std::error::Error + Send + Sync>>;
133
134    /// Fetch a single external source by its ID.
135    async fn get_source_by_id(
136        &self,
137        external_source_id: i32,
138    ) -> Result<ExternalSource, Box<dyn std::error::Error + Send + Sync>>;
139
140    /// Delete an external source by ID.
141    async fn delete_source_by_id(
142        &self,
143        external_source_id: i32,
144    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
145
146    /// Delete an external item by both external source and item ID.
147    async fn delete_item_by_id(
148        &self,
149        external_source_id: i64,
150        external_item_id: i64,
151    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
152
153    /// List all external sources for the bucket.
154    async fn list_sources(
155        &self,
156    ) -> Result<Vec<ExternalSource>, Box<dyn std::error::Error + Send + Sync>>;
157}
158
159/// Processor configuration - describes how the sources are processed into uploadable items.
160#[derive(Debug, Clone)]
161pub struct ProcessConfig {
162    pub kind: ProcessorKind,
163}
164
165/// Types/kinds of processing strategy.
166#[derive(Debug, Clone)]
167pub enum ProcessorKind {
168    /// For each source, outputs a single PDF (README.md converted)
169    ReadmeToPDF,
170    /// Flattens all files in the repo, uploading them with directory encoded in name
171    FlattenFiles,
172    // Future: CodeToPDF, DirectoryToPDF, etc
173}
174
175impl From<&str> for ProcessorKind {
176    fn from(s: &str) -> Self {
177        match s {
178            "ReadmeToPDF" | "readme_to_pdf" | "readme2pdf" => ProcessorKind::ReadmeToPDF,
179            "FlattenFiles" | "flattenfiles" | "flatten_files" => ProcessorKind::FlattenFiles,
180            other => {
181                tracing::warn!(
182                    kind = other,
183                    "Unknown processor kind, defaulting to FlattenFiles"
184                );
185                ProcessorKind::FlattenFiles
186            }
187        }
188    }
189}
190
191/// Input for processing step: a single source location (name, local path, etc)
192#[derive(Debug, Clone)]
193pub struct ProcessInput {
194    pub name: String,
195    pub repo_path: std::path::PathBuf,
196    // Extend as needed
197}
198
199/// Output for processing: A source with items to be uploaded
200#[derive(Debug, Clone)]
201pub struct ExternalSourceInput {
202    pub name: String,
203    pub external_items: Vec<ExternalItemInput>,
204}
205
206/// An item for upload: filename and content (e.g. PDF data)
207#[derive(Debug, Clone)]
208pub struct ExternalItemInput {
209    pub filename: String,
210    pub content: Vec<u8>,
211}
212
213#[derive(Debug)]
214pub enum ProcessError {
215    Io(std::io::Error),
216    NoReadme,
217    Other(String),
218}
219
220impl From<std::io::Error> for ProcessError {
221    fn from(e: std::io::Error) -> Self {
222        ProcessError::Io(e)
223    }
224}
225
226/// Trait for preprocessing (used in synchronise orchestration).
227/// Implemented by concrete processors and by mocks in testing.
228#[automock]
229#[async_trait]
230pub trait Preprocessor: Send + Sync {
231    /// Process an input source and return a processed external source with items, or error.
232    async fn process(&self, input: ProcessInput) -> Result<ExternalSourceInput, ProcessError>;
233}