MethodConfig.swift 24 KB

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