Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POST body: how to translate camel case JSON to Clojure kebab? #104

Open
promesante opened this issue Dec 9, 2019 · 4 comments
Open

POST body: how to translate camel case JSON to Clojure kebab? #104

promesante opened this issue Dec 9, 2019 · 4 comments

Comments

@promesante
Copy link

Hi,

I am implementing a little REST API with Duct / Integrant, following Duct's Guide and this tutorial.

POST body JSON described there is case agnostic, so it's not clear to me how to translate a typical camel case JSON into typical kebab case Clojure maps.

Thanks in advance !

Luis
https://promesante.github.io/
https://github.com/promesante

@weavejester
Copy link
Collaborator

Duct uses Ring and the Muuntaja middleware to handle JSON, so you can do this in several different ways. You probably want to start with a key conversion library like camel-snake-kebab, and then either insert your own middleware, or more elegantly, update the options on the Muuntaja middleware.

The Muuntaja middleware handles content negotiation, and is configured by the :duct.middleware.web/format key, which is added automatically by the :duct.module.web/api module. You can just override that key in your configuration and add the options you want. It looks like there might be a :decoder-key-fn option in Muuntaja, but you'd need to investigate that.

@promesante
Copy link
Author

hi James,

I am trying to "update the options on the Muuntaja middleware", as suggested.

In REPL, everything seems to work fine till invocation to function deep-merge, inside :duct.middleware.web/format.

> (require '[muuntaja.core :as mc])
> (require '[camel-snake-kebab.core :as csk])
> mc/default-options
...
 :formats
 {"application/json"
  {:name "application/json",
   :encoder [#function[muuntaja.format.json/encoder]],
   :decoder
   [#function[muuntaja.format.json/decoder] {:decode-key-fn true}],
   :return nil,
   :matches nil},
...
> (defn deep-merge [a b]
  (if (and (map? a) (map? b))
    (merge-with deep-merge a b)
    b))
> (def formats (let [current-decoder (get-in mc/default-options [:formats "application/json" :decoder])
		     new-decoder (assoc-in current-decoder [1] {:decode-key-fn #(keyword (csk/->kebab-case %))})
		     current-encoder (get-in mc/default-options [:formats "application/json" :encoder])
		     new-encoder (conj current-encoder {:encode-key-fn #(name (csk/->camelCase %))})]
		 {:formats {"application/json" {:decoder new-decoder :encoder new-encoder}}}))
> (deep-merge mc/default-options formats)
...
 :formats
 {"application/json"
  {:name "application/json",
   :encoder
   [#function[muuntaja.format.json/encoder]
    {:encode-key-fn #function[dev/fn--16866/fn--16869]}],
   :decoder
   [#function[muuntaja.format.json/decoder]
    {:decode-key-fn #function[dev/fn--16866/fn--16867]}],
   :return nil,
   :matches nil},
...

I try to implement that using integrant the following way:

In config.edn:

 :duct.module.web/api {}

 :authorizer.serializations/formats {}
 :duct.middleware.web/format {:formats #ig/ref :authorizer.serializations/formats}

Implementation:

(ns authorizer.serializations
  (:require [clojure.data.json :as json]
            [integrant.core :as ig]
            [muuntaja.core :as mc]
            [camel-snake-kebab.core :as csk]))

(defmethod ig/init-key ::formats [_ _]
  (let [current-decoder (get-in mc/default-options [:formats "application/json" :decoder])
        new-decoder (assoc-in current-decoder [1] {:decode-key-fn #(keyword (csk/->kebab-case %))})
	current-encoder (get-in mc/default-options [:formats "application/json" :encoder])
	new-encoder (conj current-encoder {:encode-key-fn #(name (csk/->camelCase %))})]
    {"application/json" {:decoder new-decoder :encoder new-encoder}}))

However, when running (go) in integrant.repl, I get:

dev> (go)
Execution error (IllegalArgumentException) at duct.core/expand-ancestor-keys (core.clj:69).
No implementation of method: :kv-reduce of protocol: #'clojure.core.protocols/IKVReduce found for class: muuntaja.middleware$wrap_format$fn__12129

Error fully reported:

  Show: Project-Only All 
  Hide: Clojure Java REPL Tooling Duplicates  (10 frames hidden)

1. Unhandled java.lang.IllegalArgumentException
   No implementation of method: :kv-reduce of protocol:
   #'clojure.core.protocols/IKVReduce found for class:
   muuntaja.middleware$wrap_format$fn__12129

          core_deftype.clj:  583  clojure.core/-cache-protocol-fn
             protocols.clj:  175  clojure.core.protocols/fn/G
                  core.clj: 6856  clojure.core/reduce-kv
                  core.clj: 6847  clojure.core/reduce-kv
                  core.clj:   69  duct.core/expand-ancestor-keys
                  core.clj:   68  duct.core/expand-ancestor-keys
                  core.clj:   79  duct.core/merge-configs*
                  core.clj:   77  duct.core/merge-configs*
             ArraySeq.java:  111  clojure.lang.ArraySeq/reduce
                  core.clj: 6827  clojure.core/reduce
                  core.clj: 6810  clojure.core/reduce
                  core.clj:   86  duct.core/merge-configs
                  core.clj:   81  duct.core/merge-configs
               RestFn.java:  421  clojure.lang.RestFn/invoke
                  core.clj:  254  duct.core/eval8839/fn/fn
                  core.clj:  145  duct.core/fold-modules/fn
                 core.cljc:  280  integrant.core$fold$fn__8331/invoke
             ArraySeq.java:  116  clojure.lang.ArraySeq/reduce
                  core.clj: 6827  clojure.core/reduce
                  core.clj: 6810  clojure.core/reduce
                 core.cljc:  280  integrant.core$fold/invokeStatic
                 core.cljc:  272  integrant.core$fold/invoke
                  core.clj:  145  duct.core/fold-modules
                  core.clj:  139  duct.core/fold-modules
                  core.clj:  182  duct.core/build-config
                  core.clj:  173  duct.core/build-config
                  core.clj:  193  duct.core/prep-config
                  core.clj:  184  duct.core/prep-config
                   dev.clj:   30  dev/eval10170/fn
                  repl.clj:   16  integrant.repl/prep/fn
                  AFn.java:  154  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  Var.java:  308  clojure.lang.Var/alterRoot
                  core.clj: 5510  clojure.core/alter-var-root
                  core.clj: 5505  clojure.core/alter-var-root
               RestFn.java:  425  clojure.lang.RestFn/invoke
                  repl.clj:   16  integrant.repl/prep
                  repl.clj:   14  integrant.repl/prep
                  repl.clj:   54  integrant.repl/go
                  repl.clj:   53  integrant.repl/go
                      REPL:  251  dev/eval16874
                      REPL:  251  dev/eval16874
             Compiler.java: 7176  clojure.lang.Compiler/eval
             Compiler.java: 7131  clojure.lang.Compiler/eval
                  core.clj: 3214  clojure.core/eval
                  core.clj: 3210  clojure.core/eval
                  main.clj:  414  clojure.main/repl/read-eval-print/fn
                  main.clj:  414  clojure.main/repl/read-eval-print
                  main.clj:  435  clojure.main/repl/fn
                  main.clj:  435  clojure.main/repl
                  main.clj:  345  clojure.main/repl
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   79  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   55  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  142  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  171  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  170  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  748  java.lang.Thread/run

What am I making wrong?

Thanks in advance...

Luis
https://promesante.github.io/
https://github.com/promesante

@weavejester
Copy link
Collaborator

You have it almost right; you just need to put your keys into a profile:

 :duct.module.web/api {}

 :duct.profile/base
 {...
  :authorizer.serializations/formats {}
  :duct.middleware.web/format {:formats #ig/ref :authorizer.serializations/formats}}

The outer configuration is for modules and profiles (which are currently a type of module). Non-module keys need to be put into a profile.

The next version of Duct will change the design a little to make the distinction between component keys and module keys more obvious.

@promesante
Copy link
Author

It worked !

Thank you very much, James, for the quick, right replies !

Luis
https://promesante.github.io/
https://github.com/promesante

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants