RESTXQ with dynamic mediatype

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

RESTXQ with dynamic mediatype

Immanuel Normann
Hi,

I am currently switching from the URI rewriting style to the restxq style and this raises a question regarding output:media-type:

For restxq there is this output:media-type annotation to determine the content type of the response. By default it is "application/xml". A simple example would be:

declare
    %rest:GET
    %rest:path("/test/xml/{$name}")
function ex:hello-xml($name){
    <greeting>Hi {$name}!</greeting>
};


If I want the corresponding result as json then this would be an implementation:

declare
    %rest:GET
    %rest:path("/test/json/{$name}")
    %output:media-type("application/json")
function ex:hello-json($name){
    "{'greeting': 'Hi " || $name || "!'}"
};


In both cases the %output:media-type is statically attached to the function.

How can I implement a function that sets the output:media-type dynamically?

Assume, for instance, a client wants to determine the output:media-type for convenience in a query parameter - e.g. like this:

declare
    %rest:GET
    %rest:path("/test/xml/{$name}")
    %rest:query-param("format","{$format}","xml")
function ex:hello-xml($name){
    switch($format)
     case "xml" return <greeting>Hi {$name}!</greeting>
    case "json" return ("{'greeting': 'Hi " || $name || "!'}")
    default return "some appropriate error response"
};


How can existdb set the appropriate media-type in this case? What would be the restxq solution or best practise to handle this requierment?

Regards
Immanuel

------------------------------------------------------------------------------

_______________________________________________
Exist-open mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/exist-open
Reply | Threaded
Open this post in threaded view
|

Re: RESTXQ with dynamic mediatype

Adam Retter
> I am currently switching from the URI rewriting style to the restxq style
> and this raises a question regarding output:media-type:
>
> For restxq there is this output:media-type annotation to determine the
> content type of the response. By default it is "application/xml". A simple
> example would be:
>
> declare
>     %rest:GET
>     %rest:path("/test/xml/{$name}")
> function ex:hello-xml($name){
>     <greeting>Hi {$name}!</greeting>
> };
>
> If I want the corresponding result as json then this would be an
> implementation:
>
> declare
>     %rest:GET
>     %rest:path("/test/json/{$name}")
>     %output:media-type("application/json")
> function ex:hello-json($name){
>     "{'greeting': 'Hi " || $name || "!'}"
> };
>
> In both cases the %output:media-type is statically attached to the function.
>
> How can I implement a function that sets the output:media-type dynamically?
>
> Assume, for instance, a client wants to determine the output:media-type for
> convenience in a query parameter - e.g. like this:
>
> declare
>     %rest:GET
>     %rest:path("/test/xml/{$name}")
>     %rest:query-param("format","{$format}","xml")
> function ex:hello-xml($name){
>     switch($format)
>      case "xml" return <greeting>Hi {$name}!</greeting>
>     case "json" return ("{'greeting': 'Hi " || $name || "!'}")
>     default return "some appropriate error response"
> };
>
> How can existdb set the appropriate media-type in this case? What would be
> the restxq solution or best practise to handle this requierment?

To answer your question using the example you provided, you would
modify your code like so:

xquery version "3.0";

declare namespace http = "http://expath.org/ns/http-client";

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:query-param("format","{$format}","xml")
function ex:hello-xml($name) {
  let $response := switch($format)
   case "xml" return ("application/xml", <greeting>Hi {$name}!</greeting>)
   case "json" return ("application/json", "{'greeting': 'Hi " ||
$name || "!'}")
   default return ("plain/text", "some appropriate error response")
  return
  (
    <rest:response>
      <http:response>
        <http:header value="Content-Type" value="{$response[1]}"/>
      </http:response>
    </rest:response>,

    $response[2]
  )
};


You could also take advantage of eXist's auto-serialization of XML to
JSON if that makes things easier for you by controlling both the
serialization type and the Intenet Media Type dynamically, e.g.

xquery version "3.0";

declare namespace ser = "http://www.w3.org/2010/xslt-xquery-serialization";
declare namespace http = "http://expath.org/ns/http-client";

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:query-param("format","{$format}","xml")
function ex:hello-xml($name) {
  let $output := switch($format)
   case "xml" return ("xml", "application/xml")
   case "json" return ("json", "application/json")
   default return ("html", "text/html")
  return
  (
    <rest:response>
      <ser:serialization-parameters>
        <ser:method>{$output[1]}</ser:method>
      </ser:serialization-parameters>
      <http:response>
        <http:header value="Content-Type" value="{$output[2]}"/>
      </http:response>
    </rest:response>,

    <greeting>Hi {$name}!</greeting>
  )
};


However, whilst both of the above will work for you, what you are
really trying to do here is content negotiation, as such I would
encourage you to follow best practice and make use of the hHTTP
'Accept' header via the %rest:produces annotation to achieve this. I
would instead suggest the following approach to you -

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:produces("application/xml")
function ex:hello-xml($name) {
  ex:hello($name)
};

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:produces("application/json")
  %output:method("json")
function ex:hello-json($name) {
  ex:hello($name)
};

declare
  %private
function ex:hello($name) {
  <greeting>Hi {$name}!</greeting>
};

The example above makes use of eXist's auto-serialization of XML to
JSON by using the %output:method("json") annotation on the
`ex:hello-json` function, but if you wanted manual control of the JSON
creation, just remove that annotation and adapt your `ex:hello`
function to take a format argument etc.

HTH. Adam.

--
Adam Retter

eXist Developer
{ United Kingdom }
[hidden email]
irc://irc.freenode.net/existdb

------------------------------------------------------------------------------
_______________________________________________
Exist-open mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/exist-open
Reply | Threaded
Open this post in threaded view
|

Re: RESTXQ with dynamic mediatype

Immanuel Normann
Thanks Adam for the elaborated solution!
I also prefer your last proposal as I want to support content negotiation, too. In fact I had basically the same idea, but I thought it wouldn't work as you have two functions annotated with the same rest:path. I thought this would be a conflict.
Now, my new intuition is that as long as two resource functions differ in at least one (rest or output) annotation they can be annotated with the same path without conflict. In your case it is the "produces" annotation. But they could also, for example, differ just in that one has %rest:GET and the other %rest:POST and still a common %rest:path, right?


Yet a comment on "<rest:response>...</rest:response>": This is obviously an instance from the spec:
http://exquery.github.io/exquery/exquery-restxq-specification/restxq-1.0-specification.html#rest-response
Actually, this example from the spec wasn't very clear for me, now in this instance it becomes a bit clearer. I wonder, though, where I can find a spec about "ser:serialization-parameters"?

Last comment: since I didn't understand this rest:response mentioned above I tried to set the response headers via the "http://exist-db.org/xquery/response" module and response:set-header. But this yields an error "Error 500 exerr:ERROR Response not set". My conclusion is that restxq annotation and "http://exist-db.org/xquery/response" as well as "http://exist-db.org/xquery/request" don't go well together. Could one say: "if you make use of restxq annotation then don't use neither the request nor the response module"?

Immanuel


2015-06-18 12:08 GMT+02:00 Adam Retter <[hidden email]>:
> I am currently switching from the URI rewriting style to the restxq style
> and this raises a question regarding output:media-type:
>
> For restxq there is this output:media-type annotation to determine the
> content type of the response. By default it is "application/xml". A simple
> example would be:
>
> declare
>     %rest:GET
>     %rest:path("/test/xml/{$name}")
> function ex:hello-xml($name){
>     <greeting>Hi {$name}!</greeting>
> };
>
> If I want the corresponding result as json then this would be an
> implementation:
>
> declare
>     %rest:GET
>     %rest:path("/test/json/{$name}")
>     %output:media-type("application/json")
> function ex:hello-json($name){
>     "{'greeting': 'Hi " || $name || "!'}"
> };
>
> In both cases the %output:media-type is statically attached to the function.
>
> How can I implement a function that sets the output:media-type dynamically?
>
> Assume, for instance, a client wants to determine the output:media-type for
> convenience in a query parameter - e.g. like this:
>
> declare
>     %rest:GET
>     %rest:path("/test/xml/{$name}")
>     %rest:query-param("format","{$format}","xml")
> function ex:hello-xml($name){
>     switch($format)
>      case "xml" return <greeting>Hi {$name}!</greeting>
>     case "json" return ("{'greeting': 'Hi " || $name || "!'}")
>     default return "some appropriate error response"
> };
>
> How can existdb set the appropriate media-type in this case? What would be
> the restxq solution or best practise to handle this requierment?

To answer your question using the example you provided, you would
modify your code like so:

xquery version "3.0";

declare namespace http = "http://expath.org/ns/http-client";

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:query-param("format","{$format}","xml")
function ex:hello-xml($name) {
  let $response := switch($format)
   case "xml" return ("application/xml", <greeting>Hi {$name}!</greeting>)
   case "json" return ("application/json", "{'greeting': 'Hi " ||
$name || "!'}")
   default return ("plain/text", "some appropriate error response")
  return
  (
    <rest:response>
      <http:response>
        <http:header value="Content-Type" value="{$response[1]}"/>
      </http:response>
    </rest:response>,

    $response[2]
  )
};


You could also take advantage of eXist's auto-serialization of XML to
JSON if that makes things easier for you by controlling both the
serialization type and the Intenet Media Type dynamically, e.g.

xquery version "3.0";

declare namespace ser = "http://www.w3.org/2010/xslt-xquery-serialization";
declare namespace http = "http://expath.org/ns/http-client";

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:query-param("format","{$format}","xml")
function ex:hello-xml($name) {
  let $output := switch($format)
   case "xml" return ("xml", "application/xml")
   case "json" return ("json", "application/json")
   default return ("html", "text/html")
  return
  (
    <rest:response>
      <ser:serialization-parameters>
        <ser:method>{$output[1]}</ser:method>
      </ser:serialization-parameters>
      <http:response>
        <http:header value="Content-Type" value="{$output[2]}"/>
      </http:response>
    </rest:response>,

    <greeting>Hi {$name}!</greeting>
  )
};


However, whilst both of the above will work for you, what you are
really trying to do here is content negotiation, as such I would
encourage you to follow best practice and make use of the hHTTP
'Accept' header via the %rest:produces annotation to achieve this. I
would instead suggest the following approach to you -

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:produces("application/xml")
function ex:hello-xml($name) {
  ex:hello($name)
};

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:produces("application/json")
  %output:method("json")
function ex:hello-json($name) {
  ex:hello($name)
};

declare
  %private
function ex:hello($name) {
  <greeting>Hi {$name}!</greeting>
};

The example above makes use of eXist's auto-serialization of XML to
JSON by using the %output:method("json") annotation on the
`ex:hello-json` function, but if you wanted manual control of the JSON
creation, just remove that annotation and adapt your `ex:hello`
function to take a format argument etc.

HTH. Adam.

--
Adam Retter

eXist Developer
{ United Kingdom }
[hidden email]
irc://irc.freenode.net/existdb


------------------------------------------------------------------------------

_______________________________________________
Exist-open mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/exist-open
Reply | Threaded
Open this post in threaded view
|

Re: RESTXQ with dynamic mediatype

Adam Retter
> I also prefer your last proposal as I want to support content negotiation,
> too. In fact I had basically the same idea, but I thought it wouldn't work
> as you have two functions annotated with the same rest:path. I thought this
> would be a conflict.
> Now, my new intuition is that as long as two resource functions differ in at
> least one (rest or output) annotation they can be annotated with the same
> path without conflict. In your case it is the "produces" annotation. But
> they could also, for example, differ just in that one has %rest:GET and the
> other %rest:POST and still a common %rest:path, right?

Each function needs to have unique `constraints` annotations.
Annotations are slip into three categories `constraints`, `parameters`
and `output`. Constraints annotations are the HTTP method e.g.
%rest:POST, the HTTP path e.g. `%rest:path`, and the content
negotiation stuff e.g. `%rest:produces` and `%rest:consumes`.

> Yet a comment on "<rest:response>...</rest:response>": This is obviously an
> instance from the spec:
> http://exquery.github.io/exquery/exquery-restxq-specification/restxq-1.0-specification.html#rest-response
> Actually, this example from the spec wasn't very clear for me, now in this
> instance it becomes a bit clearer. I wonder, though, where I can find a spec
> about "ser:serialization-parameters"?

Yeah I am afraid the spec sucks. It is suffering from a severe lack of
my time. If anyone wants to volunteer to work on it, then I would be
happy to hear from them.

> Last comment: since I didn't understand this rest:response mentioned above I
> tried to set the response headers via the
> "http://exist-db.org/xquery/response" module and response:set-header. But
> this yields an error "Error 500 exerr:ERROR Response not set". My conclusion
> is that restxq annotation and "http://exist-db.org/xquery/response" as well
> as "http://exist-db.org/xquery/request" don't go well together. Could one
> say: "if you make use of restxq annotation then don't use neither the
> request nor the response module"?

You cannot uses eXist's request and response modules inside RESTXQ,
they ave very different ideas! There is a request module for RESTXQ
which is mentioned in the spec, but it is much much simpler as RESTXQ
believes that parameters should be injected by the annotations
instead.

>
> Immanuel
>
>
> 2015-06-18 12:08 GMT+02:00 Adam Retter <[hidden email]>:
>>
>> > I am currently switching from the URI rewriting style to the restxq
>> > style
>> > and this raises a question regarding output:media-type:
>> >
>> > For restxq there is this output:media-type annotation to determine the
>> > content type of the response. By default it is "application/xml". A
>> > simple
>> > example would be:
>> >
>> > declare
>> >     %rest:GET
>> >     %rest:path("/test/xml/{$name}")
>> > function ex:hello-xml($name){
>> >     <greeting>Hi {$name}!</greeting>
>> > };
>> >
>> > If I want the corresponding result as json then this would be an
>> > implementation:
>> >
>> > declare
>> >     %rest:GET
>> >     %rest:path("/test/json/{$name}")
>> >     %output:media-type("application/json")
>> > function ex:hello-json($name){
>> >     "{'greeting': 'Hi " || $name || "!'}"
>> > };
>> >
>> > In both cases the %output:media-type is statically attached to the
>> > function.
>> >
>> > How can I implement a function that sets the output:media-type
>> > dynamically?
>> >
>> > Assume, for instance, a client wants to determine the output:media-type
>> > for
>> > convenience in a query parameter - e.g. like this:
>> >
>> > declare
>> >     %rest:GET
>> >     %rest:path("/test/xml/{$name}")
>> >     %rest:query-param("format","{$format}","xml")
>> > function ex:hello-xml($name){
>> >     switch($format)
>> >      case "xml" return <greeting>Hi {$name}!</greeting>
>> >     case "json" return ("{'greeting': 'Hi " || $name || "!'}")
>> >     default return "some appropriate error response"
>> > };
>> >
>> > How can existdb set the appropriate media-type in this case? What would
>> > be
>> > the restxq solution or best practise to handle this requierment?
>>
>> To answer your question using the example you provided, you would
>> modify your code like so:
>>
>> xquery version "3.0";
>>
>> declare namespace http = "http://expath.org/ns/http-client";
>>
>> declare
>>   %rest:GET
>>   %rest:path("/test/xml/{$name}")
>>   %rest:query-param("format","{$format}","xml")
>> function ex:hello-xml($name) {
>>   let $response := switch($format)
>>    case "xml" return ("application/xml", <greeting>Hi {$name}!</greeting>)
>>    case "json" return ("application/json", "{'greeting': 'Hi " ||
>> $name || "!'}")
>>    default return ("plain/text", "some appropriate error response")
>>   return
>>   (
>>     <rest:response>
>>       <http:response>
>>         <http:header value="Content-Type" value="{$response[1]}"/>
>>       </http:response>
>>     </rest:response>,
>>
>>     $response[2]
>>   )
>> };
>>
>>
>> You could also take advantage of eXist's auto-serialization of XML to
>> JSON if that makes things easier for you by controlling both the
>> serialization type and the Intenet Media Type dynamically, e.g.
>>
>> xquery version "3.0";
>>
>> declare namespace ser =
>> "http://www.w3.org/2010/xslt-xquery-serialization";
>> declare namespace http = "http://expath.org/ns/http-client";
>>
>> declare
>>   %rest:GET
>>   %rest:path("/test/xml/{$name}")
>>   %rest:query-param("format","{$format}","xml")
>> function ex:hello-xml($name) {
>>   let $output := switch($format)
>>    case "xml" return ("xml", "application/xml")
>>    case "json" return ("json", "application/json")
>>    default return ("html", "text/html")
>>   return
>>   (
>>     <rest:response>
>>       <ser:serialization-parameters>
>>         <ser:method>{$output[1]}</ser:method>
>>       </ser:serialization-parameters>
>>       <http:response>
>>         <http:header value="Content-Type" value="{$output[2]}"/>
>>       </http:response>
>>     </rest:response>,
>>
>>     <greeting>Hi {$name}!</greeting>
>>   )
>> };
>>
>>
>> However, whilst both of the above will work for you, what you are
>> really trying to do here is content negotiation, as such I would
>> encourage you to follow best practice and make use of the hHTTP
>> 'Accept' header via the %rest:produces annotation to achieve this. I
>> would instead suggest the following approach to you -
>>
>> declare
>>   %rest:GET
>>   %rest:path("/test/xml/{$name}")
>>   %rest:produces("application/xml")
>> function ex:hello-xml($name) {
>>   ex:hello($name)
>> };
>>
>> declare
>>   %rest:GET
>>   %rest:path("/test/xml/{$name}")
>>   %rest:produces("application/json")
>>   %output:method("json")
>> function ex:hello-json($name) {
>>   ex:hello($name)
>> };
>>
>> declare
>>   %private
>> function ex:hello($name) {
>>   <greeting>Hi {$name}!</greeting>
>> };
>>
>> The example above makes use of eXist's auto-serialization of XML to
>> JSON by using the %output:method("json") annotation on the
>> `ex:hello-json` function, but if you wanted manual control of the JSON
>> creation, just remove that annotation and adapt your `ex:hello`
>> function to take a format argument etc.
>>
>> HTH. Adam.
>>
>> --
>> Adam Retter
>>
>> eXist Developer
>> { United Kingdom }
>> [hidden email]
>> irc://irc.freenode.net/existdb
>
>



--
Adam Retter

eXist Developer
{ United Kingdom }
[hidden email]
irc://irc.freenode.net/existdb

------------------------------------------------------------------------------
_______________________________________________
Exist-open mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/exist-open
Reply | Threaded
Open this post in threaded view
|

Re: RESTXQ with dynamic mediatype

Immanuel Normann
In reply to this post by Adam Retter
Hi Adam,

I can't get your example running.

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:query-param("format","{$format}","xml")
function ex:hello-xml($name) {
  let $output := switch($format)
   case "xml" return ("xml", "application/xml")
   case "json" return ("json", "application/json")
   default return ("html", "text/html")
  return
  (
    <rest:response>
      <ser:serialization-parameters>
        <ser:method>{$output[1]}</ser:method>
      </ser:serialization-parameters>
      <http:response>
        <http:header value="Content-Type" value="{$output[2]}"/>
      </http:response>
    </rest:response>,

    <greeting>Hi {$name}!</greeting>
  )
};

One reason of failure is certainly the two value-attributes in the header element. But that alone wouldn't fix it.
To make the example even simpler but still based on your example above I created this stand-alone xquery file:

xquery version "3.0";

declare namespace ex = "http://example.org/restxq/4";

import module namespace rest = "http://exquery.org/ns/restxq";
declare namespace ser = "http://www.w3.org/2010/xslt-xquery-serialization";

declare
  %rest:GET
  %rest:path("/test/response/{$name}")
function ex:response($name) {
  (
    <rest:response>
      <ser:serialization-parameters>
        <ser:method>xml</ser:method>
      </ser:serialization-parameters>
      <http:response>
        <http:header name="Content-Type" value="application/xml"/>
      </http:response>
    </rest:response>,
    <greeting>Hi {$name}!</greeting>
  )
};


returns this error message:

HTTP ERROR 500

Problem accessing /exist/restxq/test/response/joe. Reason:

    Server Error

Caused by:

java.lang.NullPointerException
	at org.exist.extensions.exquery.restxq.impl.adapters.HttpServletResponseAdapter.setStatus(HttpServletResponseAdapter.java:59)
	at org.exquery.restxq.impl.serialization.RestResponseHandler.processHttpResponse(RestResponseHandler.java:120)
	at org.exquery.restxq.impl.serialization.RestResponseHandler.process(RestResponseHandler.java:79)
	at org.exquery.restxq.impl.serialization.AbstractRestXqServiceSerializer.serialize(AbstractRestXqServiceSerializer.java:141)
	at org.exquery.restxq.impl.AbstractRestXqService.service(AbstractRestXqService.java:192)
	at org.exist.extensions.exquery.restxq.impl.RestXqServiceImpl.service(RestXqServiceImpl.java:119)
	at org.exist.extensions.exquery.restxq.impl.RestXqServlet.service(RestXqServlet.java:102)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)

...

Immanuel


2015-06-18 12:08 GMT+02:00 Adam Retter <[hidden email]>:
> I am currently switching from the URI rewriting style to the restxq style
> and this raises a question regarding output:media-type:
>
> For restxq there is this output:media-type annotation to determine the
> content type of the response. By default it is "application/xml". A simple
> example would be:
>
> declare
>     %rest:GET
>     %rest:path("/test/xml/{$name}")
> function ex:hello-xml($name){
>     <greeting>Hi {$name}!</greeting>
> };
>
> If I want the corresponding result as json then this would be an
> implementation:
>
> declare
>     %rest:GET
>     %rest:path("/test/json/{$name}")
>     %output:media-type("application/json")
> function ex:hello-json($name){
>     "{'greeting': 'Hi " || $name || "!'}"
> };
>
> In both cases the %output:media-type is statically attached to the function.
>
> How can I implement a function that sets the output:media-type dynamically?
>
> Assume, for instance, a client wants to determine the output:media-type for
> convenience in a query parameter - e.g. like this:
>
> declare
>     %rest:GET
>     %rest:path("/test/xml/{$name}")
>     %rest:query-param("format","{$format}","xml")
> function ex:hello-xml($name){
>     switch($format)
>      case "xml" return <greeting>Hi {$name}!</greeting>
>     case "json" return ("{'greeting': 'Hi " || $name || "!'}")
>     default return "some appropriate error response"
> };
>
> How can existdb set the appropriate media-type in this case? What would be
> the restxq solution or best practise to handle this requierment?

To answer your question using the example you provided, you would
modify your code like so:

xquery version "3.0";

declare namespace http = "http://expath.org/ns/http-client";

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:query-param("format","{$format}","xml")
function ex:hello-xml($name) {
  let $response := switch($format)
   case "xml" return ("application/xml", <greeting>Hi {$name}!</greeting>)
   case "json" return ("application/json", "{'greeting': 'Hi " ||
$name || "!'}")
   default return ("plain/text", "some appropriate error response")
  return
  (
    <rest:response>
      <http:response>
        <http:header value="Content-Type" value="{$response[1]}"/>
      </http:response>
    </rest:response>,

    $response[2]
  )
};


You could also take advantage of eXist's auto-serialization of XML to
JSON if that makes things easier for you by controlling both the
serialization type and the Intenet Media Type dynamically, e.g.

xquery version "3.0";

declare namespace ser = "http://www.w3.org/2010/xslt-xquery-serialization";
declare namespace http = "http://expath.org/ns/http-client";

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:query-param("format","{$format}","xml")
function ex:hello-xml($name) {
  let $output := switch($format)
   case "xml" return ("xml", "application/xml")
   case "json" return ("json", "application/json")
   default return ("html", "text/html")
  return
  (
    <rest:response>
      <ser:serialization-parameters>
        <ser:method>{$output[1]}</ser:method>
      </ser:serialization-parameters>
      <http:response>
        <http:header value="Content-Type" value="{$output[2]}"/>
      </http:response>
    </rest:response>,

    <greeting>Hi {$name}!</greeting>
  )
};


However, whilst both of the above will work for you, what you are
really trying to do here is content negotiation, as such I would
encourage you to follow best practice and make use of the hHTTP
'Accept' header via the %rest:produces annotation to achieve this. I
would instead suggest the following approach to you -

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:produces("application/xml")
function ex:hello-xml($name) {
  ex:hello($name)
};

declare
  %rest:GET
  %rest:path("/test/xml/{$name}")
  %rest:produces("application/json")
  %output:method("json")
function ex:hello-json($name) {
  ex:hello($name)
};

declare
  %private
function ex:hello($name) {
  <greeting>Hi {$name}!</greeting>
};

The example above makes use of eXist's auto-serialization of XML to
JSON by using the %output:method("json") annotation on the
`ex:hello-json` function, but if you wanted manual control of the JSON
creation, just remove that annotation and adapt your `ex:hello`
function to take a format argument etc.

HTH. Adam.

--
Adam Retter

eXist Developer
{ United Kingdom }
[hidden email]
irc://irc.freenode.net/existdb


------------------------------------------------------------------------------
Monitor 25 network devices or servers for free with OpManager!
OpManager is web-based network management software that monitors
network devices and physical & virtual servers, alerts via email & sms
for fault. Monitor 25 devices for free with no restriction. Download now
http://ad.doubleclick.net/ddm/clk/292181274;119417398;o
_______________________________________________
Exist-open mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/exist-open
Reply | Threaded
Open this post in threaded view
|

Re: RESTXQ with dynamic mediatype

Adam Retter
Hi Immanuel,

You are getting this NPE becuase the <http:response> element needs to
have a status attribute on it, e.g.

<http:resonse status="200">

The EXPath HTTP Client spec says that this attribute has to be present
- http://expath.org/spec/http-client#d2e483
However, I also recognise that on the server-side we could just
default to HTTP 200 OK if it is not provided so I have made that
enhancement here - https://github.com/eXist-db/exist/pull/696

On 25 June 2015 at 13:23, Immanuel Normann <[hidden email]> wrote:

> Hi Adam,
>
> I can't get your example running.
>
>> declare
>>   %rest:GET
>>   %rest:path("/test/xml/{$name}")
>>   %rest:query-param("format","{$format}","xml")
>> function ex:hello-xml($name) {
>>   let $output := switch($format)
>>    case "xml" return ("xml", "application/xml")
>>    case "json" return ("json", "application/json")
>>    default return ("html", "text/html")
>>   return
>>   (
>>     <rest:response>
>>       <ser:serialization-parameters>
>>         <ser:method>{$output[1]}</ser:method>
>>       </ser:serialization-parameters>
>>       <http:response>
>>         <http:header value="Content-Type" value="{$output[2]}"/>
>>       </http:response>
>>     </rest:response>,
>>
>>     <greeting>Hi {$name}!</greeting>
>>   )
>> };
>
>
> One reason of failure is certainly the two value-attributes in the header
> element. But that alone wouldn't fix it.
> To make the example even simpler but still based on your example above I
> created this stand-alone xquery file:
>
> xquery version "3.0";
>
> declare namespace ex = "http://example.org/restxq/4";
>
> import module namespace rest = "http://exquery.org/ns/restxq";
> declare namespace ser = "http://www.w3.org/2010/xslt-xquery-serialization";
>
> declare
>   %rest:GET
>   %rest:path("/test/response/{$name}")
> function ex:response($name) {
>   (
>     <rest:response>
>       <ser:serialization-parameters>
>         <ser:method>xml</ser:method>
>       </ser:serialization-parameters>
>       <http:response>
>         <http:header name="Content-Type" value="application/xml"/>
>       </http:response>
>     </rest:response>,
>     <greeting>Hi {$name}!</greeting>
>   )
> };
>
> The get request http://localhost:8080/exist/restxq/test/response/joe
> returns this error message:
>
> HTTP ERROR 500
>
> Problem accessing /exist/restxq/test/response/joe. Reason:
>
>     Server Error
>
> Caused by:
>
> java.lang.NullPointerException
> at
> org.exist.extensions.exquery.restxq.impl.adapters.HttpServletResponseAdapter.setStatus(HttpServletResponseAdapter.java:59)
> at
> org.exquery.restxq.impl.serialization.RestResponseHandler.processHttpResponse(RestResponseHandler.java:120)
> at
> org.exquery.restxq.impl.serialization.RestResponseHandler.process(RestResponseHandler.java:79)
> at
> org.exquery.restxq.impl.serialization.AbstractRestXqServiceSerializer.serialize(AbstractRestXqServiceSerializer.java:141)
> at
> org.exquery.restxq.impl.AbstractRestXqService.service(AbstractRestXqService.java:192)
> at
> org.exist.extensions.exquery.restxq.impl.RestXqServiceImpl.service(RestXqServiceImpl.java:119)
> at
> org.exist.extensions.exquery.restxq.impl.RestXqServlet.service(RestXqServlet.java:102)
> at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
>
> ...
>
> Immanuel
>
>
>
> 2015-06-18 12:08 GMT+02:00 Adam Retter <[hidden email]>:
>>
>> > I am currently switching from the URI rewriting style to the restxq
>> > style
>> > and this raises a question regarding output:media-type:
>> >
>> > For restxq there is this output:media-type annotation to determine the
>> > content type of the response. By default it is "application/xml". A
>> > simple
>> > example would be:
>> >
>> > declare
>> >     %rest:GET
>> >     %rest:path("/test/xml/{$name}")
>> > function ex:hello-xml($name){
>> >     <greeting>Hi {$name}!</greeting>
>> > };
>> >
>> > If I want the corresponding result as json then this would be an
>> > implementation:
>> >
>> > declare
>> >     %rest:GET
>> >     %rest:path("/test/json/{$name}")
>> >     %output:media-type("application/json")
>> > function ex:hello-json($name){
>> >     "{'greeting': 'Hi " || $name || "!'}"
>> > };
>> >
>> > In both cases the %output:media-type is statically attached to the
>> > function.
>> >
>> > How can I implement a function that sets the output:media-type
>> > dynamically?
>> >
>> > Assume, for instance, a client wants to determine the output:media-type
>> > for
>> > convenience in a query parameter - e.g. like this:
>> >
>> > declare
>> >     %rest:GET
>> >     %rest:path("/test/xml/{$name}")
>> >     %rest:query-param("format","{$format}","xml")
>> > function ex:hello-xml($name){
>> >     switch($format)
>> >      case "xml" return <greeting>Hi {$name}!</greeting>
>> >     case "json" return ("{'greeting': 'Hi " || $name || "!'}")
>> >     default return "some appropriate error response"
>> > };
>> >
>> > How can existdb set the appropriate media-type in this case? What would
>> > be
>> > the restxq solution or best practise to handle this requierment?
>>
>> To answer your question using the example you provided, you would
>> modify your code like so:
>>
>> xquery version "3.0";
>>
>> declare namespace http = "http://expath.org/ns/http-client";
>>
>> declare
>>   %rest:GET
>>   %rest:path("/test/xml/{$name}")
>>   %rest:query-param("format","{$format}","xml")
>> function ex:hello-xml($name) {
>>   let $response := switch($format)
>>    case "xml" return ("application/xml", <greeting>Hi {$name}!</greeting>)
>>    case "json" return ("application/json", "{'greeting': 'Hi " ||
>> $name || "!'}")
>>    default return ("plain/text", "some appropriate error response")
>>   return
>>   (
>>     <rest:response>
>>       <http:response>
>>         <http:header value="Content-Type" value="{$response[1]}"/>
>>       </http:response>
>>     </rest:response>,
>>
>>     $response[2]
>>   )
>> };
>>
>>
>> You could also take advantage of eXist's auto-serialization of XML to
>> JSON if that makes things easier for you by controlling both the
>> serialization type and the Intenet Media Type dynamically, e.g.
>>
>> xquery version "3.0";
>>
>> declare namespace ser =
>> "http://www.w3.org/2010/xslt-xquery-serialization";
>> declare namespace http = "http://expath.org/ns/http-client";
>>
>> declare
>>   %rest:GET
>>   %rest:path("/test/xml/{$name}")
>>   %rest:query-param("format","{$format}","xml")
>> function ex:hello-xml($name) {
>>   let $output := switch($format)
>>    case "xml" return ("xml", "application/xml")
>>    case "json" return ("json", "application/json")
>>    default return ("html", "text/html")
>>   return
>>   (
>>     <rest:response>
>>       <ser:serialization-parameters>
>>         <ser:method>{$output[1]}</ser:method>
>>       </ser:serialization-parameters>
>>       <http:response>
>>         <http:header value="Content-Type" value="{$output[2]}"/>
>>       </http:response>
>>     </rest:response>,
>>
>>     <greeting>Hi {$name}!</greeting>
>>   )
>> };
>>
>>
>> However, whilst both of the above will work for you, what you are
>> really trying to do here is content negotiation, as such I would
>> encourage you to follow best practice and make use of the hHTTP
>> 'Accept' header via the %rest:produces annotation to achieve this. I
>> would instead suggest the following approach to you -
>>
>> declare
>>   %rest:GET
>>   %rest:path("/test/xml/{$name}")
>>   %rest:produces("application/xml")
>> function ex:hello-xml($name) {
>>   ex:hello($name)
>> };
>>
>> declare
>>   %rest:GET
>>   %rest:path("/test/xml/{$name}")
>>   %rest:produces("application/json")
>>   %output:method("json")
>> function ex:hello-json($name) {
>>   ex:hello($name)
>> };
>>
>> declare
>>   %private
>> function ex:hello($name) {
>>   <greeting>Hi {$name}!</greeting>
>> };
>>
>> The example above makes use of eXist's auto-serialization of XML to
>> JSON by using the %output:method("json") annotation on the
>> `ex:hello-json` function, but if you wanted manual control of the JSON
>> creation, just remove that annotation and adapt your `ex:hello`
>> function to take a format argument etc.
>>
>> HTH. Adam.
>>
>> --
>> Adam Retter
>>
>> eXist Developer
>> { United Kingdom }
>> [hidden email]
>> irc://irc.freenode.net/existdb
>
>



--
Adam Retter

eXist Developer
{ United Kingdom }
[hidden email]
irc://irc.freenode.net/existdb

------------------------------------------------------------------------------
Monitor 25 network devices or servers for free with OpManager!
OpManager is web-based network management software that monitors
network devices and physical & virtual servers, alerts via email & sms
for fault. Monitor 25 devices for free with no restriction. Download now
http://ad.doubleclick.net/ddm/clk/292181274;119417398;o
_______________________________________________
Exist-open mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/exist-open
Reply | Threaded
Open this post in threaded view
|

Re: RESTXQ with dynamic mediatype

Adam Retter
Just to add here is the example, updated to work without an NPE -

xquery version "3.0";

module namespace ex = "http://example.org/restxq/4";

import module namespace rest = "http://exquery.org/ns/restxq";
declare namespace ser = "http://www.w3.org/2010/xslt-xquery-serialization";
declare namespace http = "http://expath.org/ns/http-client";

declare
  %rest:GET
  %rest:path("/test/response/{$name}")
function ex:response($name) {
  (
    <rest:response>
      <ser:serialization-parameters>
        <ser:method>xml</ser:method>
      </ser:serialization-parameters>
      <http:response status="200">
        <http:header name="Content-Type" value="application/xml"/>
      </http:response>
    </rest:response>,
    <greeting>Hi {$name}!</greeting>
  )
};

On 25 June 2015 at 16:35, Adam Retter <[hidden email]> wrote:

> Hi Immanuel,
>
> You are getting this NPE becuase the <http:response> element needs to
> have a status attribute on it, e.g.
>
> <http:resonse status="200">
>
> The EXPath HTTP Client spec says that this attribute has to be present
> - http://expath.org/spec/http-client#d2e483
> However, I also recognise that on the server-side we could just
> default to HTTP 200 OK if it is not provided so I have made that
> enhancement here - https://github.com/eXist-db/exist/pull/696
>
> On 25 June 2015 at 13:23, Immanuel Normann <[hidden email]> wrote:
>> Hi Adam,
>>
>> I can't get your example running.
>>
>>> declare
>>>   %rest:GET
>>>   %rest:path("/test/xml/{$name}")
>>>   %rest:query-param("format","{$format}","xml")
>>> function ex:hello-xml($name) {
>>>   let $output := switch($format)
>>>    case "xml" return ("xml", "application/xml")
>>>    case "json" return ("json", "application/json")
>>>    default return ("html", "text/html")
>>>   return
>>>   (
>>>     <rest:response>
>>>       <ser:serialization-parameters>
>>>         <ser:method>{$output[1]}</ser:method>
>>>       </ser:serialization-parameters>
>>>       <http:response>
>>>         <http:header value="Content-Type" value="{$output[2]}"/>
>>>       </http:response>
>>>     </rest:response>,
>>>
>>>     <greeting>Hi {$name}!</greeting>
>>>   )
>>> };
>>
>>
>> One reason of failure is certainly the two value-attributes in the header
>> element. But that alone wouldn't fix it.
>> To make the example even simpler but still based on your example above I
>> created this stand-alone xquery file:
>>
>> xquery version "3.0";
>>
>> declare namespace ex = "http://example.org/restxq/4";
>>
>> import module namespace rest = "http://exquery.org/ns/restxq";
>> declare namespace ser = "http://www.w3.org/2010/xslt-xquery-serialization";
>>
>> declare
>>   %rest:GET
>>   %rest:path("/test/response/{$name}")
>> function ex:response($name) {
>>   (
>>     <rest:response>
>>       <ser:serialization-parameters>
>>         <ser:method>xml</ser:method>
>>       </ser:serialization-parameters>
>>       <http:response>
>>         <http:header name="Content-Type" value="application/xml"/>
>>       </http:response>
>>     </rest:response>,
>>     <greeting>Hi {$name}!</greeting>
>>   )
>> };
>>
>> The get request http://localhost:8080/exist/restxq/test/response/joe
>> returns this error message:
>>
>> HTTP ERROR 500
>>
>> Problem accessing /exist/restxq/test/response/joe. Reason:
>>
>>     Server Error
>>
>> Caused by:
>>
>> java.lang.NullPointerException
>>       at
>> org.exist.extensions.exquery.restxq.impl.adapters.HttpServletResponseAdapter.setStatus(HttpServletResponseAdapter.java:59)
>>       at
>> org.exquery.restxq.impl.serialization.RestResponseHandler.processHttpResponse(RestResponseHandler.java:120)
>>       at
>> org.exquery.restxq.impl.serialization.RestResponseHandler.process(RestResponseHandler.java:79)
>>       at
>> org.exquery.restxq.impl.serialization.AbstractRestXqServiceSerializer.serialize(AbstractRestXqServiceSerializer.java:141)
>>       at
>> org.exquery.restxq.impl.AbstractRestXqService.service(AbstractRestXqService.java:192)
>>       at
>> org.exist.extensions.exquery.restxq.impl.RestXqServiceImpl.service(RestXqServiceImpl.java:119)
>>       at
>> org.exist.extensions.exquery.restxq.impl.RestXqServlet.service(RestXqServlet.java:102)
>>       at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
>>
>> ...
>>
>> Immanuel
>>
>>
>>
>> 2015-06-18 12:08 GMT+02:00 Adam Retter <[hidden email]>:
>>>
>>> > I am currently switching from the URI rewriting style to the restxq
>>> > style
>>> > and this raises a question regarding output:media-type:
>>> >
>>> > For restxq there is this output:media-type annotation to determine the
>>> > content type of the response. By default it is "application/xml". A
>>> > simple
>>> > example would be:
>>> >
>>> > declare
>>> >     %rest:GET
>>> >     %rest:path("/test/xml/{$name}")
>>> > function ex:hello-xml($name){
>>> >     <greeting>Hi {$name}!</greeting>
>>> > };
>>> >
>>> > If I want the corresponding result as json then this would be an
>>> > implementation:
>>> >
>>> > declare
>>> >     %rest:GET
>>> >     %rest:path("/test/json/{$name}")
>>> >     %output:media-type("application/json")
>>> > function ex:hello-json($name){
>>> >     "{'greeting': 'Hi " || $name || "!'}"
>>> > };
>>> >
>>> > In both cases the %output:media-type is statically attached to the
>>> > function.
>>> >
>>> > How can I implement a function that sets the output:media-type
>>> > dynamically?
>>> >
>>> > Assume, for instance, a client wants to determine the output:media-type
>>> > for
>>> > convenience in a query parameter - e.g. like this:
>>> >
>>> > declare
>>> >     %rest:GET
>>> >     %rest:path("/test/xml/{$name}")
>>> >     %rest:query-param("format","{$format}","xml")
>>> > function ex:hello-xml($name){
>>> >     switch($format)
>>> >      case "xml" return <greeting>Hi {$name}!</greeting>
>>> >     case "json" return ("{'greeting': 'Hi " || $name || "!'}")
>>> >     default return "some appropriate error response"
>>> > };
>>> >
>>> > How can existdb set the appropriate media-type in this case? What would
>>> > be
>>> > the restxq solution or best practise to handle this requierment?
>>>
>>> To answer your question using the example you provided, you would
>>> modify your code like so:
>>>
>>> xquery version "3.0";
>>>
>>> declare namespace http = "http://expath.org/ns/http-client";
>>>
>>> declare
>>>   %rest:GET
>>>   %rest:path("/test/xml/{$name}")
>>>   %rest:query-param("format","{$format}","xml")
>>> function ex:hello-xml($name) {
>>>   let $response := switch($format)
>>>    case "xml" return ("application/xml", <greeting>Hi {$name}!</greeting>)
>>>    case "json" return ("application/json", "{'greeting': 'Hi " ||
>>> $name || "!'}")
>>>    default return ("plain/text", "some appropriate error response")
>>>   return
>>>   (
>>>     <rest:response>
>>>       <http:response>
>>>         <http:header value="Content-Type" value="{$response[1]}"/>
>>>       </http:response>
>>>     </rest:response>,
>>>
>>>     $response[2]
>>>   )
>>> };
>>>
>>>
>>> You could also take advantage of eXist's auto-serialization of XML to
>>> JSON if that makes things easier for you by controlling both the
>>> serialization type and the Intenet Media Type dynamically, e.g.
>>>
>>> xquery version "3.0";
>>>
>>> declare namespace ser =
>>> "http://www.w3.org/2010/xslt-xquery-serialization";
>>> declare namespace http = "http://expath.org/ns/http-client";
>>>
>>> declare
>>>   %rest:GET
>>>   %rest:path("/test/xml/{$name}")
>>>   %rest:query-param("format","{$format}","xml")
>>> function ex:hello-xml($name) {
>>>   let $output := switch($format)
>>>    case "xml" return ("xml", "application/xml")
>>>    case "json" return ("json", "application/json")
>>>    default return ("html", "text/html")
>>>   return
>>>   (
>>>     <rest:response>
>>>       <ser:serialization-parameters>
>>>         <ser:method>{$output[1]}</ser:method>
>>>       </ser:serialization-parameters>
>>>       <http:response>
>>>         <http:header value="Content-Type" value="{$output[2]}"/>
>>>       </http:response>
>>>     </rest:response>,
>>>
>>>     <greeting>Hi {$name}!</greeting>
>>>   )
>>> };
>>>
>>>
>>> However, whilst both of the above will work for you, what you are
>>> really trying to do here is content negotiation, as such I would
>>> encourage you to follow best practice and make use of the hHTTP
>>> 'Accept' header via the %rest:produces annotation to achieve this. I
>>> would instead suggest the following approach to you -
>>>
>>> declare
>>>   %rest:GET
>>>   %rest:path("/test/xml/{$name}")
>>>   %rest:produces("application/xml")
>>> function ex:hello-xml($name) {
>>>   ex:hello($name)
>>> };
>>>
>>> declare
>>>   %rest:GET
>>>   %rest:path("/test/xml/{$name}")
>>>   %rest:produces("application/json")
>>>   %output:method("json")
>>> function ex:hello-json($name) {
>>>   ex:hello($name)
>>> };
>>>
>>> declare
>>>   %private
>>> function ex:hello($name) {
>>>   <greeting>Hi {$name}!</greeting>
>>> };
>>>
>>> The example above makes use of eXist's auto-serialization of XML to
>>> JSON by using the %output:method("json") annotation on the
>>> `ex:hello-json` function, but if you wanted manual control of the JSON
>>> creation, just remove that annotation and adapt your `ex:hello`
>>> function to take a format argument etc.
>>>
>>> HTH. Adam.
>>>
>>> --
>>> Adam Retter
>>>
>>> eXist Developer
>>> { United Kingdom }
>>> [hidden email]
>>> irc://irc.freenode.net/existdb
>>
>>
>
>
>
> --
> Adam Retter
>
> eXist Developer
> { United Kingdom }
> [hidden email]
> irc://irc.freenode.net/existdb



--
Adam Retter

eXist Developer
{ United Kingdom }
[hidden email]
irc://irc.freenode.net/existdb

------------------------------------------------------------------------------
Monitor 25 network devices or servers for free with OpManager!
OpManager is web-based network management software that monitors
network devices and physical & virtual servers, alerts via email & sms
for fault. Monitor 25 devices for free with no restriction. Download now
http://ad.doubleclick.net/ddm/clk/292181274;119417398;o
_______________________________________________
Exist-open mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/exist-open