Following this SO post, I would like to print the value of the preconditions in my function. However it fails for me in the following case (probably destructuring) :
I have a dir? helper function (feel free to skip this one) :
(defn dir? [s]
"returns true if the string passed is is an existing directory"
(->> (clojure.java.io/file s)
((juxt #(.exists %) #(.isDirectory %)))
(every? true?)))
It works just fine, and using the is macro, I get some nice error messages where I can see both the test and the parameters that were passed :
(is (dir? (io/file "resources/static"))) ;; => true
(is (dir? (io/file "resources/statice"))) ;; typo, error below
FAIL in clojure.lang.PersistentList$EmptyList#1
(boot.user4515592986834245937.clj:86) expected: (dir? (io/file
"resources/statice")) actual: (not (dir? #object[java.io.File
0x6730a420 "resources/statice"]))
However, when trying to use it in the precondition :pre, I get an ugly error :
(defn make-something
[&{:keys [dir]
:or {dir "default-dir"}}]
{:pre [(is (dir? (clojure.java.io/file dir)))]}
;;... do something with these
)
(make-something :dir "resources/statices") ;; doesn't exist
clojure.lang.Compiler$CompilerException: java.lang.AssertionError:
Assert failed: (is (dir? (io/file dir))),
compiling:(boot.user4515592986834245937.clj:80:12)
java.lang.AssertionError: Assert failed: (is (dir? (io/file dir)))
How can I get a nice error message in my function just like the one above ?
In case it matters, I am using Clojure 1.7.
You need to check your code (dir? function). Following snippet works for me:
(require '[clojure.java.io :as io])
(defn dir? [s]
(let [dir (io/file s)]
(and (.exists dir)
(.isDirectory dir))))
(defn make-something
[& {:keys [dir] :or {dir "default-dir"}}]
{:pre [(is (dir? dir))]}
(println dir))
(make-something :dir "/tmp")
out => /tmp
ret => nil
(make-something :dir "/xxxx")
FAIL in clojure.lang.PersistentList$EmptyList#1 (form-init3332271312167175068.clj:1)
expected: (dir? dir)
actual: (not (dir? "/xxxx"))
AssertionError Assert failed: (is (dir? dir)) user/make-sth (form-init3332271312167175068.clj:1)
Related
I am just getting started with Clojurescript. I wrote some clojurescript code to use the shared aws credentials file to initialize S3 client and list buckets . However my code does not work.
(defn -main [arg1 arg2]
(println "hello")
(let[ creds (new AWS/SharedIniFileCredentials #js{:profile "superman"})
_ (AWS/config.update creds)
; dump out the accesskey to check if it has the correct profile
_ (AWS/config.getCredentials (fn [err] (if (nil? err) (do
(println "its good")
(println AWS/config.credentials.accessKeyId)))))
s3 (new (.-S3 AWS ))
] (.listBuckets s3 (fn[err buckets] (println "err: " err) (println buckets) )) ))
The AWS/config.getCredentials in the above does pick up the correct profile as seen from (println AWS/config.credentials.accessKeyId). The listbuckets code throws the following error:
#object[NodeError TypeError [ERR_INVALID_ARG_TYPE]: The "key" argument must be of type string or an instance of Buffer, TypedArray, DataView, or KeyObject. Received undefined]
I have Google AWS SDK S3 clojuresript AND is the only link I found . I used that to configure the S3 client but that does not seem to work
I would appreciate any help.
I checked it and the problem seems to be that SDK expects the credentials to be set before anything else, before instantiating the S3 client.
The following works for me on a minimal project with shadow-cljs:
(ns server.main
(:require ["aws-sdk" :as aws]))
(defn main! []
(println "App loaded...")
(let [creds (aws/SharedIniFileCredentials. #js {:profile "example-profile"})
_ (set! (.-credentials aws/config) creds)
s3 (aws/S3.)]
(.listBuckets s3 (fn [err data]
(if err
(println "ERROR:" err)
(println "OK:" data))))))
when I run it:
$ node target/main.js
App loaded...
OK: #js {:Buckets #js [#js {:Name dummy-test-bucket, :CreationDate #inst "2019-05-05T17:32:17.000-00:00"} #js {:Name mydomain.com, :CreationDate #inst "2018-06-19T04:16:10.000-00:00"}], :Owner #js {:DisplayName username, :ID f63f30bc25ab3609b8d3b5be6b3a872dd2c9f7947b2d509e2338357d93e74f2}}
The key was on this answer: https://stackoverflow.com/a/33175424/483566
Please bear with this contrived example but it was the simplest thing I could think of to recreate the issue.
(ns something.core)
(defn call-foo [something & args]
(let [a-foo (:foo (eval (:quux something)))]
(apply a-foo args)))
(def Something {
:foo (fn [& args] args)
:bar (fn [something] (call-foo something))
})
(defn make-something []
{:quux 'Something})
Running the following in the REPL or with lein run works well.
(let [subject (make-something)
actual (call-foo subject "hello" "greetings")]
(println actual))
;;=> (hello greetings)
The problem occurs only during this test and executing lein test:
(ns something.core-test
(:require [clojure.test :refer :all]
[something.core :refer :all]))
(deftest a-test
(let [subject (make-something)
actual (call-foo subject "hello" "greetings")]
(is (= ["hello" "greetings"] actual))))
This throws an error. An example output:
ERROR in (a-test) (Compiler.java:6464)
Uncaught exception, not in assertion.
expected: nil
actual: clojure.lang.Compiler$CompilerException: java.lang.RuntimeException: Unable to resolve symbol: Something in this context, compiling:(/private/var/folders/0n/c7q7860j34xfc2r1x4q51jrh0000gn/T/form-init9215140948330409114.clj:1:6436)
The line "Unable to resolve symbol: Something in this context" makes me think Something is not in context for some reason while I eval in call-foo. But why is this the case only in the test?
The problem is that eval does not see context. Your 'Something resolves in something.core and something.core-test since you have refered all. It won't resolve from whatever namespace where lein test runs its tests.
To fix the immediate problem change
'Something
to
`Something
so that it is namespace-qualified. The test will then run (and fail), but that's another issue (println returns nil for one thing).
I'm trying to serve a clj-http generated document directly via ring/compojure.
I thought ring.util/piped-output-stream would work, but it seems I'm not understanding something here...
This:
(defn laminat-pdf-t
[natno]
(piped-input-stream
(fn [output-stream])
(pdf
[ {:title (str "Omanimali-Kuscheltierpass" natno)
:orientation :landscape
:size :a6
:author "Omanimali - Stefanie Tuschen"
:register-system-fonts true
}
;; [:svg {} (clojure.java.io/file
;; (str "/einbuergern/" natno "/svg" ))]
[:paragraph "Some Text"] ]
output-stream)))
(defn laminat-pdf
"generate individualized cuddly toy passport page"
[natno]
{:headers {"Content-Type" "application/pdf"}
:body (laminat-pdf-t natno)})
leads to an empty response...
What do I need to do differently?
Thanks,
Mathias
I think you may have a bracket out of place in your code (look at the laminat-pdf-t function below - I tweaked it slightly).
Here's exactly what I did (first creating a project with leiningen 2.3.4 called pdf-play) and it displayed a PDF correctly in IE 11.0.9600.16521, Firefox 28.0 and Chrome 33.0.1750.154 (all on Windows - sorry these are the only browsers that I have installed and I don't have a Linux or Mac box but I don't think the browser makes any difference):
project.clj
(defproject pdf-play "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.5.1"]
[compojure "1.1.6"]
[clj-pdf "1.11.15"]]
:plugins [[lein-ring "0.8.10"]]
:ring {:handler pdf-play.handler/app})
src/pdf_play/handler.clj
(ns pdf-play.handler
(:use compojure.core
ring.util.io
clj-pdf.core)
(:require [compojure.handler :as handler]
[compojure.route :as route]))
(defn laminat-pdf-t
[natno]
(piped-input-stream
(fn [output-stream]
(pdf
[{:title (str "Omanimali-Kuscheltierpass" natno)
:orientation :landscape
:size :a6
:author "Omanimali - Stefanie Tuschen"
:register-system-fonts true
}
;; [:svg {} (clojure.java.io/file
;; (str "/einbuergern/" natno "/svg" ))]
[:paragraph "Some Text"]]
output-stream))))
(defn laminat-pdf
"generate individualized cuddly toy passport page"
[natno]
{:headers {"Content-Type" "application/pdf"}
:body (laminat-pdf-t natno)})
(defroutes app-routes
(GET "/" [] (laminat-pdf 1234))
(route/resources "/")
(route/not-found "Not Found"))
(def app (handler/site app-routes))
Then started it at the command prompt like so:
lein ring server
and had a look in the browser and there was a PDF with "Some Text" printed in it.
I am trying to write a test for the compojure static content route.
I am testing the routes by examining the ring response directly.
A minimal working example is as follows:
;; src/testing-webapps.core.clj
(ns testing-webapps.core
(:use [compojure.core]
[compojure.route :as route]))
(defroutes web-app
(route/resources "/")
(route/not-found "404"))
;; test/testing-webapps.core_test.clj
(ns testing-webapps.core-test
(:require [clojure.test :refer :all]
[testing-webapps.core :refer :all]))
(defn request [resource web-app & params]
(web-app {:request-method :get :uri resource :params (first params)}))
(deftest test-routes
(is (= 404 (:status (request "/flubber" web-app))))
(is (= "404" (:body (request "/flubber" web-app))))
(is (= 200 (:status (request "/test.txt" web-app)))))
Testing the 404 route works fine but calling (request "/test.txt" web-app) leads to an unexpected NullPointerException in ring.middleware.file-info/not-modified-since?.
Here is the top part of the stacktrace:
ERROR in (test-routes) (file_info.clj:27)
Uncaught exception, not in assertion.
expected: nil
actual: java.lang.NullPointerException: null
at ring.middleware.file_info$not_modified_since_QMARK_.invoke (file_info.clj:27)
ring.middleware.file_info$file_info_response.doInvoke (file_info.clj:44)
clojure.lang.RestFn.invoke (RestFn.java:442)
ring.middleware.file_info$wrap_file_info$fn__917.invoke (file_info.clj:64)
[...]
The static route works fine in the browser but not when called through my request function.
Is there an easier way to test a static route in compojure and why do I get a NullPointerException when calling the static route with my own request map?
Looking at the source for not-modified-since?, I think the issue is you have no headers in your request map, so it throws a NPE on this expr: (headers "if-modified-since"). Try changing your request method like so:
(defn request [resource web-app & params]
(web-app {:request-method :get
:headers {"content-type" "text/plain"} ; added a header
:uri resource
:params (first params)}))
You might also consider using ring-mock to create requests for testing. It insulates you a little bit from stuff like this.
I'm using the request.el library (available via MELPA) to try and create a basic framework from which to start work in earnest on a Stack Exchange mode for Emacs. All I want to do is to be able to return the object as parsed by json-read to the calling function, but I can't even seem to make a connection.
I understand that to have my function return the object, the call must be made synchronously, so that is what :sync t is there for. I've considered making it an asynchronous call, but I don't think it would be beneficial considering its use-case.
At first, after I looked at the messages, I thought 'Maybe I don't have necessary binaries.' I did test request.el with some example calls that come with its documentation, and they work fine, so that's out.
I'm at a loss as to what is wrong. I don't yet have much experience, successful or otherwise, with anything dealing with a network, and don't fully understand the error message I'm getting. As far as I can tell, port 443 of the API is giving me the silent treatment, but I'm hesitant to think that's the case ;).
;; Works like a charm
(defun alist-to-json (alist)
"Converts the key-value pairs of `ALIST` into a JSON-friendly
string: \"key1=value1&key2=value2&\"."
(apply 'concat
(mapcar (lambda (kv)
(format "%s=%s&" (car kv)
(if (stringp (cdr kv))
(cdr kv)
(number-to-string (cdr kv)))))
alist)))
(defvar stack-api-root "https://api.stackexchange.com/2.1/")
(require 'json)
(require 'request)
(defun stack-api-request (call keys-alist)
"Makes the specified `CALL` to the Stack Exchange API with the
key-value pairs given `KEYS-ALIST`. For example,
(stack-api-request \"sites\" '((page . 2) (page_size . 25)))"
(let* ((base-call (concat stack-api-root call "?"))
(options (alist-to-json keys-alist)))
(request base-call
:params options
:parser 'json-read
:sync t)))
Backtrace
Debugger entered--Lisp error: (error "Could not create connection to api.stackexchange.com:443")
signal(error ("Could not create connection to api.stackexchange.com:443"))
error("Could not create connection to %s:%d" "api.stackexchange.com" 443)
url-http([cl-struct-url "https" nil nil "api.stackexchange.com" nil "/2.1/sites?&" nil nil t nil t] #[128 "\302\303\304p#\210\300\305\240\210\301p\240\207" [(nil) (nil) url-debug retrieval "Synchronous fetching done (%S)" t] 5 "\n\n(fn &rest IGNORED)"] (nil))
url-https([cl-struct-url "https" nil nil "api.stackexchange.com" nil "/2.1/sites?&" nil nil t nil t] #[128 "\302\303\304p#\210\300\305\240\210\301p\240\207" [(nil) (nil) url-debug retrieval "Synchronous fetching done (%S)" t] 5 "\n\n(fn &rest IGNORED)"] (nil))
url-retrieve-internal("https://api.stackexchange.com/2.1/sites?&" #[128 "\302\303\304p#\210\300\305\240\210\301p\240\207" [(nil) (nil) url-debug retrieval "Synchronous fetching done (%S)" t] 5 "\n\n(fn &rest IGNORED)"] (nil) nil nil)
url-retrieve("https://api.stackexchange.com/2.1/sites?&" #[128 "\302\303\304p#\210\300\305\240\210\301p\240\207" [(nil) (nil) url-debug retrieval "Synchronous fetching done (%S)" t] 5 "\n\n(fn &rest IGNORED)"])
url-retrieve-synchronously("https://api.stackexchange.com/2.1/sites?&")
request--url-retrieve-sync("https://api.stackexchange.com/2.1/sites?&" :params "page=2&page_size=25&" :parser json-read :sync t :error (closure (t) (&rest args) (apply (quote request-default-error-callback) (quote "https://api.stackexchange.com/2.1/sites?") args)) :url "https://api.stackexchange.com/2.1/sites?&" :response [cl-struct-request-response nil nil nil nil nil "https://api.stackexchange.com/2.1/sites?&" nil (:params "page=2&page_size=25&" :parser json-read :sync t :error (closure (t) (&rest args) (apply (quote request-default-error-callback) (quote "https://api.stackexchange.com/2.1/sites?") args)) :url "https://api.stackexchange.com/2.1/sites?&" :response #0) nil nil nil url-retrieve nil])
apply(request--url-retrieve-sync "https://api.stackexchange.com/2.1/sites?&" (:params "page=2&page_size=25&" :parser json-read :sync t :error (closure (t) (&rest args) (apply (quote request-default-error-callback) (quote "https://api.stackexchange.com/2.1/sites?") args)) :url "https://api.stackexchange.com/2.1/sites?&" :response [cl-struct-request-response nil nil nil nil nil "https://api.stackexchange.com/2.1/sites?&" nil #0 nil nil nil url-retrieve nil]))
request("https://api.stackexchange.com/2.1/sites?" :params "page=2&page_size=25&" :parser json-read :sync t)
(let* ((base-call (concat stack-api-root call "?")) (options (alist-to-json keys-alist))) (request base-call :params options :parser (quote json-read) :sync t))
stack-api-request("sites" ((page . 2) (page_size . 25)))
eval((stack-api-request "sites" (quote ((page . 2) (page_size . 25)))) nil)
eval-expression((stack-api-request "sites" (quote ((page . 2) (page_size . 25)))) nil)
call-interactively(eval-expression nil nil)
Messages:
Contacting host: api.stackexchange.com:443
Opening TLS connection to `api.stackexchange.com'...
Opening TLS connection with `gnutls-cli --insecure -p 443 api.stackexchange.com'...failed
Opening TLS connection with `gnutls-cli --insecure -p 443 api.stackexchange.com --protocols ssl3'...failed
Opening TLS connection with `openssl s_client -connect api.stackexchange.com:443 -no_ssl2 -ign_eof'...failed
Opening TLS connection to `api.stackexchange.com'...failed
I checked to make sure that this was not a problem with cURL. The call I use for curl is
curl api.stackexchange.com/2.1/sites --compressed
From the outside looking into the request repository, request is doing this as well. I don't know what could be going wrong.
I boiled down your example to the following snippet to reproduce your problem but it actually worked. Can you try this one?
(request
"http://api.stackexchange.com/2.1/sites"
:parser 'json-read
:params '((page . "2") (page_size . "25"))
:success (lambda (&rest args) (princ (plist-get args :data))))
It should print some data to *Messages* buffer and echo area.
EDIT: It seems that the problem in your example is that you are passing string to PARAMS which takes only alist. I will change code to raise error so that it is easier to debug.