Skip to content
Snippets Groups Projects
Commit b6640477 authored by Cam Saul's avatar Cam Saul
Browse files

new optional map param check pattern

parent d27edd08
Branches
Tags
No related merge requests found
......@@ -5,7 +5,8 @@
[medley.core :refer :all]
[metabase.api.common.internal :refer :all]
[metabase.db :refer :all]
[metabase.db.internal :refer [entity->korma]])
[metabase.db.internal :refer [entity->korma]]
[metabase.util :as u])
(:import com.metabase.corvus.api.ApiException))
(declare check-404)
......@@ -216,6 +217,9 @@
`(require-params ~param)
param)
(defannotation int [param]
`(Integer/parseInt ~param))
;;; ### defendpoint
......@@ -229,19 +233,18 @@
- automatically calls `wrap-response-if-needed` on the result of BODY
- tags function's metadata in a way that subsequent calls to `define-routes` (see below)
will automatically include the function in the generated `defroutes` form."
[method route args & body]
[method route args & more]
{:pre [(or (string? route)
(vector? route))
(vector? args)]}
(let [name (route-fn-name method route)
route (typify-route route)
annotated-args args
args (deannotate-args-form args)]
[arg-annotations body] (u/optional map? more)]
`(do (def ~name
(~method ~route ~args
(auto-parse ~args
(catch-api-exceptions
(let-annotated-args ~annotated-args
(let-annotated-args ~arg-annotations
(-> (do ~@body)
wrap-response-if-needed))))))
(alter-meta! #'~name assoc :is-endpoint? true))))
......
(ns metabase.api.common.internal
"Internal functions used by `metabase.api.common`."
(:require [clojure.tools.logging :as log]
[metabase.util :refer [fn-> regex?]])
[metabase.util :as u])
(:import com.metabase.corvus.api.ApiException))
;;; # DEFENDPOINT HELPER FUNCTIONS + MACROS
......@@ -70,7 +70,7 @@
(route-arg-keywords \"/:id/cards\") -> [:id]"
[route]
(->> (re-seq #":([\w-]+)" route)
(map (fn-> second keyword))))
(map (u/fn-> second keyword))))
(defn typify-args
"Given a sequence of keyword ARGS, return a sequence of `[:arg pattern :arg pattern ...]`
......@@ -151,59 +151,59 @@
;;; ### Args Form Deannotation
(defn deannotate-arg
"If FORM is a symbol, strip off any annotations.
;; (defn deannotate-arg
;; "If FORM is a symbol, strip off any annotations.
(deannotate-arg 'password.required) -> password"
[form]
(if-not (symbol? form) form
(let [[arg & _] (clojure.string/split (name form) #"\.")]
(symbol arg))))
;; (deannotate-arg 'password.required) -> password"
;; [form]
;; (if-not (symbol? form) form
;; (let [[arg & _] (clojure.string/split (name form) #"\.")]
;; (symbol arg))))
(defn deannotate-args-form
"Walk ARGS-FORM and strip off any annotations.
;; (defn deannotate-args-form
;; "Walk ARGS-FORM and strip off any annotations.
(deannotate-args-form [id :as {{:keys [password.required old_password.required]} :body}])
-> [id :as {{:keys [password old_password]} :body}]"
[args-form]
(if (= args-form []) '[:as _]
(->> args-form
(mapv (partial clojure.walk/prewalk deannotate-arg)))))
;; (deannotate-args-form [id :as {{:keys [password.required old_password.required]} :body}])
;; -> [id :as {{:keys [password old_password]} :body}]"
;; [args-form]
;; (if (= args-form []) '[:as _]
;; (->> args-form
;; (mapv (partial clojure.walk/prewalk deannotate-arg)))))
;;; ### Args Form Annotation Gathering
(defn args-form->symbols
"Recursively walk ARGS-FORM and return a sequence of symbols.
(args-form->symbols [id :as {{:keys [password.required old_password.required]} :body}])
-> (id password.required old_password.required)"
[args-form]
{:post [(sequential? %)
(every? symbol? %)]}
(cond
(symbol? args-form) [args-form]
(map? args-form) (->> args-form
(mapcat (fn [[k v]]
[(args-form->symbols k) (args-form->symbols v)]))
(mapcat args-form->symbols))
(sequential? args-form) (mapcat args-form->symbols
args-form)
:else []))
(defn symb->arg+annotations
"Return a sequence of pairs of `[annotation-kw deannotated-arg-symb]` for an annotated ARG.
(symb->arg+annotations 'password) -> nil
(symb->arg+annotations 'password.required) -> [[:required password]]
(symb->arg+annotations 'password.required.str) -> [[:required password], [:str password]]"
[arg]
{:pre [(symbol? arg)]}
(let [[arg & annotations] (clojure.string/split (name arg) #"\.")]
(when (seq annotations)
(->> annotations
(map (fn [annotation]
[(keyword annotation) (symbol arg)]))))))
;; (defn args-form->symbols
;; "Recursively walk ARGS-FORM and return a sequence of symbols.
;; (args-form->symbols [id :as {{:keys [password.required old_password.required]} :body}])
;; -> (id password.required old_password.required)"
;; [args-form]
;; {:post [(sequential? %)
;; (every? symbol? %)]}
;; (cond
;; (symbol? args-form) [args-form]
;; (map? args-form) (->> args-form
;; (mapcat (fn [[k v]]
;; [(args-form->symbols k) (args-form->symbols v)]))
;; (mapcat args-form->symbols))
;; (sequential? args-form) (mapcat args-form->symbols
;; args-form)
;; :else []))
;; (defn symb->arg+annotations
;; "Return a sequence of pairs of `[annotation-kw deannotated-arg-symb]` for an annotated ARG.
;; (symb->arg+annotations 'password) -> nil
;; (symb->arg+annotations 'password.required) -> [[:required password]]
;; (symb->arg+annotations 'password.required.str) -> [[:required password], [:str password]]"
;; [arg]
;; {:pre [(symbol? arg)]}
;; (let [[arg & annotations] (clojure.string/split (name arg) #"\.")]
;; (when (seq annotations)
;; (->> annotations
;; (map (fn [annotation]
;; [(keyword annotation) (symbol arg)]))))))
;;; ### let-annotated-args
......@@ -216,14 +216,23 @@
(symbol? arg-symb)]}
`[~arg-symb ~((eval 'metabase.api.common/arg-annotation-fn) annotation-kw arg-symb)])
(defn process-arg-annotations [annotations]
{:pre [(or (nil? annotations)
(map? annotations))]}
(some->> annotations
(mapcat (fn [[arg annotations]]
(if (sequential? annotations) (->> annotations
(map keyword)
(map (u/rpartial vector arg)))
[[(keyword annotations) arg]])))
(mapcat arg-annotation-let-binding)))
(defmacro let-annotated-args
"Wrap BODY in a let-form that calls corresponding implementations of `arg-annotation-fn` for annotated args in ANNOTATED-ARGS-FORM."
[annotated-args-form & body]
{:pre [(vector? annotated-args-form)]}
(let [annotations (->> annotated-args-form
args-form->symbols
(mapcat symb->arg+annotations)
(mapcat arg-annotation-let-binding))]
[arg-annotations & body]
{:pre [(or (nil? arg-annotations)
(map? arg-annotations))]}
(let [annotations (process-arg-annotations arg-annotations)]
(if (seq annotations)
`(let [~@annotations]
~@body)
......
......@@ -31,7 +31,8 @@
`(check (is-email? ~email) [400 (format ~(str (name email) " '%s' is not a valid email.") ~email)])
email)
(defendpoint PUT "/:id" [id :as {{:keys [email.email] :as body} :body}]
(defendpoint PUT "/:id" [id :as {{:keys [email] :as body} :body}]
{email email}
;; user must be getting their own details OR they must be a superuser to proceed
(check-403 (or (= id *current-user-id*) (:is_superuser @*current-user*)))
;; can't change email if it's already taken BY ANOTHER ACCOUNT
......@@ -45,8 +46,9 @@
`(check (password/is-complex? ~password) [400 "Insufficient password strength"])
password)
(defendpoint PUT "/:id/password" [id :as {{:keys [password.req.complex-pw old_password.req]} :body}]
(require-params password old_password)
(defendpoint PUT "/:id/password" [id :as {{:keys [password old_password]} :body}]
{password [req complex-pw]
old_password req}
(check-403 (or (= id *current-user-id*)
(:is_superuser @*current-user*)))
(let-404 [user (sel :one [User :password_salt :password] :id id)]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment