@@ -191,8 +191,8 @@ impl ClassifiedFiles {
191191 }
192192}
193193
194- pub async fn run ( base : BaseArgs , args : PushArgs ) -> Result < ( ) > {
195- if let Some ( manifest_path) = args. manifest . clone ( ) {
194+ pub async fn run ( base : BaseArgs , mut args : PushArgs ) -> Result < ( ) > {
195+ if let Some ( manifest_path) = args. manifest . take ( ) {
196196 return run_manifest_push ( base, args, manifest_path) . await ;
197197 }
198198
@@ -692,21 +692,15 @@ struct FileSuccess {
692692 bundle_id : Option < String > ,
693693}
694694
695- /// Push a raw JSON manifest of function definitions, skipping the SDK runner.
696- ///
697- /// Use case: function types that the Braintrust SDK doesn't expose builders
698- /// for (preprocessor with inline-quickjs runtime, facet, classifier). The
699- /// manifest body is posted directly to /insert-functions, the same endpoint
700- /// the SDK-runner path uses after scraping registrations.
695+ /// Push function definitions from a raw JSON manifest, skipping the SDK runner.
696+ /// Used for function types the SDK has no builder for (facet, classifier,
697+ /// inline-quickjs preprocessor).
701698async fn run_manifest_push (
702699 base : BaseArgs ,
703700 args : PushArgs ,
704701 manifest_path : PathBuf ,
705702) -> Result < ( ) > {
706- let auth_ctx = match resolve_auth_context ( & base)
707- . await
708- . context ( "failed to resolve auth context" )
709- {
703+ let auth_ctx = match resolve_auth_context ( & base) . await {
710704 Ok ( ctx) => ctx,
711705 Err ( err) => {
712706 return fail_push (
@@ -745,9 +739,9 @@ async fn run_manifest_push(
745739 }
746740 } ;
747741
748- let entries: Vec < Value > = match parsed {
742+ let mut entries: Vec < Value > = match parsed {
749743 Value :: Array ( arr) => arr,
750- Value :: Object ( _) => vec ! [ parsed ] ,
744+ obj @ Value :: Object ( _) => vec ! [ obj ] ,
751745 _ => {
752746 return fail_push (
753747 & base,
@@ -769,46 +763,29 @@ async fn run_manifest_push(
769763 ) ;
770764 }
771765
766+ // `-p` is a project name by convention (see resolve_project_context_optional);
767+ // used as fallback for entries that don't carry their own project_id / name.
772768 let mut project_name_cache = BTreeMap :: new ( ) ;
773- let mut function_events: Vec < Value > = Vec :: with_capacity ( entries. len ( ) ) ;
774-
775- // Resolve the CLI `-p` flag once. The flag is treated as a project name
776- // (the convention shared by every other `bt` subcommand — see
777- // resolve_project_context_optional in functions/mod.rs). Used as fallback
778- // when a manifest entry doesn't carry its own project_id / project_name.
779- let cli_project_default_id = match super :: resolve_project_context_optional (
780- & base, & auth_ctx, false ,
781- )
782- . await
783- {
784- Ok ( Some ( project) ) => {
785- project_name_cache. insert ( project. name . clone ( ) , project. id . clone ( ) ) ;
786- Some ( project. id )
787- }
788- Ok ( None ) => None ,
789- Err ( err) => {
790- return fail_push (
791- & base,
792- 0 ,
793- HardFailureReason :: ManifestSchemaInvalid ,
794- format ! ( "failed to resolve --project: {err:#}" ) ,
795- "failed to resolve CLI project flag" ,
796- ) ;
797- }
798- } ;
799-
800- for ( index, mut entry) in entries. into_iter ( ) . enumerate ( ) {
801- if !entry. is_object ( ) {
802- return fail_push (
803- & base,
804- 0 ,
805- HardFailureReason :: ManifestSchemaInvalid ,
806- format ! ( "manifest entry {index} is not a JSON object" ) ,
807- "manifest entry must be a JSON object" ,
808- ) ;
809- }
769+ let cli_project_default_id =
770+ match super :: resolve_project_context_optional ( & base, & auth_ctx, false ) . await {
771+ Ok ( Some ( project) ) => {
772+ project_name_cache. insert ( project. name . clone ( ) , project. id . clone ( ) ) ;
773+ Some ( project. id )
774+ }
775+ Ok ( None ) => None ,
776+ Err ( err) => {
777+ return fail_push (
778+ & base,
779+ 0 ,
780+ HardFailureReason :: ManifestSchemaInvalid ,
781+ format ! ( "failed to resolve --project: {err:#}" ) ,
782+ "failed to resolve CLI project flag" ,
783+ ) ;
784+ }
785+ } ;
810786
811- if let Err ( err) = validate_manifest_entry ( & entry, index) {
787+ for ( index, entry) in entries. iter_mut ( ) . enumerate ( ) {
788+ if let Err ( err) = validate_manifest_entry ( entry, index) {
812789 return fail_push (
813790 & base,
814791 0 ,
@@ -818,23 +795,11 @@ async fn run_manifest_push(
818795 ) ;
819796 }
820797
821- let entry_project_id = entry
822- . get ( "project_id" )
823- . and_then ( Value :: as_str)
824- . map ( str:: trim)
825- . filter ( |value| !value. is_empty ( ) )
826- . map ( ToOwned :: to_owned) ;
827- let entry_project_name = entry
828- . get ( "project_name" )
829- . and_then ( Value :: as_str)
830- . map ( str:: trim)
831- . filter ( |value| !value. is_empty ( ) )
832- . map ( ToOwned :: to_owned) ;
833- let resolved_project_id = match resolve_project_id (
798+ let project_id = match resolve_project_id (
834799 & auth_ctx. client ,
835800 cli_project_default_id. as_deref ( ) ,
836- entry_project_id . as_deref ( ) ,
837- entry_project_name . as_deref ( ) ,
801+ entry . get ( "project_id" ) . and_then ( Value :: as_str ) ,
802+ entry . get ( "project_name" ) . and_then ( Value :: as_str ) ,
838803 & mut project_name_cache,
839804 args. create_missing_projects ,
840805 )
@@ -852,25 +817,18 @@ async fn run_manifest_push(
852817 }
853818 } ;
854819
855- if let Some ( object) = entry. as_object_mut ( ) {
856- object. insert ( "project_id" . to_string ( ) , Value :: String ( resolved_project_id) ) ;
857- object. remove ( "project_name" ) ;
858- if object. get ( "if_exists" ) . is_none ( ) {
859- object. insert (
860- "if_exists" . to_string ( ) ,
861- Value :: String ( args. if_exists . as_str ( ) . to_string ( ) ) ,
862- ) ;
863- }
864- }
865-
866- function_events. push ( entry) ;
820+ let object = entry. as_object_mut ( ) . expect ( "validated as object above" ) ;
821+ object. insert ( "project_id" . to_string ( ) , Value :: String ( project_id) ) ;
822+ object. remove ( "project_name" ) ;
823+ object
824+ . entry ( "if_exists" . to_string ( ) )
825+ . or_insert_with ( || Value :: String ( args. if_exists . as_str ( ) . to_string ( ) ) ) ;
867826 }
868827
869828 if !args. yes && is_interactive ( ) {
870- let count = function_events. len ( ) ;
871829 let prompt = format ! (
872- "Push {count} manifest entr{} from {} to {}?" ,
873- if count == 1 { "y" } else { "ies" } ,
830+ "Push {} entries from {} to {}?" ,
831+ entries . len ( ) ,
874832 manifest_path. display( ) ,
875833 current_org_label( & auth_ctx) ,
876834 ) ;
@@ -879,12 +837,11 @@ async fn run_manifest_push(
879837 . default ( false )
880838 . interact ( ) ?;
881839 if !confirmed {
882- return cancel_push ( & base, & [ manifest_path. clone ( ) ] ) ;
840+ return cancel_push ( & base, & [ manifest_path] ) ;
883841 }
884842 }
885843
886- let total_entries = function_events. len ( ) ;
887- let insert_result = match api:: insert_functions ( & auth_ctx. client , & function_events) . await {
844+ let insert_result = match api:: insert_functions ( & auth_ctx. client , & entries) . await {
888845 Ok ( result) => result,
889846 Err ( err) => {
890847 return fail_push (
@@ -901,7 +858,7 @@ async fn run_manifest_push(
901858 } ;
902859
903860 let ( uploaded_entries, ignored_entries) =
904- calculate_upload_counts ( total_entries , insert_result. ignored_entries ) ;
861+ calculate_upload_counts ( entries . len ( ) , insert_result. ignored_entries ) ;
905862
906863 let summary = PushSummary {
907864 status : CommandStatus :: Success ,
@@ -923,14 +880,11 @@ async fn run_manifest_push(
923880 errors : vec ! [ ] ,
924881 } ;
925882
926- let use_progress =
927- !base. json && std:: io:: stderr ( ) . is_terminal ( ) && animations_enabled ( ) && !is_quiet ( ) ;
928- if use_progress {
883+ if !base. json && std:: io:: stderr ( ) . is_terminal ( ) && animations_enabled ( ) && !is_quiet ( ) {
929884 eprintln ! (
930- "{} Successfully pushed {} entr{} from {}" ,
885+ "{} Pushed {} entries from {}" ,
931886 style( "✓" ) . green( ) ,
932887 uploaded_entries,
933- if uploaded_entries == 1 { "y" } else { "ies" } ,
934888 manifest_path. display( ) ,
935889 ) ;
936890 }
@@ -939,28 +893,22 @@ async fn run_manifest_push(
939893 Ok ( ( ) )
940894}
941895
942- /// Validate the minimum required fields on a manifest entry.
943- ///
944- /// Required: `name`, `slug`, `function_type`, `function_data` (object).
945- /// Project may come from `project_id` / `project_name` on the entry, or
946- /// fall back to the CLI's `-p` flag.
947896fn validate_manifest_entry ( entry : & Value , index : usize ) -> Result < ( ) > {
948897 let object = entry
949898 . as_object ( )
950899 . ok_or_else ( || anyhow ! ( "entry {index} is not a JSON object" ) ) ?;
951900
952901 for required in [ "name" , "slug" , "function_type" ] {
953- let value = object
902+ let present = object
954903 . get ( required)
955904 . and_then ( Value :: as_str)
956- . map ( str:: trim)
957- . filter ( |value| !value. is_empty ( ) ) ;
958- if value. is_none ( ) {
905+ . is_some_and ( |value| !value. trim ( ) . is_empty ( ) ) ;
906+ if !present {
959907 bail ! ( "entry {index} is missing required string field '{required}'" ) ;
960908 }
961909 }
962910
963- if !object. get ( "function_data" ) . map_or ( false , Value :: is_object) {
911+ if !object. get ( "function_data" ) . is_some_and ( Value :: is_object) {
964912 bail ! ( "entry {index} is missing required object field 'function_data'" ) ;
965913 }
966914
0 commit comments