Parse an URL-path based rest API - swift

I'm create a client app using a REST API. This one use a URL-path format, i.e /api/subPath/{variable}/otherSubPath
I know Apple gives a URLComponents class but seems to work very well only for URL-query-argument i.e /api/path?param=value
I would like to create a class URLBuilder for giving me the different API url dynamically. For the moment I ended up with a class who looks like this:
class URLBuilder {
fileprivate static let base = "https://theAPI.com/"
fileprivate static let objectsPath = kBase + "objects/"
static func informationOfObject(withID id: Int) {
return objectsPath + "\(id)/" + "information/"
}
// Many other functions like this
}
So I would like to know how to get something more elegant (if possible) and maybe use URL-path format with URLComponents.
Or maybe should I use regex? I've never use it but maybe it's useful here.

Have you looked into using the append... family of functions from URL?
appendPathComponent(_:)
appendPathComponent(_:isDirectory:)
appendingPathComponent(_:)
appendingPathComponent_:isDirectory:)
If I understand your requirements, you can do something like:
let baseURL = URL(string: "/api/subPath")!
let variableURL = baseURL.appendingPathComponent("variable")
let trailingURL = variableURL.appendingPathComponent("otherSubPath")
print(trailingURL.absoluteString)
Output: api/subPath/variable/otherSubPath
You could even chain the appending calls together if you need it on a single line.

Related

Transform whitespace to + as a result of user input in Swift

I am almost finished with my GitHub Repo Search app. I would need that if users' input contained whitespace, it would be changed to + instead (as GitHub's original browser does while changing the input to the part of the URL).
How to do this?
The code is quite complicated, has many dependancies and is scattered in several files, so if possible I don't want to copy all the passages.
From the frontend side, input is handled like this:
TextField("Enter search", text: $viewModel.query)
where viewModel is a variable that represents struct having a function that allows the screens to change based on the query.
Query itself is a Published var in that struct (Content View Model: Observable Object)
#Published var query = ""
If you need any more information, please let me know in comments. I can copy whole passages as I said but I don't know if it wouldn't complicate understanding the case further ;P
All right, so #jnpdx and #LeoDabus were right and the easiest way in my case was to just transform the String.
I am posting the passage, that I made the transformation in (it has a pointing comment in the line):
import Foundation
import Combine
(...)
private func createRequest(with query: String) -> AnyPublisher<[Item], Error> {
let fixedQuery = query.replacingOccurrences(of: " ", with: "+") //the transformation command
guard let url = URL(string: "https://api.github.com/search/repositories?q=\(fixedQuery)&per_page=20"), !query.isEmpty else {
return Just<[Item]>([])
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
I also add an URL to the more elaborative answer I have found:
Any way to replace characters on Swift String?

Swift 3: NSURL to URL missing init?

I just used XCode 8 and let it convert my existing project. Now I face the error that there is no init function for new URL without parameters.
class fileObj : NSObject, NSCopying, Comparable {
var URL = NSURL() // initial
...
The new code looks like:
class fileObj : NSObject, NSCopying, Comparable {
var myUrl = Foundation.URL() // initial
...
How should I init the new URL var?
it makes absolutely zero sense to do so, but currently (Swift3, Xcode Version 8.0 (8A218a)) it is working and gives you a totally blank URL object with no purpose at all, as you just asked for.
var myURL: URL = NSURLComponents().url!
Your observation is indeed correct, there is no empty initialiser because this would be an invalid URL, therefore they decided to disallow that.
What I recommend you doing is not initialising the variable at first and making it an optional (URL?). Later in the code, you'll be able to initialise it.

Swift string interpolation at runtime

I'm looking to implement some simple string templating in Swift and am wondering what the built-in options are for replacing macros in a string with values at runtime.
It would be awesome if there were a way for me to use the string interpolation syntax "\(variable)" at runtime, but I'm guessing these are actually parsed at compile time since the macros can contain actual code.
I've also found a String constructor that accepts a format string in Objective-C style, using %#, etc.
let myString = String(format: "Hello %#", name)
That could work, though I like the syntax less. I'm just wondering if there are better approaches I should take, or if it would be better to just write my own.
Being able to execute commands (like operators or method/property calls) within the macro would be awesome, but not required (and considering how static a language Swift is, not expected).
import Foundation
let serverName = "Fantastic"
var resultString = "We are using $SERVERNAME$"
if let range = resultString.rangeOfString("$SERVERNAME$") {
resultString.replaceRange(range, with: serverName)
}
You could, of course, implement a String extension if you are going to be using this regularly.
extension String {
func replaceString(sourceString:String, withNewElements newElements:String) throws {
//implementation left for the reader
let userInfo = ["missingString": sourceString,
"message": "Substring '\(sourceString)' not found in '\(self)'"]
throw NSError(domain: "ReplaceStringFailure", code: 1, userInfo: userInfo)
}
}

Mocking a String-class method in Swift

I've got some Swift 2.0 code, for which I'm trying to achieve 100% code coverage. I'm doing some JSON handling, part of which looks like so:
guard let jsonData = jsonText.dataUsingEncoding(NSUTF8StringEncoding) else {
throw ErrorCode.JSONEncodingFailure
}
I don't think there's a real-world case in which any string can't be encoded as UTF-8, but I don't want to open myself up to a crashing error either, so that code must remain. How can I mock the jsonText object to return nil for dataUsingEncoding()?
The closest I've come is to subclass NSString like so:
public class BadString: NSString {
public override var length: Int {
get {
return 5
}
}
public override func characterAtIndex(index: Int) -> unichar {
return 0
}
public override func dataUsingEncoding(encoding: NSStringEncoding) -> NSData? {
return nil
}
}
Here's the problem, though. In order for my mock implementation to be used, I have to define jsonText (a function parameter) as an NSString, rather than String, which feels wrong for an all-Swift codebase. With it defined as a Swift String, I have to cast my BadString to that type, and it uses the String implementation instead of my own.
Is there another (clean) way to achieve this?
You will be hard-pressed to find a string that cannot be encoded using UTF-8! As long as you know that is the encoding you will be using, I would suggest that you not worry about testing the "encoding failure" case.
However, if you still desire to test it then I recommend making one of the following changes in order to allow you to do so:
(1) change the way you are thinking about the 'failure': if you know that the string you are encoding will always be non-empty, then broaden the guard to also require that the encoded data has length > 0, e.g. =>
guard let jsonData = jsonText.dataUsingEncoding(NSUTF8StringEncoding)
where jsonData.length > 0 else {
throw ErrorCode.JSONEncodingFailure
}
...using this idea, you can now use an empty string for jsonText and trigger this code path (assuming that an empty string would also satisfy your definition of 'failure' here)
(2) store your string encoding value in a variable (let's call it stringEncoding) that you can access during your test setup, and then test this using incompatible values for jsonText and stringEncoding, e.g. =>
var jsonText = "🙈"
let stringEncoding = NSASCIIStringEncoding
...I guarantee that jsonText.dataUsingEncoding(stringEncoding) will return nil in this case :)
Happy Testing! I hope this helps!

Realm Swift Results get object at index

I couldn't find this anywhere so I thought I'd ask here.
I'm using Realm in Swift and I'm having trouble to get an object out of the Results at a certain index. I'm using it inside my UITableViewController. I'm making a var at the beginning of the class:
var tasks:Results<Task>?
And then to get it I .objects(type: T.Type):
tasks = realm.objects(Task)
I was hoping to be able to do something like this:
let task = tasks!.objectAtIndex(1)
Is this a limitation or is there another way to do this?
Use standard indexing syntax to retrieve the value:
let task = tasks![1]
Since tasks is an optional, it could be nil. A safer way to write this would be to use optional binding with optional chaining:
if let task = tasks?[1] {
// use task
}