Skip to content

Commit

Permalink
Use spec, expound for option validation
Browse files Browse the repository at this point in the history
  • Loading branch information
taylorwood committed Nov 20, 2018
1 parent f5cea70 commit 530a5a9
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 17 deletions.
47 changes: 43 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@ Uses deps.edn and [clj.native-image](https://github.com/taylorwood/clj.native-im

## Prerequisites

- GraalVM 1.0.0-RC7 or higher
- GraalVM 1.0.0-RC9 or higher _(may also work with RC7 or RC8)_
- Clojure

GraalVM 1.0.0-RC7 adds HTTPS as a supported protocol, and this is a brief walkthrough
GraalVM 1.0.0-RC7 added HTTPS as a supported protocol, and this is a brief walkthrough
for using it in a Clojure project with GraalVM Community Edition for macOS.

### Enable GraalVM HTTPS Support

This details the steps necessary to get HTTPS working with native-image.
You can disregard this section if you're using the pre-compiled image, but it
may be helpful for compiling this project with native-image or getting HTTPS
support working in another project.

1. Enable HTTPS protocol support with `native-image` options:
`--enable-https` or `--enable-url-protocols=https`
1. Configure path to `libsunec.dylib` on macOS (or `libsunec.do` on Linux)
Expand Down Expand Up @@ -68,15 +73,16 @@ Compile the program with GraalVM `native-image`:
$ clojure -A:native-image
```

CLI options:
Print CLI options:
```
$ ./clojurl -h
-u, --uri URI URI of request
-H, --header HEADER Request header(s)
-d, --data DATA Request data
-m, --method METHOD GET Request method e.g. GET, POST, etc.
-o, --output FORMAT edn Output format e.g. edn, hickory
-h, --help
-v, --verbose Print verbose info
-h, --help Print this message
```
Responses can be printed in EDN or Hickory format.

Expand Down Expand Up @@ -115,3 +121,36 @@ $ ./clojurl -H Accept=application/json -H X-Session-Id=1234 -H Content-Type=appl
:body
"{\"args\":{},\"data\":\"{'foo':true}\",\"files\":{},\"form\":{},\"headers\":{\"host\":\"postman-echo.com\",\"content-length\":\"12\",\"accept\":\"application/json\",\"accept-encoding\":\"gzip, deflate\",\"content-type\":\"application/json\",\"user-agent\":\"Java/1.8.0_172\",\"x-session-id\":\"1234\",\"x-forwarded-port\":\"443\",\"x-forwarded-proto\":\"https\"},\"json\":null,\"url\":\"https://postman-echo.com/post\"}"}
```

As a proof-of-concept for using Clojure 1.9 + clojure.spec.alpha + Expound with GraalVM native-image,
the CLI options are validated using specs and invalid options can be explained using Expound:
```
$ ./clojurl -u https://postman-echo.com/get -o foo --verbose
Invalid option(s)
-- Spec failed --------------------
{:headers ...,
:method ...,
:output-fn nil,
^^^
:url ...,
:verbose? ...}
should satisfy
ifn?
-- Relevant specs -------
:clojurl/output-fn:
clojure.core/ifn?
:clojurl/options:
(clojure.spec.alpha/keys
:req-un
[:clojurl/url :clojurl/output-fn]
:opt-un
[:clojurl/method :clojurl/headers :clojurl/body])
-------------------------
Detected 1 error
```
5 changes: 3 additions & 2 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{:deps {org.clojure/clojure {:mvn/version "1.9.0"}
{:deps {expound {:mvn/version "0.7.1"}
org.clojure/clojure {:mvn/version "1.9.0"}
org.clojure/tools.cli {:mvn/version "0.4.1"}
org.martinklepsch/clj-http-lite {:mvn/version "0.4.1"}
hickory {:mvn/version "0.7.1"}}
Expand All @@ -10,4 +11,4 @@
:extra-deps
{clj.native-image
{:git/url "https://github.com/taylorwood/clj.native-image.git"
:sha "0f113d46f9f0d07e8a29545c636431ddf5360a7d"}}}}}
:sha "d97f25aa153e0f94139f5d03e60a345151815d4d"}}}}}
50 changes: 39 additions & 11 deletions src/clojurl.clj
Original file line number Diff line number Diff line change
@@ -1,40 +1,68 @@
(ns clojurl
(:require [clj-http.lite.client :as http]
[clojure.spec.alpha :as s]
[clojure.string :as cs]
[clojure.tools.cli :as cli]
[clojure.pprint :refer [pprint]]
[expound.alpha :as expound]
[hickory.core :as hick])
(:gen-class))

(set! *warn-on-reflection* true)

(def cli-options
[["-u" "--uri URI" "URI of request"
:id :url
:validate [(comp not cs/blank?) "Must be a non-empty string"]]
["-H" "--header HEADER" "Request header(s)"
:parse-fn #(cs/split % #"=")
:id :headers, :default {}, :assoc-fn (fn [m k [l r]] (update m k assoc l r))
:default-desc ""]
["-d" "--data DATA" "Request data"]
["-d" "--data DATA" "Request data"
:id :body]
["-m" "--method METHOD" "Request method e.g. GET, POST, etc."
:default :get, :parse-fn (comp keyword cs/lower-case)
:default-desc "GET"]
["-o" "--output FORMAT" "Output format e.g. edn, hickory"
:id :output
:id :output-fn
:parse-fn {"edn" pprint
"hickory" (comp prn hick/as-hickory hick/parse :body)}
:default pprint :default-desc "edn"]
["-h" "--help" :id :help?]])
["-v" "--verbose" "Print verbose info" :id :verbose?]
["-h" "--help" "Print this message" :id :help?]])

(s/def ::non-empty-string (s/and string? (comp not cs/blank?)))
(s/def ::url ::non-empty-string)
(s/def ::output-fn ifn?)
(s/def ::method keyword?)
(s/def ::headers (s/coll-of (s/tuple ::non-empty-string ::non-empty-string)))
(s/def ::body string?)
(s/def ::options
(s/keys :req-un [::url ::output-fn]
:opt-un [::method ::headers ::body]))

(defn -main [& args]
;; for sunec native lib loading at native-image runtime
(System/setProperty "java.library.path"
(str (System/getenv "GRAALVM_HOME") "/jre/lib"))
(let [{:keys [options summary]} (cli/parse-opts args cli-options)
{:keys [help? uri method headers data output]} options]
(when help? (println summary))
(when uri
(output (http/request {:url uri
:method (keyword method)
:headers headers
:body data})))))
(let [{{:keys [help? verbose? output-fn] :as opts} :options, help :summary}
(cli/parse-opts args cli-options)]
(cond
help?
(println help)

(s/valid? ::options opts)
(-> opts
(select-keys [:url :headers :method :body])
(http/request)
(output-fn))

:else
(do (println "Invalid option(s)")
(if verbose?
(expound/expound ::options opts)
(println "Use --verbose for more detail"))
(println "Usage:")
(println help)
(flush)
(System/exit 1)))))

0 comments on commit 530a5a9

Please sign in to comment.