1010import java .util .Arrays ;
1111import java .util .Base64 ;
1212import java .util .HashMap ;
13+ import java .util .LinkedHashSet ;
1314import java .util .List ;
1415import java .util .Map ;
16+ import java .util .Set ;
1517import java .util .concurrent .TimeUnit ;
1618import okhttp3 .Call ;
1719import okhttp3 .Callback ;
3335/** Constructor.io Client */
3436public class ConstructorIO {
3537
38+ /** Valid file extensions for catalog uploads */
39+ private static final Set <String > VALID_CATALOG_EXTENSIONS =
40+ new LinkedHashSet <>(Arrays .asList (".csv" , ".json" , ".jsonl" ));
41+
3642 /** the HTTP client used by all instances */
3743 private static OkHttpClient client =
3844 new OkHttpClient .Builder ()
@@ -2041,6 +2047,53 @@ protected static String getResponseBody(Response response) throws ConstructorExc
20412047 throw new ConstructorException (errorMessage , errorCode );
20422048 }
20432049
2050+ /**
2051+ * Validates and extracts the file extension from a File object for catalog uploads.
2052+ *
2053+ * @param file the File object containing the actual file
2054+ * @param fileName the logical file name (items, variations, item_groups)
2055+ * @return the validated file extension (including the dot, e.g., ".csv", ".json", or ".jsonl")
2056+ * @throws ConstructorException if the file extension is not in VALID_CATALOG_EXTENSIONS
2057+ */
2058+ private static String getValidatedFileExtension (File file , String fileName )
2059+ throws ConstructorException {
2060+ if (file == null ) {
2061+ throw new ConstructorException (
2062+ "Invalid file for '" + fileName + "': file cannot be null." );
2063+ }
2064+
2065+ String actualFileName = file .getName ();
2066+ if (actualFileName == null || actualFileName .isEmpty ()) {
2067+ throw new ConstructorException (
2068+ "Invalid file for '" + fileName + "': file name cannot be empty." );
2069+ }
2070+
2071+ int lastDotIndex = actualFileName .lastIndexOf ('.' );
2072+ if (lastDotIndex == -1 || lastDotIndex == actualFileName .length () - 1 ) {
2073+ throw new ConstructorException (
2074+ "Invalid file for '"
2075+ + fileName
2076+ + "': file must have "
2077+ + VALID_CATALOG_EXTENSIONS
2078+ + " extension. Found: "
2079+ + actualFileName );
2080+ }
2081+
2082+ String extension = actualFileName .substring (lastDotIndex ).toLowerCase ();
2083+
2084+ if (!VALID_CATALOG_EXTENSIONS .contains (extension )) {
2085+ throw new ConstructorException (
2086+ "Invalid file type for '"
2087+ + fileName
2088+ + "': file must have "
2089+ + VALID_CATALOG_EXTENSIONS
2090+ + " extension. Found: "
2091+ + actualFileName );
2092+ }
2093+
2094+ return extension ;
2095+ }
2096+
20442097 /**
20452098 * Grabs the version number (hard coded ATM)
20462099 *
@@ -2320,9 +2373,12 @@ protected static JSONArray transformItemsAPIV2Response(JSONArray results) {
23202373 /**
23212374 * Send a full catalog to replace the current one (sync)
23222375 *
2323- * @param req the catalog request
2324- * @return a string of JSON
2325- * @throws ConstructorException if the request is invalid.
2376+ * <p>Supports CSV, JSON, and JSONL file formats. The file type is automatically detected from
2377+ * the file extension (.csv, .json, or .jsonl).
2378+ *
2379+ * @param req the catalog request containing files with .csv, .json, or .jsonl extensions
2380+ * @return a string of JSON containing task information
2381+ * @throws ConstructorException if the request is invalid or file extensions are not supported
23262382 */
23272383 public String replaceCatalog (CatalogRequest req ) throws ConstructorException {
23282384 try {
@@ -2348,10 +2404,11 @@ public String replaceCatalog(CatalogRequest req) throws ConstructorException {
23482404 for (Map .Entry <String , File > entry : files .entrySet ()) {
23492405 String fileName = entry .getKey ();
23502406 File file = entry .getValue ();
2407+ String fileExtension = getValidatedFileExtension (file , fileName );
23512408
23522409 multipartBuilder .addFormDataPart (
23532410 fileName ,
2354- fileName + ".csv" ,
2411+ fileName + fileExtension ,
23552412 RequestBody .create (MediaType .parse ("application/octet-stream" ), file ));
23562413 }
23572414 }
@@ -2377,9 +2434,12 @@ public String replaceCatalog(CatalogRequest req) throws ConstructorException {
23772434 /**
23782435 * Send a partial catalog to update specific items (delta)
23792436 *
2380- * @param req the catalog request
2381- * @return a string of JSON
2382- * @throws ConstructorException if the request is invalid.
2437+ * <p>Supports CSV, JSON, and JSONL file formats. The file type is automatically detected from
2438+ * the file extension (.csv, .json, or .jsonl).
2439+ *
2440+ * @param req the catalog request containing files with .csv, .json, or .jsonl extensions
2441+ * @return a string of JSON containing task information
2442+ * @throws ConstructorException if the request is invalid or file extensions are not supported
23832443 */
23842444 public String updateCatalog (CatalogRequest req ) throws ConstructorException {
23852445 try {
@@ -2405,10 +2465,11 @@ public String updateCatalog(CatalogRequest req) throws ConstructorException {
24052465 for (Map .Entry <String , File > entry : files .entrySet ()) {
24062466 String fileName = entry .getKey ();
24072467 File file = entry .getValue ();
2468+ String fileExtension = getValidatedFileExtension (file , fileName );
24082469
24092470 multipartBuilder .addFormDataPart (
24102471 fileName ,
2411- fileName + ".csv" ,
2472+ fileName + fileExtension ,
24122473 RequestBody .create (MediaType .parse ("application/octet-stream" ), file ));
24132474 }
24142475 }
@@ -2435,9 +2496,12 @@ public String updateCatalog(CatalogRequest req) throws ConstructorException {
24352496 /**
24362497 * Send a patch delta catalog to update specific items (delta)
24372498 *
2438- * @param req the catalog request
2439- * @return a string of JSON
2440- * @throws ConstructorException if the request is invalid.
2499+ * <p>Supports CSV, JSON, and JSONL file formats. The file type is automatically detected from
2500+ * the file extension (.csv, .json, or .jsonl).
2501+ *
2502+ * @param req the catalog request containing files with .csv, .json, or .jsonl extensions
2503+ * @return a string of JSON containing task information
2504+ * @throws ConstructorException if the request is invalid or file extensions are not supported
24412505 */
24422506 public String patchCatalog (CatalogRequest req ) throws ConstructorException {
24432507 try {
@@ -2468,10 +2532,11 @@ public String patchCatalog(CatalogRequest req) throws ConstructorException {
24682532 for (Map .Entry <String , File > entry : files .entrySet ()) {
24692533 String fileName = entry .getKey ();
24702534 File file = entry .getValue ();
2535+ String fileExtension = getValidatedFileExtension (file , fileName );
24712536
24722537 multipartBuilder .addFormDataPart (
24732538 fileName ,
2474- fileName + ".csv" ,
2539+ fileName + fileExtension ,
24752540 RequestBody .create (MediaType .parse ("application/octet-stream" ), file ));
24762541 }
24772542 }
0 commit comments