├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle ├── chinook-core ├── build.gradle └── src │ ├── docs │ ├── background.gif │ └── stylesheet.css │ └── main │ ├── frege │ └── chinook │ │ ├── Chinook.fr │ │ ├── Core.fr │ │ ├── Router.fr │ │ ├── Spark.fr │ │ ├── Utils.fr │ │ └── util │ │ └── ContentType.fr │ └── java │ └── chinook │ └── into │ └── Rest.java ├── chinook-docs ├── build.gradle └── src │ └── docs │ └── asciidoc │ ├── chapter │ ├── development.ad │ ├── getting.ad │ ├── http.ad │ ├── http_config.ad │ ├── http_intro.ad │ ├── http_req_res.ad │ ├── http_templating.ad │ └── versions.adoc │ └── index.ad ├── chinook-sample ├── build.gradle └── src │ └── main │ ├── frege │ └── chinook │ │ ├── App.fr │ │ ├── Data.fr │ │ └── Handlers.fr │ └── resources │ ├── chinook │ ├── form.gtpl │ └── sender.gtpl │ └── public │ └── css │ └── style.css ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.class 3 | .gradle/ 4 | .nb-gradle/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | before_cache: 5 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 6 | cache: 7 | directories: 8 | - "$HOME/.gradle/caches/" 9 | - "$HOME/.gradle/wrapper/" 10 | env: 11 | global: 12 | - secure: YkZj/sBwdg6a6LPWecSXJtEzEkMERUVNgYZnRinf33nUYA4LtQGSs6osXDgDhCdWErN/TMyNsH/keLSfkL0BMsdIKBwFW6E6MnDJZj/RwIL4o6jpO6xIqYPXRzqL38qi7Z59Ao7s6/GBqkWIHwNpJJ9nMETld2c+7jrMyCvyTUyfe+QM6yd9dB7guZ45Gg38fQV9/ohJno8OBLpT4F+A48afzHomEljiYr9BivbyGikb2XJoCzOtGyjCS+hB4rWLXBpEiNyLx8dLKySnxnY6l/srVV0LoTe2W+pyp0blGqEUTHv0bOLK389efP0pzy9MutkybhL1Z/6o+wvfJTzWLcw2pu5B3Er84wnaeHc9Pr1wCUcq4RvD9vO7rV4ma8a+2ecLMG+VujlPUO8kJIaJMCw898vql6NoIcNzXuVr3EsdPFG22N8oHl1hNQOSrJO6Y0rNDPQEQ0o9xHQCw/OZ28409+RP0iEGyZnMyK+lR9ZAkyIsBngqI8CVT7NpWD+Loav2giZ+yAIlyR9bxH2b63bI99Z4UiA9A2qTfvWgLw5y+MIqyNkVkBKISqos2EFuZqSJ2FhkB0a/ger73qpG90G6hiC3twj5WQa2uIP+Vf7q/XF+ME1hPjeyOqjNne19/HPBbMM1zS1CxwOEji5jahW62TP07KDRGnPRRIHNobU= 13 | - secure: fQdTrzTNMu5LPgpqMoVNnIhRUTVMygsnW6y0x/0QHsVg1Qd9xEB8GfJtGbOwob0kw3gSX0FpgLIOwU8pEeMamxfq0gn/NAUINEFuSF1uruu2oXRgfNbZ/qk3Q4sERySV50dHABQrU17Zbxp6iSQkOjgoFjZArKQ964aiy25Ya40HXZ6xQSzqMpDKybXyg4g1y0lanDEu6WsBdKsDh+hnAinYcvAhZqdL1F1oxNZogfEYTFcUctEuZCP4mTdaxW75pBWe2L5tcRHWo5/2xcLwFgaDBGsu/zQ9FVZNBaziqH4MNZl6GQCgacyi5N1XY0YwZ3/TghhrtZLPR5TJrhpihjWMjPzAdY2CHz5k/E/eBEBtI5kWdnYHOpt74h3BHeNNXM1Iw0bEPe4CAMIL7YdrSKVqnRMbEqV/1xl5e98Um1xV6ic6FqZuNS+fmJVNcMM/c2A4Zntm4GODlNFxoCWaYuH9KfVWe0tEFi9cAxdOlQishfT4fixU9PUVexoi0rH/UHFICECl76iK1A04IzMcndthZDl4aVQlb+kTB6BriDFvjVH7dGlfeZpL6e1BrlRDPx3fxhhDYfoFuFN0ArsvlS5T+2foKkaWt6L7O4mWbS0cLDbOgDLWnfjAofoHGTQGZo/wXuGnHnM943DtNQp0GRVoZwJSB3lvsjvuJddO2+E= 14 | - secure: EaM5W6K3ZXcGVgSwzcNaCij0wH4Rp/BSROgQ/QPU7esZ6DD8GkKqPx8wh3v+6SQIHQu9cB6RFunXO4mRThdO/zytMEQhdeMkKZ7ExcOZIlsCc9LCtYzdv+8ILZEV+VsTxfSpvIqcQmtuAMCKVFvtso0HqaNwz1AoU/whi+hT9fYIsbA1oB057bl72dg8fIZB5VS2dvOuZYJX0P+H/hoJ+LD2iYDT5RYaE+ZmN7xjYpVKhlBINtWuJ+0SV2mnxbEhDuA7DuSFY27UvXAIWnc8N16tk0Dq/pnc2xEQd3gngY9gOqrzddF/IRggCIQWEQ3HLs7v8F1uHAhcSQDpRBqJCQfVVSFu5bDPunlOMbj2nwGcP5nr+sNWWKsD+N0OXDsNeh/pzYpxU5m1pWGmez5vkRFBpHN3UMWaZIcLPoYgTJzcy/jP938PhwxGbSFbNiEdnpLPLc0TjGIH7i7+sYvWBid5crYo+WhYLoGyA/eRE/qXj6upn0iZfdsC8YVy1t6LMimtTp4SChUpnsEkZhHm6m6xR3eVjvMbjTejAc8dbPYFrSH1ag88SXNCI75srhxC8eQLp8+cOO1r9BmYD5COZfWva0mKANdF8lc19AbSC7/24rc/OOQuIqbkxPESgEW18OI3G1kYzz6HTaO+mJe9gieg57wjEKH4bZQf/iaekDc= 15 | install: "./gradlew assemble -PbintrayUser=${bintrayUser} -PbintrayKey=${bintrayKey}" 16 | script: "./gradlew clean build :chinook-core:bintrayUpload :chinook-docs:ascii :chinook-docs:publishGhPages 17 | -x fregeQuickCheck -PbintrayUser=${bintrayUser} -PbintrayKey=${bintrayKey}" 18 | branches: 19 | only: 20 | - master 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [0.2.1] 2016-05-10 5 | ### Changes 6 | - First release at fregelab 7 | - Some chinook.Spark params: String => Maybe String 8 | - Upgraded to Frege 3.24 (jdk 1.7) 9 | 10 | ### Added 11 | - Request interceptors `Before` and `After` 12 | 13 | ## [0.2.0] 2016-02-15 14 | ### Breaking changes 15 | - Massive refactor and new URI mapping 16 | 17 | ### Added 18 | - New URI mapping 19 | - Expose some server settings (port and static files) 20 | - Upgrade sample html rendering to `diablo-groovy` 0.1.0 21 | - Update docs 22 | 23 | ### Fixed 24 | - JSON ajax endpoints 25 | 26 | ## [0.1.1] 2016-02-05 27 | ### Added 28 | - Add html content type 29 | - Add basic templating with `Diablo` to `chinook-sample` 30 | - Add Travis automatic deployment 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright [yyyy] [name of copyright owner] 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/fregelab/chinook.svg?branch=master)](https://travis-ci.org/fregelab/chinook) 2 | [![Bintray](https://img.shields.io/bintray/v/fregelab/maven/chinook-core.svg?style=flat-square)](https://bintray.com/fregelab/maven/chinook-core) 3 | 4 | **Chinook** is a `Frege` (http://frege-lang.org) port of 5 | `SparkJava` http://sparkjava.com for creating web applications with 6 | minimum effort. 7 | 8 | Docs are available at https://fregelab.github.io/chinook 9 | 10 | Binaries are available at Bintray: 11 | 12 | repositories { 13 | maven { 14 | url "http://dl.bintray.com/fregelab/maven" 15 | } 16 | } 17 | 18 | Gradle dependencies: 19 | 20 | compile 'com.github.fregelab:chinook-core:VERSION' 21 | 22 | Replace `VERSION` with the latest available version number (see above 23 | or check the `versions` chapter in the documentation). 24 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | maven { 6 | url "https://plugins.gradle.org/m2/" 7 | } 8 | mavenCentral() 9 | } 10 | dependencies { 11 | classpath "gradle.plugin.org.frege-lang:frege-gradle-plugin:0.7" 12 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' 13 | } 14 | } 15 | 16 | repositories { 17 | jcenter() 18 | maven { url "https://oss.sonatype.org/content/groups/public" } 19 | maven { url "http://dl.bintray.com/fregelab/maven" } 20 | mavenCentral() 21 | } 22 | 23 | pluginManager.withPlugin('java') { 24 | targetCompatibility = libraries.jdk 25 | sourceCompatibility = libraries.jdk 26 | } 27 | 28 | pluginManager.withPlugin('org.frege-lang') { 29 | compileFrege { 30 | target = libraries.jdk 31 | } 32 | 33 | compileTestFrege { 34 | target = libraries.jdk 35 | } 36 | } 37 | } 38 | 39 | ext.libraries = [ 40 | jdk: '1.7', 41 | frege_core: 'org.frege-lang:frege:3.24-7.100', 42 | frege_repl: 'org.frege-lang:frege-repl-core:1.3', 43 | spark_java: 'com.sparkjava:spark-core:2.5' 44 | ] 45 | -------------------------------------------------------------------------------- /chinook-core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: "org.frege-lang" 3 | apply plugin: 'maven-publish' 4 | apply plugin: 'com.jfrog.bintray' 5 | 6 | // _____ _ _ 7 | // | __ \ | | (_) 8 | // | | | | ___ _ __ ___ _ __ __| | ___ _ __ ___ _ ___ ___ 9 | // | | | |/ _ \ '_ \ / _ \ '_ \ / _` |/ _ \ '_ \ / __| |/ _ \/ __| 10 | // | |__| | __/ |_) | __/ | | | (_| | __/ | | | (__| | __/\__ \ 11 | // |_____/ \___| .__/ \___|_| |_|\__,_|\___|_| |_|\___|_|\___||___/ 12 | // | | 13 | // |_| 14 | 15 | dependencies { 16 | compile libraries.frege_core 17 | compile libraries.frege_repl 18 | 19 | compile libraries.spark_java 20 | } 21 | 22 | // _____ _ _ _ _ _ _ 23 | // | __ \(_) | | (_) | | | (_) 24 | // | | | |_ ___| |_ _ __ _| |__ _ _| |_ _ ___ _ __ 25 | // | | | | / __| __| '__| | '_ \| | | | __| |/ _ \| '_ \ 26 | // | |__| | \__ \ |_| | | | |_) | |_| | |_| | (_) | | | | 27 | // |_____/|_|___/\__|_| |_|_.__/ \__,_|\__|_|\___/|_| |_| 28 | // 29 | // 30 | 31 | publishing { 32 | publications { 33 | mavenCustom(MavenPublication) { 34 | groupId releaseGroup 35 | artifactId 'chinook-core' 36 | version releaseVersion 37 | 38 | from components.java 39 | } 40 | } 41 | } 42 | 43 | bintray { 44 | /* Normally taken from ~/.gradle/gradle.properties */ 45 | user = project.hasProperty('bintrayUser') ? project.bintrayUser : 'bintrayUser' 46 | key = project.hasProperty('bintrayKey') ? project.bintrayKey : 'bintrayKey' 47 | publish = false 48 | publications = ['mavenCustom'] 49 | pkg { 50 | repo = 'maven' 51 | name = 'chinook-core' 52 | userOrg = 'fregelab' 53 | desc = releaseDescription 54 | websiteUrl = 'http://fregelab.github.io/chinook/' 55 | issueTrackerUrl = 'https://github.com/fregelab/chinook/issues' 56 | licenses = ['Apache-2.0'] 57 | vcsUrl = 'https://github.com/fregelab/chinook.git' 58 | labels = ['frege', 'rest'] 59 | version { 60 | name = releaseVersion 61 | desc = 'Chinook Release' 62 | released = new Date() 63 | vcsTag = releaseVersion 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /chinook-core/src/docs/background.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fregelab/chinook/8e4ea68f6bc27fb3cde8cb357187d8a541aa96d1/chinook-core/src/docs/background.gif -------------------------------------------------------------------------------- /chinook-core/src/docs/stylesheet.css: -------------------------------------------------------------------------------- 1 | /* Javadoc style sheet */ 2 | /* 3 | Overall document style 4 | */ 5 | body { 6 | background-color:#ffffff; 7 | color:#353833; 8 | font-family:Arial, Helvetica, sans-serif; 9 | font-size:76%; 10 | margin:0; 11 | } 12 | a:link, a:visited { 13 | text-decoration:none; 14 | color:#4c6b87; 15 | } 16 | a:hover, a:focus { 17 | text-decoration:none; 18 | color:#bb7a2a; 19 | } 20 | a:active { 21 | text-decoration:none; 22 | color:#4c6b87; 23 | } 24 | a[name] { 25 | color:#353833; 26 | } 27 | a[name]:hover { 28 | text-decoration:none; 29 | color:#353833; 30 | } 31 | pre { 32 | font-size:1.3em; 33 | } 34 | h1 { 35 | font-size:1.8em; 36 | } 37 | h2 { 38 | font-size:1.5em; 39 | } 40 | h3 { 41 | font-size:1.4em; 42 | } 43 | h4 { 44 | font-size:1.3em; 45 | } 46 | h5 { 47 | font-size:1.2em; 48 | } 49 | h6 { 50 | font-size:1.1em; 51 | } 52 | ul { 53 | list-style-type:disc; 54 | } 55 | code, tt { 56 | font-size:1.2em; 57 | } 58 | dt code { 59 | font-size:1.2em; 60 | } 61 | table tr td dt code { 62 | font-size:1.2em; 63 | vertical-align:top; 64 | } 65 | sup { 66 | font-size:.6em; 67 | } 68 | /* 69 | Document title and Copyright styles 70 | */ 71 | .clear { 72 | clear:both; 73 | height:0px; 74 | overflow:hidden; 75 | } 76 | .aboutLanguage { 77 | float:right; 78 | padding:0px 21px; 79 | font-size:.8em; 80 | z-index:200; 81 | margin-top:-7px; 82 | } 83 | .legalCopy { 84 | margin-left:.5em; 85 | } 86 | .bar a, .bar a:link, .bar a:visited, .bar a:active { 87 | color:#FFFFFF; 88 | text-decoration:none; 89 | } 90 | .bar a:hover, .bar a:focus { 91 | color:#bb7a2a; 92 | } 93 | .tab { 94 | background-color:#0066FF; 95 | background-image:url(resources/titlebar.gif); 96 | background-position:left top; 97 | background-repeat:no-repeat; 98 | color:#ffffff; 99 | padding:8px; 100 | width:5em; 101 | font-weight:bold; 102 | } 103 | /* 104 | Navigation bar styles 105 | */ 106 | .bar { 107 | background-image:url(background.gif); 108 | background-repeat:repeat-x; 109 | color:#FFFFFF; 110 | padding:.8em .5em .4em .8em; 111 | height:auto;/*height:1.8em;*/ 112 | font-size:1em; 113 | margin:0; 114 | } 115 | .topNav { 116 | background-image:url(resources/background.gif); 117 | background-repeat:repeat-x; 118 | color:#FFFFFF; 119 | float:left; 120 | padding:0; 121 | width:100%; 122 | clear:right; 123 | height:2.8em; 124 | padding-top:10px; 125 | overflow:hidden; 126 | } 127 | .bottomNav { 128 | margin-top:10px; 129 | background-image:url(resources/background.gif); 130 | background-repeat:repeat-x; 131 | color:#FFFFFF; 132 | float:left; 133 | padding:0; 134 | width:100%; 135 | clear:right; 136 | height:2.8em; 137 | padding-top:10px; 138 | overflow:hidden; 139 | } 140 | .subNav { 141 | background-color:#dee3e9; 142 | border-bottom:1px solid #9eadc0; 143 | float:left; 144 | width:100%; 145 | overflow:hidden; 146 | } 147 | .subNav div { 148 | clear:left; 149 | float:left; 150 | padding:0 0 5px 6px; 151 | } 152 | ul.navList, ul.subNavList { 153 | float:left; 154 | margin:0 25px 0 0; 155 | padding:0; 156 | } 157 | ul.navList li{ 158 | list-style:none; 159 | float:left; 160 | padding:3px 6px; 161 | } 162 | ul.subNavList li{ 163 | list-style:none; 164 | float:left; 165 | font-size:90%; 166 | } 167 | .topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { 168 | color:#FFFFFF; 169 | text-decoration:none; 170 | } 171 | .topNav a:hover, .bottomNav a:hover { 172 | text-decoration:none; 173 | color:#bb7a2a; 174 | } 175 | .navBarCell1Rev { 176 | background-image:url(resources/tab.gif); 177 | background-color:#a88834; 178 | color:#FFFFFF; 179 | margin: auto 5px; 180 | border:1px solid #c9aa44; 181 | } 182 | /* 183 | Page header and footer styles 184 | */ 185 | .header, .footer { 186 | clear:both; 187 | margin:0 20px; 188 | padding:5px 0 0 0; 189 | } 190 | .indexHeader { 191 | margin:10px; 192 | position:relative; 193 | } 194 | .indexHeader h1 { 195 | font-size:1.3em; 196 | } 197 | .title { 198 | color:#2c4557; 199 | margin:10px 0; 200 | } 201 | .subTitle { 202 | margin:5px 0 0 0; 203 | } 204 | .header ul { 205 | margin:0 0 25px 0; 206 | padding:0; 207 | } 208 | .footer ul { 209 | margin:20px 0 5px 0; 210 | } 211 | .header ul li, .footer ul li { 212 | list-style:none; 213 | font-size:1.2em; 214 | } 215 | /* 216 | Heading styles 217 | */ 218 | div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { 219 | background-color:#dee3e9; 220 | border-top:1px solid #9eadc0; 221 | border-bottom:1px solid #9eadc0; 222 | margin:0 0 6px -8px; 223 | padding:2px 5px; 224 | } 225 | ul.blockList ul.blockList ul.blockList li.blockList h3 { 226 | background-color:#dee3e9; 227 | border-top:1px solid #9eadc0; 228 | border-bottom:1px solid #9eadc0; 229 | margin:0 0 6px -8px; 230 | padding:2px 5px; 231 | } 232 | ul.blockList ul.blockList li.blockList h3 { 233 | padding:0; 234 | margin:15px 0; 235 | } 236 | ul.blockList li.blockList h2 { 237 | padding:0px 0 20px 0; 238 | } 239 | /* 240 | Page layout container styles 241 | */ 242 | .contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { 243 | clear:both; 244 | padding:10px 20px; 245 | position:relative; 246 | } 247 | .indexContainer { 248 | margin:10px; 249 | position:relative; 250 | font-size:1.0em; 251 | } 252 | .indexContainer h2 { 253 | font-size:1.1em; 254 | padding:0 0 3px 0; 255 | } 256 | .indexContainer ul { 257 | margin:0; 258 | padding:0; 259 | } 260 | .indexContainer ul li { 261 | list-style:none; 262 | } 263 | .contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { 264 | font-size:1.1em; 265 | font-weight:bold; 266 | margin:10px 0 0 0; 267 | color:#4E4E4E; 268 | } 269 | .contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { 270 | margin:10px 0 10px 20px; 271 | } 272 | .serializedFormContainer dl.nameValue dt { 273 | margin-left:1px; 274 | font-size:1.1em; 275 | display:inline; 276 | font-weight:bold; 277 | } 278 | .serializedFormContainer dl.nameValue dd { 279 | margin:0 0 0 1px; 280 | font-size:1.1em; 281 | display:inline; 282 | } 283 | /* 284 | List styles 285 | */ 286 | ul.horizontal li { 287 | display:inline; 288 | font-size:0.9em; 289 | } 290 | ul.inheritance { 291 | margin:0; 292 | padding:0; 293 | } 294 | ul.inheritance li { 295 | display:inline; 296 | list-style:none; 297 | } 298 | ul.inheritance li ul.inheritance { 299 | margin-left:15px; 300 | padding-left:15px; 301 | padding-top:1px; 302 | } 303 | ul.blockList, ul.blockListLast { 304 | margin:10px 0 10px 0; 305 | padding:0; 306 | } 307 | ul.blockList li.blockList, ul.blockListLast li.blockList { 308 | list-style:none; 309 | margin-bottom:25px; 310 | } 311 | ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { 312 | padding:0px 20px 5px 10px; 313 | border:1px solid #9eadc0; 314 | background-color:#f9f9f9; 315 | } 316 | ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { 317 | padding:0 0 5px 8px; 318 | background-color:#ffffff; 319 | border:1px solid #9eadc0; 320 | border-top:none; 321 | } 322 | ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { 323 | margin-left:0; 324 | padding-left:0; 325 | padding-bottom:15px; 326 | border:none; 327 | border-bottom:1px solid #9eadc0; 328 | } 329 | ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { 330 | list-style:none; 331 | border-bottom:none; 332 | padding-bottom:0; 333 | } 334 | table tr td dl, table tr td dl dt, table tr td dl dd { 335 | margin-top:0; 336 | margin-bottom:1px; 337 | } 338 | /* 339 | Table styles 340 | */ 341 | .contentContainer table, .classUseContainer table, .constantValuesContainer table { 342 | border-bottom:1px solid #9eadc0; 343 | width:100%; 344 | } 345 | .contentContainer ul li table, .classUseContainer ul li table, .constantValuesContainer ul li table { 346 | width:100%; 347 | } 348 | .contentContainer .description table, .contentContainer .details table { 349 | border-bottom:none; 350 | } 351 | .contentContainer ul li table th.colOne, .contentContainer ul li table th.colFirst, .contentContainer ul li table th.colLast, .classUseContainer ul li table th, .constantValuesContainer ul li table th, .contentContainer ul li table td.colOne, .contentContainer ul li table td.colFirst, .contentContainer ul li table td.colLast, .classUseContainer ul li table td, .constantValuesContainer ul li table td{ 352 | vertical-align:top; 353 | padding-right:20px; 354 | } 355 | .contentContainer ul li table th.colLast, .classUseContainer ul li table th.colLast,.constantValuesContainer ul li table th.colLast, 356 | .contentContainer ul li table td.colLast, .classUseContainer ul li table td.colLast,.constantValuesContainer ul li table td.colLast, 357 | .contentContainer ul li table th.colOne, .classUseContainer ul li table th.colOne, 358 | .contentContainer ul li table td.colOne, .classUseContainer ul li table td.colOne { 359 | padding-right:3px; 360 | } 361 | .overviewSummary caption, .packageSummary caption, .contentContainer ul.blockList li.blockList caption, .summary caption, .classUseContainer caption, .constantValuesContainer caption { 362 | position:relative; 363 | text-align:left; 364 | background-repeat:no-repeat; 365 | color:#FFFFFF; 366 | font-weight:bold; 367 | clear:none; 368 | overflow:hidden; 369 | padding:0px; 370 | margin:0px; 371 | } 372 | caption a:link, caption a:hover, caption a:active, caption a:visited { 373 | color:#FFFFFF; 374 | } 375 | .overviewSummary caption span, .packageSummary caption span, .contentContainer ul.blockList li.blockList caption span, .summary caption span, .classUseContainer caption span, .constantValuesContainer caption span { 376 | white-space:nowrap; 377 | padding-top:8px; 378 | padding-left:8px; 379 | display:block; 380 | float:left; 381 | background-image:url(resources/titlebar.gif); 382 | height:18px; 383 | } 384 | .overviewSummary .tabEnd, .packageSummary .tabEnd, .contentContainer ul.blockList li.blockList .tabEnd, .summary .tabEnd, .classUseContainer .tabEnd, .constantValuesContainer .tabEnd { 385 | width:10px; 386 | background-image:url(resources/titlebar_end.gif); 387 | background-repeat:no-repeat; 388 | background-position:top right; 389 | position:relative; 390 | float:left; 391 | } 392 | ul.blockList ul.blockList li.blockList table { 393 | margin:0 0 12px 0px; 394 | width:100%; 395 | } 396 | .tableSubHeadingColor { 397 | background-color: #EEEEFF; 398 | } 399 | .altColor { 400 | background-color:#eeeeef; 401 | } 402 | .rowColor { 403 | background-color:#ffffff; 404 | } 405 | .overviewSummary td, .packageSummary td, .contentContainer ul.blockList li.blockList td, .summary td, .classUseContainer td, .constantValuesContainer td { 406 | text-align:left; 407 | padding:3px 3px 3px 7px; 408 | } 409 | th.colFirst, th.colLast, th.colOne, .constantValuesContainer th { 410 | background:#dee3e9; 411 | border-top:1px solid #9eadc0; 412 | border-bottom:1px solid #9eadc0; 413 | text-align:left; 414 | padding:3px 3px 3px 7px; 415 | } 416 | td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { 417 | font-weight:bold; 418 | } 419 | td.colFirst, th.colFirst { 420 | border-left:1px solid #9eadc0; 421 | white-space:nowrap; 422 | } 423 | td.colLast, th.colLast { 424 | border-right:1px solid #9eadc0; 425 | } 426 | td.colOne, th.colOne { 427 | border-right:1px solid #9eadc0; 428 | border-left:1px solid #9eadc0; 429 | } 430 | table.overviewSummary { 431 | padding:0px; 432 | margin-left:0px; 433 | } 434 | table.overviewSummary td.colFirst, table.overviewSummary th.colFirst, 435 | table.overviewSummary td.colOne, table.overviewSummary th.colOne { 436 | width:25%; 437 | vertical-align:middle; 438 | } 439 | table.packageSummary td.colFirst, table.overviewSummary th.colFirst { 440 | width:25%; 441 | vertical-align:middle; 442 | } 443 | /* 444 | Content styles 445 | */ 446 | .description pre { 447 | margin-top:0; 448 | } 449 | .deprecatedContent { 450 | margin:0; 451 | padding:10px 0; 452 | } 453 | .docSummary { 454 | padding:0; 455 | } 456 | /* 457 | Formatting effect styles 458 | */ 459 | .sourceLineNo { 460 | color:green; 461 | padding:0 30px 0 0; 462 | } 463 | h1.hidden { 464 | visibility:hidden; 465 | overflow:hidden; 466 | font-size:.9em; 467 | } 468 | .block { 469 | display:block; 470 | margin:3px 0 0 0; 471 | } 472 | .strong { 473 | font-weight:bold; 474 | } 475 | -------------------------------------------------------------------------------- /chinook-core/src/main/frege/chinook/Chinook.fr: -------------------------------------------------------------------------------- 1 | {- 2 | This module exposes the API to build rest 3 | applications 4 | --} 5 | module chinook.Chinook where 6 | 7 | import chinook.Core as Core 8 | import chinook.Router as Router 9 | import chinook.Spark as Spark 10 | import chinook.Utils (blank) 11 | 12 | import frege.java.Util (Set, Map) 13 | import frege.data.Iterators (ArrayIterator) 14 | 15 | {-- 16 | Converts a mutable request in an immutable data structure 17 | -} 18 | toChinookRequest :: MutableIO Spark.Request -> IO Core.Request 19 | toChinookRequest source = do 20 | sQueryParams <- convertQueryParams source 21 | sHeaders <- convertHeaders source 22 | sPathParams <- convertPathParams source 23 | sBody <- convertBody source 24 | return $ Request { headers = sHeaders, 25 | queryParams = sQueryParams, 26 | pathParams = sPathParams, 27 | body = sBody } 28 | 29 | {-- 30 | Extracts query params from a mutable structure to a list of 31 | `QueryParam` values. When getting a list from the QueryMap it 32 | gives us a list of [(String, JArray String)] so we need to 33 | convert it to [(String, [String])] 34 | -} 35 | convertQueryParams :: MutableIO Spark.Request -> IO [(String, [String])] 36 | convertQueryParams request = do 37 | mQueryMap <- Spark.Request.queryMap request 38 | mMap <- Spark.QueryMap.toMap mQueryMap 39 | list <- Map.toList mMap 40 | return $ map fromArrayToList list 41 | 42 | -- We need to transform String arrays to [String] 43 | fromArrayToList :: (String, JArray String) -> (String, [String]) 44 | fromArrayToList (st, arr) = (st, (ArrayIterator.from(arr)).toList) 45 | 46 | {-- 47 | Extracts all headers coming from a mutable structure to a 48 | list of `Header` values 49 | -} 50 | convertHeaders :: MutableIO Spark.Request -> IO [(String, Maybe String)] 51 | convertHeaders request = do 52 | mutableNames <- Spark.Request.allHeaders request 53 | names <- Set.toList mutableNames 54 | values <- sequence $ map (Spark.Request.headers request) names 55 | return $ zip names values 56 | 57 | {-- 58 | Extracts path params coming from a mutable structure into a list of 59 | `PathParam` values 60 | -} 61 | convertPathParams :: MutableIO Spark.Request -> IO [(String, String)] 62 | convertPathParams source = do 63 | mutable <- Spark.Request.allPaths source 64 | tuples <- Map.toList mutable 65 | return tuples 66 | 67 | convertBody :: MutableIO Spark.Request -> IO (Maybe String) 68 | convertBody source = do 69 | Spark.Request.body source 70 | 71 | -- __ __ _ 72 | -- \ \ / / | | 73 | -- \ \ / /__ _ __| |__ ___ 74 | -- \ \/ / _ \ '__| '_ \/ __| 75 | -- \ / __/ | | |_) \__ \ 76 | -- \/ \___|_| |_.__/|___/ 77 | -- 78 | 79 | {-- 80 | Creates an HTTP GET endpoint. It receives: 81 | 82 | - A path 83 | - A function handling the request 84 | 85 | The function is a lambda function receiving a chinook.Request 86 | and a chinook.Response and returns an IO (Maybe String). 87 | 88 | -} 89 | -- tag::getFunction[] 90 | get :: String -> (IO Core.Request -> IO Core.Response) -> IO () 91 | -- end::getFunction[] 92 | get path lambda = do 93 | route <- toSparkRoute lambda 94 | Spark.Rest.get path route 95 | 96 | {-- 97 | Creates an HTTP POST endpoint. It receives: 98 | 99 | - A path 100 | - A function handling the request 101 | 102 | The function is a lambda function receiving a chinook.Request 103 | and a chinook.Response and returns an IO (). 104 | 105 | -} 106 | post :: String -> (IO Core.Request -> IO Core.Response) -> IO () 107 | post path lambda = do 108 | route <- toSparkRoute lambda 109 | Spark.Rest.post path route 110 | 111 | {-- 112 | Creates an HTTP DELETE endpoint. It receives: 113 | 114 | - A path 115 | - A function handling the request 116 | 117 | The function is a lambda function receiving a chinook.Request 118 | and a chinook.Response and returns an IO (). 119 | 120 | -} 121 | -- tag::deleteFunction[] 122 | delete :: String -> (IO Core.Request -> IO Core.Response) -> IO () 123 | -- end::deleteFunction[] 124 | delete path lambda = do 125 | route <- toSparkRoute lambda 126 | Spark.Rest.delete path route 127 | 128 | {-- 129 | Creates an HTTP PUT endpoint. It receives: 130 | 131 | - A path 132 | - A function handling the request 133 | 134 | The function is a lambda function receiving a chinook.Request 135 | and a chinook.Response and returns an IO (). 136 | 137 | -} 138 | -- tag::putFunction[] 139 | put :: String -> (IO Core.Request -> IO Core.Response) -> IO () 140 | -- end::putFunction[] 141 | put path lambda = do 142 | route <- toSparkRoute lambda 143 | Spark.Rest.put path route 144 | 145 | {-- 146 | Creates an HTTP PATCH endpoint. It receives: 147 | 148 | - A path 149 | - A function handling the request 150 | 151 | The function is a lambda function receiving a chinook.Request 152 | and a chinook.Response and returns an IO (). 153 | 154 | -} 155 | -- tag::patchFunction[] 156 | patch :: String -> (IO Core.Request -> IO Core.Response) -> IO () 157 | -- end::patchFunction[] 158 | patch path lambda = do 159 | route <- toSparkRoute lambda 160 | Spark.Rest.patch path route 161 | 162 | {-- 163 | Creates an HTTP OPTIONS endpoint. It receives: 164 | 165 | - A path 166 | - A function handling the request 167 | 168 | The function is a lambda function receiving a chinook.Request 169 | and a chinook.Response and returns an IO (). 170 | 171 | -} 172 | -- tag::optionsFunction[] 173 | options :: String -> (IO Core.Request -> IO Core.Response) -> IO () 174 | -- end::optionsFunction[] 175 | options path lambda = do 176 | route <- toSparkRoute lambda 177 | Spark.Rest.options path route 178 | 179 | {-- 180 | Creates an TRACE endpoint. It receives: 181 | 182 | - A path 183 | - A function handling the request 184 | 185 | The function is a lambda function receiving a chinook.Request 186 | and a chinook.Response and returns an IO (). 187 | 188 | -} 189 | -- tag::traceFunction[] 190 | trace :: String -> (IO Core.Request -> IO Core.Response) -> IO () 191 | -- end::traceFunction[] 192 | trace path lambda = do 193 | route <- toSparkRoute lambda 194 | Spark.Rest.trace path route 195 | 196 | {-- 197 | Creates an CONNECT endpoint. It receives: 198 | 199 | - A path 200 | - A function handling the request 201 | 202 | The function is a lambda function receiving a chinook.Request 203 | and a chinook.Response and returns an IO (). 204 | 205 | -} 206 | -- tag::connectFunction[] 207 | connect :: String -> (IO Core.Request -> IO Core.Response) -> IO () 208 | -- end::connectFunction[] 209 | connect path lambda = do 210 | route <- toSparkRoute lambda 211 | Spark.Rest.connect path route 212 | 213 | {-- 214 | Creates an HEAD endpoint. It receives: 215 | 216 | - A path 217 | - A function handling the request 218 | 219 | The function is a lambda function receiving a chinook.Request 220 | and a chinook.Response and returns an IO (). 221 | 222 | -} 223 | -- tag::connectFunction[] 224 | head :: String -> (IO Core.Request -> IO Core.Response) -> IO () 225 | -- end::connectFunction[] 226 | head path lambda = do 227 | route <- toSparkRoute lambda 228 | Spark.Rest.head path route 229 | 230 | -- tag::beforeFilter[] 231 | before :: String -> (IO Core.Request -> IO Core.Response) -> IO () 232 | -- end::beforeFilter[] 233 | before path lambda = do 234 | route <- toSparkFilter lambda 235 | Spark.Rest.before path route 236 | 237 | -- tag::afterFilter[] 238 | after :: String -> (IO Core.Request -> IO Core.Response) -> IO () 239 | -- end::afterFilter[] 240 | after path lambda = do 241 | route <- toSparkFilter lambda 242 | Spark.Rest.after path route 243 | 244 | {-- 245 | Takes the response generated in a Chinook Handler and converts that response 246 | in a Spark valid response 247 | -} 248 | applyChinookResponseToSpark :: Core.Response -> MutableIO Spark.Response -> IO () 249 | applyChinookResponseToSpark response spark = do 250 | setSparkStatus response.status spark 251 | setSparkOutput response.output spark 252 | setSparkHeaders response.headers spark 253 | 254 | {-- 255 | When processing an interceptor if the result response has the `halting` property 256 | set to true, then the process should be stopped 257 | -} 258 | applyPossibleHalt :: Core.Response -> MutableIO Spark.Response -> IO () 259 | applyPossibleHalt response spark = if (response.halting == true) 260 | then Spark.Rest.halt response.status response.output 261 | else return () 262 | 263 | setSparkStatus :: Int -> MutableIO Spark.Response -> IO () 264 | setSparkStatus status response = response.status status 265 | 266 | setSparkOutput :: Maybe String -> MutableIO Spark.Response -> IO () 267 | setSparkOutput output response = case output of 268 | Just output -> response.body output 269 | Nothing -> response.body "" 270 | 271 | setSparkHeaders :: [(String, Maybe String)] -> MutableIO Spark.Response -> IO () 272 | setSparkHeaders [] response = return () 273 | setSparkHeaders (x:xs) response = case x of 274 | (a, Just b) -> response.header a b 275 | _ -> setSparkHeaders xs response 276 | 277 | -- Converts a handler to a Spark route instance 278 | toSparkRoute :: (IO Core.Request -> IO Core.Response) -> IO (MutableIO Route) 279 | toSparkRoute lambda = Spark.Route.new $ toSparkOutput $ \req \res -> do 280 | response <- lambda $ toChinookRequest req 281 | applyChinookResponseToSpark response res 282 | return $ response.output 283 | 284 | -- Converts an interceptor to a Spark filter instance 285 | toSparkFilter :: (IO Core.Request -> IO Core.Response) -> IO (MutableIO Filter) 286 | toSparkFilter lambda = Spark.Filter.new $ toSparkFilterOutput $ \req \res -> do 287 | response <- lambda $ toChinookRequest req 288 | applyChinookResponseToSpark response res 289 | applyPossibleHalt response res 290 | return $ response.output 291 | 292 | toSparkFilterOutput :: (a -> b -> IO (Maybe String)) -> (a -> b -> IO ()) 293 | toSparkFilterOutput fn1 request response = do 294 | result <- fn1 request response 295 | return () 296 | 297 | {-- 298 | While within Frege we want to work with safe abstractions like 299 | Maybe or Either, Spark needs to receive a concrete Java type. 300 | This transformation narrows the gap between both worlds. 301 | 302 | Apart from this function, there is also another part of the 303 | bridge between Frege->Spark written in Spark.Rest.java 304 | -} 305 | toSparkOutput :: (a -> b -> IO (Maybe String)) -> (a -> b -> IO String) 306 | toSparkOutput fn1 request response = do 307 | result <- fn1 request response 308 | return $ fromMaybe blank result 309 | 310 | {-- 311 | Maps between `Router.Resource` mappings to realworld 312 | endpointgs 313 | -} 314 | convertRoute :: Router.Resource -> IO () 315 | convertRoute route = case route of 316 | Router.Get path handler -> get path handler 317 | Router.Post path handler -> post path handler 318 | Router.Put path handler -> put path handler 319 | Router.Delete path handler -> delete path handler 320 | Router.Patch path handler -> patch path handler 321 | Router.Options path handler -> options path handler 322 | Router.Trace path handler -> trace path handler 323 | Router.Head path handler -> head path handler 324 | Router.Before path handler -> before path handler 325 | Router.After path handler -> after path handler 326 | Router.Family path routes -> mapM_ convertRoute routes 327 | 328 | {-- 329 | Settings like application available port or static files 330 | location are represented by `Configuration`. Instead of 331 | using (String,String) pairs, it seems to be safer to use 332 | types to represent those settings, e.g instead of 333 | writing ("port", "808x") and receive a runtime error it's 334 | better to write `port 8080` which is checked at compile time. 335 | -} 336 | -- tag::configuration[] 337 | data Configuration = IntConfig String Int | 338 | StrConfig String String 339 | -- end::configuration[] 340 | 341 | --- Creates settings for service port 342 | -- tag::port[] 343 | port :: Int -> Configuration 344 | -- end::port[] 345 | port number = IntConfig "port" number 346 | 347 | --- Creates settings for static files location 348 | -- tag::staticFiles[] 349 | staticFiles :: String -> Configuration 350 | -- end::staticFiles[] 351 | staticFiles path = StrConfig "staticFileLocation" path 352 | 353 | --- Translates `Configuration` values to real world settings 354 | configure :: Configuration -> IO () 355 | configure cfg = case cfg of 356 | IntConfig "port" value -> Spark.Config.port value 357 | StrConfig "staticFileLocation" value -> Spark.Config.staticFileLocation value 358 | _ -> return () 359 | 360 | {-- 361 | Bootstraps the application. It receives configuration 362 | settings as well as the resource mappings 363 | -} 364 | run :: [Configuration] -> [Resource] -> IO () 365 | run cfg mappings = do 366 | _ <- mapM_ configure cfg 367 | mapM_ convertRoute mappings 368 | -------------------------------------------------------------------------------- /chinook-core/src/main/frege/chinook/Core.fr: -------------------------------------------------------------------------------- 1 | module chinook.Core where 2 | 3 | import chinook.Spark as Spark 4 | import chinook.Utils (blank) 5 | 6 | import Data.List (lookup) 7 | import frege.java.Util (Set, Map) 8 | import frege.data.Iterators (ArrayIterator) 9 | 10 | {-- 11 | This type is an alias for handlers used in Chinook 12 | -} 13 | -- tag::handler[] 14 | type Handler = IO Request -> IO Response 15 | -- end::handler[] 16 | 17 | -- _____ _ 18 | -- | __ \ | | 19 | -- | |__) |___ __ _ _ _ ___ ___| |_ 20 | -- | _ // _ \/ _` | | | |/ _ \/ __| __| 21 | -- | | \ \ __/ (_| | |_| | __/\__ \ |_ 22 | -- |_| \_\___|\__, |\__,_|\___||___/\__| 23 | -- | | 24 | -- |_| 25 | 26 | -- tag::request[] 27 | data Request = Request { headers :: [(String, Maybe String)], 28 | queryParams :: [(String, [String])], 29 | pathParams :: [(String, String)], 30 | body :: Maybe String } 31 | -- end::request[] 32 | 33 | {-- 34 | This type class is to make the access to the 35 | IO Request easier to the user 36 | -} 37 | class Requestable a where 38 | allHeaders :: a -> IO [(String, Maybe String)] 39 | header :: a -> String -> IO (Maybe String) 40 | allParams :: a -> IO [(String, [String])] 41 | params :: a -> String -> IO (Maybe [String]) 42 | param :: a -> String -> IO (Maybe String) 43 | allPaths :: a -> IO [(String, String)] 44 | path :: a -> String -> IO (Maybe String) 45 | body :: a -> IO (Maybe String) 46 | 47 | {-- 48 | Instance of Requestable to make the access to the 49 | IO Request more user-friendly 50 | -} 51 | instance Requestable (IO Request) where 52 | --- Retrieves all headers from a given request 53 | -- tag::allheaders[] 54 | allHeaders :: IO Request -> IO [(String, Maybe String)] 55 | -- end::allheaders[] 56 | allHeaders req = do 57 | request <- req 58 | case request of 59 | Request { headers, queryParams, pathParams} -> return headers 60 | _ -> return [] 61 | 62 | --- Returns a value stored under a given header name 63 | -- tag::header[] 64 | header :: IO Request -> String -> IO (Maybe String) 65 | -- end::header[] 66 | header req name = do 67 | headers <- allHeaders req 68 | return $ join $ lookup name headers 69 | 70 | -- Retrieves all query params from a given request 71 | allParams :: IO Request -> IO [(String, [String])] 72 | allParams req = do 73 | request <- req 74 | case request of 75 | Request { headers, queryParams, pathParams } -> return queryParams 76 | _ -> return [] 77 | 78 | -- Returns all param values stored under a given query param name 79 | params :: IO Request -> String -> IO (Maybe [String]) 80 | params req name = lookup name <$> allParams req 81 | 82 | -- Returns first available value stored under a given query param name 83 | -- tag::param[] 84 | param :: IO Request -> String -> IO (Maybe String) 85 | -- end::param[] 86 | param req name = do 87 | all <- params req name 88 | return $ fmap PreludeList.head all 89 | 90 | -- Returns all path params 91 | -- tag::allpaths[] 92 | allPaths :: IO Request -> IO [(String, String)] 93 | -- end::allpaths[] 94 | allPaths req = do 95 | request <- req 96 | case request of 97 | Request { headers, queryParams, pathParams } -> return pathParams 98 | _ -> return [] 99 | 100 | -- Returns a specific path param by its name 101 | -- tag::path[] 102 | path :: IO Request -> String -> IO (Maybe String) 103 | -- end::path[] 104 | path req name = do 105 | all <- allPaths req 106 | return $ lookup name all 107 | 108 | body :: IO Request -> IO (Maybe String) 109 | body req = do 110 | request <- req 111 | return $ request.body 112 | 113 | derive Show Request 114 | 115 | -- _____ 116 | -- | __ \ 117 | -- | |__) |___ ___ _ __ ___ _ __ ___ ___ 118 | -- | _ // _ \/ __| '_ \ / _ \| '_ \/ __|/ _ \ 119 | -- | | \ \ __/\__ \ |_) | (_) | | | \__ \ __/ 120 | -- |_| \_\___||___/ .__/ \___/|_| |_|___/\___| 121 | -- | | 122 | -- |_| 123 | 124 | -- tag::response[] 125 | data Response = Response { status :: Int, 126 | halting :: Bool, 127 | output :: Maybe String, 128 | headers :: [(String, Maybe String)]} where 129 | -- end::response[] 130 | -- Adds a new response header and returns a new response 131 | addHeader :: Response -> (String, Maybe String) -> Response 132 | addHeader res header = res.{ headers <- (header:) } 133 | 134 | -- Sets HTTP status and returns a new response 135 | setStatus :: Response -> Int -> Response 136 | setStatus res status = res.{ status = status} 137 | 138 | -- Sets output message and returns a new response 139 | setOutput :: Response -> Maybe String -> Response 140 | setOutput res message = res.{ output = message } 141 | 142 | derive Show Response 143 | 144 | -- Default response. 145 | --tag::responseconstant[] 146 | response = Response 200 false Nothing [] 147 | --end::responseconstant[] 148 | 149 | haltingResponse = Response 200 true Nothing [] 150 | -------------------------------------------------------------------------------- /chinook-core/src/main/frege/chinook/Router.fr: -------------------------------------------------------------------------------- 1 | module chinook.Router where 2 | 3 | import chinook.Core 4 | 5 | {-- 6 | The `Resource` type represents all possible resources 7 | exposed by Chinook. 8 | -} 9 | -- tag::resources[] 10 | data Resource = Get String Handler | 11 | Post String Handler | 12 | Put String Handler | 13 | Delete String Handler | 14 | Patch String Handler | 15 | Options String Handler | 16 | Trace String Handler | 17 | Head String Handler | 18 | Before String Handler | 19 | After String Handler | 20 | Family String [Resource] 21 | -- end::resources[] 22 | 23 | {-- 24 | When nesting resources we need children to be aware 25 | of parent's path to end with a complete path. 26 | -} 27 | modify :: Resource -> String -> Resource 28 | modify res parent = case res of 29 | Get path handler -> Get (parent ++ path) handler 30 | Post path handler -> Post (parent ++ path) handler 31 | Put path handler -> Put (parent ++ path) handler 32 | Delete path handler -> Delete (parent ++ path) handler 33 | Patch path handler -> Patch (parent ++ path) handler 34 | Options path handler -> Options (parent ++ path) handler 35 | Trace path handler -> Trace (parent ++ path) handler 36 | Head path handler -> Head (parent ++ path) handler 37 | Before path handler -> Before (parent ++ path) handler 38 | After path handler -> After (parent ++ path) handler 39 | 40 | {-- 41 | 42 | Most of the time we may want to nest URI hierarchies. In 43 | order to achieve that we use the `Family` type. 44 | 45 | A family has the family `path` and all children resources: 46 | 47 | resources = [ 48 | Before "/" securityHandler, 49 | After "/" auditHandler, 50 | "/quest" + [ 51 | (Get "/" findAll), 52 | (Get "/:id" find), 53 | (Post "/" save), 54 | (Delete "/:id" delete) 55 | ], 56 | "/stats" + [ 57 | (Get "/metrics" getMetrics), 58 | (Get "/critical" getCritical) 59 | ], 60 | Get "/ping" ping, 61 | ] 62 | -} 63 | (+) :: String -> [Resource] -> Resource 64 | (+) family [] = Family family [] 65 | (+) family (x:xs) = Family family $ case x of 66 | Family path res -> (path + res) : [(family + xs)] 67 | _ -> (modify x family) : [(family + xs)] 68 | -------------------------------------------------------------------------------- /chinook-core/src/main/frege/chinook/Spark.fr: -------------------------------------------------------------------------------- 1 | {- 2 | This module does the mapping from Sparkjava abstractions 3 | to Frege. 4 | --} 5 | module chinook.Spark where 6 | 7 | import frege.java.Util (Set, Map) 8 | -- _____ _ 9 | -- | __ \ | | 10 | -- | |__) |___ __ _ _ _ ___ ___| |_ 11 | -- | _ // _ \/ _` | | | |/ _ \/ __| __| 12 | -- | | \ \ __/ (_| | |_| | __/\__ \ |_ 13 | -- |_| \_\___|\__, |\__,_|\___||___/\__| 14 | -- | | 15 | -- |_| 16 | 17 | data QueryMap = native spark.QueryParamsMap where 18 | native toMap :: MutableIO QueryMap -> IO (MutableIO (Map String (JArray String))) 19 | {- 20 | Request type and related methods to get information 21 | from the request. 22 | --} 23 | data Request = native spark.Request where 24 | -- value of foo path parameter 25 | native paths params :: MutableIO Request -> String -> IO (Maybe String) 26 | -- all path parameters 27 | native allPaths params :: MutableIO Request -> IO (MutableIO (Map String String)) 28 | -- value of FOO query param 29 | native queryParams :: MutableIO Request -> String -> IO (Maybe String) 30 | -- all query parameters 31 | native allQueryParams queryParams :: MutableIO Request -> IO (MutableIO (Set String)) 32 | -- all values of FOO query param 33 | native queryParamsValues :: MutableIO Request -> String -> IO (ArrayOf RealWorld String) 34 | -- the query map 35 | native queryMap :: MutableIO Request -> IO (MutableIO QueryMap) 36 | -- query map for a certain parameter 37 | native queryMapFor queryMap :: MutableIO Request -> String -> IO (MutableIO QueryMap) 38 | -- the query param list 39 | native params queryParams :: MutableIO Request -> String -> IO (Maybe String) 40 | -- the attributes list 41 | native attributes :: MutableIO Request -> IO (MutableIO (Set String)) 42 | -- value of foo attribute 43 | native gattr attribute :: MutableIO Request -> Maybe String -> IO (Maybe Object) 44 | -- sets value of attribute A to V 45 | native sattr attribute :: MutableIO Request -> Maybe String -> Maybe String -> IO () 46 | -- request body sent by the client 47 | native body :: MutableIO Request -> IO (Maybe String) 48 | -- request body as bytes 49 | native bodyAsBytes :: MutableIO Request -> IO (ArrayOf RealWorld Byte) 50 | -- length of request body 51 | native contentLength :: MutableIO Request -> IO (Int) 52 | -- content type of request.body 53 | native contentType :: MutableIO Request -> IO (Maybe String) 54 | -- the context path, e.g. "/hello" 55 | native contextPath :: MutableIO Request -> IO (Maybe String) 56 | -- request cookies sent by the client 57 | native cookies :: MutableIO Request -> IO (MutableIO (Map String String)) 58 | -- Get cookies by name 59 | native cookie :: MutableIO Request -> String -> IO (Maybe String) 60 | -- the HTTP header list 61 | native allHeaders headers :: MutableIO Request -> IO (MutableIO (Set String)) 62 | -- value of BAR header 63 | native headers :: MutableIO Request -> String -> IO (Maybe String) 64 | -- the host, e.g. "example.com" 65 | native host :: MutableIO Request -> IO (Maybe String) 66 | -- client IP address 67 | native ip :: MutableIO Request -> IO (Maybe String) 68 | -- the path info 69 | native pathInfo :: MutableIO Request -> IO (Maybe String) 70 | -- the server port 71 | native port :: MutableIO Request -> IO (Maybe Int) 72 | -- the protocol, e.g. HTTP/1.1 73 | native protocol :: MutableIO Request -> IO (Maybe String) 74 | -- The HTTP method (GET, ..etc) 75 | native requestMethod :: MutableIO Request -> IO (Maybe String) 76 | -- "http" 77 | native scheme :: MutableIO Request -> IO (Maybe String) 78 | -- the servlet path, e.g. /result.jsp 79 | native servletPath :: MutableIO Request -> IO (Maybe String) 80 | -- splat (*) parameters: 81 | native splat :: MutableIO Request -> IO (ArrayOf RealWorld String) 82 | -- the uri, e.g. "http://example.com/foo" 83 | native uri :: MutableIO Request -> IO (Maybe String) 84 | -- the url. e.g. "http://example.com/foo" 85 | native url :: MutableIO Request -> IO (Maybe String) 86 | -- The user agent 87 | native userAgent :: MutableIO Request -> IO (Maybe String) 88 | 89 | -- _____ 90 | -- | __ \ 91 | -- | |__) |___ ___ _ __ ___ _ __ ___ ___ 92 | -- | _ // _ \/ __| '_ \ / _ \| '_ \/ __|/ _ \ 93 | -- | | \ \ __/\__ \ |_) | (_) | | | \__ \ __/ 94 | -- |_| \_\___||___/ .__/ \___/|_| |_|___/\___| 95 | -- | | 96 | -- |_| 97 | 98 | {- 99 | Response type and related methods to complete/alter 100 | the response. 101 | --} 102 | data Response = native spark.Response where 103 | native status :: MutableIO Response -> Int -> IO () 104 | native body :: MutableIO Response -> String -> IO () 105 | native header :: MutableIO Response -> String -> String -> IO () 106 | native cookie :: MutableIO Response -> String -> String -> IO () 107 | native contentType `type` :: MutableIO Response -> String -> IO () 108 | 109 | -- _____ _ 110 | -- | __ \ | | 111 | -- | |__) |___ _ _| |_ ___ 112 | -- | _ // _ \| | | | __/ _ \ 113 | -- | | \ \ (_) | |_| | || __/ 114 | -- |_| \_\___/ \__,_|\__\___| 115 | -- 116 | 117 | {- 118 | A Route is the function handling the current request. The only 119 | way of creating a route is by using the method Route.new and 120 | passing a lambda expression of type (MutableIO Request -> MutableIO Response -> IO a) 121 | --} 122 | data Route = native spark.Route where 123 | native new chinook.into.Rest.createRoute :: (MutableIO Request -> MutableIO Response -> IO a) -> IO (MutableIO Route) 124 | 125 | data Filter = native spark.Filter where 126 | native new chinook.into.Rest.createFilter :: (MutableIO Request -> MutableIO Response -> IO ()) -> IO (MutableIO Filter) 127 | 128 | -- _____ _ 129 | -- / ____| | | 130 | -- | (___ _ __ __ _ _ __| | __ 131 | -- \___ \| '_ \ / _` | '__| |/ / 132 | -- ____) | |_) | (_| | | | < 133 | -- |_____/| .__/ \__,_|_| |_|\_\ 134 | -- | | 135 | -- |_| 136 | 137 | {-- 138 | Rest wraps all sparkjava.com calls and exposes to Frege 139 | -} 140 | data Rest = native spark.Spark where 141 | native get spark.Spark.get :: String -> MutableIO Route -> IO () 142 | native post spark.Spark.post :: String -> MutableIO Route -> IO () 143 | native delete spark.Spark.delete :: String -> MutableIO Route -> IO () 144 | native put spark.Spark.put :: String -> MutableIO Route -> IO () 145 | native patch spark.Spark.patch :: String -> MutableIO Route -> IO () 146 | native options spark.Spark.options :: String -> MutableIO Route -> IO () 147 | native trace spark.Spark.trace :: String -> MutableIO Route -> IO () 148 | native connect spark.Spark.connect :: String -> MutableIO Route -> IO () 149 | native head spark.Spark.connect :: String -> MutableIO Route -> IO () 150 | native before spark.Spark.before :: String -> MutableIO Filter -> IO () 151 | native after spark.Spark.after :: String -> MutableIO Filter -> IO () 152 | native halt spark.Spark.halt :: Int -> Maybe String -> IO () 153 | 154 | {-- 155 | Config wraps all sparkjava.com settings calls 156 | -} 157 | data Config = native spark.Spark where 158 | native port spark.Spark.port :: Int -> IO () 159 | native staticFileLocation spark.Spark.staticFileLocation :: String -> IO () 160 | -------------------------------------------------------------------------------- /chinook-core/src/main/frege/chinook/Utils.fr: -------------------------------------------------------------------------------- 1 | {- 2 | This module holds utility functions 3 | --} 4 | module chinook.Utils where 5 | 6 | {-- Returns an empty String -} 7 | blank = "" 8 | -------------------------------------------------------------------------------- /chinook-core/src/main/frege/chinook/util/ContentType.fr: -------------------------------------------------------------------------------- 1 | module chinook.util.ContentType where 2 | 3 | json = ("Content-Type", Just "application/json") 4 | html = ("Content-Type", Just "text/html") 5 | -------------------------------------------------------------------------------- /chinook-core/src/main/java/chinook/into/Rest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright [2015] [Janus Lynd] 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package chinook.into; 17 | 18 | import frege.run7.Func; 19 | import frege.run7.Thunk; 20 | import frege.runtime.Phantom.RealWorld; 21 | 22 | import spark.Request; 23 | import spark.Response; 24 | import spark.Route; 25 | import spark.Filter; 26 | 27 | /** 28 | * Helper class for creating instances from lambda expressions 29 | * 30 | * @since 1.0.0 31 | * 32 | */ 33 | public final class Rest { 34 | 35 | /** 36 | * @since 0.2.1 37 | */ 38 | static class Builder { 39 | 40 | private final Request request; 41 | private final Response response; 42 | 43 | /** 44 | * @param request 45 | * @param response 46 | * @since 0.2.1 47 | */ 48 | public Builder(final Request request, final Response response) { 49 | this.request = request; 50 | this.response = response; 51 | } 52 | 53 | /** 54 | * @param fn 55 | * @return 56 | * @since 0.2.1 57 | */ 58 | public A execute(final Func.U>> fn) { 59 | return fn 60 | .apply(Thunk.lazy(request)) // First parameter 61 | .call() 62 | .apply(Thunk.lazy(response)) // Second parameter 63 | .call() 64 | .apply(null) // Execute IO to get value 65 | .call(); 66 | } 67 | } 68 | 69 | /** 70 | * Creates a new {@link Route} instance from a {@link Func.U} expression. 71 | * 72 | * @param fn The function representing the behavior of the 73 | * {@link Route#handle(spark.Request, spark.Response) } method 74 | */ 75 | public static spark.Route createRoute(final Func.U>> fn) { 76 | return new Route() { 77 | @Override 78 | public Object handle(final Request request, final Response response) throws Exception { 79 | return new Builder(request, response).execute(fn); 80 | } 81 | }; 82 | } 83 | 84 | /** 85 | * Creates a new {@link Route} instance from a {@link Func.U} expression. 86 | * 87 | * @param fn The function representing the behavior of the 88 | * {@link Route#handle(spark.Request, spark.Response) } method 89 | * @since 0.2.1 90 | */ 91 | public static spark.Filter createFilter(final Func.U>> fn) { 92 | return new Filter() { 93 | @Override 94 | public void handle(final Request request, final Response response) throws Exception { 95 | new Builder(request, response).execute(fn); 96 | } 97 | }; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /chinook-docs/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath 'org.ajoberstar:gradle-git:1.4.2' 8 | classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.2' 9 | } 10 | } 11 | 12 | apply plugin: 'org.asciidoctor.convert' 13 | apply plugin: 'org.ajoberstar.github-pages' 14 | 15 | asciidoctor { 16 | attributes 'source-highlighter': 'prettify', 17 | toc : 'left', 18 | icons : 'font', 19 | toclevels : 3, 20 | 'pdf-style' : 'default', 21 | 'chinook-core' : releaseVersion, 22 | 'frege-core' : libraries.frege_core, 23 | 'frege-repl' : libraries.frege_repl, 24 | 'spark-java' : libraries.spark_java, 25 | 'target-jdk' : libraries.jdk 26 | } 27 | 28 | githubPages { 29 | repoUri = 'https://github.com/fregelab/chinook.git' 30 | deleteExistingFiles = true 31 | 32 | pages { 33 | from file('build/asciidoc/html5') 34 | } 35 | 36 | //https://github.com/ajoberstar/gradle-git/wiki/Travis-CI-Authentication 37 | credentials { 38 | username = System.getenv('GH_TOKEN') 39 | password = '' 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /chinook-docs/src/docs/asciidoc/chapter/development.ad: -------------------------------------------------------------------------------- 1 | == Development 2 | 3 | === License 4 | 5 | `Chinook` uses http://www.apache.org/licenses/LICENSE-2.0[Apache 2.0 license] 6 | 7 | === Frege docs 8 | 9 | IMPORTANT: At the moment there is an issue with how `FregeDoc` that 10 | prevents `Chinook` from publishing its fregedocs. 11 | 12 | === Github 13 | 14 | `Chinook` source code is hosted at https://github.com/januslynd/chinook[Github] 15 | -------------------------------------------------------------------------------- /chinook-docs/src/docs/asciidoc/chapter/getting.ad: -------------------------------------------------------------------------------- 1 | == Getting Started 2 | 3 | WARNING: Before going any further make sure you have installed JDK 7+ 4 | in your machine. 5 | 6 | Clone `Chinook` project at github and from the project's root folder 7 | execute: 8 | 9 | [source, shell] 10 | ---- 11 | ./gradlew :chinook-sample:run 12 | ---- 13 | 14 | WARNING: If it is your first execution and you don't have Gradle or 15 | the project dependencies already installed, it may take a while before 16 | executing `Chinook`. 17 | 18 | Once the server has started: 19 | 20 | [source, shell] 21 | ---- 22 | [Thread-0] INFO org.eclipse.jetty.server.Server - Started @292ms 23 | ---- 24 | 25 | You can go to your browser a go to http://localhost:8080/hi You should 26 | see the phrase `Hello World from Chinook :-)`. 27 | 28 | -------------------------------------------------------------------------------- /chinook-docs/src/docs/asciidoc/chapter/http.ad: -------------------------------------------------------------------------------- 1 | == HTTP 2 | 3 | include::http_intro.ad[] 4 | 5 | include::http_req_res.ad[] 6 | 7 | include::http_config.ad[] 8 | 9 | include::http_templating.ad[] 10 | -------------------------------------------------------------------------------- /chinook-docs/src/docs/asciidoc/chapter/http_config.ad: -------------------------------------------------------------------------------- 1 | === Configuration 2 | 3 | Settings are passed when bootstraping the application. Instead of 4 | passing a list of tuples `Chinook` expects values of type 5 | `chinook.Chinook.Configuration`. 6 | 7 | Anyway `Chinook` provides a set of functions as a shortcut to 8 | produce these settings. 9 | 10 | [source, haskell] 11 | ---- 12 | include::{sampleSrc}/chinook/App.fr[tags=app] 13 | ---- 14 | 15 | ==== Port 16 | 17 | To produce server port settings you can use the port function: 18 | 19 | [source, haskell] 20 | ---- 21 | include::{coreSrc}/chinook/Chinook.fr[tags=port] 22 | ---- 23 | 24 | ==== Static Files 25 | 26 | You can assign a folder in the classpath to serve static files, with 27 | the `staticFiles` function. 28 | 29 | For instance `staticFiles "/public"` will make files located at 30 | "src/main/resources/public" be exposed at `http://{server}:{port}` 31 | 32 | [source, haskell] 33 | ---- 34 | include::{coreSrc}/chinook/Chinook.fr[tags=staticFiles] 35 | ---- 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /chinook-docs/src/docs/asciidoc/chapter/http_intro.ad: -------------------------------------------------------------------------------- 1 | === Overview 2 | 3 | Chinook is a tiny web framework aimed to create microservices. A basic 4 | `Chinook` application needs only three steps to expose an endpoint: 5 | 6 | * `Handler` 7 | * `Endpoint registration` 8 | * `Main method` 9 | 10 | A whole `Chinook` application could as simple as: 11 | 12 | [source, haskell] 13 | ---- 14 | import chinook.Core 15 | import chinook.Chinook 16 | 17 | main _ = Chinook.run [] [ 18 | Get "/hello" $ \req -> do 19 | return response.{ output = Just "Hello World" } 20 | ] 21 | ---- 22 | 23 | ==== Handler 24 | 25 | Handlers are the most important part of Chinook, is where client 26 | requests are handled. Chinook http handlers normally have the 27 | following signature: 28 | 29 | [source, haskell] 30 | ---- 31 | IO Request -> IO Reponse 32 | ---- 33 | 34 | As you may see there's no difference between handlers used with 35 | different http verbs: 36 | 37 | [source, haskell] 38 | .GET 39 | ---- 40 | include::{sampleSrc}/chinook/Handlers.fr[tags=helloWorldHandler] 41 | ---- 42 | 43 | [source, haskell] 44 | .DELETE 45 | ---- 46 | include::{sampleSrc}/chinook/Handlers.fr[tags=deleteHandler] 47 | ---- 48 | 49 | What makes them different ? The endpoint registration. 50 | 51 | ==== Endpoint registration 52 | 53 | The way `Chinook` works at the moment is a main entry point where 54 | handlers are registered for a specific `http verb` and a `path`. 55 | 56 | It always follows the same pattern: 57 | 58 | [source, haskell] 59 | ---- 60 | http-verb "/registered-path" handler 61 | ---- 62 | 63 | If you take a look at the `chinook-sample` application, the main entry 64 | point is located in the `App.fr` file. 65 | 66 | [source, haskell] 67 | ---- 68 | include::{sampleSrc}/chinook/App.fr[tags=routes] 69 | ---- 70 | 71 | Here you can see all available endpoints in your application. For a 72 | given URL there is the related handler function. How you manage your 73 | handler files is completely up to you. 74 | 75 | All `HTTP` registration functions have the same structure, here is the 76 | function to register a HTTP `GET` handler: 77 | 78 | [source, haskell] 79 | ---- 80 | include::{coreSrc}/chinook/Router.fr[tags=resources] 81 | ---- 82 | 83 | It basically needs: 84 | 85 | - A `String` representing the URI resource A handler function to 86 | - A `Handler` to deal with the `request` and `response` originated in 87 | this invocation. 88 | 89 | NOTE: a handler is just an alias for a function with the following 90 | shape: 91 | 92 | [source, haskell] 93 | ---- 94 | include::{coreSrc}/chinook/Core.fr[tags=handler] 95 | ---- 96 | 97 | A `handler` basically receives a `IO Request` and should return a `IO 98 | Response`. Both are available undere `chinook.Core`: 99 | 100 | [source, haskell] 101 | ---- 102 | include::{sampleSrc}/chinook/Handlers.fr[tags=basic-imports] 103 | ---- 104 | 105 | Here's a simple example: 106 | 107 | [source, haskell] 108 | ---- 109 | include::{sampleSrc}/chinook/Handlers.fr[tags=helloWorldHandler] 110 | ---- 111 | 112 | NOTE: `Core.response` is a way of creating a new 113 | `chinook.Core.Response` without having to specify all possible 114 | fields in `Response`. It creates a new copy from the default 115 | `Response` value, but it lets you set different field values. This is 116 | not a `Chinook` thing but `Frege's` and it's call `value update`. 117 | 118 | If you want a `GET` handler to return an output you should be setting 119 | the response's `output` field. 120 | 121 | Then the only thing remaining is to register the handler to receive 122 | a get call in a given URI: 123 | 124 | [source, haskell] 125 | ---- 126 | include::{sampleSrc}/chinook/App.fr[tags=helloWorldHandler, indent=0] 127 | ---- 128 | 129 | ==== Main 130 | 131 | To start a `Chinook` application you only have to invoke the main 132 | function containing all the endpoint registrations as we saw in the 133 | previous chapter. 134 | 135 | [source, haskell] 136 | ---- 137 | include::{sampleSrc}/chinook/App.fr[tags=app, indent=0] 138 | ---- 139 | 140 | This is independent of the tool used to call that method. For instance, 141 | using gradle you can use the `application` plugin. 142 | 143 | [source, groovy] 144 | ---- 145 | include::{sampleBase}/build.gradle[] 146 | ---- 147 | 148 | <1> Gradle Application plugin 149 | <2> Namespace where the `main` function is located 150 | -------------------------------------------------------------------------------- /chinook-docs/src/docs/asciidoc/chapter/http_req_res.ad: -------------------------------------------------------------------------------- 1 | === Request / Response 2 | 3 | ==== Basics 4 | 5 | ===== To return simple values 6 | 7 | The first endpoint returns a typical `hello world` message when 8 | invoking the `/hi` endpoint. Here is the handler implementation: 9 | 10 | [source, haskell] 11 | ---- 12 | include::{sampleSrc}/chinook/Handlers.fr[tags=helloWorldHandler] 13 | ---- 14 | 15 | ===== Changing response 16 | 17 | Sometimes depending on the request we may want to change something 18 | about the response. 19 | 20 | [source, haskell] 21 | ---- 22 | include::{sampleSrc}/chinook/Handlers.fr[tags=greetingsHandler] 23 | ---- 24 | 25 | <1> Setting response status 26 | 27 | Here we're setting the content type: 28 | 29 | [source, haskell] 30 | ---- 31 | include::{sampleSrc}/chinook/Handlers.fr[tags=getJSON] 32 | ---- 33 | 34 | <1> Setting response content type 35 | 36 | NOTE: Instead setting content type headers manually you can use 37 | `chinook.util.ContentType`, e.g. `ContentType.json` 38 | 39 | There are many functions available for `Request` and `Response` 40 | abstractions, if you want to explore it please don't hesitate to 41 | explore the project's Frege docs. 42 | 43 | For the rest of HTTP verbs it doesn't change. For instance in a `POST` 44 | request it basically works the same way `GET` did. The main difference 45 | is that in `POST` calls you would like to do something with the body 46 | text sent from the client. Most of the times that body could be a 47 | `JSON`/`XML` payload. You can get that information with the `body` 48 | field from the request. 49 | 50 | Here's an example building a new `Lang` instance out of the json 51 | payload coming from the client: 52 | 53 | [source, haskell] 54 | ---- 55 | include::{sampleSrc}/chinook/Handlers.fr[tags=postJSONHandler] 56 | ---- 57 | 58 | <1> Successful response sending a `201` (created) status response 59 | <2> Failure response sending a `400` (bad request) in case the payload 60 | wasn't correct. 61 | 62 | Of course it would be nice to send back more feedback about validation ;) 63 | 64 | ==== Request 65 | 66 | A `chinook.Core.Request` is a immutable data structure containing: 67 | 68 | [source, haskell, indent=0] 69 | ---- 70 | include::{coreSrc}/chinook/Core.fr[tags=request] 71 | ---- 72 | 73 | ===== Headers 74 | 75 | Headers are of type `(String, Maybe String)` meaning `(name of the 76 | header, possible value)`. There are two utility methods to get headers 77 | from a `IO Request`. 78 | 79 | [source, haskell, indent=0] 80 | ---- 81 | include::{coreSrc}/chinook/Core.fr[tags=allheaders] 82 | ---- 83 | 84 | Returns a list of all headers from the request passed as first 85 | argument. 86 | 87 | [source, haskell, indent=0] 88 | ---- 89 | include::{coreSrc}/chinook/Core.fr[tags=header] 90 | ---- 91 | 92 | Returns a specific header. First argument is the request, then the 93 | name of the header. 94 | 95 | ===== Query Parameters 96 | 97 | A typical URL containing a query string is as follows: 98 | 99 | [source, text] 100 | ---- 101 | http://anysite.com/over/there?name=ferret 102 | ---- 103 | 104 | In order to get the name parameter we'll be using the function: 105 | 106 | [source, haskell, indent=0] 107 | ---- 108 | include::{coreSrc}/chinook/Core.fr[tags=param] 109 | ---- 110 | 111 | First parameter is the request, and then the name of the parameter 112 | (`name` in this particular example). 113 | 114 | ===== Path Parameters 115 | 116 | Bind the value of a path segment to a parameter. For instance: 117 | 118 | [source, text] 119 | ---- 120 | http://localhost:4567/john/34 121 | ---- 122 | 123 | Is mapped: 124 | 125 | [source, haskell, indent=0] 126 | ---- 127 | include::{sampleSrc}/chinook/App.fr[tags=greetingsHandler] 128 | ---- 129 | 130 | So how can we retrieve the `:name` and `:age` path parameters ? With 131 | the following functions: 132 | 133 | [source, haskell, indent=0] 134 | ---- 135 | include::{coreSrc}/chinook/Core.fr[tags=allpaths] 136 | ---- 137 | 138 | This function receives the request as argument and it will return 139 | a list of type `(String, String)`. 140 | 141 | NOTE: Why `(String, String)` and not `(String, Maybe String)` ? Well 142 | if it didn't have any value it couldn't match the path. So it's safe 143 | to assume everytime the URI is hit you will get a value. 144 | 145 | [source, haskell, indent=0] 146 | ---- 147 | include::{coreSrc}/chinook/Core.fr[tags=path] 148 | ---- 149 | 150 | `path` should be use to get a specific path parameter. It receives the 151 | request and the name of the path parameter, and it will return a 152 | string representation of that parameter. 153 | 154 | This way you could be able to use it like the following: 155 | 156 | [source, haskell, indent=0] 157 | ---- 158 | include::{sampleSrc}/chinook/Handlers.fr[tags=putHandler] 159 | ---- 160 | 161 | ===== Body 162 | 163 | Specially when trying to save or update data you will need to access 164 | the request `body`. 165 | 166 | [source, haskell, indent=0] 167 | ---- 168 | include::{sampleSrc}/chinook/Handlers.fr[tags=postJSONHandler] 169 | ---- 170 | 171 | ==== Response 172 | 173 | `chinook.Core.Response` is just a data type: 174 | 175 | [source, haskell, indent=0] 176 | ---- 177 | include::{coreSrc}/chinook/Core.fr[tags=response] 178 | ---- 179 | 180 | Unlike other web frameworks, you won't be mutating the response via 181 | functions modifiying internal fields. `Chinook` forces you to create 182 | an immutable structure. 183 | 184 | Well, but, does it means I should be building from the scratch a 185 | `Response` every time ? Yes but `Chinook` (well `Frege` actually) will 186 | help you in this task. `Chinook` has a default `Response` value 187 | available through the constant `chinook.Core.response`. This 188 | value looks like this: 189 | 190 | [source, haskell, indent=0] 191 | ---- 192 | include::{coreSrc}/chinook/Core.fr[tags=responseconstant] 193 | ---- 194 | 195 | So e.g next time you wanted to return a message with no headers and 196 | returning a 200 http status you don't have to set everything, you 197 | can take advantage of `Frege's value update` and write something 198 | like this: 199 | 200 | [source, haskell, indent=0] 201 | ---- 202 | include::{sampleSrc}/chinook/Handlers.fr[tags=helloWorldHandler] 203 | ---- 204 | 205 | A `Response` may contain: 206 | 207 | ===== Headers 208 | 209 | `(String, Maybe String)` 210 | 211 | When setting content type, or setting browser cache...etc we should be using 212 | http response headers. A header is of type `(String, Maybe String)` where 213 | the first tuple argument is the **name** of the header and the second part 214 | of the tuple is the **possible value** of the header. 215 | 216 | ===== Output 217 | 218 | `Maybe String` 219 | 220 | If you would like to respond back with a message, you can set the output field 221 | with a `Maybe String`. That string could be anything: text, json... 222 | 223 | ===== Status 224 | 225 | `Int` 226 | 227 | HTTP statuses are used often when doing Rest. For instance if you 228 | would like to create a resource, you would send a message to a 229 | resource using a POST endpoint and if it succeed it will return a 201 230 | code meaning `resource created`. A status code is of type `Int`. 231 | 232 | ==== Interceptors 233 | 234 | Sometimes you may need to check something for every request on a given 235 | path. An example could be a security interceptor, or just an audit 236 | trace. 237 | 238 | For that purpose Chinook has two types of routes that don't represent 239 | a rest action but a handler capable of intercepting a given request, 240 | or set of requests and do something about it. Those routes are 241 | `Before` and `After` and as their name implies, one can be used to 242 | intercept before the request has been processed and the latter once 243 | the request has been processed. 244 | 245 | WARNING: `Chinook` interceptors are not `filters`. They can only 246 | access to the request and modify only the response. 247 | 248 | In the sample application, there's a silly security interceptor that 249 | may help as a starting point. 250 | 251 | [source, haskell, indent=0] 252 | .Mapping configuration 253 | ---- 254 | include::{sampleSrc}/chinook/App.fr[tags=securityInterceptor] 255 | ---- 256 | 257 | All requests found under `/hi` path will be intercepted by the 258 | `securityHandler` 259 | 260 | [source, haskell, indent=0] 261 | .Handler implementation 262 | ---- 263 | include::{sampleSrc}/chinook/Handlers.fr[tags=interceptorHandler] 264 | ---- 265 | 266 | In an interceptor in you would like to stop the process and return 267 | inmediately to the user, you should return a **halting `Response`** 268 | with its `halting` property set to true. There's a shortcut which is 269 | to make use of the `haltingResponse` value. If you would like to allow 270 | further execution just return a `Response` value making sure its 271 | halting property is set to false (which is the default value). 272 | 273 | NOTE: There are two alias for a normal response and a halting 274 | response: `response` and `haltingResponse`. 275 | -------------------------------------------------------------------------------- /chinook-docs/src/docs/asciidoc/chapter/http_templating.ad: -------------------------------------------------------------------------------- 1 | === Templating 2 | 3 | `Chinook` doesn't have any templating solution `by default`. 4 | 5 | TIP: However `chinook-sample` application is using 6 | http://januslynd.github.io/diablo/[`Diablo`]. Diablo is a small 7 | templating engine abstraction. It tries to expose different templating 8 | engines with a unified api. 9 | 10 | In the `chinook-sample` application `Diablo` is used to render html 11 | using Groovy templates. These templates are located in the classpath, 12 | precisely at `src/main/resources`. In order to be able to use `Diablo` 13 | first you need some import statements. 14 | 15 | [source, haskell] 16 | ---- 17 | include::{sampleSrc}/chinook/Handlers.fr[tags=diablo-imports, indent=0] 18 | ---- 19 | 20 | Then a helper function is created to execute any template available in 21 | the classpath (See `Diablo` docs for more options): 22 | 23 | [source, haskell] 24 | ---- 25 | include::{sampleSrc}/chinook/Handlers.fr[tags=diabloTemplating, indent=0] 26 | ---- 27 | 28 | Finally we use the helper function in our handlers: 29 | 30 | [source, haskell] 31 | ---- 32 | include::{sampleSrc}/chinook/Handlers.fr[tags=getFormHandler, indent=0] 33 | ---- 34 | -------------------------------------------------------------------------------- /chinook-docs/src/docs/asciidoc/chapter/versions.adoc: -------------------------------------------------------------------------------- 1 | == Versions 2 | 3 | This is the compatibility version table of the current Chinook release: 4 | 5 | .Compatibility table 6 | |=== 7 | 8 | |name|version 9 | 10 | |chinook 11 | |{chinook-core} 12 | 13 | |spark-java 14 | |{spark-java} 15 | 16 | |frege-core 17 | |{frege-core} 18 | 19 | |frege-repl 20 | |{frege-repl} 21 | 22 | |jdk 23 | |{target-jdk}+ 24 | |=== 25 | 26 | IMPORTANT: Because Frege is evolving at fast pace, at least at the 27 | moment, Chinook is trying to follow latest versions of Frege. That's 28 | why I thought it would be important to have a chapter where users 29 | would see a compatibility table of the current release. -------------------------------------------------------------------------------- /chinook-docs/src/docs/asciidoc/index.ad: -------------------------------------------------------------------------------- 1 | = Chinook 2 | :revnumber: {chinook-core} 3 | :numbered: 4 | :imagesDir: images/ 5 | :baseDir: ../../../../.. 6 | :stem: 7 | 8 | :coreBase: {baseDir}/chinook-core 9 | :sampleBase: {baseDir}/chinook-sample 10 | 11 | :coreSrc: {coreBase}/src/main/frege 12 | :sampleSrc: {sampleBase}/src/main/frege 13 | 14 | "Chinook winds /ʃɪˈnʊk/, or simply chinooks, are foehn winds in the 15 | interior West of North America, where the Canadian Prairies and Great 16 | Plains meet various mountain ranges, although the original usage is in 17 | reference to wet, warm coastal winds in the Pacific Northwest." 18 | -- Wikipedia 19 | 20 | **Chinook** is a `Frege` (http://frege-lang.org) port of 21 | `SparkJava` (http://sparkjava.com) for creating web applications with 22 | minimum effort. 23 | 24 | include::chapter/getting.ad[] 25 | 26 | include::chapter/versions.adoc[] 27 | 28 | include::chapter/http.ad[] 29 | 30 | include::chapter/development.ad[] 31 | -------------------------------------------------------------------------------- /chinook-sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: "org.frege-lang" 3 | apply plugin: 'application' // <1> 4 | 5 | dependencies { 6 | compile project (':chinook-core') 7 | 8 | compile 'com.github.fregelab:diablo-groovy:0.1.1' 9 | } 10 | 11 | mainClassName = "chinook.App" // <2> 12 | -------------------------------------------------------------------------------- /chinook-sample/src/main/frege/chinook/App.fr: -------------------------------------------------------------------------------- 1 | module chinook.App where 2 | 3 | import chinook.Router 4 | import chinook.Chinook 5 | import chinook.Handlers 6 | 7 | -- tag::routes[] 8 | mappings = [ 9 | -- tag::securityInterceptor[] 10 | Before "/hi" securityHandler, 11 | -- end::securityInterceptor[] 12 | -- tag::helloWorldHandler[] 13 | Get "/hi" helloWorldHandler, 14 | -- end::helloWorldHandler[] 15 | -- tag::greetingsHandler[] 16 | Get "/hi/:name/:age" greetingsHandler, 17 | -- end::greetingsHandler[] 18 | "/json" + [ 19 | Get "/get" getJSONHandler, 20 | Get "/form" getFormHandler, 21 | Get "/sender" getJsonSenderHandler, 22 | Post "/post" postJSONHandler, 23 | Post "/html" postFormHandler 24 | ], 25 | Get "/bye" goodbyeHandler, 26 | Delete "/deleteme/:id" deleteHandler, 27 | Put "/updateme/:id" putHandler 28 | ] 29 | -- end::routes[] 30 | 31 | -- tag::app[] 32 | main = Chinook.run config routes 33 | where config = [port 8080, staticFiles "/public"] 34 | routes = mappings 35 | -- end::app[] 36 | -------------------------------------------------------------------------------- /chinook-sample/src/main/frege/chinook/Data.fr: -------------------------------------------------------------------------------- 1 | module chinook.Data where 2 | import Data.JSON 3 | import Data.List 4 | 5 | data Lang = Lang { code :: String, 6 | desc :: String } 7 | 8 | derive Show Lang 9 | 10 | instance ToJSON Lang where 11 | toJSON Lang {code, desc} = 12 | Struct [ 13 | assoc "code" code, 14 | assoc "desc" desc 15 | ] 16 | 17 | instance FromJSON Lang where 18 | fromJSON v = case v of 19 | Struct s -> do 20 | code <- field "code" s 21 | desc <- field "desc" s 22 | return Lang {code, desc} 23 | _ -> fail ("expected {\"code\" : ..., \"desc\" : ...}, found " ++ show v) 24 | -------------------------------------------------------------------------------- /chinook-sample/src/main/frege/chinook/Handlers.fr: -------------------------------------------------------------------------------- 1 | module chinook.Handlers where 2 | 3 | import Data.JSON 4 | 5 | import chinook.Data (Lang) 6 | import chinook.util.ContentType 7 | --tag::basic-imports[] 8 | import chinook.Core (Request, Response, response, haltingResponse) 9 | --end::basic-imports[] 10 | 11 | -- tag::diablo-imports[] 12 | import diablo.Diablo (fromPath) 13 | import diablo.Groovy (GroovyEngine) 14 | -- end::diablo-imports[] 15 | 16 | -- tag::helloWorldHandler[] 17 | helloWorldHandler :: IO Request -> IO Response 18 | helloWorldHandler req = do 19 | return $ response.{ output = Just "Hello World from Chinook :-)" } 20 | -- end::helloWorldHandler[] 21 | 22 | -- tag::interceptorHandler[] 23 | securityHandler :: IO Request -> IO Response 24 | securityHandler req = do 25 | token <- req.param "token" 26 | return $ case token of 27 | Just _ -> response 28 | Nothing -> haltingResponse.{ status = 401, 29 | output = Just "Please add a token to your query params '?token='" } 30 | -- end::interceptorHandler[] 31 | 32 | goodbyeHandler :: IO Request -> IO Response 33 | goodbyeHandler req = do 34 | return $ response.{ output = Just "Bye bye" } 35 | 36 | -- tag::greetingsHandler[] 37 | greetingsHandler :: IO Request -> IO Response 38 | greetingsHandler req = do 39 | name <- req.path ":name" 40 | age <- req.path ":age" 41 | return $ response.{ status = 200, -- <1> 42 | output = createGreetings name age } 43 | -- end::greetingsHandler[] 44 | 45 | createGreetings :: Maybe String -> Maybe String -> Maybe String 46 | createGreetings name age = fmap (concat) $ sequence [greeting, name, question, age, mark] 47 | where greeting = Just "Hello " 48 | question = Just ". Are you " 49 | mark = Just "?" 50 | 51 | -- tag::getJSON[] 52 | getJSONHandler :: IO Request -> IO Response 53 | getJSONHandler req = do 54 | code <- req.param "code" 55 | desc <- req.param "desc" 56 | return $ response.{ status = 200, 57 | output = getLangAsJSON code desc, 58 | headers = [("Content-Type", Just "application/json")] } -- <1> 59 | -- end::getJSON[] 60 | 61 | getLangAsJSON :: Maybe String -> Maybe String -> Maybe String 62 | getLangAsJSON pCode pDesc = do 63 | code <- pCode 64 | desc <- pDesc 65 | return $ (show . toJSON) $ Lang { code = code, desc = desc } 66 | 67 | -- tag::getFormHandler[] 68 | getFormHandler :: IO Request -> IO Response 69 | getFormHandler req = do 70 | html <- render "chinook/form.gtpl" 71 | return response.{ status = 200, 72 | output = html, 73 | headers = [ContentType.html] 74 | } 75 | -- end::getFormHandler[] 76 | 77 | -- tag::getJsonSenderHandler[] 78 | getJsonSenderHandler :: IO Request -> IO Response 79 | getJsonSenderHandler req = do 80 | html <- render "chinook/sender.gtpl" 81 | return response.{ status = 200, 82 | output = html, 83 | headers = [ContentType.html] 84 | } 85 | -- end::getJsonSenderHandler[] 86 | 87 | -- tag::diabloTemplating[] 88 | render :: String -> IO (Maybe String) 89 | render tplName = do 90 | groovy <- GroovyEngine.new () 91 | Just <$> fromPath groovy tplName [] 92 | -- end::diabloTemplating[] 93 | 94 | -- tag::postJSONHandler[] 95 | postJSONHandler :: IO Request -> IO Response 96 | postJSONHandler req = do 97 | body <- req.body 98 | return $ case (processJSON body) of 99 | Just Lang { code, desc } -> createdResponse 100 | Nothing -> badRequest 101 | 102 | createdResponse :: Response 103 | createdResponse = response.{ status = 201 , -- <1> 104 | output = Just "Created", 105 | headers = [ContentType.json] } 106 | 107 | badRequest :: Response 108 | badRequest = response.{ status = 400 , -- <2> 109 | output = Just "Bad request", 110 | headers = [ContentType.json] } 111 | 112 | -- end::postJSONHandler[] 113 | 114 | 115 | -- tag::postFormHandler[] 116 | postFormHandler :: IO Request -> IO Response 117 | postFormHandler req = do 118 | code <- req.param "code" 119 | desc <- req.param "desc" 120 | return $ case (code, desc) of 121 | (Just code, Just desc) -> langCreatedResponse code desc 122 | _ -> badRequest 123 | 124 | 125 | langCreatedResponse :: String -> String -> Response 126 | langCreatedResponse code desc = response.{ status = 201 , -- <1> 127 | output = Just ((show . toJSON) $ Lang { code, desc }), 128 | headers = [ContentType.json] } 129 | -- end::postFormHandler[] 130 | 131 | -- tag::processJSON[] 132 | processJSON :: Maybe String -> Maybe Lang 133 | processJSON body = do 134 | text <- body 135 | json <- parseJSON text 136 | case json of 137 | Lang { code, desc } -> Just json 138 | _ -> Nothing 139 | -- end::processJSON[] 140 | 141 | -- tag::deleteHandler[] 142 | deleteHandler :: IO Request -> IO Response 143 | deleteHandler req = do 144 | id <- req.path ":id" 145 | return $ response.{ status = 204 } 146 | -- end::deleteHandler[] 147 | 148 | -- tag::putHandler[] 149 | putHandler :: IO Request -> IO Response 150 | putHandler req = do 151 | id <- req.path ":id" 152 | return $ response.{ status = 202 } 153 | -- end::putHandler[] 154 | -------------------------------------------------------------------------------- /chinook-sample/src/main/resources/chinook/form.gtpl: -------------------------------------------------------------------------------- 1 | html { 2 | head(title: "Html Input") { 3 | link(rel: "stylesheet", type: "text/css", href:"/css/style.css") 4 | } 5 | body { 6 | form(action: '/json/html', method: 'POST') { 7 | label("Code:") 8 | input(type:'text', name: 'code') 9 | label("Description") 10 | input(type:'text', name: 'desc') 11 | input(type: 'submit') 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chinook-sample/src/main/resources/chinook/sender.gtpl: -------------------------------------------------------------------------------- 1 | html { 2 | head(title: "Html Input") { 3 | link(rel: "stylesheet", type: "text/css", href:"/css/style.css") 4 | } 5 | script { 6 | yieldUnescaped """ 7 | function sendIt() { 8 | var xhttp = new XMLHttpRequest(); 9 | xhttp.open("POST", "/json/post", false); 10 | xhttp.send('{"code": "key","desc":"value"}'); 11 | document.getElementById("status").innerHTML = xhttp.status; 12 | document.getElementById("result").innerHTML = xhttp.responseText; 13 | } 14 | """ 15 | } 16 | body { 17 | p(id: 'status') 18 | p(id: 'result') 19 | input(type: 'button', onclick:'sendIt();', value:'Send') 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chinook-sample/src/main/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | color: #333333; 4 | font-size: 16px; 5 | } 6 | 7 | input[type=text] { 8 | display: block; 9 | margin-top: 5px; 10 | margin-bottom: 10px; 11 | background-color: #EBEBEB; 12 | } 13 | 14 | input[type=button] { 15 | border: 1px; 16 | padding: 10px; 17 | } 18 | 19 | input[type=submit] { 20 | border: 1px; 21 | padding: 10px; 22 | } 23 | 24 | label { 25 | font-weight: bold; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | releaseVersion=0.2.1 2 | releaseGroup=com.github.fregelab 3 | releaseDescription=Chinook is a Frege (http://frege-lang.org) port of SparkJava (http://sparkjava.com) for creating web applications with minimum effort. 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fregelab/chinook/8e4ea68f6bc27fb3cde8cb357187d8a541aa96d1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 21 23:56:15 CEST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'chinook-core' 2 | include 'chinook-sample' 3 | include 'chinook-docs' 4 | --------------------------------------------------------------------------------