Feedback on idiomatic API design

2016-03-08 Thread Johan Haleby
Hi, 

I've just committed an embryo of an open source project 
 to fake http requests by 
starting an actual (programmable) HTTP server. Currently the API looks like 
this (which in my eyes doesn't look very Clojure idiomatic):

(let [fake-server (fake-server/start!)
(fake-route! fake-server "/x" {:status 200 :content-type 
"application/json" :body (slurp (io/resource "my.json"))})
(fake-route! fake-server {:path "/y" :query {:q "something")}} {:status 
200 :content-type "application/json" :body (slurp (io/resource "my2.json"))})]
; Do actual HTTP request
 (shutdown! fake-server))


fake-server/start! starts the HTTP server on a free port (and thus have 
side-effects) then you add routes to it by using fake-route!. The first 
route just returns an HTTP response with status code 200 and content-type 
"application/json" and the specified response body if a request is made 
with path "/x". The second line also matches that a query parameter called 
"q" must be equal to "something. In the end the server is stopped.

I'm thinking of converting all of this into a macro that is used like this:

(with-fake-routes! 
"/x" {:status 200 :content-type "application/json" :body (slurp 
(io/resource "my.json"))}
{:path "/y" :query {:q "something")}} {:status 200 :content-type 
"application/json" :body (slurp (io/resource "my2.json"))})

This looks better imho and it can automatically shutdown the webserver 
afterwards but there are some potential problems. First of all, since 
starting a webserver is (relatively) slow it you might want to do this once 
for a number of tests. I'm thinking that perhaps as an alternative (both 
options could be available) it could be possible to first start the 
fake-server and then supply it to with-fake-routes! as an additional 
parameter. Something like this:

(with-fake-routes! 
fake-server ; We pass the fake-server as the first argument in 
order to have multiple tests sharing the same fake-server
"/x" {:status 200 :content-type "application/json" :body (slurp 
(io/resource "my.json"))}
 {:path "/y" :query {:q "something")}} {:status 200 :content-type 
"application/json" :body (slurp (io/resource "my2.json"))})

If so you would be responsible for shutting it down just as in the initial 
example. 

Another thing that concerns me a bit with the macro is that routes doesn't 
compose. For example you can't define the route outside of the 
with-fake-routes! 
body and just supply it as an argument to the macro (or can you?). I.e. I 
think it would be quite nice to be able to do something like this:

(let [routes [["/x" {:status 200 :content-type "application/json" :body 
(slurp (io/resource "my.json"))}]
  [{:path "/y" :query {:q "something")}} {:status 200 
:content-type "application/json" :body (slurp (io/resource "my2.json"))}]]]
 (with-fake-routes routes))

Would this be a good idea? Would it make sense to have overloaded variants 
of the with-fake-routes! macro to accommodate this as well? Should it be a 
macro in the first place? What do you think? 

Regards,
/Johan

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


New Clojars feature: atomic deploys

2016-03-08 Thread Toby Crawley
We just pushed a new release of Clojars that includes a new
implementation of our internal deploy logic. The big change there is
deployments are now atomic - if the deployment fails (due to a network
interruption/corruption or invalid artifacts), then we no longer write
it to the repository.

We verify the deployment by applying a set of validations (see
https://github.com/clojars/clojars-web/wiki/Pushing#validations) after
all of the artifacts have been uploaded, but before we send back a
response to the final PUT. This allows us to return a (hopefully
helpful) error message if any of the validations fail.

>From a user perspective, deployment should behave the same for the
most part - the only thing that would be different is we now validate
after all of the artifacts are uploaded instead of applying some
validations for each artifact. This means that if you try to redeploy
a non-SNAPSHOT version, for example, it used to fail on the first
artifact, but will now fail after the /last/ artifact has been
uploaded.

As part of this change, we repaired any invalid non-SNAPSHOT
maven-metadata.xml files that had resulted from partial deploys
(likely from network interruptions) in the past.

Since this is a big internal change, please let us know if you see any
issues or oddness with deployment. You can file an issue
(https://github.com/clojars/clojars-web/issues), or find us in
#clojars on either the Clojurians slack or Freenode.

- Toby

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: Feedback on idiomatic API design

2016-03-08 Thread Marc Limotte
I don't think you need a macro here.  In any case, I'd avoid using a macro
as late as possible.  See how far you get with just functions, and then
maybe at the end, add one macro if you absolutely need it to add just a
touch of syntactic sugar.

routes should clearly be some sort of data-structure, rather than
side-effect setter functions.  Maybe this:

(with-fake-routes!
  optional-server-instance
  route-map)


Where optional-server-instance, if it exists is, an object returned by (
fake-server/start!).  If optional-server-instance is not passed in,
then with-fake-routes!
creates it's own and is free to call (shutdown!) on it automatically. And
route-map is a Map of routes:

{
"/x"
  {:status 200 :content-type "application/json" :body (slurp (io/resource
"my.json"))}
{:path "/y" :query {:q "something")}}
  {:status 200 :content-type "application/json" :body (slurp (io/resource
"my2.json"))}
}


Also, at the risk of scope creep, I could foresee wanting the response to
be based on the input instead of just a static blob.  So maybe the value of
:body could be a string or a function of 1 arg, the route-- in your code
test with (fn?).

This gives you a single api, no macros, optional auto-server start/stop or
explicit server management.

marc


On Tue, Mar 8, 2016 at 3:10 AM, Johan Haleby  wrote:

> Hi,
>
> I've just committed an embryo of an open source project
>  to fake http requests by
> starting an actual (programmable) HTTP server. Currently the API looks like
> this (which in my eyes doesn't look very Clojure idiomatic):
>
> (let [fake-server (fake-server/start!)
> (fake-route! fake-server "/x" {:status 200 :content-type 
> "application/json" :body (slurp (io/resource "my.json"))})
> (fake-route! fake-server {:path "/y" :query {:q "something")}} 
> {:status 200 :content-type "application/json" :body (slurp (io/resource 
> "my2.json"))})]
> ; Do actual HTTP request
>  (shutdown! fake-server))
>
>
> fake-server/start! starts the HTTP server on a free port (and thus have
> side-effects) then you add routes to it by using fake-route!. The first
> route just returns an HTTP response with status code 200 and content-type
> "application/json" and the specified response body if a request is made
> with path "/x". The second line also matches that a query parameter called
> "q" must be equal to "something. In the end the server is stopped.
>
> I'm thinking of converting all of this into a macro that is used like this:
>
> (with-fake-routes!
> "/x" {:status 200 :content-type "application/json" :body (slurp
> (io/resource "my.json"))}
> {:path "/y" :query {:q "something")}} {:status 200 :content-type
> "application/json" :body (slurp (io/resource "my2.json"))})
>
> This looks better imho and it can automatically shutdown the webserver
> afterwards but there are some potential problems. First of all, since
> starting a webserver is (relatively) slow it you might want to do this once
> for a number of tests. I'm thinking that perhaps as an alternative (both
> options could be available) it could be possible to first start the
> fake-server and then supply it to with-fake-routes! as an additional
> parameter. Something like this:
>
> (with-fake-routes!
> fake-server ; We pass the fake-server as the first argument in
> order to have multiple tests sharing the same fake-server
> "/x" {:status 200 :content-type "application/json" :body (slurp
> (io/resource "my.json"))}
>  {:path "/y" :query {:q "something")}} {:status 200 :content-type
> "application/json" :body (slurp (io/resource "my2.json"))})
>
> If so you would be responsible for shutting it down just as in the initial
> example.
>
> Another thing that concerns me a bit with the macro is that routes doesn't
> compose. For example you can't define the route outside of the 
> with-fake-routes!
> body and just supply it as an argument to the macro (or can you?). I.e. I
> think it would be quite nice to be able to do something like this:
>
> (let [routes [["/x" {:status 200 :content-type "application/json" :body
> (slurp (io/resource "my.json"))}]
>   [{:path "/y" :query {:q "something")}} {:status 200
> :content-type "application/json" :body (slurp (io/resource "my2.json"))}]]]
>  (with-fake-routes routes))
>
> Would this be a good idea? Would it make sense to have overloaded variants
> of the with-fake-routes! macro to accommodate this as well? Should it be
> a macro in the first place? What do you think?
>
> Regards,
> /Johan
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clojure@googlegroups.com
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You recei

Re: New Clojars feature: atomic deploys

2016-03-08 Thread Mayank Jain
Thank you so much for this! I've personally faced this issue before. So I
know how helpful this change is. Great job! :)
On Mar 8, 2016 7:39 PM, "Toby Crawley"  wrote:

> We just pushed a new release of Clojars that includes a new
> implementation of our internal deploy logic. The big change there is
> deployments are now atomic - if the deployment fails (due to a network
> interruption/corruption or invalid artifacts), then we no longer write
> it to the repository.
>
> We verify the deployment by applying a set of validations (see
> https://github.com/clojars/clojars-web/wiki/Pushing#validations) after
> all of the artifacts have been uploaded, but before we send back a
> response to the final PUT. This allows us to return a (hopefully
> helpful) error message if any of the validations fail.
>
> From a user perspective, deployment should behave the same for the
> most part - the only thing that would be different is we now validate
> after all of the artifacts are uploaded instead of applying some
> validations for each artifact. This means that if you try to redeploy
> a non-SNAPSHOT version, for example, it used to fail on the first
> artifact, but will now fail after the /last/ artifact has been
> uploaded.
>
> As part of this change, we repaired any invalid non-SNAPSHOT
> maven-metadata.xml files that had resulted from partial deploys
> (likely from network interruptions) in the past.
>
> Since this is a big internal change, please let us know if you see any
> issues or oddness with deployment. You can file an issue
> (https://github.com/clojars/clojars-web/issues), or find us in
> #clojars on either the Clojurians slack or Freenode.
>
> - Toby
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clojure@googlegroups.com
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google Groups
> "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to clojure+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: New Clojars feature: atomic deploys

2016-03-08 Thread Ning Sun
Thanks! This is a great feature and especially important for users with
a poor network connection.

On 03/08/2016 10:43 PM, Mayank Jain wrote:
> Thank you so much for this! I've personally faced this issue before. So
> I know how helpful this change is. Great job! :)
>
> On Mar 8, 2016 7:39 PM, "Toby Crawley"  > wrote:
>
> We just pushed a new release of Clojars that includes a new
> implementation of our internal deploy logic. The big change there is
> deployments are now atomic - if the deployment fails (due to a network
> interruption/corruption or invalid artifacts), then we no longer write
> it to the repository.
>
> We verify the deployment by applying a set of validations (see
> https://github.com/clojars/clojars-web/wiki/Pushing#validations) after
> all of the artifacts have been uploaded, but before we send back a
> response to the final PUT. This allows us to return a (hopefully
> helpful) error message if any of the validations fail.
>
> >From a user perspective, deployment should behave the same for the
> most part - the only thing that would be different is we now validate
> after all of the artifacts are uploaded instead of applying some
> validations for each artifact. This means that if you try to redeploy
> a non-SNAPSHOT version, for example, it used to fail on the first
> artifact, but will now fail after the /last/ artifact has been
> uploaded.
>
> As part of this change, we repaired any invalid non-SNAPSHOT
> maven-metadata.xml files that had resulted from partial deploys
> (likely from network interruptions) in the past.
>
> Since this is a big internal change, please let us know if you see any
> issues or oddness with deployment. You can file an issue
> (https://github.com/clojars/clojars-web/issues), or find us in
> #clojars on either the Clojurians slack or Freenode.
>
> - Toby
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clojure@googlegroups.com
> 
> Note that posts from new members are moderated - please be patient
> with your first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com
> 
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it,
> send an email to clojure+unsubscr...@googlegroups.com
> .
> For more options, visit https://groups.google.com/d/optout.
>
> -- 
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clojure@googlegroups.com
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to clojure+unsubscr...@googlegroups.com
> .
> For more options, visit https://groups.google.com/d/optout.


-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


signature.asc
Description: OpenPGP digital signature


Re: a project template containing clj, cljs cljc files

2016-03-08 Thread Zach Oakes
For those looking for such a template for Boot, check out my example 
project:

https://github.com/oakes/full-stack-boot-example

On Sunday, March 6, 2016 at 12:25:41 AM UTC-5, Sunil Nandihalli wrote:
>
> Hi everybody,
>  I am trying to port an old project which uses cljx plugin and reagent to 
> use cljc and reader-conditionals. I was wondering if somebody can share a 
> simple template which has clj, cljs and cljc files in it?
>
> Thanks and regards,
> Sunil.
>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: Understanding init (the zero arity function) for transducers.

2016-03-08 Thread Patrick Curran
Thanks Alex,

If you ever do get a chance, I'd be curious to know what it was. The more I 
think about it the more I think Dan is correct. Also "scan" seems like a 
natural thing that one should be able to do without having to jump through 
hoops.

On Monday, February 29, 2016 at 5:10:53 PM UTC-5, Alex Miller wrote:
>
> I think that Rich had an objection to this, however in the haziness of 
> time I don't recall specifically what it was. If I get a chance, I will ask 
> him this week.
>
> On Monday, February 29, 2016 at 3:27:15 PM UTC-6, Patrick Curran wrote:
>>
>> Hi,
>>
>> I was trying to write a transducer and the 0-arity part of it never got 
>> called, which was unexpected. I did some searching and found this post: 
>> https://groups.google.com/forum/#!msg/clojure/uVKP4_0KMwQ/-oUJahvUarIJ. 
>> What Dan is proposing in that post would essentially solve my problem, but 
>> it doesn't look like his proposal has gotten much traction...
>>
>> Specifically I was trying to implement scan 
>> .
>>
>> (defn scan
>>   ([f] (scan f (f)))
>>   ([f init]
>>(fn [xf]
>>  (let [state (volatile! init)]
>>(fn
>>  ([] (xf (xf) init))
>>  ([result] (xf result))
>>  ([result input]
>>   (let [next-state (f @state input)]
>> (vreset! state next-state)
>> (xf result next-state
>>
>> Which results in the following:
>> (require '[clojure.core.reducers :as r])
>> (r/reduce ((scan + 3) conj) [1 2 3])
>> => [3 4 6 9]
>> (transduce (scan + 3) conj [1 2 3])
>> => [4 6 9]
>> (transduce (scan + 3) conj (((scan + 3) conj)) [1 2 3])
>> => [3 4 6 9]
>>
>> My expectation would be that we'd always get the 3 at the front of the 
>> vector.
>>
>> I'm actually using core.async and I'm expecting that the initial value be 
>> available to be taken from the channel.
>> (require '[clojure.core.async :as a :include-macros true])
>> (def c (a/chan 1 (scan + 3)))
>> (a/go (println (a/> ; expecting 3 to immediately be printed.
>> (a/>!! c 1)
>> => 4
>>
>> So this is more of a conceptual thing rather than just how transduce is 
>> implemented.
>>
>> I'd love to hear other people's thoughts on this. I'm quite new, but 
>> Dan's proposal definitely feels "correct" and the current implementation 
>> definitely feels "wrong".
>>
>> --Patrick
>>
>>
>>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: Feedback on idiomatic API design

2016-03-08 Thread Johan Haleby
Thanks for your feedback, exactly what I wanted.

On Tuesday, March 8, 2016 at 3:16:02 PM UTC+1, mlimotte wrote:
>
> I don't think you need a macro here.  In any case, I'd avoid using a macro 
> as late as possible.  See how far you get with just functions, and then 
> maybe at the end, add one macro if you absolutely need it to add just a 
> touch of syntactic sugar.
>
> routes should clearly be some sort of data-structure, rather than 
> side-effect setter functions.  Maybe this:
>
> (with-fake-routes!
>   optional-server-instance
>   route-map) 
>
>
> Where optional-server-instance, if it exists is, an object returned by (
> fake-server/start!).  If optional-server-instance is not passed in, then 
> with-fake-routes! 
> creates it's own and is free to call (shutdown!) on it automatically. And 
> route-map is a Map of routes:
>
> {
> "/x" 
>   {:status 200 :content-type "application/json" :body (slurp (io/resource 
> "my.json"))}
> {:path "/y" :query {:q "something")}} 
>   {:status 200 :content-type "application/json" :body (slurp (io/resource 
> "my2.json"))}
> }
>
>
+1. I'm gonna go for this option.
 

>
> Also, at the risk of scope creep, I could foresee wanting the response to 
> be based on the input instead of just a static blob.  So maybe the value of 
> :body could be a string or a function of 1 arg, the route-- in your code 
> test with (fn?).
>

That's a good idea indeed. I've already thought about this for matching the 
request. I'd like this to work:

{
 (fn [request] (= (:path request) "/x")) 
  {:status 200 :content-type "application/json" :body (slurp (io/resource 
"my.json"))}
{:path "/y" :query {:q (fn [q] (clojure.string/starts-with? q "some"))}} 
  {:status 200 :content-type "application/json" :body (slurp (io/resource 
"my2.json"))}
}

Thanks a lot for your help and feedback!
 

>
> This gives you a single api, no macros, optional auto-server start/stop or 
> explicit server management.
>
> marc
>
>
> On Tue, Mar 8, 2016 at 3:10 AM, Johan Haleby  > wrote:
>
>> Hi, 
>>
>> I've just committed an embryo of an open source project 
>>  to fake http requests by 
>> starting an actual (programmable) HTTP server. Currently the API looks like 
>> this (which in my eyes doesn't look very Clojure idiomatic):
>>
>> (let [fake-server (fake-server/start!)
>> (fake-route! fake-server "/x" {:status 200 :content-type 
>> "application/json" :body (slurp (io/resource "my.json"))})
>> (fake-route! fake-server {:path "/y" :query {:q "something")}} 
>> {:status 200 :content-type "application/json" :body (slurp (io/resource 
>> "my2.json"))})]
>> ; Do actual HTTP request
>>  (shutdown! fake-server))
>>
>>
>> fake-server/start! starts the HTTP server on a free port (and thus have 
>> side-effects) then you add routes to it by using fake-route!. The first 
>> route just returns an HTTP response with status code 200 and content-type 
>> "application/json" and the specified response body if a request is made 
>> with path "/x". The second line also matches that a query parameter called 
>> "q" must be equal to "something. In the end the server is stopped.
>>
>> I'm thinking of converting all of this into a macro that is used like 
>> this:
>>
>> (with-fake-routes! 
>> "/x" {:status 200 :content-type "application/json" :body (slurp 
>> (io/resource "my.json"))}
>> {:path "/y" :query {:q "something")}} {:status 200 :content-type 
>> "application/json" :body (slurp (io/resource "my2.json"))})
>>
>> This looks better imho and it can automatically shutdown the webserver 
>> afterwards but there are some potential problems. First of all, since 
>> starting a webserver is (relatively) slow it you might want to do this once 
>> for a number of tests. I'm thinking that perhaps as an alternative (both 
>> options could be available) it could be possible to first start the 
>> fake-server and then supply it to with-fake-routes! as an additional 
>> parameter. Something like this:
>>
>> (with-fake-routes! 
>> fake-server ; We pass the fake-server as the first argument in 
>> order to have multiple tests sharing the same fake-server
>> "/x" {:status 200 :content-type "application/json" :body (slurp 
>> (io/resource "my.json"))}
>>  {:path "/y" :query {:q "something")}} {:status 200 :content-type 
>> "application/json" :body (slurp (io/resource "my2.json"))})
>>
>> If so you would be responsible for shutting it down just as in the 
>> initial example. 
>>
>> Another thing that concerns me a bit with the macro is that routes 
>> doesn't compose. For example you can't define the route outside of the 
>> with-fake-routes! 
>> body and just supply it as an argument to the macro (or can you?). I.e. I 
>> think it would be quite nice to be able to do something like this:
>>
>> (let [routes [["/x" {:status 200 :content-type "application/json" :body 
>> (slurp (io/resource "my.json"))}]
>>   [{:path "/y" :query {:q "something")}} {:status 

Re: Feedback on idiomatic API design

2016-03-08 Thread Johan Haleby
On Wed, Mar 9, 2016 at 6:20 AM, Johan Haleby  wrote:

> Thanks for your feedback, exactly what I wanted.
>
> On Tuesday, March 8, 2016 at 3:16:02 PM UTC+1, mlimotte wrote:
>>
>> I don't think you need a macro here.  In any case, I'd avoid using a
>> macro as late as possible.  See how far you get with just functions, and
>> then maybe at the end, add one macro if you absolutely need it to add just
>> a touch of syntactic sugar.
>>
>> routes should clearly be some sort of data-structure, rather than
>> side-effect setter functions.  Maybe this:
>>
>> (with-fake-routes!
>>   optional-server-instance
>>   route-map)
>>
>>
Hmm now that I come to think of it I don't see how this would actually work
unless you also perform the HTTP request from inside the scope of
with-fake-routes!,
otherwise the server instance would be closed before you get the chance to
make the request. Since you make an actual HTTP request you need access to
the URI generated when starting the fake-server instance (at least if the
port is chosen randomly). So either I suppose you would have to do like
this (which requires a macro?):

(with-fake-routes!
  {"/x" {:status 200 :content-type "application/json" :body (slurp
(io/resource "my.json"))}}
  ; Actual HTTP request
  (http/get uri "/x"))

where "uri" is created by the  with-fake-routes! macro *or* we could return
the generated fake-server. But if so with-fake-routes! cannot automatically
close the fake-server instance since we need the instance to be alive when
we make the call to the generated uri. I suppose it would have to look
something like this:

(let [fake-server (with-fake-routes! {"/x" {:status 200 :content-type
"application/json" :body (slurp (io/resource "my.json"))}})]
(http/get (:uri fake-server) "/x")
(shutdown! fake-server))

If so I think that the second option is unnecessary since then you might
just go with:

(with-fake-routes!
  *required*-server-instance
  route-map)

instead of having two options. But then we loose the niceness of having the
server instance be automatically created and stopped for us?


>> Where optional-server-instance, if it exists is, an object returned by (
>> fake-server/start!).  If optional-server-instance is not passed in, then 
>> with-fake-routes!
>> creates it's own and is free to call (shutdown!) on it automatically.
>> And route-map is a Map of routes:
>>
>
>> {
>> "/x"
>>   {:status 200 :content-type "application/json" :body (slurp (io/resource
>> "my.json"))}
>> {:path "/y" :query {:q "something")}}
>>   {:status 200 :content-type "application/json" :body (slurp (io/resource
>> "my2.json"))}
>> }
>>
>>
> +1. I'm gonna go for this option.
>
>
>>
>> Also, at the risk of scope creep, I could foresee wanting the response to
>> be based on the input instead of just a static blob.  So maybe the value of
>> :body could be a string or a function of 1 arg, the route-- in your code
>> test with (fn?).
>>
>
> That's a good idea indeed. I've already thought about this for matching
> the request. I'd like this to work:
>
> {
>  (fn [request] (= (:path request) "/x"))
>   {:status 200 :content-type "application/json" :body (slurp (io/resource
> "my.json"))}
> {:path "/y" :query {:q (fn [q] (clojure.string/starts-with? q "some"))}}
>   {:status 200 :content-type "application/json" :body (slurp (io/resource
> "my2.json"))}
> }
>
> Thanks a lot for your help and feedback!
>
>
>>
>> This gives you a single api, no macros, optional auto-server start/stop
>> or explicit server management.
>>
>> marc
>>
>>
>> On Tue, Mar 8, 2016 at 3:10 AM, Johan Haleby  wrote:
>>
>>> Hi,
>>>
>>> I've just committed an embryo of an open source project
>>>  to fake http requests by
>>> starting an actual (programmable) HTTP server. Currently the API looks like
>>> this (which in my eyes doesn't look very Clojure idiomatic):
>>>
>>> (let [fake-server (fake-server/start!)
>>> (fake-route! fake-server "/x" {:status 200 :content-type 
>>> "application/json" :body (slurp (io/resource "my.json"))})
>>> (fake-route! fake-server {:path "/y" :query {:q "something")}} 
>>> {:status 200 :content-type "application/json" :body (slurp (io/resource 
>>> "my2.json"))})]
>>> ; Do actual HTTP request
>>>  (shutdown! fake-server))
>>>
>>>
>>> fake-server/start! starts the HTTP server on a free port (and thus have
>>> side-effects) then you add routes to it by using fake-route!. The first
>>> route just returns an HTTP response with status code 200 and content-type
>>> "application/json" and the specified response body if a request is made
>>> with path "/x". The second line also matches that a query parameter called
>>> "q" must be equal to "something. In the end the server is stopped.
>>>
>>> I'm thinking of converting all of this into a macro that is used like
>>> this:
>>>
>>> (with-fake-routes!
>>> "/x" {:status 200 :content-type "application/json" :body (slurp
>>> (io/resource "my.json"))}
>>> {:path "/y" :query {: