Fork me on GitHub
#clojure
<
2023-03-17
>
sparkofreason01:03:40

I'd like to be able to take existing instances of java classes and wrap them such that method calls are generally delegated to the wrapped object, except for any overrides I specify (which also need to be able to call the wrapped class). Pretty sure I could do this with gen-class, but I believe that means I also need to hand-author all of the delegating methods. Is there some library or function which just lets you override selected methods on a class instance without the boilerplate?

phronmophobic01:03:00

What do you mean by wrap them? Would you be calling these methods from java or clojure? Are you talking about generating a new class that has the same methods or trying to monkey patch classes somehow?

sparkofreason01:03:10

Monkey patch. Specifically, I want to extend the implementation of java.sql.Connection to include some extra schema meta-data for consumption by honeyeql and graphqlize.

sparkofreason01:03:05

I'd need to do something like pass in a custom instance of java.sql.DataSource which provides an instance of my monkey-patched java.sql.Connection.

phronmophobic01:03:32

java.sql.Connection is an interface, so you don't need to monkey patch. You can just create a new instance that also implements java.sql.Connection.

phronmophobic01:03:56

I'm not super familiar with honeysql and graphqlize, but there's probably a more idiomatic way to get what you want too.

sparkofreason01:03:40

Right, but I'm going to want to use the existing MySQL implementation, and just monkey-patch the bits I need (which would include calling through to the MySQL instance.

phronmophobic01:03:26

Can you give a little more background on why the existing implementation isn't working for you?

sparkofreason01:03:23

I'm not seeing any other way to do this without forking honeyeql. The schema-related stuff is pretty much hardwired, which does make sense. The issue I'm trying to address is that MySQL views don't provide any schema info about foreign keys, so honeyeql (and hence graphqlize) don't know how to navigate from an entity defined by a MySQL view to a foreign key reference from the original table.

phronmophobic01:03:11

Given an existing instance, it would be pretty easy to create a new instance that adds the bits of functionality you're interested in using proxy or reify. It wouldn't apply to every instance, but it should be workable.

phronmophobic01:03:13

There are some limitations to proxy and reify which I don't think will get in your way, but in some cases, it may make sense to either write a small bit of java or use https://github.com/jgpc42/insn.

phronmophobic02:03:54

There's also a #C66EM8D5H channel that can probably give you more specific advice.

2
Alexandre EL-KHOURY10:03:37

Hello clojurians ! Is there a way to change the name of a body parameter in the swagger ? Here's a screenshot below.

p-himik10:03:16

What did you use to generate the Swagger JSON? (Or whatever it is that ends up creating this web page)

p-himik11:03:55

And how is the endpoint defined?

p-himik11:03:19

I tried the reitit-swagger example and it uses body as the name by default.

Alexandre EL-KHOURY11:03:13

Yes, I dont want it to use body as the name by default. Here's how my route is defined :

(def routes
  [["/"
    {:swagger {:tags ["Authentication"]}
     :middleware
     [wrap-json-params
      wrap-keyword-params
      wrap-spec
      wrap-json-response]}
    ["authentication"
     {:post
      {:spec ::authentication/authentication#post.body
       :handler handle-authentication
       :summary "Authenticate user"
       :name "Authenticate user"
       :body "Authenticate user"
       :description "TODO abstract description"
       :parameters {:body ::authentication/authentication#post.body}}}]]])
I've tried :body and :name. It doesn't work. Any idea ?

dgb2311:03:43

are you talking about ::authentication/authentication#post.body or that you pass this to the :body ? If it's the former you should be able to define it as you wish? If it's the latter, that's part of the http standard. You put the data you want to send into the body of a HTTP message. Sorry if it's obvious, but I'm confused.

Alexandre EL-KHOURY11:03:34

Sorry if I'm not clear. I'm talking about the rs.metav.request.api.authentication/authentication#post.body in the screenshot. I want to change it to something else in the swagger. By default, it takes the path of the body param which I dont want.

p-himik11:03:30

Ah, I wondered where that # came from. :) Just in case - while it works, note that it's not a valid character for keywords. No idea whether it can lead to any problems, but I wouldn't use it.

Alexandre EL-KHOURY11:03:32

I've tried without it, it didnt solve my problem

p-himik11:03:42

Yeah, that was beside the main point.

p-himik12:03:50

Judging by the source, you can require [spec-tools.core :as st] and then wrap you spec in (st/spec ... {:name "body data"}).

Alexandre EL-KHOURY12:03:07

Thanks, will check it out !!!

👍 2
brianwitte15:03:50

I was curious how this page was generated -> https://clojuredocs.org/clojure.core and I get most of the way there with (sort (keys (ns-publics 'clojure.core))) minus def seemingly. I assume that because def is needed for the vars in https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj that it's implementation is actually somewhere else. Perhaps I am overlooking a few things, but I thought this would be a good question for this channel. 😄

pez15:03:36

defis a ”special function”, if that's what you mean?

dpsutton15:03:58

more commonly called a “special form”

🤝 2
brianwitte15:03:57

@U0ETXRFEW yes? I think so, haha. I am actually unfamiliar

pez15:03:57

Oh, I meant a special form. Thanks @U11BV7MTK!

pez15:03:37

@U03810HB7QS Clojure the language is bootstrapped from some first special forms, 14 I think they are. def fn, are amongst them. Then core functions and macros are build from these, and then more core functions and macros built from these and so on. You might want to try the Getting Started REPL in Calva, which tries to get this across in an interactive way, using the REPL.

pez15:03:58

Actually, looking at core.clj I see that fn is defined there.

(def
 ^{:macro true
   :added "1.0"}
 fn (fn* fn [&form &env & decl] 
         (.withMeta ^clojure.lang.IObj (cons 'fn* decl) 
                    (.meta ^clojure.lang.IMeta &form))))
It is a special special form. 😃

pez15:03:40

Or, rather, a macro using the fs* special form.

✔️ 2
brianwitte15:03:43

reading this closely -> https://clojure.org/reference/special_forms is filling in some blanks, for sure

practicalli-johnny16:03:45

If its of interest, I did a small random function generator which would show a public function (and its metadata) from clojure.core or any namespace provided https://practical.li/clojure/simple-projects/random-clojure-function/

👀 2
✔️ 2
Ben Lieberman16:03:43

Is there a way to type hint (or cast) from a direct child of java.lang.Object to Object? Trying to type hint function args seems to have no effect. And I'm curious why if I write some (contrived) code like (type (first (object-array ["foo"]))) I still get java.lang.String.

p-himik16:03:55

Because type works at run time. Does ^Object not work?

Ben Lieberman16:03:41

It does, another case of me following a red herring 😅 thanks @U2FRKM4TW

👍 2
Jakub Šťastný21:03:38

Hey guys. Spec question:

(s/def ::year (s/and int? #(< 1900 % 2150)))
(s/def ::month (s/and int? #(<= 1 % 12)))
(s/def ::start-date (s/keys :req [::year ::month]))

(generate (s/gen ::year)) ; Gave 2039
(generate (s/gen ::month)) ; Gave 7
This is all nice, simple and it works. The issue is when I try (generate (s/gen ::start-date)), which fails with:
Error: Couldn't satisfy such-that predicate after 100
What the hell? It's just composing two fields together.

Jakub Šťastný21:03:43

It's CLJS, but that shouldn't matter.

hiredman21:03:57

that is just sort of how s/and works

hifumi12321:03:36

I would personally use :req-un in the keys spec and if it still fails, write a generator function

hiredman21:03:52

req or req-un has nothing to do with it

2
hifumi12321:03:19

the sad reality of spec is that most interesting schemas are going to be too hard to generate randomly and youll simply have to write generators for most of your stuff, but once you get the hang of it, it's not bad

hiredman21:03:58

it has to do with the internals of test.check, which is what does the generating

hiredman21:03:15

the way s/and is implemented is using such-that from test.check

hiredman21:03:29

such-that basically acts as a filter on a generator

ghadi21:03:48

what you're encountering is that some generator making guesses about the inputs to your arbitrary function, and it might take a very long time if you have a picky predicate

ghadi21:03:57

so it gives up rather than heats the universe

hiredman21:03:05

so (s/and int? #(< 1900 % 2150)) generates using int? and then throws away stuff that doesn't pass #(< 1900 % 2150)

2
hiredman21:03:37

so the more a/and stuff you combine together the less likely it is that you will be able to generate something successfully

hifumi12321:03:52

hmm... maybe s/int-in will work better for year and month here

👍 2
Jakub Šťastný21:03:52

Confirmed: s/int-in is the solution here.

Jakub Šťastný21:03:58

Thanks everybody!

👍 2
p-himik21:03:26

And for other similar cases, s/and or not, it should be possible to write a custom generator.

Jakub Šťastný21:03:59

I'm a lazy bastard. If the computer can do it, why should I? @U2FRKM4TW

p-himik21:03:53

Because sometimes the computer can't do it.

hifumi12321:03:56

well, I expect s/int-in and related stuff to have reasonably "good" generators ootb, but again, any interesting spec, like a string representing a valid IP address, will simply need a custom generator

Jakub Šťastný21:03:51

That's fair enough.

hifumi12321:03:57

sometimes randomly generated data isnt specific enough to conform to your spec and give you useful samples of valid data, that's why custom generators are important

hifumi12321:03:43

it lets you randomly generate data in a way that hopefully gives you valid samples quickly