oliyh / martian

The HTTP abstraction library for Clojure/script, supporting OpenAPI, Swagger, Schema, re-frame and more
MIT License
542 stars 44 forks source link

Custom `:parameter-aliases` #150

Open onetom opened 2 years ago

onetom commented 2 years ago

Problem

In our applications, we are aiming to use fully qualified keywords whenever possible, to reduce ambiguity in data.

Martian contributes to attaining this goal, by allowing API request keys in kebab form, via the :parameter-aliases mechanism, lowering the impedance mismatch between the data structures of external data sources and Clojure programs internal nomenclature.

While this :parameter-aliases mechanism seems to be an internal API, since its structure is not explained, I assumed it might be customisable on a per-handler basis, but the automatically generated aliases override the custom ones.

  (-> "api-url"
      (martian/bootstrap
        [{:parameter-aliases
          {:path-schema
           {[] {:qb.company/realm-id :realmId}}}}])
      :handlers first :parameter-aliases :path-schema)
=> {}

or more specifically:

(-> "api-url"
      (martian/bootstrap
        [{:path-schema {:realmId s/Str}
          :parameter-aliases
          {:path-schema
           {[] {:qb.company/realm-id :realmId}}}}])
      :handlers first :parameter-aliases :path-schema)
=> {[] {:realm-id :realmId}}

Solution

I can introduce a custom interceptor, which would also do this kind of aliasing, but if martian.core/enrich-handler would be modified as follows:

(defn- enrich-handler [handler]
  (-> handler
      (update :parameter-aliases
              (fn [aliases]
                (reduce (fn [aliases parameter-key]
                          (update aliases parameter-key
                                  #(merge-with merge % (parameter-aliases (get handler parameter-key)))))
                        aliases
                        parameter-schemas)))))

then, in the example above, the per-handler, custom, namespaced aliases would be retained:

(-> "api-url"
      (martian/bootstrap
        [{:path-schema {:realmId s/Str}
          :parameter-aliases
          {:path-schema
           {[] {:qb.company/realm-id :realmId}}}}])
      :handlers first :parameter-aliases :path-schema)
=> {[] {:qb.company/realm-id :realmId, :realm-id :realmId}}

@oliyh, would you consider incorporating such a change or is it a bad idea, to piggyback this feature for providing handcrafted aliases?

onetom commented 2 years ago

btw, currently im working the problem around by retouching the handlers of a bootstrapped martian api:

(defn req-param-alias
  "Attach a parameter alias for a martian handler."
  [martian-handler key-alias schema & param-key-path]
  (-> martian-handler
      (assoc-in [:parameter-aliases
                 schema
                 (vec (butlast param-key-path))
                 key-alias]
                (last param-key-path))))

(defn mk-api
  [api-url handlers opts]
  (-> api-url
      (martian/bootstrap handlers opts)
      (update :handlers
              (partial map #(req-param-alias
                              % :qb.company/realm-id :path-schema :realmId)))))

but this adds an extra wrapper layer around martian.core/bootstrap...

oliyh commented 2 years ago

Hello,

Yes, I am in favour of letting data flow through and for any 'enriching' to be additive and not destructive, so what you propose sounds good. If you make a PR, there is a spec at https://github.com/oliyh/martian/blob/master/core/src/martian/spec.cljc describing the martian input data structure, you'll need to update that to include the :parameter-aliases key.

Thanks