(please note, the following example has been simplified from a much larger example)
(ns example-app
(:require [ring.util.http-response :refer :all]
[compojure.api.sweet :refer :all]
[schema.core :as s]))
(defapi my-api
{:formats [:json]}
(context* "v0" []
:tags ["unversioned"]
(GET* "api-endpoint-1" []
:return Long
:summary "example endpoint v0/1"
(ok 123)))
(context* "v1" []
:tags ["version-1"]
(GET* "api-endpoint-2 []
:return String
:summary "example endpoint v1/1"
(ok "hello"))))
So, given the above, I'd like to rewrite it as:
(def context-v0
(context* "v0" []
:tags ["unversioned"]
(GET* "api-endpoint-1" []
:return Long
:summary "example endpoint v0/1
(ok 123)))
(def context-v1
(context* "v1" []
:tags ["version-1"]
(GET* "api-endpoint-2 []
:return String
:summary "example endpoint v1/1"
(ok "hello")))
(defapi my-api
context-v0
context-v1)
This would allow me to support older versions of the api while creating new ones without having to worry about a gigantic single file that could get accidentally clobbered by careless future me.
As it stands right now, doing this causes swagger to run, but the extracted contexts do not load. Only ones left in the first format show up.
Do I have to explode this out into its macroexpanded version to get it to work? Any better way?
I think this should work.
(def v1 '("v0" []
:tags ["unversioned"]
(GET* "api-endpoint-1" []
:return Long
:summary "example endpoint v0/1"
(ok 123)))
(defapi my-api
(apply context* v0)
(apply context* v2))
Just to add some more alternatives for other folks who stumble onto this problem.
(def v1-routes
(routes
(GET "/a" [] (ok "Stable A"))
(GET "/b" [] (ok "Stable B"))))
(def v0-routes
(routes
(GET "/a" [] (ok "Next gen A"))
(GET "/b" [] (ok "Next gen B"))))
(defapi myapi
(context "/v0" [] v0-routes)
(context "/v1" [] v1-routes))
One of the nice side-effects of following this route is the "mounting point" of the v*-routes could be changed without changing them directly. The child routes have no knowledge of their parent context.
Related
My code:
(ns model.document
(:gen-class
:name model.document
:implements java.io.Serializable
:state "state"
:init "init"
:constructors {[String String String] []}
:methods [[getContent [] String]
[getTitle [] String]
[getUrl [] String]]))
(defn -init [content title url]
[[] (atom {:content content
:title title
:url url})])
(defn- get-field [this k]
(#(.state this) k))
(defn getContent [this]
(get-field this :content))
(defn getTitle [this]
(get-field this :title))
(defn getUrl [this]
(get-field this :url))
And it's use:
(ns classification-server.classifier
(:require [model.document :refer :all]))
(new model.document "my-content" "my-title" "my-url")
And I get the unhelpful:
Caused by: java.lang.ClassNotFoundException: model.document, compiling:(classification_server/classifier.clj:13:12)
Please help me SO. You're my only hope...
The gen-class namespace you posted doesn’t compile, because the :implements specification expects a vector of symbols, not a symbol. If you change that line to
:implements [java.io.Serializable]
you will be able to compile (and instantiate) the class – however, it will not be functional, because there are other issues with your gen-class spec,such as the prefixed functions being absent (-getContent etc.).
I suggest you read the gen-class documentation and simplify the
problem further.
Here's a simple re-frame app that I tried to create based on the existing example project in re-frame's github repo. But it is only displaying things from the html file. Seems like no event is being dispatched. Can anyone point out what am I doing wrong? Thanks.
(ns simple.core
(:require [reagent.core :as reagent]
[re-frame.core :as rf]
[clojure.string :as str]))
(rf/reg-event-db
:rand
(fn [db [_ _]]
(assoc db :winner ( + 2 (rand-int 3)))))
(rf/reg-sub
:winner
(fn [db _]
(:winner db)))
(def participants ["Alice" "Bob" "Ellie"])
(defn winners-name
[idx]
(get participants idx))
(defn show-winner
[]
[:h1
(winners-name
(#(rf/subscribe [:winner])))])
(defn ui
[]
[:div
[:h1 "Lottery"]
[show-winner]])
(defn ^:export run
[]
(rf/dispatch-sync [:rand])
(reagent/render [ui]
(js/document.getElementById "app")))
The :rand handler will produce nil most times since you are adding 2 to the generated value and the participants vector only has 3 entries.
The issue is caused because of a pair of extra parenthesis around the deref thing. So the function winners-name is treating it as a list instead of an integer.
(winners-name
(#(rf/subscribe [:winner]))
I'm using liberator with compojure, and wanted to send multiple methods (but not all methods) to the save resource. Rather than repeating myself, I'd like to have something that defines multiple handlers at once.
An example:
(defroutes abc
(GET "/x" [] my-func)
(HEAD "/x" [] my-func)
(OPTIONS "/x" [] my-func))
Should be closer to:
(defroutes abc
(GET-HEAD-OPTIONS "/x" [] my-func))
As shown in the tutorial the idiomatic way is to use the ANY key on the route and then define the :allowed-methods [:get :head :options] on your resource. You will need to implement the :handle-ok and :handle-options
(defroute collection-example
(ANY ["/collection/:id" #".*"] [id] (entry-resource id))
(ANY "/collection" [] list-resource))
(defresource list-resource
:available-media-types ["application/json"]
:allowed-methods [:get :post]
:known-content-type? #(check-content-type % ["application/json"])
:malformed? #(parse-json % ::data)
:handle-ok #(map (fn [id] (str (build-entry-url (get % :request) id)))
(keys #entries)))
After several false starts, I realized that the compojure.core/context macro can be used for this purpose. I defined the following macro:
(defmacro read-only "Generate a route that matches HEAD, GET, or OPTIONS"
[path args & body]
`(context "" []
(GET ~path ~args ~#body)
(HEAD ~path ~args ~#body)
(OPTIONS ~path ~args ~#body)))
Which will let you do:
(read-only "/x" [] my-func)
And seems to do what I need.
I want to create a function that allows me to pull contents from some feed, here's what I have... zf is from here
(:require
[clojure.zip :as z]
[clojure.data.zip.xml :only (attr text xml->)]
[clojure.xml :as xml ]
[clojure.contrib.zip-filter.xml :as zf]
)
(def data-url "http://api.eventful.com/rest/events/search?app_key=4H4Vff4PdrTGp3vV&keywords=music&location=Belgrade&date=Future")
(defn zipp [data] (z/xml-zip data))
(defn contents[cont & tags]
(assert (= (zf/xml-> (zipp(parsing cont)) (seq tags) text))))
but when I call it
(contents data-url :events :event :title)
I get an error
java.lang.RuntimeException: java.lang.ClassCastException: clojure.lang.ArraySeq cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)
(Updated in response to the comments: see end of answer for ready-made function parameterized by the tags to match.)
The following extracts the titles from the XML pointed at by the URL from the question text (tested at a Clojure 1.5.1 REPL with clojure.data.xml 0.0.7 and clojure.data.zip 0.1.1):
(require '[clojure.zip :as zip]
'[clojure.data.xml :as xml]
'[clojure.data.zip.xml :as xz]
'[clojure.java.io :as io])
(def data-url "http://api.eventful.com/rest/events/search?app_key=4H4Vff4PdrTGp3vV&keywords=music&location=Belgrade&date=Future")
(def data (-> data-url io/reader xml/parse))
(def z (zip/xml-zip data))
(mapcat (comp :content zip/node)
(xz/xml-> z
(xz/tag= :events)
(xz/tag= :event)
(xz/tag= :title)))
;; value of the above right now:
("Belgrade Early Music Festival, Gosta / Purcell: Dido & Aeneas"
"Belgrade Early Music Festival, Gosta / Purcell: Dido & Aeneas"
"Belgrade Early Music Festival, Gosta / Purcell: Dido & Aeneas"
"VIII Early Music Festival, Belgrade 2013"
"Kevlar Bikini"
"U-Recken - Tree of Life Pre event"
"Green Day"
"Smallman - Vrane Kamene (Crows Of Stone)"
"One Direction"
"One Direction in Serbia")
Some comments:
The clojure.contrib.* namespaces are all deprecated. xml-> now lives in clojure.data.zip.xml.
xml-> accepts a zip loc and a bunch of "predicates"; in this context, however, the word "predicate" has an unusual meaning of a filtering function working on zip locs. See clojure.data.zip.xml source for several functions which return such predicates; for an example of use, see above.
If you want to define a list of predicates separately, you can do that too, then use xml-> with apply:
(def loc-preds [(xz/tag= :events) (xz/tag= :event) (xz/tag= :title)])
(mapcat (comp :content zip/node) (apply xz/xml-> z loc-preds))
;; value returned as above
Update: Here's a function which takes the url and keywords naming tags as arguments and returns the content found at the tags:
(defn get-content-from-tags [url & tags]
(mapcat (comp :content zip/node)
(apply xz/xml->
(-> url io/reader xml/parse zip/xml-zip)
(for [t tags]
(xz/tag= t)))))
Calling it like so:
(get-content-from-tags data-url :events :event :title)
gives the same result as the mapcat form above.
If I have the request "size=3&mean=1&sd=3&type=pdf&distr=normal" what's the idiomatic way of writing the function (defn request->map [request] ...) that takes this request and
returns a map {:size 3, :mean 1, :sd 3, :type pdf, :distr normal}
Here is my attempt (using clojure.walk and clojure.string):
(defn request-to-map
[request]
(keywordize-keys
(apply hash-map
(split request #"(&|=)"))))
I am interested in how others would solve this problem.
Using form-decode and keywordize-keys:
(use 'ring.util.codec)
(use 'clojure.walk)
(keywordize-keys (form-decode "hello=world&foo=bar"))
{:foo "bar", :hello "world"}
Assuming you want to parse HTTP request query parameters, why not use ring? ring.middleware.params contains what you want.
The function for parameter extraction goes like this:
(defn- parse-params
"Parse parameters from a string into a map."
[^String param-string encoding]
(reduce
(fn [param-map encoded-param]
(if-let [[_ key val] (re-matches #"([^=]+)=(.*)" encoded-param)]
(assoc-param param-map
(codec/url-decode key encoding)
(codec/url-decode (or val "") encoding))
param-map))
{}
(string/split param-string #"&")))
You can do this easily with a number of Java libraries. I'd be hesitant to try to roll my own parser unless I read the URI specs carefully and made sure I wasn't missing any edge cases (e.g. params appearing in the query twice with different values). This uses jetty-util:
(import '[org.eclipse.jetty.util UrlEncoded MultiMap])
(defn parse-query-string [query]
(let [params (MultiMap.)]
(UrlEncoded/decodeTo query params "UTF-8")
(into {} params)))
user> (parse-query-string "size=3&mean=1&sd=3&type=pdf&distr=normal")
{"sd" "3", "mean" "1", "distr" "normal", "type" "pdf", "size" "3"}
Can also use this library for both clojure and clojurescript: https://github.com/cemerick/url
user=> (-> "a=1&b=2&c=3" cemerick.url/query->map clojure.walk/keywordize-keys)
{:a "1", :b "2", :c "3"}
Yours looks fine. I tend to overuse regexes, so I would have solved it as
(defn request-to-keywords [req]
(into {} (for [[_ k v] (re-seq #"([^&=]+)=([^&]+)" req)]
[(keyword k) v])))
(request-to-keywords "size=1&test=3NA=G")
{:size "1", :test "3NA=G"}
Edit: try to stay away from clojure.walk though. I don't think it's officially deprecated, but it's not very well maintained. (I use it plenty too, though, so don't feel too bad).
I came across this question when constructing my own site and the answer can be a bit different, and easier, if you are passing parameters internally.
Using Secretary to handle routing: https://github.com/gf3/secretary
Parameters are automatically extracted to a map in :query-params when a route match is found. The example given in the documentation:
(defroute "/users/:id" [id query-params]
(js/console.log (str "User: " id))
(js/console.log (pr-str query-params)))
(defroute #"/users/(\d+)" [id {:keys [query-params]}]
(js/console.log (str "User: " id))
(js/console.log (pr-str query-params)))
;; In both instances...
(secretary/dispach! "/users/10?action=delete")
;; ... will log
;; User: 10
;; "{:action \"delete\"}"
You can use ring.middleware.params. Here's an example with aleph:
user=> (require '[aleph.http :as http])
user=> (defn my-handler [req] (println "params:" (:params req)))
user=> (def server (http/start-server (wrap-params my-handler)))
wrap-params creates an entry in the request object called :params. If you want the query parameters as keywords, you can use ring.middleware.keyword-params. Be sure to wrap with wrap-params first:
user=> (require '[ring.middleware.params :refer [wrap-params]])
user=> (require '[ring.middleware.keyword-params :refer [wrap-keyword-params])
user=> (def server
(http/start-server (wrap-keyword-params (wrap-params my-handler))))
However, be mindful that this includes a dependency on ring.