Malibu is a networking library built on promises

Overview

Malibu logo

CI Status Version Carthage Compatible License Platform Swift

Description

Palm trees, coral reefs and breaking waves. Welcome to the surf club Malibu, a networking library built on promises. It's more than just a wrapper around URLSession, but a powerful framework that helps to chain your requests, validations and request processing.

Using When under the hood, Malibu adds a lot of sugar helpers and moves your code up to the next level:

  • No more "callback hell".
  • Your requests are described in one place.
  • Response processing could be easily broken down into multiple logical tasks.
  • Data and errors are handled separately.
  • Your networking code is much cleaner, readable and follows DRY principle.

Equip yourself with the necessary gears of Malibu, become a big wave surfer and let the days of shark infested asynchronous networking be a thing of the past. Enjoy the ride!

Features

  • Multiple network stacks
  • Declarative requests
  • Chainable response callbacks built on promises
  • All needed content types and parameter encodings
  • HTTP response validation
  • Response data serialization
  • Response mocking
  • Request, response and error logging
  • Synchronous and asynchronous modes
  • Request pre-processing and middleware
  • Request offline storage
  • Extensive unit test coverage

Table of Contents

Catching the wave

You can start your ride straight away, not thinking about configurations:

[Board] in // Let's say we use https://github.com/zenangst/Tailor for mapping return try dictionary.relationsOrThrow("boards") as [Board] }) .done({ boards in // Handle response data }) .fail({ error in // Handle errors }) .always({ _ in // Hide progress bar }) ">
// Create your request => GET http://sharkywaters.com/api/boards?type=1
let request = Request.get("http://sharkywaters.com/api/boards", parameters: ["type": 1])

// Make a call
Malibu.request(request)
  .validate()
  .toJsonDictionary()
  .then({ dictionary -> [Board] in
    // Let's say we use https://github.com/zenangst/Tailor for mapping
    return try dictionary.relationsOrThrow("boards") as [Board]
  })
  .done({ boards in
    // Handle response data
  })
  .fail({ error in
    // Handle errors
  })
  .always({ _ in
    // Hide progress bar
  })

If you still don't see any benefits, keep scrolling down and be ready for even more magic 😉 ...

RequestConvertible

Most of the time we need separate network stacks to work with multiple API services. It's super easy to archive with Malibu. Create an enum that conforms to RequestConvertible protocol and describe your requests with all the properties:

enum SharkywatersEndpoint: RequestConvertible {
  // Describe requests
  case fetchBoards
  case showBoard(id: Int)
  case createBoard(type: Int, title: String)
  case updateBoard(id: Int, type: Int, title: String)
  case deleteBoard(id: Int)

  // Every request will be scoped by the base url
  // Base url is recommended, but optional
  static var baseUrl: URLStringConvertible? = "http://sharkywaters.com/api/"

  // Additional headers for every request
  static var headers: [String: String] = [
    "Accept" : "application/json"
  ]

  // Build requests
  var request: Request {
    switch self {
    case .fetchBoards:
      return Request.get("boards")
    case .showBoard(let id):
      return Request.get("boards/\(id)")
    case .createBoard(let type, let title):
      return Request.post("boards", parameters: ["type": type, "title": title])
    case .updateBoard(let id, let title):
      return Request.patch("boards/\(id)", parameters: ["title": title])
    case .deleteBoard(let id):
      return Request.delete("boards/\(id)")
    }
  }
}

Note that Accept-Language, Accept-Encoding and User-Agent headers are included automatically.

Request

Request is described with a struct in Malibu:

let request = Request(
  // HTTP method
  method: .get,
  // Request url or path
  resource: "boards",
  // Content type
  contentType: .query,
  // Request parameters
  parameters: ["type": 1, "text": "classic"],
  // Headers
  headers: ["custom": "header"],
  // Offline storage configuration
  storePolicy: .unspecified,
  // Cache policy
  cachePolicy: .useProtocolCachePolicy)

There are also multiple helper methods with default values for every HTTP method:

// GET request
let getRequest = Request.get("boards")

// POST request
let postRequest = Request.post(
  "boards",
  // Content type is set to `.json` by default for POST
  contentType: .formURLEncoded,
  parameters: ["type" : kind, "title" : title])

// PUT request
let putRequest = Request.put("boards/1", parameters: ["type" : kind, "title" : title])

// PATCH request
let patchRequest = Request.patch("boards/1", parameters: ["title" : title])

// DELETE request
let deleteRequest = Request.delete("boards/1")

URLSessionDataTask is default for executing requests. For uploading there are two additional options that use URLSessionUploadTask instead of URLSessionDataTask.

// Upload data to url
Request.upload(data: data, to: "boards")

// Upload multipart data with parameters
// You are responsible for constructing a proper value,
// which is normally a string created from data.
Request.upload(
  multipartParameters: ["key": "value"],
  to: "http:/api.loc/posts"
)

Content types

  • query - creates a query string to be appended to any existing url.
  • formURLEncoded - uses application/x-www-form-urlencoded as a Content-Type and formats your parameters with percent-encoding.
  • json - sets the Content-Type to application/json and sends a JSON representation of the parameters as the body of the request.
  • multipartFormData - sends parameters encoded as multipart/form-data.
  • custom(String) - uses given Content-Type string as a header.

Encoding

Malibu comes with 3 parameter encoding implementations:

  • FormURLEncoder - a percent-escaped encoding following RFC 3986.
  • JsonEncoder - JSONSerialization based encoding.
  • MultipartFormEncoder - multipart data builder.

You can extend default functionality by adding a custom parameter encoder that conforms to ParameterEncoding protocol:

// Override default JSON encoder
Malibu.parameterEncoders[.json] = CustomJsonEncoder()

// Register encoder for the custom encoding type
Malibu.parameterEncoders[.custom("application/xml")] = CustomXMLEncoder()

Cache policy

URLSession handles cache based on the URLRequest.CachePolicy property:

let getRequest = Request.get("boards". cachePolicy: .useProtocolCachePolicy)

URLRequest.CachePolicy.useProtocolCachePolicy is the default policy for URL load requests. URLSession will automatically add the If-None-Match header in the request before sending it to the backend. When URLSession gets the 304 Not Modified response status it will call the URLSessionDataTask completion block with the 200 status code and data loaded from the cached response.

You can set cachePolicy property to .reloadIgnoringLocalCacheData if you want to prevent this automatic cache management. Then URLSession will not add the If-None-Match header to the client requests, and the server will always return a full response.

Networking

Networking class is a core component of Malibu that executes actual HTTP requests on a specified API service.

Initialization

It's pretty straightforward to create a new Networking instance:

// Simple networking that works with `SharkywatersEndpoint` requests.
let simpleNetworking = Networking<SharkywatersEndpoint>()

// More advanced networking
let networking = Networking<SharkywatersEndpoint>(
  // `OperationQueue` Mode
  mode: .async,
  // Optional mock provider
  mockProvider: customMockProvider,
  // `default`, `ephemeral`, `background` or `custom`
  sessionConfiguration: .default,
  // Custom `URLSessionDelegate` could set if needed
  sessionDelegate: self
)

Mode

Malibu uses OperationQueue to execute/cancel requests. It makes it easier to manage request lifetime and concurrency.

When you create a new networking instance there is an optional argument to specify mode which will be used:

  • sync
  • async
  • limited(maxConcurrentOperationCount)

Mocks

Mocking is great when it comes to writing your tests. But it also could speed up your development while the backend developers are working really hardly on API implementation.

In order to start mocking you have to do the following:

Create a mock provider

// Delay is optional, 0.0 by default.
let mockProvider = MockProvider(delay: 1.0) { endpoint in
  switch endpoint {
    case .fetchBoards:
      // With response data from a file:
      return Mock(fileName: "boards.json")
    case .showBoard(let id):
      // With response from JSON dictionary:
      return Mock(json: ["id": 1, "title": "Balsa Fish"])
    case .updateBoard(let id, let title):
      // `Data` mock:
      return Mock(
        // Needed response
        response: mockedResponse,
        // Response data
        data: responseData,
        // Custom error, `nil` by default
        error: customError
      )
    default:
      return nil
  }
}

Create a networking instance with your mock provider

Both real and fake requests can be used in a mix:

let networking = Networking<SharkywatersEndpoint>(mockProvider: mockProvider)

Session configuration

SessionConfiguration is a wrapper around URLSessionConfiguration and could represent 3 standard session types + 1 custom type:

  • default - configuration that uses the global singleton credential, cache and cookie storage objects.
  • ephemeral - configuration with no persistent disk storage for cookies, cache or credentials.
  • background - session configuration that can be used to perform networking operations on behalf of a suspended application, within certain constraints.
  • custom(URLSessionConfiguration) - if you're not satisfied with standard types, your custom URLSessionConfiguration goes here.

Pre-processing

// Use this closure to modify your `Request` value before `URLRequest`
// is created on base of it
networking.beforeEach = { request in
  return request.adding(
    parameters: ["userId": "12345"],
    headers: ["token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"]
  )
}

// Use this closure to modify generated `URLRequest` object
// before the request is made
networking.preProcessRequest = { (request: URLRequest) in
  var request = request
  request.addValue("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", forHTTPHeaderField: "token")
  return request
}

Middleware

Middleware is the function which works as the first promise in the chain, before the actual request. It could be used to prepare networking, do some kind of pre-processing task, cancel request under particular conditions, etc.

For example, in the combination with https://github.com/hyperoslo/OhMyAuth

// Set middleware in your configuration
// Remember to `resolve` or `reject` the promise
networking.middleware = { promise in
  AuthContainer.serviceNamed("service")?.accessToken { accessToken, error in
    if let error == error {
      promise.reject(error)
      return
    }

    guard let accessToken = accessToken else {
      promise.reject(CustomError())
      return
    }

    self.networking.authenticate(bearerToken: accessToken)
    promise.resolve()
  }
}

// Send your request like you usually do.
// Valid access token will be set to headers before the each request.
networking.request(request)
  .validate()
  .toJsonDictionary()

Authentication

// HTTP basic authentication with username and password
networking.authenticate(username: "malibu", password: "surfingparadise")

// OAuth 2.0 authentication with Bearer token
networking.authenticate(bearerToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")

// Custom authorization header
networking.authenticate(authorizationHeader: "Malibu-Header")

Making a request

Networking is set up and ready, so it's time to fire some requests.

let networking = Networking<SharkywatersEndpoint>()

networking.request(.fetchBoards)
  .validate()
  .toJsonDictionary()
  .done({ data in
    print(data)
  })

networking.request(.createBoard(kind: 2, title: "Balsa Fish"))
  .validate()
  .toJsonDictionary()
  .done({ data in
    print(data)
  })

networking.request(.deleteBoard(id: 11))
  .fail({ error in
    print(error)
  })

Response and NetworkPromise

Response object consists of Data, URLRequest and HTTPURLResponse properties.

NetworkPromise is just a typealias to Promise, which is returned by every request method. You may use NetworkPromise object to add different callbacks and build chains of tasks. It has a range of useful helpers, such as validations and serialization.

let networkPromise = networking.request(.fetchBoards)

// Cancel the task
networkPromise.cancel()

// Create chains and add callbacks on promise object
networkPromise
  .validate()
  .toString()
  .then({ string in
    // ...
  })
  .done({ _ in
    // ...
  })

Offline storage

Want to store request when there is no network connection?

let request = Request.delete(
  "boards/1",
  storePolicy: .offline // Set store policy
)

Want to replay cached requests?

networking.replay().done({ result
  print(result)
})

Request storage is networking-specific, and while it replays cached requests it will be set to Sync mode. Cached request will go through normal request lifecycle, with applied middleware and pre-process operations. Request will be automatically removed from the storage when it's completed.

Backfoot surfer

Malibu has a shared networking object with default configurations for the case when you need just something simple to catch the wave. It's not necessary to create a custom RequestConvertible type, just call the same request method right on Malibu:

Malibu.request(Request.get("http://sharkywaters.com/api/boards")

Response

Serialization

Malibu gives you a bunch of methods to serialize response data:

let networkPromise = networking.request(.fetchBoards)

networkPromise.toData() // -> Promise
networkPromise.toString() // -> Promise
networkPromise.toJsonArray() // -> Promise<[[String: Any]]>
networkPromise.toJsonDictionary() // -> Promise<[String: Any]>

Validation

Malibu comes with 4 validation methods:

// Validates a status code to be within 200..<300
// Validates a response content type based on a request's "Accept" header
networking.request(.fetchBoards).validate()

// Validates a response content type
networking.request(.fetchBoards).validate(
  contentTypes: ["application/json; charset=utf-8"]
)

// Validates a status code
networking.request(.fetchBoards).validate(statusCodes: [200])

// Validates with custom validator conforming to `Validating` protocol
networking.request(.fetchBoards).validate(using: CustomValidator())

Decoding

Malibu is able to convert the response body into models that conform to Decodable:

// Declare your model conforming to `Decodable` protocol
struct User: Decodable {
  let name: String
  let dob: Date
}

// Set up a `JSONDecoder`
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601

// Decode your response body
networkPromise.decode(using: User.self, decoder: decoder)

Logging

If you want to see some request, response and error info in the console, you get this for free. Just choose one of the available log levels:

  • none - logging is disabled, so your console is not littered with networking stuff.
  • error - prints only errors that occur during the request execution.
  • info - prints incoming request method + url, response status code and errors.
  • verbose - prints incoming request headers and parameters in addition to everything printed in the info level.

Optionally you can set your own loggers and adjust the logging to your needs:

// Custom logger that conforms to `ErrorLogging` protocol
Malibu.logger.errorLogger = CustomErrorLogger.self

// Custom logger that conforms to `RequestLogging` protocol
Malibu.logger.requestLogger = RequestLogger.self

// Custom logger that conforms to `ResponseLogging` protocol
Malibu.logger.responseLogger = ResponseLogger.self

Author

Hyper Interaktiv AS, [email protected]

Installation

Malibu is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Malibu'

Malibu is also available through Carthage. To install just write into your Cartfile:

github "vadymmarkov/Malibu"

Malibu can also be installed manually. Just Download and drop /Sources folder in your project.

Author

Vadym Markov, [email protected]

Credits

This library was originally done at Hyper, a digital communications agency with a passion for good code and delightful user experiences.

Credits go to Alamofire for inspiration and to When for promises.

Contributing

Check the CONTRIBUTING file for more info.

License

Malibu is available under the MIT license. See the LICENSE file for more info.

Comments
  • Update Dependencies

    Update Dependencies

    Morning All, Tried to install the library through cocoapods yesterday and the library won't compile. Seems there are references to updates in the When library which aren't there. Installing through cocoa-odds installs When version 2.1.1 when the changes are at least in the latest version 2.3.3. I updated the pod spec locally and installed When 2.3.3 which fixed the references, but the RequestConvertible isn't running the promise. Using the default promise works, i.e. "let promise = Malibu.request(Request.get("https://api.myjson.com/bins/1bgj27"))", but using the RequestConvertible method doesn't. Didn't want to troubleshoot much without you having a chance to update the dependencies. Hope this makes sense. Let me know if I'm just doing something stupid. Bruce

    opened by bmctigue 12
  • ETag still present even after calling Malibu.clearStorage()

    ETag still present even after calling Malibu.clearStorage()

    I noticed that my requests are being dispatched with the header If-None-Match: "Some ETag hash" even after calling Malibu.clearStorage()

    I created a sample project demonstrating it: https://github.com/guilhermearaujo/malibu-etag Its README contains more details.

    The weird thing is that at this line: https://github.com/hyperoslo/Malibu/blob/26657c5e46339749aa6cc178ed2a4111fe6841ce/Sources/Networking.swift#L200-L212 before the return, I added the code:

    print(urlRequest.allHTTPHeaderFields!)
    

    And the If-None-Match header is not present, but somehow it ends being sent anyway.

    Looks like a bug to me. Tested on versions 6.2.0 and 6.4.1

    opened by guilhermearaujo 11
  • Can't get the response body if validation fails

    Can't get the response body if validation fails

    If the validation fails (let's say the server returns 400> status code) there is no way to get the data that the server returns.

    Would be very helpful if there is a way to get the data so you can display server sided exceptions.

    opened by zwolsman 6
  • Feature: endpoints

    Feature: endpoints

    This is a super breaking change, so the next version should be Malibu 3.

    We used structs to describe requests for a while and it worked, but it wasn't the most elegant solution. I see libraries like https://github.com/Moya/Moya and https://github.com/kickstarter/ios-ksapi/blob/master/KsApi/lib/Route.swift use enum for this and I think it looks really nice. Based on this idea I've made the following API changes:

    1. Endpoint

    Additional headers, base url and request description are moved to Endpoint. There are no Requestable protocols anymore, they are replaced by Request struct with helper get, post, patch, put, delete and head methods:

    enum SharkywatersService: Endpoint {
      // Describe requests
      case fetchBoards
      case showBoard(id: Int)
      case createBoard(type: Int, title: String)
      case updateBoard(id: Int, type: Int, title: String)
      case deleteBoard(id: Int)
    
      // Every request will be scoped by the base url
      static var baseUrl: URLStringConvertible = "http://sharkywaters.com/api/"
    
      // Additional headers for every request
      static var headers: [String: String] = [
        "Accept" : "application/json"
      ]
    
      // Build requests
      var request: Request {
        switch self {
        case .fetchBoards:
          return Request.get("boards")
        case .showBoard(let id):
          // Let's use JSON dictionary as a mock data
          return Request.get("boards:\(id)", mock: Mock(json: ["type": 1, "title": "Classic"]))
        case .createBoard(let type, let title):
          return Request.post("boards", parameters: ["type": type, "title": title])
        case .updateBoard(let id, let title):
          return Request.patch("boards\(id)", parameters: ["title": title])
        case .deleteBoard(let id):
          return Request.delete("boards\(id)")
        }
      }
    }
    
    let networking = Networking<SharkywatersService>()
    networking.request(.fetchBoards)
    

    2. Networking is endpoint-specific:

    let networking = Networking<SharkywatersService>(
      // `OperationQueue` Mode
      mode: .async,
      // Mock behavior (never, delayed)
      mockBehavior: .delayed(0.5),
      // `default`, `ephemeral`, `background` or `custom`
      sessionConfiguration: .default,
      // Custom `URLSessionDelegate` could set if needed
      sessionDelegate: self
    )
    

    3. There is no networking container anymore, it's up to developer to create and keep Networking instances.

    4. Mocks are created directly on a request:

    let request = Request.get(
      "boards",
      mock: Mock(fileName: "boards.json")
    )
    

    I think it simplifies the way requests are described and used, plus it's gonna remove a lot of boilerplate code from our projects.

    opened by vadymmarkov 6
  • Reject promise in middleware

    Reject promise in middleware

    I'm doing authentication in middleware and when authentication fails I want to reject the promise with a custom error object. Where do I catch this error object? I have added validate(), done(), fail() and always handlers but none of them seem to be called. Have I misunderstood how reject works?

    opened by wideving 5
  • Increased number of errors after updating to 6.3.0+

    Increased number of errors after updating to 6.3.0+

    Recently I released two updates of my app with no updates (concerning networking or the requests the app makes, or how and when they are made), but I did update Malibu from 6.2.0 to 6.3.0, and 6.4.0 in the second update.

    Since 6.3.0, several customers have been complaining that some contents cannot be load in the app, and send screenshots displaying the error messages I programmed to be displayed when a network request fails for any reason other than 2 cases: Reachability says the device is offline; or the request returns with a 401 status code.

    In my tests, I could never reproduce an error, and the worst case I could recreate was to enable the network conditioner with a very poor condition to force a request to time out.

    .fail { error in
      switch error {
      case is UnreachableError: Alert.warn(R.string.alert.unreachable()) // Reachability
      case is Unauthorized: AppNotification.loggedOut.post() // 401
      default:
        Alert.warn("some 'could not load' message")
        if !(error is ApiError) { // ApiError is base for UnreachableError, Unauthorized and some other erros like 404, 500, 503, etc.
          Crashlytics.sharedInstance().recordError(error)
        }
      }
    }
    

    So I started logging the errors to Crashlytics, but the only data I get from them is:

    NSERROR-DOMAIN: Malibu.NetworkError NSERROR-CODE: 8

    Is there any important change since 6.2.0? What case of NetworkError is this one whose code is 8?? How can I debug it better and get more data to understand what's really happening?

    This is how the UnreachableError is thrown:

    networking.middleware = { promise in
      if let connection = Reachability()?.connection, connection != .none {
        promise.resolve(Void())
      } else {
        promise.reject(UnreachableError())
      }
    }
    
    opened by guilhermearaujo 5
  • Feature: operations

    Feature: operations

    @zenangst @RamonGilabert @onmyway133 This feature introduces operations instead of tasks in Malibu meaning that NSOperationQueue is used to execute/cancel requests. That makes it easier to manage request lifetime and concurrency.

    1. When you create a new networking instance there is an optional argument to specify operationKind which could be Sync, Async or Limited:
    let networking = Networking(operationKind: .Sync) // requests will be executed one after another
    
    1. Small addition to Mock, now it's possible to specify a delay in the init.
    opened by vadymmarkov 5
  • Improve/logging

    Improve/logging

    @zenangst @RamonGilabert @onmyway133

    New level names

    public enum LogLevel {
      case None, Error, Info, Verbose
    }
    

    Request logging

    • Method + URL when the level is Info or Verbose;
    • Headers and parameters when the level is Verbose;

    Response logging

    • Status code
    opened by vadymmarkov 5
  • MockProvider does not call promise's callback

    MockProvider does not call promise's callback

    The following snippet shows a reproducible case in which neither success nor failure on the promise's part is called.

    This seems to be in part due to the Networking instance being deallocated (the weak self check) returns since it is nil.

    How can I work around this?

    final class MockProviderSpec: QuickSpec {
        override func spec() {
            describe("Test") {
                it("Passes") {
                    let fetcher = APIFetcher()
                    waitUntil { done in
                        fetcher.request(request: CustomEndpoint.test).then({ _ in
                            done()
                        }).fail({ error in
                            done()
                        })
                    }
                }
            }
        }
    }
    
    class APIFetcher {
        var mockClosure: ((RequestConvertible) -> Mock?)?
    
        func defaultClosure(_ request: RequestConvertible) -> Mock?{
            return Mock(json: [:])
        }
    
        func request(request: CustomEndpoint) -> NetworkPromise {
    
            let networking = Networking<CustomEndpoint>(mode: .sync, 
                                                        mockProvider: MockProvider<CustomEndpoint>(defaultClosure)
            )
            return networking.request(request)
        }
    }
    
    enum CustomEndpoint: RequestConvertible {
        static var baseUrl: URLStringConvertible?
    
        static var headers: [String : String] = [:]
    
        var request: Request {
            return Request.post("mock://derp")
        }
    
        case test
    }
    opened by daneov 4
  • Remove validation of 204 responses in .validate()

    Remove validation of 204 responses in .validate()

    Right now, the default .validate() fails when receiving a HTTP 204 NO CONTENT response trough Express, as Express strips away those fields when not needed.

    If the maintainers agree, I'd like to remove 204 from the standard .validate() method in ResponseValidation.swift.

    Edit: A more detailed explanation of why it doesn't work: If you set application/json as Accept and receive a 204, Express strips the Content-Type field making NSURLRequest do its best to guess the Content-Type itself, and as Express does chunk = '' on a 204, it assumes its text/plain. That obviously fails content type validation.

    opened by OscarApeland 4
  • Make Header public

    Make Header public

    • We have accept language, so I think that should be in the default headers, too 😄
    • Make Header public, as when we have our own login networking, we can reuse these default headers
    opened by onmyway133 4
  • cancelAllRequests not working?

    cancelAllRequests not working?

    Super simpel test.

    import Malibu
    
    enum Endpoint: RequestConvertible {
      case fetchUsers
    
      static let baseUrl: URLStringConvertible? = "https://jsonplaceholder.typicode.com/"
    
      static var headers: [String : String] {
        return ["Accept" : "application/json"]
      }
    
      var request: Request {
        switch self {
        case .fetchUsers:
          return Request.get("users")
        }
      }
    }
    
    class ViewController: UIViewController {
      let networking = Networking<Endpoint>()
    
      override func viewDidLoad() {
        super.viewDidLoad()
    
        let promise = networking.request(.fetchUsers)
    
        promise.validate().done({ data in
          print("Success")
        }).fail({ error in
          print(error)
        })
    
        promise.cancel()
      }
    }
    

    With the method above, where I cancel a single network promise, the request is not made and neither the success or an error is printed. Great!

    However, when I change that last line to networking.cancelAllRequests(), I do get "Success" printed out, and I can confirm that the request is indeed made.

    opened by kevinrenskers 3
  • Idea/suggestion: improve and extend Codable support

    Idea/suggestion: improve and extend Codable support

    I've been playing around with the CodyFire network library recently and while it has some flaws (and doesn't use promises), it uses Codable throughout in nifty ways. For example, it uses strongly typed structs for both request parameters and the response, like this:

    class API {
      func register(_ request: RegisterData) -> APIRequest<LoginResponse> {
        return APIRequest("user/register/", payload: request).method(.post).desiredStatusCode(.created)
      }
    
      func login(_ request: LoginRequest) -> APIRequest<LoginResponse> {
        return APIRequest("user/login/", payload: request).method(.post)
      }
    
      func logout() -> APIRequest<Nothing> {
        return APIRequest("user/logout/").method(.post).desiredStatusCode(.noContent)
      }
    }
    
    struct RegisterData: JSONPayload {
      let username: String
      let name: String
      let password: String
      let email: String
      let gender: String
    }
    
    struct LoginRequest: JSONPayload {
      let username: String
      let password: String
    }
    
    struct LoginResponse: Codable {
      var token: String
    }
    

    As you can see, the parameters are strongly typed, this works for query params, form params, multi-part upload, etc etc. And the response is also automatically decoded. I know that you can do that in Malibu 8.1.0 as well, but there you have to do it when you make the request, instead of declaring it upfront on the Request itself.

    Another thing that would be great is to have Decodable support for errors as well, something that CodyFire also lacks sadly.

    opened by kevinrenskers 1
  • Make `preProcessRequest` return a promise (or give middleware access to the Request)

    Make `preProcessRequest` return a promise (or give middleware access to the Request)

    Hi Vadym!

    We have an Auth SDK at work that exposes a simple function:

    public func addAuthentication(to request: Request, completion: (TypedResult<Request, TokenStorageError>) -> Void) 
    

    (since we don't want clients to have access to the access tokens directly)

    Since this API works asynchronously, we can't add this to preProcessRequest (which is synchronous) or to middleware (since that doesn't have access to the request). Is there a "right" way to do this?

    opened by codeOfRobin 1
  • Testing multi-request flows and MockProvider

    Testing multi-request flows and MockProvider

    Hi!

    I'm trying to test a flow where access tokens are refreshed(similar to #42 ), and I'm using a MockProvider instead of making actual network requests (imagine making a MockProvider where if a request is made with the old token, it'll return a "failed" json and if the request is made with a new token, you get the "success" json). However, since the request in the MockProvider closure doesn't contain any information about the middleware, authentication etc, I can't check what token the request is made with.

    Any suggestions on how I can write a test for this? 🤔

    opened by codeOfRobin 2
  • Can't run playground

    Can't run playground

    Playground complains: "No such module 'Malibu'". After I've updated Carthage dependencies I'm getting:

    error: Couldn't lookup symbols: _T06Malibu10NetworkingCACyxGAA0B4ModeO4mode_AA12MockProviderCyxGSg04mockF0AA20SessionConfigurationO07sessionI0So18URLSessionDelegate_pSg0jL0tcfcfA _T06Malibu10NetworkingCACyxGAA0B4ModeO4mode_AA12MockProviderCyxGSg04mockF0AA20SessionConfigurationO07sessionI0So18URLSessionDelegate_pSg0jL0tcfcfA0 __T06Malibu7RequestV3getAcA20URLStringConvertible_p_s10DictionaryVySSypG10parametersAGyS2SG7headersAA10EtagPolicyO04etagJ0AA05StoreJ0O05storeJ0So12NSURLRequestC05CacheJ0O05cacheJ0tFZ __T04When7PromiseC6MalibuAD8ResponseCRbzlE8validateACyAFGyF .....

    opened by dev4jam 0
Releases(8.1.0)
  • 8.1.0(Apr 5, 2019)

  • 7.0.3(Nov 9, 2018)

  • 7.0.2(Aug 29, 2018)

    • Update references https://github.com/vadymmarkov/Malibu/pull/108
    • Use Travis instead of CircleCI https://github.com/vadymmarkov/Malibu/pull/110
    Source code(tar.gz)
    Source code(zip)
  • 7.0.1(Aug 28, 2018)

  • 7.0.0(Aug 10, 2018)

    • Remove ETag policy in favour of URLRequest.CachePolicy https://github.com/hyperoslo/Malibu/pull/105
    • Rename Malibu.clearStorage() to Malibu.clearOfflineRequestStorage()
    Source code(tar.gz)
    Source code(zip)
  • 6.4.1(Jul 30, 2018)

  • 6.2.0(Feb 24, 2018)

  • 6.1.0(Dec 19, 2017)

  • 6.0.1(Nov 2, 2017)

  • 6.0.0(Sep 25, 2017)

  • 5.2.1(Aug 21, 2017)

  • 5.2.0(Jun 26, 2017)

  • 5.1.0(Jun 26, 2017)

  • 5.0.0(Jun 9, 2017)

    • Wave was renamed to Response
    • Ride was the last custom term that we remove in favour of NetworkPromise, which is a typealias to Promise<Response>
    • Now it's possible to suspend and resume networking: https://github.com/hyperoslo/Malibu/pull/68/files
    • Add upload requests support: https://github.com/hyperoslo/Malibu/pull/72
    Source code(tar.gz)
    Source code(zip)
  • 4.1.1(May 31, 2017)

  • 4.1.0(Mar 23, 2017)

    1. Optional baseUrl in RequestConvertible protocol. https://github.com/hyperoslo/Malibu/pull/62
    2. Fix authentication challenge https://github.com/hyperoslo/Malibu/pull/63
    Source code(tar.gz)
    Source code(zip)
  • 4.0.0(Feb 23, 2017)

    ⚠️ Breaking change

    • Endpoint protocol was renamed to RequestConvertible in order to make it more descriptive.
    • Mock property is removed from Request. When the mock is created in one single place it is not really flexible for unit testing if you want to inject JSON dictionary or custom file dynamically. That's why MockProvider was introduced in order to improve mock behaviour.

    See https://github.com/hyperoslo/Malibu/pull/60

    Source code(tar.gz)
    Source code(zip)
  • 3.0.0(Jan 30, 2017)

    ⚠️ Breaking change

    • Introduce endpoints
    • Use Request struct instead of Requestable protocols
    • Refactor networking
    • Delete networking container

    More detailed description can be found here https://github.com/hyperoslo/Malibu/pull/59

    Source code(tar.gz)
    Source code(zip)
  • 2.0.1(Nov 22, 2016)

  • 2.0.0(Oct 3, 2016)

  • 1.3.0(Sep 30, 2016)

  • 1.2.0(Sep 28, 2016)

  • 1.1.0(Sep 26, 2016)

  • 1.0.2(Aug 8, 2016)

  • 1.0.1(Aug 8, 2016)

    1. Update OS info https://github.com/hyperoslo/Malibu/pull/43/files
    2. Refactor multipart form encoding https://github.com/hyperoslo/Malibu/pull/44
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Jun 15, 2016)

    • Multiple networkings
    • Declarative requests
    • Chainable response callbacks built on promises
    • All needed content types and parameter encodings
    • HTTP response validation
    • Response data serialization
    • Response mocking
    • Request, response and error logging
    • ETag support
    • Extensive unit test coverage
    Source code(tar.gz)
    Source code(zip)
Owner
Vadym Markov
iOS Software Engineer
Vadym Markov
🤵🏽‍♀️ Janet — A thin HTTP networking layer built on URLSession for simple, declarative endpoint specification leveraging the power of async/await.

????‍♀️ Janet — Just another networking kit — A thin HTTP networking layer built on URLSession for simple, declarative endpoint specification leveragi

Niklas Holloh 3 Sep 6, 2022
Elegantly connect to a JSON api. (Alamofire + Promises + JSON Parsing)

⚠ Important Notice: Farewell ws... hello Networking ! Networking is the next generation of the ws project. Think of it as ws 2.0 built for iOS13. It u

Fresh 351 Oct 2, 2022
🏇 A Swift HTTP / HTTPS networking library just incidentally execute on machines

Thus, programs must be written for people to read, and only incidentally for machines to execute. Harold Abelson, "Structure and Interpretation of Com

John Lui 845 Oct 30, 2022
RSNetworking is a networking library written entirly for the Swift programming language.

RSNetworking is a networking library written entirly for the Swift programming language.

null 18 Feb 25, 2018
Asynchronous socket networking library for Mac and iOS

CocoaAsyncSocket CocoaAsyncSocket provides easy-to-use and powerful asynchronous socket libraries for macOS, iOS, and tvOS. The classes are described

Robbie Hanson 12.3k Jan 8, 2023
ServiceData is an HTTP networking library written in Swift which can download different types of data.

ServiceData Package Description : ServiceData is an HTTP networking library written in Swift which can download different types of data. Features List

Mubarak Alseif 0 Nov 11, 2021
foursquare iOS networking library

FSNetworking foursquare's iOS networking library FSN is a small library for HTTP networking on iOS. It comprises a single class, FSNConnection, and se

Foursquare 386 Sep 1, 2022
Swish is a networking library that is particularly meant for requesting and decoding JSON via Decodable

Swish Nothing but net(working). Swish is a networking library that is particularly meant for requesting and decoding JSON via Decodable. It is protoco

thoughtbot, inc. 369 Nov 14, 2022
A networking library for Swift

Nikka Nikka is a super simple Swift HTTP networking library that comes with many extensions to make it modular and really powerful. Installation Usage

Emilien Stremsdoerfer 29 Nov 4, 2022
QwikHttp is a robust, yet lightweight and simple to use HTTP networking library for iOS, tvOS and watchOS

QwikHttp is a robust, yet lightweight and simple to use HTTP networking library. It allows you to customize every aspect of your http requests within a single line of code, using a Builder style syntax to keep your code super clean.

Logan Sease 2 Mar 20, 2022
A networking library for iOS, macOS, watchOS and tvOS

Thunder Request Thunder Request is a Framework used to simplify making http and https web requests. Installation Setting up your app to use ThunderBas

3 SIDED CUBE 16 Nov 19, 2022
AsyncHTTP - Generic networking library written using Swift async/await

Generic networking library written using Swift async/await

Laszlo Teveli 7 Aug 3, 2022
A resource based, protocol oriented networking library designed for pure-SwiftUI applications.

Monarch ?? - WIP A resource based, protocol oriented networking library designed for pure-SwiftUI applications. Features: Async/Await Resource Based P

Emilio Pelaez Romero 4 Oct 17, 2022
Elegant HTTP Networking in Swift

Alamofire is an HTTP networking library written in Swift. Features Component Libraries Requirements Migration Guides Communication Installation Usage

Alamofire 38.7k Jan 8, 2023
Type-safe networking abstraction layer that associates request type with response type.

APIKit APIKit is a type-safe networking abstraction layer that associates request type with response type. // SearchRepositoriesRequest conforms to Re

Yosuke Ishikawa 1.9k Dec 30, 2022
Robust Swift networking for web APIs

Conduit Conduit is a session-based Swift HTTP networking and auth library. Within each session, requests are sent through a serial pipeline before bei

Mindbody 52 Oct 26, 2022
Lightweight Concurrent Networking Framework

Dots Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements iOS 8.0+ / macOS 10.10+ /

Amr Salman 37 Nov 19, 2022
Versatile HTTP Networking in Swift

Net is a versatile HTTP networking library written in Swift. ?? Features URL / JSON / Property List Parameter Encoding Upload File / Data / Stream / M

Intelygenz 124 Dec 6, 2022
A type-safe, high-level networking solution for Swift apps

What Type-safe network calls made easy Netswift offers an easy way to perform network calls in a structured and type-safe way. Why Networking in Swift

Dorian Grolaux 23 Apr 27, 2022