meander 2021-09-21

hello, trying to figure how to parte pedestal-like nested URL format using meander, for example, go from

["v1"
 ["/foo"
  ["/baz" {:post baz-handler}]]
 ["/bar"
  {:get  bar-handler
   :post post-bar}]]
to
[{:url "v1/foo/baz" :method :post :handler baz-handler}
 {:url "v1/bar" :method :get :handler bar-handler}
 {:url "v1/bar" :method :post :handler post-bar}]

I guess I gotta use cata, but not sure how

(m/rewrite '["v1"
             ["/foo"
              ["/baz" {:post baz-handler}]]
             ["/bar" {:get bar-handler :post post-bar}]]
  (m/with [%1 (m/map-of !methods !handlers)
           %2 [!paths . (m/or %2 %1) ...]]
          %2)
  [{:url !paths :method !methods :handler !handlers} ...])

often with is simpler than cata

but now I see that it is not quite so, because the paths have been concatet

cool, that almost solves, the missing part is accumulating the previous routes

honestly, I don't know how to do this

but it'll keep me busy tonight xD

haha, would love to hear, Iโ€™m trying to figure on my end as well, if I figure Iโ€™ll post here

Give this a try

The first rewrite does the data re-organization that you're looking for but suffers from nested vectors (this is often a result of using cata).

The second rewrite "flattens" the structure

cool, I guess the last rewrite can be replaced with flatten, got the same result here

thanks @markaddleman!

๐Ÿ‘ 1

not sure if its a fixable issue, but when I tried with a real longer structure, it did stack overflow ๐Ÿ˜…

๐Ÿคฃ 1

It's funny - I'm so used to using meander that I reached for meander to flatten the data without even thinking of flatten ๐Ÿ™‚

cata does stack-based recursion so a sufficiently deep structure could result in an overflow, I guess

Let me see if I can rewrite it to avoid some of the recursion

I donโ€™t think its that deep, from a quick look I guess its less than 10 depth

Can you post the structure?

stack overlow occurs when cata loops indefinitely

I assume you're getting the SO from the first rewrite?

sadly no, but there are a few other patterns that needs to be taken in account

and I guess those are screwing it up

Unfortunately, I'm not very familiar with these routing data structures

(m/rewrite 1
  (m/app inc ?x)
  (m/cata ?x))

I recommend using cata with instructions

I can make a shorter version that has more of those features, let me check

is more readable and harder to error with an infinite loop

(m/rewrite [:start 1]
  [:start (m/app inc ?x)]
  (m/cata [:end ?x])
  [:end ?x]
  ~(str "end with ?x = " ?x))

you can easily set checkpoints and also check which instruction will be executed next

found a simple stack overflow example:

'["v1"
         ["/foo"
          ["/baz" {:post baz-handler}]
          ["/other" []]]
         ["/bar"
          {:get  bar-handler
           :post post-bar}]]

sometimes instead of a map, its a vector with interceptors on the right side

ah, yeah. I can see how this would overflow

I'll take a crack at this data structure

feels a little ambiguous case, curious to see what you come with for that ๐Ÿ˜‰

you read my mind

unctions? fI'm guessing the vector of interceptors is always a vector of

functions?

What does the result for this look like?

the real one looks more like this:

'["v1"
        ["/foo"
         ["/baz" {:post baz-handler}]
         ["/other" [[:interceptor
                     [{:features    {:policy-authz-enabled? false},
                       :interceptor [:scope
                                     {:scope "xxx"}]}
                      {:features    {:policy-authz-enabled? true},
                       :interceptor [:scope
                                     [{:policy "yyy"}]]}]]]]]
        ["/bar"
         {:get  bar-handler
          :post post-bar}]]

but Iโ€™m not sure all possible variations (like if they always start with double vector)

I've got a meeting in 5 minutes but I'll pick this up after

thanks, Iโ€™ll keep playing here too

seems like a notable difference is that those never start with strings at the beginning of the vector

only routes do that

(m/rewrite '["v1"
             ["/foo"
              ["/baz" {:post baz-handler}]]
             ["/bar" {:get bar-handler :post post-bar}]]
  (m/with [%1 (m/map-of !methods !handlers)
           %2 [(m/app #(str (last !paths) %) !paths) . (m/or %2 %1) ...]]
    %2)
  [{:url !paths :method !methods :handler !handlers} ...])

I can't make it any shorter @wilkerlucio

looks nice!

on the large example it didnโ€™t match though =/

but Iโ€™ll play with it

it probably because of the interceptors in the middle

what should be returned from the longer one?

\other should be omitted?

omitting other is good

the longer one currently returns nil, so something is going bad in the middle, Iโ€™m trying to get a case to reproduce with your solution

(m/rewrite '["v1"
             ["/foo"
              ["/baz" {:post baz-handler}]
              ["/other" [[:interceptor
                          [{:features    {:policy-authz-enabled? false} ,
                            :interceptor [:scope
                                          {:scope "xxx"}]}
                           {:features    {:policy-authz-enabled? true} ,
                            :interceptor [:scope
                                          [{:policy "yyy"}]]}]]]]]
             ["/bar"
              {:get bar-handler
               :post post-bar}]]
  (m/with [%1 (m/map-of !methods !handlers)
           %2 [(m/pred string?) _]
           %3 [(m/app #(str (last !paths) %) !paths) . (m/or %3 %2 %1) ...]]
    %3)
  [{:url !paths :method !methods :handler !handlers} ...])

\other catches under %2, which is defined in the simplest possible way, [string? whatever]

here is an example thatโ€™s going off, but not the main problem yet:

(m/rewrite '["v1"
               ["/foo"
                [:some-interceptors]
                ["/baz" {:post baz-handler}]
                ["/other" [[:interceptor
                            [{:features    {:policy-authz-enabled? false},
                              :interceptor [:scope
                                            {:scope "xxx"}]}
                             {:features    {:policy-authz-enabled? true},
                              :interceptor [:scope
                                            [{:policy "yyy"}]]}]]]]]
               ["/bar" {:get bar-handler :post post-bar}]]
    (m/with [%1 (m/map-of !methods !handlers)
             %2 [(m/pred string?) _]
             %3 [(m/app #(str (last !paths) %) !paths) . (m/or %3 %2 %1) ...]]
      %3)
    [{:url !paths :method !methods :handler !handlers} ...])

probably with a more complicated example it will fall in a different place

(in this case the :some-interceptors should be ignored)

anyway you guys are meander masters, its already really helpful ๐Ÿ™‡

not so much I see that it is probably wrong, because it does not return a map with the path :url foo/bar ๐Ÿ˜•

but I'm glad I could help a little.

It would be nice if @noprompt offered a solution

I have no idea how to better concatenate urls into one, and what I've done doesn't always work

the examples you posted are nice as well, Iโ€™ll spend some time to understand and learn those patterns ๐Ÿ™‚

Thanks for showing with @huxley ! I was aware of it but I don't think I've ever used it before

this worked!

(m/rewrite
    (-> data :ig/system :pipo.module/web-routes :routes ffirst)
    
    (m/with [%route-map (m/map-of !methods !handlers)
             %item [(m/pred string?) _]
             %interceptors [(m/pred keyword?) . _ ...]
             %all [(m/app #(str (last !paths) %) !paths) . (m/or %all %item %interceptors %route-map) ...]]
      %all)
    [{:url !paths :method !methods :handler !handlers} ...])

thank you again folks!

I celebrated too soon, @huxley I found this way to concatenate is not doing the stack as expected, on the base example it outputs:

[{:url "v1", :method :post, :handler baz-handler}
 {:url "v1/foo", :method :get, :handler bar-handler}
 {:url "v1/foo/baz", :method :post, :handler post-bar}]

but it should be

({:url "v1/foo/baz", :method :post, :handler baz-handler}
 {:url "v1/bar", :method :get, :handler bar-handler}
 {:url "v1/bar", :method :post, :handler post-bar})
(which is the result given frmo the @markaddleman solution)

now trying to work more on the mark example to add the interceptors part

I thought you guys already had a solution so I didn't pick up the interceptor piece. I'll give it a look tomorrow morning

thanks, this is the example Iโ€™m working on top of now:

'["v1"
        ["/foo"
         [:some-interceptors]
         ["/baz" {:post baz-handler}]]
        ["/bar" {:get bar-handler :post post-bar}]]

you can forget the value as vectors, that wasnโ€™t a thing, just the interceptors in the middle ๐Ÿ™‚

got it ๐Ÿ˜„ ๐ŸŽ‰

(-> '["v1"
        ["/foo"
         [:bla :me []]
         ["/baz" {:post baz-handler}]]
        ["/bar" {:get bar-handler :post post-bar}]]
      (m/rewrite
        [?path (m/map-of !action !handler)]
        [{:url ?path :method !action :handler !handler} ...]

        [?path-prefix [(m/pred keyword?) & _] & ?rest]
        (m/cata [?path-prefix & ?rest])

        [?path-prefix [?path-suffix & ?deeper-or-handlers]]
        (m/cata [(m/app str ?path-prefix ?path-suffix) & ?deeper-or-handlers])

        [?path-prefix . !deeper ...]
        [(m/cata [?path-prefix !deeper]) ...])
      flatten)
that was fun to learn! thanks folks

๐ŸŽ‰ 1

Sorry I missed this conversation. I was out with a few doctor appointments for myself and my son. ๐Ÿ˜