Fixed init.jq

Fixed init.jq

import module namespace user = "http://apps.28.io/user";
import module namespace session = "http://apps.28.io/session";
import module namespace credentials = "http://apps.28.io/credentials";

if (is-available-collection($user:collection))
then ();
else create($user:collection);

if (is-available-collection($user:pictures))
then ();
else create($user:pictures);

if (is-available-collection($session:tokens))
then ();
else create($session:tokens);

if (is-available-collection($user:roles))
then truncate($user:roles);
else create($user:roles);

if (is-available-collection($user:rights))
then truncate($user:rights);
else create($user:rights);

if (is-available-collection("reportcache"))
then truncate("reportcache");
else create("reportcache");

if (is-available-collection("reportschemas"))
then ();
else create("reportschemas");

if (is-available-collection("reports"))
then ();
else create("reports");

if (is-available-collection("audit"))
then ();
else create("audit");

flush();

(: RIGHTS :)
user:new-right("rights", "Rights", (), 1);
user:new-right("settings", "Settings", "rights", 1);
user:new-right("users", "Users", "settings", 1);
user:new-right("roles", "Roles", "settings", 2);
user:new-right("reports", "Reports", "settings", 3);

(: RIGHTS: users :)
user:new-right("users_new", "Create New User", "users", 3);
user:new-right("users_list", "List All Users", "users", 2);
user:new-right("users_get", "View User", "users", 1);
user:new-right("users_get_self", "Get Profile", "users_get", 1);
user:new-right("users_edit", "Edit User", "users", 4);
user:new-right("users_edit_self", "Update Profile", "users_get_self", 1);
user:new-right("users_suspend", "Suspend User", "users_edit", 3);
user:new-right("users_activate", "Reactivate User", "users_edit", 2);
user:new-right("users_reset_password", "Reset Anybodys Password", "users", 4);
user:new-right("users_reset_password_self", "Reset Own Password", "users_get_self", 1);
user:new-right("users_upload_picture", "Upload User Picture", "users_edit", 4);
user:new-right("users_upload_picture_self", "Upload Profile Picture", "users_edit_self", 1);
user:new-right("users_remove_picture", "Remove User Picture", "users_edit", 4);
user:new-right("users_remove_picture_self", "Remove Profile Picture", "users_edit_self", 2);

(: RIGHTS: roles :)
user:new-right("users_assign_role","Assign Role to User","roles", 4);
user:new-right("users_close_assignment","Close Role Assignment of User","roles", 5);
user:new-right("roles_list","List All Roles and Rights","roles", 1);
user:new-right("roles_new","Create New Role","roles", 2);
user:new-right("roles_change_permissions","Change Permissions for Roles","roles", 3);

(: RIGHTS: reports :)
user:new-right("reports_get", "View Report", "reports", 1);
user:new-right("reports_validate", "Validate a Report", "reports", 2);
user:new-right("reports_edit", "Edit Report", "reports", 3);
user:new-right("reports_create", "Create a new Report", "reports_edit", 1);
user:new-right("reports_remove", "Remove Report", "reports_edit", 2);

(: RIGHTS: Data :)
user:new-right("data", "Data Access", (), 1);
user:new-right("data_sec", "SEC data access", "data", 1);
user:new-right("data_sec_dow30", "SEC data access to DOW30", "data_sec", 2);

(: ROLES :)
user:new-role("admin", "Administrator");
user:new-role("anybody", "All Users");
user:new-role("reports-admin", "Users Allowed to edit reports");
user:new-role("sec_pro", "Paid users (SEC Pro)");
user:new-role("sec_enterprise", "Paid users (SEC Enterprise)");

flush();

(: assign ROLES -> RIGHTS :)
user:allow("admin", "rights");
user:allow("admin", "data");
user:allow("anybody", "users_get_self");
user:allow("anybody", "users_reset_password_self");
user:allow("anybody", "users_upload_picture_self");
user:allow("anybody", "users_edit_self");
user:allow("anybody", "users_remove_picture_self");
user:allow("reports-admin", "reports");
user:allow("sec_pro", "data_sec");
user:allow("sec_pro", "reports");
user:allow("sec_enterprise", "data_sec");
user:allow("anybody", "data_sec_dow30");

(: Pro user :)
variable $user-id as string := user:get-by-email("support@28.io")._id;
if(empty($user-id))
then {
    $user-id := user:new("support@28.io", "System", "Administrator", "foobar", {| |});
    user:assign-role($user-id, "sec_pro", (), (), $user-id);
  }
else ();

(: support@28.io user token for tests :)
let $token :=
{
  "_id" : $credentials:support-token,
  "user-id" : $user-id,
  "expiration-date" : xs:dateTime("2018-10-20T22:17:23.851315Z")
}
let $existing := db:collection("Tokens")[$$."user-id" eq $user-id and $$."_id" eq $token."_id"]
return
  if(exists($existing))
  then ();
  else db:insert("Tokens", $token);

(: Disclosures Token :)
let $token := collection("Tokens")[$$."expiration-date" eq xs:dateTime("2016-09-12T22:17:23.851315Z")
and $$."user-id" eq $user-id]
return
if(empty($token))
then session:start($user-id, xs:dateTime("2016-09-12T22:17:23.851315Z"));
else ();

(: Admin user :)
variable $admin := user:get-by-email("admin@28.io");
if(empty($admin))
then {
    $admin := user:new("admin@28.io", "System", "Administrator", $credentials:admin-password, {| |});
    user:assign-role($admin, "admin", (), (), $admin);
  }
else ();

import module namespace user = "http://apps.28.io/user";
import module namespace session = "http://apps.28.io/session";
import module namespace credentials = "http://apps.28.io/credentials";

if (is-available-collection($user:collection))
then ();
else create($user:collection);

if (is-available-collection($user:pictures))
then ();
else create($user:pictures);

if (is-available-collection($session:tokens))
then ();
else create($session:tokens);

if (is-available-collection($user:roles))
then truncate($user:roles);
else create($user:roles);

if (is-available-collection($user:rights))
then truncate($user:rights);
else create($user:rights);

if (is-available-collection("reportcache"))
then truncate("reportcache");
else create("reportcache");

if (is-available-collection("reportschemas"))
then ();
else create("reportschemas");

if (is-available-collection("reports"))
then ();
else create("reports");

if (is-available-collection("audit"))
then ();
else create("audit");

flush();

(: RIGHTS :)
user:new-right("rights", "Rights", (), 1);
user:new-right("settings", "Settings", "rights", 1);
user:new-right("users", "Users", "settings", 1);
user:new-right("roles", "Roles", "settings", 2);
user:new-right("reports", "Reports", "settings", 3);

(: RIGHTS: users :)
user:new-right("users_new", "Create New User", "users", 3);
user:new-right("users_list", "List All Users", "users", 2);
user:new-right("users_get", "View User", "users", 1);
user:new-right("users_get_self", "Get Profile", "users_get", 1);
user:new-right("users_edit", "Edit User", "users", 4);
user:new-right("users_edit_self", "Update Profile", "users_get_self", 1);
user:new-right("users_suspend", "Suspend User", "users_edit", 3);
user:new-right("users_activate", "Reactivate User", "users_edit", 2);
user:new-right("users_reset_password", "Reset Anybodys Password", "users", 4);
user:new-right("users_reset_password_self", "Reset Own Password", "users_get_self", 1);
user:new-right("users_upload_picture", "Upload User Picture", "users_edit", 4);
user:new-right("users_upload_picture_self", "Upload Profile Picture", "users_edit_self", 1);
user:new-right("users_remove_picture", "Remove User Picture", "users_edit", 4);
user:new-right("users_remove_picture_self", "Remove Profile Picture", "users_edit_self", 2);

(: RIGHTS: roles :)
user:new-right("users_assign_role","Assign Role to User","roles", 4);
user:new-right("users_close_assignment","Close Role Assignment of User","roles", 5);
user:new-right("roles_list","List All Roles and Rights","roles", 1);
user:new-right("roles_new","Create New Role","roles", 2);
user:new-right("roles_change_permissions","Change Permissions for Roles","roles", 3);

(: RIGHTS: reports :)
user:new-right("reports_get", "View Report", "reports", 1);
user:new-right("reports_validate", "Validate a Report", "reports", 2);
user:new-right("reports_edit", "Edit Report", "reports", 3);
user:new-right("reports_create", "Create a new Report", "reports_edit", 1);
user:new-right("reports_remove", "Remove Report", "reports_edit", 2);

(: RIGHTS: Data :)
user:new-right("data", "Data Access", (), 1);
user:new-right("data_sec", "SEC data access", "data", 1);
user:new-right("data_sec_dow30", "SEC data access to DOW30", "data_sec", 2);

(: ROLES :)
user:new-role("admin", "Administrator");
user:new-role("anybody", "All Users");
user:new-role("reports-admin", "Users Allowed to edit reports");
user:new-role("sec_pro", "Paid users (SEC Pro)");
user:new-role("sec_enterprise", "Paid users (SEC Enterprise)");

flush();

(: assign ROLES -> RIGHTS :)
user:allow("admin", "rights");
user:allow("admin", "data");
user:allow("anybody", "users_get_self");
user:allow("anybody", "users_reset_password_self");
user:allow("anybody", "users_upload_picture_self");
user:allow("anybody", "users_edit_self");
user:allow("anybody", "users_remove_picture_self");
user:allow("reports-admin", "reports");
user:allow("sec_pro", "data_sec");
user:allow("sec_pro", "reports");
user:allow("sec_enterprise", "data_sec");
user:allow("anybody", "data_sec_dow30");

(: Pro user :)
variable $user := user:get-by-email("support@28.io");
if(empty($user))
then {
    $user := user:new("support@28.io", "System", "Administrator", "foobar", {| |});
    user:assign-role($user, "sec_pro", (), (), $user);
  }
else ();

(: support@28.io user token for tests :)
let $token :=
{
  "_id" : $credentials:support-token,
  "user-id" : $user."_id",
  "expiration-date" : xs:dateTime("2018-10-20T22:17:23.851315Z")
}
let $existing := db:collection("Tokens")[$$."user-id" eq $user."_id" and $$."_id" eq $token."_id"]
return
  if(exists($existing))
  then ();
  else db:insert("Tokens", $token);

(: Disclosures Token :)
let $token := collection("Tokens")[$$."expiration-date" eq xs:dateTime("2016-09-12T22:17:23.851315Z")
and $$."user-id" eq $user."_id"]
return
if(empty($token))
then session:start($user, xs:dateTime("2016-09-12T22:17:23.851315Z"));
else ();

(: Admin user :)
variable $admin := user:get-by-email("admin@28.io");
if(empty($admin))
then {
    $admin := user:new("admin@28.io", "System", "Administrator", $credentials:admin-password, {| |});
    user:assign-role($admin, "admin", (), (), $admin);
  }
else ();


Fixed typo.

Fixed typo.

import module namespace config = "http://apps.28.io/config";
import module namespace api = "http://apps.28.io/api";
import module namespace session = "http://apps.28.io/session";
import module namespace backend = "http://apps.28.io/test";

import module namespace http-request = "http://www.28msec.com/modules/http/request";
import module namespace csv = "http://zorba.io/modules/json-csv";

import module namespace entities = "http://28.io/modules/xbrl/entities";

import module namespace companies = "http://28.io/modules/xbrl/profiles/sec/companies";

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token        as string? external;
declare  %rest:env                              variable $request-uri  as string  external;
declare  %rest:case-insensitive                 variable $format       as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $eid          as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cik          as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag          as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker       as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic          as string* external;
declare  %rest:case-insensitive                 variable $profile-name as string  external := $config:profile-name;

session:audit-call($token);

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $tag as string* := api:preprocess-tags($tag)
let $tag := if (exists(($cik, $tag, $ticker, $sic)))
             then $tag
             else "ALL"

(: Object resolution :)
let $entities :=
    switch($profile-name)
    case "sec" return
        for $entity in companies:companies(
            $cik,
            $tag,
            $ticker,
            $sic)
        order by $entity.Profiles.SEC.CompanyName
        return $entity
    default return
        for $entity in
            if(exists($eid)) then entities:entities($eid)
                                else entities:entities()
        return {
            EID: $entity._id
        }
let $comment :=
{
    NumEntities: count($entities),
    TotalNumEntities: session:num-entities()
}
let $entities :=
  switch($profile-name)
  case "sec" return
    for $entity in $entities
    return {|
      project($entity, "_id"),
      {
        Archives: backend:url("filings", {
          cik: tokenize($entity._id, " ")[2],
          fiscalYear: "ALL",
          fiscalPeriod: "ALL",
          format: $format,
          profile-name: $profile-name
        }, true)
      },
      trim($entity, "_id")
    |}
  default return
    for $entity in $entities
    return {|
      $entity,
      {
        Archives: backend:url("filings", {
          eid: encode-for-uri($entity.EID),
          format: $format,
          profile-name: $profile-name
        }, true)
      }
  |}

let $result := { "Entities" : [ $entities ] }
let $serializers := {
    to-xml : function($res as object) as node() {
        switch($profile-name)
        case "sec" return
        <Entities>{
            companies:to-xml($res.Entities[])
        }</Entities>
        default return <Entities>
            {
                for $id in $res.Entities[].EID
                return <EID>{$id}</EID>
            }
        </Entities>
    },
    to-csv : function($res as object) as string {
        switch($profile-name)
        case "sec"
        return string-join(companies:to-csv($res.Entities[]))
        default return
            string-join(
                csv:serialize($res.Entities[], { serialize-null-as : "" }),
        "")
    }
}
return api:serialize($result, $comment, $serializers, $format, "entities")

import module namespace config = "http://apps.28.io/config";
import module namespace api = "http://apps.28.io/api";
import module namespace session = "http://apps.28.io/session";
import module namespace backend = "http://apps.28.io/test";

import module namespace http-request = "http://www.28msec.com/modules/http/request";
import module namespace csv = "http://zorba.io/modules/json-csv";

import module namespace entities = "http://28.io/modules/xbrl/entities";

import module namespace companies = "http://28.io/modules/xbrl/profiles/sec/companies";

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token        as string? external;
declare  %rest:env                              variable $request-uri  as string  external;
declare  %rest:case-insensitive                 variable $format       as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $eid          as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cik          as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag          as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker       as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic          as string* external;
declare  %rest:case-insensitive                 variable $profile-name as string  external := $config:profile-name;

session:audit-call($token);

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $tag as string* := api:preprocess-tags($tag)
let $tag := if (exists(($cik, $tag, $ticker, $sic)))
             then $tag
             else "ALL"

(: Object resolution :)
let $entities :=
    switch($profile-name)
    case "sec" return
        for $entity in companies:companies(
            $cik,
            $tag,
            $ticker,
            $sic)
        order by $entity.Profiles.SEC.CompanyName
        return $entity
    default return
        for $entity in
            if(exists($eid)) then entities:entities($eid)
                                else entities:entities()
        return {
            EID: $entity._id
        }
let $comment :=
{
    NumEntities: count($entities),
    TotalNumEntities: session:num-entities()
}
let $entities :=
  switch($profile-name)
  case "sec" return
    for $entity in $entities
    return {|
      project($entity, "_id"),
      {
        Archives: backend:url("filings", {
          cik: tokenize($entity._id, " ")[2],
          fiscalYear: "ALL",
          fiscalPeriod: "ALL",
          format: $format,
          profile-name: $profile-name
        }, true)
      },
      trim($entity, "_id"),
    |}
  default return
    for $entity in $entities
    return {|
      $entity,
      {
        Archives: backend:url("filings", {
          eid: encode-for-uri($entity.EID),
          format: $format,
          profile-name: $profile-name
        }, true)
      }
  |}

let $result := { "Entities" : [ $entities ] }
let $serializers := {
    to-xml : function($res as object) as node() {
        switch($profile-name)
        case "sec" return
        <Entities>{
            companies:to-xml($res.Entities[])
        }</Entities>
        default return <Entities>
            {
                for $id in $res.Entities[].EID
                return <EID>{$id}</EID>
            }
        </Entities>
    },
    to-csv : function($res as object) as string {
        switch($profile-name)
        case "sec"
        return string-join(companies:to-csv($res.Entities[]))
        default return
            string-join(
                csv:serialize($res.Entities[], { serialize-null-as : "" }),
        "")
    }
}
return api:serialize($result, $comment, $serializers, $format, "entities")


:green_heart: Fix typo.

:green_heart: Fix typo.

jsoniq version "1.0";
module namespace test = "http://apps.28.io/test";
import module namespace http-client = "http://zorba.io/modules/http-client";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace response = "http://www.28msec.com/modules/http-response";
import module namespace config = "http://apps.28.io/config";
import module namespace credentials = "http://www.28msec.com/modules/credentials";
import module namespace functx = "http://www.functx.com";
import module namespace backend = "http://apps.28.io/backend";

declare function test:is-dow30() as boolean{
  contains(credentials:credentials("MongoDB", "xbrl").db, "dow30")
};

declare function test:is-japan-big() as boolean{
  starts-with(credentials:credentials("MongoDB", "xbrl").db, "japanxbrl-big")
};

declare function test:url($endpoint as string, $parameters as object) as string
{
    test:url($endpoint, $parameters, false)
};

declare function test:url($endpoint as string, $parameters as object, $includeToken as boolean) as string
{
  backend:url(
    $endpoint,
    {|
      { token: if($includeToken) then $config:test-token else "{{token}}" },
      $parameters
    |}
  )
};

declare %an:nondeterministic function test:invoke($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:invoke-xml($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-xml($response.body.content))
};

declare %an:sequential function test:invoke-body($endpoint as string, $parameters as object, $body as string) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:post($url, $body, "application/x-www-form-urlencoded")
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:invoke-raw($endpoint as string, $parameters as object) as object
{
  let $url as string:= test:url($endpoint, $parameters, true)
  return http-client:get($url)
};

declare %an:nondeterministic function test:invoke-public($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:get-expected-result(
  $expected-file as string
) as item*
{
  parse-json(
    http-client:get("http://" || request:server-name() || ":" || request:server-port() ||
                    "/test/" || $expected-file).body.content)
};

declare %an:nondeterministic function test:get-expected-result-xml(
  $expected-file as string
) as item*
{
  parse-xml(
    http-client:get("http://" || request:server-name() || ":" || request:server-port() ||
                    "/test/" || $expected-file).body.content)
};

declare %an:nondeterministic function test:invoke-and-assert-deep-equal(
  $endpoint as string,
  $parameters as object,
  $transform as function(object) as item*,
  $expected as item*
) as item
{
  test:invoke-and-assert-deep-equal($endpoint, $parameters, $transform, $expected, {})
};

declare %an:sequential function test:invoke-and-assert-status(
  $endpoint as string,
  $parameters as object,
  $expected-status as integer
) as item
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:send-request({href: $url, method: "GET", "options" : { "status-only": true, "follow-redirect" : false } })
  let $actual-status := $response.status
  return if($actual-status eq $expected-status)
         then true
         else {
           url: test:url($endpoint, $parameters),
           expected: $expected-status,
           actual: $actual-status
         }
};

(:
  Note on NoArrayOrder option: it allows for false positives in case arrays have duplicate elements. The number
  of occurrences of duplicate elements is not checked.
:)
declare %an:nondeterministic function test:invoke-and-assert-deep-equal(
  $endpoint as string,
  $parameters as object,
  $transform as function(object) as item*,
  $expected as item*,
  $options as object?
) as item
{
  let $request := switch($options.Format)
                  case "xml" return test:invoke-xml($endpoint, $parameters)
                  default return test:invoke($endpoint, $parameters)
  let $status as integer := $request[1]
  let $actual as item* := $transform($request[2])
  let $expected as item* := if($options.TrimIdField) then test:trim-ids($expected) else $expected
  let $actual as item* := if($options.TrimIdField) then test:trim-ids($actual) else $actual
  return if($options.NoArrayOrder)
          then test:assert-deep-equal-no-array-order($expected, $actual, $status, test:url($endpoint, $parameters))
          else test:assert-deep-equal($expected, $actual, $status, test:url($endpoint, $parameters))
};

declare %an:sequential function test:check-all-success($o as object) as object
{
  if (not(every $k in (keys($o) ! $o.$$) satisfies ($k instance of boolean and $k)))
  then {
    response:status-code(500);
    $o
  } else $o
};

declare %private function test:trim-ids($input as item*) as item*
{
  for $i in $input
  return typeswitch($i)
          case object return {|
                                 for $key in keys($i)
                                 where $key ne "_id"
                                 return {$key: test:trim-ids($i.$key) }
                               |}
          case array return [ test:trim-ids($i[]) ]
          default return $i
};

(:    return
        if ($actual eq $expected)
        then true
        else "false [Actual="||$actual||", Expected="||$expected ||"]":)

declare function test:assert-eq(
    $expected as atomic,
    $actual as atomic?,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case try {$expected eq $actual} catch * { false } return true
    default return
    {
        "url": $url,
        "expected": $expected,
        "actual": $actual
    }
};

declare function test:assert-deep-equal(
    $expected as item*,
    $actual as item*,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case functx:sequence-deep-equal($expected, $actual) return true
    default return
    {
        "url": $url,
        "expected": typeswitch($expected) case json-item return $expected default return serialize($expected),
        "actual": typeswitch($actual) case json-item return $actual default return serialize($actual)
    }
};

declare function test:assert-deep-equal-no-array-order(
    $expected as item*,
    $actual as item*,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case test:deep-equal-no-array-order($expected, $actual) return true
    default return
    {
        "url": $url,
        "expected": typeswitch($expected) case json-item return $expected default return serialize($expected),
        "actual": typeswitch($actual) case json-item return $actual default return serialize($actual)
    }
};

declare function test:deep-equal-no-array-order(
    $expected as item*,
    $actual as item*) as boolean
{
    if(count($expected) ne count($actual))
    then false
    else
      every $i in 1 to count($expected)
      satisfies
        let $expected := $expected[$i]
        let $actual := $actual[$i]
        return
        typeswitch(($expected, $actual))
        case object* return
          switch(true)
          case not (every $key in keys($expected) satisfies keys($actual) = $key)
            return false
          case not (every $key in keys($actual) satisfies keys($expected) = $key)
            return false
          default return
            let $recursive-calls :=
                for $key in keys($expected)
                return test:deep-equal-no-array-order($expected.$key, $actual.$key)
            return if (every $item in $recursive-calls satisfies $item)
                    then true
                    else false
        case array* return
          if(not size($expected) eq size($actual))
          then false
          else
            let $expected-items as item* :=
              for $e in $expected[]
              let $s := serialize($e)
              order by $s
              return $e
            let $actual-items as item* :=
              for $a in $actual[]
              let $s := serialize($a)
              order by $s
              return $a
            let $size := size($expected)
            return every $i in 1 to $size
                   satisfies test:deep-equal-no-array-order(
                       $expected-items[$i],
                       $actual-items[$i])
        default return deep-equal($expected, $actual)
};

declare function test:assert-eq-array(
    $expected as array,
    $actual as array,
    $status as integer,
    $url as string) as item
{
    let $diff := try {
            {
                actual: [
                    for $a in flatten($actual)
                    where not($a = flatten($expected))
                    return $a ],
                actualDuplicates: [
                    for $a in flatten($actual)
                    where $a = flatten($expected) and count(flatten($actual)[$$ eq $a]) ne count(flatten($expected)[$$ eq $a])
                    return $a ],
                expected: [
                    for $e in flatten($expected)
                    where not($e = flatten($actual))
                    return $e ]
            }
        } catch * { "error: " || $err:description }

    return
        switch(true)
        case $status ne 200 return { "url": $url, status: $status }
        case empty(($diff.actual[],$diff.actualDuplicates[],$diff.expected[])) return true
        default return
        {
            "url": $url,
            "diff": $diff,
            "expected": $expected,
            "actual": $actual
        }
};

jsoniq version "1.0";
module namespace test = "http://apps.28.io/test";
import module namespace http-client = "http://zorba.io/modules/http-client";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace response = "http://www.28msec.com/modules/http-response";
import module namespace config = "http://apps.28.io/config";
import module namespace credentials = "http://www.28msec.com/modules/credentials";
import module namespace functx = "http://www.functx.com";
import module namespace backend = "http://apps.28.io/backend";

declare function test:is-dow30() as boolean{
  contains(credentials:credentials("MongoDB", "xbrl").db, "dow30")
};

declare function test:is-japan-big() as boolean{
  starts-with(credentials:credentials("MongoDB", "xbrl").db, "japanxbrl-big")
};

declare function test:url($endpoint as string, $parameters as object) as string
{
    test:url($endpoint, $parameters, false)
};

declare function test:url($endpoint as string, $parameters as object, $includeToken as boolean) as string
{
  backend:url(
    $endpoint,
    {|
      { token: if($includeToken) then $config:test-token else "{{token}}" },
      $parameters
    |}
  )
};

declare %an:nondeterministic function test:invoke($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:invoke-xml($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-xml($response.body.content))
};

declare %an:sequential function test:invoke-body($endpoint as string, $parameters as object, $body as string) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:post($url, $body, "application/x-www-form-urlencoded")
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:invoke-raw($endpoint as string, $parameters as object) as object
{
  let $url as string:= test:url($endpoint, $parameters, true)
  return http-client:get($url)
};

declare %an:nondeterministic function test:invoke-public($endpoint as string, $parameters as object) as item*
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:get-expected-result(
  $expected-file as string
) as item*
{
  parse-json(
    http-client:get("http://" || request:server-name() || ":" || request:server-port() ||
                    "/test/" || $expected-file).body.content)
};

declare %an:nondeterministic function test:get-expected-result-xml(
  $expected-file as string
) as item*
{
  parse-xml(
    http-client:get("http://" || request:server-name() || ":" || request:server-port() ||
                    "/test/" || $expected-file).body.content)
};

declare %an:nondeterministic function test:invoke-and-assert-deep-equal(
  $endpoint as string,
  $parameters as object,
  $transform as function(object) as item*,
  $expected as item*
) as item
{
  test:invoke-and-assert-deep-equal($endpoint, $parameters, $transform, $expected, {})
};

declare %an:sequential function test:invoke-and-assert-status(
  $endpoint as string,
  $parameters as object,
  $expected-status as integer
) as item
{
  let $url as string:= test:url($endpoint, $parameters, true)
  let $response as object := http-client:send-request({href: $url, method: "GET", "options" : { "status-only": true, "follow-redirect" : false } })
  let $actual-status := $response.status
  return if($actual-status eq $expected-status)
         then true
         else {
           url: test:url($endpoint, $parameters),
           expected: $expected-status,
           actual: $actual-status
         }
};

(:
  Note on NoArrayOrder option: it allows for false positives in case arrays have duplicate elements. The number
  of occurrences of duplicate elements is not checked.
:)
declare %an:nondeterministic function test:invoke-and-assert-deep-equal(
  $endpoint as string,
  $parameters as object,
  $transform as function(object) as item*,
  $expected as item*,
  $options as object?
) as item
{
  let $request := switch($options.Format)
                  case "xml" return test:invoke-xml($endpoint, $parameters)
                  default return test:invoke($endpoint, $parameters)
  let $status as integer := $request[1]
  let $actual as item* := $transform($request[2])
  let $expected as item* := if($options.TrimIdField) then test:trim-ids($expected) else $expected
  let $actual as item* := if($options.TrimIdField) then test:trim-ids($actual) else $actual
  return if($options.NoArrayOrder)
          then test:assert-deep-equal-no-array-order($expected, $actual, $status, test:url($endpoint, $parameters))
          else test:assert-deep-equal($expected, $actual, $status, test:url($endpoint, $parameters))
};

declare %an:sequential function test:check-all-success($o as object) as object
{
  if (not(every $k in (keys($o) ! $o.$$) satisfies ($k instance of boolean and $k)))
  then {
    response:status-code(500);
    $o
  } else $o
};

declare %private function test:trim-ids($input as item*) as item*
{
  for $i in $input
  return typeswitch($i)
          case object return {|
                                 for $key in keys($i)
                                 where $key ne "_id"
                                 return {$key: test:trim-ids($i.$key) }
                               |}
          case array return [ test:trim-ids($i[]) ]
          default return $i
};

(:    return
        if ($actual eq $expected)
        then true
        else "false [Actual="||$actual||", Expected="||$expected ||"]":)

declare function test:assert-eq(
    $expected as atomic,
    $actual as atomic?,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case try {$expected eq $actual} catch * { false } return true
    default return
    {
        "url": $url,
        "expected": $expected,
        "actual": $actual
    }
};

declare function test:assert-deep-equal(
    $expected as item*,
    $actual as item*,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case functx:sequence-deep-equal($expected, $actual) return true
    default return
    {
        "url": $url,
        "expected": typeswitch($expected) case json-item return $expected default return serialize($expected),
        "actual": typeswitch($actual) case json-item return $actual default return serialize($actual)
    }
};

declare function test:assert-deep-equal-no-array-order(
    $expected as item*,
    $actual as item*,
    $status as integer,
    $url as string) as item
{
    switch(true)
    case $status ne 200 return { "url": $url, status: $status }
    case test:deep-equal-no-array-order($expected, $actual) return true
    default return
    {
        "url": $url,
        "expected": typeswitch($expected) case json-item return $expected default return serialize($expected),
        "actual": typeswitch($actual) case json-item return $actual default return serialize($actual)
    }
};

declare function test:deep-equal-no-array-order(
    $expected as item*,
    $actual as item*) as boolean
{
    if(count($expected) ne count($actual))
    then false
    else
      every $i in 1 to count($expected)
      satisfies
        let $expected := $expected[$i]
        let $actual := $actual[$i]
        return
        typeswitch(($expected, $actual))
        case object* return
          switch(true)
          case not (every $key in keys($expected) satisfies keys($actual) = $key)
            return false
          case not (every $key in keys($actual) satisfies keys($expected) = $key)
            return false
          default return
            let $recursive-calls :=
                for $key in keys($expected)
                return test:deep-equal-no-array-order($expected.$key, $actual.$key)
            return if (every $item in $recursive-calls satisfies $item)
                    then true
                    else false
        case array* return
          if(not size($expected) eq size($actual))
          then false
          else
            let $expected-items as item* :=
              for $e in $expected[]
              let $s := serialize($e)
              order by $s
              return $e
            let $actual-items as item* :=
              for $a in $actual[]
              let $s := serialize($e)
              order by $s
              return $a
            let $size := size($expected)
            return every $i in 1 to $size
                   satisfies test:deep-equal-no-array-order(
                       $expected-items[$i],
                       $actual-items[$i])
        default return deep-equal($expected, $actual)
};

declare function test:assert-eq-array(
    $expected as array,
    $actual as array,
    $status as integer,
    $url as string) as item
{
    let $diff := try {
            {
                actual: [
                    for $a in flatten($actual)
                    where not($a = flatten($expected))
                    return $a ],
                actualDuplicates: [
                    for $a in flatten($actual)
                    where $a = flatten($expected) and count(flatten($actual)[$$ eq $a]) ne count(flatten($expected)[$$ eq $a])
                    return $a ],
                expected: [
                    for $e in flatten($expected)
                    where not($e = flatten($actual))
                    return $e ]
            }
        } catch * { "error: " || $err:description }

    return
        switch(true)
        case $status ne 200 return { "url": $url, status: $status }
        case empty(($diff.actual[],$diff.actualDuplicates[],$diff.expected[])) return true
        default return
        {
            "url": $url,
            "diff": $diff,
            "expected": $expected,
            "actual": $actual
        }
};


Fixed test module.

Fixed test module.

jsoniq version "1.0";
module namespace test = "http://apps.28.io/test";
import module namespace http-client = "http://zorba.io/modules/http-client";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace credentials = "http://apps.28.io/credentials";

declare %an:nondeterministic function test:invoke($endpoint as string, $parameters as object) as item*
{
  let $url as string:=
    "http://" || request:server-name() || ":" || request:server-port() ||
    "/v1/_queries/public/api/"||$endpoint||".jq?_method=POST&token="||$credentials:support-token||"&"||
    string-join(for $key in keys($parameters)
                for $value as string in flatten($parameters.$key)
                return ($key||"="||$value),
                "&")
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:invoke-public($endpoint as string, $parameters as object) as item*
{
  let $url as string:=
    "http://" || request:server-name() || ":" || request:server-port() ||
    "/v1/_queries/public/"||$endpoint||".jq?_method=POST&token="||$credentials:support-token||"&"||
    string-join(for $key in keys($parameters)
                for $value as string in flatten($parameters.$key)
                return ($key||"="||$value),
                "&")
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

(:    return
        if ($actual eq $expected)
        then true
        else "false [Actual="||$actual||", Expected="||$expected ||"]":)

declare function test:assert-eq(
    $expected as atomic,
    $actual as atomic?,
    $status as integer) as item
{
    switch(true)
    case $status ne 200 return { status: $status }
    case try {$expected eq $actual} catch * { false } return true
    default return
    {

        "expected": $expected,
        "actual": $actual
    }
};

declare function test:assert-deep-equal(
    $expected as json-item,
    $actual as json-item?,
    $status as integer) as item
{
    switch(true)
    case $status ne 200 return { status: $status }
    case deep-equal($expected, $actual) return true
    default return
    {

        "expected": $expected,
        "actual": $actual
    }
};

jsoniq version "1.0";
module namespace test = "http://apps.28.io/test";
import module namespace http-client = "http://zorba.io/modules/http-client";
import module namespace request = "http://www.28msec.com/modules/http-request";
import module namespace credentials = "http://apps.28.io/credentials";

declare %an:nondeterministic function test:invoke($endpoint as string, $parameters as object) as item*
{
  let $url as string:=
    "http://" || request:server-name() || ":" || request:server-port() ||
    "/v1/_queries/public/api/"||$endpoint||".jq?_method=POST&token="||$credentials:support-token||"&"||
    string-join(for $key in keys($parameters)
                for $value as string in flatten($parameters.$key)
                return ($key||"="||$value),
                "&")
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

declare %an:nondeterministic function test:invoke-public($endpoint as string, $parameters as object) as item*
{
  let $url as string:=
    "http://" || request:server-name() || ":" || request:server-port() ||
    "/v1/_queries/public/"||$endpoint||".jq?_method=POST&token="||$credentials:support-token||"&"||
    string-join(for $key in keys($parameters)
                for $value as string in flatten($parameters.$key)
                return ($key||"="||$value),
                "&")
  let $response as object := http-client:get($url)
  return ($response.status, parse-json($response.body.content))
};

(:    return
        if ($actual eq $expected)
        then true
        else "false [Actual="||$actual||", Expected="||$expected ||"]":)

declare function test:assert-eq(
    $expected as atomic,
    $actual as atomic?,
    $status as integer)
{
    switch(true)
    case $status ne 200 return { status: $status }
    case try {$expected eq $actual} catch * { false } return true
    default return
    {

        "expected": $expected,
        "actual": $actual
    }
};

declare function test:assert-deep-equal(
    $expected as json-item,
    $actual as json-item?,
    $status as integer)
{
    switch(true)
    case $status ne 200 return { status: $status }
    case deep-equal($expected, $actual) return true
    default return
    {

        "expected": $expected,
        "actual": $actual
    }
};


:lipstick: Refactored a bit.

:lipstick: Refactored a bit.

import module namespace config = "http://apps.28.io/config";
import module namespace api = "http://apps.28.io/api";
import module namespace session = "http://apps.28.io/session";
import module namespace backend = "http://apps.28.io/backend";

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace components = "http://28.io/modules/xbrl/components";

import module namespace sec-filings = "http://28.io/modules/xbrl/profiles/sec/filings";
import module namespace sec-networks = "http://28.io/modules/xbrl/profiles/sec/networks";
import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";

declare option rest:response "first-item";

declare function local:links($component as object)
{
  {
    FactTable: backend:url("facttable-for-component", {
                    token: $token,
                    aid: $component.Archive,
                    format: $format,
                    role: $component.Role,
                    profile-name: $profile-name
                    }),
    SpreadSheet: "http://rendering.secxbrl.info/#?url=" || encode-for-uri(
                backend:url("spreadsheet-for-component", {
                    token: $token,
                    aid: $component.Archive,
                    format: $format,
                    role: $component.Role,
                    profile-name: $profile-name,
                    eliminate: "true"
                })),
    ReportElements: backend:url("report-elements", {
                    token: $token,
                    aid: $component.Archive,
                    format: $format,
                    role: $component.Role,
                    profile-name: $profile-name
                }),
    ModelStructure: backend:url("modelstructure-for-component", {
                    token: $token,
                    aid: $component.Archive,
                    format: "json",
                    role: $component.Role,
                    profile-name: $profile-name
                })
  }
};

declare function local:statistics($component as object)
{
  {
    NumRules: size($component.Rules),
    NumNetworks: components:num-networks($component),
    NumReportElements: components:num-report-elements($component),
    NumHypercubes: components:num-hypercubes($component),
    NumDimensions: components:num-dimensions($component),
    NumMembers: components:num-members($component),
    NumLineItems: components:num-line-items($component),
    NumAbstracts: components:num-abstracts($component),
    NumConcepts: components:num-concepts($component),
    Hypercubes: [ keys($component.Hypercubes) ]
  }
};

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token              as string? external;
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $edinetcode   as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind         as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $fiscalYear as integer* := api:preprocess-fiscal-years($fiscalYear)
let $fiscalPeriod as string* := api:preprocess-fiscal-periods($fiscalPeriod)
let $tag as string* := api:preprocess-tags($tag)
let $reportElement := ($reportElement, $concept)
let $networkIdentifier := distinct-values(($networkIdentifier, $role))

let $cik as string* :=
    switch($profile-name)
    case "sec" return $cik
    case "japan" return $edinetcode
    default return ()

(: Entity resolution :)
let $entities := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic, ())

let $entities-not-found as boolean :=
  exists(($eid, $cik, $tag, $ticker, $sic)) and empty($entities)

let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $filingKind,
  $aid)

let $archives-not-found as boolean :=
  exists(($entities, $fiscalPeriod, $fiscalYear, $filingKind, $aid)) and empty($archives)

let $entities as object*  := entities:entities($archives.Entity)
let $components as object* :=
    multiplexer:components(
      $profile-name,
      $archives,
      $cid,
      $reportElement,
      $disclosure,
      $networkIdentifier,
      $label[$profile-name ne "sec"],
      $label[$profile-name eq "sec"]
    )

let $res as object* :=
    switch($profile-name)
    case "sec" return
        for $component-group in $components
        let $disclosure := sec-networks:disclosures($component-group)
        where $disclosure ne "DefaultComponent"
        order by $component-group.Label
        group by $archive := $component-group.Archive
        let $archive := $archives[archives:aid($$) eq $archive]
        let $e := $entities[entities:eid($$) eq $archive.Entity]
        return
            {
               AccessionNumber : archives:aid($archive),
               EntityRegistrantName : $e.Profiles.SEC.CompanyName,
               CIK : entities:eid($e),
               FiscalYear :$archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
               FiscalPeriod :$archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
               AcceptanceDatetime : sec-filings:acceptance-dateTimes($archive),
               FormType : $archive.Profiles.SEC.FormType,
               Components : [
                 for $component in $component-group
                 return {|
                   sec-networks:summaries($component),
                   local:links($component),
                   local:statistics($component),
                   {
                     ValidationErrors: [ components:validation-errors($component) ]
                   }
                 |}
               ]
           }
    default return
        for $component in $components
        return {|
          project($component, ("Archive", "Label", "Role")),
          local:links($component),
          local:statistics($component),
          {
            ValidationErrors: [ components:validation-errors($component) ]
          },
          { Disclosure: $component.Profiles.FSA.Disclosure}[$profile-name eq "japan"]
        |}
let $result := switch($profile-name)
               case "sec"
               return { Archives: [ $res ] }
               default return { Components : [ $res ] }
let $comment :=
 {
    NumComponents : count($components),
    TotalNumComponents: session:num-components(),
    TotalNumArchives: session:num-archives()
}
let $serializers := {
    to-xml : switch($profile-name)
        case "sec"
        return function($res as object) as node() {
        <Archives>{
                  for $r in flatten($res.Archives)
                  return
                      <Archive id="{$r.AccessionNumber}">
                         <EntityRegistrantName>{$r.EntityRegistrantName}</EntityRegistrantName>
                         <CIK>{$r.CIK}</CIK>
                         <FiscalYear>{$r.FiscalYear}</FiscalYear>
                         <FiscalPeriod>{$r.FiscalPeriod}</FiscalPeriod>
                         <AcceptanceDatetime>{$r.AcceptanceDatetime}</AcceptanceDatetime>
                         <FormType>{$r.FormType}</FormType>
                         <Components>{
                             api:json-to-xml($r.Components[], "Component")
                         }</Components>
                     </Archive>
             }</Archives>
        }
        default return function($res as object) as node() {
        <Components>{
          api:json-to-xml($res.Components[], "Component")
        }</Components>
    },
    to-csv : function($res as object) as string {
        switch($profile-name)
        case "sec" return api:json-to-csv($res.Archives[].Components[])
        default return api:json-to-csv($res.Components[])
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "components")
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       default return api:check-and-return-results($token, $results, $format)

import module namespace config = "http://apps.28.io/config";
import module namespace api = "http://apps.28.io/api";
import module namespace session = "http://apps.28.io/session";
import module namespace backend = "http://apps.28.io/backend";

import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace components = "http://28.io/modules/xbrl/components";

import module namespace sec-filings = "http://28.io/modules/xbrl/profiles/sec/filings";
import module namespace sec-networks = "http://28.io/modules/xbrl/profiles/sec/networks";
import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";

declare option rest:response "first-item";

(: Query parameters :)
declare  %rest:case-insensitive                 variable $token              as string? external;
declare  %rest:env                              variable $request-uri        as string  external;
declare  %rest:case-insensitive                 variable $format             as string? external;
declare  %rest:case-insensitive %rest:distinct  variable $cik                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $edinetcode   as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $tag                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $ticker             as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $sic                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $fiscalYear         as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $fiscalPeriod       as string* external := "ALL";
declare  %rest:case-insensitive %rest:distinct  variable $filingKind         as string* external := ();
declare  %rest:case-insensitive %rest:distinct  variable $eid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $aid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $networkIdentifier  as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $role               as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $cid                as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $reportElement      as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $concept            as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $disclosure         as string* external;
declare  %rest:case-insensitive %rest:distinct  variable $label              as string* external;
declare  %rest:case-insensitive                 variable $profile-name       as string  external := $config:profile-name;

(: Post-processing :)
let $format as string? := api:preprocess-format($format, $request-uri)
let $fiscalYear as integer* := api:preprocess-fiscal-years($fiscalYear)
let $fiscalPeriod as string* := api:preprocess-fiscal-periods($fiscalPeriod)
let $tag as string* := api:preprocess-tags($tag)
let $reportElement := ($reportElement, $concept)
let $networkIdentifier := distinct-values(($networkIdentifier, $role))

let $cik as string* :=
    switch($profile-name)
    case "sec" return $cik
    case "japan" return $edinetcode
    default return ()

(: Entity resolution :)
let $entities := multiplexer:entities(
  $profile-name,
  $eid,
  $cik,
  $tag,
  $ticker,
  $sic, ())

let $entities-not-found as boolean :=
  exists(($eid, $cik, $tag, $ticker, $sic)) and empty($entities)

let $archives as object* := multiplexer:filings(
  $profile-name,
  $entities,
  $fiscalPeriod,
  $fiscalYear,
  $filingKind,
  $aid)

let $archives-not-found as boolean :=
  exists(($entities, $fiscalPeriod, $fiscalYear, $filingKind, $aid)) and empty($archives)

let $entities as object*  := entities:entities($archives.Entity)
let $components as object* :=
    multiplexer:components(
      $profile-name,
      $archives,
      $cid,
      $reportElement,
      $disclosure,
      $networkIdentifier,
      $label[$profile-name ne "sec"],
      $label[$profile-name eq "sec"]
    )

let $res as object* :=
    switch($profile-name)
    case "sec" return
        for $r in $components
        let $disclosure := sec-networks:disclosures($r)
        where $disclosure ne "DefaultComponent"
        order by $r.Label
        group by $archive := $r.Archive
        let $archive := $archives[archives:aid($$) eq $archive]
        let $e := $entities[entities:eid($$) eq $archive.Entity]
        return
            {
               AccessionNumber : archives:aid($archive),
               EntityRegistrantName : $e.Profiles.SEC.CompanyName,
               CIK : entities:eid($e),
               FiscalYear :$archive.Profiles.SEC.Fiscal.DocumentFiscalYearFocus,
               FiscalPeriod :$archive.Profiles.SEC.Fiscal.DocumentFiscalPeriodFocus,
               AcceptanceDatetime : sec-filings:acceptance-dateTimes($archive),
               FormType : $archive.Profiles.SEC.FormType,
               Components : [
                    for $r in $r
                    let $component := sec-networks:summaries($r)
                    return copy $c := $component
                    modify insert json {
                        FactTable: backend:url("facttable-for-component", {
                            token: $token,
                            aid: archives:aid($archive),
                            format: $format,
                            role: $component.NetworkIdentifier,
                            profile-name: $profile-name
                            }),
                        SpreadSheet: "http://rendering.secxbrl.info/#?url=" || encode-for-uri(
                            backend:url("spreadsheet-for-component", {
                            token: $token,
                            aid: archives:aid($archive),
                            format: $format,
                            role: $component.NetworkIdentifier,
                            profile-name: $profile-name
                            })),
                        ReportElements: backend:url("report-elements", {
                            token: $token,
                            aid: archives:aid($archive),
                            format: $format,
                            role: $component.NetworkIdentifier,
                            profile-name: $profile-name
                            }),
                        ModelStructure: backend:url("modelstructure-for-component", {
                            token: $token,
                            aid: archives:aid($archive),
                            format: "json",
                            role: $component.NetworkIdentifier,
                            profile-name: $profile-name
                            }),
                        NumRules: size($r.Rules),
                        NumNetworks: components:num-networks($r),
                        NumReportElements: components:num-report-elements($r),
                        NumHypercubes: components:num-hypercubes($r),
                        NumDimensions: components:num-dimensions($r),
                        NumMembers: components:num-members($r),
                        NumLineItems: components:num-line-items($r),
                        NumAbstracts: components:num-abstracts($r),
                        NumConcepts: components:num-concepts($r),
                        Hypercubes: [ keys($r.Hypercubes) ],
                        ValidationErrors: [ components:validation-errors($r) ]
                    } into $c
                    return $c
               ]
           }
    default return
        for $r in $components
        return {|
          {
            Archive: $r.Archive,
            Label: $r.Label,
            Role: $r.Role,
            FactTable: backend:url("facttable-for-component", {
                            token: $token,
                            aid: $r.Archive,
                            format: $format,
                            role: $r.Role,
                            profile-name: $profile-name
                            }),
            SpreadSheet: "http://rendering.secxbrl.info/#?url=" || encode-for-uri(
                        backend:url("spreadsheet-for-component", {
                            token: $token,
                            aid: $r.Archive,
                            format: $format,
                            role: $r.Role,
                            profile-name: $profile-name,
                            eliminate: "true"
                        })),
            ReportElements: backend:url("report-elements", {
                            token: $token,
                            aid: $r.Archive,
                            format: $format,
                            role: $r.Role,
                            profile-name: $profile-name
                        }),
            ModelStructure: backend:url("modelstructure-for-component", {
                            token: $token,
                            aid: $r.Archive,
                            format: "json",
                            role: $r.Role,
                            profile-name: $profile-name
                        }),
            NumRules: size($r.Rules),
            NumNetworks: components:num-networks($r),
            NumReportElements: components:num-report-elements($r),
            NumHypercubes: components:num-hypercubes($r),
            NumDimensions: components:num-dimensions($r),
            NumMembers: components:num-members($r),
            NumLineItems: components:num-line-items($r),
            NumAbstracts: components:num-abstracts($r),
            NumConcepts: components:num-concepts($r),
            Hypercubes: [ keys($r.Hypercubes) ],
            ValidationErrors: [ components:validation-errors($r) ]
          },
          { Disclosure: $r.Profiles.FSA.Disclosure}[$profile-name eq "japan"]
        |}
let $result := switch($profile-name)
               case "sec"
               return { Archives: [ $res ] }
               default return { Components : [ $res ] }
let $comment :=
 {
    NumComponents : count($components),
    TotalNumComponents: session:num-components(),
    TotalNumArchives: session:num-archives()
}
let $serializers := {
    to-xml : switch($profile-name)
        case "sec"
        return function($res as object) as node() {
        <Archives>{
                  for $r in flatten($res.Archives)
                  return
                      <Archive id="{$r.AccessionNumber}">
                         <EntityRegistrantName>{$r.EntityRegistrantName}</EntityRegistrantName>
                         <CIK>{$r.CIK}</CIK>
                         <FiscalYear>{$r.FiscalYear}</FiscalYear>
                         <FiscalPeriod>{$r.FiscalPeriod}</FiscalPeriod>
                         <AcceptanceDatetime>{$r.AcceptanceDatetime}</AcceptanceDatetime>
                         <FormType>{$r.FormType}</FormType>
                         <Components>{
                             api:json-to-xml($r.Components[], "Component")
                         }</Components>
                     </Archive>
             }</Archives>
        }
        default return function($res as object) as node() {
        <Components>{
          api:json-to-xml($res.Components[], "Component")
        }</Components>
    },
    to-csv : function($res as object) as string {
        switch($profile-name)
        case "sec" return api:json-to-csv($res.Archives[].Components[])
        default return api:json-to-csv($res.Components[])
    }
}

let $results := api:serialize($result, $comment, $serializers, $format, "components")
return switch(true)
       case $entities-not-found return api:not-found("entity")
       case $archives-not-found return api:not-found("archive")
       default return api:check-and-return-results($token, $results, $format)


:bug: Fix error upon entering several fiscal years.

jsoniq version "1.0";

module namespace request = "http://apps.28.io/request";

import module namespace req = "http://www.28msec.com/modules/http-request";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace api = "http://apps.28.io/api";

declare function request:request(
  $profile-name as string,
  $entities as object*,
  $request-uri as string?,
  $format as string?,
  $concept as string*,
  $fiscalYear as string*,
  $fiscalPeriod as string*,
  $fiscalPeriodType as string*,
  $cik as string*,
  $tag as string*,
  $ticker as string*,
  $sic as string*,
  $aid as string*,
  $open as boolean) as object
{
  let $request as object :=
    {
      profile-name: $profile-name,
      format: api:preprocess-format($format, $request-uri),
      concept: [ $concept ],
      fiscalYear: [ $fiscalYear ],
      fiscalPeriod: [ api:preprocess-fiscal-periods($fiscalPeriod) ],
      fiscalPeriodType: [ $fiscalPeriodType ],
      cik: [ $cik ],
      tag: [ api:preprocess-tags($tag) ],
      ticker: [ $ticker ],
      sic: [ $sic ],
      aid: [ $aid ],
      open: $open
    }
  let $dimensions as object* :=
    for $parameter in request:param-names($request)
    group by $dimension-name :=
      switch(true)
      case ends-with(lower-case($parameter), "::default")
        return substring-before($parameter, "::default")
      case ends-with(lower-case($parameter), ":default")
        return substring-before($parameter, ":default")
      case ends-with(lower-case($parameter), "::type")
        return substring-before($parameter, "::type")
      default
        return $parameter
    let $all as boolean :=
      (request:param-values($dimension-name, $request, $entities) ! upper-case(string($$))) = "ALL"
    let $type as string? :=
      (request:param-values($dimension-name || "::type", $request, $entities),
       request:param-values($dimension-name || ":type", $request, $entities))[1]

    let $values := request:param-values($dimension-name, $request, $entities)
    let $typed-values := if (exists($type)) then request:cast-sequence($values[$$ ne "ALL"], $type) else $values

    let $has-default := ($parameter = $dimension-name || "::default") or ($parameter = $dimension-name || ":default")
    let $default-value := (request:param-values($dimension-name || "::default", $request, $entities),
                           request:param-values($dimension-name || ":default", $request, $entities))[1]
    let $typed-default-value := if (exists($type)) then request:cast-sequence($default-value, $type) else $default-value

    return
    {
       $dimension-name : {|
         { "Type" : $type }[exists($type)],
         { "Domain" : [ $typed-values ] }
             [exists($typed-values) and not($all)],
         { "Default" : $typed-default-value }[$has-default],
         { "Default" : null }
             [$all and (not $has-default) and exists($type)],
         { "Default" : "xbrl28:Domain" }
             [$all and (not $has-default) and empty($type)]
       |}
    }
  return
    {|
      $request,
      { dimensions: {| $dimensions |} }
    |}
};

declare function request:param-values(
  $name as string) as string*
{
  req:param-values($name)
};

declare %private function request:param-values(
    $name as string,
    $request as object,
    $entities as object*) as string*
{
  let $profile-name as string := $request.profile-name
  let $concept as string* := $request.concept[]
  let $fiscalYear as string* := $request.fiscalYear[]
  let $fiscalPeriod as string* := $request.fiscalPeriod[]
  let $fiscalPeriodType as string* := $request.fiscalPeriodType[]
  let $cik as string* := $request.cik[]
  let $tag as string* := $request.tag[]
  let $ticker as string* := $request.ticker[]
  let $sic as string* := $request.sic[]
  let $aid as string* := $request.aid[]
  return
    switch(true)
     case $name eq "xbrl:Concept"
        return ($concept, req:param-values("xbrl:Concept"))

    (: sec profile :)
     case $name eq "sec:FiscalYear" and $profile-name eq "sec"
        return ($fiscalYear, req:param-values("sec:FiscalYear"))[$$ ne "LATEST"]
     case $name eq "sec:FiscalYear::type" and $profile-name eq "sec"
        return "integer"
     case $name eq "sec:FiscalPeriod" and $profile-name eq "sec"
        return ($fiscalPeriod, req:param-values("sec:FiscalPeriod"))
     case $name eq "sec:FiscalPeriod::type" and $profile-name eq "sec"
        return "string"
     case $name eq "sec:FiscalPeriodType" and $profile-name eq "sec"
        return ($fiscalPeriodType, req:param-values("sec:FiscalPeriodType"))
     case $name eq "sec:FiscalPeriodType::type" and $profile-name eq "sec"
        return "string"
     case $name eq "dei:LegalEntityAxis" and $profile-name eq "sec"
        return
         if(empty((req:param-values("sec:LegalEntityAxis"), req:param-values("sec:LegalEntityAxis::default"))))
         then "sec:DefaultLegalEntity"
         else req:param-values("sec:LegalEntityAxis")
     case $name eq "dei:LegalEntityAxis::default" return
         if(empty((req:param-values("sec:LegalEntityAxis"), req:param-values("sec:LegalEntityAxis::default"))))
         then "sec:DefaultLegalEntity"
         else req:param-values("sec:LegalEntityAxis::default")

     case $name eq "xbrl:Entity" and $profile-name = ("sec", "japan")
         return (
                if(empty(($cik,$tag,$ticker,$sic)) or exists($entities))
                then entities:eid($entities)
                else "dummy",
                req:param-values("xbrl:Entity")
            )
     case $name eq "xbrl28:Archive" and $profile-name = ("sec", "japan") return (
            let $prefix as string :=
                switch($profile-name)
                case "sec" return "sec"
                case "japan" return "fsa"
                default return (: not reachable :) ()
            let $fiscalYears :=
                let $years := req:param-values( $prefix || ":FiscalYear")
                return if (exists($years)) then $years else $fiscalYear
            let $fiscalPeriods := request:param-values($prefix || ":FiscalPeriod", $request, $entities)
            return
                if($fiscalYears = "LATEST")
                then archives:aid(multiplexer:latest-filings($profile-name, $entities, $fiscalPeriods))
                else (),
            $aid,
            req:param-values("xbrl28:Archive")
        )

     (: japan profile :)
     case $name eq "fsa:FiscalYear" and $profile-name eq "japan"
     return ($fiscalYear ! string($$), req:param-values("fsa:FiscalYear"))[$$ ne "LATEST"]
     case $name eq "fsa:FiscalYear::type" and $profile-name eq "japan"
     return "integer"
     case $name eq "fsa:FiscalPeriod" and $profile-name eq "japan"
     return ($fiscalPeriod, req:param-values("fsa:FiscalPeriod"))
     case $name eq "fsa:FiscalPeriod::type" and $profile-name eq "japan"
     return "string"
     case $name eq "fsa:FiscalPeriodType" and $profile-name eq "japan"
     return ($fiscalPeriodType, req:param-values("fsa:FiscalPeriodType"))
     case $name eq "fsa:FiscalPeriodType::type" and $profile-name eq "japan"
     return "string"
     case $name eq "fsa:ArchiveFiscalYear::type" and $profile-name eq "japan"
     return "integer"
     case $name eq "fsa:ArchiveFiscalPeriod::type" and $profile-name eq "japan"
     return "string"

     default return req:param-values($name)
};

declare %private function request:param-names($request as object) as string*
{
  let $profile-name as string := $request.profile-name
  let $concept as string* := $request.concept[]
  let $fiscalYear as string* := $request.fiscalYear[]
  let $fiscalPeriod as string* := $request.fiscalPeriod[]
  let $fiscalPeriodType as string* := $request.fiscalPeriodType[]
  let $names := req:param-names()
  return distinct-values((
    (: generic xbrl :)
    $names[contains($$, ":")],
    "xbrl:Entity"[$names = ("eid")],
    "xbrl28:Archive"[$profile-name = ("sec", "japan")],
    "xbrl:Concept"[exists($concept)],

    (: sec profile :)
    "sec:Accepted"[$profile-name eq "sec"],
    "sec:FiscalPeriod"[$profile-name eq "sec"],
    "sec:FiscalPeriodType"[$profile-name eq "sec"],
    "sec:FiscalYear"[$profile-name eq "sec"],
    "xbrl:Entity"[$profile-name eq "sec" and
                  $names = ("cik", "tag", "ticker", "sic")],
    "dei:LegalEntityAxis"[$profile-name eq "sec"],
    "dei:LegalEntityAxis::default"[$profile-name eq "sec"],

    (: japan profile :)
    "fsa:FiscalPeriod"[$profile-name eq "japan" and
                       exists($fiscalPeriod)],
    "fsa:FiscalPeriodType"[$profile-name eq "japan" and
                           exists($fiscalPeriodType)],
    "fsa:FiscalYear"[$profile-name eq "japan" and
                     exists($fiscalYear)],
    "xbrl:Entity"[$profile-name eq "japan" and
                  $names = ("edinetcode", "tag", "ticker")]
  ))
};

declare %private function request:cast-sequence($values as atomic*, $type as string) as atomic*
{
  for $value in $values
  return
      switch ($type)
      case "integer" return $value cast as integer
      case "string" return $value cast as string
      default return error(xs:QName("local:unsupported-type"), $type || ": unsupported type")
};

declare function request:hypercube($request as object) as object
{
  let $open as boolean := $request.open
  let $hypercube-spec := $request.dimensions
  return hypercubes:user-defined-hypercube($hypercube-spec, { IsOpen: $open })
};

jsoniq version "1.0";

module namespace request = "http://apps.28.io/request";

import module namespace req = "http://www.28msec.com/modules/http-request";
import module namespace entities = "http://28.io/modules/xbrl/entities";
import module namespace archives = "http://28.io/modules/xbrl/archives";
import module namespace multiplexer = "http://28.io/modules/xbrl/profiles/multiplexer";
import module namespace hypercubes = "http://28.io/modules/xbrl/hypercubes";
import module namespace api = "http://apps.28.io/api";

declare function request:request(
  $profile-name as string,
  $entities as object*,
  $request-uri as string?,
  $format as string?,
  $concept as string*,
  $fiscalYear as string*,
  $fiscalPeriod as string*,
  $fiscalPeriodType as string*,
  $cik as string*,
  $tag as string*,
  $ticker as string*,
  $sic as string*,
  $aid as string*,
  $open as boolean) as object
{
  let $request as object :=
    {
      profile-name: $profile-name,
      format: api:preprocess-format($format, $request-uri),
      concept: [ $concept ],
      fiscalYear: [ $fiscalYear ],
      fiscalPeriod: [ api:preprocess-fiscal-periods($fiscalPeriod) ],
      fiscalPeriodType: [ $fiscalPeriodType ],
      cik: [ $cik ],
      tag: [ api:preprocess-tags($tag) ],
      ticker: [ $ticker ],
      sic: [ $sic ],
      aid: [ $aid ],
      open: $open
    }
  let $dimensions as object* :=
    for $parameter in request:param-names($request)
    group by $dimension-name :=
      switch(true)
      case ends-with(lower-case($parameter), "::default")
        return substring-before($parameter, "::default")
      case ends-with(lower-case($parameter), ":default")
        return substring-before($parameter, ":default")
      case ends-with(lower-case($parameter), "::type")
        return substring-before($parameter, "::type")
      default
        return $parameter
    let $all as boolean :=
      (request:param-values($dimension-name, $request, $entities) ! upper-case(string($$))) = "ALL"
    let $type as string? :=
      (request:param-values($dimension-name || "::type", $request, $entities),
       request:param-values($dimension-name || ":type", $request, $entities))[1]

    let $values := request:param-values($dimension-name, $request, $entities)
    let $typed-values := if (exists($type)) then request:cast-sequence($values[$$ ne "ALL"], $type) else $values

    let $has-default := ($parameter = $dimension-name || "::default") or ($parameter = $dimension-name || ":default")
    let $default-value := (request:param-values($dimension-name || "::default", $request, $entities),
                           request:param-values($dimension-name || ":default", $request, $entities))[1]
    let $typed-default-value := if (exists($type)) then request:cast-sequence($default-value, $type) else $default-value

    return
    {
       $dimension-name : {|
         { "Type" : $type }[exists($type)],
         { "Domain" : [ $typed-values ] }
             [exists($typed-values) and not($all)],
         { "Default" : $typed-default-value }[$has-default],
         { "Default" : null }
             [$all and (not $has-default) and exists($type)],
         { "Default" : "xbrl28:Domain" }
             [$all and (not $has-default) and empty($type)]
       |}
    }
  return
    {|
      $request,
      { dimensions: {| $dimensions |} }
    |}
};

declare function request:param-values(
  $name as string) as string*
{
  req:param-values($name)
};

declare %private function request:param-values(
    $name as string,
    $request as object,
    $entities as object*) as string*
{
  let $profile-name as string := $request.profile-name
  let $concept as string* := $request.concept[]
  let $fiscalYear as string* := $request.fiscalYear[]
  let $fiscalPeriod as string* := $request.fiscalPeriod[]
  let $fiscalPeriodType as string* := $request.fiscalPeriodType[]
  let $cik as string* := $request.cik[]
  let $tag as string* := $request.tag[]
  let $ticker as string* := $request.ticker[]
  let $sic as string* := $request.sic[]
  let $aid as string* := $request.aid[]
  return
    switch(true)
     case $name eq "xbrl:Concept"
        return ($concept, req:param-values("xbrl:Concept"))

    (: sec profile :)
     case $name eq "sec:FiscalYear" and $profile-name eq "sec"
        return ($fiscalYear, req:param-values("sec:FiscalYear"))[$$ ne "LATEST"]
     case $name eq "sec:FiscalYear::type" and $profile-name eq "sec"
        return "integer"
     case $name eq "sec:FiscalPeriod" and $profile-name eq "sec"
        return ($fiscalPeriod, req:param-values("sec:FiscalPeriod"))
     case $name eq "sec:FiscalPeriod::type" and $profile-name eq "sec"
        return "string"
     case $name eq "sec:FiscalPeriodType" and $profile-name eq "sec"
        return ($fiscalPeriodType, req:param-values("sec:FiscalPeriodType"))
     case $name eq "sec:FiscalPeriodType::type" and $profile-name eq "sec"
        return "string"
     case $name eq "dei:LegalEntityAxis" and $profile-name eq "sec"
        return
         if(empty((req:param-values("sec:LegalEntityAxis"), req:param-values("sec:LegalEntityAxis::default"))))
         then "sec:DefaultLegalEntity"
         else req:param-values("sec:LegalEntityAxis")
     case $name eq "dei:LegalEntityAxis::default" return
         if(empty((req:param-values("sec:LegalEntityAxis"), req:param-values("sec:LegalEntityAxis::default"))))
         then "sec:DefaultLegalEntity"
         else req:param-values("sec:LegalEntityAxis::default")

     case $name eq "xbrl:Entity" and $profile-name = ("sec", "japan")
         return (
                if(empty(($cik,$tag,$ticker,$sic)) or exists($entities))
                then entities:eid($entities)
                else "dummy",
                req:param-values("xbrl:Entity")
            )
     case $name eq "xbrl28:Archive" and $profile-name = ("sec", "japan") return (
            let $prefix as string :=
                switch($profile-name)
                case "sec" return "sec"
                case "japan" return "fsa"
                default return (: not reachable :) ()
            let $fiscalYears :=
                let $years := req:param-values( $prefix || ":FiscalYear")
                return if ($years) then $years else $fiscalYear
            let $fiscalPeriods := request:param-values($prefix || ":FiscalPeriod", $request, $entities)
            return
                if($fiscalYears = "LATEST")
                then archives:aid(multiplexer:latest-filings($profile-name, $entities, $fiscalPeriods))
                else (),
            $aid,
            req:param-values("xbrl28:Archive")
        )

     (: japan profile :)
     case $name eq "fsa:FiscalYear" and $profile-name eq "japan"
     return ($fiscalYear ! string($$), req:param-values("fsa:FiscalYear"))[$$ ne "LATEST"]
     case $name eq "fsa:FiscalYear::type" and $profile-name eq "japan"
     return "integer"
     case $name eq "fsa:FiscalPeriod" and $profile-name eq "japan"
     return ($fiscalPeriod, req:param-values("fsa:FiscalPeriod"))
     case $name eq "fsa:FiscalPeriod::type" and $profile-name eq "japan"
     return "string"
     case $name eq "fsa:FiscalPeriodType" and $profile-name eq "japan"
     return ($fiscalPeriodType, req:param-values("fsa:FiscalPeriodType"))
     case $name eq "fsa:FiscalPeriodType::type" and $profile-name eq "japan"
     return "string"
     case $name eq "fsa:ArchiveFiscalYear::type" and $profile-name eq "japan"
     return "integer"
     case $name eq "fsa:ArchiveFiscalPeriod::type" and $profile-name eq "japan"
     return "string"

     default return req:param-values($name)
};

declare %private function request:param-names($request as object) as string*
{
  let $profile-name as string := $request.profile-name
  let $concept as string* := $request.concept[]
  let $fiscalYear as string* := $request.fiscalYear[]
  let $fiscalPeriod as string* := $request.fiscalPeriod[]
  let $fiscalPeriodType as string* := $request.fiscalPeriodType[]
  let $names := req:param-names()
  return distinct-values((
    (: generic xbrl :)
    $names[contains($$, ":")],
    "xbrl:Entity"[$names = ("eid")],
    "xbrl28:Archive"[$profile-name = ("sec", "japan")],
    "xbrl:Concept"[exists($concept)],

    (: sec profile :)
    "sec:Accepted"[$profile-name eq "sec"],
    "sec:FiscalPeriod"[$profile-name eq "sec"],
    "sec:FiscalPeriodType"[$profile-name eq "sec"],
    "sec:FiscalYear"[$profile-name eq "sec"],
    "xbrl:Entity"[$profile-name eq "sec" and
                  $names = ("cik", "tag", "ticker", "sic")],
    "dei:LegalEntityAxis"[$profile-name eq "sec"],
    "dei:LegalEntityAxis::default"[$profile-name eq "sec"],

    (: japan profile :)
    "fsa:FiscalPeriod"[$profile-name eq "japan" and
                       exists($fiscalPeriod)],
    "fsa:FiscalPeriodType"[$profile-name eq "japan" and
                           exists($fiscalPeriodType)],
    "fsa:FiscalYear"[$profile-name eq "japan" and
                     exists($fiscalYear)],
    "xbrl:Entity"[$profile-name eq "japan" and
                  $names = ("edinetcode", "tag", "ticker")]
  ))
};

declare %private function request:cast-sequence($values as atomic*, $type as string) as atomic*
{
  for $value in $values
  return
      switch ($type)
      case "integer" return $value cast as integer
      case "string" return $value cast as string
      default return error(xs:QName("local:unsupported-type"), $type || ": unsupported type")
};

declare function request:hypercube($request as object) as object
{
  let $open as boolean := $request.open
  let $hypercube-spec := $request.dimensions
  return hypercubes:user-defined-hypercube($hypercube-spec, { IsOpen: $open })
};