• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / supabase-swift / 17321717294

29 Aug 2025 10:44AM UTC coverage: 78.634% (+1.2%) from 77.386%
17321717294

Pull #781

github

web-flow
Merge 80b20054e into e4d8c3718
Pull Request #781: RFC: Migrate HTTP networking from URLSession to Alamofire

1027 of 1123 new or added lines in 27 files covered. (91.45%)

27 existing lines in 8 files now uncovered.

5156 of 6557 relevant lines covered (78.63%)

29.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.74
/Sources/PostgREST/PostgrestBuilder.swift
1
import Alamofire
2
import ConcurrencyExtras
3
import Foundation
4

5
#if canImport(FoundationNetworking)
6
  import FoundationNetworking
7
#endif
8

9
/// The builder class for creating and executing requests to a PostgREST server.
10
public class PostgrestBuilder: @unchecked Sendable {
11
  /// The configuration for the PostgREST client.
12
  let configuration: PostgrestClient.Configuration
13
  let session: Alamofire.Session
14

15
  struct MutableState {
16
    var request: URLRequest
17
    var query: Parameters
18

19
    /// The options for fetching data from the PostgREST server.
20
    var fetchOptions: FetchOptions
21
  }
22

23
  let mutableState: LockIsolated<MutableState>
24

25
  init(
26
    configuration: PostgrestClient.Configuration,
27
    request: URLRequest,
28
    query: Parameters
29
  ) {
110✔
30
    self.configuration = configuration
110✔
31
    self.session = configuration.session
110✔
32

110✔
33
    mutableState = LockIsolated(
110✔
34
      MutableState(
110✔
35
        request: request,
110✔
36
        query: query,
110✔
37
        fetchOptions: FetchOptions()
110✔
38
      )
110✔
39
    )
110✔
40
  }
110✔
41

42
  convenience init(_ other: PostgrestBuilder) {
52✔
43
    self.init(
52✔
44
      configuration: other.configuration,
52✔
45
      request: other.mutableState.value.request,
52✔
46
      query: other.mutableState.value.query
52✔
47
    )
52✔
48
  }
52✔
49

50
  /// Set a HTTP header for the request.
51
  @discardableResult
52
  public func setHeader(name: String, value: String) -> Self {
6✔
53
    mutableState.withValue {
6✔
54
      $0.request.headers[name] = value
6✔
55
    }
6✔
56
    return self
6✔
57
  }
6✔
58

59
  /// Executes the request and returns a response of type Void.
60
  /// - Parameters:
61
  ///   - options: Options for querying Supabase.
62
  /// - Returns: A `PostgrestResponse<Void>` instance representing the response.
63
  @discardableResult
64
  public func execute(
65
    options: FetchOptions = FetchOptions()
66
  ) async throws -> PostgrestResponse<Void> {
48✔
67
    try await execute(options: options) { _ in () }
48✔
68
  }
46✔
69

70
  /// Executes the request and returns a response of the specified type.
71
  /// - Parameters:
72
  ///   - options: Options for querying Supabase.
73
  /// - Returns: A `PostgrestResponse<T>` instance representing the response.
74
  @discardableResult
75
  public func execute<T: Decodable>(
76
    options: FetchOptions = FetchOptions()
77
  ) async throws -> PostgrestResponse<T> {
7✔
78
    try await execute(options: options) { [configuration] data in
7✔
79
      do {
7✔
80
        return try configuration.decoder.decode(T.self, from: data)
7✔
81
      } catch {
7✔
82
        configuration.logger?.error("Fail to decode type '\(T.self) with error: \(error)")
×
83
        throw error
×
84
      }
×
85
    }
7✔
86
  }
7✔
87

88
  private func execute<T>(
89
    options: FetchOptions,
90
    decode: (Data) throws -> T
91
  ) async throws -> PostgrestResponse<T> {
55✔
92
    let (request, query) = mutableState.withValue {
55✔
93
      $0.fetchOptions = options
55✔
94

55✔
95
      if $0.fetchOptions.head {
55✔
96
        $0.request.method = .head
2✔
97
      }
2✔
98

55✔
99
      if let count = $0.fetchOptions.count {
55✔
100
        $0.request.headers.appendOrUpdate("Prefer", value: "count=\(count.rawValue)")
1✔
101
      }
1✔
102

55✔
103
      if $0.request.headers["Accept"] == nil {
55✔
104
        $0.request.headers["Accept"] = "application/json"
50✔
105
      }
50✔
106
      $0.request.headers["Content-Type"] = "application/json"
55✔
107

55✔
108
      if let schema = configuration.schema {
55✔
109
        if $0.request.method == .get || $0.request.method == .head {
3✔
110
          $0.request.headers["Accept-Profile"] = schema
2✔
111
        } else {
2✔
112
          $0.request.headers["Content-Profile"] = schema
1✔
113
        }
1✔
114
      }
3✔
115

55✔
116
      return ($0.request, $0.query)
55✔
117
    }
55✔
118

55✔
119
    let urlEncoder = URLEncoding(destination: .queryString)
55✔
120

55✔
121
    let response = await session.request(try urlEncoder.encode(request, with: query))
55✔
122
      .validate { request, response, data in
55✔
123
        guard 200..<300 ~= response.statusCode else {
55✔
124

2✔
125
          guard let data else {
2✔
NEW
126
            return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
×
127
          }
2✔
128

2✔
129
          do {
2✔
130
            return .failure(
2✔
131
              try self.configuration.decoder.decode(PostgrestError.self, from: data)
2✔
132
            )
1✔
133
          } catch {
2✔
134
            return .failure(HTTPError(data: data, response: response))
1✔
135
          }
1✔
136
        }
53✔
137
        return .success(())
53✔
138
      }
55✔
139
      .serializingData()
55✔
140
      .response
55✔
141

55✔
142
    let value = try decode(response.result.get())
55✔
143

53✔
144
    return PostgrestResponse(
53✔
145
      data: response.data ?? Data(), response: response.response!, value: value)
53✔
146
  }
55✔
147
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc