33//! Per RFC 0004, handlers support both binary protobuf (`application/protobuf`)
44//! and ProtoJSON (`application/protobuf+json`) formats.
55
6+ use std:: ops:: RangeBounds ;
67use std:: sync:: Arc ;
78use std:: time:: Duration ;
89
@@ -15,25 +16,121 @@ use axum::http::HeaderMap;
1516use super :: error:: ApiError ;
1617use super :: metrics:: Metrics ;
1718use super :: proto:: {
18- AppendResponse , CountResponse , KeysResponse , ScanResponse , Segment , SegmentsResponse , Value ,
19+ AppendResponse , CountResponse , KeysResponse , ScanResponse , Segment as ProtoSegment ,
20+ SegmentsResponse , Value ,
1921} ;
2022use super :: request:: { AppendRequest , CountParams , ListKeysParams , ListSegmentsParams , ScanParams } ;
2123use super :: response:: { ApiResponse , ResponseFormat , to_api_response} ;
22- use crate :: LogDb ;
24+ use crate :: error :: AppendError ;
2325use crate :: reader:: LogRead ;
26+ use crate :: {
27+ AppendOutput , AppendResult , LogDb , LogDbReader , LogIterator , LogKeyIterator , Record , Segment ,
28+ SegmentId , Sequence ,
29+ } ;
30+
31+ /// Storage backend behind the HTTP server.
32+ ///
33+ /// A full read-write [`LogDb`] exposes every route; a [`LogDbReader`] backs a
34+ /// read-only gateway where the append route is not registered (see
35+ /// [`LogServer`](super::http::LogServer)). Read methods delegate to the
36+ /// [`LogRead`] implementation shared by both variants; write methods are only
37+ /// meaningful on the read-write variant.
38+ #[ derive( Clone ) ]
39+ pub ( crate ) enum LogBackend {
40+ /// Full read-write log.
41+ ReadWrite ( Arc < LogDb > ) ,
42+ /// Read-only view that periodically discovers data written elsewhere.
43+ ReadOnly ( Arc < LogDbReader > ) ,
44+ }
45+
46+ impl LogBackend {
47+ /// Appends records to the log.
48+ ///
49+ /// Errors on a read-only backend. The append route is not registered in
50+ /// read-only mode, so this arm is a defensive guard rather than a reachable
51+ /// code path.
52+ pub async fn try_append ( & self , records : Vec < Record > ) -> AppendResult < AppendOutput > {
53+ match self {
54+ Self :: ReadWrite ( log) => log. try_append ( records) . await ,
55+ Self :: ReadOnly ( _) => Err ( AppendError :: Storage ( "log gateway is read-only" . to_string ( ) ) ) ,
56+ }
57+ }
58+
59+ /// Flushes pending writes. A no-op on a read-only backend.
60+ pub async fn flush ( & self ) -> crate :: Result < ( ) > {
61+ match self {
62+ Self :: ReadWrite ( log) => log. flush ( ) . await ,
63+ Self :: ReadOnly ( _) => Ok ( ( ) ) ,
64+ }
65+ }
66+
67+ /// Verifies the storage backend is reachable (readiness probe).
68+ pub async fn check_storage ( & self ) -> crate :: Result < ( ) > {
69+ match self {
70+ Self :: ReadWrite ( log) => log. check_storage ( ) . await ,
71+ Self :: ReadOnly ( reader) => reader. check_storage ( ) . await ,
72+ }
73+ }
74+
75+ /// Scans entries for a key within a sequence range.
76+ pub async fn scan (
77+ & self ,
78+ key : Bytes ,
79+ seq_range : impl RangeBounds < Sequence > + Send ,
80+ ) -> crate :: Result < LogIterator > {
81+ match self {
82+ Self :: ReadWrite ( log) => log. scan ( key, seq_range) . await ,
83+ Self :: ReadOnly ( reader) => reader. scan ( key, seq_range) . await ,
84+ }
85+ }
86+
87+ /// Counts entries for a key within a sequence range.
88+ pub async fn count (
89+ & self ,
90+ key : Bytes ,
91+ seq_range : impl RangeBounds < Sequence > + Send ,
92+ ) -> crate :: Result < u64 > {
93+ match self {
94+ Self :: ReadWrite ( log) => log. count ( key, seq_range) . await ,
95+ Self :: ReadOnly ( reader) => reader. count ( key, seq_range) . await ,
96+ }
97+ }
98+
99+ /// Lists distinct keys within a segment range.
100+ pub async fn list_keys (
101+ & self ,
102+ segment_range : impl RangeBounds < SegmentId > + Send ,
103+ ) -> crate :: Result < LogKeyIterator > {
104+ match self {
105+ Self :: ReadWrite ( log) => log. list_keys ( segment_range) . await ,
106+ Self :: ReadOnly ( reader) => reader. list_keys ( segment_range) . await ,
107+ }
108+ }
109+
110+ /// Lists segments overlapping a sequence range.
111+ pub async fn list_segments (
112+ & self ,
113+ seq_range : impl RangeBounds < Sequence > + Send ,
114+ ) -> crate :: Result < Vec < Segment > > {
115+ match self {
116+ Self :: ReadWrite ( log) => log. list_segments ( seq_range) . await ,
117+ Self :: ReadOnly ( reader) => reader. list_segments ( seq_range) . await ,
118+ }
119+ }
120+ }
24121
25122/// Shared application state.
26123#[ derive( Clone ) ]
27- pub struct AppState {
28- pub log : Arc < LogDb > ,
124+ pub ( crate ) struct AppState {
125+ pub log : LogBackend ,
29126 pub metrics : Arc < Metrics > ,
30127}
31128
32129/// Handle POST /api/v1/log/append
33130///
34131/// Supports both `Content-Type: application/protobuf` and `Content-Type: application/protobuf+json`.
35132/// Returns response in format matching the `Accept` header.
36- pub async fn handle_append (
133+ pub ( crate ) async fn handle_append (
37134 State ( state) : State < AppState > ,
38135 headers : HeaderMap ,
39136 body : Bytes ,
@@ -70,7 +167,7 @@ pub async fn handle_append(
70167///
71168/// Returns response in format matching the `Accept` header.
72169/// Supports long-polling via `follow=true` and `timeout_ms` parameters.
73- pub async fn handle_scan (
170+ pub ( crate ) async fn handle_scan (
74171 State ( state) : State < AppState > ,
75172 headers : HeaderMap ,
76173 Query ( params) : Query < ScanParams > ,
@@ -172,7 +269,7 @@ async fn scan_entries(
172269/// Handle GET /api/v1/log/keys
173270///
174271/// Returns response in format matching the `Accept` header.
175- pub async fn handle_list_keys (
272+ pub ( crate ) async fn handle_list_keys (
176273 State ( state) : State < AppState > ,
177274 headers : HeaderMap ,
178275 Query ( params) : Query < ListKeysParams > ,
@@ -198,7 +295,7 @@ pub async fn handle_list_keys(
198295/// Handle GET /api/v1/log/segments
199296///
200297/// Returns response in format matching the `Accept` header.
201- pub async fn handle_list_segments (
298+ pub ( crate ) async fn handle_list_segments (
202299 State ( state) : State < AppState > ,
203300 headers : HeaderMap ,
204301 Query ( params) : Query < ListSegmentsParams > ,
@@ -207,9 +304,9 @@ pub async fn handle_list_segments(
207304 let seq_range = params. seq_range ( ) ;
208305
209306 let segments = state. log . list_segments ( seq_range) . await ?;
210- let segment_entries: Vec < Segment > = segments
307+ let segment_entries: Vec < ProtoSegment > = segments
211308 . into_iter ( )
212- . map ( |s| Segment {
309+ . map ( |s| ProtoSegment {
213310 id : s. id ,
214311 start_seq : s. start_seq ,
215312 start_time_ms : s. start_time_ms ,
@@ -223,7 +320,7 @@ pub async fn handle_list_segments(
223320/// Handle GET /api/v1/log/count
224321///
225322/// Returns response in format matching the `Accept` header.
226- pub async fn handle_count (
323+ pub ( crate ) async fn handle_count (
227324 State ( state) : State < AppState > ,
228325 headers : HeaderMap ,
229326 Query ( params) : Query < CountParams > ,
@@ -239,22 +336,24 @@ pub async fn handle_count(
239336}
240337
241338/// Handle GET /metrics
242- pub async fn handle_metrics ( State ( state) : State < AppState > ) -> String {
339+ pub ( crate ) async fn handle_metrics ( State ( state) : State < AppState > ) -> String {
243340 state. metrics . encode ( )
244341}
245342
246343/// Handle GET /-/healthy
247344///
248345/// Returns 200 OK if the service is running.
249- pub async fn handle_healthy ( ) -> ( axum:: http:: StatusCode , & ' static str ) {
346+ pub ( crate ) async fn handle_healthy ( ) -> ( axum:: http:: StatusCode , & ' static str ) {
250347 ( axum:: http:: StatusCode :: OK , "OK" )
251348}
252349
253350/// Handle GET /-/ready
254351///
255352/// Returns 200 OK if the service is ready to serve requests.
256353/// Performs a lightweight storage check to verify the log backend is accessible.
257- pub async fn handle_ready ( State ( state) : State < AppState > ) -> ( axum:: http:: StatusCode , & ' static str ) {
354+ pub ( crate ) async fn handle_ready (
355+ State ( state) : State < AppState > ,
356+ ) -> ( axum:: http:: StatusCode , & ' static str ) {
258357 // Verify storage is accessible with a lightweight read operation.
259358 // This reads the sequence block key, which verifies the storage backend
260359 // is responding without scanning or listing data.
@@ -293,7 +392,10 @@ mod tests {
293392 // given
294393 let log = Arc :: new ( LogDb :: open ( test_config ( ) ) . await . unwrap ( ) ) ;
295394 let metrics = Arc :: new ( Metrics :: new ( ) ) ;
296- let state = AppState { log, metrics } ;
395+ let state = AppState {
396+ log : LogBackend :: ReadWrite ( log) ,
397+ metrics,
398+ } ;
297399
298400 // when
299401 let ( status, body) = handle_ready ( State ( state) ) . await ;
@@ -418,7 +520,10 @@ mod tests {
418520 let storage = Arc :: new ( ToggleFailStorage :: new ( ) ) ;
419521 let log = Arc :: new ( LogDb :: new ( storage. clone ( ) ) . await . unwrap ( ) ) ;
420522 let metrics = Arc :: new ( Metrics :: new ( ) ) ;
421- let state = AppState { log, metrics } ;
523+ let state = AppState {
524+ log : LogBackend :: ReadWrite ( log) ,
525+ metrics,
526+ } ;
422527
423528 // Configure storage to fail after initialization
424529 storage. set_failing ( true ) ;
0 commit comments