From 2887d4e0934a9c4c57513d0d07262a3f5cfaef7a Mon Sep 17 00:00:00 2001 From: Michael Nygard Date: Sat, 25 Jul 2020 19:03:55 +0000 Subject: [PATCH] Squashed 'vase/' content from commit b2b7df6 git-subtree-dir: vase git-subtree-split: b2b7df6506986c3ad01f5f6d4aab1224b725e4eb --- .circleci/config.yml | 23 + .github/ISSUE_TEMPLATE.md | 16 + .gitignore | 68 + COPYRIGHT.txt | 1 + LICENSE | 203 + README.md | 245 + config/logback.xml | 67 + deps.edn | 12 + dev/edn_fern/convert.clj | 178 + dev/tools.clj | 28 + docs/action_literals.md | 127 + docs/adding_vase.md | 150 + docs/descriptor_facts_schema.graffle | Bin 0 -> 4326 bytes docs/descriptor_facts_schema.png | Bin 0 -> 117048 bytes docs/design.md | 464 + docs/migrating_edn_to_fern.md | 82 + docs/vase_with_fern.md | 283 + docs/your_first_api.md | 784 + project.clj | 75 + samples/omnext-todo/.gitignore | 38 + samples/omnext-todo/Capstanfile | 29 + samples/omnext-todo/Dockerfile | 8 + samples/omnext-todo/README.md | 65 + samples/omnext-todo/boot.properties | 3 + samples/omnext-todo/build.boot | 107 + samples/omnext-todo/config/logback.xml | 50 + samples/omnext-todo/project.clj | 38 + .../resources/omnext-todo_service.edn | 68 + .../omnext-todo/src/omnext_todo/demand.clj | 151 + .../omnext-todo/src/omnext_todo/server.clj | 91 + .../omnext-todo/src/omnext_todo/service.clj | 115 + .../test/omnext_todo/service_test.clj | 59 + .../test/omnext_todo/test_helper.clj | 108 + samples/petstore/README.md | 40 + samples/petstore/deps.edn | 10 + samples/petstore/petstore.fern | 74 + samples/petstore/resources/logback.xml | 52 + .../petstore/resources/public/css/print.css | 1362 + .../petstore/resources/public/css/reset.css | 125 + .../petstore/resources/public/css/screen.css | 1489 ++ .../petstore/resources/public/css/style.css | 250 + .../resources/public/css/typography.css | 14 + .../resources/public/fonts/DroidSans-Bold.ttf | Bin 0 -> 42480 bytes .../resources/public/fonts/DroidSans.ttf | Bin 0 -> 41028 bytes .../resources/public/images/collapse.gif | Bin 0 -> 69 bytes .../resources/public/images/expand.gif | Bin 0 -> 73 bytes .../public/images/explorer_icons.png | Bin 0 -> 5763 bytes .../resources/public/images/favicon-16x16.png | Bin 0 -> 645 bytes .../resources/public/images/favicon-32x32.png | Bin 0 -> 1654 bytes .../resources/public/images/favicon.ico | Bin 0 -> 5430 bytes .../resources/public/images/logo_small.png | Bin 0 -> 770 bytes .../resources/public/images/pet_store_api.png | Bin 0 -> 824 bytes .../resources/public/images/throbber.gif | Bin 0 -> 9257 bytes .../resources/public/images/wordnik_api.png | Bin 0 -> 980 bytes samples/petstore/resources/public/index.html | 107 + .../resources/public/js/backbone-min.js | 15 + .../resources/public/js/handlebars-2.0.0.js | 28 + .../public/js/highlight.9.1.0.pack.js | 2 + .../js/highlight.9.1.0.pack_extended.js | 34 + .../resources/public/js/jquery-1.8.0.min.js | 2 + .../resources/public/js/jquery.ba-bbq.min.js | 18 + .../resources/public/js/jquery.slideto.min.js | 1 + .../resources/public/js/jquery.wiggle.min.js | 8 + .../resources/public/js/js-yaml.min.js | 3 + .../resources/public/js/jsoneditor.min.js | 11 + .../resources/public/js/lodash.min.js | 102 + .../petstore/resources/public/js/marked.js | 1272 + .../public/js/object-assign-pollyfill.js | 23 + .../resources/public/js/swagger-oauth.js | 347 + samples/petstore/resources/public/lang/en.js | 56 + samples/petstore/resources/public/lang/es.js | 53 + samples/petstore/resources/public/lang/fr.js | 54 + samples/petstore/resources/public/lang/geo.js | 56 + samples/petstore/resources/public/lang/it.js | 52 + samples/petstore/resources/public/lang/ja.js | 53 + samples/petstore/resources/public/lang/pl.js | 53 + samples/petstore/resources/public/lang/pt.js | 53 + samples/petstore/resources/public/lang/ru.js | 56 + samples/petstore/resources/public/lang/tr.js | 53 + .../resources/public/lang/translator.js | 39 + .../petstore/resources/public/lang/zh-cn.js | 53 + samples/petstore/resources/public/o2c.html | 20 + .../petstore/resources/public/swagger-ui.js | 22278 ++++++++++++++++ .../resources/public/swagger-ui.min.js | 9 + .../resources/public/v1/petstore-simple.json | 247 + samples/vase-component/README.md | 26 + samples/vase-component/config/logback.xml | 52 + samples/vase-component/dev/dev.clj | 35 + samples/vase-component/dev/user.clj | 17 + samples/vase-component/project.clj | 24 + .../resources/petstore-simple.edn | 39 + .../vase-component/src/vase_component/api.clj | 19 + .../src/vase_component/endpoint.clj | 100 + .../src/vase_component/main.clj | 26 + .../src/vase_component/system.clj | 12 + samples/vasebi/.gitignore | 38 + samples/vasebi/Capstanfile | 29 + samples/vasebi/Dockerfile | 8 + samples/vasebi/README.md | 60 + samples/vasebi/boot.properties | 3 + samples/vasebi/build.boot | 107 + samples/vasebi/config/logback.xml | 52 + samples/vasebi/project.clj | 28 + .../resources/public/dist/gchart_renderers.js | 183 + .../public/dist/gchart_renderers.min.js | 2 + .../vasebi/resources/public/dist/pivot.css | 103 + samples/vasebi/resources/public/dist/pivot.js | 1674 ++ .../vasebi/resources/public/dist/pivot.min.js | 458 + samples/vasebi/resources/public/index.html | 66 + samples/vasebi/resources/vasebi_service.edn | 158 + samples/vasebi/src/vasebi/server.clj | 95 + samples/vasebi/src/vasebi/service.clj | 68 + samples/vasebi/test/vasebi/service_test.clj | 29 + samples/vasebi/test/vasebi/test_helper.clj | 108 + script/pre-release-check.sh | 37 + src/com/cognitect/vase.clj | 98 + src/com/cognitect/vase/actions.clj | 550 + src/com/cognitect/vase/api.clj | 109 + src/com/cognitect/vase/datomic.clj | 42 + src/com/cognitect/vase/edn.clj | 51 + src/com/cognitect/vase/fern.clj | 201 + src/com/cognitect/vase/interceptor.clj | 36 + src/com/cognitect/vase/literals.clj | 136 + src/com/cognitect/vase/main.clj | 37 + src/com/cognitect/vase/response.clj | 27 + src/com/cognitect/vase/routes.clj | 98 + src/com/cognitect/vase/spec.clj | 79 + src/com/cognitect/vase/try.clj | 37 + src/com/cognitect/vase/util.clj | 76 + src/data_readers.clj | 13 + template/.gitignore | 49 + template/README.md | 20 + template/project.clj | 20 + template/src/leiningen/new/vase.clj | 37 + template/src/leiningen/new/vase/.gitignore | 38 + template/src/leiningen/new/vase/Capstanfile | 29 + template/src/leiningen/new/vase/Dockerfile | 8 + template/src/leiningen/new/vase/README.md | 47 + .../src/leiningen/new/vase/boot.properties | 3 + template/src/leiningen/new/vase/build.boot | 105 + template/src/leiningen/new/vase/logback.xml | 52 + template/src/leiningen/new/vase/project.clj | 27 + template/src/leiningen/new/vase/service.clj | 47 + .../src/leiningen/new/vase/service_test.clj | 3 + .../src/leiningen/new/vase/vase_service.edn | 105 + .../src/leiningen/new/vase/vase_service.fern | 6 + .../vase/new_server_integration_test.clj | 76 + test/com/cognitect/vase/actions_test.clj | 43 + test/com/cognitect/vase/conform_test.clj | 49 + .../com/cognitect/vase/datomic_cloud_test.clj | 99 + test/com/cognitect/vase/descriptor_test.clj | 125 + test/com/cognitect/vase/interceptor_test.clj | 25 + test/com/cognitect/vase/literals_test.clj | 135 + test/com/cognitect/vase/query_test.clj | 98 + test/com/cognitect/vase/redirect_test.clj | 22 + test/com/cognitect/vase/respond_test.clj | 40 + .../cognitect/vase/service_route_table.clj | 40 + test/com/cognitect/vase/service_test.clj | 59 + test/com/cognitect/vase/spec_test.clj | 53 + test/com/cognitect/vase/test_db_helper.clj | 104 + test/com/cognitect/vase/test_helper.clj | 92 + test/com/cognitect/vase/transaction_test.clj | 215 + test/com/cognitect/vase/validate_test.clj | 35 + test/resources/sample_descriptor.edn | 129 + test/resources/sample_payload.edn | 130 + test/resources/small_descriptor.edn | 63 + test/resources/test_descriptor.edn | 128 + test/resources/test_descriptor.fern | 152 + 168 files changed, 40064 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .gitignore create mode 100644 COPYRIGHT.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config/logback.xml create mode 100644 deps.edn create mode 100644 dev/edn_fern/convert.clj create mode 100644 dev/tools.clj create mode 100644 docs/action_literals.md create mode 100644 docs/adding_vase.md create mode 100644 docs/descriptor_facts_schema.graffle create mode 100644 docs/descriptor_facts_schema.png create mode 100644 docs/design.md create mode 100644 docs/migrating_edn_to_fern.md create mode 100644 docs/vase_with_fern.md create mode 100644 docs/your_first_api.md create mode 100644 project.clj create mode 100644 samples/omnext-todo/.gitignore create mode 100644 samples/omnext-todo/Capstanfile create mode 100644 samples/omnext-todo/Dockerfile create mode 100644 samples/omnext-todo/README.md create mode 100644 samples/omnext-todo/boot.properties create mode 100644 samples/omnext-todo/build.boot create mode 100644 samples/omnext-todo/config/logback.xml create mode 100644 samples/omnext-todo/project.clj create mode 100644 samples/omnext-todo/resources/omnext-todo_service.edn create mode 100644 samples/omnext-todo/src/omnext_todo/demand.clj create mode 100644 samples/omnext-todo/src/omnext_todo/server.clj create mode 100644 samples/omnext-todo/src/omnext_todo/service.clj create mode 100644 samples/omnext-todo/test/omnext_todo/service_test.clj create mode 100644 samples/omnext-todo/test/omnext_todo/test_helper.clj create mode 100644 samples/petstore/README.md create mode 100644 samples/petstore/deps.edn create mode 100644 samples/petstore/petstore.fern create mode 100644 samples/petstore/resources/logback.xml create mode 100644 samples/petstore/resources/public/css/print.css create mode 100644 samples/petstore/resources/public/css/reset.css create mode 100644 samples/petstore/resources/public/css/screen.css create mode 100644 samples/petstore/resources/public/css/style.css create mode 100644 samples/petstore/resources/public/css/typography.css create mode 100644 samples/petstore/resources/public/fonts/DroidSans-Bold.ttf create mode 100644 samples/petstore/resources/public/fonts/DroidSans.ttf create mode 100644 samples/petstore/resources/public/images/collapse.gif create mode 100644 samples/petstore/resources/public/images/expand.gif create mode 100644 samples/petstore/resources/public/images/explorer_icons.png create mode 100755 samples/petstore/resources/public/images/favicon-16x16.png create mode 100755 samples/petstore/resources/public/images/favicon-32x32.png create mode 100755 samples/petstore/resources/public/images/favicon.ico create mode 100644 samples/petstore/resources/public/images/logo_small.png create mode 100644 samples/petstore/resources/public/images/pet_store_api.png create mode 100644 samples/petstore/resources/public/images/throbber.gif create mode 100644 samples/petstore/resources/public/images/wordnik_api.png create mode 100644 samples/petstore/resources/public/index.html create mode 100644 samples/petstore/resources/public/js/backbone-min.js create mode 100644 samples/petstore/resources/public/js/handlebars-2.0.0.js create mode 100644 samples/petstore/resources/public/js/highlight.9.1.0.pack.js create mode 100644 samples/petstore/resources/public/js/highlight.9.1.0.pack_extended.js create mode 100644 samples/petstore/resources/public/js/jquery-1.8.0.min.js create mode 100644 samples/petstore/resources/public/js/jquery.ba-bbq.min.js create mode 100644 samples/petstore/resources/public/js/jquery.slideto.min.js create mode 100644 samples/petstore/resources/public/js/jquery.wiggle.min.js create mode 100644 samples/petstore/resources/public/js/js-yaml.min.js create mode 100644 samples/petstore/resources/public/js/jsoneditor.min.js create mode 100644 samples/petstore/resources/public/js/lodash.min.js create mode 100644 samples/petstore/resources/public/js/marked.js create mode 100644 samples/petstore/resources/public/js/object-assign-pollyfill.js create mode 100644 samples/petstore/resources/public/js/swagger-oauth.js create mode 100644 samples/petstore/resources/public/lang/en.js create mode 100644 samples/petstore/resources/public/lang/es.js create mode 100644 samples/petstore/resources/public/lang/fr.js create mode 100644 samples/petstore/resources/public/lang/geo.js create mode 100644 samples/petstore/resources/public/lang/it.js create mode 100755 samples/petstore/resources/public/lang/ja.js create mode 100644 samples/petstore/resources/public/lang/pl.js create mode 100644 samples/petstore/resources/public/lang/pt.js create mode 100644 samples/petstore/resources/public/lang/ru.js create mode 100644 samples/petstore/resources/public/lang/tr.js create mode 100644 samples/petstore/resources/public/lang/translator.js create mode 100644 samples/petstore/resources/public/lang/zh-cn.js create mode 100644 samples/petstore/resources/public/o2c.html create mode 100644 samples/petstore/resources/public/swagger-ui.js create mode 100644 samples/petstore/resources/public/swagger-ui.min.js create mode 100644 samples/petstore/resources/public/v1/petstore-simple.json create mode 100644 samples/vase-component/README.md create mode 100644 samples/vase-component/config/logback.xml create mode 100644 samples/vase-component/dev/dev.clj create mode 100644 samples/vase-component/dev/user.clj create mode 100644 samples/vase-component/project.clj create mode 100644 samples/vase-component/resources/petstore-simple.edn create mode 100644 samples/vase-component/src/vase_component/api.clj create mode 100644 samples/vase-component/src/vase_component/endpoint.clj create mode 100644 samples/vase-component/src/vase_component/main.clj create mode 100644 samples/vase-component/src/vase_component/system.clj create mode 100644 samples/vasebi/.gitignore create mode 100644 samples/vasebi/Capstanfile create mode 100644 samples/vasebi/Dockerfile create mode 100644 samples/vasebi/README.md create mode 100644 samples/vasebi/boot.properties create mode 100644 samples/vasebi/build.boot create mode 100644 samples/vasebi/config/logback.xml create mode 100644 samples/vasebi/project.clj create mode 100644 samples/vasebi/resources/public/dist/gchart_renderers.js create mode 100644 samples/vasebi/resources/public/dist/gchart_renderers.min.js create mode 100644 samples/vasebi/resources/public/dist/pivot.css create mode 100644 samples/vasebi/resources/public/dist/pivot.js create mode 100644 samples/vasebi/resources/public/dist/pivot.min.js create mode 100644 samples/vasebi/resources/public/index.html create mode 100644 samples/vasebi/resources/vasebi_service.edn create mode 100644 samples/vasebi/src/vasebi/server.clj create mode 100644 samples/vasebi/src/vasebi/service.clj create mode 100644 samples/vasebi/test/vasebi/service_test.clj create mode 100644 samples/vasebi/test/vasebi/test_helper.clj create mode 100755 script/pre-release-check.sh create mode 100644 src/com/cognitect/vase.clj create mode 100644 src/com/cognitect/vase/actions.clj create mode 100644 src/com/cognitect/vase/api.clj create mode 100644 src/com/cognitect/vase/datomic.clj create mode 100644 src/com/cognitect/vase/edn.clj create mode 100644 src/com/cognitect/vase/fern.clj create mode 100644 src/com/cognitect/vase/interceptor.clj create mode 100644 src/com/cognitect/vase/literals.clj create mode 100644 src/com/cognitect/vase/main.clj create mode 100644 src/com/cognitect/vase/response.clj create mode 100644 src/com/cognitect/vase/routes.clj create mode 100644 src/com/cognitect/vase/spec.clj create mode 100644 src/com/cognitect/vase/try.clj create mode 100644 src/com/cognitect/vase/util.clj create mode 100644 src/data_readers.clj create mode 100644 template/.gitignore create mode 100644 template/README.md create mode 100644 template/project.clj create mode 100644 template/src/leiningen/new/vase.clj create mode 100644 template/src/leiningen/new/vase/.gitignore create mode 100644 template/src/leiningen/new/vase/Capstanfile create mode 100644 template/src/leiningen/new/vase/Dockerfile create mode 100644 template/src/leiningen/new/vase/README.md create mode 100644 template/src/leiningen/new/vase/boot.properties create mode 100644 template/src/leiningen/new/vase/build.boot create mode 100644 template/src/leiningen/new/vase/logback.xml create mode 100644 template/src/leiningen/new/vase/project.clj create mode 100644 template/src/leiningen/new/vase/service.clj create mode 100644 template/src/leiningen/new/vase/service_test.clj create mode 100644 template/src/leiningen/new/vase/vase_service.edn create mode 100644 template/src/leiningen/new/vase/vase_service.fern create mode 100644 template/test/com/cognitect/vase/new_server_integration_test.clj create mode 100644 test/com/cognitect/vase/actions_test.clj create mode 100644 test/com/cognitect/vase/conform_test.clj create mode 100644 test/com/cognitect/vase/datomic_cloud_test.clj create mode 100644 test/com/cognitect/vase/descriptor_test.clj create mode 100644 test/com/cognitect/vase/interceptor_test.clj create mode 100644 test/com/cognitect/vase/literals_test.clj create mode 100644 test/com/cognitect/vase/query_test.clj create mode 100644 test/com/cognitect/vase/redirect_test.clj create mode 100644 test/com/cognitect/vase/respond_test.clj create mode 100644 test/com/cognitect/vase/service_route_table.clj create mode 100644 test/com/cognitect/vase/service_test.clj create mode 100644 test/com/cognitect/vase/spec_test.clj create mode 100644 test/com/cognitect/vase/test_db_helper.clj create mode 100644 test/com/cognitect/vase/test_helper.clj create mode 100644 test/com/cognitect/vase/transaction_test.clj create mode 100644 test/com/cognitect/vase/validate_test.clj create mode 100644 test/resources/sample_descriptor.edn create mode 100644 test/resources/sample_payload.edn create mode 100644 test/resources/small_descriptor.edn create mode 100644 test/resources/test_descriptor.edn create mode 100644 test/resources/test_descriptor.fern diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..517d50b --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,23 @@ +version: 2 + +jobs: + build: + docker: + - image: circleci/clojure:lein-2.7.1 + environment: + LEIN_ROOT: nbd + JVM_OPTS: -Xmx3200m + steps: + - checkout + - restore_cache: + key: vase-{{ checksum "project.clj" }} + - run: lein install + - save_cache: + paths: + - ~/.m2 + key: vase-{{ checksum "project.clj" }} + - run: cd template && lein test + - run: lein do test, uberjar + - store_artifacts: + path: target/uberjar/vase.jar + destination: uberjar diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..bae2fca --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,16 @@ +# Description + +## Expected Behavior + +## Actual Behavior + +## Steps to reproduce + +# Environment + +## Operating System (including version). + +## Your current Leiningen/Boot/Maven version (`lein --version`) + +## Pedestal and Vase version + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf508d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Project related +.rebel_readline_history +my-new-payload.edn +data/ +/datomic/ +/resources/public/ratings/ + +# Java related +pom.xml +*jar +*.class + +# Leiningen +classes/ +lib/ +native/ +checkouts/ +target/ +.lein-* +repl-port +.nrepl-port +.repl + +# Datomic +*.db + +# Python +*.pyc +*.pyo +/__pycache__/ + +# Ruby +Gemfile.lock + +# Temp Files +*.orig +*~ +.*.swp +.*.swo +*.tmp +*.bak + +# Editors (IntelliJ / Eclipse) +*/.idea +.idea +*/.classpath +.classpath +*/.project +.project +*/.settings +.settings +*.iml + +# OS X +.DS_Store + +# Logging +*.log + +# Docs +autodoc/ + +# Builds +out/ + +.cpcache + +.kein diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt new file mode 100644 index 0000000..0c11040 --- /dev/null +++ b/COPYRIGHT.txt @@ -0,0 +1 @@ +All data and code in this repository is copyright © 2014-2017 Cognitect, Inc. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f735bee --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' + from a Contributor if it was added to the Program by such Contributor + itself or anyone acting on such Contributor's behalf. Contributions do not + include additions to the Program which: (i) are separate modules of + software distributed in conjunction with the Program under their own + license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly + perform, distribute and sublicense the Contribution of such Contributor, + if any, and such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of + the Contribution and the Program if, at the time the Contribution is + added by the Contributor, such addition of the Contribution causes such + combination to be covered by the Licensed Patents. The patent license + shall not apply to any other combinations which include the Contribution. + No hardware per se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses + to its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other + entity based on infringement of intellectual property rights or + otherwise. As a condition to exercising the rights and licenses granted + hereunder, each Recipient hereby assumes sole responsibility to secure + any other intellectual property rights needed, if any. For example, if a + third party patent license is required to allow Recipient to distribute + the Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained + within the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, +if a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits and +other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such Commercial +Contributor in connection with its distribution of the Program in a commercial +product offering. The obligations in this section do not apply to any claims +or Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: +a) promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such claim at +its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to the +risks and costs of program errors, compliance with applicable laws, damage to +or loss of data, programs or equipment, and unavailability or interruption of +operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of the +Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in the +Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c2d80d --- /dev/null +++ b/README.md @@ -0,0 +1,245 @@ +[![CircleCI](https://circleci.com/gh/cognitect-labs/vase.svg?style=svg&circle-token=21b84b7aea75483821d3852de6c5d9930e85720a)](https://circleci.com/gh/cognitect-labs/vase) + +[![Clojars Project](https://img.shields.io/clojars/v/com.cognitect/pedestal.vase.svg)](https://clojars.org/com.cognitect/pedestal.vase) + +# Vase: Data-driven microservices + +This system provides a data-driven and extensible way to describe and +run HTTP APIs. Building blocks make it very easy to use Datomic as a +durable database. Other databases can be added via an extension mechanism. + +## Major Changes Since v0.9 + +We learned from using Vase on real-world projects and from hearing +feedback in our user community. People like the promise of Vase but +found it hard to use. + +This release aims to make Vase much easier for common cases: + +1. A new input format called + [Fern](https://github.com/cognitect-labs/fern) lets us produce much + better error messages when something goes wrong. +2. Step-by-step examples show how to grow an API from scratch + rather than relying on the Leiningen template. +3. A new public API allows applications to skip the files altogether + and directly feed Vase with data structures to describe the + application. You can produce those however you like, from code, + files, values in a database... whatever. +4. Datomic has been "demoted" by one namespace level so we can add + support for other external integrations. Instead of `vase/query`, + for example, you'll now use `vase.datomic/query`. + +## Releases and Dependency Information + +If you would like to use the latest developments in Vase, you will need to +clone this repository and install it locally: + +``` +git clone https://github.com/cognitect-labs/vase +cd vase +lein install +``` + +Stable versions are currently deployed to the Clojars repository. + +[Leiningen](https://github.com/technomancy/leiningen) dependency information: + +```clj + [com.cognitect/pedestal.vase "0.9.3"] +``` + +[Maven](http://maven.apache.org/) dependency information: + +```xml + + com.cognitect + pedestal.vase + 0.9.3 + +``` + +## Before you get started + +Vase is built on top of [Pedestal](http://pedestal.io/) +and [Datomic](http://www.datomic.com/). While you don't need to be a +Pedestal or Datomic expert to use Vase, a little introductory material +goes a long way. Newcomers to either will find these resources especially helpful. + +### Pedestal + +[Pedestal](http://pedestal.io/index#what-is-pedestal) is a collection +of libraries for building services and applications. The core +components of Pedestal +are [Interceptors](http://pedestal.io/reference/interceptors), +the [Context Map](http://pedestal.io/reference/context-map) and the +[Interceptor Chain Provider](http://pedestal.io/reference/chain-providers). + +Interceptors implement functionality and the Context Map controls how +Pedestal behaves (i.e., when interceptor chain execution terminates, +going async, etc...). The Interceptor Chain Provider connects the +interceptor chain to a given platform, creates an initial context and +executes the interceptor chain against it. Each interceptor has access +to the context map during execution which means that interceptors can +_alter_ how Pedestal behaves. What about routes? + +Routes are data structures that relate request paths to +interceptors. After expansion, They are consumed by a Router which is +implemented as an interceptor. When a matching route is found for a +given request, the interceptor(s) it relates to are enqueued on the +interceptor chain. + +Pedestal ships with support for the Jetty, Tomcat and Immutant +platforms. Although these are all servlet-based platforms, interceptor +providers can be implemented for other platforms. + +It should come as no surprise that +Pedestal [interceptors](http://pedestal.io/reference/interceptors) are +a crucial part of Vase so it is helpful to understand what they are +and how they work. + +As you dive into more advanced Vase usage scenarios, you'll benefit +from a deeper understanding of Pedestal. Here's where you should look +for more information: + +- [Pedestal Docs](http://pedestal.io): The Pedestal docs site is a good launching point. +- [Pedestal Samples](http://pedestal.io/samples/index): A collection of samples demonstrating Pedestal's capabilities. +- [Pedestal Repository](https://github.com/pedestal/pedestal): For those who like to dig into the source. + +### Datomic + +Datomic is a database +of [facts](http://docs.datomic.com/query.html#database-of-facts) and +Vase uses it as its backend store. You will immediately be confronted +by three Datomic concepts as you work with Vase: schema, query and +transaction. Of the three, Datomic queries offer the most variety and, +possibly, confusion. Datomic uses a declarative query language called +Datomic Datalog for +queries. [Learn Datalog Today](http://www.learndatalogtoday.org/) will +help you get up to speed with it. + +The Datomic docs [site](http://docs.datomic.com/index.html) has +in-depth resources +covering +[schema](http://docs.datomic.com/schema.html), +[query](http://docs.datomic.com/query.html), +[transactions](http://docs.datomic.com/transactions.html) and +more. These are good resources to dive into as you move to more +advanced Vase usage scenarios. + +# Getting Started + +Your path to get running depends on what you need to do: + +1. Just build an API for CRUD operations: Run Vase standalone. +2. Integrate with hand-written routes: Add Vase to an existing + Pedestal project. +3. Use Vase in a new project, for more than just CRUD: Use the template +3. Extend Vase with new actions: Create Actions. + +## Prerequisites + +By default, Vase uses an in-memory Datomic database, using the +[publicly available +Datomic-free](https://clojars.org/com.datomic/datomic-free) version +located in Clojars. + +## Run Vase Standalone + +If you just want to run an API that does CRUD (create, read, update, +delete) operations on a database, then you can run Vase +directly. Download the [latest uberjar +JAR](https://clojars.org/com.cognitect/pedestal.vase) from Clojars and +use it with `java -jar`. + +``` +java -jar vase-standalone.jar my-service.fern +``` + +This path does assume you're using Fern for your input syntax. Vase +will look for the top-level key `vase/service` in your Fern +environment. + +## Use the template + +If you want to do more than CRUD, you will need a project. This +repository includes a +Leiningen [template](./template) for getting started. Look at the +[template's README](./template/README.md) for local/developer setup, +otherwise + +`lein new vase my-service` + +Look at `my-service/src/ + +## Adding Vase to an Existing Pedestal project + +Vase turns API descriptions into Pedestal routes. The API descriptions +specify actions for routes, these are turned into interceptors in the +Pedestal routes. This is done by `vase/routes`. + +`vase/routes` takes an API base URL and an API specification. The routes it +returns can be used directly by Pedestal like this: + +```clj +(require '[io.pedestal.http.route.definition.table :as table]) +(require '[com.cognitect.vase])) + +(defn make-master-routes + [spec] + (table/table-routes + {} + (vase/routes "/api" spec))) +``` + +The routes that `vase/routes` returns can also be combined with +hand-written Pedestal routes by concatenating the input to +`table/table-routes`: + +```clj +(require '[io.pedestal.http.route.definition.table :as table]) +(require '[com.cognitect.vase])) + +(defn make-master-routes + [spec] + (table/table-routes + {} + (concat + [["/hello" :get [hello]]] + (vase/routes "/api" spec)))) +``` + + +## Documentation + +* [Adding Vase to an existing Pedestal service](./docs/adding_vase.md) +* [Building Your First API](./docs/your_first_api.md) +* [Design document](./docs/design.md) +* [Fern Input for Vase](./docs/vase_with_fern.md) - A new way to write + descriptors +* [Migrating EDN to Fern](./docs/migrating_edn_to_fern.md) - Tooling + to help port your descriptors + +## Contributing + +Contributing guidelines for Vase are the same as +[Pedestal](https://github.com/pedestal/pedestal/blob/master/CONTRIBUTING.md). + +For code contribution, you'll need a signed [Cognitect Contributor +Agreement](https://secure.echosign.com/public/hostedForm?formid=8JU33Z7A7JX84U). +For small changes and doc updates, no CA is required. + +If you're proposing a significant change, please open an Issue to +discuss the design of the change before submitting a Pull Request. + +## Support + +Don't hesitate to reach out if you run into issues or have questions! +The Vase community can be found in the +the [pedestal-users](https://groups.google.com/d/forum/pedestal-users) +mailing list or +the [#pedestal](https://clojurians.slack.com/messages/pedestal/) slack +channel. + +## Copyright + +Copyright © 2015-2018 Cognitect, Inc. All rights reserved. diff --git a/config/logback.xml b/config/logback.xml new file mode 100644 index 0000000..5e34dcd --- /dev/null +++ b/config/logback.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + logs/vase-%d{yyyy-MM-dd}.%i.log + + + 64 MB + + + + + true + + + + + + + + %-5level %logger{36} - %msg%n + + + + INFO + + + + + + + + + + + + + + + diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..fcc3599 --- /dev/null +++ b/deps.edn @@ -0,0 +1,12 @@ +{:paths ["src"] + :deps {com.datomic/datomic-pro {:mvn/version "0.9.5930"} + com.datomic/client-cloud {:mvn/version "0.8.78"} + + io.rkn/conformity {:mvn/version "0.5.1"} + + ;; Pedestal + io.pedestal/pedestal.service {:mvn/version "0.5.5"} + io.pedestal/pedestal.jetty {:mvn/version "0.5.5"} + + com.cognitect/fern {:mvn/version "0.1.6"}} + :aliases {:repl {:extra-paths ["test" "test/resources"]}}} diff --git a/dev/edn_fern/convert.clj b/dev/edn_fern/convert.clj new file mode 100644 index 0000000..260de56 --- /dev/null +++ b/dev/edn_fern/convert.clj @@ -0,0 +1,178 @@ +(ns edn-fern.convert + (:require [clojure.string :as str] + [io.pedestal.interceptor :as i]) + (:import [com.cognitect.vase.actions RespondAction RedirectAction + ValidateAction TransactAction QueryAction ConformAction + InterceptAction ValidateAction AttachAction])) + +(defn- kn [k] + (keyword (name k))) + +(defn- affix [n a] + (symbol (str n "-" a))) + +(defn- symbolize [k] + (if (namespace k) + (symbol (str (namespace k)) (name k)) + (symbol (name k)))) + +(defn- pathize [s] + (str "/" (str/replace (str s) #"\." "/"))) + +(defn- routize [s] + (symbol (str/replace (str s) #"/" ".") "routes")) + +(defn- lit + [kind & args] + (list* 'fern/lit kind args)) + +(defn- r [s] + (list `deref s)) + +(def default-spec + {'vase/plugin [] + 'http-options {:io.pedestal.http/port 8080 + :io.pedestal.http/file-path "/public"}}) + +(defmulti convert (fn [ret k v] k)) + +(defmethod convert :default + [ret k v] + ret) + +(defmethod convert :datomic-uri + [ret k v] + (-> ret + (assoc 'connection (lit 'vase.datomic/connection (r 'datomic-uri))) + (assoc 'datomic-uri v))) + +(defmethod convert :activated-apis + [ret k v] + (-> ret + (assoc 'vase/service (lit 'vase/service {:apis (mapv (comp r symbolize) v) + :service-map (r 'http-options)})) + (assoc 'http-options {:io.pedestal.http/port 8080 + :io.pedestal.http/file-path "/public"}))) + +(defmethod convert :descriptor + [ret k v] + (reduce-kv convert ret v)) + +(defmethod convert :vase/specs + [ret k v] + (reduce-kv + (fn [m specname speccode] + (assoc m + (symbolize specname) + speccode)) + ret + v)) + +(defn- attribute? [ent] (contains? ent :db.install/_attribute)) + +(defn- convert-attribute + [{:keys [db/doc db/cardinality db/ident db/valueType db/isComponent db/noHistory db/unique db/index db/fulltext] :as attr}] + (-> [ident (kn cardinality) (kn valueType)] + (into + (keep identity + [(when (= :db.unique/value unique) + :unique) + (when (= :db.unique/identity unique) + :identity) + (when index + :index) + (when fulltext + :fulltext) + (when isComponent + :component) + (when noHistory + :no-history)])) + (conj (or doc "")))) + +(defn- convert-schema-part + [ents] + (if (every? attribute? ents) + (apply lit 'vase.datomic/attributes (map convert-attribute ents)) + (apply lit 'vase.datomic/tx ents))) + +(defn- convert-schema + [ret schemaname {:keys [vase.norm/requires vase.norm/txes]}] + (let [schemaname (symbolize schemaname) + named-parts (map-indexed (fn [idx part] + {(affix schemaname idx) + (convert-schema-part part)}) txes)] + (apply merge ret named-parts))) + +(defmethod convert :vase/norms + [ret k v] + (reduce-kv convert-schema ret v)) + +(defn- remove-nils + [m] + (into {} (remove (comp nil? val) m))) + +(def ^:private fern-literals-for-action-types + {RespondAction ['vase/respond [:params :edn-coerce :body :status :headers]] + RedirectAction ['vase/redirect [:params :body :status :headers :url]] + ValidateAction ['vase/validate [:params :headers :spec :request-params-path]] + ConformAction ['vase/conform [:from :to :spec :explain-to]] + InterceptAction ['vase/intercept [:enter :leave :error]] + AttachAction ['vase/attach [:key :val]] + TransactAction ['vase.datomic/transact [:properties :db-op :headers :to]] + QueryAction ['vase.datomic/query [:params :query :edn-coerce :constants :headers :to]]}) + +(defn- make-action-lit + [action] + (if (symbol? action) + action + (let [[nm ks] (fern-literals-for-action-types (type action))] + (lit nm (remove-nils (select-keys action ks)))))) + +(defn make-action-name + [action] + (if (not (symbol? action)) + (symbolize (:name action)) + action)) + +(defn make-action-ref + [action] + (r (make-action-name action))) + +(defn- convert-single-route + [routesname ret path verbs] + (let [ret (update ret routesname + #(into (or % #{}) + (for [[verb a-or-as] verbs] + [path verb (if (sequential? a-or-as) + (mapv make-action-ref a-or-as) + (make-action-ref a-or-as))])))] + (into ret + (for [[verb a-or-as] verbs + a (if (sequential? a-or-as) a-or-as [a-or-as]) + :when (not (symbol? a))] + {(make-action-name a) (make-action-lit a)})))) + +(defn- convert-routes + [ret routesname routes] + (reduce + (fn [m [path verbs]] + (convert-single-route routesname m path verbs)) + ret + routes)) + +(defn- convert-api + [ret apiname {:keys [vase.api/routes vase.api/schemas vase.api/forward-headers vase.api/interceptors]}] + (let [apiname (symbolize apiname) + routesname (routize apiname) + ret (assoc ret apiname (lit 'vase/api {:path (pathize apiname) + :expose-api-at (str (pathize apiname) "/api") + :on-request (into [(r 'connection)] + (mapv symbolize interceptors)) + :on-startup (into [(r 'connection)] + (mapv (comp r symbolize) schemas)) + :routes (r routesname)}))] + (convert-routes ret routesname routes))) + +(defmethod convert :vase/apis + [ret k v] + (reduce-kv convert-api ret v)) diff --git a/dev/tools.clj b/dev/tools.clj new file mode 100644 index 0000000..fde713f --- /dev/null +++ b/dev/tools.clj @@ -0,0 +1,28 @@ +(ns tools + (:require [com.cognitect.vase.fern :as fern] + [com.cognitect.vase :as vase] + [fern.easy :as fe] + [fern.printer :as fp] + [datomic.api :as d] + [edn-fern.convert :as c] + [clojure.java.io :as io])) + +(defn edn-spec->fern-spec + [m] + (assert (map? m) "edn-spec->fern-spec works on a map value") + (reduce-kv c/convert {} m)) + +(defn edn-spec->fern-spec-str + [m & {:as opts}] + (fp/pprint-str + (edn-spec->fern-spec m) + (merge + {:print-handlers fe/underef-handlers + :map-delimiter nil + :width 100} + opts))) + +(defn edn-file->fern-file + [e f] + (spit (io/file f) + (edn-spec->fern-spec-str (read-string (slurp e))))) diff --git a/docs/action_literals.md b/docs/action_literals.md new file mode 100644 index 0000000..cad1656 --- /dev/null +++ b/docs/action_literals.md @@ -0,0 +1,127 @@ +# Action Literals + +This page describes the action literals and their parameters. For the +full scoop, you should definitely look at +src/com/cognitect/vase/literals.clj. + +All the literals can be chained together in a single route, _except_ +for `#vase/respond` and `#vase/redirect`, which end a chain. To chain literals, put them in a vector. + + +## #vase/respond + +Immediately send an HTTP response. This cannot be chained together +with any other literals. + +| Param | Meaning | +|-------------|-----------------------------------------------------------| +| :name | Name of the interceptor. Required. | +| :params | Symbols to bind from `:params`. | +| :headers | Any extra headers to merge with the defaults. | +| :edn-coerce | Symbols that should be parsed as EDN data before binding. | +| :body | An expression that produces the body of the response | +| :status | The HTTP status code to return | + +## #vase/redirect + +Immediately send a 302 redirect response to the client. This cannot be +chained together with any other literals. + +| Param | Meaning | +|----------|-------------------------------------------------------------------------------------------| +| :name | Name of the interceptor. Required. | +| :params | Symbols to bind from `:params`. | +| :headers | Any extra headers to merge with the defaults. | +| :body | The body of the response | +| :status | The HTTP status code to return | +| :url | An expression that produces a URL. This will be the "Location" header sent to the client. | + +## #vase/validate + +Apply specs to the inputs. + +| Param | Meaning | +|----------------------|---------------------------------------------------------------------------------------------------------| +| :name | Name of the interceptor. Required. | +| :params | Symbols to bind from `:params`. | +| :headers | Any extra headers to merge with the defaults. | +| :spec | The spec to apply. Must be registered before this action executes. | +| :request-params-path | Instead of locating parameters directly in the :request, use this path to navigate through nested maps. | + +If validate is the last interceptor _or_ if any of the specs fail, it will +generate a response. + +## #vase/conform + +Apply specs to data on the context, reattaching the conformed data to +the context. + +| Param | Meaning | +|-------------|-------------------------------------------------------------------------------------------------------------------------------------------| +| :name | Name of the interceptor. | +| :from | The context map key where data will be found. | +| :spec | The spec to apply. Must be registered before this action executes. | +| :to | The context map key where the conformed data will be attached. | +| :explain-to | The context map key where explain-data will be attached if the data fails the spec. Defaults to :com.cognitect.vase.actions/explain-data. | + +## #vase/query + +Run a datalog query using the [Datomic API](https://docs.datomic.com/on-prem/clojure/index.html) +and return the results as JSON. + +| Param | Meaning | +|-------------|------------------------------------------------------------------------------------------------------------------ | +| :name | Name of the interceptor. Required. | +| :params | Symbols to bind from `:params`. | +| :headers | Any extra headers to merge with the defaults. | +| :edn-coerce | Symbols that should be parsed as EDN data before binding. | +| :query | A datalog query expression. Has access to symbols from `params`, in order, in the `:in` clause. | +| :constants | Additional values to be passed to the query, following all the parameters. | +| :to | The context map key where the query results will be attached. Defaults to :com.cognitect.vase.actions/query-data | + +If this is the last interceptor in the chain, it generates a response. + +If this is not the last interceptor in the chain, it attaches the +query results to the context map at the `:to` key + +## #vase.datomic/query + +Same as `#vase/query`. + +## #vase.datomic.cloud/query + +Like `#vase.datomic/query` but runs a datalog query using the [Datomic Cloud Client API](https://docs.datomic.com/client-api/datomic.client.api.html). + + +## #vase/transact + +Execute a Datomic transaction [DatomicAPI](https://docs.datomic.com/on-prem/clojure/index.html). +Return the results (the tx-result) as JSON. The new database value +will be used for any subsequent datalog queries. + +| Param | Meaning | +|-------------|--------------------------------------------------------------------------------------------------------------------------- | +| :name | Name of the interceptor. Required. | +| :properties | Whitelist of parameters to accept from the client | +| :headers | Any extra headers to merge with the defaults. | +| :db-op | Either :vase/assert-entity or :vase/retract-entity. Defaults to :vase/assert-entity. | +| :to | The context map key where the transaction results will be attached. Defaults to :com.cognitect.vase.actions/transact-data | + +If this is the last interceptor in the chain, it generates a response. + +If this is not the last interceptor in the chain, it attaches the +transaction result to the context map at the `:to` key. + +## #vase.datomic/transact + +Same as `#vase/transact`. + +## #vase.datomic.cloud/transact + +Like `#vase.datomic/transact` but runs a Datomic transaction using the [Datomic Client API](https://docs.datomic.com/client-api/datomic.client.api.html). + +## #vase/intercept + +Define an interceptor, given code in the descriptor file. The form +immediately after the literal is used directly as the interceptor +body. diff --git a/docs/adding_vase.md b/docs/adding_vase.md new file mode 100644 index 0000000..1bbe34a --- /dev/null +++ b/docs/adding_vase.md @@ -0,0 +1,150 @@ +# Adding Vase to a Pedestal project + +## Welcome + +This guide will help you add Vase to an existing Pedestal +project. Vase creates pedestal routes based on an application +description. These routes can be concatenated to your application's +routes. + +## What You Will Learn + +After reading this guide, you will be able to: + +- Add the Vase dependency to a Pedestal service +- Add one or more Vase APIs + +## Guide Assumptions + +The guide assumes you created your Pedestal project with the template: + +``` +lein new pedestal-service my-new-service +``` + +## Step By Step + +### Step 1: Add the Vase dependency + +In your `project.clj`, add the Vase dependency information: + +``` + :dependencies [,,, + [com.cognitect/pedestal.vase "0.9.0-SNAPSHOT"] + ,,, + ] +``` + +### Step 2: Update `service.clj` + +#### Add the new required namespaces to your `service.clj` ns declaration + +```clojure +(ns my-new-service.service + (:require ;... + [com.cognitect.vase])) +``` + +#### Add Vase information to the service map + +```clojure +(def service-map + {:env :prod + + ,,, + + ;; Add these lines + ::route-set routes + ::vase/api-root "/api" + ::vase/spec-resources ["vase-api-descriptor.edn"] + ,,, + } +``` + +If you want more than one descriptor file, just add their names to the +`::vase/spec-resources` vector. + +### Step 3: Update `server.clj` + +Add this function to register specs and transact schema: + +```clojure +(defn activate-vase + ([base-routes api-root spec-paths] + (activate-vase base-routes api-root spec-paths vase/load-edn-resource)) + ([base-routes api-root spec-paths vase-load-fn] + (let [vase-specs (mapv vase-load-fn spec-paths)] + (when (seq vase-specs) + (vase/ensure-schema vase-specs) + (vase/specs vase-specs)) + {::routes (if (empty? vase-specs) + base-routes + (into base-routes (vase/routes api-root vase-specs))) + ::specs vase-specs}))) +``` + +Add this function to update a Pedestal service map with Vase info: + +```clojure +(defn vase-service + "Optionally given a default service map and any number of string paths + to Vase API Specifications, + Return a Pedestal Service Map with all Vase APIs parsed, ensured, and activated." + ([] + (vase-service service/service)) + ([service-map] + (vase-service service-map vase/load-edn-resource)) + ([service-map vase-load-fn] + (merge {:env :prod + ::server/routes (::routes (activate-vase + (::service/route-set service-map) + (::vase/api-root service-map) + (::vase/spec-resources service-map) + vase-load-fn))} + service-map))) +``` + +Update `run-dev` to account for loading resources from the filesystem instead of the classpath: + +```clojure +(defn run-dev + "The entry-point for 'lein run-dev'" + [& args] + (println "\nCreating your [DEV] server...") + (-> service/service ;; start with production configuration + (merge {:env :dev + ;; do not block thread that starts web server + ::server/join? false + ;; Routes can be a function that resolve routes, + ;; we can use this to set the routes to be reloadable + ::server/routes #(route/expand-routes + (::routes (activate-vase (deref #'service/routes) + (::vase/api-root service/service) + (mapv (fn [res-str] + (str "resources/" res-str)) + (::vase/spec-resources service/service)) + vase/load-edn-file))) + ;; all origins are allowed in dev mode + ::server/allowed-origins {:creds true :allowed-origins (constantly true)}}) + ;; Wire up interceptor chains + server/default-interceptors + server/dev-interceptors + server/create-server + server/start)) +``` + +### Step 4: Create services + +Your Pedestal application is now Vase-enabled. You can now start +populating `resources/vase-api-descriptor.edn` (or whatever you choose +to call it) with your API definition. + +## Wrapping Up + +You've seen how to: + +- Add Vase to an existing Pedestal service project +- Activate a Vase API in the service map +- Add one or more API descriptors + +The next step is to begin [defining APIs](./your_first_api.md) diff --git a/docs/descriptor_facts_schema.graffle b/docs/descriptor_facts_schema.graffle new file mode 100644 index 0000000000000000000000000000000000000000..bdb50825a7357abdb9f225ac27c4cd80f967bbe3 GIT binary patch literal 4326 zcmVyAN8iL6GpxB4{uJ64|>n{27~?S)D?rl z;l*L^{N#9e(UWxy21lpQd(Us8X!>$6xVyXSTZ*c0`x8YN4$cFADuU?YL{_#Z3-m|P z=(%hrsXf;n`RmBBqu0-VdG$p+yxzAX=T@9p4aE&I$k8kB6tm64Hh34 zMP%EKZAG%mk4eqTLRJu1>ccOu!YGip{95*~?@v5u9QdOf&F_6q zrYOS2wPiQHeFHCg;QNc-H%J+K(Sz6|eS}SJBFHh~oP7VD>%J;~;96n1s-f>XX&(wj zjt^7%#TcXPa3^HL=)n~!<>C&Ki?T}pOL{ePIk|oqSa)f+(vF>;WxFHCcR4VNPw9tb zL__h}_a`gx$ARVDTH#sXjGdG-7DF44Y<6PP5<}-NQKGM%fl?j5JDYg>t~2&ZeT3yc z#?-^9Wy>m-^ntM4*I2S<{9!>VzbIZmw4ze?5P)gBOqx0>PoAjoxKS-*sm zD;Aft4JBny#MrVQas{s~H;m;_N+j7l3&*C}pBK5X9QTnvy|G-k_TGz3r0QFjixJui z7iT`{MPYrM!%cAx2_$_9b5jaSW?~4C1THb_fDH))ee$BmieS!=Knf)Rp>+f%;i?Kk zXrJg6;lFQ1kUX-JCE<6qcPK9g3l*dW2*p_~RGwCUVO_bR+(vCH`F8RxRqrcu&3#+q z#4zJ629AM8luXV1LF0P?XSFwy*=^idQ&F|u_@#Pkv(shrBI8wKwS3Dpn5`hJ!EO_N zix{r>8uPQbj~?%1nJS%@%OJXjmzEbg@rykjLqyP}?c2VkE*Zc`zJ#+Y`Q2dP*|*DU zDQ==G_wpKCUVDywm+E{FiU{B$P=KRj(6x zRx}GN_wuW)eu$j$O(eW)%a%J6@a$Q97a>NyOZyu1uDuHW%Z+^D+qy7FbV-9{K>+$@ z?hm&O0z-p9gCGULOV65!C&s~C9CQo`5HJk|4TTg6a+L1;VAKv6WQRMY8&Qn~jfJ*# zqmFG5xT!&)L6Fgngk*-Gl}&=lJGDvBnvn)V76>EXZb>a7uuBpF0<4jsktpuxL)2+Z-p6Z)oN)&wH1l64smIcWKH(~0k4ckDhaWic&HO#6@tA&c-4!p!s=KY!lF1T_o*bXw79Af zSI5epi40sZx)p3QiXECpAD9NlCPj!E zP;LksBUuqJOD5Z1jtRc)I8KCoAbQIm8l(f+eTNS z{n1)y$>=N-Yx=~7rbKRF4C+C!YmXcS8Wb878N1Weif;CTr#8VR;=$m!JK}*tjRcK^ zw&Pb55z#3;a>9Kpu%MiEM}1=2 zFRfuvR-f8*%5};+&9rw~;~}dr1^z4&do83JOTa*|(IGHs548qE$sN(6n>v9+m)z7$ zyQwuKvXJ;X6T!n?+o2ns@<=f4wbq!(hHlK<$dMcB>vp1;I;AmbAGL-;yHQLr2s-V9 zVA^A?L9wfd?@k$8wXa$Op(?!3nzXZR=yZG^)2?cbjvq_bx4qO%)zPL+yt-;uBHEZx zr$Sd+xnj^%F*P@@xG_x?Wt&&rm>{Uatgv#$u(4veamAGu@uuD1$~|t(hQu~cfASf6zoyWGsrl&!f`t}OQl-YbXsu9K6M6JM>K zolK=AX{^%pW|VU|wAf&rF~R`U3CkYI3Pr$HvO+a* zMp3A(p4$v@Q!b|zn3&1wMfGR)Vw3d=gcur>5GlN|N`nmbVch?PS1lxjK^QaWk*PaI*upDxw3Uw9bIIuo5Wq$MHG|6df+5fl>rY-}8gX zAi5Xf4$#8pkyBM@+4Vz7L_fuaMfG(<|VhO*OcE6C{b zoWfjAs(>j%oxzeyG35-p^vAy6D(&W!28%KbUCZ8+WI5vSaxm3x{{QS*> z*fwD}u z)Q; DT-Yv?1tG zhW0{heQ6h7Xo9E$K-b`)4WJqh?HfQ7ESLuG6c)6bOk*MYEZv^Cp9W7KZqYp;=l~=Q zgp!UFPKCWA+rQCdwl+4l{Toe|X#?-dNZ)+78$Z=(G@05B@>HgFW1FUSsXx;=&IZND zP_<=DH?%SR=QO5QEO~94(hY4&*QWH>2yI{;M{rAzZ2`9GV_QU$LumAwi75fet2UAU z^d@qoP2?*^+shSJO1xObL1rIO3<;AO1Ok^K-*K}g(8vz1*1#x0x9jwU$}uFAc}@} zLA>Y)eT|nLq>rz}NwQz*CQfvIRE?9?;~T0_GS|K;QL=OGOP}khQL>fiy|#oEhU>A? zc8YDTBStkic63;JEi-jEoKeMLSM3 zP%438V9uTc_yeS@Dy!)_yB5(@I;%R zWx^YeQsA4Pv%wn=9^hnG5xC9M-Z*)5&7o=@ja4U}t;eR1SbkQW_$P}QM)=9nb4b0A zS7zs+w8em840J&8&nVCK&yRNvxRfbrG>inkMR*tkR~7n+f5RT*e=5kh(q`WY9YLlN46qxPFCu0@>=pjGJMi31w% z!m@+4VMn8@6cKApou{^VDnA*9J7j!qtI>>K4h8pvCqI`tW_^x!$oe{3RkQw&KOn!s ziR29Kdezr~r<(P55O^AM{%#e09ZIS>e^;EpTXjc=hicC6kXIHrx##f4URm7a*uxup zWpR^B5SzR5aW)TP<4|mza0#>ZCT^?aRA&Wm@HIY}69&T{al#mYA&MA=2ABVUtee!O zTwb|QXzq_0YgGXHm|*}3U%sNpT`=iyOGeHnkXSC$iLANt??Oeg9G{wDPpCa>8eaG- z(5JnL`!I7xB0TZ!FJf7+QvPJ9KRTgvmCC`IX5wTb=hj#(C%8I+biyO!{?N8Ey;x=%NwSPs4Y5>iHA&|)d z!bnBC4l6*1N8 z!^})CDn$U&J;3%5??L(!;gy7*Q z_^rQh{PWE}kB3J$`!{DF0D)JZ{&4N_f&4x?`v5=RT)qA1?#t^3N95uH{qgnlr&F+h z{OM?a_*K&UcJJOD{NCLC6LB5=$ndBorK$dNxz`JFiODLe598|A>=CtuUuQBqb(t zOB#1$BH1d@y7ukJxm`u+iVG{bY>sQ3zw+w+ z$?-=1UHuw+Fbe`X67fi;thA^kA6yt0IJ%!&o&?+fB}1H1uQL0lBn6@)*Y>3SlqL#h zB0~~nmA{OoiX!x)2g&?FXQWaz$qh-~TD4T>bGhOQEvW17XKpG`O9XNnudK%|e{x1y z^=CQWpkjn1G>5Kr^;W1gprFDaQdD{7g7v)^=a#tTPyo3lN=8AQ`UX|7DZ(Z<5#$(g zPAbOav+qwzsuQJ{aw3Nm5QYqOY+bG!s?0~rot5UZcK=ubv0o}RyTy+HE}`aC`miKitL;K0pcq05%F_LjV8( literal 0 HcmV?d00001 diff --git a/docs/descriptor_facts_schema.png b/docs/descriptor_facts_schema.png new file mode 100644 index 0000000000000000000000000000000000000000..c0db83cf30de1a245c967a0476b801ce07c80200 GIT binary patch literal 117048 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 literal 0 HcmV?d00001 diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000..f954ec8 --- /dev/null +++ b/docs/design.md @@ -0,0 +1,464 @@ +# Design Documentation and Vision + +## Introduction + +This document describes the design of the data-driven microservice +container "Vase." + +It describes the service model and describes all operations, +URIs/destinations, and input/output formats. + +This document elaborates on the constraints, trade-offs, formats, and +general architecture of the system. + +**This is a living document, it should evolve along with the system.** + +### Commonly used terms + + * **Core Service** - The main Vase container service, which hosts + other data-described APIs + * **API** - A service contained within the container; a hosted + service within the core service + * **edn** - Also EDN; + [Extensible Data Notation](https://github.com/edn-format/edn); a + data serialization notation, like JSON + +# Vase + +## Motivation + +For many reasons, we are moving into a world of +microservices. We have found that the majority of microservices +contain duplicated, mechanical code. All microservices must perform +similar functions: + + * Define HTTP routes + * Map input bodies and parameters into transaction + * Run queries and format results + * Validate inputs + +In fact, most such services can be described in a data format. We +wanted to take that data format from a static artifact to something +you could actually run. With Vase, we can take a data definition of +what a service should do and turn it into a running service. + +## Design Goals + +Our design objectives are as follows, in priority order: + + 1. Radically shorten the time needed to deliver microservices. + 1. Achieve production quality + 1. To ensure the system can easily evolve and adapt + +## Design Non-goals + +These are the things we have decided not to work towards in Vase. That +doesn't mean we expect to prevent them. Indeed, some may "fall out" of +our implementation. However, we are not considering them when making +trade offs. + + 1. Solving the general problem of mapping data into databases. + 2. Supporting arbitrary input formats. + 3. Supporting arbitrary output formats. + 4. Accommodating legacy schemata from existing applications. + 5. Databases other than Datomic. + 6. Web servers other than Pedestal. + 7. Java API + +## Prototyping or Production? + +Early prototypes of Vase allowed APIs to be submitted at runtime. That +is, Vase itself had an API to create APIs. We have removed that +feature for the present, for three reasons: + + 1. While it is very helpful for prototyping, that approach + conflicts with release management practices for production level + software. + 2. We only want to commit to a limited "surface area" at this + time. We can re-add dynamic APIs in the future, but if we + release them now we're stuck with them forever. + 3. We did not have a good means to synchronize changes to APIs + across multiple instances of Vase. This also reflects it's + origin as a rapid prototyping tool. + + +# Design + +Vase is an on-demand container service, using Pedestal and Datomic, +that allows us to write concise descriptions of data formats and API +definitions, then have a generic service bring that description to life. + +Vase will be delivered as a library that can be incorporated into an +existing Pedestal service. + +Vase will also deliver an application template that can create an +entire service from scratch. + +## On-demand Container Service + +**Vase** is an API Container. It allows us to create microservice APIs +from just a description of the routes, data model, and actions to +execute. This description is itself stored as data. The Vase runtime +should not need modification for the majority of "CRUD" services. + +## Using Pedestal + +Vase is an addition to Pedestal. Vase creates routes that Pedestal +then serves. The interface between Vase and Pedestal is just data. + +The main service is purely additive to Pedestal. Developers can +incorporate Vase in an existing Pedestal service. Likewise, developers +can use Vase and add route written in a traditional means. + +Vase will not provide every capability needed to build an +application. Its purpose is fast delivery of simple services. + +Vase allows "mixing in" features via Pedestal interceptors. A Vase +service can have arbitrary developer-provided interceptors in its +routes. + +## Using Datomic + +Vase maps its data to Datomic. Schema for the data model is expressed +in terms of Datomic attributes. Transactions return Datomic +tx-results. Queries are written in Datomic's datalog format. Queries +will support Datomic's "pull" syntax as well as "tuple" syntax. + +We have no plan to extend Vase for other databases at this time. + +## Concise Description + +Vase uses Clojure data structures, written in EDN and stored in files +to describe its data models, specifications, and APIs. + +One Vase instance can use multiple description files. + +One Vase instance can support multiple APIs. + +Complete specifications for a Vase description can be found in +`src/com/cognitect/vase/spec.clj`. +`:com.cognitect.vase.spec/spec` is the root of a description file. + +Description files are nested maps. At even-numbered levels of the map, +the keys are predefined by Vase. At odd-numbered levels, excluding the +first level, keys are user-provided names of APIs, schemas, and specs. + +Example: + +```clojure +{:activated-apis [ ,,, ] + :datomic-uri " ,,, " + :descriptor + {:vase/norms {:user.provided/name {:vase.norm/txes [ ,,, ] } } + :vase/apis {:user.provided/api {:vase.api/routes { ,,, } } } + :vase/specs { ,,, }}} +``` + +## Validation Using Clojure.spec + +The APIs that Vase hosts are all described in data. Vase functions use +clojure.spec to ensure the integrity and correctness of API +descriptions. + +## State and data management + +The core service and APIs are largely stateless - request identity is +only maintained for that given request. The core service and the +hosted APIs do not remember anything about previous requests (beyond +what was transacted into persistent/durable data). Data is only +transacted into persistent storage if it is submitted. Routes that +accept POST submissions are configured per API. + +API-specific data that is persisted in the database is owned by that +API. An API may only reference data that it owns. Consumers of the +API (other services, mobile apps, etc), may choose to integrate data +from various APIs. Unifying data across APIs is a design challenge +for API designers. There is nothing within Vase that helps or hinders +unifying data. + +## Reader Literals + +The primary input to Vase is an EDN file. Clojure's reader literals +offer a concise way to extend the input format while maintaining +uniform syntax. Vase makes use of reader literals for: + + * Actions (i.e., interceptors) on routes + * Attribute definitions in schemas + +# Norms + +"Norms" refer to fragments of schema that must exist for an API to +function. + +## Norm Identity + +The service can apply any number of norms. Each one is uniquely +identified by a namespaced keyword. + +## Norm Transactions + +A norm comprises: + + * `:vase.norm/txes` - A sequence of transaction data (tx-data) that + will be transacted in Datomic at initialization time. Each tx-data + is a vector of transactions (see below). + * `:vase.norm/requires` - A collection of norm names that this schema + requires. Vase ensures that all the required schemas are transacted + before this one. + +Norms are idempotent, so Vase transacts them at each startup. + +The norms are captured as a map, with namespaced-keyword keys, and map +values that hold the schema transactions, or `txes`. For example: + +```clojure +{:vase/norms + {:example-app/base-schema + {:vase.norm/txes [[{:db/id #db/id [:db.part/db] + :db/ident :something/title + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one + :db/index false + :db/doc "A simple title" + :db.install/_attribute :db.part/db} + ,,,]]}} + :vase/apis + {:example-app/v1 + {:vase.api/routes [[ ... ]] + :vase.api/schemas [:example-app/base-schema ...]}}} +``` + +Each API specifies which of these schema segments it uses, captured as +a vector of keywords (the norm keys). This ensures the data is +modeled appropriately per API version when queries run or data is +transacted. + +## Schema-tx Reader Literal + +Tx-data can also be described using a reader literal for shorthand. +The `#vase/schema-tx` literal takes a vector of vectors. The inner +vector is interpreted as follows: + + * The :db/ident of the attribute + * The cardinality of the attribute, written as `:one` or `:many` + * The type of the attribute, written as a simple keyword (e.g., for + `:db.valueType/string`, use `:string`.) + * An optional qualifier. One of `:unique`, `:identity`, `:index`, `:fulltext`, + or `:component` + * A doc string + +The optional qualifiers describe attributes that contain `:unique` +values, that the DB should `:index`, or that allow `:fulltext` search. +`:fulltext` also implies `:index`. You can also say an entity's unique +`:identity` can be determined by an attribute. You may also state that +a ref-attribute is a `:component` of another entity. + +The schema above using the short form would look like: + +```clojure +{:vase/norms + {:example-app/base-schema + {:vase.norm/txes [#vase/schema-tx [[:something/title :one :string "A simple title"]]]}} + + :vase/apis + {:example-app/api + {:vase.api/routes [[ ... ]] + :vase.api/schemas [:example-app/base-schema ...]}}} +``` + +# Specs + +Specs appear under the `:vase/specs` key. The value of this key is a +map of spec name to spec. + +Specs are identical to those that would be written in Clojure source code. + +For example, the following might be found in code: + +```clojure +(s/def :example.app.v1/age #(> % 21)) +``` + +This would translate into the following spec in a Vase description: + +```clojure +{:vase/specs + {:example.app.v1/age (fn [age] (> age 21))}} +``` + +APIs can apply specs using the `#vase/validate` action. + +# APIs + +## API Identity + +The service hosts any number of external APIs; each uniquely +identified via namespaced keyword. + +## API Roots + +Each API will construct routes beneath a common "root". That root is +external to the API and should not appear anywhere within the API +description or code. + +## API description + +APIs are defined under the `:vase/apis` key. The value of this key is +a map of API names to definitions. + +API names are namespaced keywords. The API name becomes part of its +routes' URLs as follows: + + * Every '.' in the keyword is replaced with a '/'. + * The '/' between the namespace and name is left in place. + * Non URL characters are URL-encoded. + +The definition of an API has the following top-level keys: + + * `:vase.api/routes` - A route map (see below) + * `:vase.api/schemas` - A collection of schema names. When this API + is activated, these schemas will be transacted into the database. + * `:vase.api/forwarded-headers` - A collection of strings. Any + request headers matching these strings are passed through into the + response headers. + * `:vase.api/interceptors` - A collection of interceptors that will + be prepended to the action interceptors for every route. + +An API describes its routes and required schema in a hashmap. See the +example below: + +```clojure +{:vase/apis + {:example.app/v1 + {:vase.api/schemas [:example/base-schema ,,,] + :vase.api/routes { ,,, }}} +``` + +In the example above, we've described a new API called, +`:example.app/v1`. It also specifies the norms that this API +requires. + +## Input and Output formats + +All hosted API operations use _JSON_ as the data exchange format. + +Each operation defines its own format. + +## Response status codes and payload navigation + +All HTTP operations return an HTTP status code. The hosted APIs use +the following HTTP status codes: + + * 200 - Success. The response body should have a value. + * 302 - Redirect returned from an obsoleted route. The response body + will probably be empty. A "Location" header contains the target URL. + * 400 - The request was rejected. There are syntactic/semantic + errors in the input (malformed, data missing, invalid values, not + existing ids, ...). The errors body should have a value and the + response body normally is empty but might contain additional info + (e.g. partial input processing, ...). + * 404 - The requested resource is not found. The response body might + contain a description for the "not found" error. + * 500 - A system error. The response body might contain a description + of the internal error/errors that occurred. + +### Routing + +Routing is described in a hashmap, keyed by `:vase.api/routes`, whose +value is a vector of nested route-verb pairs. See the example below: + +```clojure +{:example.app/v1 + {:vase.api/routes + {"/home" {:get #vase/respond {:name :example.app.v1/home + :body "Home page"}} + "/about" {:get #vase/redirect {:name :example.app.v1/about + :url "http://www.google.com"}} + "/check/:age" {:post #vase/validate {:name :example.app.v1/age-check + :spec :example.app.v1/age}}}}} +``` + +This configuration would produce the URLs: + + * _/api/example/app/v1/home_ + * _/api/example/app/v1/about_ + * _/api/example/app/v1/check_ + +### Action Map + +The action map describes the allowed HTTP verbs for a route and what +actions to invoke for each. Actions are described using reader +literals. + +Each route has exactly one action map. Each action map can have keys +from `#{:get :put :post :delete :head :options :any}` (these are the +standard HTTP verbs from Pedestal routes.) + +The value for each verb is either a single action written as a reader +literal or a vector of actions. When the value is a vector of actions, +they will be invoked in the same order as written. + +### Actions + +Here are the available actions: + + * `#vase/respond` - Return a static response, optionally setting + headers and the HTTP status code + * `#vase/redirect` - Redirect the request with an HTTP 302 status; + You can optionally set 303 status and additional headers + * `#vase/validate` - Validate a POST body or query string data + * `#vase/query` - Consume a POST body, URL parameters, or query + string data and run a Datomic query + * `#vase/transact` - Consume a POST body, URL parameters, or query + string data, and transact data into the DB + * `#vase/intercept` - Execute an interceptor written directly in the + EDN description file. + +### Action-maps + +Action-maps are hashmaps that contain Action-specific data. All action-maps +require a `:name` for the given action, a keyword. This name is used in logging and URL +generation, and thus should be a namespaced keyword. + +See documentation for the [action literals](./action_literals.md) for +the details of their keys and interpretation. + +# Operational Attributes + +## Failure and reliability + +The core service may be scaled horizontally for availability. + +APIs defined in the core service access Datomic, so their availability +is constrained to that of the underlying Datomic instance. + +Neither the core service nor APIs defined in it can make outcalls to +third parties. + +## Authorization and external requests + +The core service has no authorization or authentication mechanisms. + +Consuming applications may supply interceptors to be placed on every +Vase route. This allows an application to provide authentication and +authorization separately from the Vase API. + +## Initialization + +Vase services have some initialization: + + * Registering Clojure specs. + * Transacting Schema into Datomic. + +## Monitoring and logging + +Vase will use Pedestal's logging facilities. No additional logging, +monitoring, or reporting mechanisms are currently in place for the +core service. + + +### Norms and schemas + +An API uses a top-level key, `:vase/norms` to specify all +acceptable/avaible API schema datoms. These are called *norms* +because they're transacted with Datomic in an idempotent manner. diff --git a/docs/migrating_edn_to_fern.md b/docs/migrating_edn_to_fern.md new file mode 100644 index 0000000..47fd68c --- /dev/null +++ b/docs/migrating_edn_to_fern.md @@ -0,0 +1,82 @@ +# Migrating EDN to Fern + +EDN is just data. Fern is just data. There's no reason a human should +have to do mechanical conversions. + +The `dev` directory includes a `tools.clj` file with a conversion +utility. + +To load up your EDN file and run it through the converter: + +```clojure +(use 'tools) +(in-ns 'tools) +(edn-file->fern-file "old-file.edn" "new-file.fern") +``` + +Feel free to dig into tools.clj to see how you can use the conversion +machinery. + +# After Conversion + +Mechanical translation can take care of getting the meaning across, but it can't format things in your preferred style. You'll want to look at two things in your Fern output: + +1. Order of definitions +2. Linebreaks and alignments + +## Order of Definitions + +The Fern output will come out in roughly the order that the EDN map +was enumerated. That bears little resemblance to either the input +format or any sensible reading of the definitions. You'll want to +spend some time making the new Fern file readable to other +human. Depending on your taste, you might move the `vase/service` +definition to the top, then chain down the page. Or vice versa, with +the biggest aggregates toward the bottom. + +Either way, this is something you'll need to do by hand. + +## Linebreaks and Alignment + +Sadly, the pretty-printer has very different opinions about where to +put line breaks. You'll probably want to adjust the spacing yourself. + +For example, running the conversion on +`test/resources/test_descriptor.edn` gives this for a part of the +output: + +```clojure + example/user-schema-0 (fern/lit + vase.datomic/attributes + [:user/userId :one :long :identity "A Users unique identifier"] + [:user/userEmail :one :string :unique "The users email"] + [:user/userBio :one :string :index :fulltext "A short blurb about the user"]) + example/v1 (fern/lit + vase/api + {:expose-api-at "/example/v1/api" + :on-request [@connection] + :on-startup [@connection @example/user-schema @example/loan-schema] + :path "/example/v1" + :routes @example.v1/routes}) +``` + +I find that hard to read, thanks to the irregular indentation. I +prefer to put `fern/lit` and the literal type on the same line, and +line up all the right-hand sides. Like this: + +```clojure + example/user-schema-0 (fern/lit vase.datomic/attributes + [:user/userId :one :long :identity "A Users unique identifier"] + [:user/userEmail :one :string :unique "The users email"] + [:user/userBio :one :string :index :fulltext "A short blurb about the user"]) + example/v1 (fern/lit vase/api + {:expose-api-at "/example/v1/api" + :on-request [@connection] + :on-startup [@connection @example/user-schema @example/loan-schema] + :path "/example/v1" + :routes @example.v1/routes}) +``` + +(For Emacs users, "C-c C" in Clojure mode will do all the alignment +for you. But you still have to remove line breaks before it works +nicely.) diff --git a/docs/vase_with_fern.md b/docs/vase_with_fern.md new file mode 100644 index 0000000..3a6ae29 --- /dev/null +++ b/docs/vase_with_fern.md @@ -0,0 +1,283 @@ +# Rationale + +[Fern](https://github.com/cognitect-labs/fern) is an alternative way +to write Vase descriptors. It does basically the same things as EDN, +but with a couple of changes to make it easier for humans to write the +descriptors. + +First, it "flattens out" the deeply nested structure from the EDN +descriptors. Instead of nesting, Fern lets you incorporate data by +reference. That lets you remove some duplication, but more importantly +it means you don't have to keep track of where you are in the +descriptor nearly as much. + +Second, the EDN files have a mix of "magic" keys (`:vase/norms`, +`:vase/apis`, `:vase/specs`) and user-defined keys (for API names, +Spec names, etc.) Fern changes that model quite a bit. There's only +one reserved key name: `vase/service`. After that you get to define +all the keys. + +# Sample + +So what does a Fern version of a Vase descriptor look like? Here's a +"Hello, World" sample: + +``` +{vase/service (fern/lit vase/service + {:apis [@hello] + :service-map @http-options}) + + http-options {:io.pedestal.http/port 8080} + + hello (fern/lit vase/api + {:path "/example/v1" + :routes @hello-routes}) + + hello-routes #{["/hello" :get @hello-response]} + + hello-response (fern/lit vase/respond + {:body "Hello world"}) +} +``` + +Vase uses this by "pulling on" the `vase/service` key. You can +[read more](https://github.com/cognitect-labs/fern/#usage) about how +Fern evaluation works. The short version is that `fern/lit` is kind of +like a Clojure reader literal, but Fern decides when to dereference +symbols so we can enhance the environment and produce nicer errors +when something goes wrong. + +The `vase/service` literal has one argument, which is a map with +`:apis` and `:service-map`. See how the `:apis` key has `[@hello]` as +a value? When Fern evaluates that literal, it treats `@hello` as a +reference to the value of `hello` at the top level. So Fern follows +that reference to evaluate the `vase/api` literal. Evaluation follows +through to `@hello-routes` and eventually reaches the `vase/respond` +literal. + +If you take a closer look at `http-options` and `hello-routes`, you +might notice that these are just ordinary Clojure data +structures. That's one of the nice things about Fern. + +# Fern lits + +Fern lets us define new literals by adding `defmethod`s to any +namespace. Vase provides a set of Fern literals out of the box. All +the normal Vase reader literals have equivalent Fern forms. But Vase +also adds some new ones like the `vase/service` in the "Hello World" +sample. + +| Literal | Purpose | +|---------|---------| +| vase/plugins | Namespaces to load before evaluation. | +| vase/service | Define a service. | +| vase/api | Define an API. | +| vase/respond | Define an interceptor with the respond action. | +| vase/redirect | Define an interceptor with the redirect action. | +| vase/validate | Define an interceptor with the validate action. | +| vase/conform | Define an interceptor with the conform action. | +| vase.datomic/connection | Connect to a Datomic database _and_ act as an interceptor to attach the connection to a request. | +| vase.datomic/query | Define an interceptor with the Datomic query action. | +| vase.datomic/transact | Define an interceptor with the Datomic transaction action. | +| vase.datomic/attributes | Define an interceptor that creates Datomic schema from short vector form. | +| vase.datomic/tx | Define an interceptor that executes an arbitrary Datomic transaction. | +| vase.datomic.cloud/connect | Connect to a Datomic Cloud database _and_ act as an interceptor to attach the connection to a request. | +| vase.datomic.cloud/query | Define an interceptor to query a Datomic Cloud database. | +| vase.datomic.cloud/transact | Define an interceptor to transact request bodies as Datomic Cloud entities. | +| vase.datomic.cloud/attributes | Defing an interceptor that creates Datomic Cloud schema from short vector form. | +| vase.datomic.cloud/tx | Define an interceptor that executes an arbitrary Datomic Cloud transaction. | + + +# `:on-request` and `:on-startup` + +With the EDN format, Vase forces certain things to happen on your +behalf. When it starts up, it looks for schema definitions and loads +those into Datomic. It also puts an interceptor into every route that +will attach a Datomic connection to the [request map](http://pedestal.io/reference/request-map). + +When you use Fern, that magic is under your control. A `vase/api` has +two keys that let you define what it needs to do at startup time and +what needs to happen on every request. These are cleverly called +`:on-startup` and `:on-request`. The value for each of them should be +a vector of interceptors. + +Here is an example of an API that uses Datomic: + +``` + example/v1 (fern/lit vase/api + {:on-startup [@connection @base-attributes @user-attributes @loan-attributes @sample-users] + :on-request [@connection] + :routes @v1/routes}) + + connection (fern/lit vase.datomic/connection @datomic-uri) + datomic-uri "datomic:mem://example" +``` + +You can find the whole file in the source repo under `test/resources/test_descriptor.fern` + +Notice that `:on-startup` has a reference to `connection`, which is a +`vase.datomic/connection`. At startup time, that literal serves to +connect to (and optionally create) a database. + +On request, the connection attaches the connection and current +database value to the request, for use by later interceptors. All the +`:on-request` interceptors get run before Vase dispatches to a route. + +# Datomic schema + +Notice how the `:on-startup` from our last example vector has some +other references in it? Those are how we transact schema. Instead of +using `:vase/norms` as a top-level key, we use +interceptors. (Interceptors everywhere!) A couple of these look like: + +``` + base-attributes (fern/lit vase.datomic/tx + {:db/ident :company/name + :db/unique :db.unique/value + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one}) + + user-attributes (fern/lit vase.datomic/attributes + [:user/userId :one :long :identity "A Users unique identifier"] + [:user/userEmail :one :string :unique "The users email"] + [:user/userBio :one :string :fulltext "A short blurb about the user"]) +``` + +There are two different things going on here. `vase.datomic/tx` says +"transact this data." It's arguments are exactly the kind of entities +and datoms that you can read about in the +[Datomic docs](http://docs.datomic.com/transactions.html). + +The second literal is `vase.datomic/attributes`. It uses a kind of +shorthand notation for building schema. This is the same vector format +that the old `#vase/schema-tx` reader literal used. The format is this: + +| Field | Allowed values | Maps to | +|-------|----------------|---------| +| Attribute name | Any keyword | :db/ident | +| Cardinality | :one or :many | :db/cardinality | +| Value type | [Datomic value types](http://docs.datomic.com/schema.html#required-schema-attributes) | :db/valueType | +| Flags | Any of :index, :identity, :unique, :component, :fulltext, :no-history | :db/index true, :db.unique/identity, :db.unique/value, :db/isComponent true, :db/fulltext true, :db/no-history true | +| Docstring | String | :db/doc | + +Multiple flags are allowed. Add as many as you like before the +mandatory docstring. + +Both `vase.datomic/attributes` and `vase.datomic/tx` have exact matches in `vase.datomic.cloud/attributes` and `vase.datomic.cloud/tx`. The cloud versions work exactly the same way as the On-Prem versions. + +# Adding Your Own Literals + +One of our goals with the Fern format was to make Vase easier to +extend. Fern calls these +[plugins](https://github.com/cognitect-labs/fern/#plugins). Any +namespaces in the `vase/plugins` key get loaded before Vase evaluates +the rest of the environment. If those namespaces define new +multimethods for +[`fern/literal`](https://github.com/cognitect-labs/fern/blob/master/src/fern.clj), +then you can freely mix those with Vase's pre-defined literals. + +Suppose you want to aggregate data from an existing API into a Vase +app. There are just two steps to make it happen. + +## Provide a Multimethod for fern/literal + +Your literal can return anything. But if you want to use it in an +`:on-startup`, `:on-request`, or route chain, then it should return an +interceptor. + +``` +(ns example + (:require [fern :as f])) + +(defmethod f/literal 'example/api-call + [_ target-uri] + ;; return an interceptor) +``` + +The first argument to the multimethod will just be the symbol you +define. Since we've already dispatched on that symbol, it's not very +interesting and we ignore it in the function body. + +The remaining arguments are whatever you want them to be. A lot of our +examples above use maps. Most of the built in literals that Vase +provides take maps. But Fern does allow you to use any arguments you +like. + +## Add the Namespace to `vase/plugins` + +Your defmethod has to get evaluated before you create the +service. Vase can do this for you if you add the namespace name to the +`vase/plugins` key like this: + +``` +{vase/plugins [example] + vase/service (fern/lit vase/service ,,,) +} +``` + +# Using Fern version of Vase + +One way to use Fern descriptors is to use the new Vase `-main`. Just +give Vase a path to your descriptor file when you start Clojure. This +is most useful when you don't want to create a whole project +structure. Just use vase.jar directly. + +The other way to use it when you embed Vase in your project. In that +case, the new API functions are +`com.cognitect.vase.fern/load` and +`com.cognitect.vase.fern/prepare-service`. You can use them like this: + +``` + (when-let [prepared-service-map (try + (-> filename + io/file + io/reader + (load) + (prepare-service)) + (catch Throwable t + (fe/print-evaluation-exception t) + nil))] + (try + (a/start-service prepared-service-map) + (catch Throwable t + (fe/print-other-exception t filename)))) +``` + +The nesting gets a little weird because we need to catch exceptions at +two different times. If an exception happens while evaluating the +`vase/service`, then we can use Fern's `print-evaluation-exception` +function, which prints a kind of trace of the chain of values leading +up to the error. On the other hand, if an exception happens while +starting the service, we want to call `print-other-exception` which +produces nicely readable output of ordinary exceptions. + +There's a macro called `try->` that makes this look a little nicer: + +``` + (try-> + filename + io/file + io/reader + load + (:! java.io.IOException ioe (fe/print-other-exception ioe filename)) + + prepare-service + (:! Throwable t (fe/print-evaluation-exception t)) + + api/start-service + (:! Throwable t (fe/print-other-exception t filename))) +``` + +It's purely syntactic sugar. + +Of course, if you're already happy with your error handling, then just let the functions throw: + +``` + (-> + filename + io/file + io/reader + load + prepare-service + api/start-service) +``` diff --git a/docs/your_first_api.md b/docs/your_first_api.md new file mode 100644 index 0000000..fff9ca9 --- /dev/null +++ b/docs/your_first_api.md @@ -0,0 +1,784 @@ +# Building your first API + +## Welcome + +This is a guide to get you started with Vase - the data-driven +microservice container. It will help you write your first descriptor +file. + +## What You Will Learn + +After reading this guide, you will be able to: + +- Write a Vase API descriptor +- Exchange data with Vase services + +## Getting Started + +It may help to understand the [design](./design.md) of the system and +the file formats used before you begin. The most important pieces: + +* Vase descriptors are usually written in [Fern](https://github.com/cognitect-labs/fern). +* Vase descriptors are just Clojure data in memory. The namespace + `com.cognitect.vase.api` has clojure.spec definitions of the inputs + needed. +* Vase services use JSON as their data exchange format. + +It also important to note that the terms *service*, *server*, +*container* are all used interchangeably. + +## Setting Up + +Note that the template project uses Datomic Free and therefore offers +only an in-memory store. Later, you can change to a persistent store. +You'll have get a Datomic license (free versions are available), +launch a Datomic transactor, make minor changes to your project.clj +or build.boot, and update the `:datomic-uri` value in your EDN +descriptor file. This is explained below, as well as +in [the Datomic docs](http://docs.datomic.com). + +Create a new project from the Vase leiningen template: + +``` +lein new vase your-first-api +``` + +## Up and running + +The exact sequence varies a little bit depending on whether you prefer +Leiningen or Boot. + +### With Leiningen + +Start the service by running the following command at a terminal: + + lein repl + +Once the REPL is running, start a dev mode server like this: + +```clojure +(def srv (run-dev)) +``` + +That means your server is up and running, listening on port 8080. + +### With Boot + +Start the service by running the following command at a terminal: + + boot repl + +Once the REPL is running, start a dev mode server like this: + +```clojure +(require 'your-first-api.server) +(in-ns 'your-first-api.server) +(def srv (your-first-api.server/run-dev)) +``` + +### Either way + +There will be a lot of logging, but it should end with something like +this: + +``` +INFO o.e.jetty.server.AbstractConnector - Started ServerConnector@5d04c566{HTTP/1.1,[http/1.1, h2c]}{0.0.0.0:8080} +INFO org.eclipse.jetty.server.Server - Started @85814ms +``` + +That means your service is up and running on port 8080. + +## A Personal Inventory System + +Let's build a system to handle basic user accounts and can track items +they own. + +With this simple goal statement, we can already envision some pieces +of the data model and even some URLs that might appear in the API. + +Our data model needs to handle: + + * Items (with item IDs, names, and descriptions) + * Users (with user IDs, emails, and a list of items they own) + +And we might have URLs that: + + * Fetch all items in the system + * Fetch all users in the system + * Fetch a user given their user ID + * Fetch a user's owned-item list given their user ID + * etc. + +We put the description into a [`.fern`](https://github.com/cognitect-labs/fern) file that our service loads. This description +tells Vase what API a service offers, what schema it defines, and what +specifications the data must follow. + +The Vase template created `resources/your-first-api_service.edn` with +just the minimum you need to get started. Excluding the commented-out +portions, it should look like this: + +```clojure +{vase/service (fern/lit vase/service + {:apis [] + :service-map @http-options}) + http-options {:io.pedestal.http/port 8080}} +``` + +This defines a Vase service, but not a very exciting one. It has no +APIs, no schema, and no specs. It gets better in a bit. + +On the left, we have `vase/service` as a symbol. That is the only +"magic symbol" on the left. Vase knows to look for this special symbol +when it starts up. On the right, we have a [Fern +literal](https://github.com/cognitect-labs/fern#usage). Vase defines a +bunch of Fern literals. This literal is also called `vase/service` and +it means Fern should create a service record based on the contents of the map. + +A `vase/service` record can have the following keys: + +| Key | Type | Meaning | +|-------------------|---------|---------| +| `:apis` | Vector of APIs | Routes for these APIs will be created. Schema used by these APIs will be applied. | +| `:service-map` | Map | These are HTTP parameters for starting a Pedestal service | + +The `:apis` key needs a vector of actual APIs. But defining them +directly in the right-hand side of the file would get awkward and hard +to read. Fern lets us use references to locate the APIs elsewhere in +the file: + +``` +{vase/service (fern/lit vase/service {})} +``` + +## Building the data model + +We often start with a model of the entities a service must manipulate. + +Data models are defined with a schema. You may split your schema up +into logical pieces that map directly to your application domain. In +our example, we'll define one part of the schema for Items, and +another part for Users. + +We can define a schema as a symbol whose right-hand side is a +collection of attributes. The schema name is any symbol you +choose. We recommend using a namespaces for clarity. It's best to +avoid using `vase` as part of your namespace. It's not a reserved +name, but you may confuse parts of your application with parts that +Vase requires. + +To start with, let's make space for our two schemas: + +```clojure + accounts/item (fern/lit vase.datomic/attributes) + accounts/user (fern/lit vase.datomic/attributes) +``` + +This says we're building one schema named `accounts/item` and another +named `accounts/user`. We'll define attributes for Items and Users +inside the Fern literals on the right-hand side. + +```clojure + accounts/item (fern/lit vase.datomic/attributes + [:item/itemId :one :long :identity "The unique identifier for an item"] + [:item/name :one :string "The name of an item"] + [:item/description :one :string "A short description of the item"]]) + + accounts/user (fern/lit vase.datomic/attributes + [:user/userId :one :long :identity "The unique identifier for a user"] + [:user/email :one :string :unique "The email address for a given user"] + [:user/items :many :ref "The collection of items a user owns"]]) +``` + +The `vase.datomic/attributes` literal offers a shorthand notation for +attributes. It takes an arbitrary number of vectors, where each vector +defines an attribute. Each vector defines an attribute +name, its cardinality (`:one` or `:many`), its type, some optional +flags, and a doc string. + +The allowed flags are translated into parts of the attribute +definition for Datomic: + +| Flag | Meaning | Equates to | +|----------------|---------|------------| +| `:unique` | Only one entity in the DB can have this value for this attribute | `:db/unique :db.unique/value` | +| `:identity` | An entity is uniquely identified by this value. Upsert is enabled | `:db/unique :db.unique/identity` | +| `:index` | This attribute should be indexed | `:db/index true` | +| `:fulltext` | This attribute should be searchable as text. (Mildly deprecated.) | `:db/fulltext true` | +| `:component` | The entity referenced by this attribute should be retracted when this one is. (Cascading delete) | `:db/isComponent true` | +| `:no-history` | Do not preserve old values of this attribute | `:db/noHistory true` | + +Even though the short-schema version covers most of the use cases +you'll need, you're always free to use full Datomic +[schema](http://docs.datomic.com/schema.html) definitions like: + +```clojure + accounts/item (fern/lit vase.datomic/tx + {:db/id #db/id[:db.part/db] + :db/ident :item/name + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one + :db/doc "The name of an item"}]] +``` + +Notice that this uses the `vase.datomic/tx` literal, whose body is an +arbitrary number of Datomic transaction elements (either entity maps +like this one or datoms.) + +Fill in the schema parts like this: + +```clojure + :vase/norms + {:accounts/item + {:vase.norm/txes [#vase/schema-tx + [[:item/itemId :one :long :identity "The unique identifier for an item"] + [:item/name :one :string "The name of an item"] + [:item/description :one :string "A short description of the item"]]]} + + :accounts/user + {:vase.norm/requires [:accounts/item] + :vase.norm/txes [#vase/schema-tx + [[:user/userId :one :long :identity "The unique identifier for a user"] + [:user/email :one :string :unique "The email address for a given user"] + [:user/items :many :ref "The collection of items a user owns"]]]}} +``` + +This says the `:accounts/item` schema has three attributes: +`:item/itemId`, `:item/name`, and `:item/description`. The +`:accounts/user` schema has three more attributes: `:user/userId`, +`:user/email`, and `:user/items`. Furthermore, the `:accounts/user` +schema requires that the `:accounts/item` schema must be in place +first. (It doesn't really... there are no definitions in +`:accounts/user` that depend on `:accounts/item`. This is just for +illustration in this tutorial.) + +## Making It Act + +With the data model in place, we can build our API. + +Vase defines routes defined with URL strings, HTTP verbs (get, post, +etc), and action literals. The foundation of Vase's routing is based +on [Pedestal's +capabilities](http://pedestal.io/reference/routing-quick-reference), +but with care given to represent routes in an external data file. + +Fill in `:vase/apis` with this route that gives some information about our API: + +```clojure + :vase/apis + {:accounts/v1 + {:vase.api/routes + {"/about" {:get #vase/respond {:name :accounts.v1/about-response + :body "General User and Item Information"}}}}} +``` + +Routes are defined as nested maps. Each map defines a single route, +and then a map of HTTP verbs to action literals---in this case +`#vase/respond`. Every action literal requires a unique `:name`. Other +keys and their interpretations are defined per literal. Here we see +that `GET /api/accounts/v1/about` will respond with a string. + +These are the action literals: + +| Literal | Purpose | +| ------------------|--------------------------------------------------------| +| `#vase/respond` | Respond with static data, optionally formatted as JSON | +| `#vase/redirect` | Redirect an incoming request to a different URL | +| `#vase/query` | Respond with the results of a database query | +| `#vase/transact` | Add or update data in the database | +| `#vase/validate` | Validate data against specs | +| `#vase/intercept` | Apply a hand-crafted, artisanal interceptor | + +An API usually depends on some amount of schema. In this example, +we've added a dependency from the `:accounts/v1` API to the User +schema. Since the User schema depends on the Item schema, both parts +of schema will be applied to our database. + +```clojure +{:vase/apis + {:accounts/v1 + {:vase.api/routes ,,, ;; skipping the routes for space + :vase.api/schemas [:accounts/user]}}} +``` + +An API can depend on any number of schemas. You should feel free to +grow and evolve your schema by adding new "norms." + +One note: Vase tracks which schemas it has already applied to a +database. Think of each schema like a migration in other database +frameworks: once it's applied to the database you don't change +it. Just add new schemas under `:vase/schemas` and add them to your +APIs' `:vase.api/schemas` dependencies. + +## Activating the API + +You can use curl to test the new URL, but it won't work yet. + +``` +curl http://localhost:8080/api/accounts/v1/about +``` + +If you've been following along in this guide, you got a 404 response +just now. That's because Vase has one more concept about APIs: +activation. + +A single EDN file can define many APIs and many schema fragments. It +is up to a service instance to determine which of these to activate +using the top-level key `:activated-apis`. This is in the EDN file for +ease, but could be supplied separately as part of your service +config. (For example, as EC2 instance data.) + +To activate the accounts API, modify the top of your EDN file like +this: + +```clojure +{:activated-apis [:accounts/v1] + ,,, +``` + +Now re-run curl and you'll get back a 200 status code with the body +string from our `#vase/respond` literal. + +## Forwarding Headers + +Some Javascript and Clojurescript clients provide request IDs that +they use to correlate requests and responses. We can tell Vase to +forward headers from the request through to the client. This is at the +level of the whole API: + +```clojure +{:vase/apis + {:accounts/v1 + {:vase.api/routes ,,, ;; skipping the routes for space + :vase.api/schemas [:accounts/user] + :vase.api/forward-headers ["vaserequest-id"]}}} +``` + +This says to forward the `vaserequest-id` header from every +request to every response. This HTTP header is used to trace and +debug requests to the service (and is automatically added if it's not +sent in). + +## Handling Parameters + +Vase does some code generation to make HTTP parameters available as +Clojure bindings for a route's action literal(s). These are conveyed +from the route to Vase by Pedestal, using the `:params` keyword in the +[request map](http://pedestal.io/reference/request-map). + +Params arrive in various forms, extracted from: + +- EDN payload for POSTs +- JSON payload for POSTs +- Form data for POSTs +- Query string arguments +- URL (i.e., path) parameters. + +Parameters are resolved with an order of precedence, as listed below: + + * EDN POST payloads override + * JSON POST payloads override + * POST form data payloads override + * Query string args override + * URL parameters + +See [Pedestal's docs](http://pedestal.io/reference/parameters) for a +complete reference on parameters. + +Let's look at them from the bottom up. + +### Path Parameters + +A path parameter binds a single value from a URL to a symbol name in your +action literal. + +Add a route with a path parameter: + +```clojure + :vase/apis + {:accounts/v1 + {:vase/routes + {"/about" {:get #vase/respond {:name :accounts.v1/about-response + :body "General User and Item Information"}} + "/about/:your-name" {:get #vase/respond {:name :accounts.v1/about-yourname + :params [your-name] + :body your-name}}} + :vase.api/schemas [:accounts/user] + :vase.api/forward-headers ["vaserequest-id"]}} +``` + +In this trivial example we bind part of the URL path to the symbol +`your-name` and return it as the body of our response. A path +parameter value is always a string. + +Try it out with curl: + + curl http://127.0.0.1:8080/api/accounts/v1/about/paul + +Let's see how we might take multiple parameters for a given route. + +### Query Parameters + +Query string args are typically used for filtering the result of +returned data. Here's an example were we'll return a JSON response +for all of our expected query args + +```clojure + :vase/apis + {:accounts/v1 + {:vase/routes + {"/about" {:get #vase/respond {:name :accounts.v1/about-response + :body "General User and Item Information"}} + "/about/:your-name" {:get #vase/respond {:name :accounts.v1/about-yourname + :params [your-name] + :body your-name}} + "/aboutquery" {:get #vase/respond {:name :accounts.v1/about-query + :params [one-thing another-thing] + :body {:first-param one-thing + :second-param another-thing}}}} + :vase.api/schemas [:accounts/user] + :vase.api/forward-headers ["vaserequest-id"]}} +``` + +With curl: + + curl 'http://127.0.0.1:8080/api/accounts/v1/aboutquery?one-thing=hello&another-thing=world' + +(Make sure you quote the whole string properly... the '?' and '&' mean +something entirely different to the shell.) + +Notice that the response that comes back is JSON, not text like the +other `#vase/respond` action we specified. That's because the new action +returned a Clojure data structure instead of a string. When the body +of a response is not a string, Vase converts it to JSON. + +### Parameter Defaults + +You can provide a default value for any `:params` binding. +For example, `:params [your-name [age 42]]`. Of course, if a path +parameter is nil, then the route didn't even match. But query and body +parameters can be defaulted this way. + +## A Dangerous Truth + +So far, we've lead you to believe that action literals are _purely_ +data. That's not entirely true. + +Many parts of the action literals are evaluated as code, in an +environment where the parameter names are bound. Everything from +`clojure.core` is available. That is, unless you shadow something from +`clojure.core` with a parameter name! + +This means we can add a function call directly to our `#vase/respond` +actions. Let's update our url-param route to print a more interesting +string using `clojure.core/str`. + +Modify the action for "/about/:your-name" like this: + +```clojure + :vase/apis + {:accounts/v1 + {:vase.api/routes + {"/about" {:get #vase/respond {:name :accounts.v1/about-response + :body "General User and Item Information"}} + "/about/:your-name" {:get #vase/respond {:name :accounts.v1/about-yourname + :params [your-name] + :body (str "You said your name was: " your-name)}} + "/aboutquery" {:get #vase/respond {:name :accounts.v1/about-query + :params [one-thing another-thing] + :body {:first-param one-thing + :second-param another-thing}}}} + :vase.api/schemas [:accounts/user] + :vase.api/forward-headers ["vaserequest-id"]}} +``` + +With great power comes great responsibility - bending this ability is +dangerous and can cause the container to crash, but in a pinch it can +allow you to shape an API to meet your needs. + +Obviously, this means anyone who can alter your descriptor can run +arbitrary code in your server. Don't accept user inputs as +descriptors! + +## Getting data in with `transact` + +In addition to rendering content, the Vase system also provides a +`#vase/transact` action allowing the storage of incoming POST data. + +Vase expects transaction data to arrive as a JSON entity body. The top +level of the body is an object with the single key `payload`. The +payload should be a collection of entity bodies (i.e., maps) to transact. + +Add the "/user" route shown here to your descriptor: + +```clojure + :vase/apis + {:accounts/v1 + {:vase.api/routes + {"/about" {:get #vase/respond {:name :accounts.v1/about-response + :body "General User and Item Information"}} + "/about/:your-name" {:get #vase/respond {:name :accounts.v1/about-yourname + :params [your-name] + :body (str "You said your name was: " your-name)}} + "/aboutquery" {:get #vase/respond {:name :accounts.v1/about-query + :params [one-thing another-thing] + :body {:first-param one-thing + :second-param another-thing}}} + "/user" {:post #vase/transact {:name :accounts.v1/user-create + :properties [:db/id + :user/userId + :user/email]}}} + :vase.api/schemas [:accounts/user] + :vase.api/forward-headers ["vaserequest-id"]}} +``` + +The new route has a single `#vase/transact` literal with a name and +properties. The `:properties` key holds a whitelist of attribute names +that Vase will accept in the incoming POST data. In this case, the +payload should have a sequence of maps that each have `:user/userId` +and `:user/email` keys and values. These will be asserted in Datomic. + +### Try a Transaction + +We want to POST the following JSON + +```json +{"payload": + [{ + "user/userId" : 42, + "user/email" : "user@example.com" + }] +} +``` + +This is how you would use cURL to POST such a payload: + +``` +curl -H "Content-Type: application/json" -X POST -d '{"payload": [{"user/userId": 42, "user/email": "user@example.com"}]}' http://localhost:8080/api/accounts/v1/user +``` + +The quoting is a little different on Windows: + +``` +curl -H "Content-Type: application/json" -X POST -d "{\"payload\":[{\"user/userId\":42,\"user/email\":\"user@example.com\"}]}" http://localhost:8080/api/accounts/v1/user +``` + +Give it a try! + +### Insert, Assert, Upsert + +The properties `:user/userId` and `:user/userEmail` are fairly +self-explanatory, but the `:db/id` property is handled specially. +That is, the `:db/id` key signifies if the incoming data refers to +existing entities in the Vase database, or to new entities. + +In the JSON packet above, the entity did not contain a `:db/id` +field. Vase treats it as a new entity and attaches a tempid before +asserting it. + +At this point, one of three things will happen: + +1. The entity has an `:identity` attribute whose value matches an + existing entity in the database. This becomes an "upsert" on that + entity: the new attribute values are _merged_ with the existing + entity. +2. The entity has a `:unique` value that already exists in the + database. Because it has a tempid, Datomic will reject the + transaction and Vase will return an error to the client. +3. Neither of the above are true, and Datomic will create a new + entity. + +It is possible to supply a db/id directly, like this: + +```json +{"payload": + [{ + "db/id" : 100, + "user/userId" : 9, + "user/email" : "user9@example.com" + }] +} +``` + +Because the `:db/id` field is set to a value, Vase will try to +resolve the entity in the database before transacting the +data. Obviously, if no such entity exists then a failure will occur, +thus notifying the calling client. + +One final way to refer to existing entities is to set the value at the +`:db/id` field to correspond to a unique value for the entity in +question. For example: + +```json +{"payload": + [{ + "db/id" : ["user/userId", 9], + "user/email" : "user9@example.com" + }] +} +``` + +We declared `:user/userId` as an `:identity` attribute. That means we +can use it in lookup refs as well as doing upsert with it. + +See Datomic's +[lookup refs](http://docs.datomic.com/identity.html#lookup-refs) for +more details. + +## Getting data out with `query` + +The `#vase/query` action provides a way to define service routes that +return data based on Datalog queries. + +Go ahead and add a `:get` action to the "/user" route like this: + +```clojure + :vase/apis + {:accounts/v1 + {:vase.api/routes + {"/about" {:get #vase/respond {:name :accounts.v1/about-response + :body "General User and Item Information"}} + "/about/:your-name" {:get #vase/respond {:name :accounts.v1/about-yourname + :params [your-name] + :body (str "You said your name was: " your-name)}} + "/aboutquery" {:get #vase/respond {:name :accounts.v1/about-query + :params [one-thing another-thing] + :body {:first-param one-thing + :second-param another-thing}}} + "/user" {:post #vase/transact {:name :accounts.v1/user-create + :properties [:db/id + :user/userId + :user/email]} + :get #vase/query {:name :accounts.v1/user-page + :params [email] + :query [:find ?e + :in $ ?email + :where [?e :user/email ?email]]}}} + :vase.api/schemas [:accounts/user] + :vase.api/forward-headers ["vaserequest-id"]}} +``` + +The "/user" route now supports both POST and GET requests. The POST +request hits the `#vase/transact` the same as before. Now a GET runs +the `#vase/query` action. The query looks up an entity based on a +query string parameter. The two main keys of interest in the +`#vase/query` action are `:params` and `:query`. (We'll discuss an +optional third property a bit later.) + +The `:params` property defines +the accepted keyed data names that are used as external arguments to +the query to resolve those listed parameters to the incoming +values. The `#vase/query` action passes these as extra arguments to +`datomic.api/q`, following the database value itself. + +If the `:params` field is empty or missing, they query doesn't accept +any arguments. In that case all parameters in the URL, query string, +form, etc. will be ignored. + +The `:query` property contains a Datomic +[datalog query](http://docs.datomic.com/query.html). + +One limitation of providing query parameters as URL arguments or path +parameters is that only string types are shuttled across to the +server. Often it's useful to refer to arguments that are other useful +types, numbers are a common case. Here's a new route to illustrate +that conversion: + +```clojure + "/user/:id" {:get #vase/query {:name :accounts-v1/user-id-page + :params [id] + :edn-coerce [id] + :query + [:find ?e + :in $ ?id + :where [?e :user/userId ?id]]}} +``` + +(We're going to stop repeating all the routes in the interest of +saving space and helping focus on the new stuff. It should be clear by +now where this goes in the EDN file.) + +The new route `/user/:id` allows the a similar information lookup, but +with the argument as a path parameter +(e.g. http://example.com/user/id). By default the `:id` parameter +would be a string value, but by using the `:edn-coerce` property of +the `#query` action we tell Vase to attempt to parse the string as a +valid EDN data type. Therefore, when clients hit the URL bound to +that query the proper types will match (i.e. the DB expects integer +IDs, not string IDs). + +### Querying `or` and other constants + +It's often useful to model a query that match against anything within +a given data set, for example, "Give me all users whose email is in +`["jane@domain.com", "bill@domain.com"]`" + +In Datomic this is called a "parameterized in/or query," and it's achieved by +binding the names with the `:in` clause. To do this, we supply additional +constant data to the Vase query with the `:constants` option. +This can also be used with parameter binding (covered above), in which case parameters +are passed in to the query before the constants. + +An example of simple constants follows - observe our last change to the schema below: + +```clojure + "/special-users" {:get #vase/query {:name :accounts.v1/special-users + :params [] + :constants [["jane@domain.com" "bill@domain.com"]] + :query + [:find ?e + :in $ [?email ...] + :where + [?e :user/email ?email]]}} +``` + +## Be Persistent + +So far, we've used an in-memory URI for Datomic. That means just what it sounds like: +values are only stored in memory. To make it persistent, you need to pick +a [storage engine](http://docs.datomic.com/storage.html#storage-services) and update the +`:datomic-uri` value. For your initial efforts, +Datomic's [dev storage protocol](http://docs.datomic.com/dev-setup.html) may suffice. + +By default, Vase uses the free version of Datomic. In order to configure dev or +other persistent stores, you will need to first +[obtain a Datomic Starter or Datomic Pro license](http://www.datomic.com/get-datomic.html) +and install the software on your machine. + +You'll also need to reference datomic-pro in your dependencies. + +You will then need to change your project's dependencies to reference the +correct version of Datomic (in the project's `project.clj` or `build.boot` +file). This is slightly subtle because the template actually did not add any +direct reference to Datomic in your project. Instead, it included a dependency +on Vase which, in turn, depends on datamic-free. + +So, you will need to add an explicit dependency on datamic-pro, and neutralize Vase's +inclusion of datomic-free: + +Look for the existing Vase dependency, e.g., +``` +[com.cognitect/pedestal.vase "0.9.1-SNAPSHOT"] +``` + +and change it to + +``` +[com.datomic/datomic-pro "0.9.NNNN" :exclusions [[com.fasterxml.jackson.core/jackson-core] + [com.fasterxml.jackson.core/jackson-databind] + [joda-time]]] + +[com.cognitect/pedestal.vase "0.9.1-SNAPSHOT" :exclusions [com.datomic/datomic-free]] +``` + +where `NNNN` is the version of Datomic you've installed. + +You can see an example of this in the samples at +[../samples/petstore-full/project.clj-with-datomic-pro](../samples/petstore-full/project.clj-with-datomic-pro). + +## Wrapping Up + +We've covered most of the action literals. The examples in this guide +created a real, if somewhat quirky, API for an accounts system. + +The next step is to read the [Action Literals](./action_literals.md) +reference. diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..d96afa7 --- /dev/null +++ b/project.clj @@ -0,0 +1,75 @@ +(defproject com.cognitect/pedestal.vase "0.9.4-SNAPSHOT" + :description "Vase: Pedestal API Container" + :url "https://github.com/cognitect-labs/vase" + :dependencies [;; Platform + [org.clojure/clojure "1.10.1"] + + ;; Datomic + [com.datomic/datomic-free "0.9.5697" :exclusions [[org.slf4j/slf4j-api] + [org.slf4j/slf4j-nop] + [org.eclipse.jetty/jetty-util] + [org.eclipse.jetty/jetty-client]]] + [com.datomic/client-cloud "0.8.78" :exclusions [commons-logging]] + [io.rkn/conformity "0.5.1" :exclusions [com.datomic/datomic-free]] + + ;; Pedestal + [io.pedestal/pedestal.service "0.5.7"] + [io.pedestal/pedestal.jetty "0.5.7"] + + ;; Pin Jetty versions to avoid conflict between Datomic and Pedestal + [org.eclipse.jetty/jetty-client "9.4.18.v20190429"] + + ;; Pin core.async to avoid conflict between Datomic and Pedestal + [org.clojure/core.async "0.4.490"] + + ;; Pin joda-time to avoid conflict between Datomic and Pedestal + [joda-time "2.9.9"] + + ;; Configuration + [com.cognitect/fern "0.1.6"] + + ;; Replace Java EE module for JDK 11 + [javax.xml.bind/jaxb-api "2.3.0"] + + ;; Cleanup + [commons-codec "1.12"] + [cheshire "5.8.1"]] + + :main ^:skip-aot com.cognitect.vase.main + :pedantic? :warn + :uberjar-name "vase-standalone.jar" + :plugins [] + :jvm-opts ~(let [version (System/getProperty "java.version") + [major _ _] (clojure.string/split version #"\.")] + (if (<= 9 (java.lang.Integer/parseInt major) 10) + ["--add-modules" "java.xml.bind"] + [])) + :test-selectors {:default (complement :integration) + :cloud :integration + :all (constantly true)} + + :profiles {:srepl {:jvm-opts ^:replace ["-XX:+UseG1GC" + "-Dclojure.server.repl={:port 5555 :accept clojure.core.server/repl}"]} + :dev {:aliases {"crepl" ["trampoline" "run" "-m" "clojure.main/main"] + "srepl" ["with-profile" "srepl" "trampoline" "run" "-m" "clojure.main/main"]} + :source-paths ["dev"] + :resource-paths ["config" + "resources" + "test/resources"] + :dependencies [[org.clojure/tools.trace "0.7.10"] + [org.clojure/tools.namespace "0.3.0" :exclusions [[org.clojure/tools.reader]]] + [org.clojure/tools.reader "1.3.2"] + [org.clojure/test.check "0.9.0"] + ;; Logging + [org.slf4j/slf4j-api "1.7.26"] + [ch.qos.logback/logback-classic "1.2.3" :exclusions [[org.slf4j/slf4j-api]]] + [org.slf4j/jul-to-slf4j "1.7.26"] + [org.slf4j/jcl-over-slf4j "1.7.26"] + [org.slf4j/log4j-over-slf4j "1.7.26"]]} + :test {:dependencies [[org.clojure/test.check "0.9.0"] + [io.pedestal/pedestal.service-tools "0.5.7" :exclusions [[org.slf4j/log4j-over-slf4j] + [org.slf4j/jul-to-slf4j] + [org.slf4j/jcl-over-slf4j]]]] + :resource-paths ["resources" + "test/resources"]}} + :min-lein-version "2.0.0") diff --git a/samples/omnext-todo/.gitignore b/samples/omnext-todo/.gitignore new file mode 100644 index 0000000..0835bd0 --- /dev/null +++ b/samples/omnext-todo/.gitignore @@ -0,0 +1,38 @@ +# Project related + +# Java related +pom.xml +pom.xml.asc +*jar +*.class + +# Leiningen +classes/ +lib/ +native/ +checkouts/ +target/ +.lein-* +repl-port +.nrepl-port +.repl + +# Temp Files +*.orig +*~ +.*.swp +.*.swo +*.tmp +*.bak + +# OS X +.DS_Store + +# Logging +*.log +/logs/ + +# Builds +out/ +build/ + diff --git a/samples/omnext-todo/Capstanfile b/samples/omnext-todo/Capstanfile new file mode 100644 index 0000000..654b3d8 --- /dev/null +++ b/samples/omnext-todo/Capstanfile @@ -0,0 +1,29 @@ + +# +# Name of the base image. Capstan will download this automatically from +# Cloudius S3 repository. +# +#base: cloudius/osv +base: cloudius/osv-openjdk8 + +# +# The command line passed to OSv to start up the application. +# +#cmdline: /java.so -cp /omnext-todo/app.jar clojure.main -m omnext-todo +cmdline: /java.so -jar /omnext-todo/app.jar + +# +# The command to use to build the application. +# You can use any build tool/command (make/rake/lein/boot) - this runs locally on your machine +# +# For Leiningen, you can use: +#build: lein uberjar +# For Boot, you can use: +#build: boot build + +# +# List of files that are included in the generated image. +# +files: + /omnext-todo/app.jar: ./target/omnext-todo-0.0.1-SNAPSHOT-standalone.jar + diff --git a/samples/omnext-todo/Dockerfile b/samples/omnext-todo/Dockerfile new file mode 100644 index 0000000..e63e573 --- /dev/null +++ b/samples/omnext-todo/Dockerfile @@ -0,0 +1,8 @@ +FROM java:8-alpine +MAINTAINER Your Name + +ADD target/omnext-todo-0.0.1-SNAPSHOT-standalone.jar /omnext-todo/app.jar + +EXPOSE 8080 + +CMD ["java", "-jar", "/omnext-todo/app.jar"] diff --git a/samples/omnext-todo/README.md b/samples/omnext-todo/README.md new file mode 100644 index 0000000..203c8f1 --- /dev/null +++ b/samples/omnext-todo/README.md @@ -0,0 +1,65 @@ +# omnext-todo + +This is a simple "TODO" example using Vase. +An [Om.next](https://github.com/omcljs/om/wiki/Documentation-(om.next)) demand-driven API is also integrated. + + +## Getting Started + +1. Start the application: `lein run` +2. Go to [localhost:8080](http://localhost:8080/) to see: `Hello World!` +3. You can see [all available API endpoints](http://localhost:8080/api) + and the [current list of TODOs](http://localhost:8080/api/omnext-todo/main/todos). + You can [optionally filter that list](http://localhost:8080/api/omnext-todo/main/todos?selector=[:todo/title]) too. +3. Read your app's source code at `src/omnext_todo/service.clj`. Explore the docs of functions + that define routes and responses. +4. See your Vase API Specification at `resources/omnext-todo_service.edn`. +5. Run your app's tests with `lein test`. Read the tests at `test/omnext_todo/service_test.clj`. + +### POSTing data to the API + +You can POST new TODOs with curl: + +`curl -H "Content-Type: application/json" -X POST -d '{"payload": [{"todo/title": "Write a test for TODO example"}]}' http://localhost:8080/api/omnext-todo/main/todos` + +You can also see the spec/validation response if you try to transact and incomplete TODO + +`curl -H "Content-Type: application/json" -X POST -d '{"payload": [{}]}' http://localhost:8080/api/omnext-todo/main/todos` + + +## Configuration + +To configure logging see config/logback.xml. By default, the app logs to stdout and logs/. +To learn more about configuring Logback, read its [documentation](http://logback.qos.ch/documentation.html). + + +## Developing your service + +1. Start a new REPL: `lein repl` +2. Start your service in dev-mode: `(def dev-serv (run-dev))` +3. Connect your editor to the running REPL session. + Re-evaluated code will be seen immediately in the service. +4. All changes to your Vase Service Descriptor will be loaded - no re-evaluation + needed. + +### [Docker](https://www.docker.com/) container support + +1. Build an uberjar of your service: `lein uberjar` +2. Build a Docker image: `sudo docker build -t omnext-todo .` +3. Run your Docker image: `docker run -p 8080:8080 omnext-todo` + +### [OSv](http://osv.io/) unikernel support with [Capstan](http://osv.io/capstan/) + +1. Build and run your image: `capstan run -f "8080:8080"` + +Once the image it built, it's cached. To delete the image and build a new one: + +1. `capstan rmi omnext-todo; capstan build` + + +## Links + + * [Pedestal examples](https://github.com/pedestal/samples) + * [Vase examples](https://github.com/cognitect-labs/vase/tree/master/samples) + + diff --git a/samples/omnext-todo/boot.properties b/samples/omnext-todo/boot.properties new file mode 100644 index 0000000..7d05262 --- /dev/null +++ b/samples/omnext-todo/boot.properties @@ -0,0 +1,3 @@ +#http://boot-clj.com +BOOT_CLOJURE_NAME=org.clojure/clojure +BOOT_CLOJURE_VERSION=1.9.0-alpha13 diff --git a/samples/omnext-todo/build.boot b/samples/omnext-todo/build.boot new file mode 100644 index 0000000..769e5c8 --- /dev/null +++ b/samples/omnext-todo/build.boot @@ -0,0 +1,107 @@ +(def proj '{:app {:project omnext-todo + :version "0.0.1-SNAPSHOT" + :description "FIXME: write description" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"}} + :source-paths #{"src"} + :test-paths #{"test"} + :resource-paths #{"resources" "config"} + :dependencies [[org.clojure/clojure "1.9.0-alpha14"] + [io.pedestal/pedestal.service "0.5.2"] + [com.cognitect/pedestal.vase "0.9.0"] + + ;; Remove this line and uncomment one of the next lines to + ;; use Immutant or Tomcat instead of Jetty: + [io.pedestal/pedestal.jetty "0.5.2"] + ;; [io.pedestal/pedestal.immutant "0.5.2-SNAPSHOT"] + ;; [io.pedestal/pedestal.tomcat "0.5.2-SNAPSHOT"] + + [ch.qos.logback/logback-classic "1.1.8" :exclusions [org.slf4j/slf4j-api]] + [org.slf4j/jul-to-slf4j "1.7.22"] + [org.slf4j/jcl-over-slf4j "1.7.22"] + [org.slf4j/log4j-over-slf4j "1.7.22"]] + :dev-dependencies [[io.pedestal/pedestal.service-tools "0.5.2"] + [org.clojure/tools.namespace "0.3.0-alpha3"]] + :external {:nrepl-options {:bind "127.0.0.1" + :reply true} + :main omnext-todo.server}}) + +(set-env! :source-paths (set (:source-paths proj)) + :test-paths (set (:test-paths proj)) + :resource-paths (set (:resource-paths proj)) + ;:repositories (:repositories proj) + :dependencies (:dependencies proj)) + +(task-options! pom {:project (get-in proj [:app :project]) + :version (str (get-in proj [:app :version]) "-standalone") + :description (get-in proj [:app :description]) + :license (get-in proj [:app :license])}) + +(load-data-readers!) + +;; == Testing tasks ======================================== + +(deftask with-test + "Add test to source paths" + [] + (set-env! :source-paths #(clojure.set/union % (get-env :test-paths))) + (set-env! :dependencies #(into % (:dev-dependencies proj))) + ;(set-env! :dependencies #(conj % '[it.frbracch/boot-marginalia "0.1.3-1" :scope "test"])) + ;(set-env! :dependencies #(conj % '[boot-codox "0.9.6" :scope "test"])) + identity) + +;; Include test/ in REPL sources +(replace-task! + [r repl] (fn [& xs] (with-test) (apply r xs))) + +(require '[clojure.test :refer [run-tests]]) + +(deftask test + "Run project tests" + [] + (with-test) ;; test source paths and test/dev deps added + (require '[clojure.tools.namespace.find :refer [find-namespaces-in-dir]]) + (let [find-namespaces-in-dir (resolve 'clojure.tools.namespace.find/find-namespaces-in-dir) + test-nses (->> (get-env :test-paths) + (mapcat #(find-namespaces-in-dir (clojure.java.io/file %))) + distinct)] + (doseq [tns test-nses] (require tns)) + (apply clojure.test/run-tests test-nses))) + +;; == Dev tasks ============================================ + +(deftask dumbrepl + "Launch a standard Clojure REPL" + [] + ;; Include test/ in REPL sources + (with-test) + (clojure.main/repl :init (fn [] + (use 'clojure.core) + (use 'clojure.repl)))) + +;(deftask docs +; "Generate API (Codox) and Literate (Marginalia) docs" +; [] +; (with-test) +; (require '[codox.boot :refer [codox]]) +; (require '[it.frbracch.boot-marginalia :refer [marginalia]]) +; (let [codox (resolve 'codox.boot/codox) +; marginalia (resolve 'it.frbracch.boot-marginalia/marginalia)] +; (comp (codox :name (name (get-in proj [:app :project])) +; :description (str (get-in proj [:app :description]) +; "\n -- Also: [Literate docs](./uberdoc.html)")) +; (marginalia :dir "doc" +; :desc (str (get-in proj [:app :description]) +; "\n -- Also: API docs")) +; (target)))) + +;; == Server Tasks ========================================= + +(deftask build + "Build my project." + [] + (comp (aot :namespace #{(get-in proj [:external :main])}) + (pom) + (uber) + (jar :main (get-in proj [:external :main])))) + diff --git a/samples/omnext-todo/config/logback.xml b/samples/omnext-todo/config/logback.xml new file mode 100644 index 0000000..8bf7f31 --- /dev/null +++ b/samples/omnext-todo/config/logback.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + logs/omnext-todo-%d{yyyy-MM-dd}.%i.log + + 64 MB + + + + false + + + + + + + %-5level %logger{36} - %msg%n + + + + INFO + + + + + + + + + + + + + + diff --git a/samples/omnext-todo/project.clj b/samples/omnext-todo/project.clj new file mode 100644 index 0000000..71a954e --- /dev/null +++ b/samples/omnext-todo/project.clj @@ -0,0 +1,38 @@ +(defproject omnext-todo "0.9.4-SNAPSHOT" + :description "Vase sample application, Todo list compatible with Om.next" + :url "https://github.com/cognitect-labs/vase/tree/master/samples/omnext-todo" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :dependencies [[org.clojure/clojure "1.10.0"] + [io.pedestal/pedestal.service "0.5.5"] + [com.cognitect/pedestal.vase "0.9.3"] + + ;; Remove this line and uncomment one of the next lines to + ;; use Immutant or Tomcat instead of Jetty: + [io.pedestal/pedestal.jetty "0.5.5"] + ;; [io.pedestal/pedestal.immutant "0.5.3"] + ;; [io.pedestal/pedestal.tomcat "0.5.3"] + + [ch.qos.logback/logback-classic "1.2.3" :exclusions [org.slf4j/slf4j-api]] + [org.slf4j/jul-to-slf4j "1.7.25"] + [org.slf4j/jcl-over-slf4j "1.7.25"] + [org.slf4j/log4j-over-slf4j "1.7.25"] + + ;; Om.next Demand-driven API + ;[org.clojure/clojurescript "1.9.293"] + [org.omcljs/om "1.0.0-alpha47"] + + ;; Deps cleanup + ;; -- Om and Pedestal + [com.cognitect/transit-clj "0.8.313"] + ;; -- Vase and Pedestal + [cheshire "5.8.1"]] + :pedantic? :abort + :min-lein-version "2.0.0" + :resource-paths ["config", "resources"] + ;; If you use HTTP/2 or ALPN, use the java-agent to pull in the correct alpn-boot dependency + ;:java-agents [[org.mortbay.jetty.alpn/jetty-alpn-agent "2.0.3"]] + :profiles {:dev {:aliases {"run-dev" ["trampoline" "run" "-m" "omnext-todo.server/run-dev"]} + :dependencies [[io.pedestal/pedestal.service-tools "0.5.5"]]} + :uberjar {:aot [omnext-todo.server]}} + :main ^{:skip-aot true} omnext-todo.server) diff --git a/samples/omnext-todo/resources/omnext-todo_service.edn b/samples/omnext-todo/resources/omnext-todo_service.edn new file mode 100644 index 0000000..8077871 --- /dev/null +++ b/samples/omnext-todo/resources/omnext-todo_service.edn @@ -0,0 +1,68 @@ +{:activated-apis [:omnext-todo/main] + :datomic-uri "datomic:mem://example" + :descriptor + ;; Datomic Schema Norms + ;; -------------------- + {:vase/norms + {:omnext-todo/todo-categories + {:vase.norm/txes [[{:db/id #db/id[:db.part/db] + :db/ident :todo.category/personal} + {:db/id #db/id[:db.part/db] + :db/ident :todo.category/work}]]} + :omnext-todo/base-schema + {:vase.norm/requires [:omnext-todo/todo-categories] + :vase.norm/txes [#vase/schema-tx [[:todo/created :one :instant "A todo's creation timestamp"] + [:todo/title :one :string :fulltext "A todo's title/description"] + [:todo/completed :one :boolean "A todo's completion status/state"] + [:todo/category :one :ref "The tag/group/category of the todo"]]]}} + + ;; Global Specs for the API + ;; ------------------------ + :vase/specs + {:todo/category keyword? + :todo/title (clojure.spec.alpha/and string? not-empty) + :omnext-todo/todo (clojure.spec.alpha/keys :req [:todo/title] + :opt [:todo/category]) + :omnext-todo/todos (clojure.spec.alpha/+ :omnext-todo/todo + )} + + ;; API Tagged Chunks/Versions + ;; -------------------------- + :vase/apis + {:omnext-todo/main + {:vase.api/routes + {"/todos" {:get #vase/query {:name :omnext-todo.main/todos-list + :params [[selector [:* {:todo/category [:db/ident]}]]] + :edn-coerce [selector] + :query [:find [(pull ?todo selector) ...] + :in $ selector + :where [?todo :todo/title]]} + :post [#vase/validate {:name :omnext-todo.main/todos-create-validation + :spec :omnext-todo/todos + :request-params-path [:json-params :payload]} + #vase/intercept {:name :omnext-todo.main/todos-create-shaping + :enter (fn [ctx] + (update-in ctx [:request :json-params :payload] + (fn [tx] + (mapv (fn [ent] + (when ent + (merge {:todo/category :todo.category/personal + :todo/created (java.util.Date.)} + ent))) + tx))))} + #vase/transact {:name :omnext-todo.main/todos-create + ;; `:properties` are pulled from the `payload` parameters + :properties [:db/id + :todo/title + :todo/created + :todo/category + :todo/completed]}] + :delete #vase/transact {:name :omnext-todo.main/todos-delete + :db-op :vase/retract-entity + ;; :vase/retract-entity requires :db/id to be supplied + :properties [:db/id]}} + "/todos/validate" {:post #vase/validate {:name :omnext-todo.main/validate-page + :spec :omnext-todo/todo}}} + ;:vase.api/interceptors [] ;; Any extra interceptors to apply to this API chunk/version + :vase.api/schemas [:omnext-todo/base-schema] + :vase.api/forward-headers ["vaserequest-id"]}}}} diff --git a/samples/omnext-todo/src/omnext_todo/demand.clj b/samples/omnext-todo/src/omnext_todo/demand.clj new file mode 100644 index 0000000..cb19103 --- /dev/null +++ b/samples/omnext-todo/src/omnext_todo/demand.clj @@ -0,0 +1,151 @@ +(ns omnext-todo.demand + "Demand-driven query-handling" + (:require [clojure.walk :as walk] + [datomic.api :as d] + [io.pedestal.log :as log]) + (:import [java.util Date])) + +;; Demand-driven data query handling +;; --------------------------------- + +;; ### Auxiliary functions +;; ------------------------ +(defn demand-query* + [eid-slice* opts] + (let [{:keys [eid-lvar bindings where find-cardinality] + :or {eid-lvar (first eid-slice*) + find-cardinality '...}} opts + eid-slice (assoc eid-slice* 0 eid-lvar)] + (into `[:find [(~'pull ~eid-lvar ~'selector) ~find-cardinality] + :in ~'$ ~'selector ~@(keys bindings) + :where + ~eid-slice] + where))) + +(defn demand-query + "Given a db value, a pull-based selector, a `where` vector for finding + a specific entity type, and a map of additional options, + perform a DB demand-driven query and return the results. + Options include: + :as-of - to pass to Datomic's `as-of` function + :eid-lvar - an lvar symbol to use for the eid, e.g., to reference in `where` constraints + :bindings - a map of query arg lvars to values + :where - a vector of `where` clauses to use when making the query + :find-cardinality - Either '. or '... -- defaults to '... + + Example: + (demand-query my-db '[*] '[?todo :todo/title] {})" + ([db selector eid-slice] + (demand-query db selector eid-slice nil)) + ([db selector eid-slice opts] + (let [{:keys [as-of bindings]} opts + db (if as-of (d/as-of db as-of) db) + query (demand-query* eid-slice opts)] + (apply d/q query db (or selector '[*]) (vals bindings))))) + +(defn todos + ([db] + (todos db nil)) + ([db selector] + (todos db selector nil)) + ([db selector opts] + (demand-query db selector '[?todo :todo/title] opts))) + +;; ### The `read` function +;; This handles query calls from clients for data +;; The read keys align with the logical resource entities + +(defmulti demand-read (fn [env k params] k)) + +(defmethod demand-read :default + [env k params] + {:value {:error (str "No handler for read key/resource " k)}}) + +(defmethod demand-read :by-id + [env k params] + (let [{:keys [conn db db-ref query]} env + {:keys [id]} params + db (or (some-> db-ref deref) db (d/db conn))] + {:value (d/pull db (or query '[*]) id)})) + +(defmethod demand-read :todos/list + [env k params] + (let [{:keys [conn db db-ref query]} env + db (or (some-> db-ref deref) db (d/db conn))] + {:value (todos db query params)})) + +;; ### The `write`/mutate function +;; This handles mutate/update calls from clients with novel data +;; The write ops/keys are narrow and strictly controlled + +(defmulti demand-write + (fn [env k params] + (if (:readonly env) + :feature/readonly + k))) + +(defmethod demand-write :default + [env k params] + {:value {:error (str "No handler for write/mutation key " k)}}) + +(defmethod demand-write :feature/readonly + [env k params] + {:value {:error (str "Mutations disabled, readonly mode enabled.")}}) + +(defmethod demand-write 'todos/create + [env k params] + (let [{:keys [conn db db-ref]} env + db (or (some-> db-ref deref) db (d/db conn)) + todo-txes [(merge + {:db/id (d/tempid :db.part/user) + :todo/title (:title params) + :todo/completed false + :todo/created (java.util.Date.)} + (when-let [category (:category params)] + {:todo/category category}))]] + {:value {:keys [:todos/list]} + :action (fn [] + (let [tx-result (d/transact conn todo-txes)] + (some-> db-ref (reset! (:db-after tx-result))) + {:mutation/success true}))})) + +;; ### The `post-process` function +;; This shapes all outbound results from the demand driven api +;; It works on the attribute level. If the result is a list of +;; entites, it's mapped across the entities. + +(defn resolve-enum-ref + "Resolve an enum ref to its keyword name/ident" + [env v] + (if-let [db (:db env)] + (:db/ident (d/entity db (:db/id v))) + v)) + +;; A dispatch-map of response keys and how to post-process their results +;; -- All functions take two args: the env and the value to shape +(def demand-post-processors + {:todo/category resolve-enum-ref + :result (fn [env v] + (if (map? v) + (dissoc v :db-before :db-after :telem-data) + v))}) + +(defn post-process + ([env result] + (post-process env result demand-post-processors)) + ([env result processors-map] + (walk/postwalk + (fn [obj] + (if-let [v-fn (or (and (instance? clojure.lang.MapEntry obj) + (get processors-map (key obj))) + (and (vector? obj) + (= 2 (count obj)) + (get processors-map (first obj))))] + (let [[k v] obj] + [k (if (or (sequential? v) + (set? v)) + (into (empty v) (map #(v-fn env %) v)) + (v-fn env v))]) + obj)) + result))) + diff --git a/samples/omnext-todo/src/omnext_todo/server.clj b/samples/omnext-todo/src/omnext_todo/server.clj new file mode 100644 index 0000000..48b40ed --- /dev/null +++ b/samples/omnext-todo/src/omnext_todo/server.clj @@ -0,0 +1,91 @@ +(ns omnext-todo.server + (:gen-class) ; for -main method in uberjar + (:require [io.pedestal.http :as server] + [io.pedestal.http.route :as route] + [com.cognitect.vase :as vase] + [omnext-todo.service :as service])) + +(defn activate-vase + ([base-routes api-root spec-paths] + (activate-vase base-routes api-root spec-paths vase/load-edn-resource)) + ([base-routes api-root spec-paths vase-load-fn] + (let [vase-specs (mapv vase-load-fn spec-paths)] + (when (seq vase-specs) + (vase/ensure-schema vase-specs) + (vase/specs vase-specs)) + {::routes (if (empty? vase-specs) + base-routes + (into base-routes (vase/routes api-root vase-specs))) + ::specs vase-specs}))) + +(defn vase-service + "Optionally given a default service map and any number of string paths + to Vase API Specifications, + Return a Pedestal Service Map with all Vase APIs parsed, ensured, and activated." + ([] + (vase-service service/service)) + ([service-map] + (vase-service service-map vase/load-edn-resource)) + ([service-map vase-load-fn] + (merge {:env :prod + ::server/routes (::routes (activate-vase + (::service/route-set service-map) + (::vase/api-root service-map) + (::vase/spec-resources service-map) + vase-load-fn))} + service-map))) + +;; This is an adapted service map, that can be started and stopped +;; From the REPL you can call server/start and server/stop on this service +(defonce runnable-service (server/create-server (vase-service))) + +(defn run-dev + "The entry-point for 'lein run-dev'" + [& args] + (println "\nCreating your [DEV] server...") + (-> service/service ;; start with production configuration + (merge {:env :dev + ;; do not block thread that starts web server + ::server/join? false + ;; Routes can be a function that resolve routes, + ;; we can use this to set the routes to be reloadable + ::server/routes #(route/expand-routes + (::routes (activate-vase (deref #'service/routes) + (::vase/api-root service/service) + (mapv (fn [res-str] + (str "resources/" res-str)) + (::vase/spec-resources service/service)) + vase/load-edn-file))) + ;; all origins are allowed in dev mode + ::server/allowed-origins {:creds true :allowed-origins (constantly true)}}) + ;; Wire up interceptor chains + server/default-interceptors + server/dev-interceptors + server/create-server + server/start)) + +(defn -main + "The entry-point for 'lein run'" + [& args] + (println "\nCreating your server...") + (server/start runnable-service)) + +;; If you package the service up as a WAR, +;; some form of the following function sections is required (for io.pedestal.servlet.ClojureVarServlet). + +;;(defonce servlet (atom nil)) +;; +;;(defn servlet-init +;; [_ config] +;; ;; Initialize your app here. +;; (reset! servlet (server/servlet-init service/service nil))) +;; +;;(defn servlet-service +;; [_ request response] +;; (server/servlet-service @servlet request response)) +;; +;;(defn servlet-destroy +;; [_] +;; (server/servlet-destroy @servlet) +;; (reset! servlet nil)) + diff --git a/samples/omnext-todo/src/omnext_todo/service.clj b/samples/omnext-todo/src/omnext_todo/service.clj new file mode 100644 index 0000000..3f5bc0e --- /dev/null +++ b/samples/omnext-todo/src/omnext_todo/service.clj @@ -0,0 +1,115 @@ +(ns omnext-todo.service + (:require [clojure.edn] + [datomic.api :as d] + [io.pedestal.http :as http] + [io.pedestal.http.route :as route] + [io.pedestal.http.body-params :as body-params] + [ring.util.response :as ring-resp] + [com.cognitect.vase :as vase] + [om.next.server :as om] + [omnext-todo.demand :as demand])) + +(defn about-page + [request] + (ring-resp/response (format "Clojure %s - served from %s" + (clojure-version) + (route/url-for ::about-page)))) + +(defn home-page + [request] + (ring-resp/response "Hello World!")) + +(def demand-parser (om/parser {:read demand/demand-read + :mutate demand/demand-write})) + +(defn params->query + "Given a map of filter parameters (as from the query-string), + Return a valid Datomic Datalog query that can be passed to `q`." + ([params] + (params->query params + '[:find ?todo + :in $ + :where + [?todo :todo/title]])) + ([params initial-query] + (if-let [dlog-query (:raw-datalog params)] + (clojure.edn/read-string dlog-query) + (if (some? params) + (cond-> initial-query + (:completed params) (conj '[?todo :todo/completed true])) + initial-query)))) + +(defn query-results + "A read-only demand-like query interface for clients that can't speak 'Om' + Allows a" + ([request params] + (d/q (params->query params) (:db request))) + ([request params initial-query] + (d/q (params->query params initial-query) (:db request)))) + +(defn handle-demand + "Handle a demand-driven request. + If the request has a parameter `:demand`, it is an Om.next query. + Otherwise, handle the request with our limited 'demand-style' function, 'query-results'" + [request] + (let [demand-params (get-in request [:transit-params :demand] + (get-in request [:params :demand])) + parsed-demand-params (if (string? demand-params) + (clojure.edn/read-string demand-params) + demand-params)] + (if parsed-demand-params + ;; We shouldn't need `trim` here, but we're defensively guarding against a `mutate` client call + + ;; NOTE: :db-ref is an atom that holds the result db value from demand mutations so + ;; subsequent reads in a transaction containing a mutation can access the resulting db + ;; (without the potential pitfalls of requesting a new db value from the datomic connection + ;; with other requests in flight) + (let [env (assoc request :db-ref (atom nil))] + (demand/post-process request (demand-parser env parsed-demand-params))) + (set (query-results request (:params request)))))) + +;; Defines "/" and "/about" routes with their associated :get handlers. +;; The interceptors defined after the verb map (e.g., {:get home-page} +;; apply to / and its children (/about). +(def common-interceptors [(body-params/body-params) http/html-body]) + +;; Tabular routes +(def routes #{["/" :get (conj common-interceptors `home-page)] + ["/about" :get (conj common-interceptors `about-page)] + ["/api/demand" :get (conj common-interceptors `handle-demand)]}) + +(def service + {:env :prod + ;; You can bring your own non-default interceptors. Make + ;; sure you include routing and set it up right for + ;; dev-mode. If you do, many other keys for configuring + ;; default interceptors will be ignored. + ;; ::http/interceptors [] + + ;; Uncomment next line to enable CORS support, add + ;; string(s) specifying scheme, host and port for + ;; allowed source(s): + ;; + ;; "http://localhost:8080" + ;; + ;;::http/allowed-origins ["scheme://host:port"] + + ::route-set routes + ::vase/api-root "/api" + ::vase/spec-resources ["omnext-todo_service.edn"] + + ;; Root for resource interceptor that is available by default. + ::http/resource-path "/public" + + ;; Either :jetty, :immutant or :tomcat (see comments in project.clj) + ::http/type :jetty + ;;::http/host "localhost" + ::http/port 8080 + ;; Options to pass to the container (Jetty) + ::http/container-options {:h2c? true + :h2? false + ;:keystore "test/hp/keystore.jks" + ;:key-password "password" + ;:ssl-port 8443 + :ssl? false}}) + diff --git a/samples/omnext-todo/test/omnext_todo/service_test.clj b/samples/omnext-todo/test/omnext_todo/service_test.clj new file mode 100644 index 0000000..6e8c74b --- /dev/null +++ b/samples/omnext-todo/test/omnext_todo/service_test.clj @@ -0,0 +1,59 @@ +(ns omnext-todo.service-test + (:require [clojure.test :refer :all] + [io.pedestal.test :refer :all] + [io.pedestal.http :as http] + [omnext-todo.test-helper :as helper] + [omnext-todo.service :as service])) + +;; To test your service, call `(helper/service` to get a new service instance. +;; If you need a constant service over multiple calls, use `(helper/with-service ...) +;; All generated services will have randomized, consistent in-memory Datomic DBs +;; if required by the service +;; +;; `helper` also contains shorthands for common `response-for` patterns, +;; like GET, POST, post-json, post-edn, and others + +(deftest home-page-test + (is (= (:body (response-for (helper/service) :get "/")) + "Hello World!")) + (is (= (:headers (helper/GET "/")) + {"Content-Type" "text/html;charset=UTF-8" + "Strict-Transport-Security" "max-age=31536000; includeSubdomains" + "X-Frame-Options" "DENY" + "X-Content-Type-Options" "nosniff" + "X-XSS-Protection" "1; mode=block" + "X-Download-Options" "noopen" + "X-Permitted-Cross-Domain-Policies" "none" + "Content-Security-Policy" "object-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https: http:;"}))) + + +(deftest about-page-test + (helper/with-service service/service + (is (.contains (:body (response-for (helper/service) :get "/about")) + "Clojure 1.9")) + (is (= (:headers (helper/GET "/about")) + {"Content-Type" "text/html;charset=UTF-8" + "Strict-Transport-Security" "max-age=31536000; includeSubdomains" + "X-Frame-Options" "DENY" + "X-Content-Type-Options" "nosniff" + "X-XSS-Protection" "1; mode=block" + "X-Download-Options" "noopen" + "X-Permitted-Cross-Domain-Policies" "none" + "Content-Security-Policy" "object-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https: http:;"})))) + +;; Let's test the API from our descriptor +(deftest todos-CRUD-test + (helper/with-service service/service + (let [initial-list (helper/response-data (helper/GET "/api/omnext-todo/main/todos")) + expected-todo {:todo/title "Write a test for TODO example" + :todo/category {:db/ident "todo.category/personal"}} + tx-resp (helper/post-json "/api/omnext-todo/main/todos" {:payload [{:todo/title "Write a test for TODO example"}]}) + new-list (helper/response-data (helper/GET "/api/omnext-todo/main/todos")) + new-todo (first new-list)] + (is (empty? initial-list)) + (is (= 1 (count new-list))) + (is (:db/id new-todo)) + (is (= expected-todo + (dissoc new-todo :db/id :todo/created)))))) + + diff --git a/samples/omnext-todo/test/omnext_todo/test_helper.clj b/samples/omnext-todo/test/omnext_todo/test_helper.clj new file mode 100644 index 0000000..93bf04c --- /dev/null +++ b/samples/omnext-todo/test/omnext_todo/test_helper.clj @@ -0,0 +1,108 @@ +(ns omnext-todo.test-helper + (:require [io.pedestal.test :refer [response-for]] + [io.pedestal.http :as http] + [io.pedestal.interceptor.chain :as chain] + [io.pedestal.log :as log] + [com.cognitect.vase :as vase] + [com.cognitect.vase.util :as util] + [com.cognitect.vase.datomic :as datomic] + [omnext-todo.server :as server] + [omnext-todo.service])) + +(def write-edn pr-str) + +(defn new-service + "This generates a new testable service for use with io.pedestal.test/response-for. + It will also create a new Datomic DB (randomized URI)." + ([] (new-service omnext-todo.service/service)) + ([service-map] + (let [db-table (volatile! {}) + vase-service-map (server/vase-service + service-map + (fn [spec-path] + (let [spec (vase/load-edn-resource spec-path) + prod-db-uri (:datomic-uri spec) + new-db-uri (when-let [db-uri (and prod-db-uri (get @db-table prod-db-uri (datomic/new-db-uri)))] + (vswap! db-table assoc prod-db-uri db-uri) + db-uri)] + (if prod-db-uri + (assoc spec :datomic-uri new-db-uri) + spec))))] + (::http/service-fn (http/create-servlet vase-service-map))))) + +(def ^:dynamic *current-service* nil) + +(defmacro with-service + "Executes all requests in the body with the same service (using a thread-local binding)" + [srv-map & body] + `(binding [*current-service* (new-service ~srv-map)] + ~@body)) + +(defn service + [& args] + (or *current-service* (apply new-service args))) + +(defn GET + "Make a GET request on our service using response-for." + [& args] + (apply response-for (service) :get args)) + +(defn POST + "Make a POST request on our service using response-for." + [& args] + (apply response-for (service) :post args)) + +(defn DELETE + "Make a DELETE request on our service using response-for." + [& args] + (apply response-for (service) :delete args)) + +(defn json-request + ([verb url payload] + (json-request verb url payload {})) + ([verb url payload opts] + (response-for (service) + verb url + :headers (merge {"Content-Type" "application/json"} + (:headers opts)) + :body (util/write-json payload)))) + +(defn post-json + "Makes a POST request to URL-path expecting a payload to submit as JSON. + + Options: + * :headers: Additional headers to send with the request." + ([URL-path payload] + (post-json URL-path payload {})) + ([URL-path payload opts] + (json-request :post URL-path payload opts))) + +(defn post-edn + "Makes a POST request to URL-path expecting a payload to submit as edn. + + Options: + * :headers: Additional headers to send with the request." + ([URL-path payload] + (post-edn URL-path payload {})) + ([URL-path payload opts] + (response-for (service) + :post URL-path + :headers (merge {"Content-Type" "application/edn"} + (:headers opts)) + :body (write-edn payload)))) + +(defn response-data + "Return the parsed payload data from a vase api http response." + ([response] (response-data response util/read-json)) + ([response reader] + (-> response + :body + reader))) + +(defn run-interceptor + ([i] (run-interceptor {} i)) + ([ctx i] (chain/execute (chain/enqueue* ctx i)))) + +(defn new-req-ctx + [& {:as headers}] + {:request {:headers headers}}) diff --git a/samples/petstore/README.md b/samples/petstore/README.md new file mode 100644 index 0000000..69ab8cf --- /dev/null +++ b/samples/petstore/README.md @@ -0,0 +1,40 @@ +# Pet Store Example + +This is a simple version of a back end for the well-known Pet Store +app. This app should help Vase newcomers see how to create an API with +Vase. + +# Running the sample + +1. Start it up with `clj -Mrun` +2. Go to [localhost:8080/index.html](http://localhost:8080/index.html) + to see the Swagger UI. +3. Click `default` + - GET /pets will get all pets + + click "Try it out!" button. + + - POST /pets will transact new pet(s) + + Click "Example Value" and edit the name and tag values, then click "Try it out!". + + - GET /pet/{id} will get a single pet by ID + + Input a pet ID, then click "Try it out!" button + +# Datomic Setup + +This sample uses Datomic with an in-memory database. Since Vase +already depends on "datomic-free," you don't need to add anything +specific for the in-memory case. + +If you want to use on-disk storage with Datomic Pro, two steps are +needed: + +1. In `project.clj`, uncomment the dependency for `com.datomic/datomic-pro` +2. Change the connection defined in `resources/petstore.fern` as + described in comments in that file. + +```clojure +:vase.descriptor/datomic-uri "datomic:dev://localhost:4334/pet-store" +``` diff --git a/samples/petstore/deps.edn b/samples/petstore/deps.edn new file mode 100644 index 0000000..af1b110 --- /dev/null +++ b/samples/petstore/deps.edn @@ -0,0 +1,10 @@ +{:deps {com.cognitect/pedestal.vase {:local/root "../../" :exclusions [org.slf4j/slf4j-nop]} + io.pedestal/pedestal.service {:mvn/version "0.5.5"} + io.pedestal/pedestal.jetty {:mvn/version "0.5.5"} + ch.qos.logback/logback-classic {:mvn/version "1.2.3" :exclusions [org.slf4j/slf4j-api]} + org.slf4j/jul-to-slf4j {:mvn/version "1.7.25"} + org.slf4j/jcl-over-slf4j {:mvn/version "1.7.25"} + org.slf4j/log4j-over-slf4j {:mvn/version "1.7.25"}} + :paths ["src" "resources"] + :aliases {:run {:main-opts ["-m" "com.cognitect.vase.main" "petstore.fern"]} + :dev {:extra-deps {io.pedestal/pedestal.service-tools {:mvn/version "0.5.5"}}}}} diff --git a/samples/petstore/petstore.fern b/samples/petstore/petstore.fern new file mode 100644 index 0000000..e1a8936 --- /dev/null +++ b/samples/petstore/petstore.fern @@ -0,0 +1,74 @@ +{;; The only "magic" key here is `vase/service`. Vase finds everything + ;; else by following references + vase/service (fern/lit vase/service + {:apis [@petstore/v1] + :service-map @http-options}) + + ;; For in-memory, use this definition + datomic-uri "datomic:mem:/pet-store" + + ;; For on-disk Datomic, use this definition + ;; datomic "datomic:dev://localhost:4334/petstore" + + ;; Configure Jetty, via Pedestals' service map. + http-options {:io.pedestal.http/resource-path "/public" + :io.pedestal.http/port 8080 + :io.pedestal.http/secure-headers {:content-security-policy-settings + {:object-src "none"}}} + + ;; The connection is used in the :on-startup and :on-request chains. + connection (fern/lit vase.datomic/connection @datomic-uri) + + ;; Define an API + petstore/v1 (fern/lit vase/api + {:path "/petstore/v1" + :expose-api-at "/petstore/api" + :on-request [@connection @io.pedestal.http.body-params/body-params @io.pedestal.http/json-body] + :on-startup [@connection @petstore.v1/schema @petstore.v1/seed] + :routes @petstore.v1/routes}) + + ;; The schema used by the API + petstore.v1/schema (fern/lit vase.datomic/attributes + [:pet/id :one :long :unique "The id of a pet"] + [:pet/name :one :string "The name of a pet"] + [:pet/tag :one :string "The tag of a pet"]) + + petstore.v1/seed (fern/lit vase.datomic/tx + {:pet/id 1 + :pet/name "Fido"} + {:pet/id 2 + :pet/name "Spot"} + {:pet/id 3 + :pet/name "Itchy"} + {:pet/id 4 + :pet/name "Scratchy"} + {:pet/id 5 + :pet/name "Cerberus"}) + + petstore.v1/routes #{["/pet/:id" :get @petstore.v1/find-a-pet] + ["/pets" :delete @petstore.v1/delete-a-pet] + ["/pets" :get @petstore.v1/find-pets] + ["/pets" :post @petstore.v1/add-pets]} + + petstore.v1/add-pets (fern/lit vase.datomic/transact + {:db-op :vase/assert-entity + :properties [:pet/id + :pet/name + :pet/tag]}) + + petstore.v1/delete-a-pet (fern/lit vase.datomic/transact + {:db-op :vase/retract-entity + :properties [:pet/id]}) + + petstore.v1/find-a-pet (fern/lit vase.datomic/query + {:edn-coerce [id] + :params [id] + :query [:find (pull ?e [*]) + :in $ ?id + :where [?e :pet/id ?id]]}) + + petstore.v1/find-pets (fern/lit vase.datomic/query + {:params [] + :query [:find (pull ?e [*]) + :in $ + :where [?e :pet/id ?id]]})} diff --git a/samples/petstore/resources/logback.xml b/samples/petstore/resources/logback.xml new file mode 100644 index 0000000..972e25a --- /dev/null +++ b/samples/petstore/resources/logback.xml @@ -0,0 +1,52 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + logs/pet-store-%d{yyyy-MM-dd}.%i.log + + + 64 MB + + + + + true + + + + + + + + %-5level %logger{36} - %msg%n + + + + INFO + + + + + + + + + + + + + + + diff --git a/samples/petstore/resources/public/css/print.css b/samples/petstore/resources/public/css/print.css new file mode 100644 index 0000000..d4c1e70 --- /dev/null +++ b/samples/petstore/resources/public/css/print.css @@ -0,0 +1,1362 @@ +/* Original style from softwaremaniacs.org (c) Ivan Sagalaev */ +.swagger-section pre code { + display: block; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section pre code, +.swagger-section pre .subst, +.swagger-section pre .tag .title, +.swagger-section pre .lisp .title, +.swagger-section pre .clojure .built_in, +.swagger-section pre .nginx .title { + color: black; +} +.swagger-section pre .string, +.swagger-section pre .title, +.swagger-section pre .constant, +.swagger-section pre .parent, +.swagger-section pre .tag .value, +.swagger-section pre .rules .value, +.swagger-section pre .rules .value .number, +.swagger-section pre .preprocessor, +.swagger-section pre .ruby .symbol, +.swagger-section pre .ruby .symbol .string, +.swagger-section pre .aggregate, +.swagger-section pre .template_tag, +.swagger-section pre .django .variable, +.swagger-section pre .smalltalk .class, +.swagger-section pre .addition, +.swagger-section pre .flow, +.swagger-section pre .stream, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .apache .cbracket, +.swagger-section pre .tex .command, +.swagger-section pre .tex .special, +.swagger-section pre .erlang_repl .function_or_atom, +.swagger-section pre .markdown .header { + color: #800; +} +.swagger-section pre .comment, +.swagger-section pre .annotation, +.swagger-section pre .template_comment, +.swagger-section pre .diff .header, +.swagger-section pre .chunk, +.swagger-section pre .markdown .blockquote { + color: #888; +} +.swagger-section pre .number, +.swagger-section pre .date, +.swagger-section pre .regexp, +.swagger-section pre .literal, +.swagger-section pre .smalltalk .symbol, +.swagger-section pre .smalltalk .char, +.swagger-section pre .go .constant, +.swagger-section pre .change, +.swagger-section pre .markdown .bullet, +.swagger-section pre .markdown .link_url { + color: #080; +} +.swagger-section pre .label, +.swagger-section pre .javadoc, +.swagger-section pre .ruby .string, +.swagger-section pre .decorator, +.swagger-section pre .filter .argument, +.swagger-section pre .localvars, +.swagger-section pre .array, +.swagger-section pre .attr_selector, +.swagger-section pre .important, +.swagger-section pre .pseudo, +.swagger-section pre .pi, +.swagger-section pre .doctype, +.swagger-section pre .deletion, +.swagger-section pre .envvar, +.swagger-section pre .shebang, +.swagger-section pre .apache .sqbracket, +.swagger-section pre .nginx .built_in, +.swagger-section pre .tex .formula, +.swagger-section pre .erlang_repl .reserved, +.swagger-section pre .prompt, +.swagger-section pre .markdown .link_label, +.swagger-section pre .vhdl .attribute, +.swagger-section pre .clojure .attribute, +.swagger-section pre .coffeescript .property { + color: #88F; +} +.swagger-section pre .keyword, +.swagger-section pre .id, +.swagger-section pre .phpdoc, +.swagger-section pre .title, +.swagger-section pre .built_in, +.swagger-section pre .aggregate, +.swagger-section pre .css .tag, +.swagger-section pre .javadoctag, +.swagger-section pre .phpdoc, +.swagger-section pre .yardoctag, +.swagger-section pre .smalltalk .class, +.swagger-section pre .winutils, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .go .typename, +.swagger-section pre .tex .command, +.swagger-section pre .markdown .strong, +.swagger-section pre .request, +.swagger-section pre .status { + font-weight: bold; +} +.swagger-section pre .markdown .emphasis { + font-style: italic; +} +.swagger-section pre .nginx .built_in { + font-weight: normal; +} +.swagger-section pre .coffeescript .javascript, +.swagger-section pre .javascript .xml, +.swagger-section pre .tex .formula, +.swagger-section pre .xml .javascript, +.swagger-section pre .xml .vbscript, +.swagger-section pre .xml .css, +.swagger-section pre .xml .cdata { + opacity: 0.5; +} +.swagger-section .hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section .hljs, +.swagger-section .hljs-subst { + color: #444; +} +.swagger-section .hljs-keyword, +.swagger-section .hljs-attribute, +.swagger-section .hljs-selector-tag, +.swagger-section .hljs-meta-keyword, +.swagger-section .hljs-doctag, +.swagger-section .hljs-name { + font-weight: bold; +} +.swagger-section .hljs-built_in, +.swagger-section .hljs-literal, +.swagger-section .hljs-bullet, +.swagger-section .hljs-code, +.swagger-section .hljs-addition { + color: #1F811F; +} +.swagger-section .hljs-regexp, +.swagger-section .hljs-symbol, +.swagger-section .hljs-variable, +.swagger-section .hljs-template-variable, +.swagger-section .hljs-link, +.swagger-section .hljs-selector-attr, +.swagger-section .hljs-selector-pseudo { + color: #BC6060; +} +.swagger-section .hljs-type, +.swagger-section .hljs-string, +.swagger-section .hljs-number, +.swagger-section .hljs-selector-id, +.swagger-section .hljs-selector-class, +.swagger-section .hljs-quote, +.swagger-section .hljs-template-tag, +.swagger-section .hljs-deletion { + color: #880000; +} +.swagger-section .hljs-title, +.swagger-section .hljs-section { + color: #880000; + font-weight: bold; +} +.swagger-section .hljs-comment { + color: #888888; +} +.swagger-section .hljs-meta { + color: #2B6EA1; +} +.swagger-section .hljs-emphasis { + font-style: italic; +} +.swagger-section .hljs-strong { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap { + line-height: 1; + font-family: "Droid Sans", sans-serif; + min-width: 760px; + max-width: 960px; + margin-left: auto; + margin-right: auto; + /* JSONEditor specific styling */ +} +.swagger-section .swagger-ui-wrap b, +.swagger-section .swagger-ui-wrap strong { + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap q, +.swagger-section .swagger-ui-wrap blockquote { + quotes: none; +} +.swagger-section .swagger-ui-wrap p { + line-height: 1.4em; + padding: 0 0 10px; + color: #333333; +} +.swagger-section .swagger-ui-wrap q:before, +.swagger-section .swagger-ui-wrap q:after, +.swagger-section .swagger-ui-wrap blockquote:before, +.swagger-section .swagger-ui-wrap blockquote:after { + content: none; +} +.swagger-section .swagger-ui-wrap .heading_with_menu h1, +.swagger-section .swagger-ui-wrap .heading_with_menu h2, +.swagger-section .swagger-ui-wrap .heading_with_menu h3, +.swagger-section .swagger-ui-wrap .heading_with_menu h4, +.swagger-section .swagger-ui-wrap .heading_with_menu h5, +.swagger-section .swagger-ui-wrap .heading_with_menu h6 { + display: block; + clear: none; + float: left; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + width: 60%; +} +.swagger-section .swagger-ui-wrap table { + border-collapse: collapse; + border-spacing: 0; +} +.swagger-section .swagger-ui-wrap table thead tr th { + padding: 5px; + font-size: 0.9em; + color: #666666; + border-bottom: 1px solid #999999; +} +.swagger-section .swagger-ui-wrap table tbody tr:last-child td { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap table tbody tr.offset { + background-color: #f0f0f0; +} +.swagger-section .swagger-ui-wrap table tbody tr td { + padding: 6px; + font-size: 0.9em; + border-bottom: 1px solid #cccccc; + vertical-align: top; + line-height: 1.3em; +} +.swagger-section .swagger-ui-wrap ol { + margin: 0px 0 10px; + padding: 0 0 0 18px; + list-style-type: decimal; +} +.swagger-section .swagger-ui-wrap ol li { + padding: 5px 0px; + font-size: 0.9em; + color: #333333; +} +.swagger-section .swagger-ui-wrap ol, +.swagger-section .swagger-ui-wrap ul { + list-style: none; +} +.swagger-section .swagger-ui-wrap h1 a, +.swagger-section .swagger-ui-wrap h2 a, +.swagger-section .swagger-ui-wrap h3 a, +.swagger-section .swagger-ui-wrap h4 a, +.swagger-section .swagger-ui-wrap h5 a, +.swagger-section .swagger-ui-wrap h6 a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap h1 a:hover, +.swagger-section .swagger-ui-wrap h2 a:hover, +.swagger-section .swagger-ui-wrap h3 a:hover, +.swagger-section .swagger-ui-wrap h4 a:hover, +.swagger-section .swagger-ui-wrap h5 a:hover, +.swagger-section .swagger-ui-wrap h6 a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap h1 span.divider, +.swagger-section .swagger-ui-wrap h2 span.divider, +.swagger-section .swagger-ui-wrap h3 span.divider, +.swagger-section .swagger-ui-wrap h4 span.divider, +.swagger-section .swagger-ui-wrap h5 span.divider, +.swagger-section .swagger-ui-wrap h6 span.divider { + color: #aaaaaa; +} +.swagger-section .swagger-ui-wrap a { + color: #547f00; +} +.swagger-section .swagger-ui-wrap a img { + border: none; +} +.swagger-section .swagger-ui-wrap article, +.swagger-section .swagger-ui-wrap aside, +.swagger-section .swagger-ui-wrap details, +.swagger-section .swagger-ui-wrap figcaption, +.swagger-section .swagger-ui-wrap figure, +.swagger-section .swagger-ui-wrap footer, +.swagger-section .swagger-ui-wrap header, +.swagger-section .swagger-ui-wrap hgroup, +.swagger-section .swagger-ui-wrap menu, +.swagger-section .swagger-ui-wrap nav, +.swagger-section .swagger-ui-wrap section, +.swagger-section .swagger-ui-wrap summary { + display: block; +} +.swagger-section .swagger-ui-wrap pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; +} +.swagger-section .swagger-ui-wrap pre code { + line-height: 1.6em; + background: none; +} +.swagger-section .swagger-ui-wrap .content > .content-type > div > label { + clear: both; + display: block; + color: #0F6AB4; + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap .content pre { + font-size: 12px; + margin-top: 5px; + padding: 5px; +} +.swagger-section .swagger-ui-wrap .icon-btn { + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .info_title { + padding-bottom: 10px; + font-weight: bold; + font-size: 25px; +} +.swagger-section .swagger-ui-wrap .footer { + margin-top: 20px; +} +.swagger-section .swagger-ui-wrap p.big, +.swagger-section .swagger-ui-wrap div.big p { + font-size: 1em; + margin-bottom: 10px; +} +.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input { + width: 500px !important; +} +.swagger-section .swagger-ui-wrap .info_license { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_tos { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .message-fail { + color: #cc0000; +} +.swagger-section .swagger-ui-wrap .info_url { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_email { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_name { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_description { + padding-bottom: 10px; + font-size: 15px; +} +.swagger-section .swagger-ui-wrap .markdown ol li, +.swagger-section .swagger-ui-wrap .markdown ul li { + padding: 3px 0px; + line-height: 1.4em; + color: #333333; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input { + display: block; + padding: 4px; + width: auto; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title { + font-size: 1.3em; +} +.swagger-section .swagger-ui-wrap table.fullwidth { + width: 100%; +} +.swagger-section .swagger-ui-wrap .model-signature { + font-family: "Droid Sans", sans-serif; + font-size: 1em; + line-height: 1.5em; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a { + text-decoration: none; + color: #AAA; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap .model-signature .propType { + color: #5555aa; +} +.swagger-section .swagger-ui-wrap .model-signature pre:hover { + background-color: #ffffdd; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + font-size: .85em; + line-height: 1.2em; + overflow: auto; + max-height: 200px; + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav { + display: block; + min-width: 230px; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li { + float: left; + margin: 0 5px 5px 0; + padding: 2px 5px 2px 0; + border-right: 1px solid #ddd; +} +.swagger-section .swagger-ui-wrap .model-signature .propOpt { + color: #555; +} +.swagger-section .swagger-ui-wrap .model-signature .snippet small { + font-size: 0.75em; +} +.swagger-section .swagger-ui-wrap .model-signature .propOptKey { + font-style: italic; +} +.swagger-section .swagger-ui-wrap .model-signature .description .strong { + font-weight: bold; + color: #000; + font-size: .9em; +} +.swagger-section .swagger-ui-wrap .model-signature .description div { + font-size: 0.9em; + line-height: 1.5em; + margin-left: 1em; +} +.swagger-section .swagger-ui-wrap .model-signature .description .stronger { + font-weight: bold; + color: #000; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper { + border-spacing: 0; + position: absolute; + background-color: #ffffff; + border: 1px solid #bbbbbb; + display: none; + font-size: 11px; + max-width: 400px; + line-height: 30px; + color: black; + padding: 5px; + margin-left: 10px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th { + text-align: center; + background-color: #eeeeee; + border: 1px solid #bbbbbb; + font-size: 11px; + color: #666666; + font-weight: bold; + padding: 5px; + line-height: 15px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:first-child, +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:last-child { + display: inline; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:not(:first-child):before { + display: block; + content: ''; +} +.swagger-section .swagger-ui-wrap .model-signature .description span:last-of-type.propDesc.markdown > p:only-child { + margin-right: -3px; +} +.swagger-section .swagger-ui-wrap .model-signature .propName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-container { + clear: both; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 300px; + height: 100px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap .markdown p code, +.swagger-section .swagger-ui-wrap .markdown li code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #f0f0f0; + color: black; + padding: 1px 3px; +} +.swagger-section .swagger-ui-wrap .required { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .editor_holder { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap .editor_holder label { + font-weight: normal!important; + /* JSONEditor uses bold by default for all labels, we revert that back to normal to not give the impression that by default fields are required */ +} +.swagger-section .swagger-ui-wrap .editor_holder label.required { + font-weight: bold!important; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 300px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap h1 { + color: black; + font-size: 1.5em; + line-height: 1.3em; + padding: 10px 0 10px 0; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .heading_with_menu { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap .heading_with_menu ul { + display: block; + clear: none; + float: right; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + margin-top: 10px; +} +.swagger-section .swagger-ui-wrap h2 { + color: black; + font-size: 1.3em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap h2 span.sub { + font-size: 0.7em; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap h2 span.sub a { + color: #777777; +} +.swagger-section .swagger-ui-wrap span.weak { + color: #666666; +} +.swagger-section .swagger-ui-wrap .message-success { + color: #89BF04; +} +.swagger-section .swagger-ui-wrap caption, +.swagger-section .swagger-ui-wrap th, +.swagger-section .swagger-ui-wrap td { + text-align: left; + font-weight: normal; + vertical-align: middle; +} +.swagger-section .swagger-ui-wrap .code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea { + font-family: "Droid Sans", sans-serif; + height: 250px; + padding: 4px; + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select { + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label { + display: block; + float: left; + clear: none; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input { + display: block; + float: left; + clear: none; + margin: 0 5px 0 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label { + color: black; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label { + display: block; + clear: both; + width: auto; + padding: 0 0 3px; + color: #666666; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr { + padding-left: 3px; + color: #888888; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints { + margin-left: 0; + font-style: italic; + font-size: 0.9em; + margin: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap span.blank, +.swagger-section .swagger-ui-wrap span.empty { + color: #888888; + font-style: italic; +} +.swagger-section .swagger-ui-wrap .markdown h3 { + color: #547f00; +} +.swagger-section .swagger-ui-wrap .markdown h4 { + color: #666666; +} +.swagger-section .swagger-ui-wrap .markdown pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; + margin: 0 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown pre code { + line-height: 1.6em; + overflow: auto; +} +.swagger-section .swagger-ui-wrap div.gist { + margin: 20px 0 25px 0 !important; +} +.swagger-section .swagger-ui-wrap ul#resources { + font-family: "Droid Sans", sans-serif; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource { + border-bottom: 1px solid #dddddd; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a { + color: #555555; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading { + border: 1px solid transparent; + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 14px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + border-right: 1px solid #dddddd; + color: #666666; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a { + color: #aaaaaa; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #999999; + padding-left: 0; + display: block; + clear: none; + float: left; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #999999; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0 0 10px; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 { + display: block; + clear: none; + float: left; + width: auto; + margin: 0; + padding: 0; + line-height: 1.1em; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path { + padding-left: 10px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a.toggleOperation.deprecated { + text-decoration: line-through; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a { + text-transform: uppercase; + text-decoration: none; + color: white; + display: inline-block; + width: 50px; + font-size: 0.7em; + text-align: center; + padding: 7px 0 4px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 6px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + border-top: none; + padding: 10px; + -moz-border-radius-bottomleft: 6px; + -webkit-border-bottom-left-radius: 6px; + -o-border-bottom-left-radius: 6px; + -ms-border-bottom-left-radius: 6px; + -khtml-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -moz-border-radius-bottomright: 6px; + -webkit-border-bottom-right-radius: 6px; + -o-border-bottom-right-radius: 6px; + -ms-border-bottom-right-radius: 6px; + -khtml-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + margin: 0 0 20px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a { + padding: 4px 0 0 10px; + display: inline-block; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit { + display: block; + clear: none; + float: left; + padding: 6px 8px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { + background-image: url('../images/throbber.gif'); + width: 128px; + height: 16px; + display: block; + clear: none; + float: right; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error { + outline: 2px solid black; + outline-color: #cc0000; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] { + max-width: 300px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + padding: 10px; + font-size: 0.9em; + max-height: 400px; + overflow-y: auto; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading { + background-color: #f9f2e9; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a { + background-color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0e0ca; + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content { + background-color: #faf5ee; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #ffd20f; + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading { + background-color: #f5e8e8; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #e8c6c7; + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + background-color: #f7eded; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a { + color: #c8787a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading { + background-color: #e7f6ec; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a { + background-color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3e8d1; + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content { + background-color: #ebf7f0; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading { + background-color: #FCE9E3; + border: 1px solid #F5D5C3; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a { + background-color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0cecb; + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content { + background-color: #faf0ef; + border: 1px solid #f0cecb; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + border-top: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap p#colophon { + margin: 0 15px 40px 15px; + padding: 10px 0; + font-size: 0.8em; + border-top: 1px solid #dddddd; + font-family: "Droid Sans", sans-serif; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap p#colophon a { + text-decoration: none; + color: #547f00; +} +.swagger-section .swagger-ui-wrap h3 { + color: black; + font-size: 1.1em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown ol, +.swagger-section .swagger-ui-wrap .markdown ul { + font-family: "Droid Sans", sans-serif; + margin: 5px 0 10px; + padding: 0 0 0 18px; + list-style-type: disc; +} +.swagger-section .swagger-ui-wrap form.form_box { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box label { + color: #0f6ab4 !important; +} +.swagger-section .swagger-ui-wrap form.form_box input[type=submit] { + display: block; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box p.weak { + font-size: 0.8em; +} +.swagger-section .swagger-ui-wrap form.form_box p { + font-size: 0.9em; + padding: 0 0 15px; + color: #7e7b6d; +} +.swagger-section .swagger-ui-wrap form.form_box p a { + color: #646257; +} +.swagger-section .swagger-ui-wrap form.form_box p strong { + color: black; +} +.swagger-section .swagger-ui-wrap .operation-status td.markdown > p:last-child { + padding-bottom: 0; +} +.swagger-section .title { + font-style: bold; +} +.swagger-section .secondary_form { + display: none; +} +.swagger-section .main_image { + display: block; + margin-left: auto; + margin-right: auto; +} +.swagger-section .oauth_body { + margin-left: 100px; + margin-right: 100px; +} +.swagger-section .oauth_submit { + text-align: center; + display: inline-block; +} +.swagger-section .authorize-wrapper { + margin: 15px 0 10px; +} +.swagger-section .authorize-wrapper_operation { + float: right; +} +.swagger-section .authorize__btn:hover { + text-decoration: underline; + cursor: pointer; +} +.swagger-section .authorize__btn_operation:hover .authorize-scopes { + display: block; +} +.swagger-section .authorize-scopes { + position: absolute; + margin-top: 20px; + background: #FFF; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + max-width: 300px; + line-height: 30px; + color: black; + padding: 5px; +} +.swagger-section .authorize-scopes .authorize__scope { + text-decoration: none; +} +.swagger-section .authorize__btn_operation { + height: 18px; + vertical-align: middle; + display: inline-block; + background: url(../images/explorer_icons.png) no-repeat; +} +.swagger-section .authorize__btn_operation_login { + background-position: 0 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .authorize__btn_operation_logout { + background-position: -30px 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section #auth_container { + color: #fff; + display: inline-block; + border: none; + padding: 5px; + width: 87px; + height: 13px; +} +.swagger-section #auth_container .authorize__btn { + color: #fff; +} +.swagger-section .auth_container { + padding: 0 0 10px; + margin-bottom: 5px; + border-bottom: solid 1px #CCC; + font-size: 0.9em; +} +.swagger-section .auth_container .auth__title { + color: #547f00; + font-size: 1.2em; +} +.swagger-section .auth_container .basic_auth__label { + display: inline-block; + width: 60px; +} +.swagger-section .auth_container .auth__description { + color: #999999; + margin-bottom: 5px; +} +.swagger-section .auth_container .auth__button { + margin-top: 10px; + height: 30px; +} +.swagger-section .auth_container .key_auth__field { + margin: 5px 0; +} +.swagger-section .auth_container .key_auth__label { + display: inline-block; + width: 60px; +} +.swagger-section .api-popup-dialog { + position: absolute; + display: none; +} +.swagger-section .api-popup-dialog-wrapper { + z-index: 1000; + width: 500px; + background: #FFF; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 13px; + color: #777; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} +.swagger-section .api-popup-dialog-shadow { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.2; + background-color: gray; + z-index: 900; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .error-msg { + padding-left: 5px; + padding-bottom: 5px; +} +.swagger-section .api-popup-dialog .api-popup-content { + max-height: 500px; + overflow-y: auto; +} +.swagger-section .api-popup-dialog .api-popup-authbtn { + height: 30px; +} +.swagger-section .api-popup-dialog .api-popup-cancel { + height: 30px; +} +.swagger-section .api-popup-scopes { + padding: 10px 20px; +} +.swagger-section .api-popup-scopes li { + padding: 5px 0; + line-height: 20px; +} +.swagger-section .api-popup-scopes li input { + position: relative; + top: 2px; +} +.swagger-section .api-popup-scopes .api-scope-desc { + padding-left: 20px; + font-style: italic; +} +.swagger-section .api-popup-actions { + padding-top: 10px; +} +#header { + display: none; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + max-height: none; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 100px; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 100px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + display: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints { + display: block !important; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + display: block !important; +} diff --git a/samples/petstore/resources/public/css/reset.css b/samples/petstore/resources/public/css/reset.css new file mode 100644 index 0000000..b2b0789 --- /dev/null +++ b/samples/petstore/resources/public/css/reset.css @@ -0,0 +1,125 @@ +/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/samples/petstore/resources/public/css/screen.css b/samples/petstore/resources/public/css/screen.css new file mode 100644 index 0000000..9d680e2 --- /dev/null +++ b/samples/petstore/resources/public/css/screen.css @@ -0,0 +1,1489 @@ +/* Original style from softwaremaniacs.org (c) Ivan Sagalaev */ +.swagger-section pre code { + display: block; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section pre code, +.swagger-section pre .subst, +.swagger-section pre .tag .title, +.swagger-section pre .lisp .title, +.swagger-section pre .clojure .built_in, +.swagger-section pre .nginx .title { + color: black; +} +.swagger-section pre .string, +.swagger-section pre .title, +.swagger-section pre .constant, +.swagger-section pre .parent, +.swagger-section pre .tag .value, +.swagger-section pre .rules .value, +.swagger-section pre .rules .value .number, +.swagger-section pre .preprocessor, +.swagger-section pre .ruby .symbol, +.swagger-section pre .ruby .symbol .string, +.swagger-section pre .aggregate, +.swagger-section pre .template_tag, +.swagger-section pre .django .variable, +.swagger-section pre .smalltalk .class, +.swagger-section pre .addition, +.swagger-section pre .flow, +.swagger-section pre .stream, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .apache .cbracket, +.swagger-section pre .tex .command, +.swagger-section pre .tex .special, +.swagger-section pre .erlang_repl .function_or_atom, +.swagger-section pre .markdown .header { + color: #800; +} +.swagger-section pre .comment, +.swagger-section pre .annotation, +.swagger-section pre .template_comment, +.swagger-section pre .diff .header, +.swagger-section pre .chunk, +.swagger-section pre .markdown .blockquote { + color: #888; +} +.swagger-section pre .number, +.swagger-section pre .date, +.swagger-section pre .regexp, +.swagger-section pre .literal, +.swagger-section pre .smalltalk .symbol, +.swagger-section pre .smalltalk .char, +.swagger-section pre .go .constant, +.swagger-section pre .change, +.swagger-section pre .markdown .bullet, +.swagger-section pre .markdown .link_url { + color: #080; +} +.swagger-section pre .label, +.swagger-section pre .javadoc, +.swagger-section pre .ruby .string, +.swagger-section pre .decorator, +.swagger-section pre .filter .argument, +.swagger-section pre .localvars, +.swagger-section pre .array, +.swagger-section pre .attr_selector, +.swagger-section pre .important, +.swagger-section pre .pseudo, +.swagger-section pre .pi, +.swagger-section pre .doctype, +.swagger-section pre .deletion, +.swagger-section pre .envvar, +.swagger-section pre .shebang, +.swagger-section pre .apache .sqbracket, +.swagger-section pre .nginx .built_in, +.swagger-section pre .tex .formula, +.swagger-section pre .erlang_repl .reserved, +.swagger-section pre .prompt, +.swagger-section pre .markdown .link_label, +.swagger-section pre .vhdl .attribute, +.swagger-section pre .clojure .attribute, +.swagger-section pre .coffeescript .property { + color: #88F; +} +.swagger-section pre .keyword, +.swagger-section pre .id, +.swagger-section pre .phpdoc, +.swagger-section pre .title, +.swagger-section pre .built_in, +.swagger-section pre .aggregate, +.swagger-section pre .css .tag, +.swagger-section pre .javadoctag, +.swagger-section pre .phpdoc, +.swagger-section pre .yardoctag, +.swagger-section pre .smalltalk .class, +.swagger-section pre .winutils, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .go .typename, +.swagger-section pre .tex .command, +.swagger-section pre .markdown .strong, +.swagger-section pre .request, +.swagger-section pre .status { + font-weight: bold; +} +.swagger-section pre .markdown .emphasis { + font-style: italic; +} +.swagger-section pre .nginx .built_in { + font-weight: normal; +} +.swagger-section pre .coffeescript .javascript, +.swagger-section pre .javascript .xml, +.swagger-section pre .tex .formula, +.swagger-section pre .xml .javascript, +.swagger-section pre .xml .vbscript, +.swagger-section pre .xml .css, +.swagger-section pre .xml .cdata { + opacity: 0.5; +} +.swagger-section .hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section .hljs, +.swagger-section .hljs-subst { + color: #444; +} +.swagger-section .hljs-keyword, +.swagger-section .hljs-attribute, +.swagger-section .hljs-selector-tag, +.swagger-section .hljs-meta-keyword, +.swagger-section .hljs-doctag, +.swagger-section .hljs-name { + font-weight: bold; +} +.swagger-section .hljs-built_in, +.swagger-section .hljs-literal, +.swagger-section .hljs-bullet, +.swagger-section .hljs-code, +.swagger-section .hljs-addition { + color: #1F811F; +} +.swagger-section .hljs-regexp, +.swagger-section .hljs-symbol, +.swagger-section .hljs-variable, +.swagger-section .hljs-template-variable, +.swagger-section .hljs-link, +.swagger-section .hljs-selector-attr, +.swagger-section .hljs-selector-pseudo { + color: #BC6060; +} +.swagger-section .hljs-type, +.swagger-section .hljs-string, +.swagger-section .hljs-number, +.swagger-section .hljs-selector-id, +.swagger-section .hljs-selector-class, +.swagger-section .hljs-quote, +.swagger-section .hljs-template-tag, +.swagger-section .hljs-deletion { + color: #880000; +} +.swagger-section .hljs-title, +.swagger-section .hljs-section { + color: #880000; + font-weight: bold; +} +.swagger-section .hljs-comment { + color: #888888; +} +.swagger-section .hljs-meta { + color: #2B6EA1; +} +.swagger-section .hljs-emphasis { + font-style: italic; +} +.swagger-section .hljs-strong { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap { + line-height: 1; + font-family: "Droid Sans", sans-serif; + min-width: 760px; + max-width: 960px; + margin-left: auto; + margin-right: auto; + /* JSONEditor specific styling */ +} +.swagger-section .swagger-ui-wrap b, +.swagger-section .swagger-ui-wrap strong { + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap q, +.swagger-section .swagger-ui-wrap blockquote { + quotes: none; +} +.swagger-section .swagger-ui-wrap p { + line-height: 1.4em; + padding: 0 0 10px; + color: #333333; +} +.swagger-section .swagger-ui-wrap q:before, +.swagger-section .swagger-ui-wrap q:after, +.swagger-section .swagger-ui-wrap blockquote:before, +.swagger-section .swagger-ui-wrap blockquote:after { + content: none; +} +.swagger-section .swagger-ui-wrap .heading_with_menu h1, +.swagger-section .swagger-ui-wrap .heading_with_menu h2, +.swagger-section .swagger-ui-wrap .heading_with_menu h3, +.swagger-section .swagger-ui-wrap .heading_with_menu h4, +.swagger-section .swagger-ui-wrap .heading_with_menu h5, +.swagger-section .swagger-ui-wrap .heading_with_menu h6 { + display: block; + clear: none; + float: left; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + width: 60%; +} +.swagger-section .swagger-ui-wrap table { + border-collapse: collapse; + border-spacing: 0; +} +.swagger-section .swagger-ui-wrap table thead tr th { + padding: 5px; + font-size: 0.9em; + color: #666666; + border-bottom: 1px solid #999999; +} +.swagger-section .swagger-ui-wrap table tbody tr:last-child td { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap table tbody tr.offset { + background-color: #f0f0f0; +} +.swagger-section .swagger-ui-wrap table tbody tr td { + padding: 6px; + font-size: 0.9em; + border-bottom: 1px solid #cccccc; + vertical-align: top; + line-height: 1.3em; +} +.swagger-section .swagger-ui-wrap ol { + margin: 0px 0 10px; + padding: 0 0 0 18px; + list-style-type: decimal; +} +.swagger-section .swagger-ui-wrap ol li { + padding: 5px 0px; + font-size: 0.9em; + color: #333333; +} +.swagger-section .swagger-ui-wrap ol, +.swagger-section .swagger-ui-wrap ul { + list-style: none; +} +.swagger-section .swagger-ui-wrap h1 a, +.swagger-section .swagger-ui-wrap h2 a, +.swagger-section .swagger-ui-wrap h3 a, +.swagger-section .swagger-ui-wrap h4 a, +.swagger-section .swagger-ui-wrap h5 a, +.swagger-section .swagger-ui-wrap h6 a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap h1 a:hover, +.swagger-section .swagger-ui-wrap h2 a:hover, +.swagger-section .swagger-ui-wrap h3 a:hover, +.swagger-section .swagger-ui-wrap h4 a:hover, +.swagger-section .swagger-ui-wrap h5 a:hover, +.swagger-section .swagger-ui-wrap h6 a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap h1 span.divider, +.swagger-section .swagger-ui-wrap h2 span.divider, +.swagger-section .swagger-ui-wrap h3 span.divider, +.swagger-section .swagger-ui-wrap h4 span.divider, +.swagger-section .swagger-ui-wrap h5 span.divider, +.swagger-section .swagger-ui-wrap h6 span.divider { + color: #aaaaaa; +} +.swagger-section .swagger-ui-wrap a { + color: #547f00; +} +.swagger-section .swagger-ui-wrap a img { + border: none; +} +.swagger-section .swagger-ui-wrap article, +.swagger-section .swagger-ui-wrap aside, +.swagger-section .swagger-ui-wrap details, +.swagger-section .swagger-ui-wrap figcaption, +.swagger-section .swagger-ui-wrap figure, +.swagger-section .swagger-ui-wrap footer, +.swagger-section .swagger-ui-wrap header, +.swagger-section .swagger-ui-wrap hgroup, +.swagger-section .swagger-ui-wrap menu, +.swagger-section .swagger-ui-wrap nav, +.swagger-section .swagger-ui-wrap section, +.swagger-section .swagger-ui-wrap summary { + display: block; +} +.swagger-section .swagger-ui-wrap pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; +} +.swagger-section .swagger-ui-wrap pre code { + line-height: 1.6em; + background: none; +} +.swagger-section .swagger-ui-wrap .content > .content-type > div > label { + clear: both; + display: block; + color: #0F6AB4; + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap .content pre { + font-size: 12px; + margin-top: 5px; + padding: 5px; +} +.swagger-section .swagger-ui-wrap .icon-btn { + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .info_title { + padding-bottom: 10px; + font-weight: bold; + font-size: 25px; +} +.swagger-section .swagger-ui-wrap .footer { + margin-top: 20px; +} +.swagger-section .swagger-ui-wrap p.big, +.swagger-section .swagger-ui-wrap div.big p { + font-size: 1em; + margin-bottom: 10px; +} +.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input { + width: 500px !important; +} +.swagger-section .swagger-ui-wrap .info_license { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_tos { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .message-fail { + color: #cc0000; +} +.swagger-section .swagger-ui-wrap .info_url { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_email { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_name { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_description { + padding-bottom: 10px; + font-size: 15px; +} +.swagger-section .swagger-ui-wrap .markdown ol li, +.swagger-section .swagger-ui-wrap .markdown ul li { + padding: 3px 0px; + line-height: 1.4em; + color: #333333; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input { + display: block; + padding: 4px; + width: auto; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title { + font-size: 1.3em; +} +.swagger-section .swagger-ui-wrap table.fullwidth { + width: 100%; +} +.swagger-section .swagger-ui-wrap .model-signature { + font-family: "Droid Sans", sans-serif; + font-size: 1em; + line-height: 1.5em; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a { + text-decoration: none; + color: #AAA; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap .model-signature .propType { + color: #5555aa; +} +.swagger-section .swagger-ui-wrap .model-signature pre:hover { + background-color: #ffffdd; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + font-size: .85em; + line-height: 1.2em; + overflow: auto; + max-height: 200px; + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav { + display: block; + min-width: 230px; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li { + float: left; + margin: 0 5px 5px 0; + padding: 2px 5px 2px 0; + border-right: 1px solid #ddd; +} +.swagger-section .swagger-ui-wrap .model-signature .propOpt { + color: #555; +} +.swagger-section .swagger-ui-wrap .model-signature .snippet small { + font-size: 0.75em; +} +.swagger-section .swagger-ui-wrap .model-signature .propOptKey { + font-style: italic; +} +.swagger-section .swagger-ui-wrap .model-signature .description .strong { + font-weight: bold; + color: #000; + font-size: .9em; +} +.swagger-section .swagger-ui-wrap .model-signature .description div { + font-size: 0.9em; + line-height: 1.5em; + margin-left: 1em; +} +.swagger-section .swagger-ui-wrap .model-signature .description .stronger { + font-weight: bold; + color: #000; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper { + border-spacing: 0; + position: absolute; + background-color: #ffffff; + border: 1px solid #bbbbbb; + display: none; + font-size: 11px; + max-width: 400px; + line-height: 30px; + color: black; + padding: 5px; + margin-left: 10px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th { + text-align: center; + background-color: #eeeeee; + border: 1px solid #bbbbbb; + font-size: 11px; + color: #666666; + font-weight: bold; + padding: 5px; + line-height: 15px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:first-child, +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:last-child { + display: inline; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:not(:first-child):before { + display: block; + content: ''; +} +.swagger-section .swagger-ui-wrap .model-signature .description span:last-of-type.propDesc.markdown > p:only-child { + margin-right: -3px; +} +.swagger-section .swagger-ui-wrap .model-signature .propName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-container { + clear: both; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 300px; + height: 100px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap .markdown p code, +.swagger-section .swagger-ui-wrap .markdown li code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #f0f0f0; + color: black; + padding: 1px 3px; +} +.swagger-section .swagger-ui-wrap .required { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .editor_holder { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap .editor_holder label { + font-weight: normal!important; + /* JSONEditor uses bold by default for all labels, we revert that back to normal to not give the impression that by default fields are required */ +} +.swagger-section .swagger-ui-wrap .editor_holder label.required { + font-weight: bold!important; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 300px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap h1 { + color: black; + font-size: 1.5em; + line-height: 1.3em; + padding: 10px 0 10px 0; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .heading_with_menu { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap .heading_with_menu ul { + display: block; + clear: none; + float: right; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + margin-top: 10px; +} +.swagger-section .swagger-ui-wrap h2 { + color: black; + font-size: 1.3em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap h2 span.sub { + font-size: 0.7em; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap h2 span.sub a { + color: #777777; +} +.swagger-section .swagger-ui-wrap span.weak { + color: #666666; +} +.swagger-section .swagger-ui-wrap .message-success { + color: #89BF04; +} +.swagger-section .swagger-ui-wrap caption, +.swagger-section .swagger-ui-wrap th, +.swagger-section .swagger-ui-wrap td { + text-align: left; + font-weight: normal; + vertical-align: middle; +} +.swagger-section .swagger-ui-wrap .code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea { + font-family: "Droid Sans", sans-serif; + height: 250px; + padding: 4px; + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select { + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label { + display: block; + float: left; + clear: none; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input { + display: block; + float: left; + clear: none; + margin: 0 5px 0 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label { + color: black; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label { + display: block; + clear: both; + width: auto; + padding: 0 0 3px; + color: #666666; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr { + padding-left: 3px; + color: #888888; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints { + margin-left: 0; + font-style: italic; + font-size: 0.9em; + margin: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap span.blank, +.swagger-section .swagger-ui-wrap span.empty { + color: #888888; + font-style: italic; +} +.swagger-section .swagger-ui-wrap .markdown h3 { + color: #547f00; +} +.swagger-section .swagger-ui-wrap .markdown h4 { + color: #666666; +} +.swagger-section .swagger-ui-wrap .markdown pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; + margin: 0 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown pre code { + line-height: 1.6em; + overflow: auto; +} +.swagger-section .swagger-ui-wrap div.gist { + margin: 20px 0 25px 0 !important; +} +.swagger-section .swagger-ui-wrap ul#resources { + font-family: "Droid Sans", sans-serif; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource { + border-bottom: 1px solid #dddddd; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a { + color: #555555; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading { + border: 1px solid transparent; + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 14px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + border-right: 1px solid #dddddd; + color: #666666; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a { + color: #aaaaaa; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #999999; + padding-left: 0; + display: block; + clear: none; + float: left; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #999999; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0 0 10px; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 { + display: block; + clear: none; + float: left; + width: auto; + margin: 0; + padding: 0; + line-height: 1.1em; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path { + padding-left: 10px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a.toggleOperation.deprecated { + text-decoration: line-through; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a { + text-transform: uppercase; + text-decoration: none; + color: white; + display: inline-block; + width: 50px; + font-size: 0.7em; + text-align: center; + padding: 7px 0 4px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 6px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + border-top: none; + padding: 10px; + -moz-border-radius-bottomleft: 6px; + -webkit-border-bottom-left-radius: 6px; + -o-border-bottom-left-radius: 6px; + -ms-border-bottom-left-radius: 6px; + -khtml-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -moz-border-radius-bottomright: 6px; + -webkit-border-bottom-right-radius: 6px; + -o-border-bottom-right-radius: 6px; + -ms-border-bottom-right-radius: 6px; + -khtml-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + margin: 0 0 20px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a { + padding: 4px 0 0 10px; + display: inline-block; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit { + display: block; + clear: none; + float: left; + padding: 6px 8px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { + background-image: url('../images/throbber.gif'); + width: 128px; + height: 16px; + display: block; + clear: none; + float: right; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error { + outline: 2px solid black; + outline-color: #cc0000; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] { + max-width: 300px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + padding: 10px; + font-size: 0.9em; + max-height: 400px; + overflow-y: auto; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading { + background-color: #f9f2e9; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a { + background-color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0e0ca; + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content { + background-color: #faf5ee; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #ffd20f; + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading { + background-color: #f5e8e8; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #e8c6c7; + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + background-color: #f7eded; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a { + color: #c8787a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading { + background-color: #e7f6ec; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a { + background-color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3e8d1; + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content { + background-color: #ebf7f0; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading { + background-color: #FCE9E3; + border: 1px solid #F5D5C3; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a { + background-color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0cecb; + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content { + background-color: #faf0ef; + border: 1px solid #f0cecb; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + border-top: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap p#colophon { + margin: 0 15px 40px 15px; + padding: 10px 0; + font-size: 0.8em; + border-top: 1px solid #dddddd; + font-family: "Droid Sans", sans-serif; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap p#colophon a { + text-decoration: none; + color: #547f00; +} +.swagger-section .swagger-ui-wrap h3 { + color: black; + font-size: 1.1em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown ol, +.swagger-section .swagger-ui-wrap .markdown ul { + font-family: "Droid Sans", sans-serif; + margin: 5px 0 10px; + padding: 0 0 0 18px; + list-style-type: disc; +} +.swagger-section .swagger-ui-wrap form.form_box { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box label { + color: #0f6ab4 !important; +} +.swagger-section .swagger-ui-wrap form.form_box input[type=submit] { + display: block; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box p.weak { + font-size: 0.8em; +} +.swagger-section .swagger-ui-wrap form.form_box p { + font-size: 0.9em; + padding: 0 0 15px; + color: #7e7b6d; +} +.swagger-section .swagger-ui-wrap form.form_box p a { + color: #646257; +} +.swagger-section .swagger-ui-wrap form.form_box p strong { + color: black; +} +.swagger-section .swagger-ui-wrap .operation-status td.markdown > p:last-child { + padding-bottom: 0; +} +.swagger-section .title { + font-style: bold; +} +.swagger-section .secondary_form { + display: none; +} +.swagger-section .main_image { + display: block; + margin-left: auto; + margin-right: auto; +} +.swagger-section .oauth_body { + margin-left: 100px; + margin-right: 100px; +} +.swagger-section .oauth_submit { + text-align: center; + display: inline-block; +} +.swagger-section .authorize-wrapper { + margin: 15px 0 10px; +} +.swagger-section .authorize-wrapper_operation { + float: right; +} +.swagger-section .authorize__btn:hover { + text-decoration: underline; + cursor: pointer; +} +.swagger-section .authorize__btn_operation:hover .authorize-scopes { + display: block; +} +.swagger-section .authorize-scopes { + position: absolute; + margin-top: 20px; + background: #FFF; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + max-width: 300px; + line-height: 30px; + color: black; + padding: 5px; +} +.swagger-section .authorize-scopes .authorize__scope { + text-decoration: none; +} +.swagger-section .authorize__btn_operation { + height: 18px; + vertical-align: middle; + display: inline-block; + background: url(../images/explorer_icons.png) no-repeat; +} +.swagger-section .authorize__btn_operation_login { + background-position: 0 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .authorize__btn_operation_logout { + background-position: -30px 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section #auth_container { + color: #fff; + display: inline-block; + border: none; + padding: 5px; + width: 87px; + height: 13px; +} +.swagger-section #auth_container .authorize__btn { + color: #fff; +} +.swagger-section .auth_container { + padding: 0 0 10px; + margin-bottom: 5px; + border-bottom: solid 1px #CCC; + font-size: 0.9em; +} +.swagger-section .auth_container .auth__title { + color: #547f00; + font-size: 1.2em; +} +.swagger-section .auth_container .basic_auth__label { + display: inline-block; + width: 60px; +} +.swagger-section .auth_container .auth__description { + color: #999999; + margin-bottom: 5px; +} +.swagger-section .auth_container .auth__button { + margin-top: 10px; + height: 30px; +} +.swagger-section .auth_container .key_auth__field { + margin: 5px 0; +} +.swagger-section .auth_container .key_auth__label { + display: inline-block; + width: 60px; +} +.swagger-section .api-popup-dialog { + position: absolute; + display: none; +} +.swagger-section .api-popup-dialog-wrapper { + z-index: 1000; + width: 500px; + background: #FFF; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 13px; + color: #777; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} +.swagger-section .api-popup-dialog-shadow { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.2; + background-color: gray; + z-index: 900; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .error-msg { + padding-left: 5px; + padding-bottom: 5px; +} +.swagger-section .api-popup-dialog .api-popup-content { + max-height: 500px; + overflow-y: auto; +} +.swagger-section .api-popup-dialog .api-popup-authbtn { + height: 30px; +} +.swagger-section .api-popup-dialog .api-popup-cancel { + height: 30px; +} +.swagger-section .api-popup-scopes { + padding: 10px 20px; +} +.swagger-section .api-popup-scopes li { + padding: 5px 0; + line-height: 20px; +} +.swagger-section .api-popup-scopes li input { + position: relative; + top: 2px; +} +.swagger-section .api-popup-scopes .api-scope-desc { + padding-left: 20px; + font-style: italic; +} +.swagger-section .api-popup-actions { + padding-top: 10px; +} +.swagger-section .access { + float: right; +} +.swagger-section .auth { + float: right; +} +.swagger-section .api-ic { + height: 18px; + vertical-align: middle; + display: inline-block; + background: url(../images/explorer_icons.png) no-repeat; +} +.swagger-section .api-ic .api_information_panel { + position: relative; + margin-top: 20px; + margin-left: -5px; + background: #FFF; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + max-width: 300px; + line-height: 30px; + color: black; + padding: 5px; +} +.swagger-section .api-ic .api_information_panel p .api-msg-enabled { + color: green; +} +.swagger-section .api-ic .api_information_panel p .api-msg-disabled { + color: red; +} +.swagger-section .api-ic:hover .api_information_panel { + position: absolute; + display: block; +} +.swagger-section .ic-info { + background-position: 0 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .ic-warning { + background-position: -60px 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .ic-error { + background-position: -30px 0; + width: 18px; + margin-top: -6px; + margin-left: 4px; +} +.swagger-section .ic-off { + background-position: -90px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section .ic-on { + background-position: -160px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section #header { + background-color: #89bf04; + padding: 9px 14px 19px 14px; + height: 23px; + min-width: 775px; +} +.swagger-section #input_baseUrl { + width: 400px; +} +.swagger-section #api_selector { + display: block; + clear: none; + float: right; +} +.swagger-section #api_selector .input { + display: inline-block; + clear: none; + margin: 0 10px 0 0; +} +.swagger-section #api_selector input { + font-size: 0.9em; + padding: 3px; + margin: 0; +} +.swagger-section #input_apiKey { + width: 200px; +} +.swagger-section #explore, +.swagger-section #auth_container .authorize__btn { + display: block; + text-decoration: none; + font-weight: bold; + padding: 6px 8px; + font-size: 0.9em; + color: white; + background-color: #547f00; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} +.swagger-section #explore:hover, +.swagger-section #auth_container .authorize__btn:hover { + background-color: #547f00; +} +.swagger-section #header #logo { + font-size: 1.5em; + font-weight: bold; + text-decoration: none; + color: white; +} +.swagger-section #header #logo .logo__img { + display: block; + float: left; + margin-top: 2px; +} +.swagger-section #header #logo .logo__title { + display: inline-block; + padding: 5px 0 0 10px; +} +.swagger-section #content_message { + margin: 10px 15px; + font-style: italic; + color: #999999; +} +.swagger-section #message-bar { + min-height: 30px; + text-align: center; + padding-top: 10px; +} +.swagger-section .swagger-collapse:before { + content: "-"; +} +.swagger-section .swagger-expand:before { + content: "+"; +} +.swagger-section .error { + outline-color: #cc0000; + background-color: #f2dede; +} diff --git a/samples/petstore/resources/public/css/style.css b/samples/petstore/resources/public/css/style.css new file mode 100644 index 0000000..fc21a31 --- /dev/null +++ b/samples/petstore/resources/public/css/style.css @@ -0,0 +1,250 @@ +.swagger-section #header a#logo { + font-size: 1.5em; + font-weight: bold; + text-decoration: none; + background: transparent url(../images/logo.png) no-repeat left center; + padding: 20px 0 20px 40px; +} +#text-head { + font-size: 80px; + font-family: 'Roboto', sans-serif; + color: #ffffff; + float: right; + margin-right: 20%; +} +.navbar-fixed-top .navbar-nav { + height: auto; +} +.navbar-fixed-top .navbar-brand { + height: auto; +} +.navbar-header { + height: auto; +} +.navbar-inverse { + background-color: #000; + border-color: #000; +} +#navbar-brand { + margin-left: 20%; +} +.navtext { + font-size: 10px; +} +.h1, +h1 { + font-size: 60px; +} +.navbar-default .navbar-header .navbar-brand { + color: #a2dfee; +} +/* tag titles */ +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #393939; + font-family: 'Arvo', serif; + font-size: 1.5em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #525252; + padding-left: 0px; + display: block; + clear: none; + float: left; + font-family: 'Arvo', serif; + font-weight: bold; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #0A0A0A; +} +.container1 { + width: 1500px; + margin: auto; + margin-top: 0; + background-image: url('../images/shield.png'); + background-repeat: no-repeat; + background-position: -40px -20px; + margin-bottom: 210px; +} +.container-inner { + width: 1200px; + margin: auto; + background-color: rgba(223, 227, 228, 0.75); + padding-bottom: 40px; + padding-top: 40px; + border-radius: 15px; +} +.header-content { + padding: 0; + width: 1000px; +} +.title1 { + font-size: 80px; + font-family: 'Vollkorn', serif; + color: #404040; + text-align: center; + padding-top: 40px; + padding-bottom: 100px; +} +#icon { + margin-top: -18px; +} +.subtext { + font-size: 25px; + font-style: italic; + color: #08b; + text-align: right; + padding-right: 250px; +} +.bg-primary { + background-color: #00468b; +} +.navbar-default .nav > li > a, +.navbar-default .nav > li > a:focus { + color: #08b; +} +.navbar-default .nav > li > a, +.navbar-default .nav > li > a:hover { + color: #08b; +} +.navbar-default .nav > li > a, +.navbar-default .nav > li > a:focus:hover { + color: #08b; +} +.text-faded { + font-size: 25px; + font-family: 'Vollkorn', serif; +} +.section-heading { + font-family: 'Vollkorn', serif; + font-size: 45px; + padding-bottom: 10px; +} +hr { + border-color: #00468b; + padding-bottom: 10px; +} +.description { + margin-top: 20px; + padding-bottom: 200px; +} +.description li { + font-family: 'Vollkorn', serif; + font-size: 25px; + color: #525252; + margin-left: 28%; + padding-top: 5px; +} +.gap { + margin-top: 200px; +} +.troubleshootingtext { + color: rgba(255, 255, 255, 0.7); + padding-left: 30%; +} +.troubleshootingtext li { + list-style-type: circle; + font-size: 25px; + padding-bottom: 5px; +} +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; +} +.block.response_body.json:hover { + cursor: pointer; +} +.backdrop { + color: blue; +} +#myModal { + height: 100%; +} +.modal-backdrop { + bottom: 0; + position: fixed; +} +.curl { + padding: 10px; + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + font-size: 0.9em; + max-height: 400px; + margin-top: 5px; + overflow-y: auto; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + border-radius: 4px; +} +.curl_title { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; + font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif; + font-weight: 500; + line-height: 1.1; +} +.footer { + display: none; +} +.swagger-section .swagger-ui-wrap h2 { + padding: 0; +} +h2 { + margin: 0; + margin-bottom: 5px; +} +.markdown p { + font-size: 15px; + font-family: 'Arvo', serif; +} +.swagger-section .swagger-ui-wrap .code { + font-size: 15px; + font-family: 'Arvo', serif; +} +.swagger-section .swagger-ui-wrap b { + font-family: 'Arvo', serif; +} +#signin:hover { + cursor: pointer; +} +.dropdown-menu { + padding: 15px; +} +.navbar-right .dropdown-menu { + left: 0; + right: auto; +} +#signinbutton { + width: 100%; + height: 32px; + font-size: 13px; + font-weight: bold; + color: #08b; +} +.navbar-default .nav > li .details { + color: #000000; + text-transform: none; + font-size: 15px; + font-weight: normal; + font-family: 'Open Sans', sans-serif; + font-style: italic; + line-height: 20px; + top: -2px; +} +.navbar-default .nav > li .details:hover { + color: black; +} +#signout { + width: 100%; + height: 32px; + font-size: 13px; + font-weight: bold; + color: #08b; +} diff --git a/samples/petstore/resources/public/css/typography.css b/samples/petstore/resources/public/css/typography.css new file mode 100644 index 0000000..efb785f --- /dev/null +++ b/samples/petstore/resources/public/css/typography.css @@ -0,0 +1,14 @@ +/* Google Font's Droid Sans */ +@font-face { + font-family: 'Droid Sans'; + font-style: normal; + font-weight: 400; + src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf'), format('truetype'); +} +/* Google Font's Droid Sans Bold */ +@font-face { + font-family: 'Droid Sans'; + font-style: normal; + font-weight: 700; + src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf'), format('truetype'); +} diff --git a/samples/petstore/resources/public/fonts/DroidSans-Bold.ttf b/samples/petstore/resources/public/fonts/DroidSans-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..036c4d135bf954ef3c2b15bc6493f72a01909aee GIT binary patch literal 42480 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 literal 0 HcmV?d00001 diff --git a/samples/petstore/resources/public/fonts/DroidSans.ttf b/samples/petstore/resources/public/fonts/DroidSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e517a0c5b9dfcdde4c4dee7569fa191b40f208bb GIT binary patch literal 41028 zcmbrn34ByVwm)8Vm)@7#`<703r_<>qourfQtYqn%y-7m$gd~up10n1L3<`=!5D@_p zP&N^jaTrIDfkv5#3{Sxgof)^sFz?NC9AQSs=Zp(8qmCl!{7>CZ0Gapuzu*7!Z@Ibm zR^6rQ)H$cl`JPi(gb_ko_`@J)O-=iZmT2&|2yIJ+(uCTo>Kdq_;k*jYowdzPZO1-e zUIgcZaMPLEwvLJ=J6CQ;=wLFbZ))=g5K`Ta5Pk~IBXgGyEdSB_$KFARZHKb`b8lPe zEV`?#0U_=qIKMiyeE!mr^X@1@qRR-0+4%z_%b^`&czy!hub98&_Mz#;iaQa~--pm} z>VkO#gI|B`JP-F~Kz;rKC{Vu2Zh>=X4@zFJbmi)$BkfR6Nub#cONQqTytw6kxL11- zu6b(d!0P4nLQL+5GUQw~uyo%0ZEMaVv;o>6-mrXlWaSettBVoZnhV!_viz2L%kTcZ zONWs1TX^PYh|mhQtjDi>(yuQ08ad!K@O#75^o_8eeQ$T{U`#JQDjtDz38F|T{NNt( zNK8+Pp#BN*QQ=APy7=GoLdkiw5V5EYXh0zj`B4wN)qy^S5x|IMQt%F_MeKey2#-vR z@9B5Z5dJ${Cl`rm2}4oLMMQncBj+r5a4LCtPI+4;f&yoi$N_D*M?8W*b0YjWsblKc zG1xf;f{_AfAv_CON_o*kbOGAP`cRNrfYyliqapS&v=qOCHc|^vD{Q4u-U{0gbTdRf z1Z5ud5Oo#lp==Ip&%zdgja*}bZ3Ap8VC#Wx5!r<6JtzdnLsJ{Q0C_}Tpryyc z^z0sVlGzI5poR8D;MoPRHN!KOqC?PT6YPUb1xlmqCayA%;mu44^)XR&ls*A%j=~m3 zE2t-so$(+KGlq^*T6B=oPMoH{6^@UJeds7DXC4!-KT7VQbKw31s0Z5Wg1STW1sJ_= z;r$4?=}(c2-URQzKnn1w~JxLF!3VMSna= zf1o`nW6;DEdZ}>z5Y*?u<`DXF79C|vq5UvE_z-eKd5C%oRlxo2?8hhsTLx?npna7< zcS8G{Hqm<%F)|ip9B$m8Z)L(*j3Eu|c^E7I)Hsl_xN(DiY!l95EXa6FZNgZT0tgS@eneRP2lxUU=JI;9))NM?V%cB9AU$<*g>GJ0z?ROrm!V| zU2)C zGXd&~NZrJrCSIP{IX*uAm+?<8K6f#C@z}*97x!G;Hu)~{zW?EeMS>y{YB14EWV`VP zEu|S&B$h~Ja)nZ*<}_NJ-e5GDEmm8CJ<;KGCApJ5Dc;nybYDhhmOncu5X{ZXFDNXU zR$NkA7UIh*Dq+T`4NtGDZT8jchlyr5AA#G$4?x3 z@+Uui`kBK=es=6=^!Rf>KQVUl`P0Anw-?bZgYy=?z4}MX7o*!2q5JkD(4+HL35{HG zCwl7C`nk)<$w; zwClj5kG%ZKYby|X^$y`Ne06tMM|)dqOLJ4>jE4HU>EYU%>Z;0$az0d6T2ef%sIVYE zFDKidm6?(1P4OhVlUxq7Uc;$XO1Vrb5sO%crZCE=cGpCl(UeG(NpXk6ndIC(00jd# z6hxvmD&3XyWSJT0 zxJ(X*ayW{nx|bivsbyF=pi--+9jA~)NnRVJJ=Ft)(dL$}>MEPd)t#9!J*slUlb{M! z3XhD6Dx+fIkJJf3l8%l#)gG$-Oy47vvgW_LyD0l0SyUA<+?rl`gjO>^H6oQY zFj8B#)VQ5BTOwNqPEL%@aXUHpmg5S=mgUtzVbt6e#ZZ1~t1Vh{e|MCNEWpzyJ6qFQ zAJw(Y>WWgH8s~xm=sx@lxm|@emj*f@5Tpj~|DUT75CX^nG2bs6L+UhdTx$FhmQC_M!|5LyesOjhI@h8tG5u|B26IAa<_E7fPxbj zkLNmV&jnE~>aK#~sIe0GDy4c$*Wggp5wQ(|qzyT{Y_2HZ4FlZm?wZ$47!4?tb`f3& zKj>DJs%-D7Z*$kT^mM_fy1IoLQb8Wac&cxDjJwMge+;l(RN|31yC@so4Ht4y;H-fI zcSQ;Ojfy=I*f=1#P)b;}qQu#SZD{IhXht;6Sv{|6@;Y+rC$!~!xQ7gP(Bnx5V76jo<6w6-PTni zTn!pzt?dr-ZY`?E_3agz8KBH6j=S-FEysC$Ut3St3miiw>N*O_ zn0Og4ly$=oM#;PY7!nkFwR4b+&z;>1wnVxKk0T>c1peVDc9#Kl+-1ixB~nCX?s*ka zxx0dthe&xSUM?c#Vs}Lp8*ygF9b33+_t)l31bySA-_jF~?f-oIpVYd4;K<+c$%+5w zeScMOH6I2)==j6L?>n+Tz#m|`drk_sNJ{+YW)v}9&CD$<{wn3Yy3O$<2&)D zd(961&gMHQnp^2ynZ1&xR^q>}#Ij*LF^r!c#(RhHt-~9Ksrq3oTZSi=;is43y~}X@ zGAy!UbB9GY;Se(u4iWS{G#@+W`{#$|Pt0fdq2{53L(w57G!Lui9h*n72L~?>QZ(sg z)1XLJGtlpIMEYwT{mn^^f6l_6&BE=o?wLhRpM{HO;qQ9zmpyoK53cXQ#XVT03u-%9 zARQB=I~+7t(;@m8O{-@*X8LE+Q{%Sd?I!a2SJz4h>d!a6xqkSSB5~ z7Il1o#pk$U(>l~@>`=d>rqt>QH6(DXuC1N;)M~#@pC-IKs?QK3^{Ymiz%ygpW zSy6mn)YC@(@-014(S1?W(KD;-IK~fjZ``&GRoLsJfwrz_#NJ&W9fSj(9E`#NXFqO4 z72P8veIqMx^-cb8|H!o;8u4LL+&_XwR>J-Vb>tjs;NFpum55vxuZC0jt%N&PLV=Im zIYKUkb2t_D2>xOp@{O#-Bjm3Sb|XF?{KG!D7x~P6B0Z9$M(}h^=vH=&eGAzCh96Uh z^awDFiO)#k^}jKF?1roU{~yM&0(1n8q0{g*@CAim2ik!4qQ}u%@bizte=FcW@;ilI zgN^(SLGAs*(K&Puu3IY{PyLCa7Z4yI@OuP33H^v$%kkPh@tyE@AzFrZpc7y!PrVtT zAv6r$$R2bCT>!3$|6+I=ym>W>fyNL}jT@dZb@&Aw>_PWIYxkhdSb_FnGx!d6w2SIN zchlspPsLkI-Hv{TPGA{AcmrAr>~~Oj#8SBT=HEj?-Q8&O^$K?E>Mo?2_!1}t;{!kN zZLim&)A%qAxDxzMqcQv+csCsF0Vn1tI~F?~8;)&2!_?1&`uLw3v>GFPimIb2I)(0} z@1;-Ee_@>F57PtvJ!r8 zUuOT$#FWI*#6u3`Xovp}IFa))E6wNl|x?`_*J~a!qohC()DddC>EAN@B{c zlv7^DTj9Oa`?B{+YDMaUsi#x_n5Ip;FYRF3$+UOV{yUvYPfdR?{mJwf(|?=(g-_yh z`U-u`zD2&beSh}FGK?8n88sP^jMp>6nUT!ZnL9EMXTF@J$a*U4OxAC+KJ!z4t3TlX zqyL+1Rd#arwCvXGMcH>}@6SGwQBF35)Uv~J(8AEF{RH14Dp@9$g&8b;L`TsmB7K==Xc0w;X=q0<=nrCl z@1@>eZIS;{PBy09vZcL0jVd<*ZeAqRSx@1kj|)oe_( ziy4v0B(fmPFzj)&*=2AfNEoSJuT`t$jPAIsnZq2XP)M6)SeAgpN~}DpkT41d;*M%8 z22DuQr1?-oD>MoXYto5iEb<3KL2VIO@x7X05Qr2IcD;cBG?uj4-^+buDhg<{rXo$y zPY#2V$3Z{oh06fF&F&B*i8RUq(zx1ABE5f zKA7uI!)bmOc8QkzOK^!l5hv=LVk{Obvz8^RfojwQSXBs%QMpoCu*jTppK$S82iyM`E)Jt6dHkC2{5IMkFeUNW@CW1T0gAB?!1E=+_V$ z`TKeaZG6HB5KjxF32IYnTLMFocBgA*WS+l!LuY!< zto2P>2A{61D7?R}YtyW(sNPo-?41?UYEmn5y4&!JHMg`EDE|C`RBP27;apY?em1e9 zV_9A0a9f`AM3hZ;?6jnbVm*$Ew#8vrL1v0JwpBHU`f+lRKShIAaNUH@Su`~9nJCEq z3Q3V2c~LRYz4zG?uIwbpUa+LBq%2jRcp~s~_leYUbvb5F$bODaaC)acTTkmlqB65* ztl)XqSf+&@Q$H^rvxrgX(pQ&+Ekq>Yt4qDyAKV86n`oNS%m7z}7Qcan&m6iq@O zBC7cRSR=)5uzH#g9B8h^=aTD2nx-#rNO4bJ(OAD?h8G{1ta;rHHFeADCuVWXY1lx0aRNdU(9o9dY98PmhE+Whj`3TFk*ugI^kv&AK~07=%O3@(F`WMm{c z!#Lbw4|7S*Br2)AJV)EoWEAdI*OnBsOwM$Jx;Y2=FNHwr;9rRF5+ND)dM_OH^$dWr77yfjll-UM7tMs_b_?ooj(hN)Y_s2}B`3}6-6$*D0 z=FcimzT9}nU}t*rjG1$b>Q{lG*|w^lU)tN09J}9MQq}0M7@pIS7JL6lW^IlwZ~kr& zX3)Es5T8cre8L&_UG^eN``KZZx>4a$l%Q?}di8?JCyG{3vT^+i8UeM&PE$U3jt*IQ zrAYO6>E#fVs=fpGJtVN^MC3*6yJ;?ZF#k zYxcy}K?~v8q1dBIeE%zL7<2p{RRIVLKh8>2323ljlvOQrT)MKO}# zif7l|c5I|5IDGuBRne8x#&wAmi)YMOQtfb5FPSl8aYZ8a!SUFCzdSVbGBzJSj?HI> z=ADiG_p^I0?yj%fb!qROi@WOTc3mW6{w(zI9Q2U`y1IBRR%4Y4Eqe)1VCpUm7C48U zn8%C=E7c-o261_duyHS$ST#YQtIMq+f~lg_*o%YIxhSS-tg+e>4Z9+E-5z?UZ_Yq> zlGbXrX>VTxt{-`41pJT7Fps%W3GZ}EhHW+v=fO$C*o8G3C&pNk#3@INVk&7R_$r}G zfzTzeiGWDLeE&IEM*d4c^)#FZN^?@ZioIp@bzKTZ(B-SkabNvyZ5c_S&bwd6!(OjGfGw^hq7$>^LEba*uQ&EOYDN+0Ro>+ zd`5o(Z7o8*yxf)T%GO$krQTt}XMC#O6h=r|>@3DjTC5d0N1k!v+g(`h!V;Iu;LjMz z6B|afDj4J-j572gPCC*RVDf<9M`jB$Z%is5ush=XnF0n041RpfeK>FG5ayAoj?!nd z+E&&cKUsdyFSjf`+cEQ3y|eF~nU*(aTldC|Eq8TfBv;HXT>Rs@*(trt*48Zf@v=fD zGJSPdkwRqr`Oe6}m3&g>;jBPv-IB_xfl%V!)ViV4+}5JR%KWvC{4{g^z(%lnO%vv{zBU@1JF68i={a-Lo9^t)iauovg^CP41=O?SpLv}N>GcIm4lcZ9 z?5;|gE>VBPlx$Xd8gFaazL%9qnIbCsWUN=DBK@Jz3eX!e;5i>ycm=#OXPF|YoGZts z;$cX}Ku%CT`M28b;*1fmg&PqQJ5I1j1T_+XrU$^Ef$5SV@?bJ#fj&_&;^1}p5Uh(E z6bhMAR~YhUKk)iST^5+p?k-=?=E2HzJ+06eHS`zv?_W|}w(`)>;IYv<{C>&Yu-8>F zx1w^m(dU`Ij9Rhq-IosCUQMxL_7R1`*SKWEuIA)WrnUH%pDem%Y+Y63!N12|NNZa+ zvu3z4-CrNcuH3k&QW!VLm|#ROZX)D6E&_?V%xj?gn3Zr6OtI8TI<9eqsSLygOf8~p z!Nq{N0b>gl99Pjt#xJnklRK}x4^&wMbVHzTy=WS$1N*Nn(H&T(mZgT5y~3Vnsh^#ifbjJ4>{1_AkuK8Eh18VoqNQIVD!aZTfx7G8-DZ+THTLT z7$=DY(}rlY6e_QM_lkZa;hA}rw?93!?3r6jGaD96%jpbxDsS0+&RMk}T(dZw z?43RoO1x_n8yBs0m(2`jcZ8DfV&BT>x~Hph$;|2mM??Q@(`WBqP?$gO!P)hzBXtRm z={-v;W^U-p`2NYf?vUGEKD#itJ8oYU!~oVmB!Y~u70kC?kvY@H$w;l!XYqs7fChH%`JN-F?SC+F9E z36Lhz^C=z2FjK6Ul|fi>)6pRxtJD`ZLcK4WB1$_zzs0+I}M&F0P2nn_ycoL&7{=#wT!TqiH(;gkch8 zd-VTmUhr~aU$`H!aB&+W1Qrlj8NrVUpmDRAL3G>Hv_v}nFJ{O8W`@u^y?HK`%9ZE! zL7h!+(mu(1%7G>Upg4u6?+lrH-t|y6y%Z6AkMS-QZXgv z;o{d3{sqEMBYZEyYY<+5a0S9e&;sUX4%W|7>%i<|IhGQ!Ec)4H43^R`v=zZC!4@JA z1L69~A50)`6Q6}BD%ghT1EeK%`|jPbp*;Y(-qd$i=sUOyEQ2wPhDGZ{R5tM(sQZ^zJRYJhjyuU~@pfE+mmG{ahGJdNchAJ1Xb;FU!4JxKS73i?s<#Gm_T)7Q zrT$W^mz&fGgU6EjNP&)PF)<+0LI1}DVi7Dbm={3}LlJ?@1Z{Ss1xD;u0mc)nXgmHF zezB;%6@lz3pCPfRqp)pHO{nGcKriSfTYhtXZbgdf7dNvIe#}!eyLm=>T4i-pUUpNF zBR6ShdT}tab!O$lo_2p`WvC)U8+-3&%bn=0r4x0c^)R>0L<`U`AL=qiybcpy=fx&( zO^w$?H|98D6c+J|a53MA8#5HMv|{aGf?Dg)zNCeTCjpDK3EBiczm4Xrrvof^Hgr~S z@%wuFdbzUzHJueqo3k+At~d++zaJnwsNl{Dwp?7}-K6PEv1wyFC>-35D|>=$!Y|9aR#QMiPq{ZJdP8 zt4dXuF1Ww(fe-dI4WEmB`NZD89BfsY9hz+^8AGpN%X6o3^%wWe`?uKNViz{9%4{2{ z9+xPm^+6hykio=!z@7%FgUm)gS;W>*xJHjDJw06l;0~66)32hz#u4LD@ec93Vp{Mx z%_3P?%!$QDwb5bp8@C%DH-2aoiD>|r!D3*jew9Z|r!Y8?BY{1D5!R!w)h>hGM*j(KNpn_sr1wLmIUWHznt9DH=?G`cRa zxVw1ug7LlMms7*@N(=icJ!HPbXf4$Z!d~)S1G~dEmu)aq$Xx2rmj;NAwFD3LVNKA9n5&Vo*gv0&J&5-{{O!^H*DCHhdFz``023>yPhW_A`inkx zZ1&G%UyVI@VN2!rn=whc2HF9y&@aOK<*0&B(MzyIf*CYlDU$WzL4~xxNrcs+VCulrM?|>Oqh;Z|TVA9aETrKy@x66;o8L*o zGfMoD5<8Vx!t}6%3c6oShFBsK(QI5Z1qHl#QmP4mfj9$(0>~Ue=rlp*!m070Q>Uo? zr>Mi@oq&<>A5lJ|{9ehlE3r>mtgKhk4ke}}a#;^c zy$SyL$%$`yPRT*E2FxWXBcf>;X~Wq7{39TaulH==Z)XGj#AaC`z$pb9Fw+cTjfol@ z|Ki9IN_*tUP#Uu@ZD=U%`vqx)mnOooHFyNtp+kN?;X?zC7?v9-zX7Xz6oB(8I0YqE zC}}Z_8^Gn{EhN+jFUZNJldhv0&{yz??7@bFq{d87eQC-siWfcQmK$~^K4Bv?6hIE z4NK(Y0xMLd$g!M~^hgJ-W{f$hHjGtrTB868jKPUQBCub;7l6no1j>Zo2YNwl$=m`M zlqTp4`hXxl;K$xx7j_#>07emz28ckoGdI5=zrYRXeXLp{0+;Xz*5d;r{211sVj!+6 ziIuacZhB~I(bYZd*!Nw`Q&;NfRk`<;)=vMPiuaRMK|f8Xg-=tZf}L*wd9g$-c)JB7 zP{EWJhLx7~$Og?uiW8Z`l$=(Q*2AGvVBA2!7lKw!HeF~qG+2&*?~V2T$!Y)z0= z{l|Dtyr~zcU&Gu^kjbOWsTmFB4-{PmT=)BihB{|uWto+B5#OKEs{8uq94r&0P@vY}LWznZc1vvGYCA6r7i_)PJ?qPOgdmV=)^^5ZWbZD@Gx z@{b?+>%o@q-^*S6lUr{2$>Km@(V<&zd2&fE^}*Azzr4N#<}cIX!`SfZ;>EAU{`$=3 z_aAO)dHDU!TmEZrOUvH>inp1_MiA(#0uHL-GYqp*Q6VA$+WEG3Y?v3AM5Gq?NCvIy zNVbRmX>nc~Atu;8wD_U$=^7v1~v zT_xj<>wCA($~tuPAS0Xg)cP6Y03sJHJs6wstX)YIO`~05; zHEG)ajm#VV`15;G`nH!mF#Yv)6!m{g?}I>ZHLMP}o9~pWEo$mJfEOilby%&CDzI3A zjk6?TvzUS`1Qq^N^__}Rky@3KQznKIwg zhM7vxQe@D?@92NfINyl?c?ja9$+*|F}?)>O#FyxMTft2LjU2%Y=!jVSKD5N9-Ql<&R{xUCvDhP#X5r7qBLJj&rGZSyiCrqb6l{hE?qaOsW3}TSq zGF(8v_Ik|pi`!djcu;!%>1q4>1IBmG}gBj*Lg|N%mBE+C8k;=P|l3(3`(7WC5}YWM_r=P3H>#4&{v}pK_-aDGf4y3 z!?Yv~dpJ80Rp`Bj5CQgak27vm5T6s01xg7p0M@ZE!CwDENTW-nAwQr%2Q;D~+a$#+ z605M7c%+jxblPhltL+w@h*5Al`5WhAJ5R?{R=Gkhm&+`wzn_lX_1cFDy+#GFw%(+K z47{fbmoMx~N$Ht8KZm{rCg8)tCH{ikV1a*m{#Eeuu5QYjJ7-pkXU>x0oJpRD0IlU{ zJ#UxFIGGF;fE6Jr1fZM(Xba)xXcY1dX(8IBC$J}2T1vH&V2bOM3@oq#N<0T^83k9I zV^S&X1?V0!prB~d-W8l}pi6*+O0Rq41hfLH3;4wh<;3WMAgYx3;$U+{&c_~uzJkx) z^Ez(8jW5P#QESFGQdg-{t z40^-(H0u4G%(^Fce!o8co>7a^Yi}CIcKa8CgUDHpvDae3a;RXAL2c+MMv3pc7F#5Bt~hJhq16{0Uy?m(KMMKMTo?oVI0Vd zL2RTr4gYEgzx1QnvDl9(55YUB564~Lr(BJlrW+@SA*3CH+$jvHq(X9ma4(4<(T!J+ z(vW!~BfD{>(vGV(Gcl8mZRKbSB8?^=J2{ci!iAm_xJ2YDeTTD#iaIwQor z&-t>l4b1Nn+PYe7hNPC31~PrZO?@zb9$@+a9|@p#Uc=$ET6agTI(Imi5@m-K){_(O z@`*}K*vbLFTCIxCpvBm1PjBToJD1!{iH*oW;-{AY#)7a?Kwt--kGljM0X_>cc)iz< z#rzP!utI_n1cu}ItHuMpS8>|Hny5_@!-H<{(*0+CYNy*aveZ`R(#U_Pk z;}``$iD}x5h^2XPxy@F-q-E2(bxdD)`)}^wc45bKt8bcfE2Prg=Wa?$O>viGSoQ`N z99TH-(ZvP-_3L-vhE8A-Yo&)_tQ?TTWVS0^{jPN`igRIOnC*1xT4bE11sNy#m3VOi zuFqZzUrk~SV8XzpOkgWawa$y)UDvSj*`dbWfqYY8$u#SuJ9h4B*^r;VMjzUIVb7!Q zZ!Sx+?3d}((l>u|{uPhq343Cko+R!|3G~cHxx76E*=iFz0$c!Rh2@<&4nqR+H5)n3 z*^EWdB*ET_({hS0JlEJF9(6JiUkHOQq;V6TD466F!WQikWAThgN$>t;B_+%D_X0^v zjNrjj%-BP**Bcg=JE%!=K9p3NWxYnxjA_m!hqZviC%*e^Lss9mzO*?K!)Z841N2PQG~1GVNt8MXi;|M`j%FFBpQ5gSZ%w))#C9Yx z?CWGsr0m!g&xjzWr3|ky|#( z@n^Q8E`8kt|Gw$I-|lX#UH|iCE6$F|baw3%x&*C+e*eJksuj%{0v~|rQkO*z$nRaw zH@;*4&`w3{qxK#4gLcMY580dSw9U4Tdx@hs&Pvq+RIFx6ROlGXKsX4pi=7ak+Cd+r zVKT&QlNedC*s9h;6pkXYd?^GF1Bqi3JOt)EkZQ0P37$(VWReTv&V$h*qc&+213CgT z%hs=-fBEIijyoHR=X(8)>8YvHQgmO_XRn6nv-MSx#oOB5Ce=2%Mwi`I4k!^!HOLdw z(^p{(LVy_cp`Y>=Wq@s~EztTS1>Je+wBoY1iuvdFhU)SVBX6E#NegafRZWcY?aB9W;c~&G^$NlY%-N_)lEq@ z3@%aVEcBGPEoX%gxc?H^KVT%E1;ZHhu3#Vo{smbtXN9a65-uSg1CYP~F5Xn?OFT!i z_{QW5A<_h#3?RgH=Yix`PP!C6f*lL#szg(OOF>_KEg{9la0)QZfPDhMKvS@Xpfe$5Mo2;-_)Jg}Pwg-oH1zC+%UhOJ+Q-HuGD~&q z?Cj@XpkBHA?%bhWk@0h&2Ln5Np>+LA|2`g!>zjk10B?nF3DA%+>*4jB&Z(mmsFuO) zT5+2i79z#xA^*pMOGm=n;M_pEetta6Cjz$)4~~t!TAFF}+BKn!%u=Qfr64bQ{JxwdxlT2+6hf*zW2C)Oqfk0#2^a1gJm+Afm zFx1`n>Wb`;$(rxBWw~`@N#Uis>#d0CRq|ajx!PMEWWM^|JY&sFzqtR#X2t1GUlwvU z`yimtKs=ZhRe{}0q{2j`f@4`u&G|V>td!Qu+SPIdRxkjnAO~UriWAc!nSlnT7ZUct z0}^JnDGQfm5do+MtPLn4$q$S}iZt%rggxhD!}!R1vFOqhCpfC$FkTq*jPJzXw#Iry z`tkhO=kfO8L1-ThIk75^bwW~YEpBJPA|d)j@D1a`K}GlxP#keg>MyhupH- z7vy-4{7E^rS&rw+SIH^4+=yy3I1 zGa+abvkpvb=(Atn_hyQu*)i#UL})O+4>ue20byo1VbU_6Xox6muA|gObGh@ ztiJpEC(Q<8gaKWl4;1u)i}%6jWIeUXg5jeI8#{B7ZAogUyxGQOAm|M!3KBlN(eMF_ z_$Kqlbj?7647gwc*guo_wThfZI; zFYyK4G@CPt7)MjG2VeB*0~(j}@@MInX^L0_@inP7u^ggn=4^AbdDKj6l(njMlUl7b zXi4NEbat{Y0fL=C;0#p8bt6E~@Pt>X&GxX>oKS2HEvQc$+hvqkifX%c%f-a_eYicY70+@OuzM{=ccT1J2*}0aoMKdxUd8Bb6*2g{zt!G3MdW%RQWu1*1=TU208vlHC z^Z15=IRalC0-Ig}t5&Tc5BWlA`m8zo7E*7Gm%@}kZ&!qx_C0vMgkJEYFF+eKXz2^E zMDtr-4fzrpB!Rz3D#dCJCcWphhA`Rl3BasCH%|AG?j7BiI=Weh)jGefNk@w*0N?=p z!?jW@1;{&Y9T1eH@GO}ffPD*nzOw@K2f-vzxN#s0xR+pDcvWok>2vbLL<=Kk&54Qf zbEjjQnY!^uT?>{(ESAWU1ukkXG+DrE**`&h{pgh!kRRHsfR_4^L<;ToLUI9sfNa&}Ig5Ez`n$pJlhs9O!pZi^q2-&F=4l zvjt22R9IjJ444F>-v`>t(Z~FcHcRon5FBRkKNTDpIQcW3Zz=p2 z3Ks!nh8MGISZXGVr?GV`HHcSYstZG=Sv6$pVjvYu%S96WPYM25g7YEfE#{4uu$Zjl zTP$89re=!qG+3t##uTT_R#H2ZQ6;5@)hoY~;WuRXlnhG&zAKj3%c)d3X5|JjR>Tlg zCZ-NCH^77F1rW7&1+3M96h-1uK!O1QSIs2tiu^dC3{tmYjN->R*{HV{P&%7nfDs#P zU+nRBu3UL1_9(vfo!D2gzrRBTDN}3+?;rnk{4IPUHi;|Bz=!$}Xq1Eo$C5aS1gS{& zIFS$`i`))eqvq7=4)N&YgKZcfJPWPfr~GA%Y^FcKl;jNmqRLbJi9B3==I`LH07 zu*_LNKz}GGpmTHr+&jq!B$eS>oIHeO|Mnj$y9Gv_HQMY%Vj2_5J6AU6HrNcjJG&;= zqb`DBe&VxQl0sogv_d2OWPNIvxTR5Rg=@SP>R8O{|*Y;E0kmVGxT; z5SfJyh6>hX!W2d(GT1}k$Qq4F>@siMPMKp;DmwlZmAIC!+Wg?v_u_3M>$6CF--~$O zV`bTYI=z2$>(t+H5D|8(zy2KuDypA&-f7maE#I=EhXxjBH9Qv0CqX{7JU zU#sHasbGZzQcRY#{&}+R;2jm@ z5x9rM*kC~`bQv6E1H{)z^hlfsnVOeKKd1WdDvy+1rvNTqbO-cq%|hHD&|$YghuCwu z;Y>YHBKE96i9G@(fS-n_I2 zKtZM7Q{Yb(3e0nNlLBfhXH>E}SBB+@C9Kk-|G^m$9@eSHwnDCSANWxwRQ>{dG9tcC zJsZ}jM=fV8V0Z2ZJV^)#sX+`>2-c}15+D!*UfkDv({gnXRkv;`6gO$eUx3x>U+Ubj zT0P6H*SoV|wYpnJ9s2$gHhe8Hbux7u=yn=xvr`aT^`IW!qor%q3?uUxyu24DhLJ2= z#>?K7F(R35M6Ff3Mp%j!RE|Qe4Xar-OLJnFi9*Cg^T*da6D%ptT-@s?s85GMkn3BG2S;OX;Tju2E4Q%b)9KJ6r4}i*I%Op_wn?&l3x0Ur9&u!lJ ztNZGeM-I_0lYm$L@;CqfipTsTqED&-zi_i}qEvJ%uTg4--DIyN$ZlfCmlgaqXAEkES(NC4z{GM`{ROK&iZJ6$K0FH4Au^By0ab(w zO`{I%P>9TlY7rw%JBn=BreuRh-;sVPrFOvQDxy-_Aq~NY3TQ}wfXU&~>q3a1Ebo84 zH{O3zMo7>LGv43M2(n|d*BY=8dlq;Ci~*nzK~)84mB<4GU&%`UKv6FV%?C+>6DS|J8vH8~xf9HK zFzkhBeV!{1N~j?2c`3H;%h-9G89V=MeD{~w#xo?U2M+mTn`2k72zHR7Omxc)bAT8X z@g5|_*|3sexpY(tF#cg!UBHQX@u>KWm=%jjj+7`q7Z5HMkYI2-ND2q>PA6vtGA~^H zDrUL*52CwnpZJFE15COazAbw@U(a?*T54P=4K*b-X=&Vq3mV0cjZqdfXBh}6P5d7M&cb`a<9Hpy(||{lB}@?C zfaMf$43rYqCW3hak_6oJ19W;SWkZm<>&PwniUR2G$s`ZLe~!r&nvh++*_t`cQQsjr zfscj@%kQt9J7g~E2Pd#G-JF)2P~A|xAuD1P z_Z|cOJR`c7WE%3Q85XP|HNDgHILRFWMiOKemi`DT*!p`ReTsmTkdZ^+QO}KlB`gp4AwU8aj&(6uq3k34=vlW&U8ypcFe-OU)P$kS0<){PJDO9qr zS=H(!EBu$z@CwSsXL51w%!*f1WN#YKn+~+%f84q1=&K z`$log8<}J+had1eU_Y{k0frmHnEwH?iiI)|5Dw6w>si=jA{H`hCzomokqI1MSIEG~ zlQj;^%}Rz|Q`*P7?4|vcru?Epi#aE+#67fcUNC>*!-JuFipz2buD+N*u;r&`7A!dP z(=7w}Hy$sYckC}4H~!_=ys157&uCj+-?+NfCst^r8{}G*xZ;k(^XL6^b*0O`&YBQ= z@ur7to|Bh1XEQwf#;3s3gt3Kh@N!fIdk*4LnXux>pNZ8Mk(~n+#IN={{7wFK{$qYN z`J&upia;7fR^~@)wH13mLZUOekF0+H>REsP>ZKx>WC1?|-Aoc&If!ACh%5#M7xHs* zg@7!e^JJke(ar$N3xQZ77^N70lA!ha3*EkOu6^6~xuu2KJ?YuOTT9lidNWv+rVXSg zP4ii3d#llHQCfpDfyGF*yL~=;iIm5dk%q&%_)sL%>scF$)9|z-KglMH$CSxHbQo+Ky z3;3{%kh3%S=@elp74+_PAJ`OxUqBZSPX(K3ofv!KyH=cc>la_#8v7OAi}P20{`tz- zFR3*ASYzx^>`)_q#CH707jKb7&O?D(O z($i2QdYNB)$auo|s*yfndeuakjJVaHGO3KO8B}_ML8X!+%WGEYYm9+`upP3VvA%1i z#oHNdWvq;&`W2Lk&tw8jRs4!WWquPOqs?frO5U`=#cHd=>bI`5Znr*e1+f!iILjf5 z!&+@tTMGY1uCDHl6e4#d(=sl1D+npcViXeDzft~#Bq1mzvslq4&c~s%n^+(BDux-W z7dVLkus%$_#3F{=T+pm;ngkAk#r`*$q&^AHYHI7Q!4KLpiyW1CrgWD~_T1b3kAC{p zW8eSvMhU#}_+O-vM&atZ;`Q0}MXAbVGd;D18V!!b4$^Dyj=l2#K@Rc66_K71i4KC6 z`#RUQ;ayo7)_g3d7CgxUqQ)}}aj=WmnLkiY6B9KG!tqwJs z#=AK9@Sj8iAM8+IeB(Dels{A4@YxPfF2rDgiCKuf5I7hdXqc$MxCPe{QVVbVghy;G zec<|MJYrX1aUq6piPup}!8T6>pVAG-Sf6@^pP75Ty5%*OnUttA6h z9(#UMa2LLxs$2e1Sx04YQaK+;>B}y6g=Q56Iy+_+JPPe|P({=NXkRw0UM-;!__m_N zOAy$)BntEgwg;#HX{gt$;w{dwine?dm$H8AhgMc%wQ5t`A7}hQ`>|U7p^yY|2^1Ic zuH#K74JXSJLBL?K7Z4BJ{u>RHYk~ydQ~-&Z%A66t9YHm=RCjfyw%yfPv!*C?Pu`ra z#gRF4X1D9zep~TCu-aZ))sRuWu*S`N(zwWMTGUuFSet5bY)ndNn_gGBqKS9<6IE%R zh{`bdbEJy*x{=HNhB)S0rR|bYNprQYZKZNtu`8hByvN76o4m) zEW}qI2$3E#O~rFZg^Uo$ArgFrsYL%E9$Ylsx~wu`>>-;hd&m6nBKW8Yg~!J5rt5(6 zLv8I@=_#r1j`Y|W5y_WZ5xdF^0S=;rHo2IG5leuOW)=V|R*4@*2F`#HV1ASmvPD@) zr-H>Ngug)!++c2#uycbn*-Hh9~a z`yYC3HCRZ59uHAryptyJxFB!k4DqkRF#qqLg!x!lto=EN)y-PqUzTQ5=l=))-3{^V zeeiY*%wrC{PG?xB-43A!tw?zfvWcwP`{8pos<6SJlWl_~%9s2k-2tox=r4h6h*%3e zc@5+fc+guAgpUN^ecSdJ|DWo<1+J>=%6so~?&ZP-?nB-naCpDuUf_zLfcL5hh(r*@ zw*}-)co;yeNv%_jF^@@1rkRW%wY8HN6HPRBLe!WhYKduHZIWrHOun>Lr%8YFwY16f z>+qYI>b>({`g-KNPme%{% zt^3+0n{CtA*6rT2XZM$}RVd1{9=6X@n(ec}W!DTG!>@Ic5g$1?$i&FlA?F+y0^MNn z!0a*N<7@MfgD!Q*n4hKa@f%_q1zk1tuNW$3gkduyJ@#tCMSjZtE*Uy}k-O9V9vMHp zP%^pO*6?k7+Zy+dxanK6OPp{#gwM4lop!>sWU!Bh=+|)FUTEDeV0&R9a{>q_yp{CdR&oAD#fj&@IV3xPGt(6E=%hHpnX#h)m!R#xz*+`U^a$J`smnwPugd1K9k!^_NNh+L|G zlj8huQ^N!-U!nGpe{NnBNc4BC&X*g`5@>WPS(PAzza1~>!%7)$iU?-`As4S(TOd9Jbm4SziF^MnPn;?J2% z-_4ScKFSp;O9}dPc>YFYsc%pPAU@M|&}r`kus0L;vV4~!OP|N`Yq7j8mb+rP63eko z5NzdGSz?J=Aqyehn#d3?_z7YA-T;Im`-Mx<2Zc2T$c>iJMq{m!n~g6Sr9cZD+Tn(7!rnyWrGLZj zBnuo<9b{q)3UsHvcTqYl zzk^8I$XmaX%Kos6f4xUUhif6X{4EL1i0HEy{WapGL7#LX6Zi6)_#yZlugy-4R)jE! zbco@S-w?8ssmacH!CR=QbeL-28=Z%_F(29SPe06@DsOo-UZ@Vi2~pnEv*I7~{EILC zTcaTA)Eg+bil1abEl;=SUsYLv6p--n5JgSzBMVEx#8y!UXej8T74BDj^LXCB4iwM& zuuuA7&zF7b(0yAStVn;-C;gbmtfeO+Nxn4@b#@{BZUfapEl;;6bVePRe;uH&>}X>Y z8*V}Ww%xTPHr8a_8rYGfB=P9^TTB)UH?=Bpmd0r8kOAWg+cCeyt}zcyu~A?&GMwLJ znithX+uVu)pPizjVaAhF8_gC7D-6mEwg=34iYAZHLd#RqV2L8;jwH>YF#8cTrd$g! zKcWj1!R5TpeO3gR%W2XR6kC2)#F4foaIgUm=MRext3%lW+&+4GPd%VV3W?O^N+fW+iGnuNX$ye*5xKQBJwO) z7Ze+*%WVt^(!%&KGfZ2W3NJ&bX#@zX4`9a`B1vee-=#$cu~&(<2!XLhl(tWa-h$;U zjI@?Y`tWUAzeEAAuOD3V(3a+{6!ZF}zg=E;^SmujT9G$UN#U+?_oiEK*aucp2(13h zU$?k_eqO}CR_gP`kV($ZWt_5~>A(r0QN46#Hv| z`)sKzDGKqm@yFu1C7xS@DdKm&wJ9L3DL4QhgBI{BmP@GnQ#}BQx0-b>B7PkaG%q4J zCuWX|cD(Ls{gES_gn2=pqvsle~T~&q(8>T|cF^MsDc+j{) z!>Sr>{ct*kezh|>Fvf=C`C{Z{sg>4^cUkUI?z()J?5bS0z9!XBC`T^A4#LYK66<0k z;jTqdBf^^;<)t!1LJ?Poi5-mhQ5!M9z??J||FP~Z#v0^A;kz1vp`}h8@0%p@s?+9x z{d>{77N!vZ|W2;-1j7knGS&yHM zE_Q?@PxBtK;-7vPb`)8UPP}l?v3NT5ENRF7Nli=09VI@Py=nEom@3EB*VVKoNSjq! zeLG(L$5Uy$f6i2`TWW>HXm?bqsZ|O0;tn`8mHtg{`eI6-SEiLjYY{TlF@VMo#n`I3 zj(x=yYY$(YP^DYGoYhpN$8W5!t#7P9RxdBEVq*Q?hL9?3Ub z=GBR-CBy2~)}+!p`+|j$*iE1=3WtP2sA!5!{Eto<>&lqQ->wE*(agTOzX+00fCAQA z{Q|8b#p0`9UIkWCml)3TW< zWb;*62YT}i+4N68T(8Q|LcB1XX`4<>WuTZLAKYV*36f(%F6a4R_NEo`rk11oe{1rV zPx8YuOI6HXdGi1j?-01ZC+fGt-GKT9VoJRq|oU^%B`!!UGE6guX_5Y>HT&Vsow0JFYT*z4nN=3K9Uod_~k7gR{e2igS}$e zy(Pb>9$V30yWqRY6@9Dk>a9phs_d@*(O&+UM^2Q6&-ClwLxx|rA)aPvxCB6F%!;yOqX#QrUOJIKAs1iW?>W$ z)D(6$>~h$LVFm*>?mEqBRgQK%fXvv%k_z@?!JHW;XJ|r|)+YRxR=nM;+9%s2?1Xsh z(!9j1=%{RE-aI8cDmp81p8t0&XguZqZHhf1G&I4Ul4MUbnG)^NCw>pm)KIfQC> zdjhs^Dpqy3xk@q;GhzFtRYgTdMi>Hd3S=nTXm{B^v|qQ&W;+jvs|wv<%S^~f)a4}C z8H~}GLWrDa$*BvZn2on?IN$OyeyKh9*uKK9P-Vnm|29uep1Iu{aOp5Y!ytfD&+<5AdKeV|GW^N63{v+8TB zK6+sN0f%(RI`4yzjq;`49O0U6I$&Xqe*cgFD05}9t%{{5^|Q}gV~!_$fR11Zi!@4;;E#}E_nVh8fRB=d-WOK5x%ve z?At`yhfOMeJMed;WU=Xzk{A((wT^;#78kdmF2Wp{ zREM28g^@TNU0k3=w_tt6cdgGS(8X#<8_=SK8H-q#bAde3H*7>c=Nr=E z6YPRBqZd7C^^0&Jo{4%3TB-H!{*LIoBu>hN2d5ONHPu!plWm(^_2>Dc8!o#`)_Ij}Jhhiqfm8T&rgoMYdo%@=4L zA_ZC;J5Hp)X|6~_wvX&_^ekM|?bs7rzM=$i6D2EF#^^pyZZ0ZnPL31nQOP3AUBY?0 zAItdxhs0Wd%at2`fM!H$5i^f9^aFY13i_Ai5$H76nj6srdCZ0YbNpYL^^I_8L`iP^ z7)k^K;Tu2p?l`1+PpAvC2C?3###GDsDP`;P3+hW0rKG+fZ~fAgUCw;$@K~Jh)c>7h zZE<{jajnBqi)&Hs!ot$hLV!k#kMzI5$f($rVve$7*`Hyn7s(J5sXuIT!9UgHf*-fZ z6m}RZWf--?t&Y}3au8_=1@}I!yJBgW{5Wu&0Z9-|IOBj0$(>nSQ=#PMnL71Jh1Is~ zwKXf_b8Y4>`lkCNB?r_j-LWz)B|Bx|($eOY>0k};!$(0rBhh&W@vmaK^Rg>A0PfSE z1x`J99a>fFYJivtQlj;}YwlsERz7w3l;Cdc#6jda=+izfl4-j$cK#1i)i~}JaGSPt$`g4&*d-mR2TzdcggxL4& ziNaTz3G@j!Mpt=J?-QuGIDq0jwjadF6F9Vta-lPTw%-8Vrgka5k|ooWQWaM-_)4fv6&s%S+ZI?+riUOW<1#>pVPq{QXLxe)qHZZiR4-kMN7 z0*L;Po35L_!08gGj^tC35BN1608WSvVvUyQDz!ck3Q-?bEND4T)QGk?^bwKiI?dR@ zdS;z`x38XYdI`t5b)QMS^6#MgDqN}dh{lM1c;0;w@p*(N!hrSeMq|J67?vQe89z4$ z7(aw?(>hlOyvtc4!lo>UGKRLfinys9{7)tGUy6Pz)w!V{e}gM6&9x!FV1qMNny74b z&Y$mWt*mHuCL}mpRh_~H`AxC@S^6Cjso_e5rPc^vR_}5KbJi+f*i2YBhSc7M`0pZW4$da$9Ccc zdESb3RDKhyr@#4)-|8vl<^DFtNS$b((33OHT)`R+gca-8zDKTf-u#De$~ZYl9JwN~ z5cs_u)_<275t&xu$_|ol1mmo?sK%(n(EhYC9&tm@b)N;9gA=jQsT>#_ESWzu{8WPP zi`ZCH+oRz|4(&)wlLOR6!jV2nJ*R?pAAlrs>G0me?uYoENB6paFUv6gqJzF68!un( zmsZ^R!B}=nesEfLruhq02Y(5?4+5b6Eo9x=Jx3W|(e5R9ypxd7A5O#JI_+M^j66@f zN9-=|((VJ`XZ)mgZ(wUA^mgLiKo%}}aT-~NJfb}hX7hF4G$G8UJFh(tWj1}ec5h*(j`DA5_kk=^dPKW7vL8zCYWKmc7@_WJ8A8}L-Dd4R6!*W>?oDjHeo(ug$HMi$ z$IMw5>i`S^?qzK_5l8_v<9jpiTCiZVlkH&xB2_1zD(obn0Dee$@a$iJ`%0wg2aX=R ztzaFbAE|eW{|QSM?)sRGvGT5tuA#1b+gg>@=AmY#rGMw1fv%3uA?2h}ke64qK&kBS z@91e$R`d_->>p?z>gw;Kw}7l4NT^urP^3NvFNX4g{;pPKU2`Aq>yc{**5Z5c;{d+4 zb?oYC#?3M$Y{74Rc(o0`C>ZG|v!h!!*wWV5+BTr%D!!QiXx1A9;z6y7WN_OASroPd z1?obPsMPuRI2hZ|HZX`PDfza%JY~nOuAWvUKOfJhbD3E^wRY3LDnK|0bi|b*)Tde0 zt5?+eZanM9$km@0L@p`h%`3`A1{{~-xB4M_(rVILG z5X9dlw8&6ro3do2e&{alop&fF%5-2o!^%qaLEp46~aP_irGqP-6P z=Ry^WLijR6WNzyp=&<*wMH{r&tXi>h&AOGjq5!^HWv15Alq%*>#h<4gl0MttkMNKq zm5iaQ9)9!&>?at6{y7+9C+rvrj}!~DVlzY7rm!m_F_#t%@4r}#Ea$@$GXZN^3ih^1 zMs!#zqWaP?Q=5rs&TP!MEkKoQsAeA2W&srELhQ(}2t7?P=5m+7C#n=@>MX^u*#&Mb z2O-KqgbMUQRUk_>R?uqLT_8;@);`vvZ&(L^w+8TSBiqC_BO+)k+s3wIkEce~1Wv!m zo?-X1gY0#j^8C;20rnVsgnbkJ+i@1ufPJ0)j{RTuD9*2bkd3jQvwvaVVyD=@ zvfr~w_B1=q-hto88R*p(oIKsi-h&jq&E91nu=m*y*bmwN0rh?aJLMxdJ#-+h=n3`{ z_G6q*-N}BhXY6?T;!VsEfZI6YctKs(RNTUH*_ zKaB1Njk7JJy?@uh;I5reAN}YhVK{<+66RWD!BPD8efQaG^uv`A^iKGDJF0e4SUH$< z0vG9z_NHd+@A1D*d?Q(4;C=$Q$9tNG`XCMT3(eusFZ}Md0eYsUqqih{!-k}cTYoE3 zwu3T1;Sci1_@{thNp(`UG>RCIPWgoVsjgagT=z@3$W~xS#1X@jfsX}Qcpaa~;LPBq{K4R+U^hNZAy0;8OWmPwo92bS9r`wKi;ro& zX~6r@Rb%8NK4zQ1Mmf;uF=M9X%i3qZ<#F%F_?YE{_Bm_KM%h2LR#~e+g;dWO{GSE6 zt%uy!Lvq_8yIGLlddP1UtMH7mNnvz9GfHX&qyn#+-OuGFk^dioWfb%1F z;QSeIe(cZW{0KNdGACEYftk2Ma`nf!GCnI;Ea0~ToN$O%P4x51RT(hO-9qnyG^w&T zm&E*6$~L>iP}`b7zo$prHv&jzEP!dbIe1%}UW7M_ARFaqcNKt2Koy`G@719Vtp(KM z`#N0Lm|IsojWo{yjsu7O;6=b`z{|*g6xUY(=K$vc7XTLlui>}v0$vBa0l0*GuK?b{{r6F? zcR-u>1&z$$nH4~7ckcE%4jaRlrqlEsyG?hLBcGZ2d<=9P2OU+tz7AK??G3nY0Bi#6 z$M*w(`vC_54*(7U9zAtNx&(<3xF2^rvWb`uTflI0h|Mz2V4ML1iXgdu%`rS zK7pD~pyut64jMmu`w0L3p#rHY0aXB;Zi|{t+>uYujMggw?&H>H? z;8BMXQ3=$(rF;&lBi%Vu^HM7=hh$d(DgjjhwFQrX2V*GZ2zWqN#RzyX1|FP&G6pla&M<9hGkirp2;fU69=k5ugMw({;#{tg*P5_<*JP$YtI0bkC@FL(e z;AP}Bit8(YbAa;zxRRjeBjCjtcrgZEjDZ)hNKm#bf+A{*#jGDnOrs37XY}@HK~qw? z8M;ihm%?Gsgu|Ypeoy77eu|^j;;Bky!Z{{zsuqEl`jVgyNomLw_Huk9*?t=E4B$B6 zS-=Uva{%9*R2{rgid(@_9qsos(i9<}}|9<^5w6tYONHkVvw zOj%u_&@5(*mJ$HTsJ*v6=VCN;CrhjhcIztSuo18sCEbGSHe9J4-^q6P2GaRkX_xTM z6@b@TOZ%gx`*TY6KUu1MJ~SKuFVSqH=QGgjA3(3qK(9~!3|f5xT75D_E8--yG9c9R z|Grdjqa4J=_i!bWT>>{*`#rWOdW_00M6W~r3!)z9P7;>7nH}X(=8W)gGxf7$MiGFbs{o*A0#Yyywljs*Gp|vN`Cr+YIoJ5~E zi9T@>ec~kglrr|N=TR|o`vI;Wg4c%Ia`x>BXoUFRpK+LbEb~7z+kkxsh{Bi$qM1St z-D<_WQz~Zv0w9mnBQC%vSj_*0VD_&NGl@m8#OI;Us=(}F73TNiF}t@0bBA?k0m+!# z+lcwY&FI52*?!D>*)Xg2SC}(=k{!W&BNzj(5c71aFhh0$sV~Ap*a{2b3TFS_!d%@B V%-PYbojQLtAN=*7zd~Ud`%m*7fg1n- literal 0 HcmV?d00001 diff --git a/samples/petstore/resources/public/images/collapse.gif b/samples/petstore/resources/public/images/collapse.gif new file mode 100644 index 0000000000000000000000000000000000000000..8843e8ce5a46781defd33d5304a0cb4191e72546 GIT binary patch literal 69 zcmZ?wbhEHbwd`J-OQ1PRqNMh0sDWgikt literal 0 HcmV?d00001 diff --git a/samples/petstore/resources/public/images/expand.gif b/samples/petstore/resources/public/images/expand.gif new file mode 100644 index 0000000000000000000000000000000000000000..477bf13718dc56928f313ef6eeb1c2b1a47db69a GIT binary patch literal 73 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 literal 0 HcmV?d00001 diff --git a/samples/petstore/resources/public/images/favicon-32x32.png b/samples/petstore/resources/public/images/favicon-32x32.png new file mode 100755 index 0000000000000000000000000000000000000000..32f319f89bd07e691de2ccaf21632bb9793a9cd9 GIT binary patch literal 1654 zcmV-+28sEJP)Z zYjBlU6^5U+_c^%{LI@BBNTw~6nu{hdI!rrEwGFgm#aci_3sGk}<7m?sf9eb^ z&gef46cL=-R%Vz2t>aV&tTwa+k{T7cTawZs4M`*flAN4x@8yqkX_Av0()aswzFB*( z_g(v2XYI9wX~KJNyap8@D?yj3`UN7k2td_e5i#!rt)NGYgj$R-e!9LVh4Vp9;GzxA zMK7v)oerFfUJTsuv(5*(Q~8(4BX%1_Ls=xV0tMmqzMUI4PvXOov* zN@+ngMR`jQiMjRax>fZ#BX;*hCo--8?_I7}_iH1eH6lVJdXsn0?&9dFMn390GigKx zvpz#@c|A3YzfF2L8&$=tJ7mOs!x(#QN&()xLa+W!YeGv@mDYd1!hz-=a(&=GlM;_b zPG%vitA9$x!Y2?Bybn%_m{rEu%M%KSMehG%O=wBrhS}Hj9gdybeM`h+QN3s*YnL|? zvgvUVc7-t}c@jwh@o&E0n$ViS4YT|3W1Q?bbX(*J+;!)d`PKvb6Cw_Y*hdmMVUhyW zZ8T!ns4DxK>OV1ZfPY>3J+HpK1yx1F)_HZC5;g(w-sLEI-iR$|x$s+l^XGNR&0(9# z+U1ShGrx|W{)=pX?Y>FLo_?g2xjCQaWXJ2g+O&meFp#vZ`{G`z$~NHDbpvJAgsz!b z^uFGREr>*Ka-jL?Q4VX$H&eA}1J?(yaQ5=yDWg5td6>SNom7^sV|jV~DBHp21|rcL z7_oCzwI0BD?~T9~fOpTln3C=D^Q!>ZbNFi<93yT#96a8@?!#XNptPWRl&yd8BaWWl z34kD5y!Xa<4JwQYEl^dOPB*4F%u0_Hvw`81zwm@e6sU}FPKxd5=|)r)V{EC{pvuH* zJ6!Jl2VH$<$8i_SQ&l%@0nSHJRgly=clG*tI(ymx5M-qRT?Ww6)jY1Vth8L5k8)$E zd)fkqhWl`Sm>KDF#NbSZjd4f1W4iLB@ygIB2Q(Evj zKI~~rAs@*lV&6Bw-ypSUBrp)UhBaZbGx8@q#<9o<&%&CtWIY&*`2|&ppREQs0#KB< zgo0U%##!zYYNsv0+Hfp4R8wsEISVP7vn=s?#2B$H-n(`Yp>|B|1{jR=0g#zCWqX%I zR@!U;hN9O~Y_;X}i3a5ZBlfs4#&|`42~e~6Npdm^M{Vf1dK`d_D_^Fr>iH?jAFkZS zH}BsU+uL<~lx=2a0X5~D0Z?>@F~%DJ5oz@5yVJwjtghZZYSYovJ2-LvC30t!u()K^ zlmg02Rxx|VJX$Zjl5)4yRc$9DJQJ_JTg3bVMs!5ZMK_7qUje-Q*G)8^+cPEEt)Q}O zJ?p-(7XYvB$&d~2f)O2&h~4AW9TE}NE^p+nc@KSJTfS+;@4SL}@UgYJ}w)Q)V=$7{=rRQ;RAgzi>VS}wfAf#wGK z2d~@`@yyISSY5T9%ChzGtaqnG%mc=liQDoKB63yzUJ+Xt8%%ES&Y7Jwo!-IKzH^fj z=jW7BTfT+b^39}&XT}GU;0+^o{j>&?k41Rn)ol`yZ6ims&fdS%-gS)L{`TapmX$u6 zyqTqx=2ufZ=iU*cneSC~&lw5r`oD)&=Y!n94L=*WXvefB)7Ws@5xC*4w>6xOb0Q(H zL4_bI75%KLUtq)*#v*lBL~MLK=dE~!3#Q}ufBOd*=m{K;N&o-=07*qoM6N<$g6kDD A00000 literal 0 HcmV?d00001 diff --git a/samples/petstore/resources/public/images/favicon.ico b/samples/petstore/resources/public/images/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..8b60bcf06a7685b9ea53983c125e7058906fbcbd GIT binary patch literal 5430 zcmcgwYiv|S6douFYJ&9 zoHH|LSE(UtsLIS#7_-&1VM>ivN@Zu;-zA{00&N^|8r`@w~u}3a1X>=27;pf_a{DPJMTR$JSvjrVZRL?J~Ga>i0GO0Y%B;(7M$}2m+kcrz@>Hae2Pk^@b^f8%H zL>`N;qJuC@Oe+t~7I z<(F|AfU$#EfN{kFKA+53u@A<+4YnEIf**{2=CuCE#{h4Wo9v-X#BN#D=ro%g~u zG`=OQBiQEDZgc6n2RYf*+oRWbKVZW9eJJuGNa8A-F{8(f+y@)q2tylad}QmVTBh{m zkpvl@O*Y<9#KzvA;3if`|g4qjlr75YyFqbGlV2Q--{B?*ERj@$;+A#v6A5% zKJZ-k(ti_bGM?eOj#f{ZHOKc6lK8&sk-j?4Kdjr4z{}bU>r#UnlQAQ!`bOZB;Ct>?+|WpbgrOY2zPu-B!V?-m447=gVK(*?-RQObKSO{281uhZ-RJZ_ax0d9HE}&gAII8uJO7-x&ULT3$-hG# zr#*GDR$v46R``GL&)??M=Z|+Z8{*GeE`I+!{6E}3tWV7S1MCU*9ccYQdsa7AC-|$l z32U8*#-^_c?O%nK{oI ze@n2sr7p*=%uQX9Dk-qo-r2V)7}P0ZWaab6B%)NcuSZfL*ZERjG4kd;J~ udLgV-)@7w`Z&hkIdqAlS_J#Nx!E}|RnRSkVm|Sa24|P&EF^Huxf&C8$As%)B literal 0 HcmV?d00001 diff --git a/samples/petstore/resources/public/images/logo_small.png b/samples/petstore/resources/public/images/logo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..5496a65579ae903d4008f9d268fac422ef9d3679 GIT binary patch literal 770 zcmV+d1O5DoP)K11rQipnJ)eVnTSzHNF zN8ab&RhE5cC$$4FI-PZXx$pga@8yN)KS}L2Us~^y$(x-xioWbnFcV+~b9ig=!ft8Q z0RD+rpA8910Smyc0GviVUOPGiY6YM@-r6Nn8S&~cxHl27$l)-R$1(!Xx045RDy;_& zeXkG{;_#i9rz0B6149#Ddj=KM6MV^rTD%ylzGdCBX<^=^@I0X3SCR7OMbn}sUKdeF zKO-flaJa%@kJ27@Rod?J9=+Qx5|=PtG8n> zy~9rIu}+48M}FW5Bbqw3t#po?c?kmG!FX32W(dOjzTb+U@64MzHItoeB!M0Jcd}|E z>ekW`<~FjR_ZVVJkF|_htH&v!({Oad?xax?0K0sLwBY%nr46DpCmIIaa?@|Y&?n0q z@kJlMy`pE2HtEgASNd~xNzt$Kn7w#^Fy5oi`e$bUE*+f>Vk5z7=-2pj68afrqli$_ zvqe##5V?a)QU_-s9+s?mJYT5m`MQDRH4cYs^L1lCW;Dua5Ln9lG0BC@9DJQHA(}y&Z}$apb{kU zbezR}b^|O%6i+$BFsT3zqAe8wg9`vfiRp#{)z2bsJw`vBQL7Bt!IexM3$Hsf0tHK3 z+R=x{lR$K`s;7__?ASPW=3?*xgCpGaiadSEpoi0pw-_V#OXM8Ap{4qlG08x0ig9IY z3Ijqh(t1_=g#jocuqyJO=729e9OSiNDSrhR0Gc5G)(QGH?*IS*07*qoM6N<$f<~fU A82|tP literal 0 HcmV?d00001 diff --git a/samples/petstore/resources/public/images/pet_store_api.png b/samples/petstore/resources/public/images/pet_store_api.png new file mode 100644 index 0000000000000000000000000000000000000000..f9f9cd4aeb35a108c4b2f1dddb59977d56c595d8 GIT binary patch literal 824 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 z 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 + + + + Swagger UI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+
+ + diff --git a/samples/petstore/resources/public/js/backbone-min.js b/samples/petstore/resources/public/js/backbone-min.js new file mode 100644 index 0000000..a3f544b --- /dev/null +++ b/samples/petstore/resources/public/js/backbone-min.js @@ -0,0 +1,15 @@ +// Backbone.js 1.1.2 + +(function(t,e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}})(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION="1.1.2";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,"once",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,"off",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,"el"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:"json"};if(!s.url){a.url=i.result(r,"url")||M()}if(s.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n==="PUT"||n==="DELETE"||n==="PATCH")){a.type="POST";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",n);if(o)return o.apply(this,arguments)}}if(a.type!=="GET"&&!s.emulateJSON){a.processData=false}if(a.type==="PATCH"&&k){a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger("request",r,h,s);return h};var k=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var H=/(\(\?)?:\w+/g;var A=/\*\w+/g;var I=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=""}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,["route:"+r].concat(a));n.trigger("route",r,a);e.history.trigger("route",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,"\\$&").replace(S,"(?:$1)?").replace(H,function(t,e){return e?t:"([^/?]+)"}).replace(A,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var R=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=("/"+this.root+"/").replace(O,"/");if(n&&this._wantsHashChange){var a=e.$('