-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handler should take clj body w/ optional headers & env as context #23
Merged
ccfontes
merged 18 commits into
main
from
handler-should-take-clj-body-w-optional-headers-env-context
Aug 20, 2023
Merged
Changes from 2 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
cf8c68a
Handler should take clj body w/ optional headers & env as context
ccfontes 6f2540c
🔧 chore(.clj-kondo/config.edn): update linter configuration for tests…
ccfontes d868917
🚀 feat(bb.edn): remove unnecessary tasks key from bb.edn file
ccfontes 20aa51f
🐛 fix(workflows): fix docker run command in faas_fn_build_invoke.yml
ccfontes 9226377
🔧 chore(broken-link-checker.yml): update asciidoc-link-check version …
ccfontes d7668cc
🐛 fix(broken-link-checker.yml): fix typo in asciidoc-link-check packa…
ccfontes 0666ef4
🔧 chore(faas_fn_build_invoke.yml): remove redundant docker logs commands
ccfontes 9367ec9
🐛 fix(workflows): fix curl command in faas_fn_build_invoke.yml
ccfontes 4909549
🔧 chore(faas_fn_build_invoke.yml): remove unnecessary docker logs and…
ccfontes 18b25f8
🔧 chore(faas_fn_build_invoke.yml): stop and remove containers before …
ccfontes dd60921
🐛 fix(faas_fn_build_invoke.yml): increase retry count from 3 to 6 for…
ccfontes 9009a69
🐛 fix(faas_fn_build_invoke.yml): add sleep command before starting co…
ccfontes 7b3ef2d
Handler should take clj body w/ optional headers & env as context
ccfontes 0abd8df
🐛 fix(faas_fn_build_invoke.yml): increase retry count for curl comman…
ccfontes 32c4c3e
🐛 fix(faas_fn_build_invoke.yml): add sleep command before starting th…
ccfontes 07c23f8
🔧 chore(faas_fn_build_invoke.yml): remove unnecessary docker stop and…
ccfontes e1cd4a2
🔧 chore(handler.clj): refactor handler function to include context pa…
ccfontes 9a44c67
🔀 refactor(handler.clj): change the name of the `:keywords` environme…
ccfontes File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,8 @@ | ||
{:linters | ||
{:redefined-var {:level :off} | ||
:duplicate-require {:level :off} | ||
:namespace-name-mismatch {:level :off}}} | ||
:namespace-name-mismatch {:level :off}} | ||
:config-in-ns {tests {:linters {:unresolved-symbol {:exclude [(eg/eg) | ||
(eg/ex) | ||
(plumula.mimolette.alpha/defspec-test [spec-check-index])]}}} | ||
index {:linters {:invalid-arity {:level :off}}}}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
examples/build | ||
examples/template | ||
.clj-kondo/.cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{:paths ["."]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
(ns function.handler) | ||
|
||
(defn handler [content context] | ||
(println "content" content) | ||
(println "context" context) | ||
(update content :bar str "spam")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{:paths ["."] | ||
:deps {eg/eg {:mvn/version "0.5.6-alpha"}} | ||
:tasks {test tests/-main}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,59 @@ | ||
#!/usr/bin/env bb | ||
(ns index | ||
(:require | ||
[function.handler :as function] | ||
[org.httpkit.server :refer [run-server]] | ||
[ring.middleware.json :as json-middleware] | ||
[clojure.walk :refer [keywordize-keys]] | ||
[clojure.string :as str :refer [lower-case]] | ||
[clojure.edn :as edn])) | ||
|
||
(require | ||
'[function.handler :as function] | ||
'[org.httpkit.server :refer [run-server]]) | ||
(defn read-string [s] | ||
(try | ||
(let [res (edn/read-string s)] | ||
(if (symbol? res) | ||
(str res) | ||
res)) | ||
(catch Exception _ | ||
s))) | ||
|
||
(run-server function/handler {:port 8082}) | ||
(defn keywords? [env-val] | ||
(if-some [keywords (edn/read-string env-val)] | ||
keywords | ||
true)) | ||
|
||
@(promise) | ||
(defn ->kebab-case [s] | ||
(lower-case (str/replace s #"_" "-"))) | ||
|
||
(def fn-arg-cnt (comp count first :arglists meta)) | ||
|
||
(defn format-context [m] | ||
(->> m | ||
(map (fn [[k v]] [(->kebab-case k) (read-string v)])) | ||
(into {}) | ||
(keywordize-keys))) | ||
|
||
(defn ->context [headers env] | ||
{:headers (format-context headers) | ||
:env (format-context env)}) | ||
|
||
(def response {:status 200}) | ||
|
||
(defn ->handler [f-var env] | ||
(fn [request] | ||
(let [f (var-get f-var) | ||
faas-fn (case (fn-arg-cnt f-var) | ||
1 (comp f :body) | ||
2 #(f (:body %) (->context (:headers %) env)))] | ||
; TODO replace {} with request, but need to remove troublesome keys | ||
(merge (assoc {} :body (faas-fn request)) | ||
response)))) | ||
|
||
(defn ->app [f-var env] | ||
(-> (->handler f-var env) | ||
(json-middleware/wrap-json-body {:keywords? (keywords? (get env "keywords"))}) | ||
(json-middleware/wrap-json-response))) | ||
|
||
(defn -main [] | ||
(run-server (->app #'function/handler (System/getenv)) | ||
{:port 8082}) | ||
@(promise)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
(ns ring.middleware.json | ||
"Ring middleware for parsing JSON requests and generating JSON responses." | ||
(:require [cheshire.core :as json]) | ||
(:import [java.io InputStream])) | ||
|
||
(def ^{:doc "HTTP token: 1*<any CHAR except CTLs or tspecials>. See RFC2068"} | ||
re-token | ||
#"[!#$%&'*\-+.0-9A-Z\^_`a-z\|~]+") | ||
|
||
(def ^{:doc "HTTP quoted-string: <\"> *<any TEXT except \"> <\">. See RFC2068."} | ||
re-quoted | ||
#"\"((?:\\\"|[^\"])*)\"") | ||
|
||
(def ^{:doc "HTTP value: token | quoted-string. See RFC2109"} | ||
re-value | ||
(str "(" re-token ")|" re-quoted)) | ||
|
||
(def ^{:doc "Pattern for pulling the charset out of the content-type header"} | ||
re-charset | ||
(re-pattern (str ";(?:.*\\s)?(?i:charset)=(?:" re-value ")\\s*(?:;|$)"))) | ||
|
||
(defn find-content-type-charset | ||
"Return the charset of a given a content-type string." | ||
[s] | ||
(when-let [m (re-find re-charset s)] | ||
(or (m 1) (m 2)))) | ||
|
||
(defn character-encoding | ||
"Return the character encoding for the request, or nil if it is not set." | ||
[request] | ||
(some-> (get-in request [:headers "content-type"]) | ||
find-content-type-charset)) | ||
|
||
(defn header | ||
"Returns an updated Ring response with the specified header added." | ||
[resp name value] | ||
(assoc-in resp [:headers name] (str value))) | ||
|
||
(defn content-type | ||
"Returns an updated Ring response with the a Content-Type header corresponding | ||
to the given content-type." | ||
[resp content-type] | ||
(header resp "Content-Type" content-type)) | ||
|
||
(defn- json-request? [request] | ||
(when-let [type (get-in request [:headers "content-type"])] | ||
(seq (re-find #"^application/(.+\+)?json" type)))) | ||
|
||
(defn- read-json [request & [{:keys [keywords? key-fn]}]] | ||
(when (json-request? request) | ||
(when-let [^InputStream body (:body request)] | ||
(let [^String encoding (or (character-encoding request) | ||
"UTF-8") | ||
body-reader (java.io.InputStreamReader. body encoding)] | ||
(try | ||
[true (json/parse-stream body-reader (or key-fn keywords?))] | ||
(catch Exception _ | ||
(println "Error parsing json stream") | ||
[false nil])))))) | ||
|
||
(def ^{:doc "The default response to return when a JSON request is malformed."} | ||
default-malformed-response | ||
{:status 400 | ||
:headers {"Content-Type" "text/plain"} | ||
:body "Malformed JSON in request body."}) | ||
|
||
(defn json-body-request | ||
"Parse a JSON request body and assoc it back into the :body key. Returns nil | ||
if the JSON is malformed. See: wrap-json-body." | ||
[request options] | ||
(if-let [[valid? json] (read-json request options)] | ||
(when valid? (assoc request :body json)) | ||
request)) | ||
|
||
(defn wrap-json-body | ||
"Middleware that parses the body of JSON request maps, and replaces the :body | ||
key with the parsed data structure. Requests without a JSON content type are | ||
unaffected. | ||
|
||
Accepts the following options: | ||
|
||
:key-fn - function that will be applied to each key | ||
:keywords? - true if the keys of maps should be turned into keywords | ||
:bigdecimals? - true if BigDecimals should be used instead of Doubles | ||
:malformed-response - a response map to return when the JSON is malformed" | ||
{:arglists '([handler] [handler options])} | ||
[handler & [{:keys [malformed-response] | ||
:or {malformed-response default-malformed-response} | ||
:as options}]] | ||
(fn | ||
([request] | ||
(if-let [request (json-body-request request options)] | ||
(handler request) | ||
malformed-response)) | ||
([request respond raise] | ||
(if-let [request (json-body-request request options)] | ||
(handler request respond raise) | ||
(respond malformed-response))))) | ||
|
||
(defn- assoc-json-params [request json] | ||
(if (map? json) | ||
(-> request | ||
(assoc :json-params json) | ||
(update-in [:params] merge json)) | ||
request)) | ||
|
||
(defn json-params-request | ||
"Parse the body of JSON requests into a map of parameters, which are added | ||
to the request map on the :json-params and :params keys. Returns nil if the | ||
JSON is malformed. See: wrap-json-params." | ||
[request options] | ||
(if-let [[valid? json] (read-json request options)] | ||
(if valid? (assoc-json-params request json)) | ||
request)) | ||
|
||
(defn wrap-json-params | ||
"Middleware that parses the body of JSON requests into a map of parameters, | ||
which are added to the request map on the :json-params and :params keys. | ||
|
||
Accepts the following options: | ||
|
||
:key-fn - function that will be applied to each key | ||
:bigdecimals? - true if BigDecimals should be used instead of Doubles | ||
:malformed-response - a response map to return when the JSON is malformed | ||
|
||
Use the standard Ring middleware, ring.middleware.keyword-params, to | ||
convert the parameters into keywords." | ||
{:arglists '([handler] [handler options])} | ||
[handler & [{:keys [malformed-response] | ||
:or {malformed-response default-malformed-response} | ||
:as options}]] | ||
(fn | ||
([request] | ||
(if-let [request (json-params-request request options)] | ||
(handler request) | ||
malformed-response)) | ||
([request respond raise] | ||
(if-let [request (json-params-request request options)] | ||
(handler request respond raise) | ||
(respond malformed-response))))) | ||
|
||
(defn json-response | ||
"Converts responses with a map or a vector for a body into a JSON response. | ||
See: wrap-json-response." | ||
[response options] | ||
(if (coll? (:body response)) | ||
(let [json-resp (update-in response [:body] json/generate-string options)] | ||
(if (contains? (:headers response) "Content-Type") | ||
json-resp | ||
(content-type json-resp "application/json; charset=utf-8"))) | ||
response)) | ||
|
||
(defn wrap-json-response | ||
"Middleware that converts responses with a map or a vector for a body into a | ||
JSON response. | ||
|
||
Accepts the following options: | ||
|
||
:key-fn - function that will be applied to each key | ||
:pretty - true if the JSON should be pretty-printed | ||
:escape-non-ascii - true if non-ASCII characters should be escaped with \\u | ||
:stream? - true to create JSON body as stream rather than string" | ||
{:arglists '([handler] [handler options])} | ||
[handler & [{:as options}]] | ||
(fn | ||
([request] | ||
(json-response (handler request) options)) | ||
([request respond raise] | ||
(handler request (fn [response] (respond (json-response response options))) raise)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
language: bb | ||
fprocess: ./index.clj | ||
fprocess: bb --main index | ||
welcome_message: | | ||
You have created a new Function which uses Babashka | ||
You have created a new HTTP Function which uses Babashka |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
(ns tests | ||
(:require | ||
[index] | ||
[clojure.test :refer [run-tests]] | ||
[eg :refer [eg]])) | ||
|
||
(eg index/keywords? | ||
"true" => true | ||
"false" => false | ||
nil => true) | ||
|
||
(eg index/read-string | ||
"0A" => "0A" | ||
"0" => 0 | ||
"abc" => string?) | ||
|
||
(eg index/->kebab-case | ||
"" => "" | ||
"Boo_baR" => "boo-bar") | ||
|
||
(eg index/format-context | ||
{} => {} | ||
{"Foo_baR" "false"} => {:foo-bar false}) | ||
|
||
(eg index/->context | ||
[{} {}] => {:headers {} :env {}} | ||
[{"Foo_baR" "[]"} {"eggs" "4.3"}] => {:headers {:foo-bar []} | ||
:env {:eggs 4.3}}) | ||
|
||
(defn arity-2-handler [{:keys [bar] :as a} {:keys [headers env]}] | ||
ccfontes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
[bar (get headers :content-type) (:my-env env)]) | ||
|
||
(def handler (index/->handler (var arity-2-handler) {"my-env" "env-val"})) | ||
|
||
(eg handler | ||
{:headers {} :body {}} => {:body [nil nil "env-val"] :status 200} | ||
{:headers {"content-type" "application/json"}, :body {:bar "foo"}} => {:body ["foo" "application/json" "env-val"] :status 200}) | ||
|
||
(def app (index/->app (var arity-2-handler) {"MY_ENV" "env-val"})) | ||
|
||
(def str->stream #(-> % (.getBytes "UTF-8") (java.io.ByteArrayInputStream.))) | ||
|
||
(def resp-fixture {:headers {"Content-Type" "application/json; charset=utf-8"} | ||
:body "[\"spam\",\"application/json\",\"env-val\"]" | ||
:status 200}) | ||
|
||
(eg app | ||
{:headers {"content-type" "application/json"}, :body (str->stream "{\"bar\": \"spam\"}")} => resp-fixture) | ||
|
||
; TODO check examples of ring apps | ||
|
||
(defn -main [] | ||
(run-tests 'tests)) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[clj-kondo] reported by reviewdog 🐶
Missing else branch.