Skip to content

Commit 60ba062

Browse files
esezenahmedmaazin
andauthored
feat: Add dynamic file type support for catalog uploads (CSV, JSON, JSONL) additional changes (#178)
* feat: add support for multiple file types in catalog uploads * validates .csv, .json and .jsonl * derive the file extension from the uploaded file. * chore: added relevant tests * Update tests * Utilize set * Use LinkedHashSet * Use generators instead of static files * Lint and cleanup * Utilize existing functions * Cleanup * Lint * Address comments --------- Co-authored-by: Mazin <ahmedmaazin@gmail.com>
1 parent d0708c6 commit 60ba062

3 files changed

Lines changed: 651 additions & 34 deletions

File tree

constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
import java.util.Arrays;
1111
import java.util.Base64;
1212
import java.util.HashMap;
13+
import java.util.LinkedHashSet;
1314
import java.util.List;
1415
import java.util.Map;
16+
import java.util.Set;
1517
import java.util.concurrent.TimeUnit;
1618
import okhttp3.Call;
1719
import okhttp3.Callback;
@@ -33,6 +35,10 @@
3335
/** Constructor.io Client */
3436
public 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

Comments
 (0)