Last active
December 24, 2021 05:42
-
-
Save ikitommi/fcab336d1a14f44bcc7ec45b86b1a73f to your computer and use it in GitHub Desktop.
Parsing specs with multimethod dispatch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns spec-tools.parse1 | |
(:require [clojure.spec :as s])) | |
(defmulti parse-list first) | |
(defn parse [spec] | |
(if (or (fn? spec) (symbol? spec)) | |
spec | |
(let [form (s/form spec)] | |
((if (seq? form) parse-list identity) form)))) | |
(defn- map-key [required? namespaced?] | |
(fn [k] | |
[required? | |
(if namespaced? k (-> k name keyword))])) | |
(defmethod parse-list 'clojure.spec/keys [[_ & args]] | |
(let [{:keys [req opt req-un opt-un]} (apply hash-map args) | |
zipped (fn [required? namespaced? source] | |
(zipmap | |
(map (map-key required? namespaced?) source) | |
(map parse source)))] | |
(merge | |
(zipped true true req) | |
(zipped false true opt) | |
(zipped true false req-un) | |
(zipped false false opt-un)))) | |
(defmethod parse-list 'clojure.spec/every [[_ pred]] | |
[(parse pred)]) | |
(defmethod parse-list 'clojure.spec/and [[_ pred]] | |
(parse pred)) | |
(defmethod parse-list 'clojure.core/fn [_] | |
::unknown) | |
;; | |
;; spike | |
;; | |
(s/def ::order-id integer?) | |
(s/def ::product-id integer?) | |
(s/def ::product-name string?) | |
(s/def ::price double?) | |
(s/def ::quantity integer?) | |
(s/def ::name string?) | |
(s/def ::zip #(> % 10)) ; should be (s/and integer? #(> % 10)) | |
(s/def ::street string?) | |
(s/def ::country (s/and keyword? #{:fi :po})) | |
(s/def ::receiver (s/keys :req-un [::name ::street ::zip] | |
:opt-un [::country])) | |
(s/def ::orderline (s/keys :req [::product-id ::price] | |
:opt-un [::product-name])) | |
(s/def ::orderlines (s/coll-of ::orderline)) | |
(s/def ::order (s/keys :req-un [::order-id ::orderlines ::receiver])) | |
(s/def ::order-with-line (s/and ::order #(> (::orderlines 1)))) | |
(parse ::order) | |
;{[true :order-id] clojure.core/integer?, | |
; [true :orderlines] [{[true :spec-tools.parse/product-id] clojure.core/integer?, | |
; [true :spec-tools.parse/price] clojure.core/double?, | |
; [false :product-name] clojure.core/string?}], | |
; [true :receiver] {[true :name] clojure.core/string?, | |
; [true :street] clojure.core/string?, | |
; [true :zip] :spec-tools.parse/unknown, | |
; [false :country] clojure.core/keyword?}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns spec-tools.parse | |
(:require [clojure.spec :as s])) | |
(defn parse [{:keys [parser] :as opts} spec] | |
(assert (map? opts) "options should be a map") | |
(assert (ifn? parser) "parser is not a ifn") | |
(parser | |
(if (or (sequential? spec) (keyword? spec)) | |
(s/form spec) spec) | |
opts)) | |
;; | |
;; A sample parser | |
;; | |
(defmulti parse-spec (fn [x _] (if (sequential? x) (first x) x))) | |
(defn- map-key [required? namespaced?] | |
(fn [k] | |
[required? | |
(if namespaced? k (-> k name keyword))])) | |
(defmethod parse-spec 'clojure.spec/keys [[_ & args] opts] | |
(let [{:keys [req opt req-un opt-un]} (apply hash-map args) | |
zipped (fn [required? namespaced? source] | |
(zipmap | |
(map (map-key required? namespaced?) source) | |
(map (partial parse opts) source)))] | |
(merge | |
(zipped true true req) | |
(zipped false true opt) | |
(zipped true false req-un) | |
(zipped false false opt-un)))) | |
(defmethod parse-spec 'clojure.spec/every [[_ pred] opts] | |
[(parse opts pred)]) | |
(defmethod parse-spec 'clojure.spec/and [[_ pred] opts] | |
(parse opts pred)) | |
(defmethod parse-spec 'clojure.core/fn [_ _] | |
::unknown) | |
;; Sample handler for a leaf spec | |
(defmethod parse-spec 'clojure.core/integer? [x _] | |
x) | |
(defmethod parse-spec :default [x _] | |
x) | |
;; | |
;; spike | |
;; | |
(s/def ::order-id integer?) | |
(s/def ::product-id integer?) | |
(s/def ::product-name string?) | |
(s/def ::price double?) | |
(s/def ::quantity integer?) | |
(s/def ::name string?) | |
(s/def ::zip #(> % 10)) | |
(s/def ::street string?) | |
(s/def ::country (s/and keyword? #{:fi :po})) | |
(s/def ::receiver (s/keys :req-un [::name ::street ::zip] | |
:opt-un [::country])) | |
(s/def ::orderline (s/keys :req [::product-id ::price] | |
:opt-un [::product-name])) | |
(s/def ::orderlines (s/coll-of ::orderline)) | |
(s/def ::order (s/keys :req-un [::order-id ::orderlines ::receiver])) | |
(s/def ::order-with-line (s/and ::order #(> (::orderlines 1)))) | |
(def parser (partial parse {:parser parse-spec})) | |
(parser ::order) | |
;{[true :order-id] clojure.core/integer?, | |
; [true :orderlines] [{[true :spec-tools.parse/product-id] clojure.core/integer?, | |
; [true :spec-tools.parse/price] clojure.core/double?, | |
; [false :product-name] clojure.core/string?}], | |
; [true :receiver] {[true :name] clojure.core/string?, | |
; [true :street] clojure.core/string?, | |
; [true :zip] :spec-tools.parse/unknown, | |
; [false :country] clojure.core/keyword?}} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment