metosin / metosin/compojure-api

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

Open
#417 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Clojure
Stars
1.1k
Forks
146
PR merge metrics
No merged PRs in 30d

Description

## 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.:

```clj
(defmethod st-visitor/visit-spec `closed-keys
[spec accept options]
(let [form (st-impl/extract-form spec)
keys-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:

```clj
(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)))
```
2) Run it and navigate to Swagger UI URL.
3) 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with spec-tools.visitor/visit and spec-tools.impl/extract-form, then run the provided reproduction against the Swagger UI generation path for custom specs in :return and :body. Compare the metadata on the extracted form with the working s/coll-of case; done means the expected metadata remains available to the custom visitor.

Written by the indexing model from the issue text.

Assessment

Tech stack
clojure
Domain
api
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.