@@ -19,45 +19,26 @@ use crate::storage::StoredObjectMeta;
1919const INGEST_MAX_BODY_BYTES : usize = 50 * 1024 * 1024 ;
2020
2121#[ derive( Debug , Deserialize ) ]
22- pub struct SourcemapEntry {
23- #[ serde( alias = "fileName" ) ]
24- pub file_name : String ,
25- pub sourcemap : String ,
26- }
27-
28- #[ derive( Debug , Deserialize ) ]
29- pub struct ProguardEntry {
30- #[ serde( alias = "fileName" ) ]
22+ #[ serde( rename_all = "camelCase" ) ]
23+ pub struct UploadFile {
3124 pub file_name : String ,
32- #[ serde( alias = "content" ) ]
33- pub mapping : String ,
25+ pub content : String ,
3426}
3527
3628#[ derive( Debug , Deserialize ) ]
37- #[ serde( tag = "mappingType " ) ]
29+ #[ serde( tag = "type " ) ]
3830pub enum IngestPayload {
3931 #[ serde( rename = "javascript" ) ]
4032 JavaScript {
41- #[ serde( alias = "buildId" ) ]
4233 build_id : String ,
43- bundler : String ,
44- #[ serde( alias = "uploadedAt" ) ]
4534 uploaded_at : String ,
46- sourcemaps : Vec < SourcemapEntry > ,
35+ files : Vec < UploadFile > ,
4736 } ,
4837 #[ serde( rename = "proguard" ) ]
4938 Proguard {
50- #[ serde( alias = "buildId" ) ]
5139 build_id : String ,
52- #[ serde( alias = "uploadedAt" ) ]
5340 uploaded_at : String ,
54- #[ serde( alias = "fileName" ) ]
55- file_name : Option < String > ,
56- mapping : Option < String > ,
57- #[ serde( default ) ]
58- mappings : Vec < ProguardEntry > ,
59- #[ serde( default ) ]
60- files : Vec < ProguardEntry > ,
41+ files : Vec < UploadFile > ,
6142 } ,
6243}
6344
@@ -157,7 +138,7 @@ pub fn public_router(state: SharedState) -> Router {
157138 Router :: new ( )
158139 . route ( "/health" , get ( health) )
159140 . route (
160- "/api/sourcemaps " ,
141+ "/v0/upload " ,
161142 post ( ingest) . route_layer ( DefaultBodyLimit :: max ( INGEST_MAX_BODY_BYTES ) ) ,
162143 )
163144 . layer ( TraceLayer :: new_for_http ( ) )
@@ -190,49 +171,36 @@ pub async fn ingest(
190171 let ( build_id, uploaded_at, ingested, total_bytes, mapping_type) = match & payload {
191172 IngestPayload :: JavaScript {
192173 build_id,
193- bundler,
194174 uploaded_at,
195- sourcemaps ,
175+ files ,
196176 } => {
197- validate_js_ingest ( build_id, uploaded_at, sourcemaps) ?;
198- let entries: Vec < ( String , String ) > = sourcemaps
199- . iter ( )
200- . map ( |e| ( e. file_name . clone ( ) , e. sourcemap . clone ( ) ) )
201- . collect ( ) ;
177+ let entries = normalize_upload_files ( build_id, uploaded_at, files) ?;
202178 crate :: mappings:: javascript:: ingest ( & state. storage , project_id, build_id, & entries)
203179 . await ?;
204- let total_bytes: usize = sourcemaps. iter ( ) . map ( |e| e. sourcemap . len ( ) ) . sum ( ) ;
205- let file_names: Vec < & str > = sourcemaps. iter ( ) . map ( |e| e. file_name . as_str ( ) ) . collect ( ) ;
180+ let total_bytes: usize = entries. iter ( ) . map ( |( _, content) | content. len ( ) ) . sum ( ) ;
181+ let file_names: Vec < & str > = entries
182+ . iter ( )
183+ . map ( |( file_name, _) | file_name. as_str ( ) )
184+ . collect ( ) ;
206185 info ! (
207186 %project_id,
208187 build_id,
209- %bundler,
210188 files = ?file_names,
211189 ) ;
212190 (
213191 build_id. as_str ( ) ,
214192 uploaded_at. as_str ( ) ,
215- sourcemaps . len ( ) ,
193+ entries . len ( ) ,
216194 total_bytes,
217195 "javascript" ,
218196 )
219197 }
220198 IngestPayload :: Proguard {
221199 build_id,
222200 uploaded_at,
223- mapping,
224- file_name,
225- mappings,
226201 files,
227202 } => {
228- let mappings = normalize_proguard_entries (
229- build_id,
230- uploaded_at,
231- file_name,
232- mapping,
233- mappings,
234- files,
235- ) ?;
203+ let mappings = normalize_upload_files ( build_id, uploaded_at, files) ?;
236204 crate :: mappings:: proguard:: ingest ( & state. storage , project_id, build_id, & mappings)
237205 . await ?;
238206 let total_bytes: usize = mappings. iter ( ) . map ( |( _, mapping) | mapping. len ( ) ) . sum ( ) ;
@@ -463,68 +431,23 @@ pub async fn cleanup_old_builds(
463431 } ) )
464432}
465433
466- fn validate_js_ingest (
434+ fn normalize_upload_files (
467435 build_id : & str ,
468436 uploaded_at : & str ,
469- sourcemaps : & [ SourcemapEntry ] ,
470- ) -> Result < ( ) , AppError > {
471- require_non_empty ( "build_id" , build_id) ?;
472- require_non_empty ( "uploaded_at" , uploaded_at) ?;
473- if sourcemaps. is_empty ( ) {
474- return Err ( AppError :: BadRequest ( "no sourcemaps provided" . into ( ) ) ) ;
475- }
476- for entry in sourcemaps {
477- require_non_empty ( "file_name" , & entry. file_name ) ?;
478- require_non_empty ( "sourcemap" , & entry. sourcemap ) ?;
479- }
480- Ok ( ( ) )
481- }
482-
483- fn normalize_proguard_entries (
484- build_id : & str ,
485- uploaded_at : & str ,
486- file_name : & Option < String > ,
487- mapping : & Option < String > ,
488- mappings : & [ ProguardEntry ] ,
489- files : & [ ProguardEntry ] ,
437+ files : & [ UploadFile ] ,
490438) -> Result < Vec < ( String , String ) > , AppError > {
491439 require_non_empty ( "build_id" , build_id) ?;
492440 require_non_empty ( "uploaded_at" , uploaded_at) ?;
493-
494- let mut normalized =
495- Vec :: with_capacity ( mappings. len ( ) + files. len ( ) + usize:: from ( mapping. is_some ( ) ) ) ;
496- normalized. extend (
497- mappings
498- . iter ( )
499- . map ( |entry| ( entry. file_name . clone ( ) , entry. mapping . clone ( ) ) ) ,
500- ) ;
501- normalized. extend (
502- files
503- . iter ( )
504- . map ( |entry| ( entry. file_name . clone ( ) , entry. mapping . clone ( ) ) ) ,
505- ) ;
506-
507- match ( file_name. as_deref ( ) , mapping. as_deref ( ) ) {
508- ( Some ( file_name) , Some ( mapping) ) => {
509- normalized. push ( ( file_name. to_string ( ) , mapping. to_string ( ) ) ) ;
510- }
511- ( Some ( _) , None ) => return Err ( AppError :: BadRequest ( "mapping is required" . into ( ) ) ) ,
512- ( None , Some ( mapping) ) if normalized. is_empty ( ) => {
513- normalized. push ( ( "mapping.txt" . to_string ( ) , mapping. to_string ( ) ) ) ;
514- }
515- ( None , Some ( _) ) => return Err ( AppError :: BadRequest ( "file_name is required" . into ( ) ) ) ,
516- ( None , None ) => { }
517- }
518-
519- if normalized. is_empty ( ) {
520- return Err ( AppError :: BadRequest ( "no proguard mappings provided" . into ( ) ) ) ;
441+ if files. is_empty ( ) {
442+ return Err ( AppError :: BadRequest ( "no files provided" . into ( ) ) ) ;
521443 }
522444
523- for ( file_name, mapping) in & normalized {
524- require_non_empty ( "file_name" , file_name) ?;
525- require_non_empty ( "mapping" , mapping) ?;
445+ let mut normalized = Vec :: with_capacity ( files. len ( ) ) ;
446+ for entry in files {
447+ require_non_empty ( "file_name" , & entry. file_name ) ?;
448+ require_non_empty ( "content" , & entry. content ) ?;
449+ normalized. push ( ( entry. file_name . clone ( ) , entry. content . clone ( ) ) ) ;
526450 }
527-
528451 Ok ( normalized)
529452}
530453
@@ -600,7 +523,7 @@ fn select_builds_for_cleanup(
600523#[ cfg( test) ]
601524mod tests {
602525 use super :: {
603- ProguardEntry , normalize_proguard_entries , normalized_build_ids, parse_sourcemap_key,
526+ UploadFile , normalize_upload_files , normalized_build_ids, parse_sourcemap_key,
604527 select_builds_for_cleanup, validate_project_sourcemap_key,
605528 } ;
606529 use crate :: mappings:: { javascript:: map_file_name, require_non_empty} ;
@@ -674,20 +597,20 @@ mod tests {
674597 }
675598
676599 #[ test]
677- fn normalize_proguard_entries_accepts_multiple_named_files ( ) {
678- let normalized = normalize_proguard_entries (
600+ fn normalize_upload_files_accepts_multiple_named_files ( ) {
601+ let normalized = normalize_upload_files (
679602 "build-1" ,
680603 "2026-03-22T00:00:00Z" ,
681- & None ,
682- & None ,
683- & [ ProguardEntry {
684- file_name : "base.txt " . to_string ( ) ,
685- mapping : "one" . to_string ( ) ,
686- } ] ,
687- & [ ProguardEntry {
688- file_name : "feature.txt " . to_string ( ) ,
689- mapping : "two" . to_string ( ) ,
690- } ] ,
604+ & [
605+ UploadFile {
606+ file_name : "base.txt" . to_string ( ) ,
607+ content : "one " . to_string ( ) ,
608+ } ,
609+ UploadFile {
610+ file_name : "feature.txt" . to_string ( ) ,
611+ content : "two " . to_string ( ) ,
612+ } ,
613+ ] ,
691614 )
692615 . expect ( "entries should normalize" ) ;
693616
@@ -701,39 +624,26 @@ mod tests {
701624 }
702625
703626 #[ test]
704- fn normalize_proguard_entries_keeps_legacy_single_mapping_compatible ( ) {
705- let normalized = normalize_proguard_entries (
706- "build-1" ,
707- "2026-03-22T00:00:00Z" ,
708- & None ,
709- & Some ( "contents" . to_string ( ) ) ,
710- & [ ] ,
711- & [ ] ,
712- )
713- . expect ( "legacy payload should normalize" ) ;
627+ fn normalize_upload_files_rejects_empty_payload ( ) {
628+ let err = normalize_upload_files ( "build-1" , "2026-03-22T00:00:00Z" , & [ ] )
629+ . expect_err ( "empty payload should fail" ) ;
714630
715- assert_eq ! (
716- normalized,
717- vec![ ( "mapping.txt" . to_string( ) , "contents" . to_string( ) ) ]
718- ) ;
631+ assert ! ( format!( "{err}" ) . contains( "no files provided" ) ) ;
719632 }
720633
721634 #[ test]
722- fn normalize_proguard_entries_rejects_mixed_unnamed_mapping ( ) {
723- let err = normalize_proguard_entries (
635+ fn normalize_upload_files_rejects_empty_content ( ) {
636+ let err = normalize_upload_files (
724637 "build-1" ,
725638 "2026-03-22T00:00:00Z" ,
726- & None ,
727- & Some ( "contents" . to_string ( ) ) ,
728- & [ ProguardEntry {
639+ & [ UploadFile {
729640 file_name : "base.txt" . to_string ( ) ,
730- mapping : "one " . to_string ( ) ,
641+ content : " " . to_string ( ) ,
731642 } ] ,
732- & [ ] ,
733643 )
734- . expect_err ( "mixed unnamed mapping should be rejected" ) ;
644+ . expect_err ( "blank file content should be rejected" ) ;
735645
736- assert ! ( format!( "{err}" ) . contains( "file_name is required" ) ) ;
646+ assert ! ( format!( "{err}" ) . contains( "content is required" ) ) ;
737647 }
738648
739649 #[ test]
0 commit comments