@@ -51,14 +51,91 @@ struct ExportEnvelope {
5151 messages : Vec < ExportedMessage > ,
5252}
5353
54- async fn export_messages ( State ( repo) : State < JournalRepository > ) -> Response {
55- let records = match repo. fetch_all_for_export ( ) . await {
56- Ok ( records) => records,
57- Err ( err) => {
58- error ! ( error = %err, "failed to fetch journal entries for export" ) ;
59- return ( StatusCode :: INTERNAL_SERVER_ERROR , "failed to load messages" ) . into_response ( ) ;
54+ #[ derive( Debug ) ]
55+ enum DashboardError {
56+ ExportRepository ( sqlx:: Error ) ,
57+ Serialization ( serde_json:: Error ) ,
58+ UnsupportedVersion {
59+ version : u32 ,
60+ } ,
61+ ImportConflict {
62+ source : String ,
63+ source_conversation_id : String ,
64+ source_message_id : String ,
65+ } ,
66+ ImportRepository ( sqlx:: Error ) ,
67+ }
68+
69+ impl IntoResponse for DashboardError {
70+ fn into_response ( self ) -> Response {
71+ match self {
72+ Self :: ExportRepository ( err) => {
73+ error ! ( error = %err, "failed to fetch journal entries for export" ) ;
74+ ( StatusCode :: INTERNAL_SERVER_ERROR , "failed to load messages" ) . into_response ( )
75+ }
76+ Self :: Serialization ( err) => {
77+ error ! ( error = %err, "failed to serialize journal entries for export" ) ;
78+ (
79+ StatusCode :: INTERNAL_SERVER_ERROR ,
80+ "failed to serialize messages" ,
81+ )
82+ . into_response ( )
83+ }
84+ Self :: UnsupportedVersion { version } => {
85+ (
86+ StatusCode :: BAD_REQUEST ,
87+ Json ( ImportError {
88+ error : format ! (
89+ "unsupported export version {} (supported {}..={})" ,
90+ version, MIN_SUPPORTED_IMPORT_VERSION , EXPORT_FORMAT_VERSION
91+ ) ,
92+ conflict : None ,
93+ } ) ,
94+ )
95+ . into_response ( )
96+ }
97+ Self :: ImportConflict {
98+ source,
99+ source_conversation_id,
100+ source_message_id,
101+ } => {
102+ (
103+ StatusCode :: CONFLICT ,
104+ Json ( ImportError {
105+ error : format ! (
106+ "import aborted: entry ({source}, {source_conversation_id}, {source_message_id}) collides with an existing message"
107+ ) ,
108+ conflict : Some ( ConflictDetails {
109+ source : source. clone ( ) ,
110+ source_conversation_id : source_conversation_id. clone ( ) ,
111+ source_message_id : source_message_id. clone ( ) ,
112+ } ) ,
113+ } ) ,
114+ )
115+ . into_response ( )
116+ }
117+ Self :: ImportRepository ( err) => {
118+ error ! ( error = %err, "failed to import journal entries" ) ;
119+ (
120+ StatusCode :: INTERNAL_SERVER_ERROR ,
121+ Json ( ImportError {
122+ error : "failed to import messages" . to_string ( ) ,
123+ conflict : None ,
124+ } ) ,
125+ )
126+ . into_response ( )
127+ }
60128 }
61- } ;
129+ }
130+ }
131+
132+ async fn export_messages (
133+ State ( repo) : State < JournalRepository > ,
134+ ) -> Result < impl IntoResponse , DashboardError > {
135+ let records = repo
136+ . fetch_all_for_export ( )
137+ . await
138+ . map_err ( DashboardError :: ExportRepository ) ?;
62139
63140 let envelope = ExportEnvelope {
64141 version : EXPORT_FORMAT_VERSION ,
@@ -76,24 +153,14 @@ async fn export_messages(State(repo): State<JournalRepository>) -> Response {
76153 . collect ( ) ,
77154 } ;
78155
79- let body = match serde_json:: to_vec ( & envelope) {
80- Ok ( body) => body,
81- Err ( err) => {
82- error ! ( error = %err, "failed to serialize journal entries for export" ) ;
83- return (
84- StatusCode :: INTERNAL_SERVER_ERROR ,
85- "failed to serialize messages" ,
86- )
87- . into_response ( ) ;
88- }
89- } ;
156+ let body = serde_json:: to_vec ( & envelope) . map_err ( DashboardError :: Serialization ) ?;
90157
91158 let filename = format ! (
92159 "froid-messages-{}.json" ,
93160 envelope. exported_at. format( "%Y-%m-%d" )
94161 ) ;
95162
96- (
163+ Ok ( (
97164 StatusCode :: OK ,
98165 [
99166 ( header:: CONTENT_TYPE , "application/json" . to_string ( ) ) ,
@@ -103,8 +170,7 @@ async fn export_messages(State(repo): State<JournalRepository>) -> Response {
103170 ) ,
104171 ] ,
105172 body,
106- )
107- . into_response ( )
173+ ) )
108174}
109175
110176#[ derive( Deserialize ) ]
@@ -149,23 +215,15 @@ struct ImportError {
149215async fn import_messages (
150216 State ( repo) : State < JournalRepository > ,
151217 Json ( envelope) : Json < ImportEnvelope > ,
152- ) -> Response {
218+ ) -> Result < impl IntoResponse , DashboardError > {
153219 if envelope. version < MIN_SUPPORTED_IMPORT_VERSION || envelope. version > EXPORT_FORMAT_VERSION {
154- return (
155- StatusCode :: BAD_REQUEST ,
156- Json ( ImportError {
157- error : format ! (
158- "unsupported export version {} (supported {}..={})" ,
159- envelope. version, MIN_SUPPORTED_IMPORT_VERSION , EXPORT_FORMAT_VERSION
160- ) ,
161- conflict : None ,
162- } ) ,
163- )
164- . into_response ( ) ;
220+ return Err ( DashboardError :: UnsupportedVersion {
221+ version : envelope. version ,
222+ } ) ;
165223 }
166224
167225 if envelope. messages . is_empty ( ) {
168- return ( StatusCode :: OK , Json ( ImportResult { imported : 0 } ) ) . into_response ( ) ;
226+ return Ok ( ( StatusCode :: OK , Json ( ImportResult { imported : 0 } ) ) . into_response ( ) ) ;
169227 }
170228
171229 let records: Vec < JournalEntryRecord > = envelope
@@ -181,38 +239,20 @@ async fn import_messages(
181239 } )
182240 . collect ( ) ;
183241
184- match repo. bulk_import ( & records) . await {
185- Ok ( imported) => ( StatusCode :: OK , Json ( ImportResult { imported } ) ) . into_response ( ) ,
186- Err ( BulkImportError :: Conflict {
242+ let imported = repo. bulk_import ( & records) . await . map_err ( |err| match err {
243+ BulkImportError :: Conflict {
187244 source,
188245 source_conversation_id,
189246 source_message_id,
190- } ) => (
191- StatusCode :: CONFLICT ,
192- Json ( ImportError {
193- error : format ! (
194- "import aborted: entry ({source}, {source_conversation_id}, {source_message_id}) collides with an existing message"
195- ) ,
196- conflict : Some ( ConflictDetails {
197- source,
198- source_conversation_id,
199- source_message_id,
200- } ) ,
201- } ) ,
202- )
203- . into_response ( ) ,
204- Err ( BulkImportError :: Database ( err) ) => {
205- error ! ( error = %err, "failed to import journal entries" ) ;
206- (
207- StatusCode :: INTERNAL_SERVER_ERROR ,
208- Json ( ImportError {
209- error : "failed to import messages" . to_string ( ) ,
210- conflict : None ,
211- } ) ,
212- )
213- . into_response ( )
214- }
215- }
247+ } => DashboardError :: ImportConflict {
248+ source,
249+ source_conversation_id,
250+ source_message_id,
251+ } ,
252+ BulkImportError :: Database ( e) => DashboardError :: ImportRepository ( e) ,
253+ } ) ?;
254+
255+ Ok ( ( StatusCode :: OK , Json ( ImportResult { imported } ) ) . into_response ( ) )
216256}
217257
218258#[ derive( Serialize ) ]
@@ -564,7 +604,7 @@ mod tests {
564604 let parsed: Value = serde_json:: from_slice ( & body) . unwrap ( ) ;
565605 assert_eq ! ( parsed[ "imported" ] , 2 ) ;
566606
567- let entries = repo. fetch_recent ( "7" , 10 ) . await . unwrap ( ) ;
607+ let entries = repo. fetch_recent ( 10 ) . await . unwrap ( ) ;
568608 assert_eq ! ( entries. len( ) , 2 ) ;
569609 }
570610
@@ -616,7 +656,7 @@ mod tests {
616656 assert_eq ! ( parsed[ "conflict" ] [ "source_conversation_id" ] , "42" ) ;
617657 assert_eq ! ( parsed[ "conflict" ] [ "source_message_id" ] , "dup" ) ;
618658
619- let entries = repo. fetch_recent ( "7" , 10 ) . await . unwrap ( ) ;
659+ let entries = repo. fetch_recent ( 10 ) . await . unwrap ( ) ;
620660 assert_eq ! ( entries. len( ) , 1 ) ;
621661 assert_eq ! ( entries[ 0 ] . entry. text, "existing" ) ;
622662 }
0 commit comments