@@ -539,6 +539,131 @@ public static ErrorRecord createErrorRecord(Map<String, Object> recordMap, int i
539539 return err ;
540540 }
541541
542+ // ── Unary "records"-shaped exception fallback ─────────────────────────────
543+ //
544+ // A unary call's own (only) record can fail outright — e.g. an invalid column on the sole
545+ // record in an update/insert/get/delete request — and the vault reflects that as the overall
546+ // HTTP status, so the generated client throws ApiClientApiException instead of returning a
547+ // normal response body. When the exception body still has the familiar per-record shape
548+ // ({"records": [...]} for insert/update/get/delete, {"response": [...]} for detokenize), the
549+ // failure belongs on the response the same way a 200 partial-success does — not as a thrown
550+ // exception. Each handler below returns null when the body doesn't match that shape, so the
551+ // caller falls back to throwing a SkyflowException as before.
552+
553+ /** Record maps under {@code key} in an exception body, or null if the shape doesn't match. */
554+ private static List <Map <String , Object >> extractExceptionRecords (ApiClientApiException apiException , String key ) {
555+ Object rawBody = apiException .body ();
556+ if (!(rawBody instanceof Map )) {
557+ return null ;
558+ }
559+ Object recordsField = ((Map <?, ?>) rawBody ).get (key );
560+ if (!(recordsField instanceof List )) {
561+ return null ;
562+ }
563+ List <Map <String , Object >> records = new ArrayList <>();
564+ for (Object recordObj : (List <?>) recordsField ) {
565+ if (recordObj instanceof Map ) {
566+ //noinspection unchecked
567+ records .add ((Map <String , Object >) recordObj );
568+ }
569+ }
570+ return records .isEmpty () ? null : records ;
571+ }
572+
573+ public static InsertResponse handleInsertRequestException (ApiClientApiException apiException ) {
574+ List <Map <String , Object >> recordMaps = extractExceptionRecords (apiException , "records" );
575+ if (recordMaps == null ) {
576+ return null ;
577+ }
578+ String requestId = extractRequestId (apiException .headers ());
579+ List <InsertResponseRecord > records = new ArrayList <>();
580+ for (Map <String , Object > recordMap : recordMaps ) {
581+ records .add (new InsertResponseRecord (
582+ readString (recordMap , "tableName" ),
583+ readString (recordMap , "skyflowID" ),
584+ null , null , null ,
585+ readHttpCode (recordMap , apiException .statusCode ()),
586+ readErrorMessage (recordMap ),
587+ requestId ));
588+ }
589+ return new InsertResponse (records );
590+ }
591+
592+ public static UpdateResponse handleUpdateRequestException (ApiClientApiException apiException ) {
593+ List <Map <String , Object >> recordMaps = extractExceptionRecords (apiException , "records" );
594+ if (recordMaps == null ) {
595+ return null ;
596+ }
597+ String requestId = extractRequestId (apiException .headers ());
598+ List <UpdateResponseRecord > records = new ArrayList <>();
599+ for (Map <String , Object > recordMap : recordMaps ) {
600+ records .add (new UpdateResponseRecord (
601+ readString (recordMap , "tableName" ),
602+ readString (recordMap , "skyflowID" ),
603+ null , null , null ,
604+ readHttpCode (recordMap , apiException .statusCode ()),
605+ readErrorMessage (recordMap ),
606+ requestId ));
607+ }
608+ return new UpdateResponse (records );
609+ }
610+
611+ public static GetResponse handleGetRequestException (ApiClientApiException apiException ) {
612+ List <Map <String , Object >> recordMaps = extractExceptionRecords (apiException , "records" );
613+ if (recordMaps == null ) {
614+ return null ;
615+ }
616+ String requestId = extractRequestId (apiException .headers ());
617+ List <GetResponseRecord > records = new ArrayList <>();
618+ for (Map <String , Object > recordMap : recordMaps ) {
619+ records .add (new GetResponseRecord (
620+ readString (recordMap , "tableName" ),
621+ readString (recordMap , "skyflowID" ),
622+ null , null , null ,
623+ readHttpCode (recordMap , apiException .statusCode ()),
624+ readErrorMessage (recordMap ),
625+ requestId ));
626+ }
627+ return new GetResponse (records );
628+ }
629+
630+ public static DeleteResponse handleDeleteRequestException (ApiClientApiException apiException ) {
631+ List <Map <String , Object >> recordMaps = extractExceptionRecords (apiException , "records" );
632+ if (recordMaps == null ) {
633+ return null ;
634+ }
635+ String requestId = extractRequestId (apiException .headers ());
636+ List <DeleteResponseRecord > records = new ArrayList <>();
637+ for (Map <String , Object > recordMap : recordMaps ) {
638+ records .add (new DeleteResponseRecord (
639+ readString (recordMap , "skyflowID" ),
640+ readHttpCode (recordMap , apiException .statusCode ()),
641+ readErrorMessage (recordMap ),
642+ requestId ));
643+ }
644+ return new DeleteResponse (records );
645+ }
646+
647+ public static DetokenizeResponse handleDetokenizeRequestException (ApiClientApiException apiException ) {
648+ List <Map <String , Object >> recordMaps = extractExceptionRecords (apiException , "response" );
649+ if (recordMaps == null ) {
650+ return null ;
651+ }
652+ String requestId = extractRequestId (apiException .headers ());
653+ List <DetokenizeResponseRecord > records = new ArrayList <>();
654+ for (Map <String , Object > recordMap : recordMaps ) {
655+ records .add (new DetokenizeResponseRecord (
656+ readString (recordMap , "token" ),
657+ null ,
658+ readString (recordMap , "tokenGroupName" ),
659+ null ,
660+ readHttpCode (recordMap , apiException .statusCode ()),
661+ readErrorMessage (recordMap ),
662+ requestId ));
663+ }
664+ return new DetokenizeResponse (records );
665+ }
666+
542667 // Errors are parsed into ErrorRecord (shared with the other bulk ops), then projected onto
543668 // the unified BulkInsertResponseRecord shape that bulk insert now returns.
544669 public static List <BulkInsertResponseRecord > handleBulkInsertBatchException (
0 commit comments