All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cognitect.aws.protocols.rest.clj Maven / Gradle / Ivy

The newest version!
;; Copyright (c) Cognitect, Inc.
;; All rights reserved.

(ns ^:skip-wiki cognitect.aws.protocols.rest
  "Impl, don't call directly.

  Common feature across the rest protocols (rest-json, rest-xml). "
  (:require [clojure.string :as str]
            [cognitect.aws.util :as util]
            [cognitect.aws.protocols :as aws.protocols]
            [cognitect.aws.shape :as shape]))

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

;; ----------------------------------------------------------------------------------------
;; Serializer
;; ----------------------------------------------------------------------------------------

(defn- remove-leading-slash [s]
  (str/replace s #"^/" ""))

(defn serialize-uri
  "Take a URI template, an input-shape, and a map of values and replace the parameters by their values.
  Throws if args is missing any keys that are required in input-shape."
  [uri-template {:keys [required] :as _input-shape} args]
  (str/replace uri-template
               #"\{([^}]+)\}"
               (fn [[_ ^String param]]
                 (or (if (.endsWith param "+")
                       (some-> args
                               (get (keyword (.substring param 0 (dec (count param)))))
                               util/url-encode
                               (str/replace "%2F" "/")
                               (str/replace "%7E" "~")
                               remove-leading-slash)
                       (some-> args
                               (get (keyword param))
                               str
                               util/url-encode
                               remove-leading-slash))
                     ;; TODO (dchelimsky 2019-02-08) it's possible that 100% of
                     ;; params in templated URIs are required, in which case
                     ;; we don't need this extra test.
                     (let [raw-param (str/replace param #"\+" "")]
                       (when (contains? (set required) raw-param)
                         (throw (ex-info "Required key missing from request. Check the docs for this operation."
                                         {:required (mapv keyword required)}))))
                     ""))))

(declare serialize-qs-args)

(defn append-querystring
  "Append the map of arguments args to the uri's querystring."
  [shapes ^String uri shape args]
  (if-let [qs (util/query-string (mapcat (fn [[k v]]
                                           (when-let [member-shape (shape/resolve shapes (shape/structure-member-shape-ref shape k))]
                                             (serialize-qs-args shapes
                                                                member-shape
                                                                (or (:locationName member-shape)
                                                                    (name k))
                                                                v)))
                                         args))]
    (str uri (if (.contains uri "?") "&" "?") qs)
    uri))

(defmulti serialize-qs-args
  "Return a list of key-value pairs to serialize in the query string."
  (fn [_shapes shape _param-name _args] (:type shape)))

(defmethod serialize-qs-args :default
  [_shapes _shape param-name args]
  (when-not (nil? args)
    [[param-name (str args)]]))

(defmethod serialize-qs-args "timestamp"
  [_shapes shape param-name args]
  (when-not (nil? args)
    [[param-name (shape/format-date shape
                                    args
                                    (partial util/format-date util/iso8601-date-format))]]))

(defmethod serialize-qs-args "list"
  [shapes shape param-name args]
  (mapcat #(serialize-qs-args shapes
                              (shape/resolve shapes (shape/list-member-shape-ref shape))
                              param-name
                              %)
          args))

(defmethod serialize-qs-args "map"
  [shapes shape _ args]
  (mapcat (fn [[k v]]
            (serialize-qs-args shapes
                               (shape/resolve shapes (shape/map-value-shape-ref shape))
                               (name k)
                               v))
          args))

(defmulti serialize-header-value
  "Serialize a primitive shape in a HTTP header."
  (fn [shape _args] (:type shape)))

(defmethod serialize-header-value :default    [_ args] (str args))
(defmethod serialize-header-value "boolean"   [_ args] (if args "true" "false"))
(defmethod serialize-header-value "blob"      [_ args] (util/base64-encode args))
(defmethod serialize-header-value "timestamp" [shape args]
  (shape/format-date shape args
                     (partial util/format-date util/rfc822-date-format)))

(defn serialize-headers
  "Serialize the map of arguments into a map of HTTP headers."
  [shapes shape args]
  (reduce-kv (fn [serialized k v]
               (let [member-shape (shape/resolve shapes (shape/structure-member-shape-ref shape k))
                     header-name (str/lower-case (or (:locationName member-shape) (name k)))]
                 (cond
                   (:jsonvalue member-shape)
                   (assoc serialized header-name (util/encode-jsonvalue v))

                   (#{"map" "structure"} (:type member-shape))
                   (reduce-kv (fn [serialized k v]
                                (let [header-name (str header-name (name k))]
                                  (assoc serialized header-name (serialize-header-value member-shape v))))
                              serialized
                              v)

                   (#{"list"} (:type member-shape))
                   (let [list-member-shape (shape/resolve shapes (shape/list-member-shape-ref member-shape))]
                     (assoc serialized header-name (str/join "," (map (partial serialize-header-value list-member-shape) v))))

                   :else
                   (assoc serialized header-name (serialize-header-value member-shape v)))))
             {}
             args))

(defn serialize-body
  [shapes input-shape-name input-shape args serialize]
  (if-let [payload-name (:payload input-shape)]
    ;; A member of the input shape is flagged as the payload member.
    (let [payload-shape (shape/resolve shapes (shape/structure-member-shape-ref input-shape (keyword payload-name)))]
      (if (contains? #{"blob" "string"} (:type payload-shape))
        ;; Streaming - return payload directly
        (get args (keyword payload-name))
        ;; Otherwise, serialize payload value to XML
        (when-let [body-arg (get args (keyword payload-name))]
          (serialize shapes input-shape-name payload-shape body-arg))))
    ;; No payload attribute
    (serialize shapes input-shape-name input-shape args)))

(defn partition-args
  "Partition the arguments by their location."
  [shapes shape args]
  (reduce-kv (fn [partition k v]
               (if-let [member-shape (shape/resolve shapes (shape/structure-member-shape-ref shape k))]
                 (let [partition-key (or (keyword (:location member-shape)) :body)]
                   (assoc-in partition
                             [partition-key
                              (if (= :uri partition-key)
                                (or (keyword (:locationName member-shape))
                                    k)
                                k)]
                             v))
                 partition))
             {}
             (util/with-defaults shape args)))

(defn build-http-request
  [{:keys [operations] :as service} {:keys [op request]} serialize-body-args]
  (let [operation        (get operations op)
        shapes           (:shapes service)
        input-shape-name (-> operation :input :shape)
        input-shape      (shape/resolve shapes (:input operation))
        http-request     {:request-method (-> operation :http :method str/lower-case keyword)
                          :scheme         :https
                          :server-port    443
                          :uri            (get-in operation [:http :requestUri])
                          :headers        (aws.protocols/headers service operation)}]
    (if-not input-shape
      http-request
      (let [location->args (partition-args shapes input-shape request)
            body-args      (:body location->args)]
        (-> http-request
            (update :uri serialize-uri input-shape (:uri location->args))
            (update :uri (partial append-querystring shapes) input-shape (:querystring location->args))
            (update :headers merge (serialize-headers shapes input-shape (merge (location->args :header)
                                                                                 (location->args :headers))))
            (assoc :body (serialize-body shapes input-shape-name input-shape body-args serialize-body-args)))))))

;; ----------------------------------------------------------------------------------------
;; Parser
;; ----------------------------------------------------------------------------------------

(defmulti parse-header-value
  "Parse a shape from an HTTP header value."
  (fn [shape _data] (:type shape)))

(defmethod parse-header-value "string"    [shape data]
  (cond
    (nil? data)        ""
    (:jsonvalue shape) (util/parse-jsonvalue data)
    :else              data))
(defmethod parse-header-value "character" [_ data] (or data ""))
(defmethod parse-header-value "boolean"   [_ data] (= data "true"))
(defmethod parse-header-value "double"    [_ data] (Double/parseDouble ^String data))
(defmethod parse-header-value "float"     [_ data] (Double/parseDouble ^String data))
(defmethod parse-header-value "long"      [_ data] (Long/parseLong ^String data))
(defmethod parse-header-value "integer"   [_ data] (Long/parseLong ^String data))
(defmethod parse-header-value "blob"      [_ data] (util/base64-decode data))
(defmethod parse-header-value "timestamp"
  [shape data]
  (shape/parse-date shape data))

(defn parse-non-payload-attrs
  "Parse HTTP status and headers for response data."
  [shapes {:keys [members] :as output-shape} {:keys [status headers] :as _http-response}]
  (reduce (fn [parsed member-key]
            (let [member-shape (shape/resolve shapes (shape/structure-member-shape-ref output-shape member-key))]
              (case (:location member-shape)
                "statusCode" (assoc parsed member-key status)
                "headers" (let [prefix (str/lower-case (or (:locationName member-shape) ""))
                                member-value (reduce-kv (fn [parsed k v]
                                                          (let [header-name (str/lower-case (name k))]
                                                            (if (.startsWith header-name prefix)
                                                              (assoc parsed
                                                                     (keyword (.substring (name k) (count prefix)))
                                                                     v)
                                                              parsed)))
                                                        {}
                                                        headers)]
                            (assoc parsed member-key member-value))
                "header" (let [header-name (str/lower-case (or (:locationName member-shape)
                                                               (name member-key)))]
                           (merge parsed
                                  (reduce-kv (fn [m k v]
                                               (if (= header-name (str/lower-case (name k)))
                                                 (assoc m member-key (parse-header-value member-shape v))
                                                 m))
                                             {}
                                             headers)))
                parsed)))
          {}
          (keys members)))

(defn parse-body
  "Parse the HTTP response body for response data."
  [shapes output-shape body parse-body-str]
  (if-let [payload-name (:payload output-shape)]
    (let [body-shape (shape/resolve shapes (shape/structure-member-shape-ref output-shape (keyword payload-name)))]
      {(keyword payload-name) (condp = (:type body-shape)
                                "blob"   (util/bbuf->input-stream body)
                                "string" (util/bbuf->str body)
                                (parse-body-str shapes body-shape (util/bbuf->str body)))})
    ;; No payload
    (let [body-str (util/bbuf->str body)]
      (when-not (str/blank? body-str)
        (parse-body-str shapes output-shape body-str)))))

(defn parse-http-response
  [service {:keys [op]} {:keys [body] :as http-response} parse-body-str]
  (let [operation (get-in service [:operations op])
        shapes (:shapes service)
        output-shape (shape/resolve shapes (:output operation))]
    (merge (parse-non-payload-attrs shapes output-shape http-response)
           (when output-shape
             (parse-body shapes output-shape body parse-body-str)))))




© 2015 - 2025 Weber Informatics LLC | Privacy Policy