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

Spec form metadata is lost on visiting it during Swagger UI generation #417

Open
metametadata opened this issue Apr 3, 2019 · 1 comment

Comments

@metametadata
Copy link
Contributor

Library Version(s)

2.0.0-alpha29

Problem

I need to redefine spec-tools.visitor/visit for my custom spec to change how spec is shown in Swagger UI. E.g. I have closed-keys custom spec, but want to visit it as s/keys, so that Swagger UI generates appropriate model examples from my spec.

Now, it can be cumbersome to parse the custom spec form to implement its visiting. So I want instead to rely on the metadata attached to the spec form (such easy to use metadata can be conveniently generated by closed-keys macro). I.e.:

(defmethod st-visitor/visit-spec `closed-keys
  [spec accept options]
  (let [form (st-impl/extract-form spec)
        keys-form <generate from (meta form)>]
   (st-visitor/visit-spec keys-form accept options)))

But this currently doesn't work because (meta form) is always nil in this function.

I only managed to trace it back to spec-tools.visitor/visit function: it seems to get already crooked spec with no metadata in its form.

The issue is reproducible for custom specs used in :return and :body. I didn't test other places.

Steps:

  1. Code:
(ns app.foo.handler
  (:require [compojure.api.sweet :as c]
            [ring.util.http-response :as r]
            [clojure.spec.alpha :as s]
            [spec-tools.visitor :as st-visitor]
            [spec-tools.impl :as st-impl]))

; Helper to redefine spec form
(defn -with-form
  [spec form]
  {:pre [(s/spec? spec)]}
  (reify s/Spec
    (describe* [_] form)

    ; Do not modify other methods
    (conform* [_ x] (s/conform* spec x))
    (unform* [_ y] (s/unform* spec y))
    (explain* [_ path via in x] (s/explain* spec path via in x))
    (gen* [_ overrides path rmap] (s/gen* spec overrides path rmap))
    (with-gen* [_ gfn] (s/with-gen* spec gfn))))

; Create spec with a custom form and metadata attached to the form
(s/def ::my-spec (-> (s/spec int?)
                     (-with-form
                       (with-meta (list 'my-spec 1 2 3) {:my-spec-form-meta [1 2 3]}))))

; Custom visiting which relies on metadata from the form
(defmethod st-visitor/visit-spec 'my-spec
  [spec _accept _options]
  (let [form (st-impl/extract-form spec)]
    (prn :VISITED-FORM form :META (meta form))
    nil))

; Handler code
(c/context "/my" []
    (c/POST "/foo" []
      :return ::my-spec
      ;:body [x ::my-spec]
      (r/ok)))
  1. Run it and navigate to Swagger UI URL.
  2. Check console output.

Expected:

:VISITED-FORM (my-spec 1 2 3) :META {:my-spec-form-meta [1 2 3]}

Actual:

:VISITED-FORM (my-spec 1 2 3) :META nil

Workaround:

My current workaround is to attach metadata to the first symbol in the form instead of the whole form. Also in another case I just parse the form instead of relying on its metadata.

Interestingly, the issue is not reproducible when custom spec is "wrapped" by some other spec, specifically (s/coll-of ::my-spec) doesn't seem to have a problem.

@miikka
Copy link
Contributor

miikka commented Nov 2, 2019

I suspect this has something to do with compojure.api.coercion.spec/Specify wrapping the spec into a spec record with spec-tools.core/create-spec, but I'm not sure why that would cause problems.

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