├── .circleci └── config.yml ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── COPYRIGHT.txt ├── LICENSE ├── README.md ├── config └── logback.xml ├── deps.edn ├── dev ├── edn_fern │ └── convert.clj └── tools.clj ├── docs ├── action_literals.md ├── adding_vase.md ├── descriptor_facts_schema.graffle ├── descriptor_facts_schema.png ├── design.md ├── migrating_edn_to_fern.md ├── vase_with_fern.md └── your_first_api.md ├── project.clj ├── samples ├── omnext-todo │ ├── .gitignore │ ├── Capstanfile │ ├── Dockerfile │ ├── README.md │ ├── boot.properties │ ├── build.boot │ ├── config │ │ └── logback.xml │ ├── project.clj │ ├── resources │ │ └── omnext-todo_service.edn │ ├── src │ │ └── omnext_todo │ │ │ ├── demand.clj │ │ │ ├── server.clj │ │ │ └── service.clj │ └── test │ │ └── omnext_todo │ │ ├── service_test.clj │ │ └── test_helper.clj ├── petstore │ ├── README.md │ ├── deps.edn │ ├── petstore.fern │ └── resources │ │ ├── logback.xml │ │ └── public │ │ ├── css │ │ ├── print.css │ │ ├── reset.css │ │ ├── screen.css │ │ ├── style.css │ │ └── typography.css │ │ ├── fonts │ │ ├── DroidSans-Bold.ttf │ │ └── DroidSans.ttf │ │ ├── images │ │ ├── collapse.gif │ │ ├── expand.gif │ │ ├── explorer_icons.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── logo_small.png │ │ ├── pet_store_api.png │ │ ├── throbber.gif │ │ └── wordnik_api.png │ │ ├── index.html │ │ ├── js │ │ ├── backbone-min.js │ │ ├── handlebars-2.0.0.js │ │ ├── highlight.9.1.0.pack.js │ │ ├── highlight.9.1.0.pack_extended.js │ │ ├── jquery-1.8.0.min.js │ │ ├── jquery.ba-bbq.min.js │ │ ├── jquery.slideto.min.js │ │ ├── jquery.wiggle.min.js │ │ ├── js-yaml.min.js │ │ ├── jsoneditor.min.js │ │ ├── lodash.min.js │ │ ├── marked.js │ │ ├── object-assign-pollyfill.js │ │ └── swagger-oauth.js │ │ ├── lang │ │ ├── en.js │ │ ├── es.js │ │ ├── fr.js │ │ ├── geo.js │ │ ├── it.js │ │ ├── ja.js │ │ ├── pl.js │ │ ├── pt.js │ │ ├── ru.js │ │ ├── tr.js │ │ ├── translator.js │ │ └── zh-cn.js │ │ ├── o2c.html │ │ ├── swagger-ui.js │ │ ├── swagger-ui.min.js │ │ └── v1 │ │ └── petstore-simple.json ├── vase-component │ ├── README.md │ ├── config │ │ └── logback.xml │ ├── dev │ │ ├── dev.clj │ │ └── user.clj │ ├── project.clj │ ├── resources │ │ └── petstore-simple.edn │ └── src │ │ └── vase_component │ │ ├── api.clj │ │ ├── endpoint.clj │ │ ├── main.clj │ │ └── system.clj └── vasebi │ ├── .gitignore │ ├── Capstanfile │ ├── Dockerfile │ ├── README.md │ ├── boot.properties │ ├── build.boot │ ├── config │ └── logback.xml │ ├── project.clj │ ├── resources │ ├── public │ │ ├── dist │ │ │ ├── gchart_renderers.js │ │ │ ├── gchart_renderers.min.js │ │ │ ├── pivot.css │ │ │ ├── pivot.js │ │ │ └── pivot.min.js │ │ └── index.html │ └── vasebi_service.edn │ ├── src │ └── vasebi │ │ ├── server.clj │ │ └── service.clj │ └── test │ └── vasebi │ ├── service_test.clj │ └── test_helper.clj ├── script └── pre-release-check.sh ├── src ├── com │ └── cognitect │ │ ├── vase.clj │ │ └── vase │ │ ├── actions.clj │ │ ├── api.clj │ │ ├── datomic.clj │ │ ├── edn.clj │ │ ├── fern.clj │ │ ├── interceptor.clj │ │ ├── literals.clj │ │ ├── main.clj │ │ ├── response.clj │ │ ├── routes.clj │ │ ├── spec.clj │ │ ├── try.clj │ │ └── util.clj └── data_readers.clj ├── template ├── .gitignore ├── README.md ├── project.clj ├── src │ └── leiningen │ │ └── new │ │ ├── vase.clj │ │ └── vase │ │ ├── .gitignore │ │ ├── Capstanfile │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── boot.properties │ │ ├── build.boot │ │ ├── logback.xml │ │ ├── project.clj │ │ ├── service.clj │ │ ├── service_test.clj │ │ ├── vase_service.edn │ │ └── vase_service.fern └── test │ └── com │ └── cognitect │ └── vase │ └── new_server_integration_test.clj └── test ├── com └── cognitect │ └── vase │ ├── actions_test.clj │ ├── conform_test.clj │ ├── datomic_cloud_test.clj │ ├── descriptor_test.clj │ ├── interceptor_test.clj │ ├── literals_test.clj │ ├── query_test.clj │ ├── redirect_test.clj │ ├── respond_test.clj │ ├── service_route_table.clj │ ├── service_test.clj │ ├── spec_test.clj │ ├── test_db_helper.clj │ ├── test_helper.clj │ ├── transaction_test.clj │ └── validate_test.clj └── resources ├── sample_descriptor.edn ├── sample_payload.edn ├── small_descriptor.edn ├── test_descriptor.edn └── test_descriptor.fern /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: circleci/clojure:lein-2.7.1 7 | environment: 8 | LEIN_ROOT: nbd 9 | JVM_OPTS: -Xmx3200m 10 | steps: 11 | - checkout 12 | - restore_cache: 13 | key: vase-{{ checksum "project.clj" }} 14 | - run: lein install 15 | - save_cache: 16 | paths: 17 | - ~/.m2 18 | key: vase-{{ checksum "project.clj" }} 19 | - run: cd template && lein test 20 | - run: lein do test, uberjar 21 | - store_artifacts: 22 | path: target/uberjar/vase.jar 23 | destination: uberjar 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | ## Expected Behavior 4 | 5 | ## Actual Behavior 6 | 7 | ## Steps to reproduce 8 | 9 | # Environment 10 | 11 | ## Operating System (including version). 12 | 13 | ## Your current Leiningen/Boot/Maven version (`lein --version`) 14 | 15 | ## Pedestal and Vase version 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project related 2 | .rebel_readline_history 3 | my-new-payload.edn 4 | data/ 5 | /datomic/ 6 | /resources/public/ratings/ 7 | 8 | # Java related 9 | pom.xml 10 | *jar 11 | *.class 12 | 13 | # Leiningen 14 | classes/ 15 | lib/ 16 | native/ 17 | checkouts/ 18 | target/ 19 | .lein-* 20 | repl-port 21 | .nrepl-port 22 | .repl 23 | 24 | # Datomic 25 | *.db 26 | 27 | # Python 28 | *.pyc 29 | *.pyo 30 | /__pycache__/ 31 | 32 | # Ruby 33 | Gemfile.lock 34 | 35 | # Temp Files 36 | *.orig 37 | *~ 38 | .*.swp 39 | .*.swo 40 | *.tmp 41 | *.bak 42 | 43 | # Editors (IntelliJ / Eclipse) 44 | */.idea 45 | .idea 46 | */.classpath 47 | .classpath 48 | */.project 49 | .project 50 | */.settings 51 | .settings 52 | *.iml 53 | 54 | # OS X 55 | .DS_Store 56 | 57 | # Logging 58 | *.log 59 | 60 | # Docs 61 | autodoc/ 62 | 63 | # Builds 64 | out/ 65 | 66 | .cpcache 67 | 68 | .kein 69 | -------------------------------------------------------------------------------- /COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | All data and code in this repository is copyright © 2014-2017 Cognitect, Inc. 2 | -------------------------------------------------------------------------------- /config/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 24 | 25 | 26 | 27 | 28 | logs/vase-%d{yyyy-MM-dd}.%i.log 29 | 31 | 32 | 64 MB 33 | 34 | 35 | 36 | 37 | true 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | %-5level %logger{36} - %msg%n 46 | 47 | 48 | 49 | INFO 50 | 51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :deps {com.datomic/datomic-free {:mvn/version "0.9.5697"} 3 | com.datomic/client-cloud {:mvn/version "0.8.63"} 4 | 5 | io.rkn/conformity {:mvn/version "0.5.1"} 6 | 7 | ;; Pedestal 8 | io.pedestal/pedestal.service {:mvn/version "0.5.4"} 9 | io.pedestal/pedestal.jetty {:mvn/version "0.5.4"} 10 | 11 | com.cognitect/fern {:mvn/version "0.1.5"}} 12 | :aliases {:repl {:extra-paths ["test" "test/resources"]}}} 13 | -------------------------------------------------------------------------------- /dev/tools.clj: -------------------------------------------------------------------------------- 1 | (ns tools 2 | (:require [com.cognitect.vase.fern :as fern] 3 | [com.cognitect.vase :as vase] 4 | [fern.easy :as fe] 5 | [fern.printer :as fp] 6 | [datomic.api :as d] 7 | [edn-fern.convert :as c] 8 | [clojure.java.io :as io])) 9 | 10 | (defn edn-spec->fern-spec 11 | [m] 12 | (assert (map? m) "edn-spec->fern-spec works on a map value") 13 | (reduce-kv c/convert {} m)) 14 | 15 | (defn edn-spec->fern-spec-str 16 | [m & {:as opts}] 17 | (fp/pprint-str 18 | (edn-spec->fern-spec m) 19 | (merge 20 | {:print-handlers fe/underef-handlers 21 | :map-delimiter nil 22 | :width 100} 23 | opts))) 24 | 25 | (defn edn-file->fern-file 26 | [e f] 27 | (spit (io/file f) 28 | (edn-spec->fern-spec-str (read-string (slurp e))))) 29 | -------------------------------------------------------------------------------- /docs/descriptor_facts_schema.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/docs/descriptor_facts_schema.graffle -------------------------------------------------------------------------------- /docs/descriptor_facts_schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/docs/descriptor_facts_schema.png -------------------------------------------------------------------------------- /docs/migrating_edn_to_fern.md: -------------------------------------------------------------------------------- 1 | # Migrating EDN to Fern 2 | 3 | EDN is just data. Fern is just data. There's no reason a human should 4 | have to do mechanical conversions. 5 | 6 | The `dev` directory includes a `tools.clj` file with a conversion 7 | utility. 8 | 9 | To load up your EDN file and run it through the converter: 10 | 11 | ```clojure 12 | (use 'tools) 13 | (in-ns 'tools) 14 | (edn-file->fern-file "old-file.edn" "new-file.fern") 15 | ``` 16 | 17 | Feel free to dig into tools.clj to see how you can use the conversion 18 | machinery. 19 | 20 | # After Conversion 21 | 22 | Mechanical translation can take care of getting the meaning across, but it can't format things in your preferred style. You'll want to look at two things in your Fern output: 23 | 24 | 1. Order of definitions 25 | 2. Linebreaks and alignments 26 | 27 | ## Order of Definitions 28 | 29 | The Fern output will come out in roughly the order that the EDN map 30 | was enumerated. That bears little resemblance to either the input 31 | format or any sensible reading of the definitions. You'll want to 32 | spend some time making the new Fern file readable to other 33 | human. Depending on your taste, you might move the `vase/service` 34 | definition to the top, then chain down the page. Or vice versa, with 35 | the biggest aggregates toward the bottom. 36 | 37 | Either way, this is something you'll need to do by hand. 38 | 39 | ## Linebreaks and Alignment 40 | 41 | Sadly, the pretty-printer has very different opinions about where to 42 | put line breaks. You'll probably want to adjust the spacing yourself. 43 | 44 | For example, running the conversion on 45 | `test/resources/test_descriptor.edn` gives this for a part of the 46 | output: 47 | 48 | ```clojure 49 | example/user-schema-0 (fern/lit 50 | vase.datomic/attributes 51 | [:user/userId :one :long :identity "A Users unique identifier"] 52 | [:user/userEmail :one :string :unique "The users email"] 53 | [:user/userBio :one :string :index :fulltext "A short blurb about the user"]) 54 | example/v1 (fern/lit 55 | vase/api 56 | {:expose-api-at "/example/v1/api" 57 | :on-request [@connection] 58 | :on-startup [@connection @example/user-schema @example/loan-schema] 59 | :path "/example/v1" 60 | :routes @example.v1/routes}) 61 | ``` 62 | 63 | I find that hard to read, thanks to the irregular indentation. I 64 | prefer to put `fern/lit` and the literal type on the same line, and 65 | line up all the right-hand sides. Like this: 66 | 67 | ```clojure 68 | example/user-schema-0 (fern/lit vase.datomic/attributes 69 | [:user/userId :one :long :identity "A Users unique identifier"] 70 | [:user/userEmail :one :string :unique "The users email"] 71 | [:user/userBio :one :string :index :fulltext "A short blurb about the user"]) 72 | example/v1 (fern/lit vase/api 73 | {:expose-api-at "/example/v1/api" 74 | :on-request [@connection] 75 | :on-startup [@connection @example/user-schema @example/loan-schema] 76 | :path "/example/v1" 77 | :routes @example.v1/routes}) 78 | ``` 79 | 80 | (For Emacs users, "C-c C" in Clojure mode will do all the alignment 81 | for you. But you still have to remove line breaks before it works 82 | nicely.) 83 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject com.cognitect/pedestal.vase "0.9.4-SNAPSHOT" 2 | :description "Vase: Pedestal API Container" 3 | :url "https://github.com/cognitect-labs/vase" 4 | :dependencies [;; Platform 5 | [org.clojure/clojure "1.10.1"] 6 | 7 | ;; Datomic 8 | [com.datomic/datomic-free "0.9.5697" :exclusions [[org.slf4j/slf4j-api] 9 | [org.slf4j/slf4j-nop] 10 | [org.eclipse.jetty/jetty-util] 11 | [org.eclipse.jetty/jetty-client]]] 12 | [com.datomic/client-cloud "0.8.78" :exclusions [commons-logging]] 13 | [io.rkn/conformity "0.5.1" :exclusions [com.datomic/datomic-free]] 14 | 15 | ;; Pedestal 16 | [io.pedestal/pedestal.service "0.5.7"] 17 | [io.pedestal/pedestal.jetty "0.5.7"] 18 | 19 | ;; Pin Jetty versions to avoid conflict between Datomic and Pedestal 20 | [org.eclipse.jetty/jetty-client "9.4.18.v20190429"] 21 | 22 | ;; Pin core.async to avoid conflict between Datomic and Pedestal 23 | [org.clojure/core.async "0.4.490"] 24 | 25 | ;; Pin joda-time to avoid conflict between Datomic and Pedestal 26 | [joda-time "2.9.9"] 27 | 28 | ;; Configuration 29 | [com.cognitect/fern "0.1.5"] 30 | 31 | ;; Replace Java EE module for JDK 11 32 | [javax.xml.bind/jaxb-api "2.3.0"] 33 | 34 | ;; Cleanup 35 | [commons-codec "1.12"] 36 | [cheshire "5.8.1"]] 37 | 38 | :main ^:skip-aot com.cognitect.vase.main 39 | :pedantic? :warn 40 | :uberjar-name "vase-standalone.jar" 41 | :plugins [] 42 | :jvm-opts ~(let [version (System/getProperty "java.version") 43 | [major _ _] (clojure.string/split version #"\.")] 44 | (if (<= 9 (java.lang.Integer/parseInt major) 10) 45 | ["--add-modules" "java.xml.bind"] 46 | [])) 47 | :test-selectors {:default (complement :integration) 48 | :cloud :integration 49 | :all (constantly true)} 50 | 51 | :profiles {:srepl {:jvm-opts ^:replace ["-XX:+UseG1GC" 52 | "-Dclojure.server.repl={:port 5555 :accept clojure.core.server/repl}"]} 53 | :dev {:aliases {"crepl" ["trampoline" "run" "-m" "clojure.main/main"] 54 | "srepl" ["with-profile" "srepl" "trampoline" "run" "-m" "clojure.main/main"]} 55 | :source-paths ["dev"] 56 | :resource-paths ["config" 57 | "resources" 58 | "test/resources"] 59 | :dependencies [[org.clojure/tools.trace "0.7.10"] 60 | [org.clojure/tools.namespace "0.3.0" :exclusions [[org.clojure/tools.reader]]] 61 | [org.clojure/tools.reader "1.3.2"] 62 | [org.clojure/test.check "0.9.0"] 63 | ;; Logging 64 | [org.slf4j/slf4j-api "1.7.26"] 65 | [ch.qos.logback/logback-classic "1.2.3" :exclusions [[org.slf4j/slf4j-api]]] 66 | [org.slf4j/jul-to-slf4j "1.7.26"] 67 | [org.slf4j/jcl-over-slf4j "1.7.26"] 68 | [org.slf4j/log4j-over-slf4j "1.7.26"]]} 69 | :test {:dependencies [[org.clojure/test.check "0.9.0"] 70 | [io.pedestal/pedestal.service-tools "0.5.7" :exclusions [[org.slf4j/log4j-over-slf4j] 71 | [org.slf4j/jul-to-slf4j] 72 | [org.slf4j/jcl-over-slf4j]]]] 73 | :resource-paths ["resources" 74 | "test/resources"]}} 75 | :min-lein-version "2.0.0") 76 | -------------------------------------------------------------------------------- /samples/omnext-todo/.gitignore: -------------------------------------------------------------------------------- 1 | # Project related 2 | 3 | # Java related 4 | pom.xml 5 | pom.xml.asc 6 | *jar 7 | *.class 8 | 9 | # Leiningen 10 | classes/ 11 | lib/ 12 | native/ 13 | checkouts/ 14 | target/ 15 | .lein-* 16 | repl-port 17 | .nrepl-port 18 | .repl 19 | 20 | # Temp Files 21 | *.orig 22 | *~ 23 | .*.swp 24 | .*.swo 25 | *.tmp 26 | *.bak 27 | 28 | # OS X 29 | .DS_Store 30 | 31 | # Logging 32 | *.log 33 | /logs/ 34 | 35 | # Builds 36 | out/ 37 | build/ 38 | 39 | -------------------------------------------------------------------------------- /samples/omnext-todo/Capstanfile: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Name of the base image. Capstan will download this automatically from 4 | # Cloudius S3 repository. 5 | # 6 | #base: cloudius/osv 7 | base: cloudius/osv-openjdk8 8 | 9 | # 10 | # The command line passed to OSv to start up the application. 11 | # 12 | #cmdline: /java.so -cp /omnext-todo/app.jar clojure.main -m omnext-todo 13 | cmdline: /java.so -jar /omnext-todo/app.jar 14 | 15 | # 16 | # The command to use to build the application. 17 | # You can use any build tool/command (make/rake/lein/boot) - this runs locally on your machine 18 | # 19 | # For Leiningen, you can use: 20 | #build: lein uberjar 21 | # For Boot, you can use: 22 | #build: boot build 23 | 24 | # 25 | # List of files that are included in the generated image. 26 | # 27 | files: 28 | /omnext-todo/app.jar: ./target/omnext-todo-0.0.1-SNAPSHOT-standalone.jar 29 | 30 | -------------------------------------------------------------------------------- /samples/omnext-todo/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8-alpine 2 | MAINTAINER Your Name 3 | 4 | ADD target/omnext-todo-0.0.1-SNAPSHOT-standalone.jar /omnext-todo/app.jar 5 | 6 | EXPOSE 8080 7 | 8 | CMD ["java", "-jar", "/omnext-todo/app.jar"] 9 | -------------------------------------------------------------------------------- /samples/omnext-todo/README.md: -------------------------------------------------------------------------------- 1 | # omnext-todo 2 | 3 | This is a simple "TODO" example using Vase. 4 | An [Om.next](https://github.com/omcljs/om/wiki/Documentation-(om.next)) demand-driven API is also integrated. 5 | 6 | 7 | ## Getting Started 8 | 9 | 1. Start the application: `lein run` 10 | 2. Go to [localhost:8080](http://localhost:8080/) to see: `Hello World!` 11 | 3. You can see [all available API endpoints](http://localhost:8080/api) 12 | and the [current list of TODOs](http://localhost:8080/api/omnext-todo/main/todos). 13 | You can [optionally filter that list](http://localhost:8080/api/omnext-todo/main/todos?selector=[:todo/title]) too. 14 | 3. Read your app's source code at `src/omnext_todo/service.clj`. Explore the docs of functions 15 | that define routes and responses. 16 | 4. See your Vase API Specification at `resources/omnext-todo_service.edn`. 17 | 5. Run your app's tests with `lein test`. Read the tests at `test/omnext_todo/service_test.clj`. 18 | 19 | ### POSTing data to the API 20 | 21 | You can POST new TODOs with curl: 22 | 23 | `curl -H "Content-Type: application/json" -X POST -d '{"payload": [{"todo/title": "Write a test for TODO example"}]}' http://localhost:8080/api/omnext-todo/main/todos` 24 | 25 | You can also see the spec/validation response if you try to transact and incomplete TODO 26 | 27 | `curl -H "Content-Type: application/json" -X POST -d '{"payload": [{}]}' http://localhost:8080/api/omnext-todo/main/todos` 28 | 29 | 30 | ## Configuration 31 | 32 | To configure logging see config/logback.xml. By default, the app logs to stdout and logs/. 33 | To learn more about configuring Logback, read its [documentation](http://logback.qos.ch/documentation.html). 34 | 35 | 36 | ## Developing your service 37 | 38 | 1. Start a new REPL: `lein repl` 39 | 2. Start your service in dev-mode: `(def dev-serv (run-dev))` 40 | 3. Connect your editor to the running REPL session. 41 | Re-evaluated code will be seen immediately in the service. 42 | 4. All changes to your Vase Service Descriptor will be loaded - no re-evaluation 43 | needed. 44 | 45 | ### [Docker](https://www.docker.com/) container support 46 | 47 | 1. Build an uberjar of your service: `lein uberjar` 48 | 2. Build a Docker image: `sudo docker build -t omnext-todo .` 49 | 3. Run your Docker image: `docker run -p 8080:8080 omnext-todo` 50 | 51 | ### [OSv](http://osv.io/) unikernel support with [Capstan](http://osv.io/capstan/) 52 | 53 | 1. Build and run your image: `capstan run -f "8080:8080"` 54 | 55 | Once the image it built, it's cached. To delete the image and build a new one: 56 | 57 | 1. `capstan rmi omnext-todo; capstan build` 58 | 59 | 60 | ## Links 61 | 62 | * [Pedestal examples](https://github.com/pedestal/samples) 63 | * [Vase examples](https://github.com/cognitect-labs/vase/tree/master/samples) 64 | 65 | 66 | -------------------------------------------------------------------------------- /samples/omnext-todo/boot.properties: -------------------------------------------------------------------------------- 1 | #http://boot-clj.com 2 | BOOT_CLOJURE_NAME=org.clojure/clojure 3 | BOOT_CLOJURE_VERSION=1.9.0-alpha13 4 | -------------------------------------------------------------------------------- /samples/omnext-todo/config/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 12 | 13 | 14 | 15 | 16 | logs/omnext-todo-%d{yyyy-MM-dd}.%i.log 17 | 18 | 64 MB 19 | 20 | 21 | 22 | false 23 | 24 | 25 | 26 | 27 | 28 | 29 | %-5level %logger{36} - %msg%n 30 | 31 | 32 | 33 | INFO 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /samples/omnext-todo/project.clj: -------------------------------------------------------------------------------- 1 | (defproject omnext-todo "0.9.4-SNAPSHOT" 2 | :description "Vase sample application, Todo list compatible with Om.next" 3 | :url "https://github.com/cognitect-labs/vase/tree/master/samples/omnext-todo" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.10.0"] 7 | [io.pedestal/pedestal.service "0.5.5"] 8 | [com.cognitect/pedestal.vase "0.9.3"] 9 | 10 | ;; Remove this line and uncomment one of the next lines to 11 | ;; use Immutant or Tomcat instead of Jetty: 12 | [io.pedestal/pedestal.jetty "0.5.5"] 13 | ;; [io.pedestal/pedestal.immutant "0.5.3"] 14 | ;; [io.pedestal/pedestal.tomcat "0.5.3"] 15 | 16 | [ch.qos.logback/logback-classic "1.2.3" :exclusions [org.slf4j/slf4j-api]] 17 | [org.slf4j/jul-to-slf4j "1.7.25"] 18 | [org.slf4j/jcl-over-slf4j "1.7.25"] 19 | [org.slf4j/log4j-over-slf4j "1.7.25"] 20 | 21 | ;; Om.next Demand-driven API 22 | ;[org.clojure/clojurescript "1.9.293"] 23 | [org.omcljs/om "1.0.0-alpha47"] 24 | 25 | ;; Deps cleanup 26 | ;; -- Om and Pedestal 27 | [com.cognitect/transit-clj "0.8.313"] 28 | ;; -- Vase and Pedestal 29 | [cheshire "5.8.1"]] 30 | :pedantic? :abort 31 | :min-lein-version "2.0.0" 32 | :resource-paths ["config", "resources"] 33 | ;; If you use HTTP/2 or ALPN, use the java-agent to pull in the correct alpn-boot dependency 34 | ;:java-agents [[org.mortbay.jetty.alpn/jetty-alpn-agent "2.0.3"]] 35 | :profiles {:dev {:aliases {"run-dev" ["trampoline" "run" "-m" "omnext-todo.server/run-dev"]} 36 | :dependencies [[io.pedestal/pedestal.service-tools "0.5.5"]]} 37 | :uberjar {:aot [omnext-todo.server]}} 38 | :main ^{:skip-aot true} omnext-todo.server) 39 | -------------------------------------------------------------------------------- /samples/omnext-todo/resources/omnext-todo_service.edn: -------------------------------------------------------------------------------- 1 | {:activated-apis [:omnext-todo/main] 2 | :datomic-uri "datomic:mem://example" 3 | :descriptor 4 | ;; Datomic Schema Norms 5 | ;; -------------------- 6 | {:vase/norms 7 | {:omnext-todo/todo-categories 8 | {:vase.norm/txes [[{:db/id #db/id[:db.part/db] 9 | :db/ident :todo.category/personal} 10 | {:db/id #db/id[:db.part/db] 11 | :db/ident :todo.category/work}]]} 12 | :omnext-todo/base-schema 13 | {:vase.norm/requires [:omnext-todo/todo-categories] 14 | :vase.norm/txes [#vase/schema-tx [[:todo/created :one :instant "A todo's creation timestamp"] 15 | [:todo/title :one :string :fulltext "A todo's title/description"] 16 | [:todo/completed :one :boolean "A todo's completion status/state"] 17 | [:todo/category :one :ref "The tag/group/category of the todo"]]]}} 18 | 19 | ;; Global Specs for the API 20 | ;; ------------------------ 21 | :vase/specs 22 | {:todo/category keyword? 23 | :todo/title (clojure.spec.alpha/and string? not-empty) 24 | :omnext-todo/todo (clojure.spec.alpha/keys :req [:todo/title] 25 | :opt [:todo/category]) 26 | :omnext-todo/todos (clojure.spec.alpha/+ :omnext-todo/todo 27 | )} 28 | 29 | ;; API Tagged Chunks/Versions 30 | ;; -------------------------- 31 | :vase/apis 32 | {:omnext-todo/main 33 | {:vase.api/routes 34 | {"/todos" {:get #vase/query {:name :omnext-todo.main/todos-list 35 | :params [[selector [:* {:todo/category [:db/ident]}]]] 36 | :edn-coerce [selector] 37 | :query [:find [(pull ?todo selector) ...] 38 | :in $ selector 39 | :where [?todo :todo/title]]} 40 | :post [#vase/validate {:name :omnext-todo.main/todos-create-validation 41 | :spec :omnext-todo/todos 42 | :request-params-path [:json-params :payload]} 43 | #vase/intercept {:name :omnext-todo.main/todos-create-shaping 44 | :enter (fn [ctx] 45 | (update-in ctx [:request :json-params :payload] 46 | (fn [tx] 47 | (mapv (fn [ent] 48 | (when ent 49 | (merge {:todo/category :todo.category/personal 50 | :todo/created (java.util.Date.)} 51 | ent))) 52 | tx))))} 53 | #vase/transact {:name :omnext-todo.main/todos-create 54 | ;; `:properties` are pulled from the `payload` parameters 55 | :properties [:db/id 56 | :todo/title 57 | :todo/created 58 | :todo/category 59 | :todo/completed]}] 60 | :delete #vase/transact {:name :omnext-todo.main/todos-delete 61 | :db-op :vase/retract-entity 62 | ;; :vase/retract-entity requires :db/id to be supplied 63 | :properties [:db/id]}} 64 | "/todos/validate" {:post #vase/validate {:name :omnext-todo.main/validate-page 65 | :spec :omnext-todo/todo}}} 66 | ;:vase.api/interceptors [] ;; Any extra interceptors to apply to this API chunk/version 67 | :vase.api/schemas [:omnext-todo/base-schema] 68 | :vase.api/forward-headers ["vaserequest-id"]}}}} 69 | -------------------------------------------------------------------------------- /samples/omnext-todo/src/omnext_todo/server.clj: -------------------------------------------------------------------------------- 1 | (ns omnext-todo.server 2 | (:gen-class) ; for -main method in uberjar 3 | (:require [io.pedestal.http :as server] 4 | [io.pedestal.http.route :as route] 5 | [com.cognitect.vase :as vase] 6 | [omnext-todo.service :as service])) 7 | 8 | (defn activate-vase 9 | ([base-routes api-root spec-paths] 10 | (activate-vase base-routes api-root spec-paths vase/load-edn-resource)) 11 | ([base-routes api-root spec-paths vase-load-fn] 12 | (let [vase-specs (mapv vase-load-fn spec-paths)] 13 | (when (seq vase-specs) 14 | (vase/ensure-schema vase-specs) 15 | (vase/specs vase-specs)) 16 | {::routes (if (empty? vase-specs) 17 | base-routes 18 | (into base-routes (vase/routes api-root vase-specs))) 19 | ::specs vase-specs}))) 20 | 21 | (defn vase-service 22 | "Optionally given a default service map and any number of string paths 23 | to Vase API Specifications, 24 | Return a Pedestal Service Map with all Vase APIs parsed, ensured, and activated." 25 | ([] 26 | (vase-service service/service)) 27 | ([service-map] 28 | (vase-service service-map vase/load-edn-resource)) 29 | ([service-map vase-load-fn] 30 | (merge {:env :prod 31 | ::server/routes (::routes (activate-vase 32 | (::service/route-set service-map) 33 | (::vase/api-root service-map) 34 | (::vase/spec-resources service-map) 35 | vase-load-fn))} 36 | service-map))) 37 | 38 | ;; This is an adapted service map, that can be started and stopped 39 | ;; From the REPL you can call server/start and server/stop on this service 40 | (defonce runnable-service (server/create-server (vase-service))) 41 | 42 | (defn run-dev 43 | "The entry-point for 'lein run-dev'" 44 | [& args] 45 | (println "\nCreating your [DEV] server...") 46 | (-> service/service ;; start with production configuration 47 | (merge {:env :dev 48 | ;; do not block thread that starts web server 49 | ::server/join? false 50 | ;; Routes can be a function that resolve routes, 51 | ;; we can use this to set the routes to be reloadable 52 | ::server/routes #(route/expand-routes 53 | (::routes (activate-vase (deref #'service/routes) 54 | (::vase/api-root service/service) 55 | (mapv (fn [res-str] 56 | (str "resources/" res-str)) 57 | (::vase/spec-resources service/service)) 58 | vase/load-edn-file))) 59 | ;; all origins are allowed in dev mode 60 | ::server/allowed-origins {:creds true :allowed-origins (constantly true)}}) 61 | ;; Wire up interceptor chains 62 | server/default-interceptors 63 | server/dev-interceptors 64 | server/create-server 65 | server/start)) 66 | 67 | (defn -main 68 | "The entry-point for 'lein run'" 69 | [& args] 70 | (println "\nCreating your server...") 71 | (server/start runnable-service)) 72 | 73 | ;; If you package the service up as a WAR, 74 | ;; some form of the following function sections is required (for io.pedestal.servlet.ClojureVarServlet). 75 | 76 | ;;(defonce servlet (atom nil)) 77 | ;; 78 | ;;(defn servlet-init 79 | ;; [_ config] 80 | ;; ;; Initialize your app here. 81 | ;; (reset! servlet (server/servlet-init service/service nil))) 82 | ;; 83 | ;;(defn servlet-service 84 | ;; [_ request response] 85 | ;; (server/servlet-service @servlet request response)) 86 | ;; 87 | ;;(defn servlet-destroy 88 | ;; [_] 89 | ;; (server/servlet-destroy @servlet) 90 | ;; (reset! servlet nil)) 91 | 92 | -------------------------------------------------------------------------------- /samples/omnext-todo/test/omnext_todo/service_test.clj: -------------------------------------------------------------------------------- 1 | (ns omnext-todo.service-test 2 | (:require [clojure.test :refer :all] 3 | [io.pedestal.test :refer :all] 4 | [io.pedestal.http :as http] 5 | [omnext-todo.test-helper :as helper] 6 | [omnext-todo.service :as service])) 7 | 8 | ;; To test your service, call `(helper/service` to get a new service instance. 9 | ;; If you need a constant service over multiple calls, use `(helper/with-service ...) 10 | ;; All generated services will have randomized, consistent in-memory Datomic DBs 11 | ;; if required by the service 12 | ;; 13 | ;; `helper` also contains shorthands for common `response-for` patterns, 14 | ;; like GET, POST, post-json, post-edn, and others 15 | 16 | (deftest home-page-test 17 | (is (= (:body (response-for (helper/service) :get "/")) 18 | "Hello World!")) 19 | (is (= (:headers (helper/GET "/")) 20 | {"Content-Type" "text/html;charset=UTF-8" 21 | "Strict-Transport-Security" "max-age=31536000; includeSubdomains" 22 | "X-Frame-Options" "DENY" 23 | "X-Content-Type-Options" "nosniff" 24 | "X-XSS-Protection" "1; mode=block" 25 | "X-Download-Options" "noopen" 26 | "X-Permitted-Cross-Domain-Policies" "none" 27 | "Content-Security-Policy" "object-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https: http:;"}))) 28 | 29 | 30 | (deftest about-page-test 31 | (helper/with-service service/service 32 | (is (.contains (:body (response-for (helper/service) :get "/about")) 33 | "Clojure 1.9")) 34 | (is (= (:headers (helper/GET "/about")) 35 | {"Content-Type" "text/html;charset=UTF-8" 36 | "Strict-Transport-Security" "max-age=31536000; includeSubdomains" 37 | "X-Frame-Options" "DENY" 38 | "X-Content-Type-Options" "nosniff" 39 | "X-XSS-Protection" "1; mode=block" 40 | "X-Download-Options" "noopen" 41 | "X-Permitted-Cross-Domain-Policies" "none" 42 | "Content-Security-Policy" "object-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https: http:;"})))) 43 | 44 | ;; Let's test the API from our descriptor 45 | (deftest todos-CRUD-test 46 | (helper/with-service service/service 47 | (let [initial-list (helper/response-data (helper/GET "/api/omnext-todo/main/todos")) 48 | expected-todo {:todo/title "Write a test for TODO example" 49 | :todo/category {:db/ident "todo.category/personal"}} 50 | tx-resp (helper/post-json "/api/omnext-todo/main/todos" {:payload [{:todo/title "Write a test for TODO example"}]}) 51 | new-list (helper/response-data (helper/GET "/api/omnext-todo/main/todos")) 52 | new-todo (first new-list)] 53 | (is (empty? initial-list)) 54 | (is (= 1 (count new-list))) 55 | (is (:db/id new-todo)) 56 | (is (= expected-todo 57 | (dissoc new-todo :db/id :todo/created)))))) 58 | 59 | 60 | -------------------------------------------------------------------------------- /samples/omnext-todo/test/omnext_todo/test_helper.clj: -------------------------------------------------------------------------------- 1 | (ns omnext-todo.test-helper 2 | (:require [io.pedestal.test :refer [response-for]] 3 | [io.pedestal.http :as http] 4 | [io.pedestal.interceptor.chain :as chain] 5 | [io.pedestal.log :as log] 6 | [com.cognitect.vase :as vase] 7 | [com.cognitect.vase.util :as util] 8 | [com.cognitect.vase.datomic :as datomic] 9 | [omnext-todo.server :as server] 10 | [omnext-todo.service])) 11 | 12 | (def write-edn pr-str) 13 | 14 | (defn new-service 15 | "This generates a new testable service for use with io.pedestal.test/response-for. 16 | It will also create a new Datomic DB (randomized URI)." 17 | ([] (new-service omnext-todo.service/service)) 18 | ([service-map] 19 | (let [db-table (volatile! {}) 20 | vase-service-map (server/vase-service 21 | service-map 22 | (fn [spec-path] 23 | (let [spec (vase/load-edn-resource spec-path) 24 | prod-db-uri (:datomic-uri spec) 25 | new-db-uri (when-let [db-uri (and prod-db-uri (get @db-table prod-db-uri (datomic/new-db-uri)))] 26 | (vswap! db-table assoc prod-db-uri db-uri) 27 | db-uri)] 28 | (if prod-db-uri 29 | (assoc spec :datomic-uri new-db-uri) 30 | spec))))] 31 | (::http/service-fn (http/create-servlet vase-service-map))))) 32 | 33 | (def ^:dynamic *current-service* nil) 34 | 35 | (defmacro with-service 36 | "Executes all requests in the body with the same service (using a thread-local binding)" 37 | [srv-map & body] 38 | `(binding [*current-service* (new-service ~srv-map)] 39 | ~@body)) 40 | 41 | (defn service 42 | [& args] 43 | (or *current-service* (apply new-service args))) 44 | 45 | (defn GET 46 | "Make a GET request on our service using response-for." 47 | [& args] 48 | (apply response-for (service) :get args)) 49 | 50 | (defn POST 51 | "Make a POST request on our service using response-for." 52 | [& args] 53 | (apply response-for (service) :post args)) 54 | 55 | (defn DELETE 56 | "Make a DELETE request on our service using response-for." 57 | [& args] 58 | (apply response-for (service) :delete args)) 59 | 60 | (defn json-request 61 | ([verb url payload] 62 | (json-request verb url payload {})) 63 | ([verb url payload opts] 64 | (response-for (service) 65 | verb url 66 | :headers (merge {"Content-Type" "application/json"} 67 | (:headers opts)) 68 | :body (util/write-json payload)))) 69 | 70 | (defn post-json 71 | "Makes a POST request to URL-path expecting a payload to submit as JSON. 72 | 73 | Options: 74 | * :headers: Additional headers to send with the request." 75 | ([URL-path payload] 76 | (post-json URL-path payload {})) 77 | ([URL-path payload opts] 78 | (json-request :post URL-path payload opts))) 79 | 80 | (defn post-edn 81 | "Makes a POST request to URL-path expecting a payload to submit as edn. 82 | 83 | Options: 84 | * :headers: Additional headers to send with the request." 85 | ([URL-path payload] 86 | (post-edn URL-path payload {})) 87 | ([URL-path payload opts] 88 | (response-for (service) 89 | :post URL-path 90 | :headers (merge {"Content-Type" "application/edn"} 91 | (:headers opts)) 92 | :body (write-edn payload)))) 93 | 94 | (defn response-data 95 | "Return the parsed payload data from a vase api http response." 96 | ([response] (response-data response util/read-json)) 97 | ([response reader] 98 | (-> response 99 | :body 100 | reader))) 101 | 102 | (defn run-interceptor 103 | ([i] (run-interceptor {} i)) 104 | ([ctx i] (chain/execute (chain/enqueue* ctx i)))) 105 | 106 | (defn new-req-ctx 107 | [& {:as headers}] 108 | {:request {:headers headers}}) 109 | -------------------------------------------------------------------------------- /samples/petstore/README.md: -------------------------------------------------------------------------------- 1 | # Pet Store Example 2 | 3 | This is a simple version of a back end for the well-known Pet Store 4 | app. This app should help Vase newcomers see how to create an API with 5 | Vase. 6 | 7 | # Running the sample 8 | 9 | 1. Start it up with `clj -Mrun` 10 | 2. Go to [localhost:8080/index.html](http://localhost:8080/index.html) 11 | to see the Swagger UI. 12 | 3. Click `default` 13 | - GET /pets will get all pets 14 | 15 | click "Try it out!" button. 16 | 17 | - POST /pets will transact new pet(s) 18 | 19 | Click "Example Value" and edit the name and tag values, then click "Try it out!". 20 | 21 | - GET /pet/{id} will get a single pet by ID 22 | 23 | Input a pet ID, then click "Try it out!" button 24 | 25 | # Datomic Setup 26 | 27 | This sample uses Datomic with an in-memory database. Since Vase 28 | already depends on "datomic-free," you don't need to add anything 29 | specific for the in-memory case. 30 | 31 | If you want to use on-disk storage with Datomic Pro, two steps are 32 | needed: 33 | 34 | 1. In `project.clj`, uncomment the dependency for `com.datomic/datomic-pro` 35 | 2. Change the connection defined in `resources/petstore.fern` as 36 | described in comments in that file. 37 | 38 | ```clojure 39 | :vase.descriptor/datomic-uri "datomic:dev://localhost:4334/pet-store" 40 | ``` 41 | -------------------------------------------------------------------------------- /samples/petstore/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {com.cognitect/pedestal.vase {:local/root "../../" :exclusions [org.slf4j/slf4j-nop]} 2 | io.pedestal/pedestal.service {:mvn/version "0.5.5"} 3 | io.pedestal/pedestal.jetty {:mvn/version "0.5.5"} 4 | ch.qos.logback/logback-classic {:mvn/version "1.2.3" :exclusions [org.slf4j/slf4j-api]} 5 | org.slf4j/jul-to-slf4j {:mvn/version "1.7.25"} 6 | org.slf4j/jcl-over-slf4j {:mvn/version "1.7.25"} 7 | org.slf4j/log4j-over-slf4j {:mvn/version "1.7.25"}} 8 | :paths ["src" "resources"] 9 | :aliases {:run {:main-opts ["-m" "com.cognitect.vase.main" "petstore.fern"]} 10 | :dev {:extra-deps {io.pedestal/pedestal.service-tools {:mvn/version "0.5.5"}}}}} 11 | -------------------------------------------------------------------------------- /samples/petstore/petstore.fern: -------------------------------------------------------------------------------- 1 | {;; The only "magic" key here is `vase/service`. Vase finds everything 2 | ;; else by following references 3 | vase/service (fern/lit vase/service 4 | {:apis [@petstore/v1] 5 | :service-map @http-options}) 6 | 7 | ;; For in-memory, use this definition 8 | datomic-uri "datomic:mem:/pet-store" 9 | 10 | ;; For on-disk Datomic, use this definition 11 | ;; datomic "datomic:dev://localhost:4334/petstore" 12 | 13 | ;; Configure Jetty, via Pedestals' service map. 14 | http-options {:io.pedestal.http/resource-path "/public" 15 | :io.pedestal.http/port 8080 16 | :io.pedestal.http/secure-headers {:content-security-policy-settings 17 | {:object-src "none"}}} 18 | 19 | ;; The connection is used in the :on-startup and :on-request chains. 20 | connection (fern/lit vase.datomic/connection @datomic-uri) 21 | 22 | ;; Define an API 23 | petstore/v1 (fern/lit vase/api 24 | {:path "/petstore/v1" 25 | :expose-api-at "/petstore/api" 26 | :on-request [@connection @io.pedestal.http.body-params/body-params @io.pedestal.http/json-body] 27 | :on-startup [@connection @petstore.v1/schema @petstore.v1/seed] 28 | :routes @petstore.v1/routes}) 29 | 30 | ;; The schema used by the API 31 | petstore.v1/schema (fern/lit vase.datomic/attributes 32 | [:pet/id :one :long :unique "The id of a pet"] 33 | [:pet/name :one :string "The name of a pet"] 34 | [:pet/tag :one :string "The tag of a pet"]) 35 | 36 | petstore.v1/seed (fern/lit vase.datomic/tx 37 | {:pet/id 1 38 | :pet/name "Fido"} 39 | {:pet/id 2 40 | :pet/name "Spot"} 41 | {:pet/id 3 42 | :pet/name "Itchy"} 43 | {:pet/id 4 44 | :pet/name "Scratchy"} 45 | {:pet/id 5 46 | :pet/name "Cerberus"}) 47 | 48 | petstore.v1/routes #{["/pet/:id" :get @petstore.v1/find-a-pet] 49 | ["/pets" :delete @petstore.v1/delete-a-pet] 50 | ["/pets" :get @petstore.v1/find-pets] 51 | ["/pets" :post @petstore.v1/add-pets]} 52 | 53 | petstore.v1/add-pets (fern/lit vase.datomic/transact 54 | {:db-op :vase/assert-entity 55 | :properties [:pet/id 56 | :pet/name 57 | :pet/tag]}) 58 | 59 | petstore.v1/delete-a-pet (fern/lit vase.datomic/transact 60 | {:db-op :vase/retract-entity 61 | :properties [:pet/id]}) 62 | 63 | petstore.v1/find-a-pet (fern/lit vase.datomic/query 64 | {:edn-coerce [id] 65 | :params [id] 66 | :query [:find (pull ?e [*]) 67 | :in $ ?id 68 | :where [?e :pet/id ?id]]}) 69 | 70 | petstore.v1/find-pets (fern/lit vase.datomic/query 71 | {:params [] 72 | :query [:find (pull ?e [*]) 73 | :in $ 74 | :where [?e :pet/id ?id]]})} 75 | -------------------------------------------------------------------------------- /samples/petstore/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 9 | 10 | 11 | 12 | 13 | logs/pet-store-%d{yyyy-MM-dd}.%i.log 14 | 16 | 17 | 64 MB 18 | 19 | 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | %-5level %logger{36} - %msg%n 31 | 32 | 33 | 34 | INFO 35 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ 2 | html, 3 | body, 4 | div, 5 | span, 6 | applet, 7 | object, 8 | iframe, 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6, 15 | p, 16 | blockquote, 17 | pre, 18 | a, 19 | abbr, 20 | acronym, 21 | address, 22 | big, 23 | cite, 24 | code, 25 | del, 26 | dfn, 27 | em, 28 | img, 29 | ins, 30 | kbd, 31 | q, 32 | s, 33 | samp, 34 | small, 35 | strike, 36 | strong, 37 | sub, 38 | sup, 39 | tt, 40 | var, 41 | b, 42 | u, 43 | i, 44 | center, 45 | dl, 46 | dt, 47 | dd, 48 | ol, 49 | ul, 50 | li, 51 | fieldset, 52 | form, 53 | label, 54 | legend, 55 | table, 56 | caption, 57 | tbody, 58 | tfoot, 59 | thead, 60 | tr, 61 | th, 62 | td, 63 | article, 64 | aside, 65 | canvas, 66 | details, 67 | embed, 68 | figure, 69 | figcaption, 70 | footer, 71 | header, 72 | hgroup, 73 | menu, 74 | nav, 75 | output, 76 | ruby, 77 | section, 78 | summary, 79 | time, 80 | mark, 81 | audio, 82 | video { 83 | margin: 0; 84 | padding: 0; 85 | border: 0; 86 | font-size: 100%; 87 | font: inherit; 88 | vertical-align: baseline; 89 | } 90 | /* HTML5 display-role reset for older browsers */ 91 | article, 92 | aside, 93 | details, 94 | figcaption, 95 | figure, 96 | footer, 97 | header, 98 | hgroup, 99 | menu, 100 | nav, 101 | section { 102 | display: block; 103 | } 104 | body { 105 | line-height: 1; 106 | } 107 | ol, 108 | ul { 109 | list-style: none; 110 | } 111 | blockquote, 112 | q { 113 | quotes: none; 114 | } 115 | blockquote:before, 116 | blockquote:after, 117 | q:before, 118 | q:after { 119 | content: ''; 120 | content: none; 121 | } 122 | table { 123 | border-collapse: collapse; 124 | border-spacing: 0; 125 | } 126 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/css/typography.css: -------------------------------------------------------------------------------- 1 | /* Google Font's Droid Sans */ 2 | @font-face { 3 | font-family: 'Droid Sans'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf'), format('truetype'); 7 | } 8 | /* Google Font's Droid Sans Bold */ 9 | @font-face { 10 | font-family: 'Droid Sans'; 11 | font-style: normal; 12 | font-weight: 700; 13 | src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf'), format('truetype'); 14 | } 15 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/fonts/DroidSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/fonts/DroidSans-Bold.ttf -------------------------------------------------------------------------------- /samples/petstore/resources/public/fonts/DroidSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/fonts/DroidSans.ttf -------------------------------------------------------------------------------- /samples/petstore/resources/public/images/collapse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/images/collapse.gif -------------------------------------------------------------------------------- /samples/petstore/resources/public/images/expand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/images/expand.gif -------------------------------------------------------------------------------- /samples/petstore/resources/public/images/explorer_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/images/explorer_icons.png -------------------------------------------------------------------------------- /samples/petstore/resources/public/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/images/favicon-16x16.png -------------------------------------------------------------------------------- /samples/petstore/resources/public/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/images/favicon-32x32.png -------------------------------------------------------------------------------- /samples/petstore/resources/public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/images/favicon.ico -------------------------------------------------------------------------------- /samples/petstore/resources/public/images/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/images/logo_small.png -------------------------------------------------------------------------------- /samples/petstore/resources/public/images/pet_store_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/images/pet_store_api.png -------------------------------------------------------------------------------- /samples/petstore/resources/public/images/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/images/throbber.gif -------------------------------------------------------------------------------- /samples/petstore/resources/public/images/wordnik_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cognitect-labs/vase/d882bc8f28e8af2077b55c80e069aa2238f646b7/samples/petstore/resources/public/images/wordnik_api.png -------------------------------------------------------------------------------- /samples/petstore/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Swagger UI 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 90 | 91 | 92 | 93 | 103 | 104 |
 
105 |
106 | 107 | 108 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/js/highlight.9.1.0.pack_extended.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function () { 4 | var configure, highlightBlock; 5 | 6 | configure = hljs.configure; 7 | // "extending" hljs.configure method 8 | hljs.configure = function _configure (options) { 9 | var size = options.highlightSizeThreshold; 10 | 11 | // added highlightSizeThreshold option to set maximum size 12 | // of processed string. Set to null if not a number 13 | hljs.highlightSizeThreshold = size === +size ? size : null; 14 | 15 | configure.call(this, options); 16 | }; 17 | 18 | highlightBlock = hljs.highlightBlock; 19 | 20 | // "extending" hljs.highlightBlock method 21 | hljs.highlightBlock = function _highlightBlock (el) { 22 | var innerHTML = el.innerHTML; 23 | var size = hljs.highlightSizeThreshold; 24 | 25 | // check if highlightSizeThreshold is not set or element innerHTML 26 | // is less than set option highlightSizeThreshold 27 | if (size == null || size > innerHTML.length) { 28 | // proceed with hljs.highlightBlock 29 | highlightBlock.call(hljs, el); 30 | } 31 | }; 32 | 33 | })(); 34 | 35 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/js/jquery.ba-bbq.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 3 | * http://benalman.com/projects/jquery-bbq-plugin/ 4 | * 5 | * Copyright (c) 2010 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this); -------------------------------------------------------------------------------- /samples/petstore/resources/public/js/jquery.slideto.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.fn.slideto=function(a){a=b.extend({slide_duration:"slow",highlight_duration:3E3,highlight:true,highlight_color:"#FFFF99"},a);return this.each(function(){obj=b(this);b("body").animate({scrollTop:obj.offset().top},a.slide_duration,function(){a.highlight&&b.ui.version&&obj.effect("highlight",{color:a.highlight_color},a.highlight_duration)})})}})(jQuery); 2 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/js/jquery.wiggle.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Wiggle 3 | Author: WonderGroup, Jordan Thomas 4 | URL: http://labs.wondergroup.com/demos/mini-ui/index.html 5 | License: MIT (http://en.wikipedia.org/wiki/MIT_License) 6 | */ 7 | jQuery.fn.wiggle=function(o){var d={speed:50,wiggles:3,travel:5,callback:null};var o=jQuery.extend(d,o);return this.each(function(){var cache=this;var wrap=jQuery(this).wrap('
').css("position","relative");var calls=0;for(i=1;i<=o.wiggles;i++){jQuery(this).animate({left:"-="+o.travel},o.speed).animate({left:"+="+o.travel*2},o.speed*2).animate({left:"-="+o.travel},o.speed,function(){calls++;if(jQuery(cache).parent().hasClass('wiggle-wrap')){jQuery(cache).parent().replaceWith(cache);} 8 | if(calls==o.wiggles&&jQuery.isFunction(o.callback)){o.callback();}});}});}; -------------------------------------------------------------------------------- /samples/petstore/resources/public/js/object-assign-pollyfill.js: -------------------------------------------------------------------------------- 1 | if (typeof Object.assign != 'function') { 2 | (function () { 3 | Object.assign = function (target) { 4 | 'use strict'; 5 | if (target === undefined || target === null) { 6 | throw new TypeError('Cannot convert undefined or null to object'); 7 | } 8 | 9 | var output = Object(target); 10 | for (var index = 1; index < arguments.length; index++) { 11 | var source = arguments[index]; 12 | if (source !== undefined && source !== null) { 13 | for (var nextKey in source) { 14 | if (Object.prototype.hasOwnProperty.call(source, nextKey)) { 15 | output[nextKey] = source[nextKey]; 16 | } 17 | } 18 | } 19 | } 20 | return output; 21 | }; 22 | })(); 23 | } 24 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/en.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Warning: Deprecated", 6 | "Implementation Notes":"Implementation Notes", 7 | "Response Class":"Response Class", 8 | "Status":"Status", 9 | "Parameters":"Parameters", 10 | "Parameter":"Parameter", 11 | "Value":"Value", 12 | "Description":"Description", 13 | "Parameter Type":"Parameter Type", 14 | "Data Type":"Data Type", 15 | "Response Messages":"Response Messages", 16 | "HTTP Status Code":"HTTP Status Code", 17 | "Reason":"Reason", 18 | "Response Model":"Response Model", 19 | "Request URL":"Request URL", 20 | "Response Body":"Response Body", 21 | "Response Code":"Response Code", 22 | "Response Headers":"Response Headers", 23 | "Hide Response":"Hide Response", 24 | "Headers":"Headers", 25 | "Try it out!":"Try it out!", 26 | "Show/Hide":"Show/Hide", 27 | "List Operations":"List Operations", 28 | "Expand Operations":"Expand Operations", 29 | "Raw":"Raw", 30 | "can't parse JSON. Raw result":"can't parse JSON. Raw result", 31 | "Example Value":"Example Value", 32 | "Model Schema":"Model Schema", 33 | "Model":"Model", 34 | "Click to set as parameter value":"Click to set as parameter value", 35 | "apply":"apply", 36 | "Username":"Username", 37 | "Password":"Password", 38 | "Terms of service":"Terms of service", 39 | "Created by":"Created by", 40 | "See more at":"See more at", 41 | "Contact the developer":"Contact the developer", 42 | "api version":"api version", 43 | "Response Content Type":"Response Content Type", 44 | "Parameter content type:":"Parameter content type:", 45 | "fetching resource":"fetching resource", 46 | "fetching resource list":"fetching resource list", 47 | "Explore":"Explore", 48 | "Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis", 49 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Can't read from server. It may not have the appropriate access-control-origin settings.", 50 | "Please specify the protocol for":"Please specify the protocol for", 51 | "Can't read swagger JSON from":"Can't read swagger JSON from", 52 | "Finished Loading Resource Information. Rendering Swagger UI":"Finished Loading Resource Information. Rendering Swagger UI", 53 | "Unable to read api":"Unable to read api", 54 | "from path":"from path", 55 | "server returned":"server returned" 56 | }); 57 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/es.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Advertencia: Obsoleto", 6 | "Implementation Notes":"Notas de implementación", 7 | "Response Class":"Clase de la Respuesta", 8 | "Status":"Status", 9 | "Parameters":"Parámetros", 10 | "Parameter":"Parámetro", 11 | "Value":"Valor", 12 | "Description":"Descripción", 13 | "Parameter Type":"Tipo del Parámetro", 14 | "Data Type":"Tipo del Dato", 15 | "Response Messages":"Mensajes de la Respuesta", 16 | "HTTP Status Code":"Código de Status HTTP", 17 | "Reason":"Razón", 18 | "Response Model":"Modelo de la Respuesta", 19 | "Request URL":"URL de la Solicitud", 20 | "Response Body":"Cuerpo de la Respuesta", 21 | "Response Code":"Código de la Respuesta", 22 | "Response Headers":"Encabezados de la Respuesta", 23 | "Hide Response":"Ocultar Respuesta", 24 | "Try it out!":"Pruébalo!", 25 | "Show/Hide":"Mostrar/Ocultar", 26 | "List Operations":"Listar Operaciones", 27 | "Expand Operations":"Expandir Operaciones", 28 | "Raw":"Crudo", 29 | "can't parse JSON. Raw result":"no puede parsear el JSON. Resultado crudo", 30 | "Example Value":"Valor de Ejemplo", 31 | "Model Schema":"Esquema del Modelo", 32 | "Model":"Modelo", 33 | "apply":"aplicar", 34 | "Username":"Nombre de usuario", 35 | "Password":"Contraseña", 36 | "Terms of service":"Términos de Servicio", 37 | "Created by":"Creado por", 38 | "See more at":"Ver más en", 39 | "Contact the developer":"Contactar al desarrollador", 40 | "api version":"versión de la api", 41 | "Response Content Type":"Tipo de Contenido (Content Type) de la Respuesta", 42 | "fetching resource":"buscando recurso", 43 | "fetching resource list":"buscando lista del recurso", 44 | "Explore":"Explorar", 45 | "Show Swagger Petstore Example Apis":"Mostrar Api Ejemplo de Swagger Petstore", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"No se puede leer del servidor. Tal vez no tiene la configuración de control de acceso de origen (access-control-origin) apropiado.", 47 | "Please specify the protocol for":"Por favor, especificar el protocola para", 48 | "Can't read swagger JSON from":"No se puede leer el JSON de swagger desde", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Finalizada la carga del recurso de Información. Mostrando Swagger UI", 50 | "Unable to read api":"No se puede leer la api", 51 | "from path":"desde ruta", 52 | "server returned":"el servidor retornó" 53 | }); 54 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/fr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Avertissement : Obsolète", 6 | "Implementation Notes":"Notes d'implémentation", 7 | "Response Class":"Classe de la réponse", 8 | "Status":"Statut", 9 | "Parameters":"Paramètres", 10 | "Parameter":"Paramètre", 11 | "Value":"Valeur", 12 | "Description":"Description", 13 | "Parameter Type":"Type du paramètre", 14 | "Data Type":"Type de données", 15 | "Response Messages":"Messages de la réponse", 16 | "HTTP Status Code":"Code de statut HTTP", 17 | "Reason":"Raison", 18 | "Response Model":"Modèle de réponse", 19 | "Request URL":"URL appelée", 20 | "Response Body":"Corps de la réponse", 21 | "Response Code":"Code de la réponse", 22 | "Response Headers":"En-têtes de la réponse", 23 | "Hide Response":"Cacher la réponse", 24 | "Headers":"En-têtes", 25 | "Try it out!":"Testez !", 26 | "Show/Hide":"Afficher/Masquer", 27 | "List Operations":"Liste des opérations", 28 | "Expand Operations":"Développer les opérations", 29 | "Raw":"Brut", 30 | "can't parse JSON. Raw result":"impossible de décoder le JSON. Résultat brut", 31 | "Example Value":"Exemple la valeur", 32 | "Model Schema":"Définition du modèle", 33 | "Model":"Modèle", 34 | "apply":"appliquer", 35 | "Username":"Nom d'utilisateur", 36 | "Password":"Mot de passe", 37 | "Terms of service":"Conditions de service", 38 | "Created by":"Créé par", 39 | "See more at":"Voir plus sur", 40 | "Contact the developer":"Contacter le développeur", 41 | "api version":"version de l'api", 42 | "Response Content Type":"Content Type de la réponse", 43 | "fetching resource":"récupération de la ressource", 44 | "fetching resource list":"récupération de la liste de ressources", 45 | "Explore":"Explorer", 46 | "Show Swagger Petstore Example Apis":"Montrer les Apis de l'exemple Petstore de Swagger", 47 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Impossible de lire à partir du serveur. Il se peut que les réglages access-control-origin ne soient pas appropriés.", 48 | "Please specify the protocol for":"Veuillez spécifier un protocole pour", 49 | "Can't read swagger JSON from":"Impossible de lire le JSON swagger à partir de", 50 | "Finished Loading Resource Information. Rendering Swagger UI":"Chargement des informations terminé. Affichage de Swagger UI", 51 | "Unable to read api":"Impossible de lire l'api", 52 | "from path":"à partir du chemin", 53 | "server returned":"réponse du serveur" 54 | }); 55 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/geo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"ყურადღება: აღარ გამოიყენება", 6 | "Implementation Notes":"იმპლემენტაციის აღწერა", 7 | "Response Class":"რესპონს კლასი", 8 | "Status":"სტატუსი", 9 | "Parameters":"პარამეტრები", 10 | "Parameter":"პარამეტრი", 11 | "Value":"მნიშვნელობა", 12 | "Description":"აღწერა", 13 | "Parameter Type":"პარამეტრის ტიპი", 14 | "Data Type":"მონაცემის ტიპი", 15 | "Response Messages":"პასუხი", 16 | "HTTP Status Code":"HTTP სტატუსი", 17 | "Reason":"მიზეზი", 18 | "Response Model":"რესპონს მოდელი", 19 | "Request URL":"მოთხოვნის URL", 20 | "Response Body":"პასუხის სხეული", 21 | "Response Code":"პასუხის კოდი", 22 | "Response Headers":"პასუხის ჰედერები", 23 | "Hide Response":"დამალე პასუხი", 24 | "Headers":"ჰედერები", 25 | "Try it out!":"ცადე !", 26 | "Show/Hide":"გამოჩენა/დამალვა", 27 | "List Operations":"ოპერაციების სია", 28 | "Expand Operations":"ოპერაციები ვრცლად", 29 | "Raw":"ნედლი", 30 | "can't parse JSON. Raw result":"JSON-ის დამუშავება ვერ მოხერხდა. ნედლი პასუხი", 31 | "Example Value":"მაგალითი", 32 | "Model Schema":"მოდელის სტრუქტურა", 33 | "Model":"მოდელი", 34 | "Click to set as parameter value":"პარამეტრისთვის მნიშვნელობის მისანიჭებლად, დააკლიკე", 35 | "apply":"გამოყენება", 36 | "Username":"მოხმარებელი", 37 | "Password":"პაროლი", 38 | "Terms of service":"მომსახურების პირობები", 39 | "Created by":"შექმნა", 40 | "See more at":"ნახე ვრცლად", 41 | "Contact the developer":"დაუკავშირდი დეველოპერს", 42 | "api version":"api ვერსია", 43 | "Response Content Type":"პასუხის კონტენტის ტიპი", 44 | "Parameter content type:":"პარამეტრის კონტენტის ტიპი:", 45 | "fetching resource":"რესურსების მიღება", 46 | "fetching resource list":"რესურსების სიის მიღება", 47 | "Explore":"ნახვა", 48 | "Show Swagger Petstore Example Apis":"ნახე Swagger Petstore სამაგალითო Api", 49 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"სერვერთან დაკავშირება ვერ ხერხდება. შეამოწმეთ access-control-origin.", 50 | "Please specify the protocol for":"მიუთითეთ პროტოკოლი", 51 | "Can't read swagger JSON from":"swagger JSON წაკითხვა ვერ მოხერხდა", 52 | "Finished Loading Resource Information. Rendering Swagger UI":"რესურსების ჩატვირთვა სრულდება. Swagger UI რენდერდება", 53 | "Unable to read api":"api წაკითხვა ვერ მოხერხდა", 54 | "from path":"მისამართიდან", 55 | "server returned":"სერვერმა დააბრუნა" 56 | }); 57 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/it.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Attenzione: Deprecato", 6 | "Implementation Notes":"Note di implementazione", 7 | "Response Class":"Classe della risposta", 8 | "Status":"Stato", 9 | "Parameters":"Parametri", 10 | "Parameter":"Parametro", 11 | "Value":"Valore", 12 | "Description":"Descrizione", 13 | "Parameter Type":"Tipo di parametro", 14 | "Data Type":"Tipo di dato", 15 | "Response Messages":"Messaggi della risposta", 16 | "HTTP Status Code":"Codice stato HTTP", 17 | "Reason":"Motivo", 18 | "Response Model":"Modello di risposta", 19 | "Request URL":"URL della richiesta", 20 | "Response Body":"Corpo della risposta", 21 | "Response Code":"Oggetto della risposta", 22 | "Response Headers":"Intestazioni della risposta", 23 | "Hide Response":"Nascondi risposta", 24 | "Try it out!":"Provalo!", 25 | "Show/Hide":"Mostra/Nascondi", 26 | "List Operations":"Mostra operazioni", 27 | "Expand Operations":"Espandi operazioni", 28 | "Raw":"Grezzo (raw)", 29 | "can't parse JSON. Raw result":"non è possibile parsare il JSON. Risultato grezzo (raw).", 30 | "Model Schema":"Schema del modello", 31 | "Model":"Modello", 32 | "apply":"applica", 33 | "Username":"Nome utente", 34 | "Password":"Password", 35 | "Terms of service":"Condizioni del servizio", 36 | "Created by":"Creato da", 37 | "See more at":"Informazioni aggiuntive:", 38 | "Contact the developer":"Contatta lo sviluppatore", 39 | "api version":"versione api", 40 | "Response Content Type":"Tipo di contenuto (content type) della risposta", 41 | "fetching resource":"recuperando la risorsa", 42 | "fetching resource list":"recuperando lista risorse", 43 | "Explore":"Esplora", 44 | "Show Swagger Petstore Example Apis":"Mostra le api di esempio di Swagger Petstore", 45 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Non è possibile leggere dal server. Potrebbe non avere le impostazioni di controllo accesso origine (access-control-origin) appropriate.", 46 | "Please specify the protocol for":"Si prega di specificare il protocollo per", 47 | "Can't read swagger JSON from":"Impossibile leggere JSON swagger da:", 48 | "Finished Loading Resource Information. Rendering Swagger UI":"Lettura informazioni risorse termianta. Swagger UI viene mostrata", 49 | "Unable to read api":"Impossibile leggere la api", 50 | "from path":"da cartella", 51 | "server returned":"il server ha restituito" 52 | }); 53 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/ja.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"警告: 廃止予定", 6 | "Implementation Notes":"実装メモ", 7 | "Response Class":"レスポンスクラス", 8 | "Status":"ステータス", 9 | "Parameters":"パラメータ群", 10 | "Parameter":"パラメータ", 11 | "Value":"値", 12 | "Description":"説明", 13 | "Parameter Type":"パラメータタイプ", 14 | "Data Type":"データタイプ", 15 | "Response Messages":"レスポンスメッセージ", 16 | "HTTP Status Code":"HTTPステータスコード", 17 | "Reason":"理由", 18 | "Response Model":"レスポンスモデル", 19 | "Request URL":"リクエストURL", 20 | "Response Body":"レスポンスボディ", 21 | "Response Code":"レスポンスコード", 22 | "Response Headers":"レスポンスヘッダ", 23 | "Hide Response":"レスポンスを隠す", 24 | "Headers":"ヘッダ", 25 | "Try it out!":"実際に実行!", 26 | "Show/Hide":"表示/非表示", 27 | "List Operations":"操作一覧", 28 | "Expand Operations":"操作の展開", 29 | "Raw":"Raw", 30 | "can't parse JSON. Raw result":"JSONへ解釈できません. 未加工の結果", 31 | "Model Schema":"モデルスキーマ", 32 | "Model":"モデル", 33 | "apply":"実行", 34 | "Username":"ユーザ名", 35 | "Password":"パスワード", 36 | "Terms of service":"サービス利用規約", 37 | "Created by":"Created by", 38 | "See more at":"See more at", 39 | "Contact the developer":"開発者に連絡", 40 | "api version":"APIバージョン", 41 | "Response Content Type":"レスポンス コンテンツタイプ", 42 | "fetching resource":"リソースの取得", 43 | "fetching resource list":"リソース一覧の取得", 44 | "Explore":"Explore", 45 | "Show Swagger Petstore Example Apis":"SwaggerペットストアAPIの表示", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"サーバから読み込めません. 適切なaccess-control-origin設定を持っていない可能性があります.", 47 | "Please specify the protocol for":"プロトコルを指定してください", 48 | "Can't read swagger JSON from":"次からswagger JSONを読み込めません", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"リソース情報の読み込みが完了しました. Swagger UIを描画しています", 50 | "Unable to read api":"APIを読み込めません", 51 | "from path":"次のパスから", 52 | "server returned":"サーバからの返答" 53 | }); 54 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/pl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Uwaga: Wycofane", 6 | "Implementation Notes":"Uwagi Implementacji", 7 | "Response Class":"Klasa Odpowiedzi", 8 | "Status":"Status", 9 | "Parameters":"Parametry", 10 | "Parameter":"Parametr", 11 | "Value":"Wartość", 12 | "Description":"Opis", 13 | "Parameter Type":"Typ Parametru", 14 | "Data Type":"Typ Danych", 15 | "Response Messages":"Wiadomości Odpowiedzi", 16 | "HTTP Status Code":"Kod Statusu HTTP", 17 | "Reason":"Przyczyna", 18 | "Response Model":"Model Odpowiedzi", 19 | "Request URL":"URL Wywołania", 20 | "Response Body":"Treść Odpowiedzi", 21 | "Response Code":"Kod Odpowiedzi", 22 | "Response Headers":"Nagłówki Odpowiedzi", 23 | "Hide Response":"Ukryj Odpowiedź", 24 | "Headers":"Nagłówki", 25 | "Try it out!":"Wypróbuj!", 26 | "Show/Hide":"Pokaż/Ukryj", 27 | "List Operations":"Lista Operacji", 28 | "Expand Operations":"Rozwiń Operacje", 29 | "Raw":"Nieprzetworzone", 30 | "can't parse JSON. Raw result":"nie można przetworzyć pliku JSON. Nieprzetworzone dane", 31 | "Model Schema":"Schemat Modelu", 32 | "Model":"Model", 33 | "apply":"użyj", 34 | "Username":"Nazwa użytkownika", 35 | "Password":"Hasło", 36 | "Terms of service":"Warunki używania", 37 | "Created by":"Utworzone przez", 38 | "See more at":"Zobacz więcej na", 39 | "Contact the developer":"Kontakt z deweloperem", 40 | "api version":"wersja api", 41 | "Response Content Type":"Typ Zasobu Odpowiedzi", 42 | "fetching resource":"ładowanie zasobu", 43 | "fetching resource list":"ładowanie listy zasobów", 44 | "Explore":"Eksploruj", 45 | "Show Swagger Petstore Example Apis":"Pokaż Przykładowe Api Swagger Petstore", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Brak połączenia z serwerem. Może on nie mieć odpowiednich ustawień access-control-origin.", 47 | "Please specify the protocol for":"Proszę podać protokół dla", 48 | "Can't read swagger JSON from":"Nie można odczytać swagger JSON z", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Ukończono Ładowanie Informacji o Zasobie. Renderowanie Swagger UI", 50 | "Unable to read api":"Nie można odczytać api", 51 | "from path":"ze ścieżki", 52 | "server returned":"serwer zwrócił" 53 | }); 54 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/pt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Aviso: Depreciado", 6 | "Implementation Notes":"Notas de Implementação", 7 | "Response Class":"Classe de resposta", 8 | "Status":"Status", 9 | "Parameters":"Parâmetros", 10 | "Parameter":"Parâmetro", 11 | "Value":"Valor", 12 | "Description":"Descrição", 13 | "Parameter Type":"Tipo de parâmetro", 14 | "Data Type":"Tipo de dados", 15 | "Response Messages":"Mensagens de resposta", 16 | "HTTP Status Code":"Código de status HTTP", 17 | "Reason":"Razão", 18 | "Response Model":"Modelo resposta", 19 | "Request URL":"URL requisição", 20 | "Response Body":"Corpo da resposta", 21 | "Response Code":"Código da resposta", 22 | "Response Headers":"Cabeçalho da resposta", 23 | "Headers":"Cabeçalhos", 24 | "Hide Response":"Esconder resposta", 25 | "Try it out!":"Tente agora!", 26 | "Show/Hide":"Mostrar/Esconder", 27 | "List Operations":"Listar operações", 28 | "Expand Operations":"Expandir operações", 29 | "Raw":"Cru", 30 | "can't parse JSON. Raw result":"Falha ao analisar JSON. Resulto cru", 31 | "Model Schema":"Modelo esquema", 32 | "Model":"Modelo", 33 | "apply":"Aplicar", 34 | "Username":"Usuário", 35 | "Password":"Senha", 36 | "Terms of service":"Termos do serviço", 37 | "Created by":"Criado por", 38 | "See more at":"Veja mais em", 39 | "Contact the developer":"Contate o desenvolvedor", 40 | "api version":"Versão api", 41 | "Response Content Type":"Tipo de conteúdo da resposta", 42 | "fetching resource":"busca recurso", 43 | "fetching resource list":"buscando lista de recursos", 44 | "Explore":"Explorar", 45 | "Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Não é possível ler do servidor. Pode não ter as apropriadas configurações access-control-origin", 47 | "Please specify the protocol for":"Por favor especifique o protocolo", 48 | "Can't read swagger JSON from":"Não é possível ler o JSON Swagger de", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Carregar informação de recurso finalizada. Renderizando Swagger UI", 50 | "Unable to read api":"Não foi possível ler api", 51 | "from path":"do caminho", 52 | "server returned":"servidor retornou" 53 | }); 54 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/ru.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Предупреждение: Устарело", 6 | "Implementation Notes":"Заметки", 7 | "Response Class":"Пример ответа", 8 | "Status":"Статус", 9 | "Parameters":"Параметры", 10 | "Parameter":"Параметр", 11 | "Value":"Значение", 12 | "Description":"Описание", 13 | "Parameter Type":"Тип параметра", 14 | "Data Type":"Тип данных", 15 | "HTTP Status Code":"HTTP код", 16 | "Reason":"Причина", 17 | "Response Model":"Структура ответа", 18 | "Request URL":"URL запроса", 19 | "Response Body":"Тело ответа", 20 | "Response Code":"HTTP код ответа", 21 | "Response Headers":"Заголовки ответа", 22 | "Hide Response":"Спрятать ответ", 23 | "Headers":"Заголовки", 24 | "Response Messages":"Что может прийти в ответ", 25 | "Try it out!":"Попробовать!", 26 | "Show/Hide":"Показать/Скрыть", 27 | "List Operations":"Операции кратко", 28 | "Expand Operations":"Операции подробно", 29 | "Raw":"В сыром виде", 30 | "can't parse JSON. Raw result":"Не удается распарсить ответ:", 31 | "Example Value":"Пример", 32 | "Model Schema":"Структура", 33 | "Model":"Описание", 34 | "Click to set as parameter value":"Нажмите, чтобы испльзовать в качестве значения параметра", 35 | "apply":"применить", 36 | "Username":"Имя пользователя", 37 | "Password":"Пароль", 38 | "Terms of service":"Условия использования", 39 | "Created by":"Разработано", 40 | "See more at":"Еще тут", 41 | "Contact the developer":"Связаться с разработчиком", 42 | "api version":"Версия API", 43 | "Response Content Type":"Content Type ответа", 44 | "Parameter content type:":"Content Type параметра:", 45 | "fetching resource":"Получение ресурса", 46 | "fetching resource list":"Получение ресурсов", 47 | "Explore":"Показать", 48 | "Show Swagger Petstore Example Apis":"Показать примеры АПИ", 49 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Не удается получить ответ от сервера. Возможно, проблема с настройками доступа", 50 | "Please specify the protocol for":"Пожалуйста, укажите протокол для", 51 | "Can't read swagger JSON from":"Не получается прочитать swagger json из", 52 | "Finished Loading Resource Information. Rendering Swagger UI":"Загрузка информации о ресурсах завершена. Рендерим", 53 | "Unable to read api":"Не удалось прочитать api", 54 | "from path":"по адресу", 55 | "server returned":"сервер сказал" 56 | }); 57 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/tr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"Uyarı: Deprecated", 6 | "Implementation Notes":"Gerçekleştirim Notları", 7 | "Response Class":"Dönen Sınıf", 8 | "Status":"Statü", 9 | "Parameters":"Parametreler", 10 | "Parameter":"Parametre", 11 | "Value":"Değer", 12 | "Description":"Açıklama", 13 | "Parameter Type":"Parametre Tipi", 14 | "Data Type":"Veri Tipi", 15 | "Response Messages":"Dönüş Mesajı", 16 | "HTTP Status Code":"HTTP Statü Kodu", 17 | "Reason":"Gerekçe", 18 | "Response Model":"Dönüş Modeli", 19 | "Request URL":"İstek URL", 20 | "Response Body":"Dönüş İçeriği", 21 | "Response Code":"Dönüş Kodu", 22 | "Response Headers":"Dönüş Üst Bilgileri", 23 | "Hide Response":"Dönüşü Gizle", 24 | "Headers":"Üst Bilgiler", 25 | "Try it out!":"Dene!", 26 | "Show/Hide":"Göster/Gizle", 27 | "List Operations":"Operasyonları Listele", 28 | "Expand Operations":"Operasyonları Aç", 29 | "Raw":"Ham", 30 | "can't parse JSON. Raw result":"JSON çözümlenemiyor. Ham sonuç", 31 | "Model Schema":"Model Şema", 32 | "Model":"Model", 33 | "apply":"uygula", 34 | "Username":"Kullanıcı Adı", 35 | "Password":"Parola", 36 | "Terms of service":"Servis şartları", 37 | "Created by":"Oluşturan", 38 | "See more at":"Daha fazlası için", 39 | "Contact the developer":"Geliştirici ile İletişime Geçin", 40 | "api version":"api versiyon", 41 | "Response Content Type":"Dönüş İçerik Tipi", 42 | "fetching resource":"kaynak getiriliyor", 43 | "fetching resource list":"kaynak listesi getiriliyor", 44 | "Explore":"Keşfet", 45 | "Show Swagger Petstore Example Apis":"Swagger Petstore Örnek Api'yi Gör", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"Sunucudan okuma yapılamıyor. Sunucu access-control-origin ayarlarınızı kontrol edin.", 47 | "Please specify the protocol for":"Lütfen istenen adres için protokol belirtiniz", 48 | "Can't read swagger JSON from":"Swagger JSON bu kaynaktan okunamıyor", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"Kaynak baglantısı tamamlandı. Swagger UI gösterime hazırlanıyor", 50 | "Unable to read api":"api okunamadı", 51 | "from path":"yoldan", 52 | "server returned":"sunucuya dönüldü" 53 | }); 54 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/translator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Translator for documentation pages. 5 | * 6 | * To enable translation you should include one of language-files in your index.html 7 | * after . 8 | * For example - 9 | * 10 | * If you wish to translate some new texsts you should do two things: 11 | * 1. Add a new phrase pair ("New Phrase": "New Translation") into your language file (for example lang/ru.js). It will be great if you add it in other language files too. 12 | * 2. Mark that text it templates this way New Phrase or . 13 | * The main thing here is attribute data-sw-translate. Only inner html, title-attribute and value-attribute are going to translate. 14 | * 15 | */ 16 | window.SwaggerTranslator = { 17 | 18 | _words:[], 19 | 20 | translate: function(sel) { 21 | var $this = this; 22 | sel = sel || '[data-sw-translate]'; 23 | 24 | $(sel).each(function() { 25 | $(this).html($this._tryTranslate($(this).html())); 26 | 27 | $(this).val($this._tryTranslate($(this).val())); 28 | $(this).attr('title', $this._tryTranslate($(this).attr('title'))); 29 | }); 30 | }, 31 | 32 | _tryTranslate: function(word) { 33 | return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] : word; 34 | }, 35 | 36 | learn: function(wordsMap) { 37 | this._words = wordsMap; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/lang/zh-cn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jshint quotmark: double */ 4 | window.SwaggerTranslator.learn({ 5 | "Warning: Deprecated":"警告:已过时", 6 | "Implementation Notes":"实现备注", 7 | "Response Class":"响应类", 8 | "Status":"状态", 9 | "Parameters":"参数", 10 | "Parameter":"参数", 11 | "Value":"值", 12 | "Description":"描述", 13 | "Parameter Type":"参数类型", 14 | "Data Type":"数据类型", 15 | "Response Messages":"响应消息", 16 | "HTTP Status Code":"HTTP状态码", 17 | "Reason":"原因", 18 | "Response Model":"响应模型", 19 | "Request URL":"请求URL", 20 | "Response Body":"响应体", 21 | "Response Code":"响应码", 22 | "Response Headers":"响应头", 23 | "Hide Response":"隐藏响应", 24 | "Headers":"头", 25 | "Try it out!":"试一下!", 26 | "Show/Hide":"显示/隐藏", 27 | "List Operations":"显示操作", 28 | "Expand Operations":"展开操作", 29 | "Raw":"原始", 30 | "can't parse JSON. Raw result":"无法解析JSON. 原始结果", 31 | "Model Schema":"模型架构", 32 | "Model":"模型", 33 | "apply":"应用", 34 | "Username":"用户名", 35 | "Password":"密码", 36 | "Terms of service":"服务条款", 37 | "Created by":"创建者", 38 | "See more at":"查看更多:", 39 | "Contact the developer":"联系开发者", 40 | "api version":"api版本", 41 | "Response Content Type":"响应Content Type", 42 | "fetching resource":"正在获取资源", 43 | "fetching resource list":"正在获取资源列表", 44 | "Explore":"浏览", 45 | "Show Swagger Petstore Example Apis":"显示 Swagger Petstore 示例 Apis", 46 | "Can't read from server. It may not have the appropriate access-control-origin settings.":"无法从服务器读取。可能没有正确设置access-control-origin。", 47 | "Please specify the protocol for":"请指定协议:", 48 | "Can't read swagger JSON from":"无法读取swagger JSON于", 49 | "Finished Loading Resource Information. Rendering Swagger UI":"已加载资源信息。正在渲染Swagger UI", 50 | "Unable to read api":"无法读取api", 51 | "from path":"从路径", 52 | "server returned":"服务器返回" 53 | }); 54 | -------------------------------------------------------------------------------- /samples/petstore/resources/public/o2c.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/vase-component/README.md: -------------------------------------------------------------------------------- 1 | # vase-component 2 | 3 | This sample application illustrates use of a Vase API within a 4 | Component-based application. 5 | 6 | ## Usage 7 | 8 | Start the application as follows: 9 | 10 | ``` 11 | lein repl 12 | (dev) 13 | (start) 14 | ``` 15 | 16 | This will start a listener on port 8000. 17 | 18 | To reload: 19 | 20 | ``` 21 | (reset) 22 | ``` 23 | 24 | ## License 25 | 26 | Copyright © 2017 Cognitect, Inc. 27 | -------------------------------------------------------------------------------- /samples/vase-component/config/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 12 | 13 | 14 | 15 | 16 | logs/vasecomponent-%d{yyyy-MM-dd}.%i.log 17 | 18 | 64 MB 19 | 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | %-5level %logger{36} - %msg%n 31 | 32 | 33 | 34 | INFO 35 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /samples/vase-component/dev/dev.clj: -------------------------------------------------------------------------------- 1 | (ns dev 2 | (:require 3 | [clojure.core.async :as async :refer [ (system/system) 22 | (update :http endpoint/dev-mode))) 23 | 24 | (set-init dev-system) 25 | 26 | (defn run-tests [] 27 | (binding [clojure.test/*test-out* *out*] 28 | (clojure.test/run-all-tests #"vase-component.+-test"))) 29 | 30 | (s/check-asserts true) 31 | 32 | (defmethod clojure.core/print-method 33 | clojure.core.async.impl.channels.ManyToManyChannel 34 | [ch writer] 35 | (.write writer "")) 36 | -------------------------------------------------------------------------------- /samples/vase-component/dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | "Quick-start functions for interactive development. This file is 3 | automatically loaded by Clojure on startup.") 4 | 5 | (defn dev 6 | "Loads and switches to the 'dev' namespace." 7 | [] 8 | (require 'dev) 9 | (in-ns 'dev)) 10 | 11 | (defn go 12 | "Loads all code, starts the application, and switches to the 'dev' 13 | namespace." 14 | [] 15 | (in-ns 'dev) 16 | (require 'com.stuartsierra.component.repl) 17 | ((resolve 'com.stuartsierra.component.repl/reset))) 18 | -------------------------------------------------------------------------------- /samples/vase-component/project.clj: -------------------------------------------------------------------------------- 1 | ; The use and distribution terms for this software are covered by the 2 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0) 3 | ; which can be found in the file epl-v10.html at the root of this distribution. 4 | ; 5 | ; By using this software in any fashion, you are agreeing to be bound by 6 | ; the terms of this license. 7 | ; 8 | ; You must not remove this notice, or any other, from this software. 9 | 10 | (defproject com.cognitect/vase-component "0.9.4-SNAPSHOT" 11 | :description "Sample application using Component library to set up system" 12 | :url "https://github.com/cognitect-labs/vase" 13 | :license {:name "Eclipse Public License" 14 | :url "http://www.eclipse.org/legal/epl-v10.html"} 15 | :dependencies [[org.clojure/clojure "1.10.0"] 16 | [io.pedestal/pedestal.service "0.5.5"] 17 | [io.pedestal/pedestal.jetty "0.5.5"] 18 | [com.cognitect/pedestal.vase "0.9.3"] 19 | [com.stuartsierra/component "0.4.0"]] 20 | :resource-paths ["config", "resources"] 21 | :profiles {:dev {:dependencies [[com.stuartsierra/component.repl "0.2.0"] 22 | [org.clojure/tools.reader "1.3.2"] 23 | [org.clojure/tools.namespace "0.3.0-alpha3"]] 24 | :source-paths ["dev"]}}) 25 | -------------------------------------------------------------------------------- /samples/vase-component/resources/petstore-simple.edn: -------------------------------------------------------------------------------- 1 | {:activated-apis [:pet-store/v1] 2 | :datomic-uri "datomic:mem://pet-store" 3 | :descriptor 4 | {:vase/norms 5 | {:pet-store/pet-schema 6 | {:vase.norm/txes [#vase/schema-tx 7 | [[:pet-store.pet/id :one :long :unique "The id of a pet"] 8 | [:pet-store.pet/name :one :string "The name of a pet"] 9 | [:pet-store.pet/tag :one :string "The tag of a pet"]]]}} 10 | :vase/specs 11 | {:example.test/age (fn [age] (> age 21)) 12 | :example.test/name (clojure.spec.alpha/and string? not-empty) 13 | :example.test/person (clojure.spec.alpha/keys :req-un [:example.test/name 14 | :example.test/age])} 15 | :vase/apis 16 | {:pet-store/v1 17 | {:vase.api/routes {"/pets" {:get #vase/query {:name :pet-store-v1/find-pets 18 | :params [] 19 | :query [:find (pull ?e [*]) 20 | :in $ 21 | :where 22 | [?e :pet-store.pet/id ?id]]} 23 | :post #vase/transact {:name :pet-store-v1/add-pets 24 | :properties [:pet-store.pet/id 25 | :pet-store.pet/name 26 | :pet-store.pet/tag]} 27 | :delete #vase/transact {:name :pet-store-v1/delete-a-pet 28 | :db-op :vase/retract-entity 29 | :properties [:db/id]}} 30 | "/validate" {:post #vase/validate {:name :pet-store-v1/validate-page 31 | :spec :example.test/person}} 32 | "/pet/:id" {:get #vase/query {:name :pet-store-v1/find-a-pet 33 | :params [id] 34 | :edn-coerce [id] 35 | :query [:find (pull ?e [*]) 36 | :in $ ?id 37 | :where 38 | [?e :pet-store.pet/id ?id]]}}}} 39 | :vase.api/schemas [:pet-store/pet-schema]}}} 40 | -------------------------------------------------------------------------------- /samples/vase-component/src/vase_component/api.clj: -------------------------------------------------------------------------------- 1 | (ns vase-component.api 2 | (:require [com.stuartsierra.component :as component] 3 | [com.cognitect.vase :as vase])) 4 | 5 | (defrecord VaseAPI [api-root specs routes] 6 | component/Lifecycle 7 | (start [this] 8 | (vase/ensure-schema specs) 9 | (vase/specs specs) 10 | (assoc this :routes (vase/routes api-root specs))) 11 | 12 | (stop [this] 13 | (assoc this :routes nil))) 14 | 15 | (defn from-resource [api-root resource-name] 16 | (->VaseAPI api-root [(vase/load-edn-resource resource-name)] nil)) 17 | 18 | (defn datomic-uri [api] 19 | (-> api :specs first :datomic-uri)) 20 | -------------------------------------------------------------------------------- /samples/vase-component/src/vase_component/endpoint.clj: -------------------------------------------------------------------------------- 1 | (ns vase-component.endpoint 2 | "Pedestal server and HTTP request handling" 3 | (:require 4 | [com.stuartsierra.component :as component] 5 | [io.pedestal.http :as http] 6 | [io.pedestal.http.route :as http.route] 7 | [io.pedestal.log :as log] 8 | [com.cognitect.vase :as vase])) 9 | 10 | ;;; Response helpers 11 | 12 | (defn ok 13 | [s] 14 | {:status 200 15 | :body s}) 16 | 17 | (defn bad-request 18 | [s] 19 | {:status 400 20 | :body s}) 21 | 22 | (defn with-headers 23 | [r h] 24 | (assoc r :headers h)) 25 | 26 | ;;; Health check route 27 | 28 | (defn healthcheck 29 | "Pedestal response handler: simple liveness check." 30 | [_] 31 | (ok "OK")) 32 | 33 | ;;; Pedestal routes and service component 34 | 35 | (defn routes [& contribs] 36 | (http.route/expand-routes 37 | (reduce into #{["/healthcheck" :get `healthcheck]} (map :routes contribs)))) 38 | 39 | (defrecord HTTPEndpoint [service api] 40 | component/Lifecycle 41 | (start [this] 42 | (log/info :message "Starting HTTP endpoint" :port (::http/port service) :apis (:activated-apis api)) 43 | (assoc this :service 44 | (-> service 45 | ;; TODO - make ::http/routes a val instead of a fn in prod. 46 | (assoc ::http/routes #(routes api)) 47 | http/default-interceptors 48 | (cond-> (::dev? this) http/dev-interceptors) 49 | http/create-server 50 | http/start))) 51 | 52 | (stop [this] 53 | (when service 54 | (log/info :message "Stopping HTTP endpoint" :port (::http/port service)) 55 | (try (http/stop service) 56 | (catch Throwable t 57 | (log/error :message "Error in Pedestal stop" :exception t)))) 58 | (assoc this :service nil))) 59 | 60 | (defn service-map 61 | "Returns initial service map for the Pedestal server." 62 | [] 63 | {::http/type :jetty 64 | ::http/port 8000 65 | ::http/join? false}) 66 | 67 | (defn http-endpoint 68 | "Returns the Pedestal server component." 69 | [] 70 | (map->HTTPEndpoint {:service (service-map)})) 71 | 72 | (defn with-port 73 | "Updates http-endpoint component to bind to port. Must be called 74 | before start." 75 | [component port] 76 | (assoc-in component [:service ::http/port] port)) 77 | 78 | (defn with-automatic-port 79 | "Updates http-endpoint to bind to a random free port. Must be called 80 | before start." 81 | [component] 82 | (with-port component 0)) 83 | 84 | (defn port 85 | "Returns bound port of the (started) http-endpoint component." 86 | [component] 87 | (some-> component :service ::http/server 88 | .getConnectors (aget 0) .getLocalPort)) 89 | 90 | (defn dev-mode 91 | "Adds development-mode interceptors to http-endpoint component. Must 92 | be called before start." 93 | [component] 94 | (assoc component ::dev? true)) 95 | 96 | (defn join 97 | "Joins the server thread, blocking the current thread." 98 | [component] 99 | (.join ^org.eclipse.jetty.server.Server 100 | (get-in component [:service ::http/server]))) 101 | -------------------------------------------------------------------------------- /samples/vase-component/src/vase_component/main.clj: -------------------------------------------------------------------------------- 1 | (ns vase-component.main 2 | "Main entry-point for the service when deployed." 3 | (:gen-class) 4 | (:require 5 | [com.stuartsierra.component :as component] 6 | [vase-component.system :as system] 7 | [vase-component.endpoint :as endpoint] 8 | [io.pedestal.log :as log])) 9 | 10 | (defn- env-port 11 | "Gets port from environment variable, or throws." 12 | [] 13 | (if-let [s (System/getenv "PORT")] 14 | (Long. s) 15 | (throw (ex-info "Must set enviroment variable PORT" {})))) 16 | 17 | (defn -main 18 | "Command-line main entry point." 19 | [& args] 20 | (let [port (env-port) 21 | system (-> (system/system) 22 | (update :endpoint endpoint/with-port port) 23 | component/start)] 24 | (log/info :msg "System started" :port port) 25 | (println "Service started at port" port) 26 | (endpoint/join (:endpoint system)))) 27 | -------------------------------------------------------------------------------- /samples/vase-component/src/vase_component/system.clj: -------------------------------------------------------------------------------- 1 | (ns vase-component.system 2 | "Component system map definition" 3 | (:require 4 | [com.stuartsierra.component :as component] 5 | [vase-component.api :as api] 6 | [vase-component.endpoint :as endpoint])) 7 | 8 | (defn system 9 | [] 10 | (component/system-map 11 | :api (api/from-resource "/api" "petstore-simple.edn") 12 | :endpoint (component/using (endpoint/http-endpoint) [:api]))) 13 | -------------------------------------------------------------------------------- /samples/vasebi/.gitignore: -------------------------------------------------------------------------------- 1 | # Project related 2 | 3 | # Java related 4 | pom.xml 5 | pom.xml.asc 6 | *jar 7 | *.class 8 | 9 | # Leiningen 10 | classes/ 11 | lib/ 12 | native/ 13 | checkouts/ 14 | target/ 15 | .lein-* 16 | repl-port 17 | .nrepl-port 18 | .repl 19 | 20 | # Temp Files 21 | *.orig 22 | *~ 23 | .*.swp 24 | .*.swo 25 | *.tmp 26 | *.bak 27 | 28 | # OS X 29 | .DS_Store 30 | 31 | # Logging 32 | *.log 33 | /logs/ 34 | 35 | # Builds 36 | out/ 37 | build/ 38 | 39 | -------------------------------------------------------------------------------- /samples/vasebi/Capstanfile: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Name of the base image. Capstan will download this automatically from 4 | # Cloudius S3 repository. 5 | # 6 | #base: cloudius/osv 7 | base: cloudius/osv-openjdk8 8 | 9 | # 10 | # The command line passed to OSv to start up the application. 11 | # 12 | #cmdline: /java.so -cp /vasebi/app.jar clojure.main -m vasebi 13 | cmdline: /java.so -jar /vasebi/app.jar 14 | 15 | # 16 | # The command to use to build the application. 17 | # You can use any build tool/command (make/rake/lein/boot) - this runs locally on your machine 18 | # 19 | # For Leiningen, you can use: 20 | #build: lein uberjar 21 | # For Boot, you can use: 22 | #build: boot build 23 | 24 | # 25 | # List of files that are included in the generated image. 26 | # 27 | files: 28 | /vasebi/app.jar: ./target/vasebi-0.0.1-SNAPSHOT-standalone.jar 29 | 30 | -------------------------------------------------------------------------------- /samples/vasebi/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8-alpine 2 | MAINTAINER Your Name 3 | 4 | ADD target/vasebi-0.0.1-SNAPSHOT-standalone.jar /vasebi/app.jar 5 | 6 | EXPOSE 8080 7 | 8 | CMD ["java", "-jar", "/vasebi/app.jar"] 9 | -------------------------------------------------------------------------------- /samples/vasebi/README.md: -------------------------------------------------------------------------------- 1 | # FoodMart: Vase Business Intelligence 2 | 3 | This sample shows a simple business intelligence dashboard built on a Vase 4 | service. The dashboard is a [pivot table](https://en.wikipedia.org/wiki/Pivot_table) 5 | that fetches entities from a demand-driven Datomic query via Vase. 6 | 7 | The application data is based around sales from grocery stores in various locations. 8 | 9 | ## Getting Started 10 | 11 | 1. Start the application: `lein run` 12 | 2. Go to [localhost:8080](http://localhost:8080/) to see the Pivot Dasboard 13 | 3. Play around with the graph types, drag-and-drop column and row names, dig into the data. 14 | 15 | ## Digging deeper 16 | 17 | 1. Read your app's source code at src/vasebi/service.clj. Explore the docs of functions 18 | that define routes and responses. 19 | 2. See your Vase API Specification at `resources/vasebi_service.edn`. 20 | 3. Run your app's tests with `lein test`. Read the tests at test/vasebi/service_test.clj. 21 | 4. Learn more! See the [Links section below](#links). 22 | 23 | 24 | ## Configuration 25 | 26 | To configure logging see config/logback.xml. By default, the app logs to stdout and logs/. 27 | To learn more about configuring Logback, read its [documentation](http://logback.qos.ch/documentation.html). 28 | 29 | 30 | ## Developing your service 31 | 32 | 1. Start a new REPL: `lein repl` 33 | 2. Start your service in dev-mode: `(def dev-serv (run-dev))` 34 | 3. Connect your editor to the running REPL session. 35 | Re-evaluated code will be seen immediately in the service. 36 | 4. All changes to your Vase Service Descriptor will be loaded - no re-evaluation 37 | needed. 38 | 39 | ### [Docker](https://www.docker.com/) container support 40 | 41 | 1. Build an uberjar of your service: `lein uberjar` 42 | 2. Build a Docker image: `sudo docker build -t vasebi .` 43 | 3. Run your Docker image: `docker run -p 8080:8080 vasebi` 44 | 45 | ### [OSv](http://osv.io/) unikernel support with [Capstan](http://osv.io/capstan/) 46 | 47 | 1. Build and run your image: `capstan run -f "8080:8080"` 48 | 49 | Once the image it built, it's cached. To delete the image and build a new one: 50 | 51 | 1. `capstan rmi vasebi; capstan build` 52 | 53 | 54 | ## Links 55 | 56 | * [Pedestal examples](https://github.com/pedestal/samples) 57 | * [Vase examples](https://github.com/cognitect-labs/vase/samples) 58 | * [JS Pivottable](https://github.com/nicolaskruchten/pivottable) - MIT License 59 | 60 | 61 | -------------------------------------------------------------------------------- /samples/vasebi/boot.properties: -------------------------------------------------------------------------------- 1 | #http://boot-clj.com 2 | BOOT_CLOJURE_NAME=org.clojure/clojure 3 | BOOT_CLOJURE_VERSION=1.9.0-alpha13 4 | -------------------------------------------------------------------------------- /samples/vasebi/config/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 12 | 13 | 14 | 15 | 16 | logs/vasebi-%d{yyyy-MM-dd}.%i.log 17 | 18 | 64 MB 19 | 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | %-5level %logger{36} - %msg%n 31 | 32 | 33 | 34 | INFO 35 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /samples/vasebi/project.clj: -------------------------------------------------------------------------------- 1 | (defproject vasebi "0.9.3-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.10.0"] 7 | [io.pedestal/pedestal.service "0.5.5"] 8 | [com.cognitect/pedestal.vase "0.9.3"] 9 | 10 | ;; Remove this line and uncomment one of the next lines to 11 | ;; use Immutant or Tomcat instead of Jetty: 12 | [io.pedestal/pedestal.jetty "0.5.5"] 13 | ;; [io.pedestal/pedestal.immutant "0.5.5"] 14 | ;; [io.pedestal/pedestal.tomcat "0.5.5"] 15 | 16 | [ch.qos.logback/logback-classic "1.2.3" :exclusions [org.slf4j/slf4j-api]] 17 | [org.slf4j/jul-to-slf4j "1.7.25"] 18 | [org.slf4j/jcl-over-slf4j "1.7.25"] 19 | [org.slf4j/log4j-over-slf4j "1.7.25"]] 20 | :min-lein-version "2.0.0" 21 | :resource-paths ["config", "resources"] 22 | ;; If you use HTTP/2 or ALPN, use the java-agent to pull in the correct alpn-boot dependency 23 | ;:java-agents [[org.mortbay.jetty.alpn/jetty-alpn-agent "2.0.3"]] 24 | :profiles {:dev {:aliases {"run-dev" ["trampoline" "run" "-m" "vasebi.server/run-dev"]} 25 | :dependencies [[io.pedestal/pedestal.service-tools "0.5.5"]]} 26 | :uberjar {:aot [vasebi.server] 27 | :main vasebi.server}} 28 | :main ^{:skip-aot true} vasebi.server) 29 | -------------------------------------------------------------------------------- /samples/vasebi/resources/public/dist/gchart_renderers.min.js: -------------------------------------------------------------------------------- 1 | (function(){var t;(t=function(t){return"object"==typeof exports&&"object"==typeof module?t(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){var e;return e=function(e,r){return function(n,a){var o,i,l,h,s,u,g,d,c,p,f,v,C,w,A,m,y,j,b,S,x,z,T,k,F,q,D,L,N,W,B;if(d={localeStrings:{vs:"vs",by:"by"},gchart:{}},a=t.extend(!0,{},d,a),null==(i=a.gchart).width&&(i.width=window.innerWidth/1.4),null==(l=a.gchart).height&&(l.height=window.innerHeight/1.4),k=n.getRowKeys(),0===k.length&&k.push([]),s=n.getColKeys(),0===s.length&&s.push([]),c=n.aggregatorName,n.valAttrs.length&&(c+="("+n.valAttrs.join(", ")+")"),C=function(){var t,e,r;for(r=[],t=0,e=k.length;e>t;t++)f=k[t],r.push(f.join("-"));return r}(),C.unshift(""),j=0,"ScatterChart"===e){u=[],S=n.tree;for(B in S){q=S[B];for(W in q)o=q[W],u.push([parseFloat(W),parseFloat(B),c+": \n"+o.format(o.value())])}g=new google.visualization.DataTable,g.addColumn("number",n.colAttrs.join("-")),g.addColumn("number",n.rowAttrs.join("-")),g.addColumn({type:"string",role:"tooltip"}),g.addRows(u),v=n.colAttrs.join("-"),D=n.rowAttrs.join("-"),F=""}else{for(u=[C],w=0,m=s.length;m>w;w++){for(h=s[w],z=[h.join("-")],j+=z[0].length,A=0,y=k.length;y>A;A++)T=k[A],o=n.getAggregator(T,h),null!=o.value()?(L=o.value(),z.push(t.isNumeric(L)?1>L?parseFloat(L.toPrecision(3)):parseFloat(L.toFixed(3)):L)):z.push(null);u.push(z)}g=google.visualization.arrayToDataTable(u),F=D=c,v=n.colAttrs.join("-"),""!==v&&(F+=" "+a.localeStrings.vs+" "+v),p=n.rowAttrs.join("-"),""!==p&&(F+=" "+a.localeStrings.by+" "+p)}return b={title:F,hAxis:{title:v,slantedText:j>50},vAxis:{title:D},tooltip:{textStyle:{fontName:"Arial",fontSize:12}}},"ColumnChart"===e&&(b.vAxis.minValue=0),"ScatterChart"===e?(b.legend={position:"none"},b.chartArea={width:"80%",height:"80%"}):2===u[0].length&&""===u[0][1]&&(b.legend={position:"none"}),b=t.extend(!0,{},b,a.gchart,r),x=t("
").css({width:"100%",height:"100%"}),N=new google.visualization.ChartWrapper({dataTable:g,chartType:e,options:b}),N.draw(x[0]),x.bind("dblclick",function(){var t;return t=new google.visualization.ChartEditor,google.visualization.events.addListener(t,"ok",function(){return t.getChartWrapper().draw(x[0])}),t.openDialog(N)}),x}},t.pivotUtilities.gchart_renderers={"Line Chart":e("LineChart"),"Bar Chart":e("ColumnChart"),"Stacked Bar Chart":e("ColumnChart",{isStacked:!0}),"Area Chart":e("AreaChart",{isStacked:!0}),"Scatter Chart":e("ScatterChart")}})}).call(this); 2 | //# sourceMappingURL=gchart_renderers.min.js.map -------------------------------------------------------------------------------- /samples/vasebi/resources/public/dist/pivot.css: -------------------------------------------------------------------------------- 1 | .pvtUi { color: #333; } 2 | 3 | 4 | table.pvtTable { 5 | font-size: 8pt; 6 | text-align: left; 7 | border-collapse: collapse; 8 | } 9 | table.pvtTable thead tr th, table.pvtTable tbody tr th { 10 | background-color: #e6EEEE; 11 | border: 1px solid #CDCDCD; 12 | font-size: 8pt; 13 | padding: 5px; 14 | } 15 | 16 | table.pvtTable .pvtColLabel {text-align: center;} 17 | table.pvtTable .pvtTotalLabel {text-align: right;} 18 | 19 | table.pvtTable tbody tr td { 20 | color: #3D3D3D; 21 | padding: 5px; 22 | background-color: #FFF; 23 | border: 1px solid #CDCDCD; 24 | vertical-align: top; 25 | text-align: right; 26 | } 27 | 28 | .pvtTotal, .pvtGrandTotal { font-weight: bold; } 29 | 30 | .pvtVals { text-align: center;} 31 | .pvtAggregator { margin-bottom: 5px ;} 32 | 33 | .pvtAxisContainer, .pvtVals { 34 | border: 1px solid gray; 35 | background: #EEE; 36 | padding: 5px; 37 | min-width: 20px; 38 | min-height: 20px; 39 | } 40 | .pvtAxisContainer li { 41 | padding: 8px 6px; 42 | list-style-type: none; 43 | cursor:move; 44 | } 45 | .pvtAxisContainer li.pvtPlaceholder { 46 | -webkit-border-radius: 5px; 47 | padding: 3px 15px; 48 | -moz-border-radius: 5px; 49 | border-radius: 5px; 50 | border: 1px dashed #aaa; 51 | } 52 | 53 | .pvtAxisContainer li span.pvtAttr { 54 | -webkit-text-size-adjust: 100%; 55 | background: #F3F3F3; 56 | border: 1px solid #DEDEDE; 57 | padding: 2px 5px; 58 | white-space:nowrap; 59 | -webkit-border-radius: 5px; 60 | -moz-border-radius: 5px; 61 | border-radius: 5px; 62 | } 63 | 64 | .pvtTriangle { 65 | cursor:pointer; 66 | color: grey; 67 | } 68 | 69 | .pvtHorizList li { display: inline; } 70 | .pvtVertList { vertical-align: top; } 71 | 72 | .pvtFilteredAttribute { font-style: italic } 73 | 74 | .pvtFilterBox{ 75 | z-index: 100; 76 | width: 300px; 77 | border: 1px solid gray; 78 | background-color: #fff; 79 | position: absolute; 80 | text-align: center; 81 | } 82 | 83 | .pvtFilterBox h4{ margin: 15px; } 84 | .pvtFilterBox p { margin: 10px auto; } 85 | .pvtFilterBox label { font-weight: normal; } 86 | .pvtFilterBox input[type='checkbox'] { margin-right: 10px; margin-left: 10px; } 87 | .pvtFilterBox input[type='text'] { width: 230px; } 88 | .pvtFilterBox .count { color: gray; font-weight: normal; margin-left: 3px;} 89 | 90 | .pvtCheckContainer{ 91 | text-align: left; 92 | font-size: 14px; 93 | white-space: nowrap; 94 | overflow-y: scroll; 95 | width: 100%; 96 | max-height: 250px; 97 | border-top: 1px solid lightgrey; 98 | border-bottom: 1px solid lightgrey; 99 | } 100 | 101 | .pvtCheckContainer p{ margin: 5px; } 102 | 103 | .pvtRendererArea { padding: 5px;} 104 | -------------------------------------------------------------------------------- /samples/vasebi/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Food Mart Data Dash 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 |

Food Mart Pivot Explorer

24 | 61 | 62 |
63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /samples/vasebi/src/vasebi/server.clj: -------------------------------------------------------------------------------- 1 | (ns vasebi.server 2 | (:gen-class) ; for -main method in uberjar 3 | (:require [clojure.spec.alpha :as spec] 4 | [io.pedestal.http :as server] 5 | [io.pedestal.http.route :as route] 6 | [com.cognitect.vase :as vase] 7 | [com.cognitect.vase.spec :as vase.spec] 8 | [vasebi.service :as service])) 9 | 10 | (defn activate-vase 11 | ([base-routes api-root spec-paths] 12 | (activate-vase base-routes api-root spec-paths vase/load-edn-resource)) 13 | ([base-routes api-root spec-paths vase-load-fn] 14 | (let [vase-specs (mapv vase-load-fn spec-paths)] 15 | (when (seq vase-specs) 16 | (spec/check-asserts true) 17 | (doseq [vase-spec vase-specs] 18 | (spec/assert ::vase.spec/spec vase-spec)) 19 | (vase/ensure-schema vase-specs) 20 | (vase/specs vase-specs)) 21 | {::routes (if (empty? vase-specs) 22 | base-routes 23 | (into base-routes (vase/routes api-root vase-specs))) 24 | ::specs vase-specs}))) 25 | 26 | (defn vase-service 27 | "Optionally given a default service map and any number of string paths 28 | to Vase API Specifications, 29 | Return a Pedestal Service Map with all Vase APIs parsed, ensured, and activated." 30 | ([] 31 | (vase-service service/service)) 32 | ([service-map] 33 | (vase-service service-map vase/load-edn-resource)) 34 | ([service-map vase-load-fn] 35 | (merge {:env :prod 36 | ::server/routes (::routes (activate-vase 37 | (::service/route-set service-map) 38 | (::vase/api-root service-map) 39 | (::vase/spec-resources service-map) 40 | vase-load-fn))} 41 | service-map))) 42 | 43 | ;; This is an adapted service map, that can be started and stopped 44 | ;; From the REPL you can call server/start and server/stop on this service 45 | (defonce runnable-service (server/create-server (vase-service))) 46 | 47 | (defn run-dev 48 | "The entry-point for 'lein run-dev'" 49 | [& args] 50 | (println "\nCreating your [DEV] server...") 51 | (-> service/service ;; start with production configuration 52 | (merge {:env :dev 53 | ;; do not block thread that starts web server 54 | ::server/join? false 55 | ;; Routes can be a function that resolve routes, 56 | ;; we can use this to set the routes to be reloadable 57 | ::server/routes #(route/expand-routes 58 | (::routes (activate-vase (deref #'service/routes) 59 | (::vase/api-root service/service) 60 | (mapv (fn [res-str] 61 | (str "resources/" res-str)) 62 | (::vase/spec-resources service/service)) 63 | vase/load-edn-file))) 64 | ;; all origins are allowed in dev mode 65 | ::server/allowed-origins {:creds true :allowed-origins (constantly true)}}) 66 | ;; Wire up interceptor chains 67 | server/default-interceptors 68 | server/dev-interceptors 69 | server/create-server 70 | server/start)) 71 | 72 | (defn -main 73 | "The entry-point for 'lein run'" 74 | [& args] 75 | (println "\nCreating your server...") 76 | (server/start runnable-service)) 77 | 78 | ;; If you package the service up as a WAR, 79 | ;; some form of the following function sections is required (for io.pedestal.servlet.ClojureVarServlet). 80 | 81 | ;;(defonce servlet (atom nil)) 82 | ;; 83 | ;;(defn servlet-init 84 | ;; [_ config] 85 | ;; ;; Initialize your app here. 86 | ;; (reset! servlet (server/servlet-init service/service nil))) 87 | ;; 88 | ;;(defn servlet-service 89 | ;; [_ request response] 90 | ;; (server/servlet-service @servlet request response)) 91 | ;; 92 | ;;(defn servlet-destroy 93 | ;; [_] 94 | ;; (server/servlet-destroy @servlet) 95 | ;; (reset! servlet nil)) 96 | -------------------------------------------------------------------------------- /samples/vasebi/src/vasebi/service.clj: -------------------------------------------------------------------------------- 1 | (ns vasebi.service 2 | (:require [io.pedestal.http :as http] 3 | [io.pedestal.http.route :as route] 4 | [io.pedestal.http.body-params :as body-params] 5 | [ring.util.response :as ring-resp] 6 | [com.cognitect.vase :as vase] 7 | [clojure.java.io :as io])) 8 | 9 | (defn about-page 10 | [request] 11 | (ring-resp/response (format "Clojure %s - served from %s" 12 | (clojure-version) 13 | (route/url-for ::about-page)))) 14 | 15 | 16 | (def pivot-response {:status 200 17 | :headers {} 18 | :body (slurp (io/resource "public/index.html"))}) 19 | (defn home-page 20 | [request] 21 | pivot-response) 22 | 23 | ;; Defines "/" and "/about" routes with their associated :get handlers. 24 | ;; The interceptors defined after the verb map (e.g., {:get home-page} 25 | ;; apply to / and its children (/about). 26 | (def common-interceptors [(body-params/body-params) http/html-body]) 27 | 28 | ;; Tabular routes 29 | (def routes #{["/" :get (conj common-interceptors `home-page)] 30 | ["/about" :get (conj common-interceptors `about-page)]}) 31 | 32 | (def service 33 | {:env :prod 34 | ;; You can bring your own non-default interceptors. Make 35 | ;; sure you include routing and set it up right for 36 | ;; dev-mode. If you do, many other keys for configuring 37 | ;; default interceptors will be ignored. 38 | ;; ::http/interceptors [] 39 | 40 | ;; Uncomment next line to enable CORS support, add 41 | ;; string(s) specifying scheme, host and port for 42 | ;; allowed source(s): 43 | ;; 44 | ;; "http://localhost:8080" 45 | ;; 46 | ;;::http/allowed-origins ["scheme://host:port"] 47 | 48 | ::route-set routes 49 | ::vase/api-root "/api" 50 | ::vase/spec-resources ["vasebi_service.edn"] 51 | 52 | ;; Root for resource interceptor that is available by default. 53 | ::http/resource-path "/public" 54 | ;; We need to relax the secure headers a bit (specifically the CSP controls) 55 | ::http/secure-headers {:content-security-policy-settings {:object-src "none"}} 56 | 57 | ;; Either :jetty, :immutant or :tomcat (see comments in project.clj) 58 | ::http/type :jetty 59 | ;;::http/host "localhost" 60 | ::http/port 8080 61 | ;; Options to pass to the container (Jetty) 62 | ::http/container-options {:h2c? true 63 | :h2? false 64 | ;:keystore "test/hp/keystore.jks" 65 | ;:key-password "password" 66 | ;:ssl-port 8443 67 | :ssl? false}}) 68 | 69 | -------------------------------------------------------------------------------- /samples/vasebi/test/vasebi/service_test.clj: -------------------------------------------------------------------------------- 1 | (ns vasebi.service-test 2 | (:require [clojure.test :refer :all] 3 | [io.pedestal.test :refer :all] 4 | [io.pedestal.http :as http] 5 | [vasebi.test-helper :as helper] 6 | [vasebi.service :as service])) 7 | 8 | ;; To test your service, call `(helper/service` to get a new service instance. 9 | ;; If you need a constant service over multiple calls, use `(helper/with-service ...) 10 | ;; All generated services will have randomized, consistent in-memory Datomic DBs 11 | ;; if required by the service 12 | ;; 13 | ;; `helper` also contains shorthands for common `response-for` patterns, 14 | ;; like GET, POST, post-json, post-edn, and others 15 | 16 | (deftest about-page-test 17 | (helper/with-service service/service 18 | (is (.contains (:body (response-for (helper/service) :get "/about")) 19 | "Clojure 1.9")) 20 | (is (= (:headers (helper/GET "/about")) 21 | {"Content-Type" "text/html;charset=UTF-8" 22 | "Strict-Transport-Security" "max-age=31536000; includeSubdomains" 23 | "X-Frame-Options" "DENY" 24 | "X-Content-Type-Options" "nosniff" 25 | "X-XSS-Protection" "1; mode=block" 26 | "X-Download-Options" "noopen" 27 | "X-Permitted-Cross-Domain-Policies" "none" 28 | "Content-Security-Policy" "object-src none"})))) 29 | 30 | -------------------------------------------------------------------------------- /samples/vasebi/test/vasebi/test_helper.clj: -------------------------------------------------------------------------------- 1 | (ns vasebi.test-helper 2 | (:require [io.pedestal.test :refer [response-for]] 3 | [io.pedestal.http :as http] 4 | [io.pedestal.interceptor.chain :as chain] 5 | [io.pedestal.log :as log] 6 | [com.cognitect.vase :as vase] 7 | [com.cognitect.vase.util :as util] 8 | [com.cognitect.vase.datomic :as datomic] 9 | [vasebi.server :as server] 10 | [vasebi.service])) 11 | 12 | (def write-edn pr-str) 13 | 14 | (defn new-service 15 | "This generates a new testable service for use with io.pedestal.test/response-for. 16 | It will also create a new Datomic DB (randomized URI)." 17 | ([] (new-service vasebi.service/service)) 18 | ([service-map] 19 | (let [db-table (volatile! {}) 20 | vase-service-map (server/vase-service 21 | service-map 22 | (fn [spec-path] 23 | (let [spec (vase/load-edn-resource spec-path) 24 | prod-db-uri (:datomic-uri spec) 25 | new-db-uri (when-let [db-uri (and prod-db-uri (get @db-table prod-db-uri (datomic/new-db-uri)))] 26 | (vswap! db-table assoc prod-db-uri db-uri) 27 | db-uri)] 28 | (if prod-db-uri 29 | (assoc spec :datomic-uri new-db-uri) 30 | spec))))] 31 | (::http/service-fn (http/create-servlet vase-service-map))))) 32 | 33 | (def ^:dynamic *current-service* nil) 34 | 35 | (defmacro with-service 36 | "Executes all requests in the body with the same service (using a thread-local binding)" 37 | [srv-map & body] 38 | `(binding [*current-service* (new-service ~srv-map)] 39 | ~@body)) 40 | 41 | (defn service 42 | [& args] 43 | (or *current-service* (apply new-service args))) 44 | 45 | (defn GET 46 | "Make a GET request on our service using response-for." 47 | [& args] 48 | (apply response-for (service) :get args)) 49 | 50 | (defn POST 51 | "Make a POST request on our service using response-for." 52 | [& args] 53 | (apply response-for (service) :post args)) 54 | 55 | (defn DELETE 56 | "Make a DELETE request on our service using response-for." 57 | [& args] 58 | (apply response-for (service) :delete args)) 59 | 60 | (defn json-request 61 | ([verb url payload] 62 | (json-request verb url payload {})) 63 | ([verb url payload opts] 64 | (response-for (service) 65 | verb url 66 | :headers (merge {"Content-Type" "application/json"} 67 | (:headers opts)) 68 | :body (util/write-json payload)))) 69 | 70 | (defn post-json 71 | "Makes a POST request to URL-path expecting a payload to submit as JSON. 72 | 73 | Options: 74 | * :headers: Additional headers to send with the request." 75 | ([URL-path payload] 76 | (post-json URL-path payload {})) 77 | ([URL-path payload opts] 78 | (json-request :post URL-path payload opts))) 79 | 80 | (defn post-edn 81 | "Makes a POST request to URL-path expecting a payload to submit as edn. 82 | 83 | Options: 84 | * :headers: Additional headers to send with the request." 85 | ([URL-path payload] 86 | (post-edn URL-path payload {})) 87 | ([URL-path payload opts] 88 | (response-for (service) 89 | :post URL-path 90 | :headers (merge {"Content-Type" "application/edn"} 91 | (:headers opts)) 92 | :body (write-edn payload)))) 93 | 94 | (defn response-data 95 | "Return the parsed payload data from a vase api http response." 96 | ([response] (response-data response util/read-json)) 97 | ([response reader] 98 | (-> response 99 | :body 100 | reader))) 101 | 102 | (defn run-interceptor 103 | ([i] (run-interceptor {} i)) 104 | ([ctx i] (chain/execute (chain/enqueue* ctx i)))) 105 | 106 | (defn new-req-ctx 107 | [& {:as headers}] 108 | {:request {:headers headers}}) 109 | -------------------------------------------------------------------------------- /script/pre-release-check.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | # Check for version hygiene and samples all pass before releasing 4 | # 5 | 6 | echo "==== Checking top-level for dependencies up to date" 7 | lein ancient 8 | 9 | echo "==== Checking samples for dependencies up to date" 10 | pushd samples 11 | 12 | for d in *; do 13 | pushd $d 14 | echo "===== $d" 15 | lein ancient 16 | popd 17 | done 18 | popd 19 | 20 | echo "==== Running tests in samples" 21 | pushd samples 22 | 23 | for d in *; do 24 | pushd $d 25 | echo "===== $d" 26 | lein test 27 | popd 28 | done 29 | popd 30 | 31 | 32 | echo "==== Look at all project.clj version declarations" 33 | find . -path ./template/src -prune -o -name "project.clj" -print | xargs grep defproject 34 | 35 | echo "==== Check copyright declarations" 36 | CURRENT_YEAR=`date +%Y` 37 | find . \( -path ./.git -o -path ./samples/pet-store/resources -o -path ./samples/petstore-full/resources -o -path ./script \) -prune -o -type f -print | xargs grep -i "copyright.*20" | grep -v Relevance | grep -v $CURRENT_YEAR 38 | -------------------------------------------------------------------------------- /src/com/cognitect/vase.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase 2 | (:require [clojure.spec.alpha :as spec] 3 | [io.pedestal.http.route :as route] 4 | [com.cognitect.vase.datomic :as datomic] 5 | [com.cognitect.vase.edn :as edn] 6 | [com.cognitect.vase.literals :as literals] 7 | [com.cognitect.vase.routes :as routes] 8 | [com.cognitect.vase.spec :as vase.spec])) 9 | 10 | (defn load-edn-resource 11 | "Given a resource name, loads a descriptor or app-spec, 12 | using the proper readers to get support for Vase literals." 13 | [res] 14 | (if (coll? res) 15 | res 16 | (edn/from-resource res))) 17 | 18 | (defn load-edn-file 19 | "Given a path, loads a descriptor using the proper readers to get 20 | support for Vase literals." 21 | [file-path] 22 | (if (coll? file-path) 23 | file-path 24 | (edn/from-file file-path))) 25 | 26 | (defn ensure-schema 27 | "Given an api-spec or a collection of app-specs, 28 | extract the schema norms, ensure they conform, and idempotently 29 | transact them into the Datomic DB. 30 | Returns a map of {'db-uri' {:connection datomic-conn, :norms {... all merged norms ..}}}." 31 | [spec-or-specs] 32 | (let [edn-specs (if (sequential? spec-or-specs) spec-or-specs [spec-or-specs]) 33 | uri-norms (reduce (fn [acc spec] 34 | (let [uri (:datomic-uri spec) 35 | norms (get-in spec [:descriptor :vase/norms])] 36 | (if (contains? acc uri) 37 | ;; It is expected that norm chunks are complete. 38 | ;; A single chunk cannot be spread across files, 39 | ;; which is why we're using `merge` and not `merge-with concat` 40 | (update-in acc [uri] #(merge % norms)) 41 | (assoc acc uri norms)))) 42 | {} 43 | edn-specs)] 44 | (reduce (fn [acc [uri norms]] 45 | (let [conn (datomic/connect uri)] 46 | (datomic/ensure-schema conn norms) 47 | (assoc acc uri {:connection conn 48 | :norms norms}))) 49 | {} 50 | uri-norms))) 51 | 52 | (defn specs 53 | "Given a app-spec or collection of app-specs, 54 | extract all defined Clojure specs and evaluate them, 55 | placing them in spec's registry." 56 | [spec-or-specs] 57 | (let [edn-specs (if (sequential? spec-or-specs) spec-or-specs [spec-or-specs]) 58 | descriptors (map :descriptor edn-specs)] 59 | (doseq [descriptor descriptors] 60 | (when-let [specs (:vase/specs descriptor)] 61 | (doseq [[k specv] specs] 62 | (let [sv (cond 63 | (spec/spec? specv) specv 64 | (list? specv) (eval specv) 65 | (symbol? specv) (resolve specv) 66 | :else specv)] 67 | (eval `(spec/def ~k ~sv)))))))) 68 | 69 | (defn routes 70 | "Return a seq of route vectors for Pedestal's table routing syntax. Routes 71 | will all begin with `api-root/:api-namespace/api-name-tag`. 72 | 73 | `spec-or-specs` is either a single app-spec (as a map) or a collection of app-specs. 74 | 75 | The routes will support all the operations defined in the 76 | spec. Callers should treat the format of these routes as 77 | opaque. They may change in number, quantity, or layout." 78 | [api-root spec-or-specs] 79 | (let [specs (if (sequential? spec-or-specs) spec-or-specs [spec-or-specs]) 80 | ;; We need to "unpack" all the :activated-apis 81 | ;; From this part onward, :activated-apis is a single, scalar; a keyword 82 | expanded-specs (mapcat (fn [spec] 83 | (if (sequential? (:activated-apis spec)) 84 | (mapv #(assoc spec :activated-apis %) (:activated-apis spec)) 85 | [spec])) 86 | specs) 87 | routes (mapcat (partial routes/spec-routes api-root) expanded-specs) 88 | api-route (routes/api-description-route 89 | api-root 90 | routes 91 | :describe-apis)] 92 | (cons api-route routes))) 93 | 94 | (spec/fdef routes 95 | :args (spec/cat :api-route vase.spec/valid-uri? 96 | :spec-or-specs (spec/or :single-spec ::vase.spec/spec 97 | :multiple-specs (spec/* ::vase.spec/spec))) 98 | :ret ::vase.spec/route-table) 99 | -------------------------------------------------------------------------------- /src/com/cognitect/vase/api.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.api 2 | "Public functions for submitting a data structure to Vase and 3 | getting back routes, specs, and even a whole Pedestal service map." 4 | (:require [clojure.spec.alpha :as s] 5 | [clojure.string :as str] 6 | [io.pedestal.http :as http] 7 | [io.pedestal.interceptor :as i] 8 | [io.pedestal.interceptor.chain :as chain])) 9 | 10 | (s/def ::path string?) 11 | 12 | (defn- interceptor? [v] 13 | (satisfies? i/IntoInterceptor v)) 14 | 15 | (s/def ::interceptor interceptor?) 16 | (s/def ::interceptors (s/coll-of ::interceptor :min-count 1)) 17 | (s/def ::route-name keyword?) 18 | (s/def ::route (s/cat :path ::path 19 | :verb #{:get :put :post :delete :head :options :patch} 20 | :interceptors (s/or :single ::interceptor 21 | :vector ::interceptors) 22 | :route-name (s/? ::route-name))) 23 | (s/def ::routes (s/coll-of ::route :min-count 1)) 24 | (s/def ::on-startup (s/nilable ::interceptors)) 25 | (s/def ::on-request (s/nilable ::interceptors)) 26 | (s/def ::api (s/keys :req-un [::on-startup ::on-request ::routes ::path])) 27 | (s/def ::apis (s/coll-of ::api :min-count 1)) 28 | (s/def ::service-map (s/keys)) 29 | (s/def ::service (s/keys :req-un [::apis] :opt-un [::service-map])) 30 | 31 | (defn- base-route 32 | [base {:keys [path]}] 33 | (let [s (str base path)] 34 | (str/replace s #"//" "/"))) 35 | 36 | (defn- base-interceptors 37 | [on-request {:keys [interceptors]}] 38 | (let [[one-or-many intc] interceptors] 39 | (case one-or-many 40 | :single (conj on-request intc) 41 | :vector (into on-request intc)))) 42 | 43 | (defn- routes-for-api 44 | [api] 45 | (let [base (:path api "/") 46 | on-req (or (:on-request api) [])] 47 | (mapv 48 | (fn [route] 49 | (cond-> [(base-route base route) 50 | (:verb route) 51 | (base-interceptors on-req route)] 52 | (some? (:route-name route)) 53 | (into [:route-name (:route-name route)]))) 54 | (:routes api #{})))) 55 | 56 | (defn- collect-routes 57 | [spec] 58 | (reduce into #{} 59 | (map routes-for-api (:apis spec)))) 60 | 61 | (defn- add-routes 62 | [service-map all-routes] 63 | (assoc service-map ::http/routes all-routes)) 64 | 65 | (defn- collect-startups 66 | [spec] 67 | (reduce into [] 68 | (map #(:on-startup %) (:apis spec)))) 69 | 70 | (defn- add-startups 71 | [service-map startups] 72 | (assoc service-map ::startups startups)) 73 | 74 | (defn dev-mode 75 | [service-map] 76 | (assoc service-map ::http/join? false)) 77 | 78 | (def default-service-map 79 | {::http/type :jetty 80 | ::http/port 80 81 | ::http/routes #{}}) 82 | 83 | (defn service-map 84 | "Given a spec, return a Pedestal service-map 85 | or :clojure.spec/invalid. If starter-map is provided, the spec's 86 | settings will be merged into it. If starter-map is not provided then 87 | the `default-service-map` is used as a starter." 88 | ([spec] 89 | (service-map spec default-service-map)) 90 | ([spec starter-map] 91 | (let [conformed (s/conform ::service spec)] 92 | (if (s/invalid? conformed) 93 | (throw (ex-info (str "Can't create service map.\n" (s/explain ::service spec)) {})) 94 | (-> starter-map 95 | (merge (:service-map conformed)) 96 | (add-routes (collect-routes conformed)) 97 | (add-startups (collect-startups conformed))))))) 98 | 99 | (defn execute-startups 100 | [service-map] 101 | (let [startups (map i/-interceptor (get service-map ::startups []))] 102 | (chain/execute service-map startups))) 103 | 104 | (defn start-service 105 | [service-map] 106 | (-> service-map 107 | execute-startups 108 | http/create-server 109 | http/start)) 110 | -------------------------------------------------------------------------------- /src/com/cognitect/vase/datomic.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.datomic 2 | (:require [datomic.api :as d] 3 | [io.rkn.conformity :as c] 4 | [io.pedestal.interceptor :as i])) 5 | 6 | (defn new-db-uri [] 7 | (str "datomic:mem://" (d/squuid))) 8 | 9 | (defn connect 10 | "Given a Datomic URI, attempt to create the database and connect to it, 11 | returning the connection." 12 | [uri] 13 | (d/create-database uri) 14 | (d/connect uri)) 15 | 16 | (defn normalize-norm-keys 17 | [norms] 18 | (reduce 19 | (fn [acc [k-title v-map]] 20 | (assoc acc 21 | k-title (reduce 22 | (fn [norm-acc [nk nv]] 23 | (assoc norm-acc 24 | (keyword (name nk)) nv)) 25 | {} v-map))) 26 | {} 27 | norms)) 28 | 29 | (defn ensure-schema 30 | [conn norms] 31 | (c/ensure-conforms conn (normalize-norm-keys norms))) 32 | 33 | (defn insert-datomic 34 | "Provide a Datomic conn and db in all incoming requests" 35 | [conn] 36 | (i/interceptor 37 | {:name ::insert-datomic 38 | :enter (fn [context] 39 | (-> context 40 | (assoc-in [:request :conn] conn) 41 | (assoc-in [:request :db] (d/db conn))))})) 42 | 43 | -------------------------------------------------------------------------------- /src/com/cognitect/vase/edn.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.edn 2 | (:require [clojure.edn :as edn] 3 | [clojure.java.io :as io] 4 | [clojure.string :as cstr]) 5 | (:import (java.io File))) 6 | 7 | (defn read 8 | "Converts an edn string into Clojure data. `args` are clojure.edn `opts` 9 | `:readers` defaults to `*data-readers*`" 10 | [string & args] 11 | (let [e (edn/read-string (merge {:readers *data-readers*} 12 | (apply hash-map args)) 13 | string)] 14 | (if (instance? clojure.lang.IObj e) 15 | (with-meta e {:vase/src string}) 16 | e))) 17 | 18 | (defn from-resource 19 | "Load an EDN resource file and read its contents. The only required argument 20 | is `file-path`, which is the path of a file relative the projects resources 21 | directory (`resources/` or, for tests, `test/resources/`). 22 | 23 | Optional arguments: 24 | 25 | * `fallback-path` - A \"default\" path to check if file-path is actually an 26 | empty string. Useful in places you load a `file-path` from a config and its 27 | value might be absent. 28 | * `process-path-fn` - The function to use for getting the URL of the file. By 29 | default uses `clojure.java.io/resource`." 30 | ([file-path] 31 | (from-resource file-path "" io/resource)) 32 | ([file-path fallback-path] 33 | (from-resource file-path fallback-path io/resource)) 34 | ([file-path fallback-path process-path-fn] 35 | (let [trimmed-path (or (not-empty (cstr/trim file-path)) 36 | (not-empty (cstr/trim fallback-path))) 37 | contents (some->> 38 | trimmed-path 39 | process-path-fn 40 | slurp)] 41 | (if contents 42 | (read contents) 43 | (throw (ex-info 44 | (str "Failed to read an EDN file: " file-path " :: trimmed to: " trimmed-path) 45 | {:file-path file-path 46 | :trimmed-path trimmed-path})))))) 47 | 48 | (defn from-file 49 | [file-path] 50 | (from-resource file-path "" (fn [^String x] (io/as-url (File. x))))) 51 | -------------------------------------------------------------------------------- /src/com/cognitect/vase/interceptor.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.interceptor 2 | (:require [io.pedestal.interceptor.helpers :as helpers :refer [defon-request]] 3 | [io.pedestal.interceptor :as i] 4 | [clojure.stacktrace :as ctrace] 5 | [clj-time.core :as clj-time] 6 | [com.cognitect.vase.util :as util])) 7 | 8 | (def request-id-header "vaserequest-id") 9 | 10 | (def attach-received-time 11 | (i/-interceptor 12 | {:name ::attach-received-time 13 | :doc "Attaches a timestamp to every request." 14 | :enter (fn [ctx] (assoc-in ctx [:request :received-time] (clj-time/now)))})) 15 | 16 | (def attach-request-id 17 | (i/-interceptor 18 | {:name ::attach-request-id 19 | :doc "Attaches a request ID to every request; 20 | If there's a 'request_id' header, it will use that value, otherwise it will generate a short hash" 21 | :enter (fn [{:keys [request] :as ctx}] 22 | (let [req-id (get-in request [:headers request-id-header] (util/short-hash))] 23 | (-> ctx 24 | (assoc-in [:request :request-id] req-id) 25 | (assoc-in [:request :headers request-id-header] req-id))))})) 26 | 27 | (defn forward-headers 28 | [headers] 29 | (i/-interceptor 30 | {:name ::forward-headers 31 | :doc "Given an interceptor name and list of headers to forward, 32 | return an interceptor that attaches those headers to reponses IFF 33 | they are in the request" 34 | :leave (fn [context] 35 | (update-in context [:response :headers] 36 | #(merge (select-keys (get-in context [:request :headers]) headers) %)))})) 37 | -------------------------------------------------------------------------------- /src/com/cognitect/vase/main.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.main 2 | (:require [com.cognitect.vase.try :as try :refer [try->]] 3 | [fern.easy :as fe] 4 | [com.cognitect.vase.fern :as fern] 5 | [com.cognitect.vase.api :as a]) 6 | (:gen-class)) 7 | 8 | (def vase-fern-url "https://github.com/cognitect-labs/vase/blob/master/docs/vase_and_fern.md") 9 | 10 | (def usage 11 | (str 12 | "Usage: vase _filename_\n\nVase takes exactly one filename, which must be in Fern format.\nSee " 13 | vase-fern-url 14 | " for details.")) 15 | 16 | (defn- parse-args 17 | [[filename & stuff]] 18 | (if (or (not filename) (not (empty? stuff))) 19 | (throw (ex-info usage {:filename filename})) 20 | {:filename filename})) 21 | 22 | (defn -main 23 | [& args] 24 | (let [file (try-> args 25 | parse-args 26 | (:! clojure.lang.ExceptionInfo ei (fe/print-other-exception ei)) 27 | :filename)] 28 | (when (and file (not= ::try/exit file)) 29 | (try-> file 30 | fern/load-from-file 31 | (:! java.io.FileNotFoundException fnfe (fe/print-error-message (str "File not found: " (pr-str (.getMessage fnfe))))) 32 | 33 | fern/prepare-service 34 | (:! Throwable t (fe/print-evaluation-exception t file)) 35 | 36 | a/start-service 37 | (:! Throwable t (fe/print-other-exception t file)))))) 38 | -------------------------------------------------------------------------------- /src/com/cognitect/vase/response.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.response) 2 | 3 | (defn- complete-with-errors? 4 | [response errors] 5 | (and (not (nil? response)) (seq errors))) 6 | 7 | (defn- bad-request? 8 | [response errors] 9 | (and (nil? response) (seq errors))) 10 | 11 | (defn- exception? 12 | [response] 13 | (instance? Throwable response)) 14 | 15 | (defn status-code 16 | [response errors] 17 | (cond 18 | (complete-with-errors? response errors) 205 19 | (bad-request? response errors) 400 20 | (exception? response) 500 21 | :else 200)) 22 | 23 | (defn response 24 | [body headers status] 25 | {:body (or body "") 26 | :headers (or headers {}) 27 | :status (or status 200)}) 28 | -------------------------------------------------------------------------------- /src/com/cognitect/vase/routes.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.routes 2 | (:require [io.pedestal.http :as http] 3 | [io.pedestal.interceptor :as i] 4 | [io.pedestal.http.body-params :as body-params] 5 | [com.cognitect.vase.datomic :as datomic] 6 | [com.cognitect.vase.interceptor :as interceptor] 7 | [clojure.string :as string])) 8 | 9 | (defn- describe-api 10 | "Return a list of all active routes. 11 | Optionally filter the list with the query param, `f`, which is a fuzzy match 12 | string value" 13 | [routes] 14 | (i/interceptor 15 | {:name (keyword "vase" (str (gensym "describe-api-"))) 16 | :enter (fn [context] 17 | (let [{:keys [f sep edn] 18 | :or {f "" sep "
" edn false}} (get-in context [:request :query-params]) 19 | results (mapv #(take 2 %) routes)] 20 | (assoc context :response 21 | (cond 22 | edn (http/edn-response results) 23 | (string/starts-with? sep "<") {:status 200 24 | :headers {"Content-Type" "text/html"} 25 | :body (string/join sep (map #(string/join " " %) results))} 26 | :else {:status 200 27 | :body (string/join sep (map #(string/join " " %) results))}))))})) 28 | 29 | (def ^:private common-api-interceptors 30 | [interceptor/attach-received-time 31 | interceptor/attach-request-id 32 | http/json-body]) 33 | 34 | (defn- app-interceptors 35 | [spec] 36 | (let [{:keys [descriptor activated-apis datomic-uri]} spec 37 | datomic-conn (datomic/connect datomic-uri) 38 | headers-to-forward (get-in descriptor [:vase/apis activated-apis :vase.api/forward-headers] []) 39 | headers-to-forward (conj headers-to-forward interceptor/request-id-header) 40 | version-interceptors (mapv i/interceptor (get-in descriptor [:vase/apis activated-apis :vase.api/interceptors] [])) 41 | base-interceptors (conj common-api-interceptors 42 | (datomic/insert-datomic datomic-conn) 43 | (body-params/body-params (body-params/default-parser-map :edn-options {:readers *data-readers*})) 44 | (interceptor/forward-headers headers-to-forward))] 45 | (into base-interceptors 46 | version-interceptors))) 47 | 48 | (defn- specified-routes 49 | [spec] 50 | (let [{:keys [activated-apis descriptor]} spec] 51 | (get-in descriptor [:vase/apis activated-apis :vase.api/routes]))) 52 | 53 | (defn- api-routes 54 | "Given a descriptor map, an app-name keyword, and a version keyword, 55 | return route vectors in Pedestal's tabular format. Routes will all be 56 | subordinated under `base`" 57 | [base spec] 58 | (let [common (app-interceptors spec)] 59 | (for [[path verb-map] (specified-routes spec) 60 | [verb action] verb-map 61 | :let [interceptors (if (vector? action) 62 | (into common (map i/interceptor action)) 63 | (conj common (i/interceptor action)))]] 64 | (if (= path "/") 65 | [(str base) verb interceptors] 66 | [(str base path) verb interceptors])))) 67 | 68 | (defn- api-base 69 | [api-root spec] 70 | (let [{:keys [activated-apis]} spec] 71 | (str api-root "/" (namespace activated-apis) "/" (name activated-apis)))) 72 | 73 | (defn- api-description-route-name 74 | [spec] 75 | (let [{:keys [activated-apis]} spec] 76 | (keyword (str (namespace activated-apis) 77 | "." (name activated-apis)) 78 | "describe"))) 79 | 80 | (defn api-description-route 81 | [api-root routes route-name] 82 | [api-root 83 | :get 84 | (conj common-api-interceptors (describe-api routes)) 85 | :route-name 86 | route-name]) 87 | 88 | (defn spec-routes 89 | "Return a seq of route vectors from a single specification" 90 | [api-root spec] 91 | (if (nil? (:activated-apis spec)) 92 | [] 93 | (let [app-version-root (api-base api-root spec) 94 | app-version-routes (api-routes app-version-root spec) 95 | app-api-route (api-description-route app-version-root 96 | app-version-routes 97 | (api-description-route-name spec))] 98 | (cons app-api-route app-version-routes)))) 99 | -------------------------------------------------------------------------------- /src/com/cognitect/vase/spec.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.spec 2 | "Contains the clojure.spec.alpha definitions for the Vase 3 | application specification." 4 | (:require [io.pedestal.interceptor :as interceptor] 5 | [clojure.spec.alpha :as s] 6 | [clojure.spec.gen.alpha :as gen])) 7 | 8 | ;; -- Predicates -- 9 | (defn valid-uri? 10 | "Returns true if v is a non-empty string representation of a uri." 11 | [v] 12 | (try 13 | (boolean (and (string? v) (not-empty v) (java.net.URI. v))) 14 | (catch java.net.URISyntaxException e false))) 15 | 16 | ;; -- Pedestal-specs -- 17 | (s/def ::interceptor #(satisfies? interceptor/IntoInterceptor %)) 18 | (s/def ::route-table-route (s/cat :path valid-uri? 19 | :verb #{:any :get :put :post :delete :patch :options :head} 20 | :handler (s/alt :fn fn? :interceptors (s/coll-of ::interceptor :kind vector?)) 21 | :route-name (s/? (s/cat :_ #(= :route-name %) :name keyword?)) 22 | :constraints (s/? (s/cat :_ #(= :constraints %) 23 | :constraints (s/map-of keyword? 24 | #(instance? java.util.regex.Pattern %)))))) 25 | (s/def ::route-table (s/* (s/spec ::route-table-route))) 26 | 27 | ;; -- Vase app-specs -- 28 | (s/def ::activated-apis (s/or :base keyword? 29 | :top-level (s/+ keyword?))) 30 | (s/def ::datomic-uri (s/with-gen (s/and string? valid-uri? #(.startsWith % "datomic")) 31 | #(gen/return (str "datomic:mem://" (java.util.UUID/randomUUID))))) 32 | 33 | ;; -- descriptor apis specs -- 34 | (s/def :vase.api/schemas (s/+ qualified-keyword?)) 35 | (s/def :vase.api/forward-headers (s/+ string?)) 36 | 37 | ;; -- routes -- 38 | (s/def ::get (s/or :one ::interceptor 39 | :many (s/+ ::interceptor))) 40 | (s/def ::put (s/or :one ::interceptor 41 | :many (s/+ ::interceptor))) 42 | (s/def ::post (s/or :one ::interceptor 43 | :many (s/+ ::interceptor))) 44 | (s/def ::delete (s/or :one ::interceptor 45 | :many (s/+ ::interceptor))) 46 | (s/def ::head (s/or :one ::interceptor 47 | :many (s/+ ::interceptor))) 48 | (s/def ::options (s/or :one ::interceptor 49 | :many (s/+ ::interceptor))) 50 | 51 | (s/def ::action (s/and (s/keys :opt-un [::get ::put ::post ::delete ::head ::options]) 52 | #(not-empty (select-keys % [:get :put :post :delete :head :options])))) 53 | (s/def :vase.api/routes (s/map-of valid-uri? ::action)) 54 | 55 | (s/def :vase.api/interceptors (s/+ ::interceptor)) 56 | 57 | (s/def :vase/apis (s/map-of qualified-keyword? 58 | (s/keys :req [:vase.api/routes] 59 | :opt [:vase.api/schemas 60 | :vase.api/forward-headers 61 | :vase.api/interceptors]))) 62 | 63 | ;; -- descriptor app specs -- 64 | ;; -- norms -- 65 | (s/def ::tx (s/* (s/or :_ vector? :_ map?))) 66 | (s/def :vase.norm/txes (s/* (s/spec ::tx))) 67 | (s/def :vase.norm/requires (s/* qualified-keyword?)) 68 | (s/def :vase/norms (s/map-of qualified-keyword? (s/keys :req [:vase.norm/txes] 69 | :opt [:vase.norm/requires]))) 70 | 71 | (s/def :vase/specs (s/map-of qualified-keyword? any?)) 72 | 73 | ;; -- The descriptor spec -- 74 | (s/def ::descriptor (s/keys :req [:vase/apis] 75 | :opt [:vase/norms 76 | :vase/specs])) 77 | 78 | ;; -- Vase app-spec -- 79 | (s/def ::spec (s/keys :req-un [::activated-apis ::descriptor ::datomic-uri])) 80 | -------------------------------------------------------------------------------- /src/com/cognitect/vase/try.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.try) 2 | 3 | (defmacro try-> 4 | "Thread the first expression through the remaining expressions. 5 | 6 | A form like (:? classname var ,,,) catches exceptions from 7 | the preceding code. It continues threading with the value of the 8 | body expressions as the replacement value. 9 | 10 | A form like (:! classname var ,,,) catches exceptions from the 11 | preceding code, but does _not_ continue threading. It aborts and the 12 | entire expression returns ::exit" 13 | [x & forms] 14 | (loop [x x 15 | forms forms] 16 | (if forms 17 | (let [form (first forms) 18 | threaded (cond 19 | (and (seq? form) (= :! (first form))) 20 | (with-meta `(try ~x (catch ~@(rest form) ::exit)) (meta form)) 21 | 22 | (and (seq? form) (= :? (first form))) 23 | (with-meta `(try ~x (catch ~@(rest form))) (meta form)) 24 | 25 | (seq? form) 26 | (with-meta `(let [v# ~x] 27 | (if (= ::exit v#) 28 | ::exit 29 | (~(first form) v# ~@(next form)))) (meta form)) 30 | 31 | :else 32 | `(let [v# ~x] 33 | (if (= ::exit v#) 34 | ::exit 35 | (~form v#))))] 36 | (recur threaded (next forms))) 37 | x))) 38 | -------------------------------------------------------------------------------- /src/com/cognitect/vase/util.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.util 2 | (:require [clojure.edn :as edn] 3 | [clojure.java.io :as io] 4 | [clojure.string :as cstr] 5 | [cheshire.core :as json] 6 | [cognitect.transit :as transit]) 7 | (:import (java.io ByteArrayInputStream 8 | FileInputStream 9 | File) 10 | (java.util Base64))) 11 | 12 | (defn map-vals 13 | [f m] 14 | (reduce-kv (fn [m k v] (assoc m k (f v))) m m)) 15 | 16 | (defn str->inputstream 17 | ([^String text] 18 | (str->inputstream text "UTF-8")) 19 | ([^String text ^String encoding] 20 | (ByteArrayInputStream. (.getBytes text encoding)))) 21 | 22 | (defn short-hash [] 23 | (subs 24 | (.encodeToString (Base64/getEncoder) 25 | (byte-array (loop [i 0 26 | ret (transient [])] 27 | (if (< i 8) 28 | (recur (inc i) (conj! ret (.byteValue ^Long (long (rand 100))))) 29 | (persistent! ret))))) 30 | 0 11)) 31 | 32 | ;; This function is useful when writing your own action literals, 33 | ;; allowing you to expand symbol names within the descriptors. 34 | ;; It's not used within the Vase source, but has been used on projects 35 | ;; built with Vase. 36 | (defn fully-qualify-symbol 37 | ([sym] (fully-qualify-symbol *ns* sym)) 38 | ([ns sym] 39 | (if-let [ns-alias? (namespace sym)] 40 | (let [ns-aliases (ns-aliases ns)] 41 | (if-let [fqns (ns-aliases (symbol ns-alias?))] 42 | (symbol (name (ns-name fqns)) (name sym)) 43 | sym)) 44 | sym))) 45 | 46 | (defn ensure-keyword [x] 47 | (cond 48 | (keyword? x) x 49 | (string? x) (keyword x) 50 | (symbol? x) (keyword (namespace x) (name x)) 51 | :else (keyword (str x)))) 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | (defn read-json 62 | "Converts json string to Clojure data. By default, keys are keywordized." 63 | [string & args] 64 | (apply json/parse-string string keyword args)) 65 | 66 | (defn write-json 67 | "Writes json string given Clojure data. By default, unicode is not escaped." 68 | [data & args] 69 | (json/generate-string data (apply hash-map args))) 70 | 71 | (defn read-transit-json 72 | [transit-json-str] 73 | (-> transit-json-str 74 | str->inputstream 75 | (transit/reader :json) 76 | transit/read)) 77 | -------------------------------------------------------------------------------- /src/data_readers.clj: -------------------------------------------------------------------------------- 1 | {vase/respond com.cognitect.vase.literals/respond 2 | vase/redirect com.cognitect.vase.literals/redirect 3 | vase/conform com.cognitect.vase.literals/conform 4 | vase/query com.cognitect.vase.literals/query 5 | vase/transact com.cognitect.vase.literals/transact 6 | vase/validate com.cognitect.vase.literals/validate 7 | vase/intercept com.cognitect.vase.literals/intercept 8 | vase/schema-tx com.cognitect.vase.literals/schema-tx 9 | 10 | vase.datomic/query com.cognitect.vase.literals/query 11 | vase.datomic/transact com.cognitect.vase.literals/transact 12 | vase.datomic.cloud/query com.cognitect.vase.literals/query-cloud 13 | vase.datomic.cloud/transact com.cognitect.vase.literals/transact-cloud} 14 | -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | # Project related 2 | generated-js/ 3 | 4 | # Java related 5 | pom.xml 6 | pom.xml.asc 7 | *jar 8 | *.class 9 | 10 | # Leiningen 11 | classes/ 12 | lib/ 13 | native/ 14 | checkouts/ 15 | target/ 16 | .lein-deps-sum 17 | .lein-failures 18 | .lein-repl-history 19 | .lein-cljsbuild-repl 20 | .lein-plugins/ 21 | repl-port 22 | .nrepl-port 23 | 24 | # Temp Files 25 | tmp/ 26 | *.orig 27 | *~ 28 | .*.swp 29 | .*.swo 30 | *.tmp 31 | *.bak 32 | 33 | # Editors (IntelliJ / Eclipse) 34 | */.idea 35 | */.classpath 36 | */.project 37 | */.settings 38 | 39 | # OS X 40 | .DS_Store 41 | 42 | # Logging 43 | *.log 44 | logs/ 45 | 46 | # Docs 47 | autodoc/ 48 | docs 49 | codox 50 | -------------------------------------------------------------------------------- /template/README.md: -------------------------------------------------------------------------------- 1 | # Pedestal + Vase Service Template 2 | 3 | Generate a new Pedestal Service with Vase hooked up. 4 | 5 | ## Usage 6 | 7 | To generate a new app: `lein new vase my-app`. You will have a new app in my-app! To 8 | explore further, read the readme in your generated app. 9 | 10 | ## Developer Notes 11 | 12 | There are two ways to try out local changes to this template: 13 | 14 | 1. Run `lein new vase NAME` in this directory. 15 | 2. `lein install` in this directory; ensure the correct version of the template is in :plugins of your 16 | `~/.lein/profiles.clj`; generate a new app. 17 | 18 | Test this template by running `lein test` from within the `template/` directory 19 | 20 | 21 | -------------------------------------------------------------------------------- /template/project.clj: -------------------------------------------------------------------------------- 1 | ; Copyright 2017 Cognitect, Inc. 2 | 3 | ; The use and distribution terms for this software are covered by the 4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0) 5 | ; which can be found in the file epl-v10.html at the root of this distribution. 6 | ; 7 | ; By using this software in any fashion, you are agreeing to be bound by 8 | ; the terms of this license. 9 | ; 10 | ; You must not remove this notice, or any other, from this software. 11 | 12 | (defproject vase/lein-template "0.9.3-SNAPSHOT" 13 | :description "A Pedestal Service + Vase template." 14 | :url "https://github.com/pedestal/pedestal" 15 | :scm "https://github.com/pedestal/pedestal" 16 | :license {:name "Eclipse Public License" 17 | :url "http://www.eclipse.org/legal/epl-v10.html"} 18 | :min-lein-version "2.0.0" 19 | :eval-in-leiningen true 20 | :test-selectors {:travis (complement :not-travis)}) 21 | -------------------------------------------------------------------------------- /template/src/leiningen/new/vase.clj: -------------------------------------------------------------------------------- 1 | ; Copyright 2017 Cognitect, Inc. 2 | 3 | ; The use and distribution terms for this software are covered by the 4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0) 5 | ; which can be found in the file epl-v10.html at the root of this distribution. 6 | ; 7 | ; By using this software in any fashion, you are agreeing to be bound by 8 | ; the terms of this license. 9 | ; 10 | ; You must not remove this notice, or any other, from this software. 11 | 12 | (ns leiningen.new.vase 13 | (:require [leiningen.new.templates :refer [renderer name-to-path ->files 14 | project-name sanitize-ns]])) 15 | 16 | (defn vase 17 | "A Pedestal service + Vase project template." 18 | [name & args] 19 | (let [render (renderer "vase") 20 | main-ns (sanitize-ns name) 21 | data {:raw-name name 22 | :name (project-name name) 23 | :namespace main-ns 24 | :sanitized (name-to-path main-ns)}] 25 | (println (str "Generating a Vase application called " name ".")) 26 | (->files data 27 | ["README.md" (render "README.md" data)] 28 | ["project.clj" (render "project.clj" data)] 29 | ["build.boot" (render "build.boot" data)] 30 | ["boot.properties" (render "boot.properties" data)] 31 | ["Capstanfile" (render "Capstanfile" data)] 32 | ["Dockerfile" (render "Dockerfile" data)] 33 | [".gitignore" (render ".gitignore" data)] 34 | ["src/{{sanitized}}/service.clj" (render "service.clj" data)] 35 | ["resources/{{namespace}}_service.fern" (render "vase_service.fern" data)] 36 | ["config/logback.xml" (render "logback.xml" data)] 37 | ["test/{{sanitized}}/service_test.clj" (render "service_test.clj" data)]))) 38 | -------------------------------------------------------------------------------- /template/src/leiningen/new/vase/.gitignore: -------------------------------------------------------------------------------- 1 | # Project related 2 | 3 | # Java related 4 | pom.xml 5 | pom.xml.asc 6 | *jar 7 | *.class 8 | 9 | # Leiningen 10 | classes/ 11 | lib/ 12 | native/ 13 | checkouts/ 14 | target/ 15 | .lein-* 16 | repl-port 17 | .nrepl-port 18 | .repl 19 | 20 | # Temp Files 21 | *.orig 22 | *~ 23 | .*.swp 24 | .*.swo 25 | *.tmp 26 | *.bak 27 | 28 | # OS X 29 | .DS_Store 30 | 31 | # Logging 32 | *.log 33 | /logs/ 34 | 35 | # Builds 36 | out/ 37 | build/ 38 | 39 | -------------------------------------------------------------------------------- /template/src/leiningen/new/vase/Capstanfile: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Name of the base image. Capstan will download this automatically from 4 | # Cloudius S3 repository. 5 | # 6 | #base: cloudius/osv 7 | base: cloudius/osv-openjdk8 8 | 9 | # 10 | # The command line passed to OSv to start up the application. 11 | # 12 | #cmdline: /java.so -cp /{{name}}/app.jar clojure.main -m {{namespace}} 13 | cmdline: /java.so -jar /{{name}}/app.jar 14 | 15 | # 16 | # The command to use to build the application. 17 | # You can use any build tool/command (make/rake/lein/boot) - this runs locally on your machine 18 | # 19 | # For Leiningen, you can use: 20 | #build: lein uberjar 21 | # For Boot, you can use: 22 | #build: boot build 23 | 24 | # 25 | # List of files that are included in the generated image. 26 | # 27 | files: 28 | /{{name}}/app.jar: ./target/{{name}}-0.0.1-SNAPSHOT-standalone.jar 29 | 30 | -------------------------------------------------------------------------------- /template/src/leiningen/new/vase/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8-alpine 2 | MAINTAINER Your Name 3 | 4 | ADD target/{{name}}-0.0.1-SNAPSHOT-standalone.jar /{{name}}/app.jar 5 | 6 | EXPOSE 8080 7 | 8 | CMD ["java", "-jar", "/{{name}}/app.jar"] 9 | -------------------------------------------------------------------------------- /template/src/leiningen/new/vase/README.md: -------------------------------------------------------------------------------- 1 | # {{name}} 2 | 3 | FIXME 4 | 5 | ## Getting Started 6 | 7 | 1. Start the application: `lein run resources/{{namespace}}_service.fern` 8 | 2. Read your app's source code at src/{{sanitized}}/service.clj. Explore the docs of functions 9 | that define routes and responses. 10 | 3. See your Vase API Specification at `resources/{{namespace}}_service.fern`. 11 | 4. Learn more! See the [Links section below](#links). 12 | 13 | 14 | ## Configuration 15 | 16 | To configure logging see config/logback.xml. By default, the app logs to stdout and logs/. 17 | To learn more about configuring Logback, read its [documentation](http://logback.qos.ch/documentation.html). 18 | 19 | 20 | ## Developing your service 21 | 22 | 1. Start a new REPL: `lein repl` 23 | 2. Start your service in dev-mode: `(def dev-serv (run-dev))` 24 | 3. Connect your editor to the running REPL session. 25 | Re-evaluated code will be seen immediately in the service. 26 | 4. All changes to your Vase Service Descriptor will be loaded - no re-evaluation 27 | needed. 28 | 29 | ### [Docker](https://www.docker.com/) container support 30 | 31 | 1. Build an uberjar of your service: `lein uberjar` 32 | 2. Build a Docker image: `sudo docker build -t {{name}} .` 33 | 3. Run your Docker image: `docker run -p 8080:8080 {{name}}` 34 | 35 | ### [OSv](http://osv.io/) unikernel support with [Capstan](http://osv.io/capstan/) 36 | 37 | 1. Build and run your image: `capstan run -f "8080:8080"` 38 | 39 | Once the image it built, it's cached. To delete the image and build a new one: 40 | 41 | 1. `capstan rmi {{name}}; capstan build` 42 | 43 | 44 | ## Links 45 | 46 | * [Pedestal examples](https://github.com/pedestal/samples) 47 | * [Vase examples](https://github.com/cognitect-labs/vase/samples) 48 | -------------------------------------------------------------------------------- /template/src/leiningen/new/vase/boot.properties: -------------------------------------------------------------------------------- 1 | #http://boot-clj.com 2 | BOOT_CLOJURE_NAME=org.clojure/clojure 3 | BOOT_CLOJURE_VERSION=1.9.0 4 | -------------------------------------------------------------------------------- /template/src/leiningen/new/vase/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 12 | 13 | 14 | 15 | 16 | logs/{{name}}-%d{yyyy-MM-dd}.%i.log 17 | 18 | 64 MB 19 | 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | %-5level %logger{36} - %msg%n 31 | 32 | 33 | 34 | INFO 35 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /template/src/leiningen/new/vase/project.clj: -------------------------------------------------------------------------------- 1 | (defproject {{raw-name}} "0.0.1-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.9.0"] 7 | [io.pedestal/pedestal.service "0.5.4"] 8 | [com.cognitect/pedestal.vase "0.9.4-SNAPSHOT"] 9 | 10 | ;; Remove this line and uncomment one of the next lines to 11 | ;; use Immutant or Tomcat instead of Jetty: 12 | [io.pedestal/pedestal.jetty "0.5.4"] 13 | ;; [io.pedestal/pedestal.immutant "0.5.4"] 14 | ;; [io.pedestal/pedestal.tomcat "0.5.4"] 15 | 16 | [ch.qos.logback/logback-classic "1.2.3" :exclusions [org.slf4j/slf4j-api]] 17 | [org.slf4j/jul-to-slf4j "1.7.25"] 18 | [org.slf4j/jcl-over-slf4j "1.7.25"] 19 | [org.slf4j/log4j-over-slf4j "1.7.25"]] 20 | :min-lein-version "2.0.0" 21 | :resource-paths ["config", "resources"] 22 | ;; If you use HTTP/2 or ALPN, use the java-agent to pull in the correct alpn-boot dependency 23 | ;:java-agents [[org.mortbay.jetty.alpn/jetty-alpn-agent "2.0.3"]] 24 | :profiles {:dev {:aliases {"run-dev" ["trampoline" "run" "-m" "{{namespace}}.server/run-dev"]} 25 | :dependencies [[io.pedestal/pedestal.service-tools "0.5.4"]]} 26 | :uberjar {:aot [{{namespace}}.service]}} 27 | :main ^{:skip-aot true} {{namespace}}.service) 28 | -------------------------------------------------------------------------------- /template/src/leiningen/new/vase/service.clj: -------------------------------------------------------------------------------- 1 | (ns {{namespace}}.service 2 | (:require [com.cognitect.vase.try :as try :refer [try->]] 3 | [fern.easy :as fe] 4 | [com.cognitect.vase.fern :as fern] 5 | [com.cognitect.vase.api :as a] 6 | [io.pedestal.http :s server] 7 | [clojure.java.io :as io]) 8 | (:gen-class)) 9 | 10 | (defn run-server 11 | [filename & {:as opts}] 12 | (try-> filename 13 | fern/load-from-file 14 | (:! java.io.FileNotFoundException fnfe (fe/print-error-message (str "File not found: " (pr-str (.getMessage fnfe))))) 15 | 16 | fern/prepare-service 17 | (:! Throwable t (fe/print-evaluation-exception t filename)) 18 | 19 | (merge opts) 20 | a/start-service 21 | (:! Throwable t (fe/print-other-exception t filename)))) 22 | 23 | (defn run-dev [] 24 | (run-server (io/resource "{{namespace}}_service.fern") :io.pedestal.http/join? false)) 25 | 26 | (def vase-fern-url "https://github.com/cognitect-labs/vase/blob/master/docs/vase_and_fern.md") 27 | 28 | (def usage 29 | (str 30 | "Usage: vase _filename_\n\nVase takes exactly one filename, which must be in Fern format.\nSee " 31 | vase-fern-url 32 | " for details.")) 33 | 34 | (defn- parse-args 35 | [[filename & stuff]] 36 | (if (or (not filename) (not (empty? stuff))) 37 | (throw (ex-info usage {:filename filename})) 38 | {:filename filename})) 39 | 40 | (defn -main 41 | [& args] 42 | (let [file (try-> args 43 | parse-args 44 | (:! clojure.lang.ExceptionInfo ei (fe/print-other-exception ei)) 45 | :filename)] 46 | (when (and file (not= ::try/exit file)) 47 | (run-server file)))) 48 | -------------------------------------------------------------------------------- /template/src/leiningen/new/vase/service_test.clj: -------------------------------------------------------------------------------- 1 | (ns {{namespace}}.service-test 2 | (:require [{{namespace}}.service :as service] 3 | [clojure.test :refer :all])) 4 | -------------------------------------------------------------------------------- /template/src/leiningen/new/vase/vase_service.fern: -------------------------------------------------------------------------------- 1 | {vase/service (fern/lit vase/service 2 | {:apis [] 3 | :service-map @http-options}) 4 | http-options {:io.pedestal.http/port 8080 5 | :io.pedestal.http/type :jetty 6 | :io.pedestal.http/resource-path "public"}} 7 | -------------------------------------------------------------------------------- /template/test/com/cognitect/vase/new_server_integration_test.clj: -------------------------------------------------------------------------------- 1 | ; Copyright 2013 Relevance, Inc. 2 | ; Copyright 2014-2017 Cognitect, Inc. 3 | 4 | ; The use and distribution terms for this software are covered by the 5 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0) 6 | ; which can be found in the file epl-v10.html at the root of this distribution. 7 | ; 8 | ; By using this software in any fashion, you are agreeing to be bound by 9 | ; the terms of this license. 10 | ; 11 | ; You must not remove this notice, or any other, from this software. 12 | 13 | (ns com.cognitect.vase.new-server-integration-test 14 | (:require [clojure.test :refer :all] 15 | [clojure.java.shell :as sh] 16 | [clojure.string :as string] 17 | [clojure.java.io :as io])) 18 | 19 | (def lein (or (System/getenv "LEIN_CMD") "lein")) 20 | 21 | (def project-dir (io/file ".")) 22 | 23 | ;; This code was heavily inspired by Overtone's version, thanks! 24 | ;; https://github.com/overtone/overtone/blob/e3de1f7ac59af7fa3cf75d696fbcfc2a15830594/src/overtone/helpers/file.clj#L360 25 | (defn mk-tmp-dir! 26 | "Creates a unique temporary directory on the filesystem. Typically in /tmp on 27 | *NIX systems. Returns a File object pointing to the new directory. Raises an 28 | exception if the directory couldn't be created after 10000 tries." 29 | [] 30 | (let [base-dir (io/file (System/getProperty "java.io.tmpdir")) 31 | base-name (str (java.util.UUID/randomUUID)) 32 | tmp-base (str base-dir java.io.File/separator base-name) 33 | max-attempts 100] 34 | (loop [num-attempts 1] 35 | (if (= num-attempts max-attempts) 36 | (throw (Exception. (str "Failed to create temporary directory after " max-attempts " attempts."))) 37 | (let [tmp-dir-name (str tmp-base num-attempts) 38 | tmp-dir (io/file tmp-dir-name)] 39 | (if (.mkdir tmp-dir) 40 | tmp-dir 41 | (recur (inc num-attempts)))))))) 42 | 43 | (def tempdir (mk-tmp-dir!)) 44 | 45 | (defn- sh-exits-successfully 46 | [full-app-name & args] 47 | (let [sh-result (sh/with-sh-dir full-app-name (apply sh/sh args))] 48 | (println (:out sh-result)) 49 | (is (zero? (:exit sh-result)) 50 | (format "Expected `%s` to exit successfully" (string/join " " args))))) 51 | 52 | (deftest ^:not-travis generated-app-has-correct-files 53 | (let [app-name "test-app" 54 | full-app-name (.getPath (io/file tempdir app-name))] 55 | (println (sh/with-sh-dir project-dir (sh/sh lein "install"))) 56 | (println (sh/with-sh-dir tempdir (sh/sh lein "new" "vase" app-name "--snapshot"))) 57 | (println "Created app at" full-app-name) 58 | (is (.exists (io/file full-app-name "project.clj"))) 59 | (is (.exists (io/file full-app-name "README.md"))) 60 | (is (.exists (io/file full-app-name "src" "test_app" "service.clj"))) 61 | (sh-exits-successfully full-app-name lein "test") 62 | (sh-exits-successfully full-app-name lein "with-profile" "production" "compile" ":all") 63 | (sh/sh "rm" "-rf" full-app-name))) 64 | 65 | (deftest ^:not-travis generated-app-with-namespace-has-correct-files 66 | (let [app-name "pedestal.test/test-ns-app" 67 | full-app-name (.getPath (io/file tempdir "test-ns-app"))] 68 | (println (sh/with-sh-dir project-dir (sh/sh lein "install"))) 69 | (println (sh/with-sh-dir tempdir (sh/sh lein "new" "vase" app-name))) 70 | (println "Created app at" full-app-name) 71 | (is (.exists (io/file full-app-name "project.clj"))) 72 | (is (.exists (io/file full-app-name "README.md"))) 73 | (is (.exists (io/file full-app-name "src" "pedestal" "test" "test_ns_app" "service.clj"))) 74 | (sh-exits-successfully full-app-name lein "test") 75 | (sh-exits-successfully full-app-name lein "with-profile" "production" "compile" ":all") 76 | (sh/sh "rm" "-rf" full-app-name))) 77 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/actions_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.actions-test 2 | (:require [clojure.test :refer :all] 3 | [com.cognitect.vase.test-helper :as helper] 4 | [io.pedestal.interceptor :as interceptor] 5 | [com.cognitect.vase.actions :as actions])) 6 | 7 | (defn expect-response 8 | [actual status body headers] 9 | (is (= status (:status actual))) 10 | (is (= body (:body actual))) 11 | (is (= headers (select-keys (:headers actual) (keys headers))))) 12 | 13 | (defn with-query-params 14 | ([p] 15 | (with-query-params {} p)) 16 | ([ctx p] 17 | (-> ctx 18 | (update-in [:request :query-params] merge p) 19 | (update-in [:request :params] merge p)))) 20 | 21 | (defn execute-and-expect 22 | ([action status body headers] 23 | (expect-response (:response (helper/run-interceptor action)) status body headers)) 24 | ([ctx action status body headers] 25 | (expect-response (:response (helper/run-interceptor ctx action)) status body headers))) 26 | 27 | (deftest dynamic-interceptor-creation 28 | (testing "at least one function is required" 29 | (is (thrown? AssertionError (actions/dynamic-interceptor :without-functions [] {}))) 30 | 31 | (are [x] (interceptor/interceptor? x) 32 | (actions/dynamic-interceptor :enter-only [] {:enter (fn [ctx] ctx)}) 33 | (actions/dynamic-interceptor :enter-only [] {:enter identity}) 34 | (actions/dynamic-interceptor :leave-only [] {:leave identity}) 35 | (actions/dynamic-interceptor :error-only [] {:error identity})))) 36 | 37 | (deftest builtin-actions-are-interceptors 38 | (are [x] (interceptor/interceptor? (interceptor/-interceptor x)) 39 | (actions/map->RespondAction {}) 40 | (actions/map->RedirectAction {}) 41 | (actions/map->ValidateAction {}) 42 | (actions/map->QueryAction {}) 43 | (actions/map->TransactAction {}))) 44 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/conform_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.conform-test 2 | (:require [clojure.test :refer :all] 3 | [com.cognitect.vase.actions :as actions] 4 | [com.cognitect.vase.test-helper :as helper] 5 | [clojure.spec.alpha :as s] 6 | [io.pedestal.interceptor :as i])) 7 | 8 | (s/def ::a string?) 9 | (s/def ::b boolean?) 10 | (s/def ::request-body (s/keys :req-un #{::a} :opt-un #{::b})) 11 | 12 | (defn make-conformer 13 | [from spec to explain-to] 14 | (i/-interceptor 15 | (actions/->ConformAction :conformer from spec to explain-to ""))) 16 | 17 | (deftest conform-action 18 | (testing "Happy path" 19 | (is (not= ::s/invalid 20 | (-> {:query-data {:a 1 :b true}} 21 | (helper/run-interceptor (make-conformer :query-data ::request-body :shaped nil)) 22 | (get :shaped))))) 23 | 24 | (testing "Non-conforming inputs" 25 | (is (= ::s/invalid 26 | (-> {:query-data {:a 1 :b "string-not-allowed"}} 27 | (helper/run-interceptor (make-conformer :query-data ::request-body :shaped nil)) 28 | (get :shaped))))) 29 | 30 | (testing "Explain goes to :com.cognitect.vase.actions/explain-data by default" 31 | (is (not 32 | (nil? 33 | (-> {:query-data {:a 1 :b "string-not-allowed"}} 34 | (helper/run-interceptor (make-conformer :query-data ::request-body :shaped nil)) 35 | (get :com.cognitect.vase.actions/explain-data)))))) 36 | 37 | 38 | (testing "The 'from' part can be a vector to get nested data out of the context" 39 | (is (= ::s/invalid 40 | (-> {:context {:request {:query-data {:a 1 :b "string-not-allowed"}}}} 41 | (helper/run-interceptor (make-conformer [:context :request :query-data] ::request-body :shaped nil)) 42 | (get :shaped)))) 43 | 44 | (is (= {:path [:b] :pred `boolean? :val "string-not-allowed" :via [::request-body ::b] :in [:b]} 45 | (-> {:context {:request {:query-data {:a 1 :b "string-not-allowed"}}}} 46 | (helper/run-interceptor (make-conformer [:context :request :query-data] ::request-body :shaped nil)) 47 | :com.cognitect.vase.actions/explain-data 48 | :clojure.spec.alpha/problems 49 | first))))) 50 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/interceptor_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.interceptor-test 2 | (:require [clojure.test :refer :all] 3 | [com.cognitect.vase.test-helper :as helper :refer [run-interceptor new-ctx]] 4 | [com.cognitect.vase.interceptor :refer :all])) 5 | 6 | (deftest stock-interceptors 7 | (testing "attach-received-time" 8 | (is (some-> (run-interceptor attach-received-time) :request :received-time))) 9 | 10 | (testing "attach-request-id" 11 | (testing "default request id is supplied" 12 | (is (some-> (run-interceptor attach-request-id) :request :request-id))) 13 | 14 | (testing "an explicit request id is returned" 15 | (let [original-id "1234554321" 16 | ctx (new-ctx "vaserequest-id" "1234554321") 17 | resulting-id (some-> (run-interceptor ctx attach-request-id) :request :request-id)] 18 | (is (= original-id resulting-id))))) 19 | 20 | (testing "forward-headers" 21 | (let [fh (forward-headers ["vaserequest-id" "custom-header"]) 22 | ctx (new-ctx "vaserequest-id" "1234554321" "custom-header" "Any string value") 23 | result (run-interceptor ctx fh)] 24 | (is (= "1234554321" (some-> result :response :headers (get "vaserequest-id")))) 25 | (is (= "Any string value" (some-> result :response :headers (get "custom-header"))))))) 26 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/query_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.query-test 2 | (:require [clojure.test :refer :all] 3 | [io.pedestal.interceptor :as interceptor] 4 | [com.cognitect.vase.actions :as actions] 5 | [com.cognitect.vase.test-helper :as helper] 6 | [com.cognitect.vase.test-db-helper :as db-helper] 7 | [clojure.spec.alpha :as s] 8 | [datomic.api :as d])) 9 | 10 | (defn empty-db-entity-count 11 | [] 12 | (let [conn (:connection (db-helper/new-database [])) 13 | db (d/db conn)] 14 | (count 15 | (d/q '[:find ?e ?v :where [?e :db/ident ?v]] db)))) 16 | 17 | (defn make-query 18 | [query variables coercions constants to] 19 | (interceptor/-interceptor 20 | (actions/->QueryAction :query variables query coercions constants {} to ""))) 21 | 22 | (defn- context-with-db [] 23 | (let [conn (db-helper/connection)] 24 | (-> (helper/new-ctx) 25 | (assoc-in [:request :db] (d/db conn)) 26 | (assoc-in [:request :conn] conn)))) 27 | 28 | (defn- with-path-params 29 | ([p] 30 | (with-path-params {} p)) 31 | ([ctx p] 32 | (-> ctx 33 | (update-in [:request :path-params] merge p) 34 | (update-in [:request :params] merge p)))) 35 | 36 | (deftest query-action 37 | (db-helper/with-database db-helper/query-test-txes 38 | (testing "Simple query, no parameters" 39 | (let [action (make-query '[:find ?e ?v :where [?e :db/ident ?v]] [] [] [] nil) 40 | response (:response (helper/run-interceptor (context-with-db) action)) 41 | query-results (:body response)] 42 | (is (vector? query-results)) 43 | (is (< (empty-db-entity-count) (count query-results))) 44 | (is (= '(2) (distinct (map count query-results)))))) 45 | 46 | (testing "with one coerced query parameter" 47 | (let [action (make-query '[:find ?id ?email :in $ ?id :where [?e :user/userId ?id] [?e :user/userEmail ?email]] '[id] '[id] [] nil) 48 | response (:response (helper/run-interceptor 49 | (with-path-params 50 | (context-with-db) 51 | {:id "100"}) 52 | action)) 53 | query-results (:body response)] 54 | (is (vector? query-results)) 55 | (is (< 0 (count query-results))))) 56 | 57 | (testing "with two coerced query parameters" 58 | (let [action (make-query '[:find ?id ?email :in $ ?id :where [?e :user/userId ?id] [?e :user/userEmail ?email]] '[id email] '[email id] [] nil) 59 | response (:response (helper/run-interceptor 60 | (with-path-params 61 | (context-with-db) 62 | {:id "100" 63 | :email "paul@cognitect.com"}) 64 | action)) 65 | query-results (:body response)] 66 | (is (vector? query-results)) 67 | (is (< 0 (count query-results))))) 68 | 69 | (testing "with scalar result" 70 | (let [action (make-query '[:find ?e . :in $ ?id :where [?e :user/userId ?id]] '[id] '[id] [] nil) 71 | response (:response (helper/run-interceptor 72 | (with-path-params 73 | (context-with-db) 74 | {:id "100"}) 75 | action)) 76 | query-result (:body response)] 77 | (is (number? query-result)))) 78 | 79 | (testing "with nil params" 80 | (let [action (make-query '[:find ?e :in $ ?id :where [?e :user/userId ?id]] '[id] '[id] [] nil) 81 | response (:response (helper/run-interceptor 82 | (with-path-params 83 | (context-with-db) 84 | {}) 85 | action)) 86 | query-results (:body response)] 87 | (is (string? query-results)) 88 | (is (re-matches #"Missing required query parameters.*" query-results)))) 89 | 90 | (testing "scalar query with no results" 91 | (let [action (make-query '[:find ?e . :in $ ?id :where [?e :user/userId ?id]] '[id] '[id] [] nil) 92 | response (:response (helper/run-interceptor 93 | (with-path-params 94 | (context-with-db) 95 | {:id 999}) 96 | action)) 97 | query-results (:body response)] 98 | (is (= nil (read-string {:eof nil} query-results))))))) 99 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/redirect_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.redirect-test 2 | (:require [clojure.test :refer :all] 3 | [io.pedestal.interceptor :as interceptor] 4 | [com.cognitect.vase.actions :as actions] 5 | [com.cognitect.vase.test-helper :as helper] 6 | [com.cognitect.vase.test-db-helper :as db-helper] 7 | [com.cognitect.vase.actions-test :refer [expect-response with-query-params execute-and-expect]] 8 | [clojure.spec.alpha :as s] 9 | [datomic.api :as d])) 10 | 11 | (defn make-redirect 12 | ([params status body headers url] 13 | (interceptor/-interceptor 14 | (actions/->RedirectAction :redirector params body status headers url)))) 15 | 16 | (deftest redirect-action 17 | (testing "static redirect" 18 | (are [status body headers action] (execute-and-expect action status body headers) 19 | 302 "" {"Location" "http://www.example.com"} (make-redirect [] 302 "" {} "http://www.example.com")) 20 | 21 | (are [headers action ctx] (execute-and-expect ctx action 302 "" headers) 22 | {"Location" "https://donotreply.com"} (make-redirect '[p1] 302 "" {} 'p1) (with-query-params {:p1 "https://donotreply.com"})))) 23 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/respond_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.respond-test 2 | (:require [clojure.test :refer :all] 3 | [io.pedestal.interceptor :as interceptor] 4 | [com.cognitect.vase.actions :as actions] 5 | [com.cognitect.vase.test-helper :as helper] 6 | [com.cognitect.vase.actions-test :refer [expect-response with-query-params execute-and-expect]])) 7 | 8 | (defn make-respond 9 | ([params coercions exprs] 10 | (make-respond params coercions exprs 200)) 11 | ([params coercions exprs status] 12 | (make-respond params coercions exprs status {})) 13 | ([params coercions exprs status headers] 14 | (interceptor/-interceptor 15 | (actions/->RespondAction :responder params coercions exprs status headers "")))) 16 | 17 | (deftest respond-action 18 | (testing "static response" 19 | (are [ status body headers action] (execute-and-expect action status body headers) 20 | 202 "respond-only" {} (make-respond [] [] "respond-only" 202) 21 | 201 "with-header" {"a" "b"} (make-respond [] [] "with-header" 201 {"a" "b"}) 22 | 200 "two-headers" {"a" "b" "c" "d"} (make-respond [] [] "two-headers" 200 {"a" "b" "c" "d"}))) 23 | 24 | (testing "with parameters" 25 | (are [expected-body action ctx] (execute-and-expect ctx action 200 expected-body {}) 26 | "p1: foo" (make-respond '[p1] [] '(str "p1: " p1)) (with-query-params {:p1 "foo"}) 27 | "p1: &" (make-respond '[p1] [] '(str "p1: " p1)) (with-query-params {:p1 "&"}) 28 | "foo.bar." (make-respond '[p1 zort] [] '(format "%s.%s." p1 zort)) (with-query-params {:p1 "foo" :zort "bar"}))) 29 | 30 | (testing "with parameters and defaults" 31 | (are [expected-body action ctx] (execute-and-expect ctx action 200 expected-body {}) 32 | "p1: foobar" (make-respond '[[p1 "foobar"]] [] '(str "p1: " p1)) (with-query-params {}) 33 | "p1: & p2: +" (make-respond '[p1 [p2 "+"]] [] '(str "p1: " p1 " p2: " p2)) (with-query-params {:p1 "&"}) 34 | "foo.bar." (make-respond '[[p1 "baz"] [zort "bar"]] [] '(format "%s.%s." p1 zort)) (with-query-params {:p1 "foo"}))) 35 | 36 | (testing "with parameters and coercions" 37 | (are [expected-body action ctx] (execute-and-expect ctx action 200 expected-body {}) 38 | "p1's foo: 1" (make-respond '[p1] '[p1] '(str "p1's foo: " (:foo p1))) (with-query-params {:p1 "{:foo 1}"}) 39 | "p1: A" (make-respond '[p1] '[p1] '(str "p1: " p1)) (with-query-params {:p1 "\"A\""}) 40 | "Sum: 3" (make-respond '[p1 zort] '[p1 zort] '(str "Sum: " (+ p1 zort))) (with-query-params {:p1 "1" :zort "2"})))) 41 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/service_route_table.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.service-route-table 2 | (:require [io.pedestal.http :as http] 3 | [io.pedestal.http.route :as route] 4 | [io.pedestal.http.route.definition.table :as table] 5 | [com.cognitect.vase :as vase]) 6 | (:import [java.util UUID])) 7 | 8 | (defn make-master-routes 9 | [spec] 10 | (table/table-routes 11 | {} 12 | (vase/routes "/api" spec))) 13 | 14 | (defn test-spec 15 | [] 16 | {:activated-apis [:example/v1 :example/v2] 17 | :descriptor (vase/load-edn-resource "test_descriptor.edn") 18 | :datomic-uri (str "datomic:mem://" (UUID/randomUUID))}) 19 | 20 | (defn service-map 21 | "Return a new, fully initialized service map" 22 | [] 23 | (let [{:keys [activated-apis 24 | descriptor 25 | datomic-uri] :as app-spec} (test-spec) 26 | conns (vase/ensure-schema app-spec)] 27 | (vase/specs app-spec) 28 | {:env :prod 29 | ::http/routes (make-master-routes app-spec) 30 | ::http/resource-path "/public" 31 | ::http/type :jetty 32 | ::http/port 8080})) 33 | 34 | (comment 35 | 36 | (service-map) 37 | (let [s (test-spec)] 38 | (vase.datomic/normalize-norm-keys (get-in s [:descriptor :vase/norms]))) 39 | 40 | (vase/routes "/api" (test-spec))) 41 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/service_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.service-test 2 | (:require [clojure.test :refer :all] 3 | [io.pedestal.test :refer :all] 4 | [com.cognitect.vase.test-helper :as helper] 5 | [com.cognitect.vase :as vase] 6 | [com.cognitect.vase.service-route-table :as srt])) 7 | 8 | (defn selected-headers 9 | "Return a map with selected-keys out of the headers of a request to url" 10 | [verb url selected-keys] 11 | (-> url 12 | verb 13 | :headers 14 | (select-keys selected-keys))) 15 | 16 | (deftest request-tracing-test 17 | (is (= 18 | (selected-headers #(helper/GET % :headers {"vaserequest-id" "yes"}) 19 | "/api/example/v1/hello" 20 | ["Content-Type" "vaserequest-id"]) 21 | {"Content-Type" "text/plain" 22 | "vaserequest-id" "yes"})) 23 | (is (string? (get-in (helper/GET "/api/example/v1/hello") [:headers "vaserequest-id"])))) 24 | 25 | (deftest self-describing-test 26 | (helper/with-service (srt/service-map) 27 | (is (= 200 (:status (helper/GET "/api?edn=true")))) 28 | (is (= 200 (:status (helper/GET "/api")))) 29 | (is (= 200 (:status (helper/GET "/api/example/v1?edn=true")))) 30 | (is (= 200 (:status (helper/GET "/api/example/v1")))) 31 | (is (= 200 (:status (helper/GET "/api/example/v2?edn=true")))) 32 | (is (= 200 (:status (helper/GET "/api/example/v2")))))) 33 | 34 | (def known-route-names 35 | #{:describe-apis 36 | :example.v1/describe 37 | :example.v1/simple-response 38 | :example.v1/r-page 39 | :example.v1/ar-page 40 | :example.v1/url-param-example 41 | :example.v1/validate-page 42 | :example.v1/db-page 43 | :example.v1/users-page 44 | :example.v1/user-id-page 45 | :example.v1/user-create 46 | :example.v1/user-delete 47 | :example.v1/user-page 48 | :example.v1/fogus-page 49 | :example.v1/foguspaul-page 50 | :example.v1/fogussomeone-page 51 | :example.v2/describe 52 | :example.v2/hello 53 | :example.v2/intercept}) 54 | 55 | (deftest all-route-names-present 56 | (let [service (srt/service-map) 57 | routes (:io.pedestal.http/routes service) 58 | route-names (set (map :route-name routes))] 59 | (is (= known-route-names route-names)))) 60 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/spec_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.spec-test 2 | (:require [com.cognitect.vase.spec :as vase.spec] 3 | [com.cognitect.vase :as vase] 4 | [com.cognitect.vase.service-route-table :as srt] 5 | [clojure.spec.alpha :as s] 6 | [clojure.spec.test.alpha :as stest] 7 | [clojure.test :refer :all] 8 | [io.pedestal.interceptor :as interceptor])) 9 | 10 | (deftest route-table-spec-tests 11 | (let [handler-fn (fn [req] {:status 200 :body "foo"})] 12 | (is (s/valid? ::vase.spec/route-table 13 | [["/a" :get handler-fn] 14 | ["/b" :get [(interceptor/interceptor {:enter handler-fn})]] 15 | ["/c" :get handler-fn :route-name :c] 16 | ["/d/:id" :get handler-fn :route-name :d :constraints {:id #"[0-9]+"}]])) 17 | (testing "invalid routes" 18 | (are [v] (not (s/valid? ::vase.spec/route-table-route v)) 19 | [] 20 | ["/a"] 21 | ["/a" :get] 22 | ["/a" :get handler-fn :route-name] 23 | ["/a" :get handler-fn :not-route-name-k :bar] 24 | ["/a" :get handler-fn :route-name :bar :constraints] 25 | ["/a" :get handler-fn :route-name :bar :constraints 1])))) 26 | 27 | (def test-spec (srt/test-spec)) 28 | (def sample-spec (vase/load-edn-resource "sample_payload.edn")) 29 | 30 | (deftest vase-spec-tests 31 | (testing "full vase spec" 32 | (doseq [vspec [test-spec sample-spec]] 33 | (is (s/valid? ::vase.spec/spec vspec)))) 34 | 35 | (testing "descriptors" 36 | (doseq [d ["sample_descriptor.edn" 37 | "small_descriptor.edn"]] 38 | (is (s/valid? ::vase.spec/descriptor (vase/load-edn-resource d)) 39 | (format "%s is not valid!" d))))) 40 | 41 | (use-fixtures :once (fn [f] 42 | (stest/instrument `vase/routes) 43 | (f) 44 | (stest/unstrument `vase/routes))) 45 | 46 | (deftest vase-routes-fn-tests 47 | (is (vase/routes "/api" test-spec)) 48 | (is (vase/routes "/api" [])) 49 | (is (vase/routes "/api" [test-spec])) 50 | (is (vase/routes "/api" [test-spec sample-spec])) 51 | (is (thrown-with-msg? clojure.lang.ExceptionInfo #"did not conform" (vase/routes "" test-spec))) 52 | (is (thrown-with-msg? clojure.lang.ExceptionInfo #"did not conform" (vase/routes :not-a-path test-spec))) 53 | (is (thrown-with-msg? clojure.lang.ExceptionInfo #"did not conform" (vase/routes "/api" (:descriptor test-spec))))) 54 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/test_db_helper.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.test-db-helper 2 | (:require [clojure.test :as t] 3 | [datomic.api :as d]) 4 | (:import [java.util UUID])) 5 | 6 | (defn new-database 7 | "This generates a new, empty Datomic database for use within unit tests." 8 | [txes] 9 | (let [uri (str "datomic:mem://test" (UUID/randomUUID)) 10 | _ (d/create-database uri) 11 | conn (d/connect uri)] 12 | (doseq [t txes] 13 | @(d/transact conn t)) 14 | {:uri uri 15 | :connection conn})) 16 | 17 | (def ^:dynamic *current-db-connection* nil) 18 | (def ^:dynamic *current-db-uri* nil) 19 | 20 | (defmacro with-database 21 | "Executes all requests in the body with the same database." 22 | [txes & body] 23 | `(let [dbm# (new-database ~txes)] 24 | (binding [*current-db-uri* (:uri dbm#) 25 | *current-db-connection* (:connection dbm#)] 26 | ~@body))) 27 | 28 | (defn connection 29 | ([] 30 | (or *current-db-connection* (:connection (new-database [])))) 31 | ([txes] 32 | (or *current-db-connection* (:connection (new-database txes))))) 33 | 34 | 35 | (def query-test-txes 36 | [[{:db/id #db/id[:db.part/db] 37 | :db/ident :company/name 38 | :db/unique :db.unique/value 39 | :db/valueType :db.type/string 40 | :db/cardinality :db.cardinality/one 41 | :db.install/_attribute :db.part/db} 42 | {:db/id #db/id[:db.part/db] 43 | :db/ident :user/userId 44 | :db/valueType :db.type/long 45 | :db/cardinality :db.cardinality/one 46 | :db.install/_attribute :db.part/db 47 | :db/doc "A Users unique identifier" 48 | :db/unique :db.unique/identity} 49 | {:db/id #db/id[:db.part/db] 50 | :db/ident :user/userEmail 51 | :db/valueType :db.type/string 52 | :db/cardinality :db.cardinality/one 53 | :db.install/_attribute :db.part/db 54 | :db/doc "The users email" 55 | :db/unique :db.unique/value} 56 | {:db/id #db/id[:db.part/db] 57 | :db/ident :user/userBio 58 | :db/valueType :db.type/string 59 | :db/cardinality :db.cardinality/one 60 | :db.install/_attribute :db.part/db 61 | :db/doc "A short blurb about the user" 62 | :db/fulltext true :db/index true} 63 | {:db/id #db/id[:db.part/db] 64 | :db/ident :user/userActive? 65 | :db/valueType :db.type/boolean 66 | :db/cardinality :db.cardinality/one 67 | :db.install/_attribute :db.part/db 68 | :db/doc "User active flag." 69 | :db/index true} 70 | {:db/id #db/id[:db.part/db] 71 | :db/ident :loanOffer/loanId 72 | :db/valueType :db.type/long 73 | :db/cardinality :db.cardinality/one 74 | :db.install/_attribute :db.part/db 75 | :db/doc "The unique offer ID" 76 | :db/unique :db.unique/value} 77 | {:db/id #db/id[:db.part/db] 78 | :db/ident :loanOffer/fees 79 | :db/valueType :db.type/long 80 | :db/cardinality :db.cardinality/one 81 | :db.install/_attribute :db.part/db 82 | :db/doc "All of the loan fees" 83 | :db/index true} 84 | {:db/id #db/id[:db.part/db] 85 | :db/ident :loanOffer/notes 86 | :db/valueType :db.type/string 87 | :db/cardinality :db.cardinality/many 88 | :db.install/_attribute :db.part/db 89 | :db/doc "Notes about the loan"} 90 | {:db/id #db/id[:db.part/db] 91 | :db/ident :user/loanOffers 92 | :db/valueType :db.type/ref 93 | :db/cardinality :db.cardinality/many 94 | :db.install/_attribute :db.part/db 95 | :db/doc "The collection of loan offers"}] 96 | [{:db/id #db/id[:db.part/user] 97 | :user/userId 100 98 | :user/userEmail "paul@cognitect.com"} 99 | {:db/id #db/id[:db.part/user] 100 | :user/userId 101 101 | :user/userEmail "fogus@cognitect.com"} 102 | {:db/id #db/id[:db.part/user] 103 | :user/userId 102 104 | :user/userEmail "mtnygard@cognitect.com"}]]) 105 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/test_helper.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.test-helper 2 | (:require [io.pedestal.test :refer [response-for]] 3 | [io.pedestal.http :as http] 4 | [io.pedestal.log :as log] 5 | [com.cognitect.vase.interceptor :as interceptor] 6 | [com.cognitect.vase.util :as util] 7 | [com.cognitect.vase.service-route-table :as srt] 8 | [io.pedestal.interceptor.chain :as chain])) 9 | 10 | (def write-edn pr-str) 11 | 12 | (defn new-service 13 | "This generates a new testable service for use with io.pedestal.test/response-for." 14 | ([] (new-service (srt/service-map))) 15 | ([service-map] (::http/service-fn (http/create-servlet service-map)))) 16 | 17 | (def ^:dynamic *current-service* nil) 18 | 19 | (defmacro with-service 20 | "Executes all requests in the body with the same service (using a thread-local binding)" 21 | [srv-map & body] 22 | `(binding [*current-service* (new-service ~srv-map)] 23 | ~@body)) 24 | 25 | (defn service 26 | [& args] 27 | (or *current-service* (apply new-service args))) 28 | 29 | (defn GET 30 | "Make a GET request on our service using response-for." 31 | [& args] 32 | (apply response-for (service) :get args)) 33 | 34 | (defn POST 35 | "Make a POST request on our service using response-for." 36 | [& args] 37 | (apply response-for (service) :post args)) 38 | 39 | (defn DELETE 40 | "Make a DELETE request on our service using response-for." 41 | [& args] 42 | (apply response-for (service) :delete args)) 43 | 44 | (defn json-request 45 | ([verb url payload] 46 | (json-request verb url payload {})) 47 | ([verb url payload opts] 48 | (response-for (service) 49 | verb url 50 | :headers (merge {"Content-Type" "application/json"} 51 | (:headers opts)) 52 | :body (util/write-json payload)))) 53 | 54 | (defn post-json 55 | "Makes a POST request to URL-path expecting a payload to submit as JSON. 56 | 57 | Options: 58 | * :headers: Additional headers to send with the request." 59 | ([URL-path payload] 60 | (post-json URL-path payload {})) 61 | ([URL-path payload opts] 62 | (json-request :post URL-path payload opts))) 63 | 64 | (defn post-edn 65 | "Makes a POST request to URL-path expecting a payload to submit as edn. 66 | 67 | Options: 68 | * :headers: Additional headers to send with the request." 69 | ([URL-path payload] 70 | (post-edn URL-path payload {})) 71 | ([URL-path payload opts] 72 | (response-for (service) 73 | :post URL-path 74 | :headers (merge {"Content-Type" "application/edn"} 75 | (:headers opts)) 76 | :body (write-edn payload)))) 77 | 78 | (defn response-data 79 | "Return the parsed payload data from a vase api http response." 80 | ([response] (response-data response util/read-json)) 81 | ([response reader] 82 | (-> response 83 | :body 84 | reader))) 85 | 86 | (defn run-interceptor 87 | ([i] (run-interceptor {} i)) 88 | ([ctx i] (chain/execute (chain/enqueue* ctx i)))) 89 | 90 | (defn new-ctx 91 | [& {:as headers}] 92 | {:request {:headers headers}}) 93 | -------------------------------------------------------------------------------- /test/com/cognitect/vase/validate_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.cognitect.vase.validate-test 2 | (:require [clojure.test :refer :all] 3 | [io.pedestal.interceptor :as interceptor] 4 | [com.cognitect.vase.actions :as actions] 5 | [com.cognitect.vase.test-helper :as helper] 6 | [com.cognitect.vase.test-db-helper :as db-helper] 7 | [com.cognitect.vase.actions-test :refer [expect-response with-query-params execute-and-expect]] 8 | [clojure.spec.alpha :as s])) 9 | 10 | (defn make-validate 11 | [spec] 12 | (interceptor/-interceptor 13 | (actions/->ValidateAction :validator [] {} spec nil ""))) 14 | 15 | (defn- with-body [body] 16 | (assoc-in (helper/new-ctx) [:request :edn-params] body)) 17 | 18 | (s/def ::a string?) 19 | (s/def ::b boolean?) 20 | (s/def ::request-body (s/keys :req-un #{::a} :opt-un #{::b})) 21 | 22 | (deftest validate-action 23 | (testing "Passing validation" 24 | (are [body-out body-in action] (execute-and-expect (with-body body-in) action 200 body-out {}) 25 | '() {:a "one"} (make-validate `(s/keys :req-un #{::a} :opt-un #{::b})) 26 | '() {:a "one" :b false} (make-validate `(s/keys :req-un #{::a} :opt-un #{::b})) 27 | '() {:a "one" :b false} (make-validate `::request-body))) 28 | 29 | (testing "Failing validation" 30 | (are [body-out body-in action] (execute-and-expect (with-body body-in) action 200 body-out {}) 31 | '({:path [] :val {} :via [] :in []}) {} (make-validate `(s/keys :req-un #{::a} :opt-un #{::b})) 32 | '({:path [] :val {:b 12345} :via [] :in []} 33 | {:path [:b] :val 12345 :via [::b] :in [:b]}) {:b 12345} (make-validate `(s/keys :req-un #{::a} :opt-un #{::b})) 34 | '({:path [] :val {:b "false"} :via [::request-body] :in []} 35 | {:path [:b] :val "false" :via [::request-body ::b] :in [:b]}) {:b "false"} (make-validate `::request-body)))) 36 | -------------------------------------------------------------------------------- /test/resources/small_descriptor.edn: -------------------------------------------------------------------------------- 1 | 2 | ;; Idempotent Schema Datoms (norms) 3 | ;; -------------------------------- 4 | {:vase/norms 5 | {:example/base-schema 6 | ;; Supports full/long Datomic schemas 7 | {:vase.norm/txes [[{:db/id #db/id[:db.part/db] 8 | :db/ident :company/name 9 | :db/unique :db.unique/value 10 | :db/valueType :db.type/string 11 | :db/cardinality :db.cardinality/one 12 | :db.install/_attribute :db.part/db}]]} 13 | ;; End :example/base-schema 14 | 15 | :example/user-schema 16 | ;; Also supports schema dependencies 17 | {:vase.norm/requires [:example/base-schema] 18 | ;; and supports short/basic schema definitions 19 | :vase.norm/txes [#vase/schema-tx [[:user/userId :one :long :unique "A Users unique identifier"] 20 | [:user/userEmail :one :string :unique "The users email"] 21 | ;; :fulltext also implies :index 22 | [:user/userBio :one :string :fulltext "A short blurb about the user"]]]}} 23 | 24 | ;; API Tags/Versions 25 | ;; ------------------ 26 | :vase/apis 27 | {:example/v1 28 | {:vase.api/routes 29 | {"/hello" {:get #vase/respond {:name :example.v1/simple-response 30 | :body "Hello World"}} 31 | "/redirect-to-google" {:get #vase/redirect {:name :example-v1/r-page 32 | :url "http://www.google.com"}} 33 | "/capture-s/:url-thing" {:get #vase/respond {:name :example-v1/url-param-example 34 | ;; URL parameters are also bound in :params 35 | :params [url-thing] 36 | :edn-coerce [url-thing] ;; parse a param as an edn string 37 | :body (str "You said: " url-thing " which is a " (type url-thing))}} 38 | "/users" {:get #vase/query {:name :example.v1/user-page 39 | :params [email] 40 | :query [:find ?e 41 | :in $ ?email 42 | :where 43 | [?e :user/userEmail ?email]]} 44 | :post #vase/transact {:name :example.v1/user-create 45 | :properties [:db/id 46 | :user/userId 47 | :user/userEmail 48 | :user/userBio]}} 49 | "/users/:id" {:get #vase/query {:name :example.v1/user-id-page 50 | :params [id] 51 | :edn-coerce [id] 52 | :query [:find ?e 53 | :in $ ?id 54 | :where 55 | [?e :user/userId ?id]]}}} 56 | :vase.api/schemas [:example/user-schema] 57 | :vase.api/forward-headers ["vaserequest-id"]} 58 | :example/v2 59 | {:vase.api/routes 60 | {"/hello" {:get #vase/respond {:name :example.v2/hello 61 | :enforce-format true 62 | :body "Another Hello World Route"}}}}}} 63 | 64 | --------------------------------------------------------------------------------