Many API's require some form of parameterisation of the URI.
For example:
/users/{user}/repos
or a more complex and useful example (that would be harder to solve cleanly with simple interpolation)
/conversations/{conversationIdentifier}/messages{?max,after,before}
that could expand to something like
/conversations/547104ac-74b6-441a-bf9c-eda489498005/messages?max=30&after=1994-11-05T08:15:30-05:00
or
/conversations/547104ac-74b6-441a-bf9c-eda489498005/messages?max=30&before=1994-11-05T08:15:30-05:00
It would be really great if we could provide this as a part of or as an extension to Moya to make integration super easy.
For an RFC compliant implementation of URI's I'd recommend @kylef's URITemplate.
My main issue is that I'm not too sure of the best way to integrate this into Moya with minimal impact to the API.
Neat! Might go well with https://github.com/Moya/Moya/issues/73 if anyone is interested.
Agreed with your concerns – getting this to be an optional part of Moya will be tricky. Suggestions welcome!
Nice idea, @DanielTomlinson. I guess this could be part of the path within MoyaTarget? However, I can see this to be difficult to implement in a generic way.
I've added this for consideration to the v5 milestone, since now's the opportunity to break APIs (though not too much).
Is this something we might be able to take on for 8.0.0 still? Not sure how this would fit in the API as it stands right now. @Moya/contributors, anyone?
At first look, this seems doable with the associated values approach that we use--though might not be the most elegant way to handle it. Should we close this?
I think it'd be nice to see if we can improve here in the future.
Sounds good to me.
Looked at this more and took a look at @kylef's URITemplate.
I'm not sure I see the advantages to incorporating it here. This is what our current approach would look like:
var path: String {
switch self {
case .repo(let user, let repo):
return "/\(user)/\(repo)"
}
}
Using the approach from URITemplate, we could get something like this:
var path: String {
switch self {
case .repo(let user, let repo):
return uri(template: "/{user}/{repo}", values: ["user": user, "repo": repo])
}
}
This would introduce more expensive and fragile Regexes into constructing paths, has fewer compiler checks, and doesn't really look more readable to my eyes (granted Swift's string interpolation looks kinda ugly in paths).
Using reflection, I could get something like this:
var path: String {
switch self {
case .repo:
return uri(template: "/{user}/{repo}")
}
}
That does look nicer than the previous example, but it adds the additional drawbacks of requiring that the tags of the URI template match the order and number of associated values in the enum case, so it would be harder to associate any values not in the URI with the enum case. Also, you wouldn't actually be matching the tags--you could use "/{dog}/{cat}" and get the same result.
So at least in my opinion, our approach is superior because no regex and compiler checks. Maybe I am missing something though? 🤔
I agree, and think our approach is better, at least according to Moya's vision:
Some awesome features of Moya:
- Compile-time checking for correct API endpoint accesses.
Which I think the current approach seems to do. Closing this. If anyone disagrees or has an even better solution, please feel free to reopen.
I agree with @BasThomas, my intentions for using URITemplate in iOS applications are a little different than the goals of Moya. The compile-time checking in Moya is making upfront assumptions on how the API currently works and assumes that to always be the case in the future. If you're already hard-coding the URIs then I don't think it adds too much using URI Template in this case and just adds unnecessary complexity. Unless you are looking to use a common format to specify URLs which you can share against your backend or other clients.
The goals behind URI Template (and other libraries I created using URI Template) was to communicate with APIs using the REST architectural style similar to the GitHub API. This would allow machines to dynamically understanding how the API works instead of it having up-front hard-coded knowledge.
Allowing a machine to learn how to communicate with a foreign API providing it has shared semantic understanding of the given domain.
Instead an approach without hard-coding upfront knowledge of the API layout might look as follows:
let client = Client()
// Find the master commit for kylef/URITemplate.swift repository on GitHub
client.enter("https://api.github.com")
.flatMap { client.load($0.transition("repository", ["owner": "kylef", "repo": "URITemplate.swift"]) }
.flatMap { client.load($0.transition("branches", ["branch": "master"]) }
.map { $0.attributes["commit"] }
Where I've entered the API at it's root and then followed some of the transitions the API has offered. The client has an understanding of a repository and know a specific owner and repository I'm looking for.
{ "repository_url": "https://api.github.com/repos/{owner}/{repo}" }
NOTE: The client could just as easily look at all repositories for a user by not expanding the repo component of the URI.
Then following a transition called branches with a branch parameter.
{ "branches_url": "https://api.github.com/repos/kylef/URITemplate.swift/branches{/branch}" }
Obviously this example makes a lot of assumptions about the features in the API so it should instead check for the features and the types of variables that can be supplied to the transitions. For brevity and simplicity the example is as-is.
Thanks for weighing in, @kylef! I think it's a really great tool, just not for Moya. :)