Search code examples
jsonapigrailscurlhttp-put

Grails API Update (PUT) without an ID


I'm working on an API for my Grails application and am trying to do something somewhat non-REST (though quite useful). I am trying to allow the Show and Update actions to accept either ID or name. I have the following mappings in my URLMappings file:

"/api/host" (controller:"API", action:"hostList", method:'GET', parseRequest:true)
"/api/host/$id" (controller:"API", action:"hostShow", method:'GET', parseRequest:true)
"/api/host/$id" (controller:"API", action:"hostUpdate", method:'PUT', parseRequest:true)   
"/api/host" (controller:"API", action:"hostCreate", method:'POST', parseRequest:true)

What I want is to have the hostShow and hostUpdate controller actions accept either ID or name as $id. This is working fine with the Show action, but when I try it with HTTP-PUT, I get the following error:

HTTP/1.1 422 Unprocessable Entity
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8   
{"errors":[{"object":"Host","field":"id","rejected-value":"TEST","message":"Property id must be a valid number"}]}

I can't seem to get around this error. The CURL command I am using to try and perform this is:

curl -i -X PUT -H "Content-Type:application/json" -d "{name:NEWTEST}", http://localhost:8080/myapp/api/host/TEST

Any feedback or help with this would be very much appreciated! Below is the code for my hostShow and hostUpdate actions:

def hostShow() {
    def hostInstance
    try {
        hostInstance = Host.get(params.id)
    }
    catch(Exception E) {
        hostInstance = Host.findByHostname(params.id)
    }

    if(hostInstance == null)
        respond null, [status: HttpStatus.NOT_FOUND]

    respond hostInstance
}    

@Transactional
def hostUpdate() {
    // Convert incoming JSON to params structure
    request.JSON.each { k,v ->
        params[k] = v
    }    

    // Get by either ID or hostname
    def hostInstance
    try {
        hostInstance = Host.get(params.id)
    }
    catch(Exception E) {
        hostInstance = Host.findByHostname(params.id)
    }

    hostInstance.properties = params   // Set new properties
    hostInstance.save(flush:true)                 

    respond hostInstance, [status: HttpStatus.OK]
}

Solution

  • It turned out to be an issue with the code that was giving me the error, NOT a problem with Grails or the API mechanism itself.

    The issue was that when I set hostInstance.properties=params, it tries to set the hostInstance.id to params.id, which in this case is the hostname. I solved this be adding the line:

    params.id = hostInstance.id   // <--- This line solves the problem
    hostInstance.properties=params
    

    Thanks for the feedback and sorry for asking a question with such a simple bug (in retrospect).