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

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

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

(ns ^:skip-wiki cognitect.aws.protocols
  "Impl, don't call directly. "
  (:require [clojure.data.json :as json]
            [clojure.string :as str]
            [cognitect.aws.util :as util])
  (:import (java.time ZoneOffset ZonedDateTime)))

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

(defmulti parse-http-response
  "HTTP response -> AWS response"
  (fn [service _op-map _http-response]
    (get-in service [:metadata :protocol])))

(defmulti build-http-request
  "AWS request -> HTTP request."
  (fn [service _op-map]
    (get-in service [:metadata :protocol])))

(defn ^:private status-code->anomaly-category [^long code]
  (case code
    304 :cognitect.anomalies/conflict
    403 :cognitect.anomalies/forbidden
    404 :cognitect.anomalies/not-found
    429 :cognitect.anomalies/busy
    503 :cognitect.anomalies/busy
    504 :cognitect.anomalies/unavailable
    (if (<= 300 code 499)
      :cognitect.anomalies/incorrect
      :cognitect.anomalies/fault)))

(defn sanitize-error-code
  "Per https://smithy.io/2.0/aws/protocols/aws-restjson1-protocol.html#operation-error-serialization:
    If a : character is present, then take only the contents before the first : character in the value.
    If a # character is present, then take only the contents after the first # character in the value."
  [error-code]
  (some-> error-code
          (str/split #":")
          first
          (str/split #"#" 2)
          last))

(defn error-code
  "Attempt to extract an error code from well known locations in an
   error response body. Returns nil if none are found.

   See:
     https://smithy.io/2.0/aws/protocols/aws-restjson1-protocol.html#operation-error-serialization
     https://smithy.io/2.0/aws/protocols/aws-json-1_0-protocol.html#operation-error-serialization
     https://smithy.io/2.0/aws/protocols/aws-json-1_1-protocol.html#operation-error-serialization
     https://smithy.io/2.0/aws/protocols/aws-restxml-protocol.html#error-response-serialization
     https://smithy.io/2.0/aws/protocols/aws-query-protocol.html#operation-error-serialization
     https://smithy.io/2.0/aws/protocols/aws-ec2-query-protocol.html#operation-error-serialization"
  [http-response]
  (or (-> http-response :headers (get "x-amzn-errortype"))
      (-> http-response :body :__type)
      (-> http-response :body :code)
      (-> http-response :body :Error :Code)
      (-> http-response :body :ErrorResponse :Error :Code)
      (-> http-response :body :Response :Errors :Error :Code)))

(defn ^:private error-code->anomaly-category
  "Given an error message extracted from an error response body *that we
   understand*, returns the appropriate anomaly category, or nil if none
   are found."
  [error-code]
  (condp = error-code
    "ThrottlingException" :cognitect.anomalies/busy
    nil))

(defn ^:private anomaly-category
  "Given an http-response with the body already coerced to a Clojure map,
   attempt to return an anomaly-category for a specific error message or
   status. Returns nil if none are found."
  [status sanitized-error-code]
  (or (error-code->anomaly-category sanitized-error-code)
      (status-code->anomaly-category status)))

(defn ^:private anomaly-message
  "Given 301 with an x-amz-bucket-region header, returns a clear message with direction
   for the user to resubmit the request to the correct region. Else returns nil."
  [response-map]
  (when-let [region (and (= 301 (:status response-map))
                         (get (:headers response-map) "x-amz-bucket-region"))]
    (str "The bucket is in this region: " region ". Please use this region to retry the request.")))

(defn headers [service operation]
  (let [{:keys [protocol targetPrefix jsonVersion]} (:metadata service)]
    (cond-> {"x-amz-date" (.format util/x-amz-date-format (ZonedDateTime/now ZoneOffset/UTC))}
      (contains? #{"json" "rest-json"} protocol)
      (assoc "x-amz-target" (str targetPrefix "." (:name operation))
             "content-type" (str "application/x-amz-json-" jsonVersion)
             ;; NOTE: apigateway returns application/hal+json unless
             ;; we specify the accept header
             "accept"       "application/json")
      (contains? #{"query" "ec2"} protocol)
      (assoc "content-type" "application/x-www-form-urlencoded; charset=utf-8"))))

(defn ^:private parse-encoded-string
  "Given non-nil String, determine the encoding (currently either XML or JSON). Return a Map
   representation of the encoded data.

   Returns nil if encoded-str is nil."
  [encoded-str]
  (when (seq encoded-str)
    (if (= \< (first encoded-str))
      (-> encoded-str util/xml-read util/xml->map)
      (-> encoded-str (json/read-str :key-fn keyword)))))

(defn parse-http-error-response
  "Given an http error response (any status code 300 or above), return an aws-api-specific response
  Map."
  [{:keys [status] :as http-response}]
  (let [http-response* (update http-response :body #(some-> % util/bbuf->str parse-encoded-string))
        sanitized-error-code (-> http-response* error-code sanitize-error-code)
        category (anomaly-category (:status http-response) sanitized-error-code)
        message (anomaly-message http-response*)]
    (with-meta
      (cond-> (assoc (:body http-response*) :cognitect.aws.http/status status)
        category (assoc :cognitect.anomalies/category category)
        message  (assoc :cognitect.anomalies/message message)
        sanitized-error-code (assoc :cognitect.aws.error/code sanitized-error-code))
      http-response)))




© 2015 - 2025 Weber Informatics LLC | Privacy Policy