Re: Feedback on idiomatic API design

2016-03-28 Thread Johan Haleby
Thanks everyone for your input. I just wanted to say that I have an initial
version of the library available at my github
<https://github.com/johanhaleby/stub-http> page if anyone is interested.

On Sat, Mar 12, 2016 at 3:43 PM, Timothy Baldridge 
wrote:

> "Idiomatic" is always a hard word to define, and I think some of the
> points made here are good, but let me also provide a few guidelines I try
> to abide by when writing an API:
>
> Start with data, preferably hash maps. At some point your API will be
> consumed by someone else's program. Macros make it hard to compose api
> calls in a sane matter using code. So stick with hash maps and pure data.
> Something like the following:
>
> {:host "foo.bar.com"
>  :port 80
>  :path "/some/path/i/want"
>  :params {:name :value :key :value2}}
>
> Now if it comes time to modify/process/compose this request we can use
> normal Clojure functions like assoc/conj to build this request map. Of
> course, using this approach normally results in a explosion of data, so
> pretty it up with helper functions:
>
> (make-request-map "http://foo.bar.com/some/path/i/want"; {:name :value})
>
> The key here, is that these helper functions should emit the data you
> specified in the first step.
>
> And finally, write macros as a last resort to pretty up the user
> experience even further. In short:
>
> 1) Start with data to allow clojure code to easily access your API
> 2) Make generating that data simpler by writing helper functions to
> generate data
> 3) (Optionally) Write a DSL to make user interaction easier.
>
> Timothy
>
> On Sat, Mar 12, 2016 at 6:41 AM, Johan Haleby 
> wrote:
>
>> Thanks a lot for your support and insights. I'm going to rewrite it to
>> use "with-open" as we speak.
>>
>> On Sat, Mar 12, 2016 at 2:37 PM, Marc Limotte 
>> wrote:
>>
>>> Look at the source for the clojure.core with-open macro.  In the repl:
>>> `(source with-open)`.
>>>
>>> I think Gary is right.  with-open does exactly what you need, I should
>>> have thought of that, and you should probably use it.  But if you want to
>>> get your version working, trying to understand what the with-open macro is
>>> doing.  Your implementation can be simpler because you only have one
>>> explicit binding.  Essentially you'll create a let as a backquoted form and
>>> then splice in the explicit symbol from the user:
>>>
>>>
>>>`(let [~sym ...server-instance-or-uri...] ... )
>>>
>>>
>>> marc
>>>
>>>
>>>
>>>
>>> On Sat, Mar 12, 2016 at 1:57 AM, Johan Haleby 
>>> wrote:
>>>
>>>>
>>>>
>>>> On Wed, Mar 9, 2016 at 7:32 PM, Marc Limotte 
>>>> wrote:
>>>>
>>>>> With the macro approach, they don't need to escape it.
>>>>>
>>>>
>>>> Do you know of any resources of where I can read up on this? I have the
>>>> macro working with an implicit "uri" generated but I don't know how to make
>>>> it explicit (i.e. defined by the user) the way you proposed.
>>>>
>>>>
>>>>>
>>>>> On Wed, Mar 9, 2016 at 12:52 PM, Johan Haleby 
>>>>> wrote:
>>>>>
>>>>>> Thanks a lot for your support Marc, really appreciated.
>>>>>>
>>>>>> On Wed, Mar 9, 2016 at 5:33 PM, Marc Limotte 
>>>>>> wrote:
>>>>>>
>>>>>>> Yes, I was assuming the HTTP calls happen inside the
>>>>>>> with-fake-routes! block.
>>>>>>> I missed the part about the random port.  I se 3 options for that:
>>>>>>>
>>>>>>> *Assign a port, rather than random*
>>>>>>>
>>>>>>> (with-fake-routes!  ...)
>>>>>>>
>>>>>>>
>>>>>>> But then, of course, you have to worry about port already in use.
>>>>>>>
>>>>>>> *An atom*
>>>>>>>
>>>>>>> (def the-uri (atom nil))
>>>>>>> (with-fake-routes! the-uri
>>>>>>>   ...
>>>>>>>   (http/get @the-uri "/x"))
>>>>>>>
>>>>>>> *A macro*
>>>>>>>
>>>>>>> A common convention in Clojure would be to pass it a symbol (e.g.
>>>>>>> `uri` that is bound by t

Re: Feedback on idiomatic API design

2016-03-12 Thread Johan Haleby
Thanks a lot for your support and insights. I'm going to rewrite it to use
"with-open" as we speak.

On Sat, Mar 12, 2016 at 2:37 PM, Marc Limotte  wrote:

> Look at the source for the clojure.core with-open macro.  In the repl:
> `(source with-open)`.
>
> I think Gary is right.  with-open does exactly what you need, I should
> have thought of that, and you should probably use it.  But if you want to
> get your version working, trying to understand what the with-open macro is
> doing.  Your implementation can be simpler because you only have one
> explicit binding.  Essentially you'll create a let as a backquoted form and
> then splice in the explicit symbol from the user:
>
>
>`(let [~sym ...server-instance-or-uri...] ... )
>
>
> marc
>
>
>
>
> On Sat, Mar 12, 2016 at 1:57 AM, Johan Haleby 
> wrote:
>
>>
>>
>> On Wed, Mar 9, 2016 at 7:32 PM, Marc Limotte  wrote:
>>
>>> With the macro approach, they don't need to escape it.
>>>
>>
>> Do you know of any resources of where I can read up on this? I have the
>> macro working with an implicit "uri" generated but I don't know how to make
>> it explicit (i.e. defined by the user) the way you proposed.
>>
>>
>>>
>>> On Wed, Mar 9, 2016 at 12:52 PM, Johan Haleby 
>>> wrote:
>>>
>>>> Thanks a lot for your support Marc, really appreciated.
>>>>
>>>> On Wed, Mar 9, 2016 at 5:33 PM, Marc Limotte 
>>>> wrote:
>>>>
>>>>> Yes, I was assuming the HTTP calls happen inside the with-fake-routes! 
>>>>> block.
>>>>>
>>>>> I missed the part about the random port.  I se 3 options for that:
>>>>>
>>>>> *Assign a port, rather than random*
>>>>>
>>>>> (with-fake-routes!  ...)
>>>>>
>>>>>
>>>>> But then, of course, you have to worry about port already in use.
>>>>>
>>>>> *An atom*
>>>>>
>>>>> (def the-uri (atom nil))
>>>>> (with-fake-routes! the-uri
>>>>>   ...
>>>>>   (http/get @the-uri "/x"))
>>>>>
>>>>> *A macro*
>>>>>
>>>>> A common convention in Clojure would be to pass it a symbol (e.g.
>>>>> `uri` that is bound by the macro), rather implicitly creating `uri`.
>>>>>
>>>>> (with-fake-routes! [uri option-server-instance]
>>>>>
>>>>> route-map
>>>>>
>>>>> (http/get uri "/x"))
>>>>>
>>>>>
>>>> Didn't know about this convention so thanks for the tip. But is your
>>>> snippet above actually working code or does the user need escape "uri" and 
>>>> "
>>>> option-server-instance" using a single-quotes, i.e.
>>>>
>>>> (with-fake-routes! [*'*uri *'*option-server-instance] ...)
>>>>
>>>>
>>>>>
>>>>> or, with a pre-defined server
>>>>>
>>>>> (def fake-server ...)
>>>>> (with-fake-routes!
>>>>>
>>>>> route-map
>>>>>
>>>>> (http/get (:uri fake-server) "/x"))
>>>>>
>>>>>
>>>>> marc
>>>>>
>>>>>
>>>>>
>>>>> On Wed, Mar 9, 2016 at 1:00 AM, Johan Haleby 
>>>>> wrote:
>>>>>
>>>>>>
>>>>>>
>>>>>> 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 functio

Re: Feedback on idiomatic API design

2016-03-11 Thread Johan Haleby
On Wed, Mar 9, 2016 at 7:32 PM, Marc Limotte  wrote:

> With the macro approach, they don't need to escape it.
>

Do you know of any resources of where I can read up on this? I have the
macro working with an implicit "uri" generated but I don't know how to make
it explicit (i.e. defined by the user) the way you proposed.


>
> On Wed, Mar 9, 2016 at 12:52 PM, Johan Haleby 
> wrote:
>
>> Thanks a lot for your support Marc, really appreciated.
>>
>> On Wed, Mar 9, 2016 at 5:33 PM, Marc Limotte  wrote:
>>
>>> Yes, I was assuming the HTTP calls happen inside the with-fake-routes! 
>>> block.
>>>
>>> I missed the part about the random port.  I se 3 options for that:
>>>
>>> *Assign a port, rather than random*
>>>
>>> (with-fake-routes!  ...)
>>>
>>>
>>> But then, of course, you have to worry about port already in use.
>>>
>>> *An atom*
>>>
>>> (def the-uri (atom nil))
>>> (with-fake-routes! the-uri
>>>   ...
>>>   (http/get @the-uri "/x"))
>>>
>>> *A macro*
>>>
>>> A common convention in Clojure would be to pass it a symbol (e.g. `uri`
>>> that is bound by the macro), rather implicitly creating `uri`.
>>>
>>> (with-fake-routes! [uri option-server-instance]
>>>
>>> route-map
>>>
>>> (http/get uri "/x"))
>>>
>>>
>> Didn't know about this convention so thanks for the tip. But is your
>> snippet above actually working code or does the user need escape "uri" and "
>> option-server-instance" using a single-quotes, i.e.
>>
>> (with-fake-routes! [*'*uri *'*option-server-instance] ...)
>>
>>
>>>
>>> or, with a pre-defined server
>>>
>>> (def fake-server ...)
>>> (with-fake-routes!
>>>
>>> route-map
>>>
>>> (http/get (:uri fake-server) "/x"))
>>>
>>>
>>> marc
>>>
>>>
>>>
>>> On Wed, Mar 9, 2016 at 1:00 AM, Johan Haleby 
>>> wrote:
>>>
>>>>
>>>>
>>>> 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
>>>> "

Re: Feedback on idiomatic API design

2016-03-10 Thread Johan Haleby
Very interesting approach indeed. I'm going to finish up the previous
approach first then I'll look more closely into this. I like it, and its
simple!

Thanks!

On Thu, Mar 10, 2016 at 10:51 AM, Gary Verhaegen 
wrote:

> I would suggest a slightly different approach. First, define a record for
> your fake server, which implements Closeable; put your current "shutdown"
> code in the close method.
>
> This will allow you to use the existing with-open macro, instead of having
> to redefine your own, while leaving the option of not using the macro
> should the user want that.
>
> Then, define a constructor function for that record, which takes as
> argument a map or vector defining your routes, and returns an instance of
> the above record. That record should have a :uri property, and encapsulate
> the actual server.
>
> Then you can do stuff like:
>
> (with-open [srv (fake-server routes)]
>   (http/get (:uri srv) ...)
>   ...)
>
> while leaving the option of creating and closing the server manually if it
> makes sense for some use-case. You can also easily reuse routes from one
> server to another as it is a simple data structure.
>
>
> On Wednesday, 9 March 2016, Marc Limotte  wrote:
>
>> With the macro approach, they don't need to escape it.
>>
>> On Wed, Mar 9, 2016 at 12:52 PM, Johan Haleby 
>> wrote:
>>
>>> Thanks a lot for your support Marc, really appreciated.
>>>
>>> On Wed, Mar 9, 2016 at 5:33 PM, Marc Limotte 
>>> wrote:
>>>
>>>> Yes, I was assuming the HTTP calls happen inside the with-fake-routes! 
>>>> block.
>>>>
>>>> I missed the part about the random port.  I se 3 options for that:
>>>>
>>>> *Assign a port, rather than random*
>>>>
>>>> (with-fake-routes!  ...)
>>>>
>>>>
>>>> But then, of course, you have to worry about port already in use.
>>>>
>>>> *An atom*
>>>>
>>>> (def the-uri (atom nil))
>>>> (with-fake-routes! the-uri
>>>>   ...
>>>>   (http/get @the-uri "/x"))
>>>>
>>>> *A macro*
>>>>
>>>> A common convention in Clojure would be to pass it a symbol (e.g. `uri`
>>>> that is bound by the macro), rather implicitly creating `uri`.
>>>>
>>>> (with-fake-routes! [uri option-server-instance]
>>>>
>>>> route-map
>>>>
>>>> (http/get uri "/x"))
>>>>
>>>>
>>> Didn't know about this convention so thanks for the tip. But is your
>>> snippet above actually working code or does the user need escape "uri" and "
>>> option-server-instance" using a single-quotes, i.e.
>>>
>>> (with-fake-routes! [*'*uri *'*option-server-instance] ...)
>>>
>>>
>>>>
>>>> or, with a pre-defined server
>>>>
>>>> (def fake-server ...)
>>>> (with-fake-routes!
>>>>
>>>> route-map
>>>>
>>>> (http/get (:uri fake-server) "/x"))
>>>>
>>>>
>>>> marc
>>>>
>>>>
>>>>
>>>> On Wed, Mar 9, 2016 at 1:00 AM, Johan Haleby 
>>>> wrote:
>>>>
>>>>>
>>>>>
>>>>> 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 u

Re: Feedback on idiomatic API design

2016-03-09 Thread Johan Haleby
Thanks a lot for your support Marc, really appreciated.

On Wed, Mar 9, 2016 at 5:33 PM, Marc Limotte  wrote:

> Yes, I was assuming the HTTP calls happen inside the with-fake-routes! block.
>
> I missed the part about the random port.  I se 3 options for that:
>
> *Assign a port, rather than random*
>
> (with-fake-routes!  ...)
>
>
> But then, of course, you have to worry about port already in use.
>
> *An atom*
>
> (def the-uri (atom nil))
> (with-fake-routes! the-uri
>   ...
>   (http/get @the-uri "/x"))
>
> *A macro*
>
> A common convention in Clojure would be to pass it a symbol (e.g. `uri`
> that is bound by the macro), rather implicitly creating `uri`.
>
> (with-fake-routes! [uri option-server-instance]
>
> route-map
>
> (http/get uri "/x"))
>
>
Didn't know about this convention so thanks for the tip. But is your
snippet above actually working code or does the user need escape "uri" and "
option-server-instance" using a single-quotes, i.e.

(with-fake-routes! [*'*uri *'*option-server-instance] ...)


>
> or, with a pre-defined server
>
> (def fake-server ...)
> (with-fake-routes!
>
> route-map
>
> (http/get (:uri fake-server) "/x"))
>
>
> marc
>
>
>
> On Wed, Mar 9, 2016 at 1:00 AM, Johan Haleby 
> wrote:
>
>>
>>
>> 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

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
>>> <https://github.com/johanhaleby/fake-http> 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 
&

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 
>> <https://github.com/johanhaleby/fake-http> 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-

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.