MethodConfiguration.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. /*
  2. * Copyright 2023, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /// Configuration values for executing an RPC.
  17. ///
  18. /// See also: https://github.com/grpc/grpc-proto/blob/master/grpc/service_config/service_config.proto
  19. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  20. public struct MethodConfiguration: Hashable, Sendable {
  21. public struct Name: Sendable, Hashable {
  22. /// The name of the service, including the namespace.
  23. ///
  24. /// If the service is empty then `method` must also be empty and the configuration specifies
  25. /// defaults for all methods.
  26. ///
  27. /// - Precondition: If `service` is empty then `method` must also be empty.
  28. public var service: String {
  29. didSet { try! self.validate() }
  30. }
  31. /// The name of the method.
  32. ///
  33. /// If the method is empty then the configuration will be the default for all methods in the
  34. /// specified service.
  35. public var method: String
  36. /// Create a new name.
  37. ///
  38. /// If the service is empty then `method` must also be empty and the configuration specifies
  39. /// defaults for all methods. If only `method` is empty then the configuration applies to
  40. /// all methods in the `service`.
  41. ///
  42. /// - Parameters:
  43. /// - service: The name of the service, including the namespace.
  44. /// - method: The name of the method.
  45. public init(service: String, method: String = "") {
  46. self.service = service
  47. self.method = method
  48. try! self.validate()
  49. }
  50. private func validate() throws {
  51. if self.service.isEmpty && !self.method.isEmpty {
  52. throw RuntimeError(
  53. code: .invalidArgument,
  54. message: "'method' must be empty if 'service' is empty."
  55. )
  56. }
  57. }
  58. }
  59. /// The names of methods which this configuration applies to.
  60. public var names: [Name]
  61. /// The default timeout for the RPC.
  62. ///
  63. /// If no reply is received in the specified amount of time the request is aborted
  64. /// with an ``RPCError`` with code ``RPCError/Code/deadlineExceeded``.
  65. ///
  66. /// The actual deadline used will be the minimum of the value specified here
  67. /// and the value set by the application by the client API. If either one isn't set
  68. /// then the other value is used. If neither is set then the request has no deadline.
  69. ///
  70. /// The timeout applies to the overall execution of an RPC. If, for example, a retry
  71. /// policy is set then the timeout begins when the first attempt is started and _isn't_ reset
  72. /// when subsequent attempts start.
  73. public var timeout: Duration?
  74. /// The maximum allowed payload size in bytes for an individual message.
  75. ///
  76. /// If a client attempts to send an object larger than this value, it will not be sent and the
  77. /// client will see an error. Note that 0 is a valid value, meaning that the request message
  78. /// must be empty.
  79. public var maxRequestMessageBytes: Int?
  80. /// The maximum allowed payload size in bytes for an individual response message.
  81. ///
  82. /// If a server attempts to send an object larger than this value, it will not
  83. /// be sent, and an error will be sent to the client instead. Note that 0 is a valid value,
  84. /// meaning that the response message must be empty.
  85. public var maxResponseMessageBytes: Int?
  86. /// The policy determining how many times, and when, the RPC is executed.
  87. ///
  88. /// There are two policy types:
  89. /// 1. Retry
  90. /// 2. Hedging
  91. ///
  92. /// The retry policy allows an RPC to be retried a limited number of times if the RPC
  93. /// fails with one of the configured set of status codes. RPCs are only retried if they
  94. /// fail immediately, that is, the first response part received from the server is a
  95. /// status code.
  96. ///
  97. /// The hedging policy allows an RPC to be executed multiple times concurrently. Typically
  98. /// each execution will be staggered by some delay. The first successful response will be
  99. /// reported to the client. Hedging is only suitable for idempotent RPCs.
  100. public var executionPolicy: ExecutionPolicy?
  101. /// Create an execution configuration.
  102. ///
  103. /// - Parameters:
  104. /// - names: The names of methods this configuration applies to.
  105. /// - timeout: The default timeout for the RPC.
  106. /// - maxRequestMessageBytes: The maximum allowed size of a request message in bytes.
  107. /// - maxResponseMessageBytes: The maximum allowed size of a response message in bytes.
  108. /// - executionPolicy: The execution policy to use for the RPC.
  109. public init(
  110. names: [Name],
  111. timeout: Duration? = nil,
  112. maxRequestMessageBytes: Int? = nil,
  113. maxResponseMessageBytes: Int? = nil,
  114. executionPolicy: ExecutionPolicy? = nil
  115. ) {
  116. self.names = names
  117. self.timeout = timeout
  118. self.maxRequestMessageBytes = maxRequestMessageBytes
  119. self.maxResponseMessageBytes = maxResponseMessageBytes
  120. self.executionPolicy = executionPolicy
  121. }
  122. }
  123. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  124. extension MethodConfiguration {
  125. /// The execution policy for an RPC.
  126. public enum ExecutionPolicy: Hashable, Sendable {
  127. /// Policy for retrying an RPC.
  128. ///
  129. /// See ``RetryPolicy`` for more details.
  130. case retry(RetryPolicy)
  131. /// Policy for hedging an RPC.
  132. ///
  133. /// See ``HedgingPolicy`` for more details.
  134. case hedge(HedgingPolicy)
  135. }
  136. }
  137. /// Policy for retrying an RPC.
  138. ///
  139. /// gRPC retries RPCs when the first response from the server is a status code which matches
  140. /// one of the configured retryable status codes. If the server begins processing the RPC and
  141. /// first responds with metadata and later responds with a retryable status code then the RPC
  142. /// won't be retried.
  143. ///
  144. /// Execution attempts are limited by ``maximumAttempts`` which includes the original attempt. The
  145. /// maximum number of attempts is limited to five.
  146. ///
  147. /// Subsequent attempts are executed after some delay. The first _retry_, or second attempt, will
  148. /// be started after a randomly chosen delay between zero and ``initialBackoff``. More generally,
  149. /// the nth retry will happen after a randomly chosen delay between zero
  150. /// and `min(initialBackoff * backoffMultiplier^(n-1), maximumBackoff)`.
  151. ///
  152. /// For more information see [gRFC A6 Client
  153. /// Retries](https://github.com/grpc/proposal/blob/master/A6-client-retries.md).
  154. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  155. public struct RetryPolicy: Hashable, Sendable {
  156. /// The maximum number of RPC attempts, including the original attempt.
  157. ///
  158. /// Must be greater than one, values greater than five are treated as five.
  159. public var maximumAttempts: Int {
  160. didSet { self.maximumAttempts = try! validateMaxAttempts(self.maximumAttempts) }
  161. }
  162. /// The initial backoff duration.
  163. ///
  164. /// The initial retry will occur after a random amount of time up to this value.
  165. ///
  166. /// - Precondition: Must be greater than zero.
  167. public var initialBackoff: Duration {
  168. willSet { try! Self.validateInitialBackoff(newValue) }
  169. }
  170. /// The maximum amount of time to backoff for.
  171. ///
  172. /// - Precondition: Must be greater than zero.
  173. public var maximumBackoff: Duration {
  174. willSet { try! Self.validateMaxBackoff(newValue) }
  175. }
  176. /// The multiplier to apply to backoff.
  177. ///
  178. /// - Precondition: Must be greater than zero.
  179. public var backoffMultiplier: Double {
  180. willSet { try! Self.validateBackoffMultiplier(newValue) }
  181. }
  182. /// The set of status codes which may be retried.
  183. ///
  184. /// - Precondition: Must not be empty.
  185. public var retryableStatusCodes: Set<Status.Code> {
  186. willSet { try! Self.validateRetryableStatusCodes(newValue) }
  187. }
  188. /// Create a new retry policy.
  189. ///
  190. /// - Parameters:
  191. /// - maximumAttempts: The maximum number of attempts allowed for the RPC.
  192. /// - initialBackoff: The initial backoff period for the first retry attempt. Must be
  193. /// greater than zero.
  194. /// - maximumBackoff: The maximum period of time to wait between attempts. Must be greater than
  195. /// zero.
  196. /// - backoffMultiplier: The exponential backoff multiplier. Must be greater than zero.
  197. /// - retryableStatusCodes: The set of status codes which may be retried. Must not be empty.
  198. /// - Precondition: `maximumAttempts`, `initialBackoff`, `maximumBackoff` and `backoffMultiplier`
  199. /// must be greater than zero.
  200. /// - Precondition: `retryableStatusCodes` must not be empty.
  201. public init(
  202. maximumAttempts: Int,
  203. initialBackoff: Duration,
  204. maximumBackoff: Duration,
  205. backoffMultiplier: Double,
  206. retryableStatusCodes: Set<Status.Code>
  207. ) {
  208. self.maximumAttempts = try! validateMaxAttempts(maximumAttempts)
  209. try! Self.validateInitialBackoff(initialBackoff)
  210. self.initialBackoff = initialBackoff
  211. try! Self.validateMaxBackoff(maximumBackoff)
  212. self.maximumBackoff = maximumBackoff
  213. try! Self.validateBackoffMultiplier(backoffMultiplier)
  214. self.backoffMultiplier = backoffMultiplier
  215. try! Self.validateRetryableStatusCodes(retryableStatusCodes)
  216. self.retryableStatusCodes = retryableStatusCodes
  217. }
  218. private static func validateInitialBackoff(_ value: Duration) throws {
  219. if value <= .zero {
  220. throw RuntimeError(
  221. code: .invalidArgument,
  222. message: "initialBackoff must be greater than zero"
  223. )
  224. }
  225. }
  226. private static func validateMaxBackoff(_ value: Duration) throws {
  227. if value <= .zero {
  228. throw RuntimeError(
  229. code: .invalidArgument,
  230. message: "maximumBackoff must be greater than zero"
  231. )
  232. }
  233. }
  234. private static func validateBackoffMultiplier(_ value: Double) throws {
  235. if value <= 0 {
  236. throw RuntimeError(
  237. code: .invalidArgument,
  238. message: "backoffMultiplier must be greater than zero"
  239. )
  240. }
  241. }
  242. private static func validateRetryableStatusCodes(_ value: Set<Status.Code>) throws {
  243. if value.isEmpty {
  244. throw RuntimeError(code: .invalidArgument, message: "retryableStatusCodes mustn't be empty")
  245. }
  246. }
  247. }
  248. /// Policy for hedging an RPC.
  249. ///
  250. /// Hedged RPCs may execute more than once on a server so only idempotent methods should
  251. /// be hedged.
  252. ///
  253. /// gRPC executes the RPC at most ``maximumAttempts`` times, staggering each attempt
  254. /// by ``hedgingDelay``.
  255. ///
  256. /// For more information see [gRFC A6 Client
  257. /// Retries](https://github.com/grpc/proposal/blob/master/A6-client-retries.md).
  258. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  259. public struct HedgingPolicy: Hashable, Sendable {
  260. /// The maximum number of RPC attempts, including the original attempt.
  261. ///
  262. /// Values greater than five are treated as five.
  263. ///
  264. /// - Precondition: Must be greater than one.
  265. public var maximumAttempts: Int {
  266. didSet { self.maximumAttempts = try! validateMaxAttempts(self.maximumAttempts) }
  267. }
  268. /// The first RPC will be sent immediately, but each subsequent RPC will be sent at intervals
  269. /// of `hedgingDelay`. Set this to zero to immediately send all RPCs.
  270. public var hedgingDelay: Duration {
  271. willSet { try! Self.validateHedgingDelay(newValue) }
  272. }
  273. /// The set of status codes which indicate other hedged RPCs may still succeed.
  274. ///
  275. /// If a non-fatal status code is returned by the server, hedged RPCs will continue.
  276. /// Otherwise, outstanding requests will be cancelled and the error returned to the
  277. /// application layer.
  278. public var nonFatalStatusCodes: Set<Status.Code>
  279. /// Create a new hedging policy.
  280. ///
  281. /// - Parameters:
  282. /// - maximumAttempts: The maximum number of attempts allowed for the RPC.
  283. /// - hedgingDelay: The delay between each hedged RPC.
  284. /// - nonFatalStatusCodes: The set of status codes which indicate other hedged RPCs may still
  285. /// succeed.
  286. /// - Precondition: `maximumAttempts` must be greater than zero.
  287. public init(
  288. maximumAttempts: Int,
  289. hedgingDelay: Duration,
  290. nonFatalStatusCodes: Set<Status.Code>
  291. ) {
  292. self.maximumAttempts = try! validateMaxAttempts(maximumAttempts)
  293. try! Self.validateHedgingDelay(hedgingDelay)
  294. self.hedgingDelay = hedgingDelay
  295. self.nonFatalStatusCodes = nonFatalStatusCodes
  296. }
  297. private static func validateHedgingDelay(_ value: Duration) throws {
  298. if value < .zero {
  299. throw RuntimeError(
  300. code: .invalidArgument,
  301. message: "hedgingDelay must be greater than or equal to zero"
  302. )
  303. }
  304. }
  305. }
  306. private func validateMaxAttempts(_ value: Int) throws -> Int {
  307. guard value > 1 else {
  308. throw RuntimeError(
  309. code: .invalidArgument,
  310. message: "max_attempts must be greater than one (was \(value))"
  311. )
  312. }
  313. return min(value, 5)
  314. }
  315. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  316. extension Duration {
  317. fileprivate init(googleProtobufDuration duration: String) throws {
  318. guard duration.utf8.last == UInt8(ascii: "s"),
  319. let fractionalSeconds = Double(duration.dropLast())
  320. else {
  321. throw RuntimeError(code: .invalidArgument, message: "Invalid google.protobuf.duration")
  322. }
  323. let seconds = fractionalSeconds.rounded(.down)
  324. let attoseconds = (fractionalSeconds - seconds) / 1e18
  325. self.init(secondsComponent: Int64(seconds), attosecondsComponent: Int64(attoseconds))
  326. }
  327. }
  328. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  329. extension MethodConfiguration: Codable {
  330. private enum CodingKeys: String, CodingKey {
  331. case name
  332. case timeout
  333. case maxRequestMessageBytes
  334. case maxResponseMessageBytes
  335. case retryPolicy
  336. case hedgingPolicy
  337. }
  338. public init(from decoder: any Decoder) throws {
  339. let container = try decoder.container(keyedBy: CodingKeys.self)
  340. self.names = try container.decode([Name].self, forKey: .name)
  341. let timeout = try container.decodeIfPresent(GoogleProtobufDuration.self, forKey: .timeout)
  342. self.timeout = timeout?.duration
  343. let maxRequestSize = try container.decodeIfPresent(Int.self, forKey: .maxRequestMessageBytes)
  344. self.maxRequestMessageBytes = maxRequestSize
  345. let maxResponseSize = try container.decodeIfPresent(Int.self, forKey: .maxResponseMessageBytes)
  346. self.maxResponseMessageBytes = maxResponseSize
  347. if let policy = try container.decodeIfPresent(HedgingPolicy.self, forKey: .hedgingPolicy) {
  348. self.executionPolicy = .hedge(policy)
  349. } else if let policy = try container.decodeIfPresent(RetryPolicy.self, forKey: .retryPolicy) {
  350. self.executionPolicy = .retry(policy)
  351. } else {
  352. self.executionPolicy = nil
  353. }
  354. }
  355. public func encode(to encoder: any Encoder) throws {
  356. var container = encoder.container(keyedBy: CodingKeys.self)
  357. try container.encode(self.names, forKey: .name)
  358. try container.encodeIfPresent(
  359. self.timeout.map { GoogleProtobufDuration(duration: $0) },
  360. forKey: .timeout
  361. )
  362. try container.encodeIfPresent(self.maxRequestMessageBytes, forKey: .maxRequestMessageBytes)
  363. try container.encodeIfPresent(self.maxResponseMessageBytes, forKey: .maxResponseMessageBytes)
  364. switch self.executionPolicy {
  365. case .retry(let policy):
  366. try container.encode(policy, forKey: .retryPolicy)
  367. case .hedge(let policy):
  368. try container.encode(policy, forKey: .hedgingPolicy)
  369. case .none:
  370. ()
  371. }
  372. }
  373. }
  374. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  375. extension MethodConfiguration.Name: Codable {
  376. private enum CodingKeys: String, CodingKey {
  377. case service
  378. case method
  379. }
  380. public init(from decoder: Decoder) throws {
  381. let container = try decoder.container(keyedBy: CodingKeys.self)
  382. let service = try container.decodeIfPresent(String.self, forKey: .service)
  383. self.service = service ?? ""
  384. let method = try container.decodeIfPresent(String.self, forKey: .method)
  385. self.method = method ?? ""
  386. try self.validate()
  387. }
  388. public func encode(to encoder: any Encoder) throws {
  389. var container = encoder.container(keyedBy: CodingKeys.self)
  390. try container.encode(self.method, forKey: .method)
  391. try container.encode(self.service, forKey: .service)
  392. }
  393. }
  394. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  395. extension RetryPolicy: Codable {
  396. private enum CodingKeys: String, CodingKey {
  397. case maxAttempts
  398. case initialBackoff
  399. case maxBackoff
  400. case backoffMultiplier
  401. case retryableStatusCodes
  402. }
  403. public init(from decoder: any Decoder) throws {
  404. let container = try decoder.container(keyedBy: CodingKeys.self)
  405. let maxAttempts = try container.decode(Int.self, forKey: .maxAttempts)
  406. self.maximumAttempts = try validateMaxAttempts(maxAttempts)
  407. let initialBackoff = try container.decode(String.self, forKey: .initialBackoff)
  408. self.initialBackoff = try Duration(googleProtobufDuration: initialBackoff)
  409. try Self.validateInitialBackoff(self.initialBackoff)
  410. let maxBackoff = try container.decode(String.self, forKey: .maxBackoff)
  411. self.maximumBackoff = try Duration(googleProtobufDuration: maxBackoff)
  412. try Self.validateMaxBackoff(self.maximumBackoff)
  413. self.backoffMultiplier = try container.decode(Double.self, forKey: .backoffMultiplier)
  414. try Self.validateBackoffMultiplier(self.backoffMultiplier)
  415. let codes = try container.decode([GoogleRPCCode].self, forKey: .retryableStatusCodes)
  416. self.retryableStatusCodes = Set(codes.map { $0.code })
  417. try Self.validateRetryableStatusCodes(self.retryableStatusCodes)
  418. }
  419. public func encode(to encoder: any Encoder) throws {
  420. var container = encoder.container(keyedBy: CodingKeys.self)
  421. try container.encode(self.maximumAttempts, forKey: .maxAttempts)
  422. try container.encode(
  423. GoogleProtobufDuration(duration: self.initialBackoff),
  424. forKey: .initialBackoff
  425. )
  426. try container.encode(GoogleProtobufDuration(duration: self.maximumBackoff), forKey: .maxBackoff)
  427. try container.encode(self.backoffMultiplier, forKey: .backoffMultiplier)
  428. try container.encode(
  429. self.retryableStatusCodes.map { $0.googleRPCCode },
  430. forKey: .retryableStatusCodes
  431. )
  432. }
  433. }
  434. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  435. extension HedgingPolicy: Codable {
  436. private enum CodingKeys: String, CodingKey {
  437. case maxAttempts
  438. case hedgingDelay
  439. case nonFatalStatusCodes
  440. }
  441. public init(from decoder: any Decoder) throws {
  442. let container = try decoder.container(keyedBy: CodingKeys.self)
  443. let maxAttempts = try container.decode(Int.self, forKey: .maxAttempts)
  444. self.maximumAttempts = try validateMaxAttempts(maxAttempts)
  445. let delay = try container.decode(String.self, forKey: .hedgingDelay)
  446. self.hedgingDelay = try Duration(googleProtobufDuration: delay)
  447. let statusCodes = try container.decode([GoogleRPCCode].self, forKey: .nonFatalStatusCodes)
  448. self.nonFatalStatusCodes = Set(statusCodes.map { $0.code })
  449. }
  450. public func encode(to encoder: any Encoder) throws {
  451. var container = encoder.container(keyedBy: CodingKeys.self)
  452. try container.encode(self.maximumAttempts, forKey: .maxAttempts)
  453. try container.encode(GoogleProtobufDuration(duration: self.hedgingDelay), forKey: .hedgingDelay)
  454. try container.encode(
  455. self.nonFatalStatusCodes.map { $0.googleRPCCode },
  456. forKey: .nonFatalStatusCodes
  457. )
  458. }
  459. }
  460. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  461. struct GoogleProtobufDuration: Codable {
  462. var duration: Duration
  463. init(duration: Duration) {
  464. self.duration = duration
  465. }
  466. init(from decoder: any Decoder) throws {
  467. let container = try decoder.singleValueContainer()
  468. let duration = try container.decode(String.self)
  469. guard duration.utf8.last == UInt8(ascii: "s"),
  470. let fractionalSeconds = Double(duration.dropLast())
  471. else {
  472. throw RuntimeError(code: .invalidArgument, message: "Invalid google.protobuf.duration")
  473. }
  474. let seconds = fractionalSeconds.rounded(.down)
  475. let attoseconds = (fractionalSeconds - seconds) * 1e18
  476. self.duration = Duration(
  477. secondsComponent: Int64(seconds),
  478. attosecondsComponent: Int64(attoseconds)
  479. )
  480. }
  481. func encode(to encoder: any Encoder) throws {
  482. var container = encoder.singleValueContainer()
  483. var seconds = Double(self.duration.components.seconds)
  484. seconds += Double(self.duration.components.attoseconds) / 1e18
  485. let durationString = "\(seconds)s"
  486. try container.encode(durationString)
  487. }
  488. }
  489. struct GoogleRPCCode: Codable {
  490. var code: Status.Code
  491. init(code: Status.Code) {
  492. self.code = code
  493. }
  494. init(from decoder: Decoder) throws {
  495. let container = try decoder.singleValueContainer()
  496. let code: Status.Code?
  497. if let caseName = try? container.decode(String.self) {
  498. code = Status.Code(googleRPCCode: caseName)
  499. } else if let rawValue = try? container.decode(Int.self) {
  500. code = Status.Code(rawValue: rawValue)
  501. } else {
  502. code = nil
  503. }
  504. if let code = code {
  505. self.code = code
  506. } else {
  507. throw RuntimeError(code: .invalidArgument, message: "Invalid google.rpc.code")
  508. }
  509. }
  510. func encode(to encoder: Encoder) throws {
  511. var container = encoder.singleValueContainer()
  512. try container.encode(self.code.googleRPCCode)
  513. }
  514. }
  515. extension Status.Code {
  516. fileprivate init?(googleRPCCode code: String) {
  517. switch code {
  518. case "OK":
  519. self = .ok
  520. case "CANCELLED":
  521. self = .cancelled
  522. case "UNKNOWN":
  523. self = .unknown
  524. case "INVALID_ARGUMENT":
  525. self = .invalidArgument
  526. case "DEADLINE_EXCEEDED":
  527. self = .deadlineExceeded
  528. case "NOT_FOUND":
  529. self = .notFound
  530. case "ALREADY_EXISTS":
  531. self = .alreadyExists
  532. case "PERMISSION_DENIED":
  533. self = .permissionDenied
  534. case "RESOURCE_EXHAUSTED":
  535. self = .resourceExhausted
  536. case "FAILED_PRECONDITION":
  537. self = .failedPrecondition
  538. case "ABORTED":
  539. self = .aborted
  540. case "OUT_OF_RANGE":
  541. self = .outOfRange
  542. case "UNIMPLEMENTED":
  543. self = .unimplemented
  544. case "INTERNAL":
  545. self = .internalError
  546. case "UNAVAILABLE":
  547. self = .unavailable
  548. case "DATA_LOSS":
  549. self = .dataLoss
  550. case "UNAUTHENTICATED":
  551. self = .unauthenticated
  552. default:
  553. return nil
  554. }
  555. }
  556. fileprivate var googleRPCCode: String {
  557. switch self.wrapped {
  558. case .ok:
  559. return "OK"
  560. case .cancelled:
  561. return "CANCELLED"
  562. case .unknown:
  563. return "UNKNOWN"
  564. case .invalidArgument:
  565. return "INVALID_ARGUMENT"
  566. case .deadlineExceeded:
  567. return "DEADLINE_EXCEEDED"
  568. case .notFound:
  569. return "NOT_FOUND"
  570. case .alreadyExists:
  571. return "ALREADY_EXISTS"
  572. case .permissionDenied:
  573. return "PERMISSION_DENIED"
  574. case .resourceExhausted:
  575. return "RESOURCE_EXHAUSTED"
  576. case .failedPrecondition:
  577. return "FAILED_PRECONDITION"
  578. case .aborted:
  579. return "ABORTED"
  580. case .outOfRange:
  581. return "OUT_OF_RANGE"
  582. case .unimplemented:
  583. return "UNIMPLEMENTED"
  584. case .internalError:
  585. return "INTERNAL"
  586. case .unavailable:
  587. return "UNAVAILABLE"
  588. case .dataLoss:
  589. return "DATA_LOSS"
  590. case .unauthenticated:
  591. return "UNAUTHENTICATED"
  592. }
  593. }
  594. }