Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main/java/io/endee/client/Collection.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public Map<String, Object> upsert(List<ObjectItem> objects) {
for (ObjectItem item : objects) {
String filterStr = "";
if (item.getFilter() != null && !item.getFilter().isEmpty()) {
ValidationUtils.validateFilter(item.getFilter());
filterStr = JsonUtils.toJson(item.getFilter());
}

Expand Down Expand Up @@ -591,6 +592,9 @@ public Map<String, Object> updateFilters(List<UpdateFilterParams> updates) {

List<Map<String, Object>> payload = new ArrayList<>();
for (UpdateFilterParams update : updates) {
if (update.getFilter() != null) {
ValidationUtils.validateFilter(update.getFilter());
}
Map<String, Object> entry = new HashMap<>();
entry.put("id", update.getId());
entry.put("filter", update.getFilter() != null ? update.getFilter() : Map.of());
Expand Down
31 changes: 24 additions & 7 deletions src/main/java/io/endee/client/Endee.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ public void setToken(String token) {
this.token = token;
}

/** Closes the underlying HTTP client and releases resources. */
public void close() {
// Java's HttpClient doesn't have an explicit close in JDK 17,
// but we null the reference to allow GC
}

@Override
public String toString() {
return "Endee{baseUrl='" + baseUrl + "'}";
}

// ── Collection API ──────────────────────────────────────────────────────────

/**
Expand Down Expand Up @@ -194,17 +205,17 @@ public Map<String, Object> setDatabaseType(String dbName, String dbType) {

// ── Admin collection views ──────────────────────────────────────────────────

/** Lists collections in a specific database. */
/** Lists collection names in a specific database. */
@SuppressWarnings("unchecked")
public List<Map<String, Object>> listDbCollections(String dbName) {
public List<String> listDbCollections(String dbName) {
requireNonEmpty(dbName, "db_name");
Map<String, Object> result =
call("GET", "/admin/dbs/" + dbName + "/collection", null, Set.of(200));
Object c = result.get("collections");
return c instanceof List ? (List<Map<String, Object>>) c : List.of();
return c instanceof List ? (List<String>) c : List.of();
}

/** Lists all collections across all databases. */
/** Lists all collections across all databases (grouped by database). */
@SuppressWarnings("unchecked")
public List<Map<String, Object>> listAllCollections() {
Map<String, Object> result = call("GET", "/admin/collection", null, Set.of(200));
Expand Down Expand Up @@ -332,7 +343,7 @@ public Map<String, Object> restoreBackup(String backupName, String targetCollect
"POST",
"/backup/" + backupName + "/restore",
Map.of("target_collection_name", targetCollectionName),
Set.of(200, 201));
Set.of(200, 201, 202));
}

/** Deletes a backup. */
Expand All @@ -353,6 +364,12 @@ public String downloadBackup(String backupName, String destPath, String dbName)
requireNonEmpty(backupName, "backup_name");
requireNonEmpty(destPath, "dest_path");

Path dest = Path.of(destPath);
if (Files.isDirectory(dest)) {
dest = dest.resolve(backupName + ".tar");
}
String resolvedPath = dest.toString();

StringBuilder url =
new StringBuilder(baseUrl)
.append("/backup/")
Expand All @@ -375,8 +392,8 @@ public String downloadBackup(String backupName, String destPath, String dbName)
if (response.statusCode() != 200) {
EndeeApiException.raiseException(response.statusCode(), new String(response.body()));
}
Files.write(Path.of(destPath), response.body());
return destPath;
Files.write(Path.of(resolvedPath), response.body());
return resolvedPath;
} catch (EndeeException e) {
throw e;
} catch (IOException | InterruptedException e) {
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/io/endee/client/util/ValidationUtils.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package io.endee.client.util;

import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

Expand All @@ -10,6 +12,8 @@ public final class ValidationUtils {

private static final Pattern COLLECTION_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+$");
private static final int MAX_COLLECTION_NAME_LENGTH = 48;
private static final int MAX_FILTER_KEY_BYTES = 128;
private static final int MAX_FILTER_VALUE_BYTES = 1024;

private ValidationUtils() {}

Expand Down Expand Up @@ -50,4 +54,24 @@ public static void validateObjectIds(List<String> ids) {
throw new IllegalArgumentException("Duplicate IDs found: " + String.join(", ", duplicateIds));
}
}

/** Validates filter key/value sizes (key ≤ 128 bytes, value ≤ 1024 bytes). */
public static void validateFilter(Map<String, Object> filter) {
if (filter == null) return;
for (Map.Entry<String, Object> entry : filter.entrySet()) {
String key = entry.getKey();
if (key.getBytes(StandardCharsets.UTF_8).length > MAX_FILTER_KEY_BYTES) {
throw new IllegalArgumentException(
"Filter key '" + key + "' exceeds " + MAX_FILTER_KEY_BYTES + " bytes");
}
Object value = entry.getValue();
if (value != null) {
String valStr = String.valueOf(value);
if (valStr.getBytes(StandardCharsets.UTF_8).length > MAX_FILTER_VALUE_BYTES) {
throw new IllegalArgumentException(
"Filter value for key '" + key + "' exceeds " + MAX_FILTER_VALUE_BYTES + " bytes");
}
}
}
}
}
Loading