{"id":17029352,"url":"https://github.com/liang2kl/apikit","last_synced_at":"2025-08-01T20:12:33.348Z","repository":{"id":103453129,"uuid":"400521569","full_name":"liang2kl/APIKit","owner":"liang2kl","description":"Simplify HTTP request declaration and processing with pre-defined property wrappers and data parsers (playground project).","archived":false,"fork":false,"pushed_at":"2021-08-29T13:56:26.000Z","size":65,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-22T20:17:10.414Z","etag":null,"topics":["http","ios","macos","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/liang2kl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-27T13:40:38.000Z","updated_at":"2022-01-05T06:09:13.000Z","dependencies_parsed_at":"2023-07-14T10:15:54.384Z","dependency_job_id":null,"html_url":"https://github.com/liang2kl/APIKit","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/liang2kl/APIKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liang2kl%2FAPIKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liang2kl%2FAPIKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liang2kl%2FAPIKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liang2kl%2FAPIKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/liang2kl","download_url":"https://codeload.github.com/liang2kl/APIKit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liang2kl%2FAPIKit/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268290752,"owners_count":24226646,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-01T02:00:08.611Z","response_time":67,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","ios","macos","swift"],"created_at":"2024-10-14T08:00:04.658Z","updated_at":"2025-08-01T20:12:33.321Z","avatar_url":"https://github.com/liang2kl.png","language":"Swift","readme":"# APIKit\n\nSimplify HTTP request declaration and processing with pre-defined property wrappers and data parsers.\n\n```swift\nstruct SetPushRequest: JSONDecodableRequest, RequestConfiguration {\n    @Header(\"TOKEN\") var accessToken = nil\n    @Field(\"push_system_msg\") var pushSystemMessage: Bool? = nil\n    @Field(\"push_reply_me\") var pushReplyMe: Bool? = nil\n    @Field(\"push_favorited\") var pushFavourite: Bool? = nil\n    \n    struct Response: Codable {\n        var code: Int\n        var msg: String?\n    }\n\n    let base: URL = URL(string: \"https://dev-api.thuhole.com\")!\n    let path: String = \"v3/config/set_push\"\n    let method: HTTPMethod = .post\n    \n    var configuration: Self { return self }\n}\n\nlet request = SetPushRequest(accessToken: token, pushSystemMessage: true, pushReplyMe: true, pushFavourite: false)\nrequest.perform { result in\n    switch result {\n    case .success(let response):\n        print(response)\n\n    case .failure(let error):\n        print(error)\n    }\n}\n```\n\n## Usage\n\n### Define Your Requests Conforming to `Request`\n\nProtocol `Request` is an abstraction of an HTTP request:\n\n```swift\npublic protocol Request {\n    associatedtype Configuration: RequestConfiguration\n    associatedtype Response\n    associatedtype DataParser: Parser where DataParser.Object == Response\n    \n    var base: URL { get }\n    var path: String { get }\n    var method: HTTPMethod { get }\n    \n    var configuration: Configuration { get }\n    var parser: DataParser { get }\n}\n```\n\nTo define a request, you need to provide:\n\n- API information:\n    - `var base: URL` Base URL of the API\n    - `var path: String` URL path\n    - `var method: HTTPMethod` HTTP method\n- Configuration: request parameters, which will be discussed later:\n    - `associatedtype Configuration`\n    - `var configuration: Configuration`\n- Response type and parser to parse data from the request, which will be discussed later:\n    - `associatedtype Response` The final data that the request returns\n    - `associatedtype DataParser: Parser` DataParser type that conforming to `Parser`\n    - `var parser: DataParser` The parser to parse the received data\n\nAdditionally and optionally, you can customize the intermediate process.\n\n```swift\nfunc intercept(urlRequest: URLRequest) throws -\u003e URLRequest\nfunc intercept(urlResponse: URLResponse) throws\nfunc handleParameters(_ parameters: [ParameterProtocol], for request: URLRequest) throws -\u003e URLRequest\nfunc handleHeaders(_ headers: [HeaderProtocol], for request: URLRequest) throws -\u003e URLRequest\n```\n\nIf `Response` type can be parsed from JSON data, you can instead conforming to `JSONDecodableRequest`, which has default implementation of `var parser`.\n\n### Provide Request Parameters\n\nAPIKit provides simple ways to pass request parameters.\n\nFirst, define your Configuration type that conforming to `RequestConfiguration`:\n\n```swift\nstruct MyConfiguration: RequestConfiguration {\n    \n}\n```\n\n`RequestConfiguration` is nothing than a type constraint. It has no implementation requirements, and is only used to distinguish from other types.\n\nThen, you can add your parameters into `MyConfiguration` as its member. You can add these types of parameters:\n\n| Property Wrapper | Description | Wrapped Value | Encoding | Destination |\n| --- | --- | --- | --- | --- |\n| `@Header` | HTTP header | `String?` | / | / |\n| `@HeaderDict` | An dictionary of headers | `[String : String]?` | / | / |\n| `@Query` | Query parameters | `Encodable?` | URL encoding | Query string |\n| `@QueryDict` | An dictionary of query parameters | `[String : String]?` | URL encoding | Query string |\n| `@KeyQuery` | Query parameters with no value associated | `Bool` | URL encoding | Query string |\n| `@JSON` | Body parameters with JSON encoding | `Encodable?` | JSON encoding | HTTP body |\n| `@Field` | Form URL encoded parameters | `Encodable?` | URL encoding | HTTP body |\n| `@Param` | General parameters | `Encodable?` | URL encoding | Method dependent |\n\nNoted that some wrapped values are `Optional`. When the wrapped value is `nil`, the parameter will not be encoded.\n\n#### Header\n\nTo add a header into the request, define a property inside your configuration, providing header field string:\n\n```swift\n@Header(\"TOKEN\") var token = nil\n```\n\nThe type of the wrapped value `token` is `String?`. As for the property wrapper constraint, you must provide an initial value (and that applys to all following parameter types). When the value is `nil`, the header field will not be added.\n\n#### Query Parameters\n\nTo add a query parameter with `Encodable` value and the query key:\n\n```swift\n@Query(\"key\") var value: Int? = nil\n```\n\nWhen the value is `nil`, this parameter will not present.\n\nTo add a query without value:\n\n```swift\n@KeyQuery(\"key\") var value: Bool = false\n```\n\nIf the value is `true`, then `key` will be encoded into the URL (something like `...?key\u0026other_key=...`). If it's `false`, the key will not present.\n\nTo add a dictionary of query parameters (`[String : String]`):\n\n```swift\n@QueryDict var parameters = [\n    \"first_key\" : \"first_value\",\n    \"second_key\" : \"second_value\",\n    ...\n]\n```\n\nThis is useful for providing a set of constant parameters.\n\n#### Body Parameters\n\nThe declaration is similar to `@Query`.\n\nTo add JSON encoded data into HTTP body:\n\n```swift\n@JSON(\"key\") value: Int? = nil\n```\n\nTo add Form URL Encoded parameters into HTTP body:\n\n```swift\n@Field(\"key\") value: Int? = nil\n```\n\n### Provide a Parser\n\nA parser is an object that `Request` uses to transform recieved data into a desired objet:\n\n```swift\npublic protocol Parser {\n    associatedtype Object\n    var contentType: String? { get }\n    func parse(data: Data) throws -\u003e Object\n}\n```\n\n`Object` is the type of the output object, `contentType` is the header value for field `Accept`,\nand `parse()` is the transformer of the received data.\n\nTypically you don't need to create a `Parser` of your own. You can use the pre-defined `JSONParser`,\n`FormURLEncodedDataParser` and `StringParser` to parse the data.\n\n### Perform the Request\n\nAfter defining your request like this:\n\n```swift\nstruct SetPushRequest: JSONDecodableRequest, RequestConfiguration {\n    @Header(\"TOKEN\") var accessToken = nil\n    @Field(\"push_system_msg\") var pushSystemMessage: Bool? = nil\n    @Field(\"push_reply_me\") var pushReplyMe: Bool? = nil\n    @Field(\"push_favorited\") var pushFavourite: Bool? = nil\n    \n    struct Response: Codable {\n        var code: Int\n        var msg: String?\n    }\n\n    let base: URL = URL(string: \"https://dev-api.thuhole.com\")!\n    let path: String = \"v3/config/set_push\"\n    let method: HTTPMethod = .post\n    \n    var configuration: Self { return self }\n}\n```\n\nAll you have to do is calling `perform()` of the request:\n\n```swift\nlet request = SetPushRequest(accessToken: token, pushSystemMessage: true, pushReplyMe: true, pushFavourite: false)\nrequest.perform { result in\n    switch result {\n    case .success(let response):\n        // response is `SetPushRequest.Response` type\n        ...\n    case .failure(let error):\n        // error is `RequestError` type\n        ...\n    }\n}\n```\n\n## How It Works\n\nAPIKit uses `Mirror` to inspect the properties of an `RequestConfiguration` instance and search for properties\nthat comforming to certain protocols.\n\nFor example, `@Header`, `@HeaderDict` confrom to `HeaderProtocol`, and `@Query`, `@Field`, etc. conform to `ParameterProtocol`.\nWhen we discover properties that conform to these protocols, we can access the information (keys, values, etc.) they provide\nand use it to construct the request.\n\nSo, it's 100% OK to define a custom property wrapper that conforms to one of `HeaderProtocol` and `ParameterProtocol`,\nand put properties that are wrapped by the wrapper into `RequestConfiguration` types, to customize their behaviours.\n\n## Credits\n\nMany of the ideas are inspired by [ishkawa/APIKit](https://github.com/ishkawa/APIKit).\n\nParameter encoding uses wonderful features of [Alamofire](https://github.com/Alamofire/Alamofire).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliang2kl%2Fapikit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fliang2kl%2Fapikit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliang2kl%2Fapikit/lists"}