-
Notifications
You must be signed in to change notification settings - Fork 3
Implement users endpoints in Swift #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c70bc5a
d6ca1dc
6edd0a2
148fc3a
86ef464
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Expose necessary Rust APIs as public API to the Swift package's consumers. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a super interesting idea. I'd originally thought we'd do Can a I'd imagine each export type will need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think so.
|
||
// | ||
// We could export all of them using `@_exported import`, but that probably puts | ||
// us in a position where we need to make major releases due to Rust code changes. | ||
|
||
import wordpress_api_wrapper | ||
|
||
public typealias WpApiError = wordpress_api_wrapper.WpApiError | ||
|
||
// MARK: - Users | ||
|
||
public typealias SparseUser = wordpress_api_wrapper.SparseUser | ||
|
||
public extension SparseUser { | ||
typealias ID = UserId | ||
typealias View = UserWithViewContext | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't love the single-word types here – I think whatever the type name is here it probably needs |
||
typealias Edit = UserWithEditContext | ||
typealias Embed = UserWithEmbedContext | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import Foundation | ||
import wordpress_api_wrapper | ||
|
||
extension WordPressAPI { | ||
public var users: Namespace<SparseUser> { | ||
.init(api: self) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Organizing things with namespaces seems kinda nice, but I wonder about the additional overhead here with generics (and if it's worthwhile/avoidable?) You could have:
Also a nit – I wonder if it'd be possible to encourage the compiler to inline this allocation for us – the underlying object isn't really needed at runtime? WDYT? |
||
} | ||
} | ||
|
||
extension Namespace where T == SparseUser { | ||
|
||
public func get(id: T.ID) async throws -> T.View { | ||
let request = self.api.helper.retrieveUserRequest(userId: id, context: .view) | ||
let response = try await api.perform(request: request) | ||
return try parseRetrieveUserResponseWithViewContext(response: response) | ||
} | ||
|
||
public func list() async throws -> [T.View] { | ||
let request = self.api.helper.listUsersRequest(context: .view, params: nil) | ||
let response = try await api.perform(request: request) | ||
return try parseListUsersResponseWithViewContext(response: response) | ||
} | ||
|
||
public func delete(id: T.ID, reassignTo userID: T.ID) async throws { | ||
let request = self.api.helper.deleteUserRequest(userId: id, params: .init(reassign: userID)) | ||
let response = try await api.perform(request: request) | ||
// TODO: Missing parse response | ||
return | ||
} | ||
|
||
public func update(id: T.ID, with params: UserUpdateParams) async throws -> T.Edit { | ||
let request = self.api.helper.updateUserRequest(userId: id, params: params) | ||
let response = try await self.api.perform(request: request) | ||
return try parseRetrieveUserResponseWithEditContext(response: response) | ||
} | ||
|
||
public func create(using params: UserCreateParams) async throws -> T.Edit { | ||
let request = self.api.helper.createUserRequest(params: params) | ||
let response = try await self.api.perform(request: request) | ||
return try parseRetrieveUserResponseWithEditContext(response: response) | ||
} | ||
|
||
} | ||
|
||
extension Namespace where T == SparseUser { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this separation of "common" methods vs "object-specific" methods |
||
|
||
public func getCurrent() async throws -> T.View { | ||
let request = self.api.helper.retrieveCurrentUserRequest(context: .view) | ||
let response = try await api.perform(request: request) | ||
return try parseRetrieveUserResponseWithViewContext(response: response) | ||
} | ||
|
||
public func deleteCurrent(reassignTo userID: T.ID) async throws { | ||
let request = self.api.helper.deleteCurrentUserRequest(params: .init(reassign: userID)) | ||
let response = try await api.perform(request: request) | ||
// TODO: Parse response to check if there is any error | ||
return | ||
} | ||
|
||
public func updateCurrent(with params: UserUpdateParams) async throws -> T.Edit { | ||
let request = self.api.helper.updateCurrentUserRequest(params: params) | ||
let response = try await self.api.perform(request: request) | ||
return try parseRetrieveUserResponseWithEditContext(response: response) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
#if os(macOS) || os(Linux) | ||
|
||
import XCTest | ||
import wordpress_api | ||
|
||
import wordpress_api_wrapper | ||
|
||
let site = LocalSite() | ||
|
||
final class LocalSite { | ||
|
||
enum Errors: Error { | ||
/// Run `make test-server` before running end to end tests. | ||
case testServerNotRunning(underlyingError: Error) | ||
/// `localhost:80` is not wordpress site. Make sure to run `make test-server` before running end to end tests. | ||
case notWordPressSite | ||
/// Can't read the test credential file for the local test site. | ||
case testCredentialNotFound(underlyingError: Error) | ||
} | ||
|
||
let siteURL = URL(string: "http://localhost")! | ||
let currentUserID: SparseUser.ID = 1 | ||
|
||
private let username = "test@example.com" | ||
|
||
private var _api: WordPressAPI? | ||
|
||
/// Get an authenticationed API client for the admin user. | ||
var api: WordPressAPI { | ||
get async throws { | ||
if _api == nil { | ||
_api = try await createAPIClient() | ||
} | ||
return _api! | ||
} | ||
} | ||
|
||
private func createAPIClient() async throws -> WordPressAPI { | ||
try await ensureTestServerRunning() | ||
let password = try readPassword() | ||
|
||
return WordPressAPI( | ||
urlSession: .shared, | ||
baseUrl: siteURL, | ||
authenticationStategy: .init(username: username, password: password) | ||
) | ||
} | ||
|
||
private func ensureTestServerRunning() async throws { | ||
let api = WordPressAPI(urlSession: .shared, baseUrl: siteURL, authenticationStategy: .none) | ||
let response: WpNetworkResponse | ||
do { | ||
let request = WpNetworkRequest( | ||
method: .get, url: siteURL.appendingPathComponent("/wp-json").absoluteString, | ||
headerMap: [:], body: nil) | ||
response = try await api.perform(request: request) | ||
} catch { | ||
throw Errors.testServerNotRunning(underlyingError: error) | ||
} | ||
|
||
if response.statusCode != 200 { | ||
throw Errors.notWordPressSite | ||
} | ||
} | ||
|
||
private func readPassword() throws -> String { | ||
#if os(Linux) | ||
let file = URL(fileURLWithPath: #filePath) | ||
#else | ||
let file = URL(filePath: #filePath) | ||
#endif | ||
let testCredentialFile = URL(string: "../../../../test_credentials", relativeTo: file)! | ||
.absoluteURL | ||
let content: String | ||
do { | ||
content = try String(contentsOf: testCredentialFile) | ||
} catch { | ||
throw Errors.testCredentialNotFound(underlyingError: error) | ||
} | ||
|
||
return content.trimmingCharacters(in: .newlines) | ||
} | ||
|
||
} | ||
|
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most CI systems (including Buildkite) define
CI=true
I think we could probably just use that?