我无法让 compojure-api 正确验证具有 Inst 模式的查询参数的错误数据
I cant get compojure-api to correctly validate bad data for a query-parameter with an Inst schema
在这里,我使用 metosin/compojure-api
库为我的 api 配置 GET /fetch
端点。您会看到我还使用 plumatic/schema
来验证此端点上的查询参数输入,并使用 siilisolutions/humanize
来人性化任何错误数据异常。
(defn humanize-schema-exception [^Exception e]
(if (instance? schema.utils.ErrorContainer (ex-data e))
(#'humanize/explain (:error (ex-data e))
(fn [x]
(let [Inst java.util.Date]
(clojure.core.match/match
x
['not ['instance? Inst not-inst]]
(str "'" not-inst "' is not a timestamp.")
:else x))))))
(defn bad-request-handler
"Handles bad requests."
[f]
(fn [^Exception e data request]
(let [message (humanize-schema-exception e)]
(f message))))
(def app
(api
{:exceptions {:handlers
{::ex/request-parsing (parse-exception-handler response/bad-request)
::ex/request-validation (bad-request-handler response/bad-request)
::ex/default (exception-handler response/internal-server-error)}}
:swagger {:ui "/api/v1.0/docs"
:spec "/api/v1.0/swagger.json"}
(GET "/fetch" []
:query-params [{id :- schema/Int nil}
{timestamp_from :- schema/Inst nil}
{timestamp_to :- schema/Inst nil}]
:responses {200 {:description "ok"}
400 {:description "bad request"}} (foo id timestamp_from timestamo_to))))
当我发出以下请求时,端点 returns 200 ok。
curl -X GET --header 'Accept: application/json' 'http://localhost:3000/fetch?timestamp_from=2018-01-01T10%3A00%3A00'
但是当 timestamp_from 设置为错误数据时,像这样:
curl -X GET --header 'Accept: text/html' 'http://localhost:3000/api/v1.0/task?timestamp_from=blah%20blah%20blah'
我得到以下异常:
clojure.lang.ExceptionInfo: Request validation failed: {:timestamp_from (not #error {\n :cause \"Invalid format: \\"blah blah blah\\"\"\n :via\n [{:type java.lang.IllegalArgumentException\n :message \"Invalid format: \\"blah blah blah\\"\"\n :at [org.joda.time.format.DateTimeFormatter parseDateTime \"DateTimeFormatter.java\" 945]}]\n :trace\n [[org.joda.time.format.DateTimeFormatter parseDateTime \"DateTimeFormatter.java\" 945]\n [clj_time.format$parse invokeStatic \"format.clj\" 160]\n [clj_time.format$parse invoke \"format.clj\" 156]\n [ring.swagger.coerce$parse_date_time invokeStatic \"coerce.clj\" 16]\n [ring.swagger.coerce$parse_date_time invoke \"coerce.clj\" 16]\n [ring.swagger.coerce$fn__13810$fn__13811 invoke \"coerce.clj\" 33]\n [ring.swagger.coerce$coerce_if_string$fn__13807 invoke \"coerce.clj\" 31]\n [schema.coerce$fn__13046$coercer__13051$fn__13052$fn__13053$fn__13054 invoke \"coerce.clj\" 36]\n [schema.spec.variant.VariantSpec$fn__1808 invoke \"variant.clj\" 53]\n [schema.spec.collection$element_transformer$fn__1842$fn__1843 invoke \"collection.clj\" 36]\n [schema.core.MapEntry$fn__2577 invoke \"core.clj\" 766]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection.CollectionSpec$fn__1874 invoke \"collection.clj\" 79]\n [schema.spec.collection$element_transformer$fn__1842$fn__1843 invoke \"collection.clj\" 36]\n [schema.core$map_elements$iter__2602__2606$fn__2607$fn__2614 invoke \"core.clj\" 815]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection.CollectionSpec$fn__1874 invoke \"collection.clj\" 79]\n [schema.coerce$fn__13046$coercer__13051$fn__13052$fn__13053$fn__13054 invoke \"coerce.clj\" 39]\n [compojure.api.coerce$coerce_BANG_ invokeStatic \"coerce.clj\" 59]\n [compojure.api.coerce$coerce_BANG_ invoke \"coerce.clj\" 53]\n [app.api.handler$fn__15830$fn__15953$fn__15955 invoke \"handler.clj\" 88]\n [compojure.core$wrap_response$fn__9969 invoke \"core.clj\" 158]\n [compojure.core$pre_init$fn__10018 invoke \"core.clj\" 328]\n [compojure.api.coerce$body_coercer_middleware$fn__15018 invoke \"coerce.clj\" 51]\n [compojure.core$pre_init$fn__10020$fn__10023 invoke \"core.clj\" 335]\n [compojure.core$wrap_route_middleware$fn__9953 invoke \"core.clj\" 127]\n [compojure.core$wrap_route_info$fn__9958 invoke \"core.clj\" 137]\n [compojure.core$wrap_route_matches$fn__9962 invoke \"core.clj\" 146]\n [compojure.core$wrap_routes$fn__10030 invoke \"core.clj\" 348]\n [compojure.api.routes.Route invoke \"routes.clj\" 74]\n [compojure.core$routing$fn__9977 invoke \"core.clj\" 185]\n [clojure.core$some invokeStatic \"core.clj\" 2592]\n [clojure.core$some invoke \"core.clj\" 2583]\n [compojure.core$routing invokeStatic \"core.clj\" 185]\n [compojure.core$routing doInvoke \"core.clj\" 182]\n [clojure.lang.RestFn applyTo \"RestFn.java\" 139]\n [clojure.core$apply invokeStatic \"core.clj\" 648]\n [clojure.core$apply invoke \"core.clj\" 641]\n [compojure.core$routes$fn__9981 invoke \"core.clj\" 192]\n [compojure.core$routing$fn__9977 invoke \"core.clj\" 185]\n [clojure.core$some invokeStatic \"core.clj\" 2592]\n [clojure.core$some invoke \"core.clj\" 2583]\n [compojure.core$routing invokeStatic \"core.clj\" 185]\n [compojure.core$routing doInvoke \"core.clj\" 182]\n [clojure.lang.RestFn applyTo \"RestFn.java\" 139]\n [clojure.core$apply invokeStatic \"core.clj\" 648]\n [clojure.core$apply invoke \"core.clj\" 641]\n [compojure.core$routes$fn__9981 invoke \"core.clj\" 192]\n [compojure.core$make_context$handler__10007 invoke \"core.clj\" 285]\n [compojure.core$make_context$fn__10009 invoke \"core.clj\" 293]\n [compojure.api.routes.Route invoke \"routes.clj\" 74]\n [compojure.core$routing$fn__9977 invoke \"core.clj\" 185]\n [clojure.core$some invokeStatic \"core.clj\" 2592]\n [clojure.core$some invoke \"core.clj\" 2583]\n [compojure.core$routing invokeStatic \"core.clj\" 185]\n [compojure.core$routing doInvoke \"core.clj\" 182]\n [clojure.lang.RestFn applyTo \"RestFn.java\" 139]\n [clojure.core$apply invokeStatic \"core.clj\" 648]\n [clojure.core$apply invoke \"core.clj\" 641]\n [compojure.core$routes$fn__9981 invoke \"core.clj\" 192]\n [compojure.core$routing$fn__9977 invoke \"core.clj\" 185]\n [clojure.core$some invokeStatic \"core.clj\" 2592]\n [clojure.core$some invoke \"core.clj\" 2583]\n [compojure.core$routing invokeStatic \"core.clj\" 185]\n [compojure.core$routing doInvoke \"core.clj\" 182]\n [clojure.lang.RestFn applyTo \"RestFn.java\" 139]\n [clojure.core$apply invokeStatic \"core.clj\" 648]\n [clojure.core$apply invoke \"core.clj\" 641]\n [compojure.core$routes$fn__9981 invoke \"core.clj\" 192]\n [compojure.core$make_context$handler__10007 invoke \"core.clj\" 285]\n [compojure.core$make_context$fn__10009 invoke \"core.clj\" 293]\n [compojure.api.routes.Route invoke \"routes.clj\" 74]\n [compojure.api.core$handle$fn__15155 invoke \"core.clj\" 8]\n [clojure.core$some invokeStatic \"core.clj\" 2592]\n [clojure.core$some invoke \"core.clj\" 2583]\n [compojure.api.core$handle invokeStatic \"core.clj\" 8]\n [compojure.api.core$handle invoke \"core.clj\" 7]\n [clojure.core$partial$fn__4759 invoke \"core.clj\" 2515]\n [compojure.api.routes.Route invoke \"routes.clj\" 74]\n [ring.swagger.middleware$wrap_swagger_data$fn__14533 invoke \"middleware.clj\" 35]\n [ring.middleware.http_response$wrap_http_response$fn__12223 invoke \"http_response.clj\" 19]\n [ring.swagger.middleware$wrap_swagger_data$fn__14533 invoke \"middleware.clj\" 35]\n [compojure.api.middleware$wrap_options$fn__14588 invoke \"middleware.clj\" 74]\n [ring.middleware.format_params$wrap_format_params$fn__11412 invoke \"format_params.clj\" 119]\n [ring.middleware.format_params$wrap_format_params$fn__11412 invoke \"format_params.clj\" 119]\n [ring.middleware.format_params$wrap_format_params$fn__11412 invoke \"format_params.clj\" 119]\n [ring.middleware.format_params$wrap_format_params$fn__11412 invoke \"format_params.clj\" 119]\n [ring.middleware.format_params$wrap_format_params$fn__11412 invoke \"format_params.clj\" 119]\n [compojure.api.middleware$wrap_exceptions$fn__14578 invoke \"middleware.clj\" 43]\n [ring.middleware.format_response$wrap_format_response$fn__12127 invoke \"format_response.clj\" 194]\n [ring.middleware.keyword_params$wrap_keyword_params$fn__12253 invoke \"keyword_params.clj\" 36]\n [ring.middleware.nested_params$wrap_nested_params$fn__12293 invoke \"nested_params.clj\" 89]\n [ring.middleware.params$wrap_params$fn__12341 invoke \"params.clj\" 67]\n [compojure.api.middleware$wrap_options$fn__14588 invoke \"middleware.clj\" 74]\n [compojure.api.routes.Route invoke \"routes.clj\" 74]\n [ring.logger$wrap_with_logger_STAR_$fn__449 invoke \"logger.clj\" 19]\n [ring.logger$wrap_request_start$fn__453 invoke \"logger.clj\" 37]\n [app.api.handler$with_request_id$fn__15574 invoke \"handler.clj\" 39]\n [ring.adapter.jetty$proxy_handler$fn__16412 invoke \"jetty.clj\" 25]\n [ring.adapter.jetty.proxy$org.eclipse.jetty.server.handler.AbstractHandler$ff19274a handle nil -1]\n [org.eclipse.jetty.server.handler.HandlerWrapper handle \"HandlerWrapper.java\" 97]\n [org.eclipse.jetty.server.Server handle \"Server.java\" 499]\n [org.eclipse.jetty.server.HttpChannel handle \"HttpChannel.java\" 311]\n [org.eclipse.jetty.server.HttpConnection onFillable \"HttpConnection.java\" 258]\n [org.eclipse.jetty.io.AbstractConnection run \"AbstractConnection.java\" 544]\n [org.eclipse.jetty.util.thread.QueuedThreadPool runJob \"QueuedThreadPool.java\" 635]\n [org.eclipse.jetty.util.thread.QueuedThreadPool run \"QueuedThreadPool.java\" 555]\n [java.lang.Thread run \"Thread.java\" 745]]})} schema.utils.ErrorContainer@d7238842
我期待看到更多类似这样的内容:
clojure.lang.ExceptionInfo: Request validation failed {:timestamp_from (not (instance? java.util.Date \"blah blah blah\"))}
它是 ring-swagger 中的一个 feature/bug,它正在执行字符串-> 日期转换。它应该捕获解析异常并发出更好的错误,但没有。
您可以提出问题或进行 PR 来解决这些问题。
在这里,我使用 metosin/compojure-api
库为我的 api 配置 GET /fetch
端点。您会看到我还使用 plumatic/schema
来验证此端点上的查询参数输入,并使用 siilisolutions/humanize
来人性化任何错误数据异常。
(defn humanize-schema-exception [^Exception e]
(if (instance? schema.utils.ErrorContainer (ex-data e))
(#'humanize/explain (:error (ex-data e))
(fn [x]
(let [Inst java.util.Date]
(clojure.core.match/match
x
['not ['instance? Inst not-inst]]
(str "'" not-inst "' is not a timestamp.")
:else x))))))
(defn bad-request-handler
"Handles bad requests."
[f]
(fn [^Exception e data request]
(let [message (humanize-schema-exception e)]
(f message))))
(def app
(api
{:exceptions {:handlers
{::ex/request-parsing (parse-exception-handler response/bad-request)
::ex/request-validation (bad-request-handler response/bad-request)
::ex/default (exception-handler response/internal-server-error)}}
:swagger {:ui "/api/v1.0/docs"
:spec "/api/v1.0/swagger.json"}
(GET "/fetch" []
:query-params [{id :- schema/Int nil}
{timestamp_from :- schema/Inst nil}
{timestamp_to :- schema/Inst nil}]
:responses {200 {:description "ok"}
400 {:description "bad request"}} (foo id timestamp_from timestamo_to))))
当我发出以下请求时,端点 returns 200 ok。
curl -X GET --header 'Accept: application/json' 'http://localhost:3000/fetch?timestamp_from=2018-01-01T10%3A00%3A00'
但是当 timestamp_from 设置为错误数据时,像这样:
curl -X GET --header 'Accept: text/html' 'http://localhost:3000/api/v1.0/task?timestamp_from=blah%20blah%20blah'
我得到以下异常:
clojure.lang.ExceptionInfo: Request validation failed: {:timestamp_from (not #error {\n :cause \"Invalid format: \\"blah blah blah\\"\"\n :via\n [{:type java.lang.IllegalArgumentException\n :message \"Invalid format: \\"blah blah blah\\"\"\n :at [org.joda.time.format.DateTimeFormatter parseDateTime \"DateTimeFormatter.java\" 945]}]\n :trace\n [[org.joda.time.format.DateTimeFormatter parseDateTime \"DateTimeFormatter.java\" 945]\n [clj_time.format$parse invokeStatic \"format.clj\" 160]\n [clj_time.format$parse invoke \"format.clj\" 156]\n [ring.swagger.coerce$parse_date_time invokeStatic \"coerce.clj\" 16]\n [ring.swagger.coerce$parse_date_time invoke \"coerce.clj\" 16]\n [ring.swagger.coerce$fn__13810$fn__13811 invoke \"coerce.clj\" 33]\n [ring.swagger.coerce$coerce_if_string$fn__13807 invoke \"coerce.clj\" 31]\n [schema.coerce$fn__13046$coercer__13051$fn__13052$fn__13053$fn__13054 invoke \"coerce.clj\" 36]\n [schema.spec.variant.VariantSpec$fn__1808 invoke \"variant.clj\" 53]\n [schema.spec.collection$element_transformer$fn__1842$fn__1843 invoke \"collection.clj\" 36]\n [schema.core.MapEntry$fn__2577 invoke \"core.clj\" 766]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection.CollectionSpec$fn__1874 invoke \"collection.clj\" 79]\n [schema.spec.collection$element_transformer$fn__1842$fn__1843 invoke \"collection.clj\" 36]\n [schema.core$map_elements$iter__2602__2606$fn__2607$fn__2614 invoke \"core.clj\" 815]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection$element_transformer$fn__1842 invoke \"collection.clj\" 36]\n [schema.spec.collection.CollectionSpec$fn__1874 invoke \"collection.clj\" 79]\n [schema.coerce$fn__13046$coercer__13051$fn__13052$fn__13053$fn__13054 invoke \"coerce.clj\" 39]\n [compojure.api.coerce$coerce_BANG_ invokeStatic \"coerce.clj\" 59]\n [compojure.api.coerce$coerce_BANG_ invoke \"coerce.clj\" 53]\n [app.api.handler$fn__15830$fn__15953$fn__15955 invoke \"handler.clj\" 88]\n [compojure.core$wrap_response$fn__9969 invoke \"core.clj\" 158]\n [compojure.core$pre_init$fn__10018 invoke \"core.clj\" 328]\n [compojure.api.coerce$body_coercer_middleware$fn__15018 invoke \"coerce.clj\" 51]\n [compojure.core$pre_init$fn__10020$fn__10023 invoke \"core.clj\" 335]\n [compojure.core$wrap_route_middleware$fn__9953 invoke \"core.clj\" 127]\n [compojure.core$wrap_route_info$fn__9958 invoke \"core.clj\" 137]\n [compojure.core$wrap_route_matches$fn__9962 invoke \"core.clj\" 146]\n [compojure.core$wrap_routes$fn__10030 invoke \"core.clj\" 348]\n [compojure.api.routes.Route invoke \"routes.clj\" 74]\n [compojure.core$routing$fn__9977 invoke \"core.clj\" 185]\n [clojure.core$some invokeStatic \"core.clj\" 2592]\n [clojure.core$some invoke \"core.clj\" 2583]\n [compojure.core$routing invokeStatic \"core.clj\" 185]\n [compojure.core$routing doInvoke \"core.clj\" 182]\n [clojure.lang.RestFn applyTo \"RestFn.java\" 139]\n [clojure.core$apply invokeStatic \"core.clj\" 648]\n [clojure.core$apply invoke \"core.clj\" 641]\n [compojure.core$routes$fn__9981 invoke \"core.clj\" 192]\n [compojure.core$routing$fn__9977 invoke \"core.clj\" 185]\n [clojure.core$some invokeStatic \"core.clj\" 2592]\n [clojure.core$some invoke \"core.clj\" 2583]\n [compojure.core$routing invokeStatic \"core.clj\" 185]\n [compojure.core$routing doInvoke \"core.clj\" 182]\n [clojure.lang.RestFn applyTo \"RestFn.java\" 139]\n [clojure.core$apply invokeStatic \"core.clj\" 648]\n [clojure.core$apply invoke \"core.clj\" 641]\n [compojure.core$routes$fn__9981 invoke \"core.clj\" 192]\n [compojure.core$make_context$handler__10007 invoke \"core.clj\" 285]\n [compojure.core$make_context$fn__10009 invoke \"core.clj\" 293]\n [compojure.api.routes.Route invoke \"routes.clj\" 74]\n [compojure.core$routing$fn__9977 invoke \"core.clj\" 185]\n [clojure.core$some invokeStatic \"core.clj\" 2592]\n [clojure.core$some invoke \"core.clj\" 2583]\n [compojure.core$routing invokeStatic \"core.clj\" 185]\n [compojure.core$routing doInvoke \"core.clj\" 182]\n [clojure.lang.RestFn applyTo \"RestFn.java\" 139]\n [clojure.core$apply invokeStatic \"core.clj\" 648]\n [clojure.core$apply invoke \"core.clj\" 641]\n [compojure.core$routes$fn__9981 invoke \"core.clj\" 192]\n [compojure.core$routing$fn__9977 invoke \"core.clj\" 185]\n [clojure.core$some invokeStatic \"core.clj\" 2592]\n [clojure.core$some invoke \"core.clj\" 2583]\n [compojure.core$routing invokeStatic \"core.clj\" 185]\n [compojure.core$routing doInvoke \"core.clj\" 182]\n [clojure.lang.RestFn applyTo \"RestFn.java\" 139]\n [clojure.core$apply invokeStatic \"core.clj\" 648]\n [clojure.core$apply invoke \"core.clj\" 641]\n [compojure.core$routes$fn__9981 invoke \"core.clj\" 192]\n [compojure.core$make_context$handler__10007 invoke \"core.clj\" 285]\n [compojure.core$make_context$fn__10009 invoke \"core.clj\" 293]\n [compojure.api.routes.Route invoke \"routes.clj\" 74]\n [compojure.api.core$handle$fn__15155 invoke \"core.clj\" 8]\n [clojure.core$some invokeStatic \"core.clj\" 2592]\n [clojure.core$some invoke \"core.clj\" 2583]\n [compojure.api.core$handle invokeStatic \"core.clj\" 8]\n [compojure.api.core$handle invoke \"core.clj\" 7]\n [clojure.core$partial$fn__4759 invoke \"core.clj\" 2515]\n [compojure.api.routes.Route invoke \"routes.clj\" 74]\n [ring.swagger.middleware$wrap_swagger_data$fn__14533 invoke \"middleware.clj\" 35]\n [ring.middleware.http_response$wrap_http_response$fn__12223 invoke \"http_response.clj\" 19]\n [ring.swagger.middleware$wrap_swagger_data$fn__14533 invoke \"middleware.clj\" 35]\n [compojure.api.middleware$wrap_options$fn__14588 invoke \"middleware.clj\" 74]\n [ring.middleware.format_params$wrap_format_params$fn__11412 invoke \"format_params.clj\" 119]\n [ring.middleware.format_params$wrap_format_params$fn__11412 invoke \"format_params.clj\" 119]\n [ring.middleware.format_params$wrap_format_params$fn__11412 invoke \"format_params.clj\" 119]\n [ring.middleware.format_params$wrap_format_params$fn__11412 invoke \"format_params.clj\" 119]\n [ring.middleware.format_params$wrap_format_params$fn__11412 invoke \"format_params.clj\" 119]\n [compojure.api.middleware$wrap_exceptions$fn__14578 invoke \"middleware.clj\" 43]\n [ring.middleware.format_response$wrap_format_response$fn__12127 invoke \"format_response.clj\" 194]\n [ring.middleware.keyword_params$wrap_keyword_params$fn__12253 invoke \"keyword_params.clj\" 36]\n [ring.middleware.nested_params$wrap_nested_params$fn__12293 invoke \"nested_params.clj\" 89]\n [ring.middleware.params$wrap_params$fn__12341 invoke \"params.clj\" 67]\n [compojure.api.middleware$wrap_options$fn__14588 invoke \"middleware.clj\" 74]\n [compojure.api.routes.Route invoke \"routes.clj\" 74]\n [ring.logger$wrap_with_logger_STAR_$fn__449 invoke \"logger.clj\" 19]\n [ring.logger$wrap_request_start$fn__453 invoke \"logger.clj\" 37]\n [app.api.handler$with_request_id$fn__15574 invoke \"handler.clj\" 39]\n [ring.adapter.jetty$proxy_handler$fn__16412 invoke \"jetty.clj\" 25]\n [ring.adapter.jetty.proxy$org.eclipse.jetty.server.handler.AbstractHandler$ff19274a handle nil -1]\n [org.eclipse.jetty.server.handler.HandlerWrapper handle \"HandlerWrapper.java\" 97]\n [org.eclipse.jetty.server.Server handle \"Server.java\" 499]\n [org.eclipse.jetty.server.HttpChannel handle \"HttpChannel.java\" 311]\n [org.eclipse.jetty.server.HttpConnection onFillable \"HttpConnection.java\" 258]\n [org.eclipse.jetty.io.AbstractConnection run \"AbstractConnection.java\" 544]\n [org.eclipse.jetty.util.thread.QueuedThreadPool runJob \"QueuedThreadPool.java\" 635]\n [org.eclipse.jetty.util.thread.QueuedThreadPool run \"QueuedThreadPool.java\" 555]\n [java.lang.Thread run \"Thread.java\" 745]]})} schema.utils.ErrorContainer@d7238842
我期待看到更多类似这样的内容:
clojure.lang.ExceptionInfo: Request validation failed {:timestamp_from (not (instance? java.util.Date \"blah blah blah\"))}
它是 ring-swagger 中的一个 feature/bug,它正在执行字符串-> 日期转换。它应该捕获解析异常并发出更好的错误,但没有。
您可以提出问题或进行 PR 来解决这些问题。