Browse Source

Allow a Status to be created from an HTTP status code (#2170)

Motivation:

gRPC has a well defined mapping of HTTP status codes to gRPC status
codes for when a response doesn't explicitly include a gRPC status.

Modifications:

- Add an init to status to allow for a status to be created from an HTTP
status code.

Result:

Can create a status from an HTTP status code
George Barnett 1 year ago
parent
commit
067938bed6
2 changed files with 48 additions and 0 deletions
  1. 29 0
      Sources/GRPCCore/Status.swift
  2. 19 0
      Tests/GRPCCoreTests/StatusTests.swift

+ 29 - 0
Sources/GRPCCore/Status.swift

@@ -296,3 +296,32 @@ extension Status.Code {
   /// operation.
   public static let unauthenticated = Self(code: .unauthenticated)
 }
+
+extension Status {
+  /// Create a status from an HTTP status code for a response which didn't include a gRPC status.
+  ///
+  /// - Parameter httpStatusCode: The HTTP status code to map to a status.
+  public init(httpStatusCode: Int) {
+    // See the "http-grpc-status-mapping.md" doc in grpc/grpc GitHub repo.
+    switch httpStatusCode {
+    case 400:
+      self = Status(code: .internalError, message: "HTTP 400: Bad Request")
+    case 401:
+      self = Status(code: .unauthenticated, message: "HTTP 401: Unauthorized")
+    case 403:
+      self = Status(code: .permissionDenied, message: "HTTP 403: Forbidden")
+    case 404:
+      self = Status(code: .unimplemented, message: "HTTP 404: Not Found")
+    case 429:
+      self = Status(code: .unavailable, message: "HTTP 429: Too Many Requests")
+    case 502:
+      self = Status(code: .unavailable, message: "HTTP 502: Bad Gateway")
+    case 503:
+      self = Status(code: .unavailable, message: "HTTP 503: Service Unavailable")
+    case 504:
+      self = Status(code: .unavailable, message: "HTTP 504: Gateway Timeout")
+    default:
+      self = Status(code: .unknown, message: "HTTP \(httpStatusCode)")
+    }
+  }
+}

+ 19 - 0
Tests/GRPCCoreTests/StatusTests.swift

@@ -69,4 +69,23 @@ struct StatusTests {
   func fitsInExistentialContainer() {
     #expect(MemoryLayout<Status>.size <= 24)
   }
+
+  @Test(
+    "From HTTP status code",
+    arguments: [
+      (400, Status(code: .internalError, message: "HTTP 400: Bad Request")),
+      (401, Status(code: .unauthenticated, message: "HTTP 401: Unauthorized")),
+      (403, Status(code: .permissionDenied, message: "HTTP 403: Forbidden")),
+      (404, Status(code: .unimplemented, message: "HTTP 404: Not Found")),
+      (429, Status(code: .unavailable, message: "HTTP 429: Too Many Requests")),
+      (502, Status(code: .unavailable, message: "HTTP 502: Bad Gateway")),
+      (503, Status(code: .unavailable, message: "HTTP 503: Service Unavailable")),
+      (504, Status(code: .unavailable, message: "HTTP 504: Gateway Timeout")),
+      (418, Status(code: .unknown, message: "HTTP 418")),
+    ]
+  )
+  func convertFromHTTPStatusCode(code: Int, expected: Status) {
+    let status = Status(httpStatusCode: code)
+    #expect(status == expected)
+  }
 }