1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
//! Fetching and pushing objects to and from remote backends.
//!
//! # Purpose
//!
//! This module defines the [`Transport`] trait, which abstracts the
//! communication layer required to synchronize version control objects
//! between a local repository and a remote endpoint. A transport is
//! responsible for two fundamental operations:
//!
//! - Fetching an object identified by its [`Hash`] from a remote.
//! - Pushing a locally available object to a remote.
//!
//! The trait intentionally focuses only on object movement. It does not
//! define protocols, authentication, or discovery mechanisms; those concerns
//! belong to concrete implementations.
//!
//! # Design Rationale
//!
//! The transport layer is separated from the local object store
//! ([`ObjectStore`](crate::ObjectStore)) for several reasons:
//!
//! - **Different lifecycles**: A local store is typically long-lived and
//! disk-backed, while a transport represents a short-lived network session.
//! - **Different failure modes**: Transports may fail due to network
//! interruption, authentication errors, or remote rejection, which are
//! distinct from local storage failures.
//! - **Testability**: Dummy or in-memory transports make it easy to test
//! synchronization logic without real network access.
//! - **Backend flexibility**: A transport can be implemented over HTTP,
//! SSH, custom protocols, or even an in-process channel, without changing
//! the core synchronization code.
//!
//! # Method Signature Rationale
//!
//! - [`fetch_object`](Transport::fetch_object) takes `&Hash` rather than an
//! owned [`Hash`] to avoid copying the 64-byte key on the stack. It
//! returns the object bytes as a [`Vec<u8>`] because the complete remote
//! object is needed locally.
//! - [`push_object`](Transport::push_object) takes `&Hash` and `&[u8]` to
//! avoid unnecessary ownership transfer. The hash identifies the object on
//! the remote, while the byte slice carries the raw serialized content.
//!
//! # Error Handling
//!
//! Both methods return [`Result<_, VctrlError>`] to provide a unified error
//! surface. Common error variants include:
//!
//! - [`VctrlError::ObjectNotFound`](crate::VctrlError::ObjectNotFound) when
//! the remote does not have the requested object.
//! - [`VctrlError::IoError`](crate::VctrlError::IoError) for network and
//! transport-level failures.
//! - [`VctrlError::Other`](crate::VctrlError::Other) for protocol-specific
//! or remote-rejection errors.
//!
//! # Internal Mechanism
//!
//! A concrete transport implementation will typically maintain some form of
//! connection state (socket, HTTP client, or in-memory map). The
//! [`fetch_object`](Transport::fetch_object) method sends a request for the
//! hash and returns the received bytes. The
//! [`push_object`](Transport::push_object) method sends the hash and data to
//! the remote for storage. The exact wire format is implementation-defined.
//!
//! # Examples
//!
//! A complete in-memory transport implementation:
//!
//! ```
//! use libvctrl_handler::{Hash, Transport, VctrlError};
//! use std::collections::HashMap;
//!
//! #[derive(Default)]
//! struct InMemoryTransport(HashMap<Hash, Vec<u8>>);
//!
//! impl Transport for InMemoryTransport {
//! fn fetch_object(&self, hash: &Hash) -> Result<Vec<u8>, VctrlError> {
//! self.0
//! .get(hash)
//! .cloned()
//! .ok_or_else(|| VctrlError::ObjectNotFound(*hash))
//! }
//!
//! fn push_object(&mut self, hash: &Hash, data: &[u8]) -> Result<(), VctrlError> {
//! self.0.insert(*hash, data.to_vec());
//! Ok(())
//! }
//! }
//!
//! let mut transport = InMemoryTransport::default();
//! let hash = Hash::from_bytes(&[0u8; 64]).unwrap();
//! transport.push_object(&hash, b"data").unwrap();
//! assert_eq!(transport.fetch_object(&hash).unwrap(), b"data");
//! ```
use crateVctrlError;
use crateHash;
/// Defines the interface for synchronizing objects with a remote backend.
///
/// # Purpose
///
/// A `Transport` abstracts the network or inter-process communication layer
/// required to fetch and push version control objects between a local
/// [`ObjectStore`](crate::ObjectStore) and a remote endpoint. It is the
/// bridge that enables distributed version control operations such as clone,
/// fetch, push, and pull.
///
/// # Design Rationale
///
/// - **`fetch_object` takes `&Hash`**: The method borrows the hash to avoid
/// copying the 64-byte key on the stack. Since the hash is only used for
/// lookup, borrowing is sufficient and more efficient.
/// - **`push_object` takes raw bytes**: The method receives the object data
/// as `&[u8]` and the hash as `&Hash`. The hash tells the remote where to
/// store the object, and the slice carries the payload. Borrowing avoids
/// unnecessary moves.
/// - **Distinct from [`ObjectStore`]**: The local object store is optimized
/// for persistent, content-addressed storage, while a transport is a
/// communication channel. Keeping them separate allows the local store to
/// be disk-based while the transport is purely network-oriented.
///
/// # Why Not Streaming?
///
/// Unlike [`ObjectStore::get`](crate::ObjectStore::get), which returns a
/// streaming reader, [`fetch_object`](Transport::fetch_object) returns a
/// complete [`Vec<u8>`]. This choice simplifies remote protocol interactions
/// where the entire object must be received before it can be validated or
/// stored. Streaming transport protocols can still be implemented internally
/// by the concrete transport.
///
/// # Error Handling
///
/// All methods return [`Result<_, VctrlError>`] to preserve the crate's
/// unified error model. Implementations should map network and protocol
/// errors to the appropriate variants, especially
/// [`VctrlError::IoError`](crate::VctrlError::IoError) and
/// [`VctrlError::Other`](crate::VctrlError::Other).
///
/// # Internal Mechanism
///
/// A transport implementation maintains whatever state is necessary for its
/// communication channel. For example, an HTTP transport may keep an HTTP
/// client and base URL. The methods translate the high-level object requests
/// into protocol-specific operations and translate the responses back into
/// Rust data types.
///
/// # Examples
///
/// A complete in-memory transport:
///
/// ```
/// use libvctrl_handler::{Hash, Transport, VctrlError};
/// use std::collections::HashMap;
///
/// #[derive(Default)]
/// struct InMemoryTransport(HashMap<Hash, Vec<u8>>);
///
/// impl Transport for InMemoryTransport {
/// fn fetch_object(&self, hash: &Hash) -> Result<Vec<u8>, VctrlError> {
/// self.0
/// .get(hash)
/// .cloned()
/// .ok_or_else(|| VctrlError::ObjectNotFound(*hash))
/// }
///
/// fn push_object(&mut self, hash: &Hash, data: &[u8]) -> Result<(), VctrlError> {
/// self.0.insert(*hash, data.to_vec());
/// Ok(())
/// }
/// }
///
/// let mut transport = InMemoryTransport::default();
/// let hash = Hash::from_bytes(&[0u8; 64]).unwrap();
/// transport.push_object(&hash, b"data").unwrap();
/// assert_eq!(transport.fetch_object(&hash).unwrap(), b"data");
/// ```