Giter Club home page Giter Club logo

tron's Introduction

CI codecov.io CocoaPod platform CocoaPod version Swift Package Manager compatible Packagist

TRON is a lightweight network abstraction layer, built on top of Alamofire. It can be used to dramatically simplify interacting with RESTful JSON web-services.

Features

  • Generic, protocol-based implementation
  • Built-in response and error parsing
  • Support for any custom mapper (SwiftyJSON implementation provided). Defaults to Codable protocol.
  • Support for upload tasks
  • Support for download tasks and resuming downloads
  • Robust plugin system
  • Stubbing of network requests
  • Modular architecture
  • Support for iOS/Mac OS X/tvOS/watchOS/Linux
  • Support for CocoaPods/Swift Package Manager
  • RxSwift / Combine extensions
  • Support for Swift Concurrency
  • Complete documentation

Overview

We designed TRON to be simple to use and also very easy to customize. After initial setup, using TRON is very straightforward:

let request: APIRequest<User,APIError> = tron.codable.request("me")
request.perform(withSuccess: { user in
  print("Received User: \(user)")
}, failure: { error in
  print("User request failed, parsed error: \(error)")
})

Requirements

  • Xcode 13 and higher
  • Swift 5.3 and higher
  • iOS 11 / macOS 10.13 / tvOS 11.0 / watchOS 4.0

Installation

Swift Package Manager

  • Add package into Project settings -> Swift Packages

TRON framework includes Codable implementation. To use SwiftyJSON, import TRONSwiftyJSON framework. To use RxSwift wrapper, import RxTRON.

CocoaPods

pod 'TRON', '~> 5.3.0'

Only Core subspec, without SwiftyJSON dependency:

pod 'TRON/Core', '~> 5.3.0'

RxSwift extension for TRON:

pod 'TRON/RxSwift', '~> 5.3.0'

Migration Guides

Project status

TRON is under active development by MLSDev Inc. Pull requests are welcome!

Request building

TRON object serves as initial configurator for APIRequest, setting all base values and configuring to use with baseURL.

let tron = TRON(baseURL: "https://api.myapp.com/")

You need to keep strong reference to TRON object, because it holds Alamofire.Manager, that is running all requests.

URLBuildable

URLBuildable protocol is used to convert relative path to URL, that will be used by request.

public protocol URLBuildable {
    func url(forPath path: String) -> URL
}

By default, TRON uses URLBuilder class, that simply appends relative path to base URL, which is sufficient in most cases. You can customize url building process globally by changing urlBuilder property on TRON or locally, for a single request by modifying urlBuilder property on APIRequest.

Sending requests

To send APIRequest, call perform(withSuccess:failure:) method on APIRequest:

let alamofireRequest = request.perform(withSuccess: { result in }, failure: { error in})

Alternatively, you can use performCollectingTimeline(withCompletion:) method that contains Alamofire.Response inside completion closure:

request.performCollectingTimeline(withCompletion: { response in
    print(response.timeline)
    print(response.result)
})

In both cases, you can additionally chain Alamofire.Request methods, if you need:

request.perform(withSuccess: { result in }, failure: { error in }).progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
    print(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
}

Response parsing

Generic APIRequest implementation allows us to define expected response type before request is even sent. On top of Alamofire DataResponseSerializerProtocol, we are adding one additional protocol for error-handling.

public protocol DataResponseSerializerProtocol {
    associatedtype SerializedObject

    public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Self.SerializedObject
}

public protocol ErrorSerializable: Error {
    init?(serializedObject: Any?, request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?)
}

Codable

Parsing models using Swift4 Codable protocol is simple, implement Codable protocol:

struct User: Codable {
  let name : String
  let id: Int
}

And send a request:

let request: APIRequest<User,APIError> = tron.codable.request("me")
request.perform(withSuccess: { user in
  print("Received user: \(user.name) with id: \(user.id)")
})

It's possible to customize decoders for both model and error parsing:

let userDecoder = JSONDecoder()

let request : APIRequest<User,APIError> = tron.codable(modelDecoder: userDecoder).request("me")

JSONDecodable

TRON provides JSONDecodable protocol, that allows us to parse models using SwiftyJSON:

public protocol JSONDecodable {
    init(json: JSON) throws
}

To parse your response from the server using SwiftyJSON, all you need to do is to create JSONDecodable conforming type, for example:

class User: JSONDecodable {
  let name : String
  let id: Int

  required init(json: JSON) {
    name = json["name"].stringValue
    id = json["id"].intValue
  }
}

And send a request:

let request: APIRequest<User,MyAppError> = tron.swiftyJSON.request("me")
request.perform(withSuccess: { user in
  print("Received user: \(user.name) with id: \(user.id)")
})

There are also default implementations of JSONDecodable protocol for Swift built-in types like String, Int, Float, Double and Bool, so you can easily do something like this:

let request : APIRequest<String,APIError> = tron.swiftyJSON.request("status")
request.perform(withSuccess: { status in
    print("Server status: \(status)") //
})

You can also use Alamofire.Empty struct in cases where you don't care about actual response.

Some concepts for response serialization, including array response serializer, are described in Container Types Parsing document

It's possible to customize JSONSerialization.ReadingOptions, that are used by SwiftyJSON.JSON object while parsing data of the response:

let request : APIRequest<String, APIError> = tron.swiftyJSON(readingOptions: .allowFragments).request("status")

Swift Concurrency

Sending requests using Swift Concurrency is done via a proxy object RequestSender(or DownloadRequestSender for download requests). Simple usage example:

let request : APIRequest<User, APIError> = tron.codable.request("/me")
do {
 let user = try await request.sender().value
  // user variable contains User type
} catch {
  // Network request failed
}

If you prefer to receive result, containing either successful Model, or ErrorModel, you can do that too:

let request : APIRequest<User, APIError> = tron.codable.request("/me")
let result = await request.sender().result
// result is Result<User,APIError>

There is also response async property, containing all request information, if you need it:

let request : APIRequest<User, APIError> = tron.codable.request("/me")
let response = await request.sender().response
// response: AFDataResponse<Model>

Upload request

For upload requests, it's useful to monitor upload progress, and show it to the user:

let request : APIRequest<User, APIError> = tron.codable.request("/me/profile_picture")
  .upload("/post", fromFileAt: urlForResource("cat", withExtension: "jpg"))
  .method(.post)  

let sender = request.sender()
Task {
    for await progress in sender.uploadProgress {
      // Update progress view, progress: Progress
    }
}
let result = await sender.result

Download request

Similarly to upload requests, download requests have downloadProgress property implemented as async sequence:

Task {
    for await progress in sender.downloadProgress {
      // Update download view, progress: Progress
    }
}

If you only care about downloaded file URL, and not parsed data model, you can await responseURL property on request sender:

let destination = Alamofire.DownloadRequest.suggestedDownloadDestination()
let request: DownloadAPIRequest<URL, APIError> = tron
            .download("/download",
                      to: destination,
                      responseSerializer: FileURLPassthroughResponseSerializer())
do {
  let fileURL = try await request.sender().responseURL 
} catch {
  // Handle error
}

RxSwift

let request : APIRequest<Foo, APIError> = tron.codable.request("foo")
_ = request.rxResult().subscribe(onNext: { result in
    print(result)
})
let multipartRequest : UploadAPIRequest<Foo,APIError> = tron.codable.uploadMultipart("foo", formData: { _ in })
multipartRequest.rxResult().subscribe(onNext: { result in
    print(result)
})

Error handling

TRON includes built-in parsing for errors. APIError is an implementation of ErrorSerializable protocol, that includes several useful properties, that can be fetched from unsuccessful request:

request.perform(withSuccess: { response in }, failure: { error in
    print(error.request) // Original URLRequest
    print(error.response) // HTTPURLResponse
    print(error.data) // Data of response
    print(error.fileURL) // Downloaded file url, if this was a download request
    print(error.error) // Error from Foundation Loading system
    print(error.serializedObject) // Object that was serialized from network response
  })

CRUD

struct Users
{
    static let tron = TRON(baseURL: "https://api.myapp.com")

    static func create() -> APIRequest<User,APIError> {
      tron.codable.request("users").post()
    }

    static func read(id: Int) -> APIRequest<User, APIError> {
        tron.codable.request("users/\(id)")
    }

    static func update(id: Int, parameters: [String:Any]) -> APIRequest<User, APIError> {
      tron.codable.request("users/\(id)").put().parameters(parameters)
    }

    static func delete(id: Int) -> APIRequest<User,APIError> {
      tron.codable.request("users/\(id)").delete()
    }
}

Using these requests is really simple:

Users.read(56).perform(withSuccess: { user in
  print("received user id 56 with name: \(user.name)")
})

It can be also nice to introduce namespacing to your API:

enum API {}
extension API {
  enum Users {
    // ...
  }
}

This way you can call your API methods like so:

API.Users.delete(56).perform(withSuccess: { user in
  print("user \(user) deleted")
})

Stubbing

Stubbing is built right into APIRequest itself. All you need to stub a successful request is to set apiStub property and turn stubbingEnabled on:

API.Users.get(56)
         .stub(with: APIStub(data: User.fixture().asData))
         .perform(withSuccess: { stubbedUser in
           print("received stubbed User model: \(stubbedUser)")
})

Stubbing can be enabled globally on TRON object or locally for a single APIRequest. Stubbing unsuccessful requests is easy as well:

API.Users.get(56)
         .stub(with: APIStub(error: CustomError()))
         .perform(withSuccess: { _ in },
                  failure: { error in
  print("received stubbed api error")
})

You can also optionally delay stubbing time:

request.apiStub.stubDelay = 1.5

Upload

  • From file:
let request = tron.codable.upload("photo", fromFileAt: fileUrl)
  • Data:
let request = tron.codable.upload("photo", data: data)
  • Stream:
let request = tron.codable.upload("photo", fromStream: stream)
  • Multipart-form data:
let request: UploadAPIRequest<EmptyResponse,MyAppError> = tron.codable.uploadMultipart("form") { formData in
    formData.append(data, withName: "cat", mimeType: "image/jpeg")
}
request.perform(withSuccess: { result in
    print("form sent successfully")
})

Download

let responseSerializer = TRONDownloadResponseSerializer { _,_, url,_ in url }
let request: DownloadAPIRequest<URL?, APIError> = tron.download("file",
                                                                to: destination,
                                                                responseSerializer: responseSerializer)

Plugins

TRON includes plugin system, that allows reacting to most of request events.

Plugins can be used globally, on TRON instance itself, or locally, on concrete APIRequest. Keep in mind, that plugins that are added to TRON instance, will be called for each request. There are some really cool use-cases for global and local plugins.

By default, no plugins are used, however two plugins are implemented as a part of TRON framework.

NetworkActivityPlugin

NetworkActivityPlugin serves to monitor requests and control network activity indicator in iPhone status bar. This plugin assumes you have only one TRON instance in your application.

let tron = TRON(baseURL: "https://api.myapp.com", plugins: [NetworkActivityPlugin()])

NetworkLoggerPlugin

NetworkLoggerPlugin is used to log responses to console in readable format. By default, it prints only failed requests, skipping requests that were successful.

Local plugins

There are some very cool concepts for local plugins, some of them are described in dedicated PluginConcepts page.

Alternatives

We are dedicated to building best possible tool for interacting with RESTful web-services. However, we understand, that every tool has it's purpose, and therefore it's always useful to know, what other tools can be used to achieve the same goal.

TRON was heavily inspired by Moya framework and LevelUPSDK, which is no longer available in open-source.

License

TRON is released under the MIT license. See LICENSE for details.

About MLSDev

MLSDev.com

TRON is maintained by MLSDev, Inc. We specialize in providing all-in-one solution in mobile and web development. Our team follows Lean principles and works according to agile methodologies to deliver the best results reducing the budget for development and its timeline.

Find out more here and don't hesitate to contact us!

tron's People

Contributors

biow0lf avatar dentelezhkin avatar dependabot[bot] avatar github-actions[bot] avatar leonardowf avatar mgurreta avatar rengate avatar rojotek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tron's Issues

setting http headers isn't working

I am using Xcode 8.3.3, TRON, Alamofire, SwiftyJSON. I need to set my headers to ["Content-Type":"application/json"], I have tried both of the following:

request.headers = ["Content-Type":"application/json"]
and
let headers = request.headerBuilder.headers(forAuthorizationRequirement:
AuthorizationRequirement.required,
including: ["Content-Type":"application/json"])
request.headers = headers

neither of these options seems to make a difference, I still get a 400 or 405 server error and the logs show that the header is still: ["Accept":"application/json"], which I believe is the default according to the documentation.

Maybe I am misreading the docs and am trying to set the headers incorrectly. Can anyone help or have any suggestions.

Thanks in advanced,
Jonathan

How to use the plugins

Hi, How do I use the protocols from https://github.com/MLSDev/TRON#plugins

to catch responses, errors etc. Can you give a code example?

Currently I have something like this:

class APIHelper: Plugin {

static var tron = TRON(baseURL: UserDefaults.standard.value(forKey: Constants.kAPIUrlUserDefaultsKey) as! String)

func didReceiveDataResponse<Model, ErrorModel>(_ response: DataResponse<Model>, forRequest request: Request, formedFrom tronRequest: BaseRequest<Model, ErrorModel>) {
    print("response", response)
}

func didReceiveError<Model, ErrorModel>(_ error: APIError<ErrorModel>, forResponse response: (URLRequest?, HTTPURLResponse?, Data?, Error?), request: Request, formedFrom tronRequest: BaseRequest<Model, ErrorModel>) {
    print("error received", request)
} 
}

But I am not sure if that is correct since nothing is being outputted.

canceling requests

Hi, I have not found any way in TRON to cancel requests.

Is there any API for that?

Intercept requests and change response

Is it possible to intercept a request using Plugins and change the request before it continues? Or maybe intercept a request and send a pre-defined response to the user?

Is the documentation still up to date?

I got this part right of the documentation but it throws an error

Cannot convert return expression of type 'APIRequest<,> to return type APIRequest<Assessment, MyAppError>

Any idea what is wrong?

I am using

pod 'TRON', '> 2.0.0'
pod 'TRON/Core', '
> 2.0.0'
pod 'TRON/RxSwift', '~> 2.0.0'

import TRON

struct Assessments {

    static let tron = TRON(baseURL: "https://api.myapp.com")

    static func read(id: Int) -> APIRequest<Assesment, MyAppError> {
        return tron.request("assessment")
    }
}

Compatible error with xcode 8.2.1

When I tried to install Tron, it shows me this message in XCode :

“Use Legacy Swift Language Version” (SWIFT_VERSION) is required to be configured correctly for targets which use Swift. Use the [Edit > Convert > To Current Swift Syntax…] menu to choose a Swift version or use the Build Settings editor to configure the build setting directly.

As Mentioned I'm using XCode Version 8.2.1 & Cocoapods 'TRON', '~> 2.0.0'
The different is, version of Alamofire was (4.2.0)in that time TRON was working fine, but for current version Alamofire (4.4.0) showing me mentioned error

Multipart form data uploads not implemented and documentation is wrong.

Example code how to make multipart form data upload differs from reality.

I couldn't find MultipartAPIRequest type in framework, please update documentation.

There is empty implementation for handling multipart requests in func alamofireRequest(from manager: SessionManager) -> Request?

case .multipartFormData(_): return nil

Is it going to be implemented in near future?

upload multipart example

Hi,

I am trying to use the multipart upload to upload an image. However, it seems not to work to my expectation. Nothing gets uploaded while if I use PAW ( http client ) to make the request it works

This is the request I am using

typealias UploadValidationRequest = UploadAPIRequest<EmptyResponse, PAError>

  @discardableResult
    func saveEntryWithAttachment(id: String, entry: [Entry], attachment: UIImage) -> UploadValidationRequest {
        let url = URL(string: "app.cash/save_entry")!
        let imageData = UIImagePNGRepresentation(attachment)
        let request: UploadValidationRequest = APIHelper.tron.codable.uploadMultipart(url.absoluteString, formData: { (formData) in
                guard let imageData = imageData else { return }
                formData.contentType = "multipart/form-data; charset=utf-8; boundary=\(formData.boundary)"
                formData.append(imageData, withName: "attachment", mimeType: "image/png")
        })
        request.plugins = [PAAPIPlugin()]
        let data = try! JSONEncoder().encode(entry)
        request.method = .post
        request.parameters["entry_id"] = id
        request.parameters["values"] = String(data: data, encoding: .utf8)!
        return request
    }

the call itself

return coordinator.bookService
            .saveEntryWithAttachment(id: "\(transaction.id)", entry: entry, attachment: attachment)
            .rxMultipartResult()
            .trackActivity(activityIndicator)

However nothing gets uploaded. I debugged the call and everything seems fine there. Any idea what I am doing wrong here?

Getting response data from request call

Hello,

From the recommended way of doing requests,

let alamofireRequest = request.perform(withSuccess: { result in }, failure: { error in})

I can't seem to get the statusCode, header and meta data etc.. only the data. Is there a way to get these? I can't find it in the docs.

holding strong references with dynamic base url

Hi,

I have an app where every user could have different environments. For every environment there is a different base url.

Since tron requires to have a strong reference. I don't know how to change the url inside without having to completely close and restart the app.. Do you guys have thought about this use case?

var tron = TRON(baseURL: UserDefaults.standard.value(forKey: Constants.kAPIUrlUserDefaultsKey) as! String)

Version 4 questions

Hi, I have some questions that are not clear to me in the new beta version. I miss a codable error class example similar to the swiftyJSON version.

class MyAppError : JSONDecodable {
  var errors: [String:[String]] = [:]

  required init(json: JSON) {
    if let dictionary = json["errors"].dictionary {
      for (key,value) in dictionary {
          errors[key] = value.arrayValue.map( { return $0.stringValue } )
      }
    }
  }
}

I have currently got the following:

struct PAError: Decodable, Swift.Error {
    var errors: [String:[String]]
}

Is this the correct way?

Response serialization

Response serialization for TRON 2.0.0 has undergone significant changes, and is influencing experience with TRON.framework in a lot of ways. In this issue we wanted to highlight our goals and current ideas for response serialization.

Previously, ResponseParseable protocol looked like this:

public protocol ResponseParseable {
    init(data: NSData) throws
}

This approach allowed us to put ResponseParseable generic restriction on APIRequest, and gather information about how a model needs to be parsed, directly from a model type. However, it has major flows. First of, you can't create factory parser, that creates subclasses from a model. Second, you can't create protocol, that has several models conforming to it, and parse response to several model types. And third, you are forced to use a constructor, which is problematic for example for CoreData objects, that are not created using a constructor, but instead a created using a static method.

Now, in TRON 2.0.0-beta.2, we are trying a different approach, here's how response serialization protocol looks now:

public protocol Parseable {
    associatedtype ModelType

    func parse(_ data: Data) throws -> ModelType
}

And we are adding mandatory response serialization properties to each TRON request method, for example:

open func request<Model, ErrorModel>(_ path: String,
                      responseParser: @escaping APIRequest<Model,ErrorModel>.ResponseParser,
                      errorParser: @escaping APIRequest<Model,ErrorModel>.ErrorParser) -> APIRequest<Model,ErrorModel>

We are using generic typealiases to define ResponseParser closure like this:

public typealias ResponseParser = (Data) throws -> Model

This way you can directly set your own kind of parser and tweak it in any way. To maintain some form of backwards compatibility, we are adding convenience method for SwiftyJSON subspec users, that prefill response and errorParser with SwiftyJSONDecodable parsers like so:

open func request<Model: JSONDecodable, ErrorModel:JSONDecodable>(_ path: String) -> APIRequest<Model,ErrorModel>
    {
        return APIRequest(path: path, tron: self,
                          responseParser: { try JSONParser<Model>().parse($0) },
                          errorParser: { JSONErrorParser().parseError(fromRequest: $0.0, response: $0.1, data: $0.2, error: $0.3)})
    }

This is, of course, a huge change, and we want to involve community to discuss better approaches. We are releasing this form of response serialization in TRON 2.0.0-beta.2 for all of you to try it out and will be improving it towards 2.0.0 release.

NetworkLoggerPlugin buildtime warnings

The following warnings appear when I compile my project with Xcode Version 8.3.1 (8E1000a):

/Pods/TRON/Source/Core/NetworkLoggerPlugin.swift:53:29: Conditional downcast from 'Error?' to 'NSError' is a bridging conversion; did you mean to use 'as'?

/Pods/TRON/Source/Core/NetworkLoggerPlugin.swift:56:36: String interpolation produces a debug description for an optional value; did you mean to make this explicit?

MultipartAPIRequest && Int Parameters have errors

when i'm use MultipartAPIRequest and Int Parameters that:

let request: MultipartAPIRequest<APIResponse, APIResponseError> = tron.multipartRequest(path: "/social/login")

request.parameters = ["nickname": nickname, "socialUid": socialUid, "socialToken": socialToken, "socialSource": String(socialSource), "sex" : String(sex), "uuid": uuid, "pushToken": pushToken, "source": String(source)]

if the parameters have Int types, this post have some errors, must be convert the Int to String.

formData.appendBodyPart(data: value.dataUsingEncoding(NSUTF8StringEncoding) ?? NSData(), name: key)

Int types have no dataUsingEncoding method.

TRON RxSwift Error Handling

I've been trying to handle RXSwift Request Errors

ErrorHandling:  APIError<FIAppError>(request: Optional(https://url), response: Optional(<NSHTTPURLResponse: 0x1c0238ee0> { URL: https://url } { status code: 404, headers {
    "Content-Length" = 36;
    "Content-Type" = "application/json; charset=utf-8";
    Date = "Fri, 30 Jun 2017 13:15:12 GMT";
    Etag = "W/\"24-nuP261L8lpDkCVKMtmlHgw\"";
    Server = "nginx";
    "Set-Cookie" = "sails.sid=djskdjks; Path=/; HttpOnly";
    Vary = "X-HTTP-Method-Override";
    "cf-ray" = "hshdjsd-LHR";
    "x-powered-by" = "Sails <sailsjs.org>";
} }), data: Optional(36 bytes), error: Optional(Alamofire.AFError.responseValidationFailed(Alamofire.AFError.ResponseValidationFailureReason.unacceptableStatusCode(404))), errorModel: Optional(company.FIAppError))

This is my FIAppError class.

class FIAppError: JSONDecodable {
    
    var errors: [String:[String]] = [:]
    
    required init(json: JSON) {
        if let dictionary = json["errors"].dictionary { <- This block isn't entered because it isn't a dictionary
            for (key,value) in dictionary {
                errors[key] = value.arrayValue.map( { return $0.stringValue } )
            }
            print("FinalError: ", errors)
        }
    }
}

And this is my RequestHandler

func provisionDevice(key: String) -> Observable<FIUser> {
        var params = [String: Any]()
        param["key"] = key
        let request: APIRequest<FIUser, FIAppError> = tron.request("".subUrl())
        request.method = .post
        request.parameters = params
        return request.rxResult().flatMap { Observable.just($0) }
    }

Subscribing to the response

fileprivate func fetchOrganizationInfo() {
        repository.provisionDevice(key: organization.value)
            .flatMap{ Observable.just($0.userMainResponse) }
            .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
            .observeOn(MainScheduler.instance)
            .subscribe { (event) in
                switch event {
                case .next(let response):
                    print("ResponseCheck: ", response?.data ?? "No data available")
                    break
                case .error(let error):
                    print("ErrorHandling: ", error)
                    self.isLoading.value = !self.isLoading.value
                    break
                case .completed:
                    self.isLoading.value = !self.isLoading.value
                    break
                }
            }.addDisposableTo(disposeBag)
    }

Any help handling the error? Thanks.

.POST json encoding

By default when i use the .POST method the request.parameters dict is encoded as url encoding. In the CRUD example shouldn't there be a request.encoding = .JSON, to force the body to be encoded as json. Or is there a better way of doing it?

Unit tests are hitting network and are not stubbed

Hey! Thanks for the awesome lib. I'm just wondering why you don't use http stub library

  1. It could decrease time for running tests (it takes me 91 sec for 374 tests)
  2. It could make them isolated (now if you don't have internet connection or 3rd party service is down, they fail)

CocoaPods compatible version error

When I tried to install TRON 4.0 via CocoaPods, terminal shows these errors:

CocoaPods could not find compatible versions for pod "TRON":
  In Podfile:
    TRON (~> 4.0)

None of your spec sources contain a spec satisfying the dependency: `TRON (~> 4.0)`.

You have either:
 * out-of-date source repos which you can update with `pod repo update` or with `pod install --repo-update`.
 * mistyped the name or version.
 * not added the source repo that hosts the Podspec to your Podfile.

I did upgraded my project from Swift 3.2 to 4.0.
Right now using xcode 9.1 and Swift 4.0.

I already tried the below methods

pod repo remove master
pod setup
pod install

pod repo update

Setting request timeout

I have a static instance of TRON (tron). I tried the following to set the timeout to 10 seconds.

tron.manager.session.configuration.timeoutIntervalForRequest = 10
tron.manager.session.configuration.timeoutIntervalForResource = 10

I also tried overriding the Alamofire manager, but it's a let variable anyway.
Any suggestion about that ?

stubbing network calls

Hi,

I am trying to write some tests for my network calls. However, they don't seem to reach the performStub function at all. Is there something I am doing wrong here? this is how my test looks like currently.

    func testDataCanBeRetrieved() {
        
        let testBundle = Bundle(for: ProActiveTests.self)
        
        let request = self.coordinator.invoiceService.readAll()
        request.stubbingEnabled = true
        request.apiStub.stubDelay = 0.5
        request.apiStub.successful = true
        request.apiStub.buildModel(fromFileNamed: "invoices.json", inBundle: testBundle)
        request.apiStub.performStub(withSuccess: { (response) in
            print("response", response)
            XCTAssert(true)
        }) { (err) in
            print("Failure", err)
            XCTAssert(false)
        }
    }

Better way of getting error when using TRON with RxSwift.

Hello, I'm wondering if there is a better approach to avoid force casting error while using RxSwift.

MyApiClient.Token.for(user: "username", pass: "superpassword").subscribe(onNext: { token in
      dump(token)
}, onError: { error in
      guard let error = error as? APIError<MyApiError> else { return }
      dump(error.errorModel)
}).addDisposableTo(disposeBag)

I'm seeing the wrapper around APIRequest https://github.com/MLSDev/TRON/blob/master/Source/Tron%2BRxSwift.swift#L43

failure: is being implicitly called with APIError<ErrorModel> as parameter, and that parameter is passed to observer.onError, but if you see my code above, I need to force cast since I'm getting a generic Error, since rxResult() -> RxSwift.Observable<Model>

Basically, we are losing the concrete APIError<ErrorModel> type

any clue?

MyApiClient.Token implementation is like

enum MyApiClient {}
extension MyApiClient {
    private static let tron = TRON(baseURL: Environment.baseUrl)

    struct Token {
        private static let clientId = "IOS-CLIENT"
        private enum grantType {
            case `private`, `public`
        }

        final class Model: JSONDecodable {
            let token: String
            let expiresIn: Int
            
            required init(json: JSON) {
                token = json["token"].stringValue
                expiresIn = json["expiresIn"].intValue
            }
        }
        
        static func `for`(user: String, pass: String) -> Observable<Model> {
            let request : APIRequest<Model, TripBotApiError> = tron.request("tokens")
            request.method = .post
            request.parameters = [
                "clientId": Token.clientId,
                "grantType": Token.grantType.private,
                "username": user,
                "password": pass
            ]
            return request.rxResult()
        }
    }
}

How can help me with a full request example, including params, method and headers

Hi, I'm trying with this but I don't know

`
var tron = TRON(baseURL: "http://WebService.svc")

fileprivate func fecthMenu() {
let params = [
"apiSecretKey": "xxxxxxxxxxxxxxxx",
"storeId": "1",
"languageId": "1",
"customerId": "0"
]

    //tron.headerBuilder = HeaderBuilder(defaultHeaders: ["Content-Type": "application/json; charset=utf-8"])

    let request : APIRequest<Menu,Error> = tron.request("/TopMenu")
    request.parameters = params
    //request.headers = ["Content-Type": "application/json; charset=utf-8"]
    request.method = .post

   request.perform(withSuccess: { zen in
        
        print(zen)
    }, failure: { error in
        
        print(error)
    })
   
}`

And get this error
Alamofire.AFError.responseValidationFailed(Alamofire.AFError.ResponseValidationFailureReason.unacceptableStatusCode(400))

Thanks.

TRON.APIError.errorModel is not getting parsed. (using RxSwift)

Hello I'm using TRON 3.0.3 with RxSwift

When there is an error on my API calls, The following code example...

MyApiClient.Token.for(user: "username", pass: "superpassword").subscribe(onNext: { token in
      dump(token)
}, onError: { error in
      guard let error = error as? APIError<MyApiError> else {
            print("can't cast")
            return
      }
            
      dump(error.errors)
}).addDisposableTo(disposeBag)

MyApiError never gets parsed... so I cannot access error.errors


When I dump error, I see this

TRON.APIError<app.MyApiError>
  ▿ request: Optional(https://<baseurl>/tokens)
    ▿ some: https://<baseurl>/tokens
      ▿ url: Optional(https://<baseurl>/tokens)
        ▿ some: https://<baseurl>/tokens
          - _url: https://<baseurl>/tokens #0
            - super: NSObject
      - cachePolicy: 0
      - timeoutInterval: 60.0
      - mainDocumentURL: nil
      - networkServiceType: __ObjC.NSURLRequest.NetworkServiceType
      - allowsCellularAccess: true
      ▿ httpMethod: Optional("POST")
        - some: "POST"
      ▿ allHTTPHeaderFields: Optional(["Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded; charset=utf-8"])
        ▿ some: 2 key/value pairs
          ▿ (2 elements)
            - key: "Accept"
            - value: "application/json"
          ▿ (2 elements)
            - key: "Content-Type"
            - value: "application/x-www-form-urlencoded; charset=utf-8"
      ▿ httpBody: Optional(81 bytes)
        ▿ some: 81 bytes
          - count: 81
          ▿ pointer: 0x00000001700d3870
            - pointerValue: 6174881904
      - httpBodyStream: nil
      - httpShouldHandleCookies: true
      - httpShouldUsePipelining: false
  ▿ response: Optional(<NSHTTPURLResponse: 0x17022f280> { URL: https://<baseurl>/tokens } { status code: 502, headers {
    Connection = "keep-alive";
    "Content-Length" = 37;
    "Content-Type" = "application/json; charset=utf-8";
    Date = "Sun, 13 Aug 2017 23:28:51 GMT";
    Etag = "W/\"25-h8+0H4iWBtN8V1EXRAcIHw\"";
    Server = Cowboy;
    Vary = "X-HTTP-Method-Override, Accept-Encoding";
    Via = "1.1 vegur";
} })
    - some: <NSHTTPURLResponse: 0x17022f280> { URL: https://<baseurl>/tokens } { status code: 502, headers {
    Connection = "keep-alive";
    "Content-Length" = 37;
    "Content-Type" = "application/json; charset=utf-8";
    Date = "Sun, 13 Aug 2017 23:28:51 GMT";
    Etag = "W/\"25-h8+0H4iWBtN8V1EXRAcIHw\"";
    Server = Cowboy;
    Vary = "X-HTTP-Method-Override, Accept-Encoding";
    Via = "1.1 vegur";
} } #1
      - super: NSURLResponse
        - super: NSObject
  ▿ data: Optional(37 bytes)
    ▿ some: 37 bytes
      - count: 37
      ▿ pointer: 0x0000000174469900
        - pointerValue: 6245751040
      ▿ bytes: 37 elements
        - 123
        - 34
        - 101
        - 114
        - 114
        - 34
        - 58
        - 34
        - 85
        - 115
        - 101
        - 114
        - 32
        - 111
        - 114
        - 32
        - 112
        - 97
        - 115
        - 115
        - 119
        - 111
        - 114
        - 100
        - 32
        - 105
        - 115
        - 32
        - 105
        - 110
        - 118
        - 97
        - 108
        - 105
        - 100
        - 34
        - 125
  ▿ error: Optional(Alamofire.AFError.responseValidationFailed(Alamofire.AFError.ResponseValidationFailureReason.unacceptableStatusCode(502)))
    ▿ some: Alamofire.AFError.responseValidationFailed
      ▿ responseValidationFailed: Alamofire.AFError.ResponseValidationFailureReason.unacceptableStatusCode
        - unacceptableStatusCode: 502
  ▿ errorModel: Optional(app.MyApiError)
    ▿ some: app.MyApiError #2
      - errors: 0 key/value pairs

Seems like error it is already a TRON.APIError<app.MyApiError>... but if you see errorModel.errors is empty...

The API returns 502 with the following body

{
    "err": "User or password is invalid"
}

Is this because the response body never gets parsed because of the unacceptableStatusCode?

Unable To Use The Dependency On Xcode 9-beta

I am using Xcode 9-Beta for development. And I've not been able to utilize the TRON library. Most of the errors are coming from the Alamofire library. I could modify the libraries to work, but it's a project being worked on by more than one person. So, is there a way to have this fixed or I've to go migrate my codebase back to 3.1?

I installed Alamofire 4.4 and it's working fine. But, the one bundled with TRON is failing when building.

Thanks

Nested Mapper example

I have some json like the following.

{ "Devices":[ { "id":1, "Attributes":[ { "name":1 }, { "name":2 } ] } ] }

I am not sure exactly how to map that to a swift class.

Submitting JSON as body?

I'm trying to submit a JSON post to my local dev server and having issues. I haven't seen much documentation on sending complex parameters or JSON in the body for a POST request in the documentation (unless I'm missing something). I've been browsing through issues with no success unfortunately on how to get this to work, I have a complex bit of JSON that I need to send in a .POST request to my Rails API. Here is the code for sending the JSON

 `@IBAction func SessionSubmit(_ sender: Any) {
    let selected_session_type = session_type_ids[self.SessionTypePicker.selectedRow(inComponent: 0)]
    let selected_session_duration = session_type_durations[self.SessionTypePicker.selectedRow(inComponent: 0)]
    let dateFormatter = DateFormatter()
    let selected_start_time = DateTimePicker.date
    let start_time = dateFormatter.string(from: DateTimePicker.date)
    let end_time = dateFormatter.string(from: selected_start_time + selected_session_duration.minutes)
    let refreshToken = current_user.refreshToken as String
    let accessToken = current_user.accessToken as String
    let params: [String: Any] = ["access_token": accessToken, "refresh_token": refreshToken]
    let body: [String: Any] = ["data": [
                    "type": "session",
                    "attributes": [
                        "title": "",
                        "description": "",
                        "start": start_time,
                        "end": end_time,
                        "session_mode": "video_session"],
                        "relationships": [
                            "users": [
                                "data": [
                                    ["type": "user",
                                     "id": current_user.userId],
                                    ["type": "user",
                                     "id": clientId]
                                ]
                            ],
                            "session_type":[
                                "data": [
                                    "type": "session_type",
                                    "id": selected_session_type
                                    ]
                                ]
                            ]
                        ]
                    ]
    do {
        let jsonData = try JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
        print(jsonData)
        let decoded = try JSONSerialization.jsonObject(with: jsonData, options: [])
        print(decoded)
        
        if let dictFromJSON = decoded as? [String:Any] {
            print(dictFromJSON)
            self.sendData(parameters: params, body: dictFromJSON) {
                self.stopAnimating()
            }
        }
    } catch {
        print(error.localizedDescription)
    }
    startAnimating(GlobalVariables().LoadingSize)`

   `    func sendData(parameters: [String:Any], body: [String:Any], finished: @escaping () -> Void) {
    DataController().Post(parameters: parameters, path: "/v1/sessions/"+current_user.userId).perform(withSuccess: { (FetchedArray) in
        if let SessionTypesArray = FetchedArray.response["data"].array {
            self.session_types += SessionTypesArray 
        }
        finished()
    }) { (FetchFailed) in
        AuthenticationController().logout(user: self.current_user)
        self.dismiss(animated: true, completion: {});
        finished()
    }
}`

I'm thinking maybe I need to make these embedded in the document and only have my refresh and access tokens as parameters? so I tried working on doing it that way and yet still another road block because of not seeing any ways to add to the body, I know there is appendBody but I'm not sure how that would work either, the json seems to not be flowing to the rails api correctly either
`Started POST "/v1/sessions/122" for 127.0.0.1 at 2017-05-19 13:52:32 -0400
Error occurred while parsing request parameters.
Contents:

access_token=90d2befd58912f978dc681f2f0d1eaf3d217dbe25fdac3d1ec4ab45e9bfff41a&data%5Btype%5D=session&data%5Battributes%5D%5Bdescription%5D=&data%5Battributes%5D%5Btitle%5D=&data%5Battributes%5D%5Bend%5D=&data%5Battributes%5D%5Bsession_mode%5D=video_session&data%5Battributes%5D%5Bstart%5D=&data%5Brelationships%5D%5Bsession_type%5D%5Bdata%5D%5Btype%5D=session_type&data%5Brelationships%5D%5Bsession_type%5D%5Bdata%5D%5Bid%5D=379&data%5Brelationships%5D%5Busers%5D%5Bdata%5D%5B%5D%5Btype%5D=user&data%5Brelationships%5D%5Busers%5D%5Bdata%5D%5B%5D%5Bid%5D=122&data%5Brelationships%5D%5Busers%5D%5Bdata%5D%5B%5D%5Btype%5D=user&data%5Brelationships%5D%5Busers%5D%5Bdata%5D%5B%5D%5Bid%5D=6&refresh_token=6b60a52534e1fa2ed436a9542a6c27b7f05e56e7b306f48df12b286b8fe5f624

JSON::ParserError - 822: unexpected token at 'access_token=90d2befd58912f978dc681f2f0d1eaf3d217dbe25fdac3d1ec4ab45e9bfff41a&data%5Btype%5D=session&data%5Battributes%5D%5Bdescription%5D=&data%5Battributes%5D%5Btitle%5D=&data%5Battributes%5D%5Bend%5D=&data%5Battributes%5D%5Bsession_mode%5D=video_session&data%5Battributes%5D%5Bstart%5D=&data%5Brelationships%5D%5Bsession_type%5D%5Bdata%5D%5Btype%5D=session_type&data%5Brelationships%5D%5Bsession_type%5D%5Bdata%5D%5Bid%5D=379&data%5Brelationships%5D%5Busers%5D%5Bdata%5D%5B%5D%5Btype%5D=user&data%5Brelationships%5D%5Busers%5D%5Bdata%5D%5B%5D%5Bid%5D=122&data%5Brelationships%5D%5Busers%5D%5Bdata%5D%5B%5D%5Btype%5D=user&data%5Brelationships%5D%5Busers%5D%5Bdata%5D%5B%5D%5Bid%5D=6&refresh_token=6b60a52534e1fa2ed436a9542a6c27b7f05e56e7b306f48df12b286b8fe5f624':
json (1.8.6) lib/json/common.rb:155:in parse'

any help here would be greatly appreciated guys! thanks in advance!

Is XML Based Response Supported

I've been trying to utilize TRON for an API that gives response in XML and my response has been empty always. This is how I've been making my requests:

let tron = MoviepathService.create()
    
    func getSubscriptions(params: [String : Any]) -> Observable<[SubscriptionObject]> {
        let request: APIRequest<String, MoviePathError> = tron.request("".getSubscriptions())
        request.method = .post
        request.headers = ["Content-Type":"application/x-www-form-urlencoded"]
        request.parameters = params
        return request.rxResult().flatMap({ (element) -> Observable<[SubscriptionObject]> in
            print("SubscriptionFetch: ", element)
            return Observable.just([SubscriptionObject]())
        })
    }

Am I doing anything wrong? Thanks.

Hi Request failure not return main_queue

I find int APIRequest.swift
`dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), {
let object : AnyObject
do {
object = try (data ?? NSData()).parseToAnyObject()
}
catch let jsonError as NSError {
failure?(errorBuilder.buildErrorFromRequest(urlRequest, response: response, data: data, error: jsonError))
return
}

            let model: Model.ModelType
            do {
                model = try responseBuilder.buildResponseFromJSON(object)
            }
            catch let parsingError as NSError {
                failure?(errorBuilder.buildErrorFromRequest(urlRequest, response: response, data: data, error: parsingError))
                return
            }
            dispatch_async(dispatch_get_main_queue(), {
                success(model)
            })
        })`

failure?(errorBuilder.buildErrorFromRequest(urlRequest, response: response, data: data, error: parsingError)) not dispatch_get_main_queue why?

In depth example of Download Request

Hi,

The Download Request Example is a little bit weird to implement. Lets say if I download an image, would I do the same as when I would do with a normal request?

I created this function, but it keeps throwing an error

/Users/username/xcode/project/project/APISessionManager.swift:33:87: Cannot convert value of type 'DownloadAPIRequest<_, _>' to specified type 'DownloadAPIRequest<InvoiceResponse, MyAppError>'

This is my function

func downloadInvoice(id: String) -> DownloadAPIRequest<InvoiceResponse, MyAppError> {
    let url = URL(string: "url/invoice")!
    let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
    let request: DownloadAPIRequest<InvoiceResponse, MyAppError> = APIHelper.tron.download(url.absoluteString, to: destination)
    
    return request
}

and my response model

struct InvoiceResponse {
    var invoice: UIImage?
}

Could you give a more in depth example of how to use this correctly?

  • how should the model be build
  • how to handle the response

Thanks in advance!

Is there a way to access the header in the response?

I am making my request just fine, using TRON (w/Alamofire) and SwiftyJSON. I get the response body and it is parsed into the response model class I made for it, all working great. What I need to do now is to get some fields from the response HEADER. Is there any way to do that? Thanks for any help in advance. (BTW, I have searched for the past 2 hours, stackoverflow, github, google, asked co-worker). I'm stumped.

Problem with code=-999 Error, "Cancelled"

I am switching from alamofire to TRON, I had my authentication code working just fine in AlamoFire, but when switching to TRON I'm getting this error

Error APIError<AuthorizationFailed>(request: Optional(http://192.168.1.32:3000/oauth/token), response: nil, data: Optional(0 bytes), error: Optional(Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=http://192.168.1.32:3000/oauth/token, NSLocalizedDescription=cancelled, NSErrorFailingURLStringKey=http://192.168.1.32:3000/oauth/token})

I have my plist to where currently it allows arbitrary loads, and like I said earlier I was able to make my API calls just fine when using only AlamoFire, I really like TRON though and want to convert so that I can make my life a little easier with API calls. here is the method's I've written:

` let tron = TRON(baseURL: GlobalVariables().baseURL)

class Authorized: JSONDecodable {
    required init(json: JSON) throws {
        DataController().Get(path: "/v1/users").perform(withSuccess: { (fetched) in
              print("this is what i fetched", fetched)

        }) { (FetchFailed) in
            print("f*cked up")
        }
    }
}

class AuthorizationFailed: JSONDecodable {
    required init(json: JSON) throws {
        // code for failure
        print(json)
    }
}

func Authorize(parameters: [String:Any]) -> APIRequest<Authorized, AuthorizationFailed> {
    let AuthorizationRequest: APIRequest<Authorized, AuthorizationFailed> = tron.request("oauth/token")
    AuthorizationRequest.method = .post
    AuthorizationRequest.parameters = parameters
    return AuthorizationRequest
}

func AuthorizeUser(parameters: [String:Any]) {
    self.Authorize(parameters: parameters).perform(withSuccess: { (Authorized) in
        print("Success", Authorized)
    }) { (err) in
        print("Error", err)
        print(err.request) // Original URLRequest
        print(err.response) // HTTPURLResponse
        print(err.data) // Data of response
        print(err.error) // Error from Foundation Loading system
        print(err.errorModel.debugDescription) // MyAppError parsed property
    }
}`

Thanks in advance for any help you guys can relay. Great work on the pod too btw.

Request retry support

Any roadmap to support request retry scenarios. e.g. retry the request with correct auth header.

Send only a String value in the request body

Is it possible to POST just a string in the request body without using [String: Any] dictionary.
Example: I need to send parameter = "Hello" to the server instead of the parameter = ["Key": "Hello"]

Help needed with json response erroring when 200 code response

I saw a related issue to this problem i'm having already in the closed issues, but the fixes there didn't do the trick for me. I'm getting a json response and without error from my server (200 code) but i'm getting an error from xcode/swift. Below is said error:

{ status code: 200, headers { "Cache-Control" = "max-age=0, private, must-revalidate"; Connection = close; "Content-Type" = "application/json; charset=utf-8"; Etag = "W/\"f49d10c019a7a3b8c095a8da43570f5f\""; Server = thin; "X-Content-Type-Options" = nosniff; "X-Frame-Options" = SAMEORIGIN; "X-Meta-Request-Version" = "0.3.4"; "X-Request-Id" = "129f985f-31a5-4fe7-a09b-3e8d0cb4823a"; "X-Runtime" = "0.433514"; "X-XSS-Protection" = "1; mode=block"; } }), data: Optional(653 bytes), error: Optional(Alamofire.AFError.responseValidationFailed(Alamofire.AFError.ResponseValidationFailureReason.unacceptableContentType(acceptableContentTypes: ["application/vnd.api+json"], responseContentType: "application/json"))), errorModel: Optional(wecounsel.DataController.FetchFailed))

Here is what the json looks like:

{ "meta" : { "current_user_abilities" : { "facilitator" : false, "provider" : true, "consumer" : false, "org_admin" : false, "admin" : false } }, "data" : [ { "id" : "session-4763-20170422123000", "links" : { "self" : "http:\/\/localhost:3000\/v1\/sessions\/4763\/repeated\/starts\/2017-04-22%2012:30:00%20-0400\/ends\/2017-04-22%2013:30:00%20-0400" }, "type" : "session", "attributes" : { "end" : "2017-04-22 13:30:00", "start" : "2017-04-22 12:30:00", "title" : "Session", "current_user_participates" : true, "description" : "<p>Session with Doc Brown and Austin Consumer for Follow up.<\/p><p>12:30 PM - 01:30 PM EDT<\/p>", "repeat_instance" : true, "session_mode" : "video_session" } } ] }

Here is my functions I'm using:

`
public class DataController {
static let tron = TRON(baseURL: GlobalVariables().baseURL)

class Fetched: JSONDecodable {
    let response: JSON
    required init(json: JSON) throws {
        let response = json
        self.response = response
    }
}

class FetchFailed: JSONDecodable {
    let response: JSON
    required init(json: JSON) throws {
        print(json)
        let response = json
        self.response = response
}

func Get(parameters: [String:Any], path: String) -> APIRequest<Fetched, FetchFailed> {
    let request: APIRequest<Fetched, FetchFailed> = DataController.tron.request(path)
    request.parameters = parameters
    request.headers = ["Accept":"application/vnd.api+json"]

// DataController.tron.headerBuilder = HeaderBuilder(defaultHeaders: [:])
return request
}`

func getData(parameters: [String:Any], finished: @escaping () -> Void) { DataController().Get(parameters: parameters, path: "/v1/calendar").perform(withSuccess: { (Fetched) in if let eventsArray = Fetched.response["data"].array { self.events += eventsArray } print(self.events, "here are the events") finished() }) { (FetchFailed) in print(FetchFailed) finished() } }

anyone have any idea of whats going on? i know the json has an array but i don't think thats the issue is it?

APIRequest result type error

Hello,

I have tried to implement TRON in a singleton and passed codable to the APIRequest, But I keep getting this error. Please let me know if I am doing anything wrong here.

Thank You

screen shot 2017-12-26 at 8 24 11 am
screen shot 2017-12-26 at 8 24 41 am

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.