Skip to content

Commit 9db5a3e

Browse files
committed
Add module coercion and validation
1 parent 689e5a0 commit 9db5a3e

File tree

5 files changed

+130
-6
lines changed

5 files changed

+130
-6
lines changed

deps.edn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
:paths ["src" "resources"]
44
:deps {org.clojure/clojure {:mvn/version "1.11.1"}
5+
metosin/malli {:mvn/version "0.13.0"}
56
meta-merge/meta-merge {:mvn/version "1.0.0"}
67
metosin/jsonista {:mvn/version "0.3.7"}
78
clj-commons/clj-yaml {:mvn/version "1.0.27"}

src/k16/kl/api/module.clj

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,40 @@
11
(ns k16.kl.api.module
22
(:require
3+
[clj-yaml.core :as yaml]
4+
[clojure.edn :as edn]
5+
[clojure.string :as str]
6+
[jsonista.core :as json]
37
[k16.kl.api.fs :as api.fs]
8+
[k16.kl.api.module.parse :as module.parse]
9+
[k16.kl.api.module.schema :as module.schema]
410
[k16.kl.api.state :as api.state]
11+
[malli.core :as m]
12+
[malli.error :as me]
513
[meta-merge.core :as metamerge]))
614

715
(set! *warn-on-reflection* true)
816

17+
(defn read-module-file [^java.io.File file]
18+
(let [name (.getName file)
19+
contents (slurp file)
20+
21+
ext (-> name (str/split #"\.") last keyword)
22+
23+
data (cond
24+
(= ext :edn)
25+
(edn/read-string contents)
26+
27+
(= ext :json)
28+
(json/read-value contents)
29+
30+
(or (= ext :yml)
31+
(= ext :yaml))
32+
(yaml/parse-string contents)
33+
34+
:else (throw (ex-info "Failed to read module file. Unsupported file format" {:file-name name})))]
35+
36+
(module.parse/parse-module-data data)))
37+
938
(defn- filter-by-known [left right]
1039
(->> left
1140
(filter (fn [[container-name]]
@@ -35,12 +64,20 @@
3564

3665
state-routes
3766
(filter-by-known (get-in state [:network :routes])
38-
(get-in merged-root [:network :routes]))]
67+
(get-in merged-root [:network :routes]))
68+
69+
final
70+
(metamerge/meta-merge merged-root {:containers state-containers
71+
:network {:services state-services
72+
:routes state-routes}})]
73+
74+
(when-not (m/validate module.schema/?Module final)
75+
(throw (ex-info "Module invalid" {:reason (->> final
76+
(m/explain module.schema/?Module)
77+
me/humanize)})))
3978

40-
(metamerge/meta-merge merged-root {:containers state-containers
41-
:network {:services state-services
42-
:routes state-routes}})))
79+
final))
4380

4481
(defn get-resolved-module [module-name modules]
45-
(let [root-module (api.fs/read-edn (api.fs/get-root-module-file module-name))]
82+
(let [root-module (read-module-file (api.fs/get-root-module-file module-name))]
4683
(dissoc (merge-modules module-name root-module modules) :modules)))

src/k16/kl/api/module/parse.clj

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(ns k16.kl.api.module.parse
2+
(:require
3+
[clojure.walk :as walk]
4+
[k16.kl.api.module.schema :as module.schema]
5+
[malli.core :as m]
6+
[malli.error :as me]
7+
[malli.transform :as mt]))
8+
9+
(defn parse-module-data [module-data]
10+
(try
11+
(m/coerce module.schema/?PartialModule
12+
(walk/keywordize-keys module-data)
13+
mt/json-transformer)
14+
(catch Exception e
15+
(let [{:keys [type data]} (ex-data e)]
16+
(when-not (= type :malli.core/invalid-input)
17+
(throw e))
18+
19+
(throw (ex-info "Module invalid" {:reason (me/humanize (:explain data))}))))))

src/k16/kl/api/module/schema.clj

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
(ns k16.kl.api.module.schema
2+
(:require
3+
[malli.core :as m]
4+
[malli.util :as mu]))
5+
6+
(set! *warn-on-reflection* true)
7+
8+
(defn- make-recursively-optional [schema]
9+
(let [modifier
10+
(fn [schema]
11+
(let [type (m/type schema)
12+
properties (m/properties schema)]
13+
(cond
14+
(:kl/locked properties)
15+
schema
16+
17+
(= :map type)
18+
(mu/optional-keys schema)
19+
20+
:else
21+
schema)))]
22+
23+
(m/walk schema (m/schema-walker modifier))))
24+
25+
(def ?ModuleRef
26+
[:map {:kl/locked true}
27+
[:url :string]
28+
[:sha {:optional true} :string]
29+
[:ref {:optional true} :string]
30+
[:subdir {:optional true} :string]])
31+
32+
(def ?Endpoint
33+
[:map
34+
[:url :string]])
35+
36+
(def ?Service
37+
[:map
38+
[:endpoints [:map-of :keyword ?Endpoint]]
39+
[:default-endpoint :keyword]])
40+
41+
(def ?Route
42+
[:map
43+
[:host :string]
44+
[:path-prefix {:optional true} :string]
45+
[:service :keyword]
46+
[:endpoint {:optional true} :keyword]])
47+
48+
(def ?Network
49+
[:map
50+
[:services {:optional true} [:map-of :keyword ?Service]]
51+
[:routes {:optional true} [:map-of :keyword ?Route]]])
52+
53+
(def ?Module
54+
[:map
55+
[:modules {:optional true} [:map-of :keyword ?ModuleRef]]
56+
57+
[:include {:optional true} [:sequential :string]]
58+
59+
[:network {:optional true} ?Network]
60+
61+
[:volumes {:optional true} [:map-of :keyword [:map]]]
62+
[:containers {:optional true} [:map-of :keyword [:map]]]])
63+
64+
(def ?PartialModule
65+
(make-recursively-optional ?Module))

src/k16/kl/api/resolver/downloader.clj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[clojure.string :as str]
66
[k16.kl.api.fs :as api.fs]
77
[k16.kl.api.github :as api.github]
8+
[k16.kl.api.module.parse :as module.parse]
89
[k16.kl.log :as log]
910
[promesa.core :as p]))
1011

@@ -36,7 +37,8 @@
3637
([{:keys [url sha subdir] :or {subdir ".kl"}} vars]
3738
(-> (read-repo-file url sha (relative-to subdir "module.edn"))
3839
(replace-vars vars)
39-
edn/read-string)))
40+
edn/read-string
41+
module.parse/parse-module-data)))
4042

4143
(defn download-remote-module! [{:keys [module-name submodule-name module-ref]}]
4244
(let [{:keys [sha url subdir]

0 commit comments

Comments
 (0)