{"id":32150829,"url":"https://github.com/neothxt/snowdrop","last_synced_at":"2025-12-11T22:53:28.047Z","repository":{"id":222042360,"uuid":"755725222","full_name":"neothXT/Snowdrop","owner":"neothXT","description":"Networking made easy","archived":false,"fork":false,"pushed_at":"2025-02-02T18:39:06.000Z","size":146,"stargazers_count":54,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-02T19:32:31.974Z","etag":null,"topics":["asyncawait","certificate-pinning","networking","snowdrop","ssl-pinning","swift","swift-concurrency","swift-macros","type-safe","xcode"],"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/neothXT.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":"2024-02-10T22:25:42.000Z","updated_at":"2025-02-02T18:39:09.000Z","dependencies_parsed_at":"2024-02-19T15:29:39.030Z","dependency_job_id":"1cd7b291-cd1d-41f4-b512-8588c2286c16","html_url":"https://github.com/neothXT/Snowdrop","commit_stats":null,"previous_names":["neothxt/netty","neothxt/snowdrop"],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/neothXT/Snowdrop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neothXT%2FSnowdrop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neothXT%2FSnowdrop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neothXT%2FSnowdrop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neothXT%2FSnowdrop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neothXT","download_url":"https://codeload.github.com/neothXT/Snowdrop/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neothXT%2FSnowdrop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280242957,"owners_count":26297015,"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-10-21T02:00:06.614Z","response_time":58,"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":["asyncawait","certificate-pinning","networking","snowdrop","ssl-pinning","swift","swift-concurrency","swift-macros","type-safe","xcode"],"created_at":"2025-10-21T10:24:40.834Z","updated_at":"2025-10-21T10:24:45.003Z","avatar_url":"https://github.com/neothXT.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![alt [version]](https://img.shields.io/github/v/release/neothXT/Snowdrop) ![alt spm available](https://img.shields.io/badge/SPM-available-green)\n\n![alt text](https://github.com/neothXT/Snowdrop/blob/main/Snowdrop_Logo.png)\n\n# Snowdrop\n\nMeet **Snowdrop** - type-safe, easy to use framework powered by Swift Macros created to let you build and maintain complex network requests with ease.\n\n## Navigation\n\n- [Installation](#installation)\n- [Key Functionalities](#key-functionalities)\n- [Basic Usage](#basic-usage)\n    - [Service Declaration](#service-declaration)\n    - [Request Execution](#request-execution)\n- [Advanced Usage](#advanced-usage)\n    - [Default JSON Decoder](#default-json-decoder)\n    - [SSL/Certificate Pinning](#sslcertificate-pinning)\n    - [Body Argument](#body-argument)\n    - [File Upload using form-data](#file-upload-using-form-data)\n    - [Query Parameters](#query-parameters)\n    - [Arguments' Default Values](#arguments-default-values)\n    - [Interceptors](#interceptors)\n    - [Mockable](#mockable)\n    - [JSON Injection](#json-injection)\n    - [Verbose](#verbose)\n- [Acknowledgements](#acknowledgements)\n\n## Installation\n\nSnowdrop is available via SPM. It works with iOS Deployment Target 14.0 or later and macOS Deployment Target 11 or later.\n\n## Key Functionalities\n\n- Type-safe service creation with `@Service` macro\n- Support for various request method types such as\n    - `@GET`\n    - `@POST`\n    - `@PUT`\n    - `@DELETE`\n    - `@PATCH`\n    - `@CONNECT`\n    - `@HEAD`\n    - `@OPTIONS`\n    - `@QUERY`\n    - `@TRACE`\n- SSL/Certificate pinning\n- Interceptors\n- Mockable\n\n## Basic Usage\n\n### Service Declaration\n\nCreating network services with Snowdrop is really easy. Just declare a protocol along with its functions. \n\n```Swift\n@Service\nprotocol MyEndpoint {\n\n    @GET(url: \"/posts\")\n    @Headers([\"X-DeviceID\": \"testSim001\"])\n    func getAllPosts() async throws -\u003e [Post]\n}\n```\n\nIf your request includes some dynamic values, such as `id`, you can add it to your path wrapping it with `{}`. Snowdrop will automatically bind your function declaration's arguments with those you include in request's path.\n\n```Swift\n@GET(url: \"/posts/{id}\")\nfunc getPost(id: Int) async throws -\u003e Post\n```\n\n### Request Execution\n\nUpon expanding macros, Snowdrop creates a class `MyEndpointService` which implements `MyEndpoint` protocol and generates all the functions you declared.\n\n```Swift\nclass MyEndpointService: MyEndpoint {\n    func getAllPosts() async throws -\u003e [Post] {\n        // auto-generated body\n    }\n    \n    func getPost(id: Int) async throws -\u003e Post {\n        // auto-generated body\n    }\n}\n```\n\n**Please note that if your service protocol already have \"Service\" keyword like `MyEndpointService`, macro will then generate the class named `MyEndpointServiceImpl` instead.**\n\nTo send requests, just initialize `MyEndpointService` instance and call function corresponding to the request you want to execute.\n\n```Swift\nlet service = MyEndpointService(baseUrl: URL(string: \"https://my-endpoint.com\")!)\nlet post = try await service.getPost(id: 7)\n```\n\n## Advanced Usage\n\n### Default JSON Decoder\n\nIf you need to change default json decoder, you can set your own decoder when creating an instance of your service.\n\n```Swift\nlet decoder = CustomJSONDecoder()\nlet service = MyEndpointService(baseUrl: URL(string: \"https://my-endpoint.com\")!, decoder: decoder)\n```\n\n#### SSL/Certificate Pinning\n\nSnowdrop offers SSL/Certificate pinning functionality when executing network requests. You can turn it on/off when creating an instance of your service. You can also determine urls that should be excluded from pinning.\n\n```Swift\nlet service = MyEndpointService(baseUrl: URL(string: \"https://my-endpoint.com\")!, pinningMode: .ssl, urlsExcludedFromPinning: [\"https://my-endpoint.com/about\"])\n```\n\n### Body Argument\n\nIf you want to put some encodable object as a body of your request, you can either put it in your declaration as \"body\" argument or - if you want to use another name - use `@Body` macro like:\n\n```Swift\n@POST(url: \"/posts\")\n@Body(\"model\")\nfunc addPost(model: Post) async throws -\u003e Data\n```\n\n### File Upload using form-data\n\nIf you want to declare service's function that sends some file to the server as `multipart/form-data`, use `@FileUpload` macro. It'll automatically add `Content-Type: multipart/form-data` to the request's headers and extend the list of your function's arguments with `_payloadDescription: PayloadDescription` which you should then use to provide information such as `name`, `fileName` and `mimeType`.\nFor mime types such as jpeg, png, gif, tiff, pdf, vnd, plain, octetStream, you don't have to provide `PayloadDescription`. Snowdrop can automatically recognize them and create `PayloadDescription` for you.\n\n```Swift\n@Service\nprotocol MyEndpoint {\n\n    @FileUpload\n    @Body(\"image\")\n    @POST(url: \"/uploadAvatar/\")\n    func uploadImage(_ image: UIImage) async throws -\u003e Data\n}\n\nlet payload = PayloadDescription(name: \"avatar\", fileName: \"filename.jpeg\", mimeType: \"image/jpeg\")\nlet service = MyEndpointService(baseUrl: URL(string: \"https://my-endpoint.com\")!)\n_ = try await service.uploadImage(someImage, _payloadDescription: payload)\n```\n\n### Query Parameters\n\nWith Snowdrop, you can pass your query params in two ways.\n\nFirst one is to use `@QueryParams` macro. To inform which arguments of your func are supposed to be query params, put them in array like this:\n\n```Swift\n@Service\nprotocol MyEndpoint {\n    @GET(url: \"/posts/{id}\")\n    @QueryParams([\"author\"])\n    func getPost(id: Int, author: String) async throws -\u003e Post\n}\n\nlet authorName = \"John Smith\"\nlet service = MyEndpointService(baseUrl: URL(string: \"https://my-endpoint.com\")!)\nlet post = try await service.getPost(id: 7, author: authorName)\n```\n\nAlternatively, upon expanding macros, Snowdrop adds argument `_queryItems: [QueryItem]` to every service's function. Use it like this:\n\n```Swift\n@Service\nprotocol MyEndpoint {\n    @GET(url: \"/posts/{id}\")\n    func getPost(id: Int) async throws -\u003e Post\n}\n\nlet authorName = \"John Smith\"\nlet service = MyEndpointService(baseUrl: URL(string: \"https://my-endpoint.com\")!)\nlet post = try await service.getPost(id: 7, _queryItems: [.init(key: \"author\", value: authorName)])\n```\n\n### Arguments' Default Values\n\nSnowdrop allows you to define custom values for your arguments. Let's say your path includes `{id}` argument. As you already know by now, Snowdrop automatically associates it with `id` argument of your `func` declaration. If you want it to have default value equal \"3\", do it like: `{id=3}`. Be careful though as Snowdrop won't check if your default value's type conforms to the declaration.  \nWhen inserting `String` default values such as {name=\"Some name\"}, it is strongly recommended to use `Raw String` like `@GET(url: #\"/authors/{name=\"John Smith\"}\"#)`.\n\n### Interceptors\n\nEach service provides two methods to add interception blocks - `addBeforeSendingBlock` and `addOnResponseBlock`. Both accept arguments such as `path` of type `String` and `block` which is closure.\n\nTo add `addBeforeSendingBlock` or `addOnResponseBlock` for a requests matching certain path, use regular expressions like:\n\n```Swift\nservice.addBeforeSendingBlock(for: \"my/path/[0-9]{1,}/content\") { urlRequest in\n    // some operations\n    return urlRequest\n}\n```\n\nTo add `addBeforeSendingBlock` or `addOnResponseBlock` for ALL requests, do it like:\n\n```Swift\nservice.addOnResponseBlock { data, httpUrlResponse in\n    // some operations\n    return data\n}\n```\n\n**Note that if you add interception block for a certain request path, general interceptors will be ignored.**\n\n### Mockable\n\nIf you'd like to create mockable version of your service, Snowdrop got you covered. Just add `@Mockable` macro to your service declaration like\n\n```Swift\n@Service\n@Mockable\nprotocol Endpoint {\n    @Get(\"/path\")\n    func getPosts() async throws -\u003e [Posts]\n}\n```\n\nSnowdrop will automatically create a `EndpointServiceMock` class with all the properties `Service` should have and additional properties such as `getPostsResult` to which you can assign value that should be returned.\n\n#### Sample usage:\n\n```Swift\nfunc testEmptyArrayResult() async throws {\n    let mock = EndpointServiceMock(baseUrl: URL(string: \"https://some.url\")!\n    mock.getPostsResult = .success([])\n\n    let result = try await mock.getPosts()\n    XCTAssertTrue(result.isEmpty)\n}\n```\n\n**Note that mocked methods will directly return stubbed result without accessing Snowdrop.Core so your beforeSend and onResponse blocks won't be called.**\n\n### JSON Injection\n\nIf you'd like to test your service against mocked JSONs, you can easily do it. Just make sure you got your JSON mock somewhere in your project files, then instantiate your service and determine for which request your mock should be injected like in the example below.\n\n```Swift\nfunc testJSONMockInjectsion() async throws {\n    let service = MyEndpointService(baseUrl: someBaseURL)\n    service.testJSONDictionary = [\"users/123/info\": \"MyJSONMock\"]\n    \n    let result = try await service.getUserInfo(id: 123)\n    XCTAssertTrue(result.firstName, \"JSON\")\n    XCTAssertTrue(result.lastName, \"Bourne\")\n}\n```\n\n### Verbose\n\nIf you'd like to get see logs from Snowdrop, use `verbose` flag when creating new instance of your service.\n\n```Swift\nlet service = MyEndpointService(baseUrl: URL(string: \"https://my-endpoint.com\")!, verbose: true)\n```\n\n## Acknowledgements\n\nRetrofit was an inspiration for Snowdrop.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneothxt%2Fsnowdrop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneothxt%2Fsnowdrop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneothxt%2Fsnowdrop/lists"}