Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ let package = Package(
.library(
name: "PostgREST",
targets: ["PostgREST"]
),
)
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(
name: "SnapshotTesting",
url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.8.1")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
Expand All @@ -25,7 +27,7 @@ let package = Package(
),
.testTarget(
name: "PostgRESTTests",
dependencies: ["PostgREST"]
),
dependencies: ["PostgREST", "SnapshotTesting"]
)
]
)
178 changes: 96 additions & 82 deletions Sources/PostgREST/PostgrestBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,98 @@ import Foundation

public class PostgrestBuilder {
var url: String
var queryParams: [(name: String, value: String)]
var headers: [String: String]
var schema: String?
var method: String?
var body: [String: Any]?

public init(url: String, headers: [String: String] = [:], schema: String?) {
self.url = url
self.headers = headers
self.schema = schema
}

public init(url: String, method: String?, headers: [String: String] = [:], schema: String?, body: [String: Any]?) {
init(
url: String, queryParams: [(name: String, value: String)], headers: [String: String],
schema: String?, method: String?, body: [String: Any]?
) {
self.url = url
self.queryParams = queryParams
self.headers = headers
self.schema = schema
self.method = method
self.body = body
}

public func execute(head: Bool = false, count: CountOption? = nil, completion: @escaping (Result<PostgrestResponse, Error>) -> Void) {
let request: URLRequest
do {
request = try buildURLRequest(head: head, count: count)
} catch {
completion(.failure(error))
return
}

let session = URLSession.shared
let dataTask = session.dataTask(with: request, completionHandler: { [unowned self] (data, response, error) -> Void in
if let error = error {
completion(.failure(error))
return
}

guard let response = response as? HTTPURLResponse else {
completion(.failure(PostgrestError(message: "failed to get response")))
return
}

guard let data = data else {
completion(.failure(PostgrestError(message: "empty data")))
return
}

do {
try validate(data: data, response: response)
let response = try parse(data: data, response: response)
completion(.success(response))
} catch {
completion(.failure(error))
}
})

dataTask.resume()
}

private func validate(data: Data, response: HTTPURLResponse) throws {
if 200 ..< 300 ~= response.statusCode {
return
}

guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
throw PostgrestError(message: "failed to get error")
}

throw PostgrestError(from: json) ?? PostgrestError(message: "failed to get error")
}

private func parse(data: Data, response: HTTPURLResponse) throws -> PostgrestResponse {
var body: Any = data
var count: Int?

if method == "HEAD" {
if let accept = response.allHeaderFields["Accept"] as? String, accept == "text/csv" {
body = data
} else {
try JSONSerialization.jsonObject(with: data, options: [])
}
}

if let contentRange = response.allHeaderFields["content-range"] as? String,
let lastElement = contentRange.split(separator: "/").last {
count = lastElement == "*" ? nil : Int(lastElement)
}

let postgrestResponse = PostgrestResponse(body: body)
postgrestResponse.status = response.statusCode
postgrestResponse.count = count
return postgrestResponse
}

func buildURLRequest(head: Bool, count: CountOption?) throws -> URLRequest {
if head {
method = "HEAD"
}
Expand All @@ -34,100 +106,42 @@ public class PostgrestBuilder {
}
}

if method == nil {
completion(.failure(PostgrestError(message: "Missing table operation: select, insert, update or delete")))
return
guard let method = method else {
throw PostgrestError(message: "Missing table operation: select, insert, update or delete")
}

if let method = method, method == "GET" || method == "HEAD" {
if method == "GET" || method == "HEAD" {
headers["Content-Type"] = "application/json"
}

if let schema = schema {
if let method = method, method == "GET" || method == "HEAD" {
if method == "GET" || method == "HEAD" {
headers["Accept-Profile"] = schema
} else {
headers["Content-Profile"] = schema
}
}

guard let url = URL(string: url) else {
completion(.failure(PostgrestError(message: "badURL")))
return
guard var components = URLComponents(string: url) else {
throw PostgrestError(message: "badURL")
}

if !queryParams.isEmpty {
components.queryItems = components.queryItems ?? []
components.queryItems!.append(contentsOf: queryParams.map(URLQueryItem.init))
}

guard let url = components.url else {
throw PostgrestError(message: "badURL")
}

var request = URLRequest(url: url)
request.httpMethod = method
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request, completionHandler: { [unowned self] (data, response, error) -> Void in
if let error = error {
completion(.failure(error))
return
}

if let resp = response as? HTTPURLResponse {
if let data = data {
do {
completion(.success(try self.parse(data: data, response: resp)))
} catch {
completion(.failure(error))
return
}
}
} else {
completion(.failure(PostgrestError(message: "failed to get response")))
}

})

dataTask.resume()
}

private func parse(data: Data, response: HTTPURLResponse) throws -> PostgrestResponse {
if response.statusCode == 200 || 200 ..< 300 ~= response.statusCode {
var body: Any = data
var count: Int?

if let method = method, method == "HEAD" {
if let accept = response.allHeaderFields["Accept"] as? String, accept == "text/csv" {
body = data
} else {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
body = json
} catch {
throw error
}
}
}

if let contentRange = response.allHeaderFields["content-range"] as? String, let lastElement = contentRange.split(separator: "/").last {
count = lastElement == "*" ? nil : Int(lastElement)
}

let postgrestResponse = PostgrestResponse(body: body)
postgrestResponse.status = response.statusCode
postgrestResponse.count = count
return postgrestResponse
} else {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
if let errorJson: [String: Any] = json as? [String: Any] {
throw PostgrestError(from: errorJson) ?? PostgrestError(message: "failed to get error")
} else {
throw PostgrestError(message: "failed to get error")
}
} catch {
throw error
}
}
return request
}

func appendSearchParams(name: String, value: String) {
var urlComponent = URLComponents(string: url)
urlComponent?.queryItems?.append(URLQueryItem(name: name, value: value))
url = urlComponent?.url?.absoluteString ?? url
queryParams.append((name, value))
}
}
6 changes: 2 additions & 4 deletions Sources/PostgREST/PostgrestClient.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


public class PostgrestClient {
var url: String
var headers: [String: String]
Expand All @@ -12,10 +10,10 @@ public class PostgrestClient {
}

public func form(_ table: String) -> PostgrestQueryBuilder {
return PostgrestQueryBuilder(url: "\(url)/\(table)", headers: headers, schema: schema)
return PostgrestQueryBuilder(url: "\(url)/\(table)", queryParams: [], headers: headers, schema: schema, method: nil, body: nil)
}

public func rpc(fn: String, parameters: [String: Any]?) -> PostgrestTransformBuilder {
return PostgrestRpcBuilder(url: "\(url)/rpc/\(fn)", headers: headers, schema: schema).rpc(parameters: parameters)
return PostgrestRpcBuilder(url: "\(url)/rpc/\(fn)", queryParams: [], headers: headers, schema: schema, method: nil, body: nil).rpc(parameters: parameters)
}
}
Loading