clojurecompojurecompojure-api

avoiding repetition while using schema.core


I have defined the following schema:

(s/defschema Card
 {:cardNumber s/Str
  :cvv s/Str
  :creditCardMonthValidity s/Str
  :creditCardYearValidity s/Str
  :cpf s/Str
  :name s/Str
  :phoneNumber s/Str})

and then in a route I'm using the same keys in the JSON response:

(GET "/card" []
        :summary "fetches card info given the access token & checkout id"
        :query-params [accessToken :- String checkoutId :- String]
        :return Card
        (let [checkout  (CheckoutApi/show checkoutId accessToken)
              card      (.getCard checkout)
              contact   (.getContact checkout)
              (ok {:cardNumber (.getAccountNumber card)
                   :cvv "000"
                   :creditCardMonthValidity (.getExpiryMonth card)
                   :creditCardYearValidity (.getExpiryYear card)
                   :cpf (.getNationalID contact)
                   :name (.getFirstName contact)
                   :phoneNumber (.getPhoneNumber contact)})]))

is there an elegant way to avoid the repetitions of the key names? Something like a constructor method where i can just pass in the values? (maybe in some specific order)


Solution

  • something like this could work: you can define a macro that defines schema and constructor at once.

    user> (defmacro defresponse [schema constructor-name constructor-params data]
            `(do
               (s/defschema ~schema ~(into {} (map (fn [[k [t _]]] [k t])
                                                   data)))
               (defn ~constructor-name ~constructor-params
                 ~(into {} (map (fn [[k [_ init]]] [k init])
                                data)))))
    #'user/defresponse
    user> (defresponse Card card-response [card contact]
            {:cardNumber [s/Str (.getAccountNumber card)]
             :cvv [s/Str "000"]
             :creditCardMonthValidity [s/Str (.getExpiryMonth card)]
             :creditCardYearValidity [s/Str (.getExpiryYear card)]
             :cpf [s/Str (.getNationalID contact)]
             :name [s/Str (.getFirstName contact)]
             :phoneNumber [s/Str (.getPhoneNumber contact)]})
    

    this defresponse would be expanded into the following:

    (do
      (s/defschema
        Card
        {:cardNumber s/Str,
         :cvv s/Str,
         :creditCardMonthValidity s/Str,
         :creditCardYearValidity s/Str,
         :cpf s/Str,
         :name s/Str,
         :phoneNumber s/Str})
      (defn card-response [card contact]
        {:cardNumber (.getAccountNumber card),
         :cvv "000",
         :creditCardMonthValidity (.getExpiryMonth card),
         :creditCardYearValidity (.getExpiryYear card),
         :cpf (.getNationalID contact),
         :name (.getFirstName contact),
         :phoneNumber (.getPhoneNumber contact)}))
    

    and then you can use your schema as usual, and card-response like this:

    (let [checkout  (CheckoutApi/show checkoutId accessToken)
          card      (.getCard checkout)
          contact   (.getContact checkout)]
      (ok (card-response card contact)))
    

    (haven't tested this one, but it should work. otherwise will update it in the morning)