Skip to content

Commit

Permalink
Added needed documentation, coments and some refactoring.
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Hawes committed Jun 12, 2024
1 parent 14a5b3f commit f51a633
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 46 deletions.
21 changes: 20 additions & 1 deletion src/cljaws/dynamo_spec.clj
Original file line number Diff line number Diff line change
@@ -1,2 +1,21 @@
(ns cljaws.dynamo-spec)
(ns cljaws.dynamo-spec
(:require [clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen]))

(require '[clojure.spec.alpha :as s])

;; Define the acceptable value types
(s/def ::value (s/or :string string?
:number number?
:bytes bytes?))

;; Define the spec for the DynamoDB key map
(s/def ::dynamodb-key
(s/and (s/map-of (s/and keyword? #{:pk :sk}) ::value :count 1)))

;; Examples of validating maps
(s/valid? ::dynamodb-key {:pk "some string"}) ;; true
(s/valid? ::dynamodb-key {:sk 123}) ;; true
(s/valid? ::dynamodb-key {:pk (byte-array 10)}) ;; true
(s/valid? ::dynamodb-key {:pk "some string" :sk 123}) ;; false, only one key allowed
(s/valid? ::dynamodb-key {:pk [1 2 3]}) ;; false, value is not string, number, or bytes
58 changes: 31 additions & 27 deletions src/cljaws/dynamodb.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
(def ^{:dynamic true :private false} *table-name* nil)

(s/def :dynamodb/table-name string?)
(s/def :dynamodb/key (fn [key] (or (string? key) (number? key) (bytes? key))))

(s/def :dynamodb/dynamodb-key (fn [key] (= key :pk) (= key :sk)))
(s/def :dynamodb/dynamodb-value (s/or :string string?
:number number?
:bytes bytes?))

(s/def :dynamodb/attributes map?)

(defn- validate-table-name [table-name]
Expand All @@ -16,16 +21,27 @@
(throw (ex-info "Invalid table-name" {:table-name table-name}))))

(defn- validate-key [key]
(when-not (s/valid? :dynamodb/key key)
(when-not (s/valid? :dynamodb/dynamodb-value key)
(throw (ex-info (str "Invalid " key ": is of type " (type key)
" Must be either a string, number, or binary") {:key key}))))

(defn- validate-key-pair [pair]
(when-not (map? pair) (ex-info (str "Invalid " pair ": is of type " (type pair)
" Must be a map.") {:pair pair}))
(let [key (first (keys pair))
value (first (val pair))]
(when-not (s/valid? :dynamodb/dynamodb-key value)
(throw (ex-info (str "Invalid " value ": is of type " (type value)
" Must be either a string, number, or binary") {:pair pair})))))

(defn- validate-attributes [attr]
(if (s/valid? :dynamodb/attributes attr)
attr
(throw (ex-info "Attributes must be sent as a map " {:attributes attr}))))

(defn ->placeholder [attr]
(defn ->placeholder
"Converts key and attribute names and values within an UpdateItem call."
[attr]
(clojure.string/replace (name attr) #"[^a-zA-Z0-9_]" "_"))

(defn ->placeholder-name [attr]
Expand All @@ -34,7 +50,9 @@
(defn ->placeholder-value [attr]
(str ":" (->placeholder attr)))

(defn format-value [value]
(defn format-value
"Takes a value and assigns the appropriate data type for dynamodb"
[value]
(cond
(string? value) {:S value}
(number? value) {:N (str value)}
Expand Down Expand Up @@ -70,9 +88,9 @@
([table-name pk updates]
(format-update-item table-name pk nil updates nil))
([table-name pk sk updates removals]
(validate-table-name table-name)
(doseq [[k v] (concat pk (or sk {}))]
(doseq [[k v] (merge pk sk)]
(validate-key v))
(validate-table-name table-name)
(let [update-sets (when (and updates (not (empty? updates)))
(str "SET " (clojure.string/join ", " (map (fn [[key _]] (str (->placeholder-name key) " = "
(->placeholder-value key))) updates))))
Expand All @@ -90,7 +108,7 @@
:ExpressionAttributeNames expr-attr-nams}
expr-attr-vals (assoc :ExpressionAttributeValues expr-attr-vals))})))

(defn format-put
(defn format-batch-put
"Formats a put item for DynamoDB."
[item]
(let [keys (merge (:pk item) (:sk item))
Expand All @@ -100,7 +118,7 @@
(map (fn [[k v]] [(name k) (format-value v)]) (:attributes item))))]
{:PutRequest {:Item formatted-item}}))

(defn format-delete
(defn format-batch-delete
"Formats a delete item for DynamoDB."
[item]
(let [key-map (into {}
Expand All @@ -110,11 +128,11 @@
(validate-key v))
{:DeleteRequest {:Key key-map}}))

(defn format-operations
(defn format-batch-operations
"Formats the operations (put and delete) for DynamoDB."
[ops]
(let [put-requests (mapv format-put (get ops :put []))
delete-requests (mapv format-delete (get ops :delete []))]
(let [put-requests (mapv format-batch-put (get ops :put []))
delete-requests (mapv format-batch-delete (get ops :delete []))]
(apply conj put-requests delete-requests)))

(defn format-batch-write
Expand All @@ -123,7 +141,7 @@
{:op :BatchWriteItem
:request {:RequestItems (into {}
(mapv (fn [[table-name ops]]
[table-name (format-operations ops)])
[table-name (format-batch-operations ops)])
requests))}})

(defn scan-table
Expand All @@ -138,15 +156,6 @@
:request {:TableName table-name}}
environment region)))

(defn format-delete-item
"Formats data for deleting an item from the DynamoDB table."
[table-name entity-type entity-id]
(validate-table-name table-name)
{:op :DeleteItem
:request {:TableName table-name
:Key {:entity-type {:S entity-type}
:entity-id {:S entity-id}}}})

(defn put-item
"Adds an item to the dynamodb table."
([entity-type entity-id resources] (put-item *table-name* entity-type entity-id resources))
Expand All @@ -158,13 +167,8 @@
([table-name entity-type entity-id updates removals]
(format-update-item table-name entity-type entity-id updates removals)))

(defn batch-write
"Batch write operations can span multiple tables."
[requests]
)


(comment
;; Sample input data for a batch-write operation.
(def r
{:jira-account-map
{:put [{:pk {:AccountId "key1"}
Expand Down
36 changes: 18 additions & 18 deletions test/cljaws/dynamodb_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -106,40 +106,40 @@
(sut/format-update-item "jira-resource-manager" {:entity-type "group"} {:entity-id "Intern"}
{:resources ["AWS SSO Dev" "AWS SSO Test" "Databricks"]} ["no-op"])))))

(deftest test-format-put
(testing "Test format-put"
(deftest test-format-batch-put
(testing "Test format-batch-put"
(is (= {:PutRequest
{:Item
{"AccountId" {:S "key1"},
"Email" {:S "[email protected]"},
"description" {:S "Something"},
"resources" {:L [{:S "resource1"} {:S "resource2"}]}}}}
(sut/format-put {:pk {:AccountId "key1"}
:sk {:Email "[email protected]"}
:attributes {:description "Something"
:resources ["resource1" "resource2"]}})))))
(sut/format-batch-put {:pk {:AccountId "key1"}
:sk {:Email "[email protected]"}
:attributes {:description "Something"
:resources ["resource1" "resource2"]}})))))

(deftest test-format-delete
(testing "Test format-delete"
(deftest test-format-batch-delete
(testing "Test format-batch-delete"
(is (= {:DeleteRequest {:Key {"AccountId" {:S "jfdjd"}, "Email" {:S "[email protected]"}}}}
(sut/format-delete {:pk {:AccountId "jfdjd"}
:sk {:Email "[email protected]"}})))))
(sut/format-batch-delete {:pk {:AccountId "jfdjd"}
:sk {:Email "[email protected]"}})))))

(deftest test-format-operations
(testing "Test format-operations"
(deftest test-format-batch-operations
(testing "Test format-batch-operations"
(is (= [{:PutRequest
{:Item
{"description" {:S "Something"},
"resources" {:L [{:S "resource1"} {:S "resource2"}]},
"AccountId" {:S "key1"},
"Email" {:S "[email protected]"}}}}
{:DeleteRequest {:Key {"AccountId" {:S "jfdjd"}, "Email" {:S "[email protected]"}}}}]
(sut/format-operations {:put [{:pk {:AccountId "key1"}
:sk {:Email "[email protected]"}
:attributes {:description "Something"
:resources ["resource1" "resource2"]}}]
:delete [{:pk {:AccountId "jfdjd"}
:sk {:Email "[email protected]"}}]})))))
(sut/format-batch-operations {:put [{:pk {:AccountId "key1"}
:sk {:Email "[email protected]"}
:attributes {:description "Something"
:resources ["resource1" "resource2"]}}]
:delete [{:pk {:AccountId "jfdjd"}
:sk {:Email "[email protected]"}}]})))))

(deftest test-format-batch-write
(testing "Multiple tables with put and delete items."
Expand Down

0 comments on commit f51a633

Please sign in to comment.