Rest Routing with Multiple Wildcards / Supporting REST Routing for Nested Resources

Question

Running into a SalesForce REST mapping issue involving multiple wildcards in an url. Is this not supported? Is there anyway to supporting routing for nested resources?

My Setup

I have this REST structure

  • GET /teams/{teamid}/members (list team members)
  • POST /teams/{teamid}/members (create team member)
  • PUT /teams/{teamid}/members/{memberid} (update team member)
  • DELETE /teams/{teamid}/members/{memberid} (delete team member

And I setup the routing as followed:

  • REST_teams_id_members.cls @RestResource(urlMapping='/v1/teams/*/members')
  • REST_teams_id_members_id.cls @RestResource(urlMapping='/v1/teams/*/members/*')

Issue

This was working fine, but now I can only get one set of routes to work at any given time. Which ever one was compiled first gets precedence. So both GET /teams/123/members and DELETE/teams/123/members/345` get routed to the same class.

So If I compile REST_teams_id_member.cls (making the other class the first compiled), I get the following routing

  • GET /teams/{teamid}/members -> REST_teams_id_members_id.cls -> 405 Method Not Allowed
  • POST /teams/{teamid}/members -> REST_teams_id_members_id.cls -> 405 Method Not Allowed
  • PUT /teams/{teamid}/members/{memberid} -> REST_teams_id_members_id.cls -> Good
  • DELETE /teams/{teamid}/members/{memberid} -> REST_teams_id_members_id.cls -> Good

If I then compile REST_teams_id_members_id.cls (swapping the order), I get this routing

  • GET /teams/{teamid}/members -> REST_teams_id_members.cls -> Good
  • POST /teams/{teamid}/members -> REST_teams_id_members.cls -> Good
  • PUT /teams/{teamid}/members/{memberid} -> REST_teams_id_members.cls -> 405 Method Not Allowed
  • DELETE /teams/{teamid}/members/{memberid} -> REST_teams_id_members.cls -> 405 Method Not Allowed

Discussion

I feel like I’m taking crazy pills, it doesn’t make any sense how /teams/123/members/345 could possible match @RestResource(UrlMapping='/teams/*/members), and vice versa, I’m at a total loss for how /teams/123/members could route to @RestResource(UrlMapping='teams/*/members/*. They seem wholly distinct!!

We’ve got an imminent rollout and I’m complete stuck. Praying that SFSE community can help me out.

Answer

While it’s not clear why that doesn’t already work, to get your code out the door, my suggestion is to build your own REST dispatching service. The following pseudo code implementation works as followed:

  1. Register for all REST requests for a particular url (e.g. /teams/*)
  2. Register different controllers (which implement the interface Dispatchable)
  3. In case the url matches the url of your dispatchable class, the method execute is executed

Pseudo Code

@RestResource(urlMapping = '/teams/*')
global class RestDispatcher
{
    static Map<String, List<Dispatchable>> dispatchables;
    static
    {
        dispatchables = new Map <String, List<Dispatchable>>
        {
            'POST' => new List<Dispatchable>(),
            'GET' => new List<Dispatchable>()
        };

        // register your class here e.g. dispatchables.get('POST').add(new DispatchableClass());
    }


    global interface Dispatchable
    {
        String getURLMapping(); // e.g. /teams/{teamNumber}/members/{memberId}
        void execute(Map<String, String> parameters);
    }

    @HTTPGet
    global static void doGET()
    {
        execute('GET');
    }

    @HTTPPOST
    global static void doPOST()
    {
        execute('POST');
    }

    private static void execute(String httpMethod)
    {
        RestRequest request = RestContext.request;
        for (Dispatchable d : dispatchables.get(httpMethod))
        {
            if(match(d.getURLMapping, request.requestURI))
            {
                d.execute(getParamters(request.requestURI, d.getURLMapping()));
            }
        }
    }

    global static Map<String, String> getParamters(String requestURI, String dispatcherURI)
    {
        // To-Do: implement an extraction method here.
        return new Map<String, String>();
    }

    global static boolean match(String dispatchURI, String reuquestURI)
    {
        if(true) // To-Do: add a matching algorith here
        {
            return true;
        }
        return false;
    }

}

Code: https://gist.github.com/cdeckert/6991034790b0a4c9a34c

Attribution
Source : Link , Question Author : David Staley , Answer Author : Ralph Callaway

Leave a Comment