parseDelimitedParams(QueryParamsMetadata que
params.add(QueryParameter.of(queryParamsMetadata.name, Utils.valToString(value), queryParamsMetadata.allowReserved));
break;
}
- Optional> openEnumValue = Reflections.getOpenEnumValue(value.getClass(), value);
- if (openEnumValue.isPresent()) {
- params.add(QueryParameter.of(queryParamsMetadata.name, Utils.valToString(openEnumValue.get()), queryParamsMetadata.allowReserved));
+ Optional> unwrappedEnumValue = Reflections.getUnwrappedEnumValue(value.getClass(), value);
+ if (unwrappedEnumValue.isPresent()) {
+ params.add(QueryParameter.of(queryParamsMetadata.name, Utils.valToString(unwrappedEnumValue.get()), queryParamsMetadata.allowReserved));
break;
}
Field[] fields = value.getClass().getDeclaredFields();
diff --git a/src/main/java/com/glean/api_client/glean_api_client/utils/Reflections.java b/src/main/java/com/glean/api_client/glean_api_client/utils/Reflections.java
index 3526fbcc..5c3ebdbd 100644
--- a/src/main/java/com/glean/api_client/glean_api_client/utils/Reflections.java
+++ b/src/main/java/com/glean/api_client/glean_api_client/utils/Reflections.java
@@ -16,13 +16,13 @@
public class Reflections {
/**
- * Extracts the underlying value from an open enum instance if the class follows the open enum pattern.
+ * Extracts the underlying value from an enum wrapper instance if the class follows the enum wrapper pattern.
*
- * An open enum is a class that emulates enum behavior but can handle unknown values
+ *
An enum wrapper is a class that emulates enum behavior but can handle unknown values
* without runtime errors. This pattern is commonly used for API responses where new
* enum values might be added over time.
*
- *
The method validates that the class follows the open enum pattern by checking for:
+ *
The method validates that the class follows the enum wrapper pattern by checking for:
*
* - A static factory method {@code of(String)} or {@code of(Integer)} that returns the class type
* - An instance method {@code value()} returning String or Integer
@@ -32,12 +32,12 @@ public class Reflections {
* If all validation passes, the method invokes the {@code value()} method on the provided instance
* and returns the result.
*
- * @param clazz the class to examine for open enum pattern
- * @param instance the instance of the open enum class from which to extract the value
+ * @param clazz the class to examine for enum wrapper pattern
+ * @param instance the instance of the enum wrapper class from which to extract the value
* @return {@code Optional>} containing the extracted value (String or Integer) if the class
- * follows the open enum pattern and the value extraction succeeds, {@code Optional.empty()} otherwise
+ * follows the enum wrapper pattern and the value extraction succeeds, {@code Optional.empty()} otherwise
*/
- public static Optional> getOpenEnumValue(Class> clazz, Object instance) {
+ public static Optional> getUnwrappedEnumValue(Class> clazz, Object instance) {
Objects.requireNonNull(clazz, "Class cannot be null");
try {
@@ -66,6 +66,59 @@ public static Optional> getOpenEnumValue(Class> clazz, Object instance) {
}
}
+ /**
+ * Checks if the given class is an enum wrapper.
+ *
+ *
An enum wrapper is a class that emulates enum behavior but can handle unknown values
+ * without runtime errors. This pattern is commonly used for API responses where new
+ * enum values might be added over time.
+ *
+ *
The method validates that the class follows the enum wrapper pattern by checking for:
+ *
+ * - A static factory method {@code of(String)} or {@code of(Integer)} that returns the class type
+ * - An instance method {@code value()} returning String or Integer
+ * - At least one public static final field of the same class type (predefined constants)
+ *
+ *
+ * @param clazz the class to examine for enum wrapper pattern
+ * @return true if the class is an enum wrapper, false otherwise
+ */
+ public static boolean isEnumWrapper(Class> clazz) {
+ if (clazz == null) {
+ return false;
+ }
+
+ try {
+ // Check for factory method of(String) or of(Integer)
+ boolean hasFactoryMethod = Arrays.stream(clazz.getDeclaredMethods())
+ .anyMatch(method -> isValidFactoryMethod(method, clazz));
+ if (!hasFactoryMethod) {
+ return false;
+ }
+
+ // Check for at least one static constant of same type
+ if (!hasStaticConstants(clazz)) {
+ return false;
+ }
+
+ // Check for value() method returning String or Integer
+ Method valueMethod = clazz.getMethod("value");
+ return isValidValueMethod(valueMethod);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the given object is an instance of an enum wrapper class.
+ *
+ * @param obj the object to check
+ * @return true if the object is an instance of an enum wrapper class, false otherwise
+ */
+ public static boolean isEnumWrapper(Object obj) {
+ return obj != null && isEnumWrapper(obj.getClass());
+ }
+
private static boolean isNumericType(Class> type) {
// Primitive numeric types
if (type.isPrimitive()) {
@@ -84,7 +137,7 @@ private static boolean isNumericType(Class> type) {
}
/**
- * Checks if the given method is a valid factory method for an open enum.
+ * Checks if the given method is a valid factory method for an enum wrapper.
*
* @param method the method to check
* @param clazz the class that should be returned by the factory method
@@ -108,7 +161,7 @@ private static boolean isValidFactoryMethod(Method method, Class> clazz) {
}
/**
- * Checks if the given method is a valid value() method for an open enum.
+ * Checks if the given method is a valid value() method for an enum wrapper.
*
* @param method the value() method to validate
* @return true if valid value method
diff --git a/src/main/java/com/glean/api_client/glean_api_client/utils/RequestBody.java b/src/main/java/com/glean/api_client/glean_api_client/utils/RequestBody.java
index 6237922e..f78aaa50 100644
--- a/src/main/java/com/glean/api_client/glean_api_client/utils/RequestBody.java
+++ b/src/main/java/com/glean/api_client/glean_api_client/utils/RequestBody.java
@@ -254,6 +254,13 @@ public static SerializedBody serializeFormData(Object value)
} else {
switch (Types.getType(val.getClass())) {
case OBJECT: {
+ // Check if it's an enum wrapper first
+ Optional> unwrappedEnumValue = Reflections.getUnwrappedEnumValue(val.getClass(), val);
+ if (unwrappedEnumValue.isPresent()) {
+ params.add(new NameValue(metadata.name, Utils.valToString(unwrappedEnumValue.get())));
+ break;
+ }
+
if (!Utils.allowIntrospection(val.getClass())) {
params.add(new NameValue(metadata.name, String.valueOf(val)));
} else {
diff --git a/src/main/java/com/glean/api_client/glean_api_client/utils/Utils.java b/src/main/java/com/glean/api_client/glean_api_client/utils/Utils.java
index e7683bc7..7ba7c925 100644
--- a/src/main/java/com/glean/api_client/glean_api_client/utils/Utils.java
+++ b/src/main/java/com/glean/api_client/glean_api_client/utils/Utils.java
@@ -180,10 +180,10 @@ public static String generateURL(Class type, String baseURL, String path,
pathParams.put(pathParamsMetadata.name, pathEncode(valToString(value), pathParamsMetadata.allowReserved));
break;
}
- Optional> openEnumValue = Reflections.getOpenEnumValue(value.getClass(), value);
- if (openEnumValue.isPresent()) {
+ Optional> unwrappedEnumValue = Reflections.getUnwrappedEnumValue(value.getClass(), value);
+ if (unwrappedEnumValue.isPresent()) {
pathParams.put(pathParamsMetadata.name, pathEncode(
- valToString(openEnumValue.get()),
+ valToString(unwrappedEnumValue.get()),
pathParamsMetadata.allowReserved));
break;
}
@@ -368,9 +368,9 @@ public static Map> getHeadersFromMetadata(Object headers, G
if (!allowIntrospection(value.getClass())) {
break;
}
- Optional> openEnumValue = Reflections.getOpenEnumValue(value.getClass(), value);
- if (openEnumValue.isPresent()) {
- upsertHeader(result, headerMetadata.name, openEnumValue.get());
+ Optional> unwrappedEnumValue = Reflections.getUnwrappedEnumValue(value.getClass(), value);
+ if (unwrappedEnumValue.isPresent()) {
+ upsertHeader(result, headerMetadata.name, unwrappedEnumValue.get());
break;
}
@@ -495,6 +495,14 @@ public static String valToString(Object value) {
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
return "ERROR_UNKNOWN_VALUE";
}
+ } else if (Reflections.isEnumWrapper(value)) {
+ // Extract the underlying value from enum wrapper
+ Optional> unwrappedEnumValue = Reflections.getUnwrappedEnumValue(value.getClass(), value);
+ if (unwrappedEnumValue.isPresent()) {
+ return String.valueOf(unwrappedEnumValue.get());
+ } else {
+ return "ERROR_UNKNOWN_VALUE";
+ }
} else {
return String.valueOf(resolveOptionals(value));
}
@@ -1135,13 +1143,22 @@ private static String toHex(byte[] bytes, int length) {
@SuppressWarnings("unchecked")
public static String discriminatorToString(Object o) {
- // expects o to be either an Optional, Enum (with a String value() method)
- // or a String value
+ // expects o to be either an Optional, Enum (with a String value() method),
+ // an open enum wrapper, or a String value
Class> cls = o.getClass();
if (cls.equals(Optional.class)) {
Optional a = (Optional) o;
return a.map(x -> discriminatorToString(x)).orElse(null);
- } else if (cls.isEnum()) {
+ }
+
+ // Check if it's an open enum wrapper
+ if (Reflections.isEnumWrapper(o)) {
+ Optional> value = Reflections.getUnwrappedEnumValue(cls, o);
+ return value.map(String::valueOf).orElse(null);
+ }
+
+ // Handle regular enums
+ if (cls.isEnum()) {
try {
Method m = cls.getMethod("value");
return (String) m.invoke(o);
@@ -1149,9 +1166,10 @@ public static String discriminatorToString(Object o) {
| InvocationTargetException e) {
throw new RuntimeException(e);
}
- } else {
- return (String) o;
}
+
+ // Fall back to String cast
+ return (String) o;
}
public static void recordTest(String id) {