tags:

views:

539

answers:

5

We have a REST API where clients can supply parameters representing values defined on the server in Java Enums.

So we can provide a descriptive error, we add this lookup method to each Enum. Seems like we're just copying code (bad). Is there a better practice?

public enum MyEnum {
    A, B, C, D;

    public static MyEnum lookup(String id) {
        try {
            return MyEnum.valueOf(id);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException("Invalid value for my enum blah blah: " + id);
        }
    }
}

Update: The default error message provided by valueOf(..) would be No enum const class a.b.c.MyEnum.BadValue. I would like to provide a more descriptive error from the API.

+2  A: 

Probably you can implement generic static lookup method.

Like so

public class LookupUtil {
   public static <E extends Enum<E>> E lookup(Class<E> e, String id) {   
      try {          
         E result = Enum.valueOf(e, id);
      } catch (IllegalArgumentException e) {
         // log error or something here

         throw new RuntimeException("Invalid value: " + id);
      }

      return result;
   }
}

Then you can

public enum MyEnum {
   static public MyEnum lookup(String id) {
       return LookupUtil.lookup(MyEnum.class, id);
   }
}

or call explicitly utility class lookup method.

Mykola Golubyev
You can't create a base enum to extend child enums, as "All enums implicitly extend java.lang.Enum. Since Java does not support multiple inheritance, an enum cannot extend anything else", so there's nowhere to put this code, unless you want to put it in some sort of static utility class and call it with the enum class parameter.
Steve B.
That's what I meant. Utility class.
Mykola Golubyev
What advantage does your utility method give over just invoking Enum.valueOf?
sleske
Post and pre actions and error handling. As I understand it is Error handling which is copy and pasted across different enum types.
Mykola Golubyev
Another extension would be to create an annotation that injects this method into any enum marked with that annotation (using Spring, AspectJ, etc).
Carl
A: 

Why do we have to write that 5 line code ?

public class EnumTest {
public enum MyEnum {
    A, B, C, D;
}

@Test
public void test() throws Exception {
    MyEnum.valueOf("A"); //gives you A
    //this throws ILlegalargument without having to do any lookup
    MyEnum.valueOf("RADD"); 
}
}
Calm Storm
The idea/requirement is to re-throw special exception
Mykola Golubyev
That's right. See updated post for a little more description on the "default" error vs. a more descriptive error.
Marcus
In that case the answer from @"Mykola Golubyev" should have solved your problem that is a static lookup utility class and you can throw any message/exception you want in the catch block ?
Calm Storm
+1  A: 

If you want the lookup to be case insensitive you can loop through the values making it a little more friendly:

 public enum MyEnum {
   A, B, C, D;

      public static MyEnum lookup(String id) {
        boolean found = false;
        for(MyEnum enum: values()){
           if(enum.toString().equalsIgnoreCase(id)) found = true;
        }  
        if(!found) throw new RuntimeException("Invalid value for my enum: " +id);
       }
}
Adam
Good suggestion, not really what I was looking for, but a good idea. Note in your `lookup` you're not actually returning any value from the method.
Marcus
Good point. Should be :if(enum.toString().equalsIgnoreCase(id)) return enum;You could then eliminate the boolean and just throw the exception if you get past the loop.
Adam
A: 

Looks like you have a bad practice here but not where you think.

Catching an IllegalArgumentException to rethrow another RuntimeException with a clearer message might look like a good idea but it is not. Because it means you care about messages in your exceptions.

If you care about messages in your exceptions, then it means that your user is somehow seeing your exceptions. This is bad.

If you want to provide an explicit error message to your user, you should check the validity of the enum value when parsing user input and send the appropriate error message in the response if user input is incorrect.

Something like:

// This code uses pure fantasy, you are warned!
class MyApi
{
    // Return the 24-hour from a 12-hour and AM/PM

    void getHour24(Request request, Response response)
    {
        // validate user input
        int nTime12 = 1;
        try
        {
            nTime12 = Integer.parseInt(request.getParam("hour12"));
            if( nTime12 <= 0 || nTime12 > 12 )
            {
                throw new NumberFormatException();
            }
        }
        catch( NumberFormatException e )
        {
            response.setCode(400); // Bad request
            response.setContent("time12 must be an integer between 1 and 12");
            return;
        }

        AMPM pm = null;
        try
        {
            pm = AMPM.lookup(request.getParam("pm"));
        }
        catch( IllegalArgumentException e )
        {
            response.setCode(400); // Bad request
            response.setContent("pm must be one of " + AMPM.values());
            return;
        }

        response.setCode(200);
        switch( pm )
        {
            case AM:
                response.setContent(nTime12);
                break;
            case PM:
                response.setContent(nTime12 + 12);
                break;
        }
        return;
    }
}
Vincent Robert
I think in essence we do what you're suggesting. We throw the exception, then a class higher on the stack catches it, then returns just the exception message to the client through the REST API (in a defined error response)
Marcus
What I suggested is that you should not catch generic exceptions to return their message, but return the message meant to explain why the exception was catched. Having a generic error response based on your exceptions is a leak of the internal architecture to the client and is a bad practice. The error message of your API should not depend on the error reporting architecture of the implementation (in this case, Java exceptions).
Vincent Robert
A: 

The error message in IllegalArgumentException is already descriptive enough.

Your method makes a generic exception out of a specific one with the same message simply reworded. A developer would prefer the specific exception type and can handle the case appropriately instead of trying to handle RuntimeException.

If the intent is to make the message more user friendly, then references to values of enums is irrelevant to them anyway. Let the UI code determine what should be displayed to the user, and the UI developer would be better off with the IllegalArgumentException.

Robin
The "default" error message would be: `No enum const class a.b.c.MyEnum.BadValue`. I would prefer to return a more relevant error message from the REST API. The developer creating the front end won't really know what to do with the default error other than just display it - which is what I'd like to avoid.
Marcus
I fail to see how your example message is any better. Neither is appropriate for a user so the exception should be handled and a proper user facing message displayed. This is different then placing the message in the exception, which should be for the developer.
Robin