Skip to content
This repository has been archived by the owner on Nov 9, 2022. It is now read-only.

/roxy/rewrite.xqy broken with ML 8.0-2 #416

Closed
aajacobs opened this issue Apr 21, 2015 · 16 comments
Closed

/roxy/rewrite.xqy broken with ML 8.0-2 #416

aajacobs opened this issue Apr 21, 2015 · 16 comments

Comments

@aajacobs
Copy link

Roxy's /roxy/rewrite.xqy relies on a conf:rewrite function in ML server that was removed in the ML8.0-2 release. As a result, any app using the /roxy/rewrite.xqy will begin showing error similar to the one below:

<error:error xsi:schemaLocation="http://marklogic.com/xdmp/error error.xsd" xmlns:error="http://marklogic.com/xdmp/error"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <error:code>XDMP-UNDFUN</error:code>
  <error:name>err:XPST0017</error:name>
  <error:xquery-version>1.0-ml</error:xquery-version>
  <error:message>Undefined function</error:message>
  <error:format-string>XDMP-UNDFUN: (err:XPST0017) Undefined function conf:rewrite()</error:format-string>
  <error:retryable>false</error:retryable>
  <error:expr> </error:expr>
  <error:data>
    <error:datum>conf:rewrite()</error:datum>
  </error:data>
  <error:stack>
    <error:frame>
      <error:line>7</error:line>
      <error:column>9</error:column>
      <error:operation>xdmp:eval("&amp;#10;        import module namespace conf = &amp;quot;http://marklog...", (fn:QName("","method"), "GET", fn:QName("","uri"), ...))</error:operation>
      <error:xquery-version>1.0-ml</error:xquery-version>
    </error:frame>
    <error:frame>
      <error:uri>/roxy/rewrite.xqy</error:uri>
      <error:line>40</error:line>
      <error:column>6</error:column>
      <error:variables>
    <error:variable>
      <error:name xmlns="">uri</error:name>
      <error:value>"/web/scripts/d3js/crossfilter.v1.min.js"</error:value>
    </error:variable>
    <error:variable>
      <error:name xmlns="">method</error:name>
      <error:value>"GET"</error:value>
    </error:variable>
    <error:variable>
      <error:name xmlns="">path</error:name>
      <error:value>"/web/scripts/d3js/crossfilter.v1.min.js"</error:value>
    </error:variable>
    <error:variable>
      <error:name xmlns="">final-uri</error:name>
      <error:value>()</error:value>
    </error:variable>
      </error:variables>
      <error:xquery-version>1.0-ml</error:xquery-version>
    </error:frame>
  </error:stack>
</error:error>

Geert moved the functions missing from ML8 into the /roxy/rewrite.xqy file as a temporary fix, which I've verified is working with my app. His new /roxy/rewrite.xqy file is below:

xquery version "1.0-ml";

import module namespace config = "http://marklogic.com/roxy/config" at "/app/config/config.xqy";
import module namespace def = "http://marklogic.com/roxy/defaults" at "/roxy/config/defaults.xqy";
import module namespace req = "http://marklogic.com/roxy/request" at "/roxy/lib/request.xqy";

declare namespace rest = "http://marklogic.com/appservices/rest";

declare option xdmp:mapping "false";

let $uri  := xdmp:get-request-url()
let $method := xdmp:get-request-method()
let $path := xdmp:get-request-path()
let $final-uri :=
  req:rewrite(
    $uri,
    $path,
    $method,
    $config:ROXY-ROUTES)
return
  if ($final-uri) then $final-uri
  else
    try
    {
      xdmp:eval('
      import module namespace conf = "http://marklogic.com/rest-api/endpoints/config"
                at "/MarkLogic/rest-api/endpoints/config.xqy";
      import module namespace rest = "http://marklogic.com/appservices/rest"
        at "/MarkLogic/appservices/utils/rest.xqy";
      import module namespace eput = "http://marklogic.com/rest-api/lib/endpoint-util"
          at "/MarkLogic/rest-api/lib/endpoint-util.xqy";
              declare variable $method external;
              declare variable $uri external;
              declare variable $path external;

      (: This is a hack, but allows us to maintain one rule set endpoints and rewriter,
          while pushing the finer-grained error handling to the endpoint level. :)
      declare function conf:rewrite-rules(
      ) as map:map
      {
          let $old-rules := eput:get-rest-options()
          return
              if (exists($old-rules))
              then $old-rules
              else
                  let $all-methods := ("GET","POST","PUT","DELETE","HEAD","OPTIONS")
                  let $new-rules   := map:map()
                  let $unsupported := map:map()
                  return (
                      for $rule in (
                          conf:get-default-request-rule(),
                          conf:get-config-indexes-request-rule(),
                          conf:get-config-namespaces-item-request-rule(),
                          conf:get-config-namespaces-request-rule(),
                          conf:get-config-properties-request-rule(),
                          conf:get-config-query-child-request-rule(),
                          conf:get-config-query-list-request-rule(),
                          conf:get-config-query-request-rule(),
                          conf:get-document-query-rule(),
                          conf:get-document-update-rule(),
                          conf:get-keyvalue-list-request-rule(),
                          conf:get-qbe-request-rule(),
                          conf:get-ping-request-rule(),
                          conf:get-rsrc-list-query-rule(),
                          conf:get-rsrc-item-query-rule(),
                          conf:get-rsrc-item-update-rule(),
                          conf:get-rsrc-exec-query-rule(),
                          conf:get-rsrc-exec-update-rule(),
                          conf:get-search-query-request-rule(),
                          conf:get-search-update-request-rule(),
                          conf:get-tfm-list-request-rule(),
                          conf:get-tfm-item-request-rule(),
                          conf:get-txn-request-rule(),
                          conf:get-values-request-rule(),
                          conf:get-sparql-protocol-rule(),
                          conf:get-graph-explore-rule(),
                          conf:get-graphstore-protocol-rule(),
                          conf:get-suggest-request-rule(),
                          conf:get-rules-list-rule(),
                          conf:get-alert-rules-item-rule(),
                          conf:get-alert-match-rule(),
                          conf:get-extlib-root-request-rule(),
                          conf:get-extlib-request-rule()
                          )
                      let $endpoint   := $rule/@endpoint/string(.)
                      (: Note: depends on document order in rule :)
                      let $uri-params := $rule/rest:uri-param/@name/string(.)
                      let $methods    := $rule/rest:http/@method/tokenize(string(.)," ")
                      for $match in $rule/@fast-match/tokenize(string(.), "\|")
                      return (
                          for $method in $methods
                          return map:put($new-rules, $method||$match, ($endpoint,$uri-params)),

                          let $candidates :=
                              let $candidate-methods := map:get($unsupported,$match)
                              return
                                  if (exists($candidate-methods))
                                  then $candidate-methods
                                  else $all-methods
                          return map:put(
                              $unsupported, $match, $candidates[not(. = $methods)]
                              )
                          ),

                      for $match in map:keys($unsupported)
                      for $method in map:get($unsupported,$match)
                      return map:put(
                          $new-rules,
                          $method||$match,
                          "/MarkLogic/rest-api/endpoints/unsupported-method.xqy"
                          ),

                      eput:set-rest-options($new-rules),
                      $new-rules
                      )
      };

      declare function conf:rewrite(
          $method   as xs:string,
          $uri      as xs:string,
          $old-path as xs:string
      ) as xs:string?
      {
          let $rules      := conf:rewrite-rules()
          (: skip the empty step before the initial / :)
          let $raw-steps  := subsequence(tokenize($old-path,"/"), 2)
          let $raw-count  := count($raw-steps)
          (: check for an empty step after a trailing / :)
          let $extra-step := (subsequence($raw-steps,$raw-count,1) eq "")
          let $step-count :=
              if ($extra-step)
              then $raw-count - 1
              else $raw-count
          let $steps      :=
              if ($step-count eq 0)
              then ()
              else if ($extra-step)
              then subsequence($raw-steps, 1, $step-count)
              else $raw-steps
          (: generate the key for lookup in the rules map :)
          let $key        :=
              (: no rule :)
              if ($step-count eq 1)
              then ()
              (: default rule :)
              else if ($step-count eq 0)
              then ""
              else
                  let $first-step := subsequence($steps,1,1)
                  return
                  (: as in /content/help :)
                  if ($first-step eq "content")
                  then ($first-step,"*")
                  else if (not($first-step = ("v1","LATEST")))
                  (: no rule :)
                  then ()
                  else
                      let $second-step := subsequence($steps,2,1)
                      return
                      (: as in /v1/documents :)
                      if ($step-count eq 2)
                      then ("*",$second-step)
                      else
                          let $third-step := subsequence($steps,3,1)
                          return
                          if ($second-step = ("ext"))
                          then
                          ("*",$second-step,"**")
                          else if ($second-step = ("config","alert","graphs")) then
                              (: as in /v1/config/namespaces :)
                              if ($step-count eq 3)
                              then ("*", $second-step, $third-step)
                              (: /v1/config/options/NAME or /v1/config/options/NAME/SUBNAME :)
                              else if ($step-count le 5)
                              then ("*", $second-step, $third-step, (4 to $step-count) ! "*")
                              else ()
                          (: as in /v1/transactions/TXID :)
                          else if ($step-count eq 3)
                          then ("*", $second-step, "*")
                          (: catch all :)
                          else if ($step-count le 5)
                          then ("*", $second-step, $third-step, (4 to $step-count) ! "*")
                          (: no rule :)
                          else ()
          let $key-method :=
              if ($method eq "POST" and starts-with(
                  head(xdmp:get-request-header("content-type")), "application/x-www-form-urlencoded"
                  ))
              then "GET"
              else $method
          let $value :=
              if (empty($key)) then ()
              else map:get($rules, string-join(($key-method,$key), "/"))
          let $value-count := count($value)
          return
              (: fallback :)
              if ($value-count eq 0)
              then $uri
              else
                  let $old-length := string-length($old-path)
                  let $has-params := (string-length($uri) ne $old-length)
                  let $new-path   :=
                      (: append parameters to the rewritten path :)
                      if ($has-params)
                      then subsequence($value,1,1)||substring($uri,$old-length+1)
                      else subsequence($value,1,1)
                  return
                      if ($value-count eq 1)
                      then $new-path
                      (: append parameters from rule to the rewritten path :)
                      else string-join(
                          (
                              $new-path,
                              let $step-names := subsequence($value,2)
                              for $step-value at $i in
                                  for $j in 2 to count($key)
                                  let $place-holder := subsequence($key,$j,1)
                                  return
                                      if ($place-holder eq "*")
                                      then subsequence($steps,$j,1)
                                      else if ($place-holder eq "**")
                                      (: using raw-steps picks up trailing slash :)
                                      then string-join(subsequence($raw-steps,$j),"/")
                                      else ()

                              return (
                                  if ($has-params or $i gt 1)
                                      then "&amp;amp;"
                                      else "?",
                                  subsequence($step-names,$i,1),
                                  "=",
                                  $step-value
                                  )
                              ),
                          ""
                          )
      };

          (conf:rewrite($method, $uri, $path), $uri)[1]',
          (xs:QName("method"), $method,
           xs:QName("uri"), $uri,
           xs:QName("path"), $path))
    }
    catch($ex) {
      if ($ex/error:code = "XDMP-MODNOTFOUND") then
        $uri
      else
        xdmp:rethrow()
    }
@dmcassel
Copy link
Collaborator

As Geert noted, this should only affect hybrid applications, as MVC keeps its own rewriter and REST apps just use the new one from MarkLogic.

@garyrusso
Copy link
Contributor

Geert's code is not working for me yet. I get a 404 not found.

The conf:rewrite($method, $uri, $path) function returns this:

  • /MarkLogic/rest-api/endpoints/search-list-query.xqy?q=test

For parameters:

  • $method = GET
  • $uri = /v1/search?q=test
  • $path = /v1/search

Is this the correct result?

I'm running it with MarkLogic version 8.0-2

@grtjn
Copy link
Contributor

grtjn commented Apr 21, 2015

I'll take a closer look..

@grtjn
Copy link
Contributor

grtjn commented Apr 21, 2015

@garyrusso:

Ramped up an ml8.0-2 VM using mlvagrant, and deployed a fresh hybrid project (version 8, dev branch) against it, and I can do the search:

<search:response xmlns:search="http://marklogic.com/appservices/search" snippet-format="snippet" total="0" start="1" page-length="10">
<search:qtext>test</search:qtext>
<search:metrics>
<search:query-resolution-time>PT0.003603S</search:query-resolution-time>
<search:facet-resolution-time>PT0.000077S</search:facet-resolution-time>
<search:snippet-resolution-time>PT0S</search:snippet-resolution-time>
<search:total-time>PT0.114011S</search:total-time>
</search:metrics>
</search:response>

If you don't have the correct rewrite.xqy in place, merely deploying rest config and extensions will fail already. That also depends on a properly functioning REST-api. Could you have accidentally forgotten to run deploy modules?

@garyrusso
Copy link
Contributor

This fix is working for me now. My problem was the rewrite-resolves-globally setting in the build.properties file. It needs to be set to true.

@grtjn
Copy link
Contributor

grtjn commented Apr 30, 2015

I've included a small change in PR #395 that gives a warning if you use hybrid with ML 8, and point to this issue. I vote for closing this issue.

@dmcassel
Copy link
Collaborator

What about putting the conf:rewrite code into the provided code under src/roxy? It won't help upgrades, but it sounds like that would work for new projects.

@grtjn
Copy link
Contributor

grtjn commented Apr 30, 2015

You need to distinguish between 8.0-1.1 and less, against 8.0-2 and higher. And there might be more changes in the api in 8.0-2++..

Another objection is that above code is only a temporary fix, which doesn't take REST endpoint changes into account..

@grtjn
Copy link
Contributor

grtjn commented Jul 6, 2015

I vote closing this as won't fix, at least for the time being. Maybe future brings us better ideas/alternatives..

@grtjn
Copy link
Contributor

grtjn commented Jul 7, 2015

Worth creating a wiki page about this?

@grtjn
Copy link
Contributor

grtjn commented Sep 17, 2015

Closing as won't fix..

@grtjn grtjn closed this as completed Sep 17, 2015
@grtjn grtjn added the wontfix label Sep 17, 2015
@grtjn grtjn added this to the 1.7.3 milestone Sep 17, 2015
@RobertSzkutak
Copy link
Contributor

Hi,

I hit this issue today. I had to write a rewriter that supported custom routes as well as REST extensions. In the past I had just leveraged this code when such a use case came up. Unfortunately, the "quick fix" code did not work for my rest extensions. Part of this may also be due to that I was using RXQ for rewriting.

This is the solution I came up with that worked for REST extensions in the end. It would need to be refactored a bit to be able to be put into Roxy instead of RXQ. Sharing it in case is may be of value to someone else:

xquery version "1.0-ml";

module namespace restrw = "http://optum.com/restrewrite";

import module namespace rxq = "http://exquery.org/ns/restxq" at "/rxq.xqy";

import module namespace conf = "http://marklogic.com/rest-api/endpoints/config"
              at "/MarkLogic/rest-api/endpoints/config.xqy";

declare
%rxq:produces('text/html')
%rxq:GET
%rxq:base-path('/')
%rxq:path('/v1/resources/(.*)')
function  restrw:rewrite($path as xs:string) {
let $method := "GET"
let $name := $path
let $uri := xdmp:get-request-url()

return
     try
    {
      xdmp:eval('xquery version "1.0-ml";

(: Copyright 2011-2015 MarkLogic Corporation.  All Rights Reserved. :)

import module namespace conf = "http://marklogic.com/rest-api/endpoints/config"
    at "/MarkLogic/rest-api/endpoints/config.xqy";

import module namespace rsrcmodcom = "http://marklogic.com/rest-api/models/resource-model-common"
    at "/MarkLogic/rest-api/models/resource-model-common.xqy";

import module namespace rsrcmodqry = "http://marklogic.com/rest-api/models/resource-model-query"
    at "/MarkLogic/rest-api/models/resource-model-query.xqy";

import module namespace rest = "http://marklogic.com/appservices/rest" 
    at "/MarkLogic/appservices/utils/rest.xqy";

import module namespace eput = "http://marklogic.com/rest-api/lib/endpoint-util"
    at "/MarkLogic/rest-api/lib/endpoint-util.xqy";

import module namespace logger = "http://marklogic.com/rest-api/logger"
    at "/MarkLogic/rest-api/lib/logger.xqy";

declare default function namespace "http://www.w3.org/2005/xpath-functions";
declare option xdmp:mapping "false";

declare option xdmp:transaction-mode "auto";

declare variable $name external;

declare private function local:rsrcmod-callback(
    $request-result   as xs:string,
    $plugin-name      as xs:string,
    $has-content      as xs:boolean,
    $response-type    as xs:string?,
    $response-status  as xs:anyAtomicType*,
    $response-headers as item()*
) as empty-sequence()
{
    if ($request-result = ($rsrcmodqry:RESOURCE_APPLIED, $rsrcmodqry:RESOURCE_READ))
    then ()
    else error((),"RESTAPI-INTERNALERROR",
        concat("unknown result ",$request-result," for ",$plugin-name)),

    rsrcmodcom:environment(
        $plugin-name,$has-content,$response-type,$response-status,$response-headers
    )
};

logger:dump-request-environment(),

let $params  := rest:process-request(conf:get-rsrc-exec-query-rule())
let $_ := map:put($params, "name", $name)
let $headers := eput:get-request-headers()
let $method  := xdmp:get-request-method()
let $body :=
    if (not($method = ("POST")) or starts-with(
        head(map:get($headers,"content-type")), "application/x-www-form-urlencoded"
        )) then ()
    else xdmp:get-request-body()
return (
    switch ($method)
    case "GET" return (
        xdmp:security-assert("http://marklogic.com/xdmp/privileges/rest-reader", "execute"),
        rsrcmodqry:exec-get($headers,$params,local:rsrcmod-callback#6)
        )
    case "POST" return (
        xdmp:security-assert("http://marklogic.com/xdmp/privileges/rest-reader", "execute"),
        rsrcmodqry:exec-post($headers,$params,$body,local:rsrcmod-callback#6)
            [not(xdmp:get-response-code()[1] eq 204)]
        )
    default return (
        error((),"RESTAPI-INVALIDREQ",
            concat("unsupported method ",$method," for ",map:get($params,"name"))
            )
        )
    )
      ',
          (xs:QName("name"), $name))
    }
    catch($ex) {
      if ($ex/error:code = "XDMP-MODNOTFOUND") then
        $uri
      else
        xdmp:rethrow()
    }

};

@grtjn
Copy link
Contributor

grtjn commented Oct 22, 2015

Thnx for sharing!

@joeglorioso
Copy link

After upgrading to 8.0-4 on OS X, I receive the following error from the rewrite.xqy workaround code when running ml local deploy modules. Is anyone able to replicate this error?

ERROR: 500 "Internal Server Error"
ERROR: <error:error xsi:schemaLocation="http://marklogic.com/xdmp/error error.xsd" xmlns:error="http://marklogic.com/xdmp/error" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <error:code>XDMP-UNDFUN</error:code>
  <error:name>err:XPST0017</error:name>
  <error:xquery-version>1.0-ml</error:xquery-version>
  <error:message>Undefined function</error:message>
  <error:format-string>XDMP-UNDFUN: (err:XPST0017) Undefined function conf:get-graphstore-protocol-rule()</error:format-string>
  <error:retryable>false</error:retryable>
  <error:expr> </error:expr>
  <error:data>
    <error:datum>conf:get-graphstore-protocol-rule()</error:datum>
  </error:data>
  <error:stack>
    <error:frame>
      <error:line>53</error:line>
      <error:column>26</error:column>
      <error:operation>xdmp:eval("&amp;#10;      import module namespace conf = &amp;quot;http://marklogic...", (fn:QName("","method"), "PUT", fn:QName("","uri"), ...))</error:operation>
      <error:xquery-version>1.0-ml</error:xquery-version>
    </error:frame>
    <error:frame>
      <error:uri>/roxy/rewrite.xqy</error:uri>
      <error:line>25</error:line>
      <error:column>6</error:column>
      <error:variables>
    <error:variable>
      <error:name xmlns="">uri</error:name>
      <error:value>"/v1/config/properties"</error:value>
    </error:variable>
    <error:variable>
      <error:name xmlns="">method</error:name>
      <error:value>"PUT"</error:value>
    </error:variable>
    <error:variable>
      <error:name xmlns="">path</error:name>
      <error:value>"/v1/config/properties"</error:value>
    </error:variable>
    <error:variable>
      <error:name xmlns="">final-uri</error:name>
      <error:value>()</error:value>
    </error:variable>
      </error:variables>
      <error:xquery-version>1.0-ml</error:xquery-version>
    </error:frame>
  </error:stack>
</error:error>

@tdiepenbrock
Copy link

I have the same issue. That file changed in 8.0-4. Working on an update to the above fix/hack/workaround.

@tdiepenbrock
Copy link

Fix for 8.0-4:

xquery version "1.0-ml";

import module namespace config = "http://marklogic.com/roxy/config" at "/app/config/config.xqy";
import module namespace def = "http://marklogic.com/roxy/defaults" at "/roxy/config/defaults.xqy";
import module namespace req = "http://marklogic.com/roxy/request" at "/roxy/lib/request.xqy";

declare namespace rest = "http://marklogic.com/appservices/rest";

declare option xdmp:mapping "false";

let $uri  := xdmp:get-request-url()
let $method := xdmp:get-request-method()
let $path := xdmp:get-request-path()
let $final-uri :=
  req:rewrite(
    $uri,
    $path,
    $method,
    $config:ROXY-ROUTES)
return
  if ($final-uri) then $final-uri
  else
    try
    {
      xdmp:eval('
      import module namespace conf = "http://marklogic.com/rest-api/endpoints/config"
                at "/MarkLogic/rest-api/endpoints/config.xqy";
      import module namespace rest = "http://marklogic.com/appservices/rest"
        at "/MarkLogic/appservices/utils/rest.xqy";
      import module namespace eput = "http://marklogic.com/rest-api/lib/endpoint-util"
          at "/MarkLogic/rest-api/lib/endpoint-util.xqy";
              declare variable $method external;
              declare variable $uri external;
              declare variable $path external;

      (: This is a hack, but allows us to maintain one rule set endpoints and rewriter,
          while pushing the finer-grained error handling to the endpoint level. :)
      declare function conf:rewrite-rules(
      ) as map:map
      {
          let $old-rules := eput:get-rest-options()
          return
              if (exists($old-rules))
              then $old-rules
              else
                  let $all-methods := ("GET","POST","PUT","DELETE","HEAD","OPTIONS")
                  let $new-rules   := map:map()
                  let $unsupported := map:map()
                  return (
                      for $rule in (
                          conf:get-default-request-rule(),
                          conf:get-config-indexes-request-rule(),
                          conf:get-config-namespaces-item-request-rule(),
                          conf:get-config-namespaces-request-rule(),
                          conf:get-config-properties-request-rule(),
                          conf:get-config-query-child-request-rule(),
                          conf:get-config-query-list-request-rule(),
                          conf:get-config-query-request-rule(),
                          conf:get-document-query-rule(),
                          conf:get-document-update-rule(),
                          conf:get-keyvalue-list-request-rule(),
                          conf:get-qbe-request-rule(),
                          conf:get-ping-request-rule(),
                          conf:get-rsrc-list-query-rule(),
                          conf:get-rsrc-item-query-rule(),
                          conf:get-rsrc-item-update-rule(),
                          conf:get-rsrc-exec-query-rule(),
                          conf:get-rsrc-exec-update-rule(),
                          conf:get-search-query-request-rule(),
                          conf:get-search-update-request-rule(),
                          conf:get-tfm-list-request-rule(),
                          conf:get-tfm-item-request-rule(),
                          conf:get-txn-request-rule(),
                          conf:get-values-request-rule(),
                          conf:get-sparql-protocol-rule(),
                          conf:get-graph-explore-rule(),
                          conf:get-graphstore-protocol-query-rule(),
                          conf:get-graphstore-protocol-update-rule(),
                          conf:get-suggest-request-rule(),
                          conf:get-rules-list-rule(),
                          conf:get-alert-rules-item-rule(),
                          conf:get-alert-match-rule(),
                          conf:get-extlib-root-request-rule(),
                          conf:get-extlib-request-rule()
                          )
                      let $endpoint   := $rule/@endpoint/string(.)
                      (: Note: depends on document order in rule :)
                      let $uri-params := $rule/rest:uri-param/@name/string(.)
                      let $methods    := $rule/rest:http/@method/tokenize(string(.)," ")
                      for $match in $rule/@fast-match/tokenize(string(.), "\|")
                      return (
                          for $method in $methods
                          return map:put($new-rules, $method||$match, ($endpoint,$uri-params)),

                          let $candidates :=
                              let $candidate-methods := map:get($unsupported,$match)
                              return
                                  if (exists($candidate-methods))
                                  then $candidate-methods
                                  else $all-methods
                          return map:put(
                              $unsupported, $match, $candidates[not(. = $methods)]
                              )
                          ),

                      for $match in map:keys($unsupported)
                      for $method in map:get($unsupported,$match)
                      return map:put(
                          $new-rules,
                          $method||$match,
                          "/MarkLogic/rest-api/endpoints/unsupported-method.xqy"
                          ),

                      eput:set-rest-options($new-rules),
                      $new-rules
                      )
      };

      declare function conf:rewrite(
          $method   as xs:string,
          $uri      as xs:string,
          $old-path as xs:string
      ) as xs:string?
      {
          let $rules      := conf:rewrite-rules()
          (: skip the empty step before the initial / :)
          let $raw-steps  := subsequence(tokenize($old-path,"/"), 2)
          let $raw-count  := count($raw-steps)
          (: check for an empty step after a trailing / :)
          let $extra-step := (subsequence($raw-steps,$raw-count,1) eq "")
          let $step-count :=
              if ($extra-step)
              then $raw-count - 1
              else $raw-count
          let $steps      :=
              if ($step-count eq 0)
              then ()
              else if ($extra-step)
              then subsequence($raw-steps, 1, $step-count)
              else $raw-steps
          (: generate the key for lookup in the rules map :)
          let $key        :=
              (: no rule :)
              if ($step-count eq 1)
              then ()
              (: default rule :)
              else if ($step-count eq 0)
              then ""
              else
                  let $first-step := subsequence($steps,1,1)
                  return
                  (: as in /content/help :)
                  if ($first-step eq "content")
                  then ($first-step,"*")
                  else if (not($first-step = ("v1","LATEST")))
                  (: no rule :)
                  then ()
                  else
                      let $second-step := subsequence($steps,2,1)
                      return
                      (: as in /v1/documents :)
                      if ($step-count eq 2)
                      then ("*",$second-step)
                      else
                          let $third-step := subsequence($steps,3,1)
                          return
                          if ($second-step = ("ext"))
                          then
                          ("*",$second-step,"**")
                          else if ($second-step = ("config","alert","graphs")) then
                              (: as in /v1/config/namespaces :)
                              if ($step-count eq 3)
                              then ("*", $second-step, $third-step)
                              (: /v1/config/options/NAME or /v1/config/options/NAME/SUBNAME :)
                              else if ($step-count le 5)
                              then ("*", $second-step, $third-step, (4 to $step-count) ! "*")
                              else ()
                          (: as in /v1/transactions/TXID :)
                          else if ($step-count eq 3)
                          then ("*", $second-step, "*")
                          (: catch all :)
                          else if ($step-count le 5)
                          then ("*", $second-step, $third-step, (4 to $step-count) ! "*")
                          (: no rule :)
                          else ()
          let $key-method :=
              if ($method eq "POST" and starts-with(
                  head(xdmp:get-request-header("content-type")), "application/x-www-form-urlencoded"
                  ))
              then "GET"
              else $method
          let $value :=
              if (empty($key)) then ()
              else map:get($rules, string-join(($key-method,$key), "/"))
          let $value-count := count($value)
          return
              (: fallback :)
              if ($value-count eq 0)
              then $uri
              else
                  let $old-length := string-length($old-path)
                  let $has-params := (string-length($uri) ne $old-length)
                  let $new-path   :=
                      (: append parameters to the rewritten path :)
                      if ($has-params)
                      then subsequence($value,1,1)||substring($uri,$old-length+1)
                      else subsequence($value,1,1)
                  return
                      if ($value-count eq 1)
                      then $new-path
                      (: append parameters from rule to the rewritten path :)
                      else string-join(
                          (
                              $new-path,
                              let $step-names := subsequence($value,2)
                              for $step-value at $i in
                                  for $j in 2 to count($key)
                                  let $place-holder := subsequence($key,$j,1)
                                  return
                                      if ($place-holder eq "*")
                                      then subsequence($steps,$j,1)
                                      else if ($place-holder eq "**")
                                      (: using raw-steps picks up trailing slash :)
                                      then string-join(subsequence($raw-steps,$j),"/")
                                      else ()

                              return (
                                  if ($has-params or $i gt 1)
                                      then "&amp;amp;"
                                      else "?",
                                  subsequence($step-names,$i,1),
                                  "=",
                                  $step-value
                                  )
                              ),
                          ""
                          )
      };

          (conf:rewrite($method, $uri, $path), $uri)[1]',
          (xs:QName("method"), $method,
           xs:QName("uri"), $uri,
           xs:QName("path"), $path))
    }
    catch($ex) {
      if ($ex/error:code = "XDMP-MODNOTFOUND") then
        $uri
      else
        xdmp:rethrow()
    }

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

7 participants