Fork me on GitHub
#clojure
<
2024-03-28
>
Leena Hyvönen08:03:11

A question about reitit - malli - ring.swagger stack: We have the following stack in use:

[metosin/malli "0.10.4"]
                 [metosin/reitit "0.6.0"]
                 [metosin/reitit-swagger "0.7.0-alpha7"]
We also have a datamodel defining object specs written in malli format and validated with malli. It is big and widely used so we can’t change it to any other spec type. To make our api to be able to run with Swagger in place, we have to explicitly define coercion to each endpoint referencing our datamodel, like this:
["/v1/object/:id"
    {:get {:coercion reitit.coercion.malli/coercion
           :parameters {:path {:id string?}}
           :responses  {201 {:body {:total string?}}}
           :produces "application/json"
           :handler (object-api/search-handler database)}}]
With that coercion in place, as simple thing as defining Swagger responses doesn’t work. The description above :responses {201 {:body {:total string?}}} yields to no responses in Swagger documentation. We also tried this
:responses  {201 {:body (malli/schema [:map
                             [:app_id {:optional true} string?]
                             [:app_key {:optional true} string?]])}} 
but same result. 😐 If we define mock objects with Plumatic schema like in the example here https://github.com/metosin/ring-swagger/blob/master/README.md, the responses part of the Swagger documentation works, but there are no sample datamodel objects in Swagger endpoint descriptions. So my question is that how can we make everything in Swagger work without converting our datamodel from malli to schema?

p-himik09:03:28

I'd try #C7YF1SBT3 and #CLDK6MFMK. I don't know what exactly makes the crucial difference, but I have it working with a similar setup. The differences that I notice: • I specify coercion at the router level, not the route level • The responses map is an unwrapped Malli schema: {200 {:body [:map ...]}} • The routes that end up being a part of the Swagger JSON have an :openapi key

Leena Hyvönen10:03:12

Tried all of the above but no difference. We start up the system using https://github.com/stuartsierra/component I wonder if that could affect somehow? :thinking_face:

p-himik10:03:36

Shouldn't be related at all. Oh, hold on - you should be using reitit 0.7.0-alpha7 (or newer, if it exists). The 0.7.0 branch has brought a lot of OpenAPI-related changes.

1
avi16:03:07

I’m using 0.7.0-alpha7 with Malli and I’m pretty happy with what I’m getting in the Swagger UI. I did have to upgrade metosin/ring-swagger-ui to {:mvn/version "5.9.0"} to make it all work together. Here’s an example of one of my routes that has various aspects that are showing up in Swagger UI:

["/"
 {:tags ["Projects"]
  :get {:handler project-handlers/get-coll
        :responses {200 {:content {:default {:schema project-schemata/Coll}}}}}
  :post {:summary "Create a new Project"
         :handler project-handlers/post-coll
         :request {:content {"application/json" {:schema project-schemata/CreateReq}}}
         :responses {201 {:description
                          (str "The new Project resource was successfully created."
                               "\n\n"
                               "Its URL is provided in the response header `Location`."
                               "\n\n"
                               "(Which *should* be listed here in the Swagger"
                               " UI but is not, for some reason. I'll try to"
                               " figure this out at some point.)")
                          ;; TODO: the headers are not showing up in the Swagger UI. Ask
                          ;; in Clojurians in #reitit about this.
                          :headers {"Location" {:description
                                                "The URL of the new Project resource"
                                                :schema URI}}
                          :content {:default {:schema project-schemata/OneWithHref}}}}}}]

Leena Hyvönen09:04:58

Thank you, now we got the response codes and str description working. Updating to 0.7.0-alpha7 to this exception java.lang.ClassNotFoundException: com.fasterxml.jackson.core.exc.StreamConstraintsException. Did not find any relevant information on that so for now on I think we are happy with just response codes and string descriptions for them. :)

p-himik10:04:27

What's the stacktrace of the exception?

Lidor Cohen09:03:36

Hello everyone 👋😄 Given a zipper: Can I edit some tree (specifically remove) and somehow (even hacky) commit the same changes to another tree (that is guaranteed to have the same shape)? If the answer is yes, can I also do that while for the first tree I either edt or remove nodes but just ignore edits (i.e just remove the same nodes from the other tree while ignoring edits)?

igrishaev09:03:20

Yes you can edit a tree using a zipper. There are functions called remove, alter, edit, etc. Pay attention, to commit the changes made into a data structure, you have to call zip/root Regarding reflecting your changes from treeA to treeB: technically it's possible by adding a side effect into a function that changes location in TreeA. But it looks strange. You can just process both trees independenty.

Lidor Cohen10:03:30

I need to map changes from one tree to another based on location. I can't really process both trees independently because information as to which nodes to remove exsits only in TreeA. Since there's a "commit" operation, I assumed it takes a tree and some set of operations to perform and returns a new tree, and so if those set of operations are independant of the actual values in the nodes then it should be possible to take the same set of operations and apply them to another tree with the same structure.

Max13:03:10

There’s nothing built into zippers to do this because it’s kind of out of scope for them, but you can do what you’re describing without any special-purpose features. Here’s what I would do: 1. Search in the first tree, while recording the actions I took to get to the node in question (even a list of clojure.zip functions would work fine). This isn’t so different from recording the path in djikstra’s algorithm. The function should return this list of actions. 2. Apply those actions to a zipper of the new tree. The zipper should now be located at the desired node 3. Remove the node

🙏 1
Lidor Cohen16:03:23

Thanks @U01EB0V3H39 that sounds pretty good

chucklehead17:03:45

Related to the above approach, you may be able to use the zipper traversal to construct a list of https://github.com/juji-io/editscript edits you can apply to the second tree using patch.

🙏 1
Lidor Cohen17:03:35

Thanks @U015879P2F8! I'll take a look

phill23:03:47

If counting on tree A forever to mirror tree B is as brittle as it sounds, you might consider putting sufficient info into tree B, as metadata perhaps!

timvisher15:03:53

Is there a way, in an EDN stream, to say "the things that follow are all in namespace 'blah'". What would essentially happen in a Clojure stream if you did (in-ns blah) ::foo ::bar ::bat (I'm probably getting some syntax wrong there. What I'm wondering if I can avoid in an EDN file is specifying the full ns over and over again.

p-himik15:03:39

user=> (do (in-ns 'x) [::a])
[:user/a]
x=>
:) Not possible, at least with clojure.edn. Some other EDN parser might have that capability. But that would be on the parser's side, not in the EDN itself.

timvisher15:03:06

That's what it looked like. Thanks for the confirmation. :)

Alex Miller (Clojure team)16:03:12

::keywords are a feature of Clojure's reader/syntax, not edn

avi16:03:33

Does edn support the syntax (I forget what it’s called) wherein one can specify a namespace for all the keys in a map, in front of the map? jet seems to think it does:

echo '#:com.foo.bar{:baz :quux}' | jet
{:com.foo.bar/baz :quux}

p-himik16:03:21

Oh, hold on - that's a bit wrong. The tagged elements are supported, but #: in particular isn't listed.

Alex Miller (Clojure team)16:03:36

Namespace map syntax is not part of the edn spec, however Clojure’s edn reader will read namespaced (not aliased or autoresolved) map syntax

Alex Miller (Clojure team)16:03:56

(because there is no namespace context)

timvisher18:03:30

@U064X3EF3 Yeah the thought that I had was something along the lines of XML namespaces support. I hadn't ever heard of anything like that around EDN of course but I figured I'd ask. :)

jjttjj19:03:27

I'm trying to get a basic clojure repl service running using the clojure cli with systemd (which I don't have much experience with). It seems the clojure process keeps immediately exiting with a success status, even though if I start the repl with the same exact command it does wait for user input. More in 🧵

jjttjj19:03:40

This is my unit file.

[Unit]
Description=Clojure REPL Server
After=network.target

[Service]
Type=simple
User=ubuntu
ExecStart=/usr/local/bin/clojure -Sdeps '{:aliases {:repl-server {:jvm-opts ["-Dclojure.server.repl={:port,6661,:accept,clojure.core.server/repl}"]}}}' -M:repl-server
Restart=on-failure

[Install]
WantedBy=multi-user.target
But then when I start it it seems to just say Succeeded immediately:
sudo systemctl daemon-reload
sudo systemctl start repl
sudo systemctl status repl

● repl.service - Clojure REPL Server
Loaded: loaded (/etc/systemd/system/repl.service; enabled; vendor preset: enabled)
Active: inactive (dead) since Thu 2024-03-28 18:55:11 UTC; 818ms ago
Process: 13887 ExecStart=/usr/local/bin/clojure -Sdeps {:aliases {:repl-server {:jvm-opts ["-Dclojure.server.repl={:port,6661,:accept,clojure.core.server/repl}"]}}} -M:repl-server (code=exited, status=0/SUCCESS)
Mar 28 18:55:10 ip-172-31-32-172 systemd[1]: Started Clojure REPL Server.
Mar 28 18:55:10 ip-172-31-32-172 clojure[13887]: Clojure 1.11.2
Mar 28 18:55:10 ip-172-31-32-172 clojure[13887]: user=>
Mar 28 18:55:11 ip-172-31-32-172 systemd[1]: repl.service: Succeeded.
Any tips to hints about how to keep it running?

p-himik19:03:13

My first guess is that the process exits because the stdin is closed. E.g. try echo | clj -r.

jjttjj19:03:12

Oh yeah that exits immediately. So I think I just need to wait for it to start receiving input

p-himik19:03:47

Try adding -e '@(promise)' at the end. No clue whether it'll work though - it might hang the whole service.

jjttjj19:03:42

Ah, that does it, thanks!

p-himik19:03:31

Cool, thanks for confirming.

Alex Miller (Clojure team)19:03:47

there is a flag you can pass for this in the https://clojure.org/reference/repl_and_main#_launching_a_socket_server: :server-daemon=false

👍 2
wow 1
Alex Miller (Clojure team)19:03:00

then you shouldn't need the -e

jjttjj19:03:32

Ah, thanks! I saw that but thought it meant the opposite

jjttjj19:03:55

Actually now that I'm reading the doc again, it was always perfectly clear :man-facepalming:

jjttjj22:03:29

I thought the :server-daemon false thing had worked but I think I didn't reload the systemd unit config properly, and the program was still exiting immediately in the systemd logs. I also tried the --repl main option which also didn't work. It only works with -e '@(promise)' for me.

[Unit]
Description=clojure repl server
After=network.target

[Service]
Type=simple
User=ubuntu
ExecStart=/usr/local/bin/clojure -Sdeps '{:deps {}, :aliases {:repl-server {:jvm-opts ["-Dclojure.server.repl={:port,6661,:accept,clojure.core.server/repl,:server-daemon,false,:client-daemon,false}"]}}}' -M:repl-server
SuccessExitStatus=143
Restart=on-failure

[Install]
WantedBy=multi-user.target

hlship23:03:35

So, I'm trying to setup my user.clj to set *print-namespace-maps* to default to false, not true. I think there's something challenging going on because the user namespace is loaded before the REPL goes interactive. The end result is that if I call (set! *print-namespace-maps* false), that fails with a Can't change/establish root binding of ... exception. However, if I use (alter-var-root #'*print-namespace-maps* (constantly false)) that doesn't seem to change the value, at runtime ... something in nrepl, or clojure.main/repl is rebinding the var. So am I missing something obvious about how to set this up (ideally, to work the same from lein repl or using Cursive).

hlship23:03:01

You'd think I would have figured this out in the last, say, 12 years or so ...

hiredman23:03:19

user.clj cannot be used to set any bindings for the repl, it is loaded outside of the repl and the only bindings in place for it are those that the compiler pushes before and pops after loading a file, anything set! in user.clj won't be in effect after it is loaded

hiredman23:03:35

lein/nrepl has, I think some set of features maybe for establishing bindings for some vars (I think I have seen it used for reflection warnings, but the binding setup for print-namespace-maps is a little different)

hiredman23:03:17

I think for a clojure.main repl the only option is some kind of custom :init function when starting it, which means you'll need to start it yourself instead of relying on clojure.main to start it

p-himik00:03:12

Wouldn't -e (alter-var-root ...) help?

hiredman00:03:44

no, for the same reason the alter-var-root in user.clj won't

hiredman00:03:00

(the repl binds it to true at start up ignoring the root value)

dpsutton00:03:59

I’m away from my computer right now but I’ve tried to do the init trick in the past and I think it’s a different notion of initialization that won’t help here

dpsutton00:03:13

(Unless I’m mistaken. But that’s my memory)

dpsutton00:03:52

It uses (from memory) a function called with bindings but it is shadowing the one in clojure core and does a different thing

hiredman00:03:55

I believe init is run inside the context of the bindings the repl sets up

hiredman00:03:12

user=> (clojure.main/repl :init (fn [] (set! *print-namespace-maps* false)) :prompt (fn [] (print "inner repl> ") (flush)))
inner repl> *print-namespace-maps*
false
inner repl> nil
user=> *print-namespace-maps*
true
user=> 

dpsutton00:03:06

then i missed what i was trying to do a few months ago. Thanks!

dpsutton00:03:05

ah it uses clojure.main/with-bindings which i thought was clojure.core/with-bindings . And i was trying to return a map of binding vals (ie, {print-namespace-maps true} (can’t use earmuffs because of slack)) instead of mutating an established binding

dpsutton00:03:29

i have this as my default clojure.main/repl for eval: :eval (fn [f] (binding [clojure.test/*test-out* *out*] (eval f))) i can change that now