drewag / decree Goto Github PK
View Code? Open in Web Editor NEWFramework for making Declarative HTTP Requests
License: MIT License
Framework for making Declarative HTTP Requests
License: MIT License
I already explained the context of my API elsewhere but I'll do a gist here for completion sake.
I have 3 endpoints named login, verify and profile. I don't need to pass any additional custom headers for the first two. For the third one (then all subsequent requests from there on out), I have to set a custom header. So I've set the authorizationRequirement
property like this for all 3.
struct Login: InOutEndpoint {
typealias Service = MyAPI
static let authorizationRequirement = AuthorizationRequirement.none
// ...
}
struct Verify: InOutEndpoint {
typealias Service = MyAPI
static let authorizationRequirement = AuthorizationRequirement.none
let path: String = "/login/DRI/verify/"
// ..
}
struct GetProfile: OutEndpoint {
typealias Service = MyAPI
static let authorizationRequirement = AuthorizationRequirement.required
}
In the WebService
implementation, I've set the authorization
property like this.
struct MyAPI: WebService {
static var shared = MyAPI()
let baseURL = URL(string: "http://example.com/api")!
var authorization = Authorization.custom(key: "token", value: SessionManager.shared.token)
// ..
}
SessionManager
is a class where I store the token.
I ran into a bit of a snag when I set the custom header. Let me elaborate. After calling Login()
endpoint, I call Verify()
. This is where I get the value for the custom header. I save the token value in the SessionManager
.
Verify().makeRequest(with: .init(phoneno: phoneNumber, code: otp)) { result in
switch result {
case .success(let output):
SessionManager.shared.token = output.data.token
case .failure(let error):
print("Failed to verify: \(error)")
}
}
I then call Profile()
endpoint and noticed that the request fails for not having a value for the custom header. I assume that MyAPI
(WebService implementation
) is only initialized once so the newly assigned value to SessionManager.shared.token
above isn't getting applied to the custom header when I call the next request.
So I assigned the token to the custom header directly like so.
Verify().makeRequest(with: .init(phoneno: phoneNumber, code: otp)) { result in
switch result {
case .success(let output):
MyAPI.shared.authorization = Authorization.custom(key: "token", value: output.data.token)
case .failure(let error):
print("Failed to verify: \(error)")
}
}
This fixes the issue. However it just looks....off. Because I'm already setting var authorization = Authorization.custom(key: "token", value: SessionManager.shared.token)
in the web service implementation with no effect so I have to set it from "outside".
Is this the correct way of doing this? I remember you mentioning about considering a way to add headers directly to the endpoints themselves. Which I also think is a better approach since you have more control over it. Did you decide against it or is it still on the table?
Swift 5.5โs new concurrency system is designed to be a drop-in replacement for callback APIs, and improves upon them considerably. As such, you should consider adopting it as soon as possible.
One of the benefits of the new concurrency system is the ability to overload a method with synchronous and asynchronous implementations. This means you can use the same name for both, which is considered best practice going forward.
The ideas was initially discussed here.
Even outside of auth, certain endpoints may want to include custom headers.
The big question is whether to think of those headers as additional input or something else? Should they simply be an optional endpoint property of type [(String,String?)]
or should we somehow use Coding? Should headers be one of the input formats? What if we want a combination of headers, url query, and body content?
From the API I'm working with, I get error messages in a format like this.
{
"status": "fail",
"code": 400,
"data": null,
"message": [
"phoneno: Enter a valid phone no. This value should be 11 digits folowed by + sign."
]
}
I want to display the strings that come inside the message
array to the user when an error occurs. So I intended to implement my own ErrorResponse
.
The problem is it already has a variable called message
. And it is of type String
. I can't turn it into a [String]
because message
is a required property.
So I put together a workaround like this.
struct APIErrorResponse: AnyErrorResponse, Decodable {
let message: String
let messages: [String]?
enum CodingKeys: String, CodingKey {
case message = "m"
case messages = "message"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
message = ""
messages = try container.decodeIfPresent([String].self, forKey: .messages)
print(messages)
}
}
However I'm not getting this response in the .fail
case where I call the endpoint. I put a breakpoint inside the nit(from decoder: Decoder)
and the JSON is parsed and the property is assigned the value too but it's not coming to topside. I traced the execution back to the func parse<Output: Decodable, E: Endpoint>(from data: Data?, for endpoint: E) throws -> Output
method but I'm unable to figure out what goes wrong from there.
Right now, all inputs and outputs must be loaded into memory in their entirety. It should be possible to stream the contents of a file as input and stream the output to disk to be more memory efficient.
This will involve enhancing the File type to include a disk option and then special casing those in WebService+MakeRequest.
A small suggestion. Maybe you could consider extracting the third-party services out to separate containers? To keep the main library from bloating. Like Eureka did with their custom rows catalog.
Because currently, if you were to add the source files manually, you get compile errors in those third-party files. So users who don't need them have to remove those files and bits and pieces of code from here and there to get it working.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.