Проверка карты известных и неизвестных пар "ключ-значение" с помощью Spec

Я хочу создать спецификацию clojure для проверки :multipart параметра http-запроса, созданного reitit.ring.middleware.multipart промежуточным программным обеспечением.

Данные составной формы должны содержать определенные параметры, которые можно проверить с помощью s/keys, и любое количество файлов с произвольным именем параметра.

Карта для проверки будет выглядеть так:

           {:visualisation "vis"
            :file-xy       {:filename     "foo.png",
                            :content-type "image/png",
                            :tempfile     "C:\\Temp\\ring-multipart-123.tmp",
                            :size         295281}
            :file-abc      {:filename     "bar.png",
                            :content-type "image/png",
                            :tempfile     "C:\\Temp\\ring-multipart-456.tmp",
                            :size         42}}

Я могу проверить файлы со спецификацией reitit.ring.middleware.multipart/temp-file-part следующим образом:

    (s/def :multipart/files (s/map-of :multipart/param multipart/temp-file-part))

Собрав все вместе, я придумал спецификацию, которая проходит, но позволяет всем неизвестным параметрам быть либо файлом, либо строкой:

(s/def :multipart/param keyword?)
(s/def :multipart/visualisation string?)
(s/def :multipart/items (s/map-of :multipart/param (s/or :file multipart/temp-file-part :visualisation string?)))
(s/def :visualisation/files (s/and (s/keys :req-un [:multipart/visualisation])
                                   :multipart/items))

Как я могу определить спецификацию для карты с определенными ключами и валидатором значений для других ключей?


person bubblefoil    schedule 20.11.2019    source источник


Ответы (1)


Вы можете добавить функцию предиката, чтобы убедиться, что только одно из значений карты является строкой:

(s/def :visualisation/files
  (s/and (s/keys :req-un [:multipart/visualisation])
         #(= 1 (count (filter string? (vals %))))
         :multipart/items))

(s/conform :visualisation/files
  {:visualisation "vis"
   :file-xy       {:filename "" :content-type "" :tempfile "" :size 0}})

(s/conform :visualisation/files
  {:visualisation "vis"
   :file-abc      "not valid"
   :file-xy       {:filename "" :content-type "" :tempfile "" :size 0}})

Но универсальная спецификация s/or создаст избыточные теги, если вы используете вывод conform.

Другим вариантом может быть использование двух проверок спецификаций:

(def data
  {:visualisation "vis"
   :file-xy       {:filename "" :content-type "" :tempfile "" :size 0}})

(s/conform (s/keys :req-un [:multipart/visualisation])
           data)

(s/conform (s/map-of :multipart/param :multipart/temp-file-part)
           (dissoc data :visualisation))

Если вы контролируете форму данных или можете изменить ее, я бы подумал о чем-то вроде этого, что было бы проще указать:

{:visualisation "foo"
 :files {:file-1 {,,,}
         :file-2 {,,,}}}
person Taylor Wood    schedule 21.11.2019