[feature] Allow resources to be loaded from the classpath

xquery version "3.1";

module namespace t = "http://exist-db.org/xquery/testing";

import module namespace system = "http://exist-db.org/xquery/system";
import module namespace util = "http://exist-db.org/xquery/util";
import module namespace xmldb = "http://exist-db.org/xquery/xmldb";
import module namespace xdiff = "http://exist-db.org/xquery/xmldiff"
        at "java:org.exist.xquery.modules.xmldiff.XmlDiffModule";

declare namespace err = "http://www.w3.org/2005/xqt-errors";
declare namespace exerr = "http://www.exist-db.org/xqt-errors/";

declare function t:setup-action($action) {
    typeswitch ($action)
        case element(create-collection) return
            xmldb:create-collection($action/@parent, $action/@name)
        case element(store) return
			t:store($action)
		case element(store-files) return
            t:store-files($action)
        case element(remove-collection) return
            xmldb:remove($action/@collection)
        case element(remove-document) return
            xmldb:remove($action/@collection, $action/@name)
        default return
            ()
};

declare function t:store($action as element(store)) {
    let $type := if ($action/@type) then $action/@type/string() else "application/xml"
    let $data :=
		if ($action/*) then
			$action/*[1]
		else
			$action/string()
	return
        xmldb:store($action/@collection, $action/@name, $data, $type)
};

declare function t:store-files($action as element(store-files)) {
    let $type := if ($action/@type) then $action/@type/string() else "application/xml"
    return
        if ($action/@classpath)
        then
            let $classpath-entries := util:system-property("java.class.path")
            let $matches := fn:tokenize($classpath-entries, ":")[fn:ends-with(., $action/@classpath)]
            return
                if (not(empty($matches)))
                then
                    xmldb:store-files-from-pattern($action/@collection, $matches[1], $action/@pattern, $type)
                else
                    util:log("ERROR", ("Could not match classpath with '" || $action/@classpath || "' in test.xq"))
        else
            xmldb:store-files-from-pattern($action/@collection, $action/@dir, $action/@pattern, $type)
};

declare function t:setup($setup as element(setup)?) {
    for $action in $setup/*
    return
        t:setup-action($action)
};

declare function t:tearDown($tearDown as element(tearDown)?) {
    for $action in $tearDown/*
    return
        t:setup-action($action)
};

declare function t:declare-variable($var as element(variable)) as item()? {
    let $children := $var/*
    return
        if (empty($children)) then
            string-join($var/node(), '')
        else
            fn:serialize($children)
};

declare function t:init-prolog($test as element(test)) {
	let $imports := $test/../imports
	let $vars :=
	    string-join(
        	for $var in $test/../variable
        	return
        	    concat("declare variable $", $var/@name, " := ", t:declare-variable($var), ";"),
            ""
        )
	return
		string-join(($imports, $vars, $test/../functions), '')
};

declare function t:test($result as item()*) {
    if ($result instance of xs:boolean) then
        $result
    else
        exists($result)
};

declare function t:run-test($test as element(test), $count as xs:integer) {
    t:run-test($test, $count, (), (), (), (), (), ())
};

declare function t:run-test($test as element(test), $count as xs:integer,
        $test-ignored-function as (function(xs:string) as empty-sequence())?,
        $test-started-function as (function(xs:string) as empty-sequence())?,
        $test-failure-function as (function(xs:string, map(xs:string, item()?), map(xs:string, item()?)) as empty-sequence())?,
        $test-assumption-failed-function as (function(xs:string, map(xs:string, item()?)?) as empty-sequence())?,
        $test-error-function as (function(xs:string, map(xs:string, item()?)?) as empty-sequence())?,
        $test-finished-function as (function(xs:string) as empty-sequence())?) {
	let $context := t:init-prolog($test)
	let $null :=
	   if ($test/@trace eq 'yes') then
	       (system:clear-trace(), system:enable-tracing(true(), false()))
       else ()
    let $highlight-option := concat("highlight-matches=",
          if ($test/expected//@*[matches(., '^(\|{3}).*\1$')] and $test/expected//exist:match) then "both"
          else if ($test/expected//@*[matches(., '^(\|{3}).*\1$')]) then "attributes"
          else if ($test/expected//exist:match) then "elements"
          else "none"
          )
    let $serialize-options :=
      let $decls := ($test/../*[name() ne 'test']|$test/code)[matches(., 'declare[\- ]option(\(("|')|\s+)exist:serialize(\2,)?\s+("|').*?\4[;)]')]
      let $ops1 := $decls/replace(., "declare[\- ]option(\(("|')|\s+)exist:serialize(\2,)?\s+("|')(.*?)\4[;)]", "_|$5_")
      let $ops2 :=
        for $a in $ops1
        for $b in tokenize($a, '_')[starts-with(., '|')]
        return tokenize(substring-after($b, '|'), '\s+')
      return if (count($ops2[matches(., 'highlight-matches')]))
        then string-join($ops2, ' ')
        else string-join(($ops2, $highlight-option), ' ')
    let $_notify-start := if(not(empty($test-started-function))) then $test-started-function($test/task) else ()
    let $queryOutput :=
        try {
            map {
                "result": util:eval(concat($context, $test/code/string()), false())
            }
        } catch * {
            map {
                "error": map {
                    "code": $err:code,
                    "description": $err:description,
                    "value": $err:value,
                    "module": $err:module,
                    "line-number": $err:line-number,
                    "column-number": $err:column-number,
                    "additional": $err:additional,
                    "xquery-stack-trace": $exerr:xquery-stack-trace,
                    "java-stack-trace": $exerr:java-stack-trace
                }
            }
        }
	let $output := if ($test/@trace eq 'yes') then map { "result": system:trace() } else $queryOutput
    let $expanded :=
        if (not(empty($output("error")))) then
            $output
        else if ($test/@serialize) then
            try {

                let $ser-params := map:merge(
                    let $options := fn:tokenize($test/@serialize/string(), " ")
                    for $option in $options
                    let $kv := fn:tokenize($option, "=")
                    return
                        map:entry($kv[1], $kv[2])
                )
                return
                    map {
                        "result": normalize-space(string-join($output("result") ! fn:serialize(., $ser-params)))
                    }
            } catch * {
                map {
                    "error": map {
                        "code": $err:code,
                        "description": $err:description,
                        "value": $err:value,
                        "module": $err:module,
                        "line-number": $err:line-number,
                        "column-number": $err:column-number,
                        "additional": $err:additional,
                        "xquery-stack-trace": $exerr:xquery-stack-trace,
                        "java-stack-trace": $exerr:java-stack-trace
                    }
                }
            }

		else if ($output("result") instance of node()) then
        	map {
        	    "result": util:expand($output("result"), $serialize-options)
        	}
		else
			$output
    let $expected :=
        if ($test/@output eq 'text') then
            data($test/expected)
        else $test/expected/node()
    let $OK :=
        if ($test/error) then
            not(empty($expanded("error"))) and (contains($expanded("error")("description"), $test/error) or local-name-from-QName($expanded("error")("code")) eq string($test/error))
        else if ($test/xpath) then
            t:test(t:xpath($output("result"), $test/xpath))
        else if ($test/@output eq 'text') then
            let $asString :=
                if ($test/@serialize) then
                    $expanded("result")
                else
                    normalize-space(string-join(for $x in $output("result") return string($x),' '))
            return
                $asString eq normalize-space($expected)
        else
            let $xn := t:normalize($expanded("result"))
            let $en := t:normalize($expected)
            return
        	   deep-equal($xn, $en)
    return
        (
            <test n="{$count}" pass="{$OK}">
            { $test/@id }
            {
                if (not($OK)) then
                    let $_notify :=
                        if(not(empty($expanded("error")))) then
                            if(not(empty($test-error-function))) then $test-error-function($test/task, $expanded("error")) else ()
                        else
                            if(not(empty($test-failure-function))) then $test-failure-function($test/task, map { "value": $test/expected/node(), "xpath": $test/xpath/node(), "error": $test/error/node() }, $expanded) else ()
                    return
                        ($test/task, $test/expected, $test/xpath, <result>{$expanded("result"), if(not(empty($expanded("error")))) then $expanded("error")("description") else ()}</result>)
                else
                    ()
            }
            </test>
            ,
            let $_notify-finished := if(not(empty($test-finished-function))) then $test-finished-function($test/task) else ()
            return ()
        )
};

declare function t:normalize($nodes as node()*) {
	for $node in $nodes return t:normalize-node($node)
};

declare function t:normalize-node($node as node()) {
	typeswitch ($node)
		case element() return
			element { node-name($node) } {
				$node/@*, for $child in $node/node() return t:normalize-node($child)
			}
		case text() return
			let $norm := normalize-space($node)
			return
				if (string-length($norm) eq 0) then () else $node
		default return
			$node
};

declare function t:xpath($output as item()*, $xpath as element()) {
    for $prefix in in-scope-prefixes($xpath)
    let $nsuri := namespace-uri-for-prefix($prefix, $xpath)
    return
        if ($prefix != 'xml') then
            util:declare-namespace($prefix, $nsuri)
        else (),
    let $expr := $xpath/string()
    return
        if (matches($expr, "^\s*/")) then
            util:eval(concat("$output", $expr))
        else
            util:eval($expr)
};

declare function t:run-testSet($set as element(TestSet), $id as xs:string?) {
    t:run-testSet($set, $id, (), (), (), (), (), ())
};

declare function t:run-testSet($set as element(TestSet), $id as xs:string?,
        $test-ignored-function as (function(xs:string) as empty-sequence())?,
        $test-started-function as (function(xs:string) as empty-sequence())?,
        $test-failure-function as (function(xs:string, map(xs:string, item()?), map(xs:string, item()?)) as empty-sequence())?,
        $test-assumption-failed-function as (function(xs:string, map(xs:string, item()?)?) as empty-sequence())?,
        $test-error-function as (function(xs:string, map(xs:string, item()?)?) as empty-sequence())?,
        $test-finished-function as (function(xs:string) as empty-sequence())?) {
    let $copy := util:expand($set)
    let $null := t:setup($copy/setup)
    let $tests :=
        if ($id) then
            $copy/test[@id = $id]
        else
            for $test in $copy/test
            return
                if($test[empty(@ignore) or @ignore = "no"])then
                    $test
                else
                    let $_notify-ignored := if(not(empty($test-ignored-function))) then $test-ignored-function($test/task) else ()
                    return ()
    let $result := util:expand(
           <TestSet>
           {$copy/testName}
           {$copy/description}
           {
               for $test at $p in $tests
               return
                   t:run-test($test, $p,
                        $test-ignored-function, $test-started-function, $test-failure-function,
                        $test-assumption-failed-function, $test-error-function, $test-finished-function)
           }
           </TestSet>
        )
    let $null := t:tearDown($copy/tearDown)
    return $result
};

declare function t:format-testResult($result) {
    let $formatted :=

{$result/testName}

{$result/description/p}

          {for $test in $result//test
           return

                    {$test/@n/.}
                    {$test/@pass/.}

          }

     return
        $formatted
};

xquery version "3.1";

module namespace t = "http://exist-db.org/xquery/testing";

import module namespace system = "http://exist-db.org/xquery/system";
import module namespace util = "http://exist-db.org/xquery/util";
import module namespace xmldb = "http://exist-db.org/xquery/xmldb";
import module namespace xdiff = "http://exist-db.org/xquery/xmldiff"
        at "java:org.exist.xquery.modules.xmldiff.XmlDiffModule";

declare namespace err = "http://www.w3.org/2005/xqt-errors";
declare namespace exerr = "http://www.exist-db.org/xqt-errors/";

declare function t:setup-action($action) {
    typeswitch ($action)
        case element(create-collection) return
            xmldb:create-collection($action/@parent, $action/@name)
        case element(store) return
			t:store($action)
		case element(store-files) return
            t:store-files($action)
        case element(remove-collection) return
            xmldb:remove($action/@collection)
        case element(remove-document) return
            xmldb:remove($action/@collection, $action/@name)
        default return
            ()
};

declare function t:store($action as element(store)) {
    let $type := if ($action/@type) then $action/@type/string() else "application/xml"
    let $data :=
		if ($action/*) then
			$action/*[1]
		else
			$action/string()
	return
        xmldb:store($action/@collection, $action/@name, $data, $type)
};

declare function t:store-files($action as element(store-files)) {
    let $type := if ($action/@type) then $action/@type/string() else "application/xml"
    return
        xmldb:store-files-from-pattern($action/@collection, $action/@dir, $action/@pattern, $type)
};

declare function t:setup($setup as element(setup)?) {
    for $action in $setup/*
    return
        t:setup-action($action)
};

declare function t:tearDown($tearDown as element(tearDown)?) {
    for $action in $tearDown/*
    return
        t:setup-action($action)
};

declare function t:declare-variable($var as element(variable)) as item()? {
    let $children := $var/*
    return
        if (empty($children)) then
            string-join($var/node(), '')
        else
            fn:serialize($children)
};

declare function t:init-prolog($test as element(test)) {
	let $imports := $test/../imports
	let $vars :=
	    string-join(
        	for $var in $test/../variable
        	return
        	    concat("declare variable $", $var/@name, " := ", t:declare-variable($var), ";"),
            ""
        )
	return
		string-join(($imports, $vars, $test/../functions), '')
};

declare function t:test($result as item()*) {
    if ($result instance of xs:boolean) then
        $result
    else
        exists($result)
};

declare function t:run-test($test as element(test), $count as xs:integer) {
    t:run-test($test, $count, (), (), (), (), (), ())
};

declare function t:run-test($test as element(test), $count as xs:integer,
        $test-ignored-function as (function(xs:string) as empty-sequence())?,
        $test-started-function as (function(xs:string) as empty-sequence())?,
        $test-failure-function as (function(xs:string, map(xs:string, item()?), map(xs:string, item()?)) as empty-sequence())?,
        $test-assumption-failed-function as (function(xs:string, map(xs:string, item()?)?) as empty-sequence())?,
        $test-error-function as (function(xs:string, map(xs:string, item()?)?) as empty-sequence())?,
        $test-finished-function as (function(xs:string) as empty-sequence())?) {
	let $context := t:init-prolog($test)
	let $null :=
	   if ($test/@trace eq 'yes') then
	       (system:clear-trace(), system:enable-tracing(true(), false()))
       else ()
    let $highlight-option := concat("highlight-matches=",
          if ($test/expected//@*[matches(., '^(\|{3}).*\1$')] and $test/expected//exist:match) then "both"
          else if ($test/expected//@*[matches(., '^(\|{3}).*\1$')]) then "attributes"
          else if ($test/expected//exist:match) then "elements"
          else "none"
          )
    let $serialize-options :=
      let $decls := ($test/../*[name() ne 'test']|$test/code)[matches(., 'declare[\- ]option(\(("|')|\s+)exist:serialize(\2,)?\s+("|').*?\4[;)]')]
      let $ops1 := $decls/replace(., "declare[\- ]option(\(("|')|\s+)exist:serialize(\2,)?\s+("|')(.*?)\4[;)]", "_|$5_")
      let $ops2 :=
        for $a in $ops1
        for $b in tokenize($a, '_')[starts-with(., '|')]
        return tokenize(substring-after($b, '|'), '\s+')
      return if (count($ops2[matches(., 'highlight-matches')]))
        then string-join($ops2, ' ')
        else string-join(($ops2, $highlight-option), ' ')
    let $_notify-start := if(not(empty($test-started-function))) then $test-started-function($test/task) else ()
    let $queryOutput :=
        try {
            map {
                "result": util:eval(concat($context, $test/code/string()), false())
            }
        } catch * {
            map {
                "error": map {
                    "code": $err:code,
                    "description": $err:description,
                    "value": $err:value,
                    "module": $err:module,
                    "line-number": $err:line-number,
                    "column-number": $err:column-number,
                    "additional": $err:additional,
                    "xquery-stack-trace": $exerr:xquery-stack-trace,
                    "java-stack-trace": $exerr:java-stack-trace
                }
            }
        }
	let $output := if ($test/@trace eq 'yes') then map { "result": system:trace() } else $queryOutput
    let $expanded :=
        if (not(empty($output("error")))) then
            $output
        else if ($test/@serialize) then
            try {

                let $ser-params := map:merge(
                    let $options := fn:tokenize($test/@serialize/string(), " ")
                    for $option in $options
                    let $kv := fn:tokenize($option, "=")
                    return
                        map:entry($kv[1], $kv[2])
                )
                return
                    map {
                        "result": normalize-space(string-join($output("result") ! fn:serialize(., $ser-params)))
                    }
            } catch * {
                map {
                    "error": map {
                        "code": $err:code,
                        "description": $err:description,
                        "value": $err:value,
                        "module": $err:module,
                        "line-number": $err:line-number,
                        "column-number": $err:column-number,
                        "additional": $err:additional,
                        "xquery-stack-trace": $exerr:xquery-stack-trace,
                        "java-stack-trace": $exerr:java-stack-trace
                    }
                }
            }

		else if ($output("result") instance of node()) then
        	map {
        	    "result": util:expand($output("result"), $serialize-options)
        	}
		else
			$output
    let $expected :=
        if ($test/@output eq 'text') then
            data($test/expected)
        else $test/expected/node()
    let $OK :=
        if ($test/error) then
            not(empty($expanded("error"))) and (contains($expanded("error")("description"), $test/error) or local-name-from-QName($expanded("error")("code")) eq string($test/error))
        else if ($test/xpath) then
            t:test(t:xpath($output("result"), $test/xpath))
        else if ($test/@output eq 'text') then
            let $asString :=
                if ($test/@serialize) then
                    $expanded("result")
                else
                    normalize-space(string-join(for $x in $output("result") return string($x),' '))
            return
                $asString eq normalize-space($expected)
        else
            let $xn := t:normalize($expanded("result"))
            let $en := t:normalize($expected)
            return
        	   deep-equal($xn, $en)
    return
        (
            <test n="{$count}" pass="{$OK}">
            { $test/@id }
            {
                if (not($OK)) then
                    let $_notify :=
                        if(not(empty($expanded("error")))) then
                            if(not(empty($test-error-function))) then $test-error-function($test/task, $expanded("error")) else ()
                        else
                            if(not(empty($test-failure-function))) then $test-failure-function($test/task, map { "value": $test/expected/node(), "xpath": $test/xpath/node(), "error": $test/error/node() }, $expanded) else ()
                    return
                        ($test/task, $test/expected, $test/xpath, <result>{$expanded("result"), if(not(empty($expanded("error")))) then $expanded("error")("description") else ()}</result>)
                else
                    ()
            }
            </test>
            ,
            let $_notify-finished := if(not(empty($test-finished-function))) then $test-finished-function($test/task) else ()
            return ()
        )
};

declare function t:normalize($nodes as node()*) {
	for $node in $nodes return t:normalize-node($node)
};

declare function t:normalize-node($node as node()) {
	typeswitch ($node)
		case element() return
			element { node-name($node) } {
				$node/@*, for $child in $node/node() return t:normalize-node($child)
			}
		case text() return
			let $norm := normalize-space($node)
			return
				if (string-length($norm) eq 0) then () else $node
		default return
			$node
};

declare function t:xpath($output as item()*, $xpath as element()) {
    for $prefix in in-scope-prefixes($xpath)
    let $nsuri := namespace-uri-for-prefix($prefix, $xpath)
    return
        if ($prefix != 'xml') then
            util:declare-namespace($prefix, $nsuri)
        else (),
    let $expr := $xpath/string()
    return
        if (matches($expr, "^\s*/")) then
            util:eval(concat("$output", $expr))
        else
            util:eval($expr)
};

declare function t:run-testSet($set as element(TestSet), $id as xs:string?) {
    t:run-testSet($set, $id, (), (), (), (), (), ())
};

declare function t:run-testSet($set as element(TestSet), $id as xs:string?,
        $test-ignored-function as (function(xs:string) as empty-sequence())?,
        $test-started-function as (function(xs:string) as empty-sequence())?,
        $test-failure-function as (function(xs:string, map(xs:string, item()?), map(xs:string, item()?)) as empty-sequence())?,
        $test-assumption-failed-function as (function(xs:string, map(xs:string, item()?)?) as empty-sequence())?,
        $test-error-function as (function(xs:string, map(xs:string, item()?)?) as empty-sequence())?,
        $test-finished-function as (function(xs:string) as empty-sequence())?) {
    let $copy := util:expand($set)
    let $null := t:setup($copy/setup)
    let $tests :=
        if ($id) then
            $copy/test[@id = $id]
        else
            for $test in $copy/test
            return
                if($test[empty(@ignore) or @ignore = "no"])then
                    $test
                else
                    let $_notify-ignored := if(not(empty($test-ignored-function))) then $test-ignored-function($test/task) else ()
                    return ()
    let $result := util:expand(
           <TestSet>
           {$copy/testName}
           {$copy/description}
           {
               for $test at $p in $tests
               return
                   t:run-test($test, $p,
                        $test-ignored-function, $test-started-function, $test-failure-function,
                        $test-assumption-failed-function, $test-error-function, $test-finished-function)
           }
           </TestSet>
        )
    let $null := t:tearDown($copy/tearDown)
    return $result
};

declare function t:format-testResult($result) {
    let $formatted :=

{$result/testName}

{$result/description/p}

          {for $test in $result//test
           return

                    {$test/@n/.}
                    {$test/@pass/.}

          }

     return
        $formatted
};


Added missing quote.

Added missing quote.

(:
 : Copyright 2006-2009 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
 :)

xquery version "3.0";

 (:~
  : Using this module, you can parse JSON data into XML, manipulate it like any
  : other XML data using XQuery, and serialize the result back as JSON.
  :
  : There are many ways to represent JSON data in XML, some loss-less ("round
  : tripable") and some lossy ("one way").  Loss-less representations preserve
  : the JSON data types boolean, number, and null; lossy
  : representations convert all data to strings.
  :
  : For a loss-less representation, Zorba implements that proposed by
  : John Snelson;
  : for a lossy representation, Zorba implements
  : JsonML (the array form).
  :)

module namespace json = "http://www.zorba-xquery.com/modules/converters/json";

import module namespace schema = "http://www.zorba-xquery.com/modules/schema";

import schema namespace json-options =
  "http://www.zorba-xquery.com/modules/converters/json-options";

declare namespace err = "http://www.w3.org/2005/xqt-errors";

declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

(:~
 : Parses JSON data from a string and returns an XDM instance using one of the
 : representations described above.
 :
 : @param $json The JSON data to parse.
 : @param $options The parsing options.
 : @return said XDM instance.
 : @error err:XQDY0027 if \a $options can not be validated against the \c
 : json-options schema.
 : @error ZJPE0001 if \a $json contains an illegal JSON character.
 : @error ZJPE0002 if \a $json contains an illegal Unicode code-point.
 : @error ZJPE0003 if \a $json contains an illegal JSON character escape.
 : @error ZJPE0004 if \a $json contains an illegal JSON literal.
 : @error ZJPE0005 if \a $json contains an illegal JSON number.
 : @error ZJPE0007 if \a $json contains an unterminated string.
 : @error ZJPE0008 if \a $json contains an illegal QName.
 :)
declare function json:parse(
  $json as xs:string?,
  $options as element(json-options:options)
) as element(*,xs:untyped)*
{
  let $validated-options := if ( schema:is-validated( $options ) ) then
                              $options
                            else
                              validate { $options }
  return json:parse-internal( $json, $validated-options )
};

(:~
 : Parses JSON data from a string and returns an XDM instance using the Snelson
 : representation described above.
 :
 : @param $json The JSON data to parse.
 : @return said XDM instance.
 : @error ZJPE0001 if \a $json contains an illegal JSON character.
 : @error ZJPE0002 if \a $json contains an illegal Unicode code-point.
 : @error ZJPE0003 if \a $json contains an illegal JSON character escape.
 : @error ZJPE0004 if \a $json contains an illegal JSON literal.
 : @error ZJPE0005 if \a $json contains an illegal JSON number.
 : @error ZJPE0007 if \a $json contains an unterminated string.
 : @error ZJPE0008 if \a $json contains an illegal QName.
 :)
declare function json:parse(
  $json as xs:string?
) as element(*,xs:untyped)*
{
  json:parse-internal(
    $json,
    validate {
      <options xmlns="http://www.zorba-xquery.com/modules/converters/json-options" >
        <json-format value="Snelson"/>
      </options>
    }
  )
};

(:~
 : Serializes an XDM into JSON using one of the representations described
 : above.
 :
 : @param $xml The XDM to serialize.
 : @param $options The serializing options.
 : @return a JSON string.
 : @error err:XQDY0027 if $options can not be validated against the \c
 : json-options schema.
 : @error ZJSE0001 if \a $xml is not a document or element node.
 : @error ZJSE0002 if \a $xml contains an element that is missing a required
 : attribute.
 : @error ZJSE0003 if \a $xml contains an attribute having an illegal value.
 : @error ZJSE0004 if \a $xml contains an illegal element.
 : @error ZJSE0005 if \a $xml contains an illegal child element for a JSON type.
 : @error ZJSE0006 if \a $xml contains an illegal child element.
 : @error ZJSE0007 if \a $xml contains an illegal text node.
 : @error ZJSE0008 if \a $xml contains an illegal value for a JSON type.
 :)
declare function json:serialize(
  $xml as item()*,
  $options as element(json-options:options)
) as xs:string
{
  let $validated-options := if ( schema:is-validated( $options ) ) then
                              $options
                            else
                              validate { $options }
  return json:serialize-internal( $xml, $validated-options )
};

(:~
 : Serializes an XDM into JSON using one of the representations described
 : above.
 :
 : @param $xml The XDM to serialize.
 : @return a JSON string.
 : @error ZJSE0001 if \a $xml is not a document or element node.
 : @error ZJSE0002 if \a $xml contains an element that is missing a required
 : attribute.
 : @error ZJSE0003 if \a $xml contains an attribute having an illegal value.
 : @error ZJSE0004 if \a $xml contains an illegal element.
 : @error ZJSE0005 if \a $xml contains an illegal child element for a JSON type.
 : @error ZJSE0006 if \a $xml contains an illegal child element.
 : @error ZJSE0007 if \a $xml contains an illegal text node.
 : @error ZJSE0008 if \a $xml contains an illegal value for a JSON type.
 :)
declare function json:serialize(
  $xml as item()*
) as xs:string
{
  json:serialize-internal($xml,
    validate {
      <options xmlns="http://www.zorba-xquery.com/modules/converters/json-options" >
        <json-format value="Snelson"/>
      </options>
   }
 )
};

(:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::)

declare %private function json:parse-internal(
  $json as xs:string,
  $options as item()?
) as element()* external;

declare %private function json:serialize-internal(
  $xml as item()*,
  $options as item()?
) as xs:string external;

(: vim:set et sw=2 ts=2: :)


local services cache -- better searching for entities

(:~
: This is the Care Services Discovery stored query registry
: @version 1.0
: @see https://github.com/openhie/openinfoman
:
:)
module namespace csd_lsc = "https://github.com/openhie/openinfoman/csd_lsc";
import module namespace csd_psd = "https://github.com/openhie/openinfoman/csd_psd";
import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm";
declare namespace csd = "urn:ihe:iti:csd:2013";
declare default element  namespace   "urn:ihe:iti:csd:2013";

declare function csd_lsc:get_document_name($name) {
  concat("local_services_cache/",$name,".xml")
};

declare function csd_lsc:directory_exists($db,$name) {
  db:is-xml($db,csd_lsc:get_document_name($name))
};

declare updating function csd_lsc:empty_cache($db,$name)
{
  (
    csd_dm:empty($db,$name)
    ,csd_lsc:set_service_directory_mtime($db,$name,$csd_lsc:beginning_of_time)
   )

};

declare function csd_lsc:get_cache($db,$name)
{
 if (csd_lsc:directory_exists($db,$name)) then
    db:open($db,csd_lsc:get_document_name($name))
  else csd_dm:blank_directory()
};

declare variable $csd_lsc:beginning_of_time := '2013-10-01T00:00:00+00:00';
declare variable $csd_lsc:cache_meta_doc := 'local_cache_meta.xml';

declare function csd_lsc:get_cache_data($db,$name)
{
  if ( not( db:is-xml($db,$csd_lsc:cache_meta_doc)))  then
    if ($name) then
      ()
    else
      <cacheData/>
  else
    let $meta_doc :=  db:open($db,$csd_lsc:cache_meta_doc)
    return if ($name) then
      $meta_doc/cacheData/serviceCache[@name = $name]
    else
      $meta_doc/cacheData
};

declare updating function csd_lsc:drop_cache_data($db,$name)
{
  if ( not(db:is-xml($db,$csd_lsc:cache_meta_doc)))  then
    ()
  else
    let $meta_doc :=  db:open($db,$csd_lsc:cache_meta_doc)
      return if ($name) then
	delete node $meta_doc/cacheData/serviceCache[@name = $name]
      else
	delete node $meta_doc/cacheData/*

};

declare  function csd_lsc:cache_meta_exists($db) {
  db:is-xml($db,$csd_lsc:cache_meta_doc)
};

declare updating function csd_lsc:init_cache_meta($db) {
  if ( not(csd_lsc:cache_meta_exists($db)))  then
    db:add($db, <cacheData/>,$csd_lsc:cache_meta_doc)
  else
      ()
};

declare updating function csd_lsc:set_service_directory_mtime($db,$name,$mtime)
{
  ( csd_lsc:init_cache_meta($db)
  ,
  let $meta :=  db:open($db,$csd_lsc:cache_meta_doc)/cacheData
  return
    if (not(exists($meta/serviceCache[@name = $name])))  then
      insert node <serviceCache name="{$name}" mtime="{$csd_lsc:beginning_of_time}"/> into $meta
    else if (not(exists($meta/serviceCache[@name = $name]/@mtime))) then
      let $attr := attribute {"mtime"}{ $csd_lsc:beginning_of_time}
      return ( insert  node $attr into $meta/serviceCache[@name = $name])
    else
      replace value of node $meta/serviceCache[@name = $name]/@mtime with $mtime
  )

};

declare function csd_lsc:get_service_directory_mtime($db,$name)
{
  if ( db:is-xml($db,$csd_lsc:cache_meta_doc))  then
    let $mtime := text{db:open($db,$csd_lsc:cache_meta_doc)/cacheData/serviceCache[@name = $name]/@mtime}
    return if ($mtime) then
	$mtime
      else
	$csd_lsc:beginning_of_time
    else
      $csd_lsc:beginning_of_time

};

declare  updating  function csd_lsc:update_cache($db,$name)
{

  let $mtime :=  csd_lsc:get_service_directory_mtime($db,$name)
(:  querying against self during an update will cause a deadlock :)
(:  see or :)
(:  http://www.mail-archive.com/basex-talk@mailman.uni-konstanz.de/msg02999.htmlhttp://www.mail-archive.com/basex-talk@mailman.uni-konstanz.de/msg02999.html  :)
(:  possible work-around: http://docs.basex.org/wiki/Server_Protocol ? :)
  let $result := csd_psd:poll_service_directory($db,$name,$mtime)
  let $cache_doc := csd_lsc:get_cache($db,$name)

  let $currtime := current-dateTime()
  return (
    csd_lsc:refresh_doc($cache_doc,$result)
    ,csd_lsc:set_service_directory_mtime($db,$name,$currtime)

  )

};

declare updating function csd_lsc:refresh_doc($cache_doc,$updates)
{
  (
   csd_lsc:update_directory($cache_doc/csd:CSD/csd:organizationDirectory,$updates/(csd:CSD/csd:organizationDirectory|csd:organizationDirectory))
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:facilityDirectory,$updates/(csd:CSD/csd:facilityDirectory|csd:facilityDirectory))
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:serviceDirectory,$updates/(csd:CSD/csd:serviceDirectory|csd:serviceDirectory))
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:providerDirectory,$updates/(csd:CSD/csd:providerDirectory|csd:providerDirectory))
  )
};

declare updating function csd_lsc:update_directory($oldDir,$newDir)
{
  for $new in  $newDir/*
  let $old := $oldDir/*[@entityID = $new/@entityID]
  return if ($old) then
    replace node $old with $new
  else
    insert node $new into $oldDir
};

(:~
: This is the Care Services Discovery stored query registry
: @version 1.0
: @see https://github.com/openhie/openinfoman
:
:)
module namespace csd_lsc = "https://github.com/openhie/openinfoman/csd_lsc";
import module namespace csd_psd = "https://github.com/openhie/openinfoman/csd_psd";
import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm";
declare namespace csd = "urn:ihe:iti:csd:2013";
declare default element  namespace   "urn:ihe:iti:csd:2013";

declare function csd_lsc:get_document_name($name) {
  concat("local_services_cache/",$name,".xml")
};

declare function csd_lsc:directory_exists($db,$name) {
  db:is-xml($db,csd_lsc:get_document_name($name))
};

declare updating function csd_lsc:empty_cache($db,$name)
{
  (
    csd_dm:empty($db,$name)
    ,csd_lsc:set_service_directory_mtime($db,$name,$csd_lsc:beginning_of_time)
   )

};

declare function csd_lsc:get_cache($db,$name)
{
 if (csd_lsc:directory_exists($db,$name)) then
    db:open($db,csd_lsc:get_document_name($name))
  else csd_dm:blank_directory()
};

declare variable $csd_lsc:beginning_of_time := '2013-10-01T00:00:00+00:00';
declare variable $csd_lsc:cache_meta_doc := 'local_cache_meta.xml';

declare function csd_lsc:get_cache_data($db,$name)
{
  if ( not( db:is-xml($db,$csd_lsc:cache_meta_doc)))  then
    if ($name) then
      ()
    else
      <cacheData/>
  else
    let $meta_doc :=  db:open($db,$csd_lsc:cache_meta_doc)
    return if ($name) then
      $meta_doc/cacheData/serviceCache[@name = $name]
    else
      $meta_doc/cacheData
};

declare updating function csd_lsc:drop_cache_data($db,$name)
{
  if ( not(db:is-xml($db,$csd_lsc:cache_meta_doc)))  then
    ()
  else
    let $meta_doc :=  db:open($db,$csd_lsc:cache_meta_doc)
      return if ($name) then
	delete node $meta_doc/cacheData/serviceCache[@name = $name]
      else
	delete node $meta_doc/cacheData/*

};

declare  function csd_lsc:cache_meta_exists($db) {
  db:is-xml($db,$csd_lsc:cache_meta_doc)
};

declare updating function csd_lsc:init_cache_meta($db) {
  if ( not(csd_lsc:cache_meta_exists($db)))  then
    db:add($db, <cacheData/>,$csd_lsc:cache_meta_doc)
  else
      ()
};

declare updating function csd_lsc:set_service_directory_mtime($db,$name,$mtime)
{
  ( csd_lsc:init_cache_meta($db)
  ,
  let $meta :=  db:open($db,$csd_lsc:cache_meta_doc)/cacheData
  return
    if (not(exists($meta/serviceCache[@name = $name])))  then
      insert node <serviceCache name="{$name}" mtime="{$csd_lsc:beginning_of_time}"/> into $meta
    else if (not(exists($meta/serviceCache[@name = $name]/@mtime))) then
      let $attr := attribute {"mtime"}{ $csd_lsc:beginning_of_time}
      return ( insert  node $attr into $meta/serviceCache[@name = $name])
    else
      replace value of node $meta/serviceCache[@name = $name]/@mtime with $mtime
  )

};

declare function csd_lsc:get_service_directory_mtime($db,$name)
{
  if ( db:is-xml($db,$csd_lsc:cache_meta_doc))  then
    let $mtime := text{db:open($db,$csd_lsc:cache_meta_doc)/cacheData/serviceCache[@name = $name]/@mtime}
    return if ($mtime) then
	$mtime
      else
	$csd_lsc:beginning_of_time
    else
      $csd_lsc:beginning_of_time

};

declare  updating  function csd_lsc:update_cache($db,$name)
{

  let $mtime :=  csd_lsc:get_service_directory_mtime($db,$name)
(:  querying against self during an update will cause a deadlock :)
(:  see or :)
(:  http://www.mail-archive.com/basex-talk@mailman.uni-konstanz.de/msg02999.htmlhttp://www.mail-archive.com/basex-talk@mailman.uni-konstanz.de/msg02999.html  :)
(:  possible work-around: http://docs.basex.org/wiki/Server_Protocol ? :)
  let $result := csd_psd:poll_service_directory($db,$name,$mtime)
  let $cache_doc := csd_lsc:get_cache($db,$name)

  let $currtime := current-dateTime()
  return (
    csd_lsc:refresh_doc($cache_doc,$result)
    ,csd_lsc:set_service_directory_mtime($db,$name,$currtime)

  )

};

declare updating function csd_lsc:refresh_doc($cache_doc,$updates)
{
  (
   csd_lsc:update_directory($cache_doc/csd:CSD/csd:organizationDirectory,$updates/csd:CSD/csd:organizationDirectory)
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:facilityDirectory,$updates/csd:CSD/csd:facilityDirectory)
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:serviceDirectory,$updates/csd:CSD/csd:serviceDirectory)
  ,csd_lsc:update_directory($cache_doc/csd:CSD/csd:providerDirectory,$updates/csd:CSD/csd:providerDirectory)
  )
};

declare updating function csd_lsc:update_directory($oldDir,$newDir)
{
  for $new in  $newDir/*
  let $old := $oldDir/*[@entityID = $new/@entityID]
  return if ($old) then
    replace node $old with $new
  else
    insert node $new into $oldDir
};


Fix doc typo

Fix doc typo

xquery version "3.0";

(:~
 : Fold adapter for BaseX / RESTXQ
 :
 : Contains the lower-level stuff to interface with BaseX.
 : Also makes dispatches the request to the main routes in
 : routes.xqm fold:route.
 :
 : @version 0.1
 : @author Marc van Grootel
 : @see https://github.com/xokomola/fold
 :)
module namespace fold = 'http://xokomola.com/xquery/fold/adapter';

import module namespace request = 'http://exquery.org/ns/request';
import module namespace res = 'http://xokomola.com/xquery/fold/response'
    at '../../fold/response.xqm';
import module namespace service = 'http://xokomola.com/xquery/fold'
    at '../../routes.xqm';

declare variable $fold:time := false();
declare variable $fold:time-cache := false();

(:~
 : Route the request through the routes map in apps.
 :
 : Note that this calls the route function in the apps module.
 :
 : @param $request a request map.
 : @return the HTTP response object.
 :)

declare function fold:serve($request as map(*)) {
    fold:serve-response(service:routes()($request))
};

declare function fold:timed-serve($request as map(*)) {
    prof:mem(prof:time(fold:serve($request),
        $fold:time-cache, 'TOTAL TIME: '),
        $fold:time-cache, 'TOTAL MEM: ')
};

declare variable $fold:handler :=
    if ($fold:time) then
        fold:timed-serve#1
    else
        fold:serve#1;

(:~
 : Create the initial request map.
 :
 : @param $segments the segments of the path to be served.
 : @return a request map.
 :)
declare function fold:path($segments as xs:string*)
    as map(*) {
    fold:build-request-map(map { 'path': '/' || string-join($segments, '/')})
};

declare function fold:path($body, $segments as xs:string*)
    as map(*) {
    fold:build-request-map(map { 'body': $body, 'path': '/' || string-join($segments, '/')})
};

(:~
 : Create the initial request map from the HTTP request as provided by request
 : module.
 :
 : @param $params The initial request which at this point probably only contains
 : the 'path' path of the request
 : @return The fully initialized HTTP request map
 :)
declare function fold:build-request-map($params as map(*))
    as map(*) {
    map:new((
        (: TODO: also add content-type and content-length (lower-case) on root map :)
        map:entry('server-port', request:port()),
        map:entry('server-name', request:hostname()),
        map:entry('remote-addr', request:remote-hostname() || ':' || request:remote-port()),
        map:entry('uri', request:path()),
        map:entry('query-string', request:query()),
        map:entry('scheme', request:scheme()),
        map:entry('request-method', upper-case(request:method())),
        map:entry('context', request:context-path()),
        (: TODO: check if it is better to leave this one out when no value :)
        map:entry('ssl-client-cert', request:attribute('javax.servlet.request.X509Certificate')),
        (: Basex request module doesn't differentiate between query string, form params etc. :)
        (: We'll add them here so fold wrap:params can deal with them and included them in the params :)
        map:entry('basex-params',
            map:new((
                for $param in request:parameter-names()
                return
                    map:entry(fn:lower-case($param), request:parameter($param))
            ))
        ),
        map:entry('headers',
            map:new((
                for $header in request:header-names()
                return
                    map:entry(fn:lower-case($header), request:header($header))
            ))
        ),
        if ($params('body')) then
            map:entry('body', $params('body'))
        else
            ()
    ))
};

(:~
 : Renders the response map into the final HTTP response.
 :
 : It assumes that this is a valid response map containing
 : all needed to render a response.
 :
 : @see response.xqm render.xqm#1 for more
 :
 : @param $response the final response map.
 : @return the HTTP response object.
 :)
declare function fold:serve-response($response as map(*)) {
    (<rest:response>
        {
            let $content-type-header := res:get-header($response, 'Content-Type')
            where $content-type-header
            return
                <output:serialization-parameters>
                    <output:media-type value="{ tokenize(res:get-header($response, 'Content-Type'), ';')[1] }"/>
                </output:serialization-parameters>
        }
        <http:response status="{ $response('status') }">{
            for $header in map:keys($response('headers'))
            return
                <http:header name="{ $header }" value="{ res:get-header($response, $header) }"/>
        }</http:response>
    </rest:response>,
    $response('body'))
};

(:~
 : Map all REST requests to serve via a proxy function.
 : Note that this adapter only covers the standard HTTP methods except PATCH, TRACE and CONNECT
 : and also that POST and PUT are using a different function
 : because RESTXQ will process the request body for POST and PUT
 : and this should not happen with the other methods (even though
 : the HTTP specs allow it) and Roy says it's bad.
 :
 : I also rely on MIXUPDATES because I don't think db:output() is flexible
 : enough to handle all situations (I think).
 :
 : Having a %rest:ANY annotation would be helpful here.
 :
 : @param $s1 to $s10 The individual path segments as receveived from RESTXQ
 : @return The final HTTP response
 :
 : It's rather clumsy but maps paths to a reasonable depth.
 :)

declare %rest:path("/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy() { $fold:handler(fold:path(())) };

declare %rest:path("/{$s1}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1) { $fold:handler(fold:path(($s1))) };

declare %rest:path("/{$s1}/{$s2}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2) { $fold:handler(fold:path(($s1,  $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3) { $fold:handler(fold:path(($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4) { $fold:handler(fold:path(($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };

declare %rest:path("/{$s1}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1) { $fold:handler(fold:path(($s1))) };

declare %rest:path("/{$s1}/{$s2}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2) { $fold:handler(fold:path(($s1,  $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3) { $fold:handler(fold:path(($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4) { $fold:handler(fold:path(($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };

(: POST :)
declare %rest:path("/") %rest:POST("{$body}")
function fold:proxy_post($body) { $fold:handler(fold:path($body, ())) };

declare %rest:path("/{$s1}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1) { $fold:handler(fold:path($body, ($s1))) };

declare %rest:path("/{$s1}/{$s2}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2) { $fold:handler(fold:path($body, ($s1, $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3) { $fold:handler(fold:path($body, ($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };

declare %rest:path("/{$s1}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1) { $fold:handler(fold:path($body, ($s1))) };

declare %rest:path("/{$s1}/{$s2}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2) { $fold:handler(fold:path($body, ($s1, $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3) { $fold:handler(fold:path($body, ($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };

(: PUT :)
declare %rest:path("/") %rest:PUT("{$body}")
function fold:proxy_put($body) { $fold:handler(fold:path($body, ())) };

declare %rest:path("/{$s1}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1) { $fold:handler(fold:path($body, ($s1))) };

declare %rest:path("/{$s1}/{$s2}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2) { $fold:handler(fold:path($body, ($s1, $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3) { $fold:handler(fold:path($body, ($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };

declare %rest:path("/{$s1}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1) { $fold:handler(fold:path($body, ($s1))) };

declare %rest:path("/{$s1}/{$s2}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2) { $fold:handler(fold:path($body, ($s1, $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3) { $fold:handler(fold:path($body, ($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };

xquery version "3.0";

(:~
 : Fold adapter for BaseX / RESTXQ
 :
 : Contains the lower-level stuff to interface with BaseX.
 : Also makes dispatches the request to the main routes in
 : routes.xqm fold:route.
 :
 : @version 0.1
 : @author Marc van Grootel
 : @see https://github.com/xokomola/fold
 :)
module namespace fold = 'http://xokomola.com/xquery/fold/adapter';

import module namespace request = 'http://exquery.org/ns/request';
import module namespace res = 'http://xokomola.com/xquery/fold/response'
    at '../../fold/response.xqm';
import module namespace service = 'http://xokomola.com/xquery/fold'
    at '../../routes.xqm';

declare variable $fold:time := false();
declare variable $fold:time-cache := false();

(:~
 : Route the request through the routes map in apps.
 :
 : Note that this calls the route function in the apps module.
 :
 : @param $request a request map.
 : @return the HTTP response object.
 : @error Foo bar
 : @since 0.1
 : @see foobar
 : @deprecated
 :)

declare function fold:serve($request as map(*)) {
    fold:serve-response(service:routes()($request))
};

declare function fold:timed-serve($request as map(*)) {
    prof:mem(prof:time(fold:serve($request),
        $fold:time-cache, 'TOTAL TIME: '),
        $fold:time-cache, 'TOTAL MEM: ')
};

declare variable $fold:handler :=
    if ($fold:time) then
        fold:timed-serve#1
    else
        fold:serve#1;

(:~
 : Create the initial request map.
 :
 : @param $segments the segments of the path to be served.
 : @return a request map.
 :)
declare function fold:path($segments as xs:string*)
    as map(*) {
    fold:build-request-map(map { 'path': '/' || string-join($segments, '/')})
};

declare function fold:path($body, $segments as xs:string*)
    as map(*) {
    fold:build-request-map(map { 'body': $body, 'path': '/' || string-join($segments, '/')})
};

(:~
 : Create the initial request map from the HTTP request as provided by request
 : module.
 :
 : @param $params The initial request which at this point probably only contains
 : the 'path' path of the request
 : @return The fully initialized HTTP request map
 :)
declare function fold:build-request-map($params as map(*))
    as map(*) {
    map:new((
        (: TODO: also add content-type and content-length (lower-case) on root map :)
        map:entry('server-port', request:port()),
        map:entry('server-name', request:hostname()),
        map:entry('remote-addr', request:remote-hostname() || ':' || request:remote-port()),
        map:entry('uri', request:path()),
        map:entry('query-string', request:query()),
        map:entry('scheme', request:scheme()),
        map:entry('request-method', upper-case(request:method())),
        map:entry('context', request:context-path()),
        (: TODO: check if it is better to leave this one out when no value :)
        map:entry('ssl-client-cert', request:attribute('javax.servlet.request.X509Certificate')),
        (: Basex request module doesn't differentiate between query string, form params etc. :)
        (: We'll add them here so fold wrap:params can deal with them and included them in the params :)
        map:entry('basex-params',
            map:new((
                for $param in request:parameter-names()
                return
                    map:entry(fn:lower-case($param), request:parameter($param))
            ))
        ),
        map:entry('headers',
            map:new((
                for $header in request:header-names()
                return
                    map:entry(fn:lower-case($header), request:header($header))
            ))
        ),
        if ($params('body')) then
            map:entry('body', $params('body'))
        else
            ()
    ))
};

(:~
 : Renders the response map into the final HTTP response.
 :
 : It assumes that this is a valid response map containing
 : all needed to render a response.
 :
 : @see response.xqm render.xqm#1 for more
 :
 : @param $response the final response map.
 : @return the HTTP response object.
 :)
declare function fold:serve-response($response as map(*)) {
    (<rest:response>
        {
            let $content-type-header := res:get-header($response, 'Content-Type')
            where $content-type-header
            return
                <output:serialization-parameters>
                    <output:media-type value="{ tokenize(res:get-header($response, 'Content-Type'), ';')[1] }"/>
                </output:serialization-parameters>
        }
        <http:response status="{ $response('status') }">{
            for $header in map:keys($response('headers'))
            return
                <http:header name="{ $header }" value="{ res:get-header($response, $header) }"/>
        }</http:response>
    </rest:response>,
    $response('body'))
};

(:~
 : Map all REST requests to serve via a proxy function.
 : Note that this adapter only covers the standard HTTP methods except PATCH, TRACE and CONNECT
 : and also that POST and PUT are using a different function
 : because RESTXQ will process the request body for POST and PUT
 : and this should not happen with the other methods (even though
 : the HTTP specs allow it) and Roy says it's bad.
 :
 : I also rely on MIXUPDATES because I don't think db:output() is flexible
 : enough to handle all situations (I think).
 :
 : Having a %rest:ANY annotation would be helpful here.
 :
 : @param $s1 to $s10 The individual path segments as receveived from RESTXQ
 : @return The final HTTP response
 :
 : It's rather clumsy but maps paths to a reasonable depth.
 :)

declare %rest:path("/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy() { $fold:handler(fold:path(())) };

declare %rest:path("/{$s1}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1) { $fold:handler(fold:path(($s1))) };

declare %rest:path("/{$s1}/{$s2}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2) { $fold:handler(fold:path(($s1,  $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3) { $fold:handler(fold:path(($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4) { $fold:handler(fold:path(($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };

declare %rest:path("/{$s1}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1) { $fold:handler(fold:path(($s1))) };

declare %rest:path("/{$s1}/{$s2}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2) { $fold:handler(fold:path(($s1,  $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3) { $fold:handler(fold:path(($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4) { $fold:handler(fold:path(($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}/") %rest:GET %rest:DELETE %rest:HEAD %rest:OPTIONS
function fold:proxy_slash($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path(($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };

(: POST :)
declare %rest:path("/") %rest:POST("{$body}")
function fold:proxy_post($body) { $fold:handler(fold:path($body, ())) };

declare %rest:path("/{$s1}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1) { $fold:handler(fold:path($body, ($s1))) };

declare %rest:path("/{$s1}/{$s2}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2) { $fold:handler(fold:path($body, ($s1, $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3) { $fold:handler(fold:path($body, ($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}") %rest:POST("{$body}")
function fold:proxy_post($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };

declare %rest:path("/{$s1}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1) { $fold:handler(fold:path($body, ($s1))) };

declare %rest:path("/{$s1}/{$s2}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2) { $fold:handler(fold:path($body, ($s1, $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3) { $fold:handler(fold:path($body, ($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}/") %rest:POST("{$body}")
function fold:proxy_post_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };

(: PUT :)
declare %rest:path("/") %rest:PUT("{$body}")
function fold:proxy_put($body) { $fold:handler(fold:path($body, ())) };

declare %rest:path("/{$s1}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1) { $fold:handler(fold:path($body, ($s1))) };

declare %rest:path("/{$s1}/{$s2}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2) { $fold:handler(fold:path($body, ($s1, $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3) { $fold:handler(fold:path($body, ($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}") %rest:PUT("{$body}")
function fold:proxy_put($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };

declare %rest:path("/{$s1}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1) { $fold:handler(fold:path($body, ($s1))) };

declare %rest:path("/{$s1}/{$s2}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2) { $fold:handler(fold:path($body, ($s1, $s2))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3) { $fold:handler(fold:path($body, ($s1, $s2, $s3))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5, $s6) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9))) };

declare %rest:path("/{$s1}/{$s2}/{$s3}/{$s4}/{$s5}/{$s6}/{$s7}/{$s8}/{$s9}/{$s10}/") %rest:PUT("{$body}")
function fold:proxy_put_slash($body,$s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10) { $fold:handler(fold:path($body, ($s1, $s2, $s3, $s4, $s5, $s6, $s7, $s8, $s9, $s10))) };