{"id":13465640,"url":"https://github.com/symentis/Corridor","last_synced_at":"2025-03-25T16:32:34.872Z","repository":{"id":78546877,"uuid":"99477843","full_name":"symentis/Corridor","owner":"symentis","description":"A Coreader-like Dependency Injection μFramework","archived":false,"fork":false,"pushed_at":"2020-08-26T14:51:54.000Z","size":52,"stargazers_count":61,"open_issues_count":1,"forks_count":3,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-10-12T14:41:59.444Z","etag":null,"topics":["coreader","dependency-injection","functional-programming","ios","swift","swift-4","swift-framework"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/symentis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-08-06T09:55:06.000Z","updated_at":"2023-05-25T19:55:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"ee859c35-d34f-4c0f-8b50-ab2144f8fec7","html_url":"https://github.com/symentis/Corridor","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symentis%2FCorridor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symentis%2FCorridor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symentis%2FCorridor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/symentis%2FCorridor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/symentis","download_url":"https://codeload.github.com/symentis/Corridor/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222088593,"owners_count":16928984,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coreader","dependency-injection","functional-programming","ios","swift","swift-4","swift-framework"],"created_at":"2024-07-31T15:00:33.153Z","updated_at":"2024-10-29T17:31:20.712Z","avatar_url":"https://github.com/symentis.png","language":"Swift","funding_links":[],"categories":["Libs","Dependency Injection [🔝](#readme)"],"sub_categories":["Dependency Injection"],"readme":"\n# Corridor\n_A Coreader-like Dependency Injection μFramework_\n\n[![Build Status](https://travis-ci.org/symentis/Corridor.svg?branch=master)](https://travis-ci.org/symentis/Corridor)\n![Language](https://img.shields.io/badge/language-Swift%204.0-orange.svg)\n[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![@elmkretzer](https://img.shields.io/badge/twitter-@elmkretzer-blue.svg?style=flat)](http://twitter.com/elmkretzer)\n\n\n# Table of Contents\n\u003ca href=\"#why\"\u003eWhy\u003c/a\u003e |\n\u003ca href=\"#examples-from-playground\"\u003eExamples\u003c/a\u003e |\n\u003ca href=\"#usage\"\u003eUsage\u003c/a\u003e |\n\u003ca href=\"#installation\"\u003eInstallation\u003c/a\u003e |\n\u003ca href=\"#credits--license\"\u003eCredits \u0026 License\u003c/a\u003e |\n\u003c/p\u003e\n\n# Why\n\nIn order to write tests we must substitute parts of our code that we do not have control over such as:\n- Network\n- File system\n- Creating dates\n- Keychain\n\n__We need to substitute them in tests in order to verify assumptions.__\n\nThe purpose of Corridor is to:\n- Provide a common interface for things that need to be replaced in TestCases\n- Simplify setup in TestCases without manually providing mocks etc\n- Transparently provide the current context to all your Types\n- Separate any kind of test related logic from production code\n\nIn an ideal World a Coeffect is under control.\n\n```swift\nclass Controller: UIViewController {\n\n  override func viewWillAppear(_ animated: Bool) {\n    print(Date())\n  }\n}\n```\n\nThe Date in the above example is _out of control_.  \n\nRunning a test for that Controller will always result in a different Date.\nIn that case the _Date_ is just a placeholder for any Coeffect.\n\nCorridor tries to solve this problem by taking the concept of a Coreader and turning it into a Swift friendly implementation via protocols and a single property.\n\n_What will it look like?_\n\n```swift\nclass Controller: UIViewController, HasInstanceContext {\n\n  var resolve = `default`\n\n  override func viewWillAppear(_ animated: Bool) {\n     print(now)\n  }\n}\n```\n\nThe idea behind Corridor was part of my talk at the [Functional Swift Conference 2017](http://2017-fall.funswiftconf.com) in Berlin.\n\n[![Reader and Coreader for Dependency Injection](https://img.youtube.com/vi/oPyqKETp3ks/0.jpg)](https://www.youtube.com/watch?v=oPyqKETp3ks)\n\n# Usage\n\n## Implement a Protocol\n_Either_ one of the two protocols provided by Corridor: `HasInstanceContext` or `HasStaticContext`.  \nOr any convenience protocol that extends one of those.\n\n##  Add a Property\nAny type that needs access to an injected value also needs to know how to resolve it. This is done by providing a property called _resolve_.  \n\nBy default it should be set to ```var resolve = `default` ```.  \n\n**Why the backticks?**  \n_default_ is a swift keyword and by using the backticks the property looks\nmore _config-ish_.\n\n## Setup Context\n\nA base protocol that defines your dependencies:\n\n```swift\nprotocol AppContext {\n\n  /// The current Date\n  var now: Date { get }\n}\n```\n\n## Context Implementation\nAn implemention of a Context.\nUsually we use two implementations.\nOne for the running application, one for the test cases.\n\n```swift\nstruct DefaultContext: AppContext {\n\n  var now: Date {\n    // The real current Date\n    return Date()\n  }\n}\n\nstruct MockContext: AppContext {\n\n  var now: Date {\n    // We assume way earlier\n    return Date.distantPast\n  }\n}\n```\n\n## Resolver\nIn order to provide the default resolver you must extend the base protocol in Corridor. This will provide a static variable called `default` of Type Resolver to your Type in order to provide access.  \nThis extension is done once in your app.\n\n```swift\nextension HasContext {\n\n    typealias Context = AppContext\n\n    static var `default`: Resolver\u003cSelf, AppContext\u003e {\n       return Resolver(context: DefaultContext())\n    }\n}\n```\n\nThe visibility of any property in the context is controlled by extending either `HasInstanceContext` or `HasStaticContext` or any derived protocol.  \n\nBy using protocols we can constrain access in a granular way. Additionally it allows for the injection of functions.  \n\n_See example CorridorDemo.playground._\n\n```swift\nextension HasInstanceContext where Self.Context == AppContext  {\n\n    /// Injected now property\n    var now: Date {\n        return resolve[\\.now]\n    }\n}\n```\n\n## Changing the Context\nIn your actual code everything resolves to the `DefaultContext`.  \nBut in your Tests you need to make sure to switch to the mock context.  \nThe simplest way is:\n\n```swift\nvar myController = withContext(Controller(), MockContext())\n```\n\nSetting up the context in the Tests can easily be simplified by making the TestCase itself Context aware. Additionally you can build functions on top of that to make instantiation automagically have the correct Context.\n\n```swift\nextension HasContext {\n\n    static var mock: Resolver\u003cSelf, AppContext\u003e {\n        return Resolver(context: MockContext())\n    }\n}\n\n/// Extension for TestCase (e.g. subclass of XCTestCase)\n/// to provide easy access to get controller with mock context\nextension HasInstanceAppContext where Self: TestCase {\n\n    func withController\u003cV\u003e() -\u003e V?\n        where V: UIViewController, V: ManagedByStoryboard, V: HasInstanceAppContext {\n        /// A simplified function that will make sure your context is set\n        return self.controller()\n    }\n}\n```\n\n# Examples from Playground\n\nSee the provided Playground in the workspace.\n\n## Intro\n\n```swift\nimport Foundation\nimport UIKit\nimport PlaygroundSupport\nimport Corridor\n\n// 1. Protocol for Context \n// e.g. AppContext.swift\npublic protocol AppContext {\n  var now: Date { get }\n}\n\n// 2. Context Implementation for Running App\n// e.g. DefaultContext.swift\nstruct DefaultContext: AppContext {\n  var now: Date { return Date() }\n}\n\n// 3. HasContext is Corridor base protocol\n// e.g. Resolver.swift\nextension HasContext {\n  typealias Context = AppContext\n  // provide default resolver\n  static var `default`: Resolver\u003cSelf, AppContext\u003e {\n    return Resolver(context: DefaultContext())\n  }\n}\n\n// 4. Add resolvable values\n// e.g. Resolver.swift\nextension HasInstanceContext\nwhere Self.Context == AppContext {\n  var now: Date {\n    return resolve[\\.now]\n  }\n}\n\n// 5. Usage\nfinal class Controller: LabelController, HasInstanceContext {\n  var resolve = `default`\n  override func viewWillAppear(_ animated: Bool) {\n    label.text = \"Now is \\(now)\"\n  }\n}\n\nPlaygroundPage.current.liveView = Controller()\n```\n\n## Test\n\n```swift\nimport Foundation\nimport PlaygroundSupport\nimport Corridor\n\n// 1. Context Implementation for Testing App\nstruct MockContext: AppContext {\n  var now: Date { return Date.distantPast }\n}\n\n// 2. Usage\nlet test = withContext(Controller(), MockContext())\n\nPlaygroundPage.current.liveView = test\n```\n\n## Api\n\nThis example combines a `Reader` composition for chained REST calls.\nDefining the `Api` in `AppContext` and by implementing `ContextAware` it\nwill have access to the current context.\nTherefore we don't need to pass additional params to the network calls, and\nit is ensured that all injected valus are correctly resolved.\n\n```swift\nimport Foundation\nimport UIKit\nimport PlaygroundSupport\nimport Corridor\n\n// 1. Protocol for Context\nprotocol AppContext {\n  var now: Date { get }\n  var api: Api { get }\n}\n\n// 2. Context Implementation for Running App\nstruct DefaultContext: AppContext {\n  var now: Date { return Date() }\n  var api: Api { return Api(connection: ServerConnection()) }\n}\n\n// 3. Context Implementation for Test App\nstruct MockContext: AppContext {\n  var now: Date { return Date.distantPast }\n  var api: Api { return Api(connection: MockConnection()) }\n}\n\n// 4. Extend Corridor\nextension HasContext {\n  typealias Context = AppContext\n  static var `default`: Resolver\u003cSelf, AppContext\u003e {\n    return Resolver(context: DefaultContext())\n  }\n}\n\n// 5. Convenience protocol\nprotocol ContextAware: HasInstanceContext\nwhere Self.Context == AppContext {}\n\n// 6. Define API for (String) -\u003e Future\u003cT\u003e\nstruct Api: ContextAware {\n  var resolve = `default`\n  let connection: Connection\n  init(connection: Connection) {\n    self.connection = connection\n  }\n  var endpoint: Endpoint {\n    return connection.endpoint\n  }\n  func getResponse\u003cT: Codable\u003e(_ s: String) -\u003e Future\u003cT\u003e {\n    return connection.getResponse(s)\n  }\n}\n\n// 7. Fake ReSwift Store\nvar dispatched: Set\u003cString\u003e = Set()\n\n// 8. Extend Corridor\nextension ContextAware {\n  // Extract\n  var now: Date {\n    return resolve[\\.now]\n  }\n  // Extend\n  var api: Api {\n    return resolve[\\.api]\n  }\n  var dispatch: Dispatch {\n    return { dispatched.insert($0) }\n  }\n  var messages: String {\n    return dispatched.sorted().joined(separator: \"\\n\")\n  }\n}\n\n// 9. Define API Operations\ntypealias ApiAware\u003cO\u003e = Reader\u003cApi, O\u003e\ntypealias ApiFuture\u003cO\u003e = ApiAware\u003cFuture\u003cO\u003e\u003e\ntypealias ApiBind\u003cI, O\u003e = (Future\u003cI\u003e) -\u003e ApiFuture\u003cO\u003e\n\nfunc bind\u003cI, O\u003e(_ urlFrom: @escaping (I) -\u003e String) -\u003e ApiBind\u003cI, O\u003e\n  where O: Codable {\n    return { future in\n      ApiAware { api in\n        future.flatMap { input in\n          api.dispatch(\"\\(api.now.formatted) \\n -\\(input)\")\n          return api.getResponse(urlFrom(input))\n        }\n      }\n    }\n}\n\nlet apiEntrypoint: ApiFuture\u003cEndpoint\u003e = ApiAware { api in\n  Future\u003cEndpoint\u003e(value: api.endpoint)\n}\nlet usersEndpoint: ApiBind\u003cEndpoint, UsersEndpoint\u003e = bind {\n  $0.usersEndpoint\n}\nlet firstUserEndpoint: ApiBind\u003cUsersEndpoint, UserEndpoint\u003e = bind {\n  $0.firstUserEndpoint\n}\nlet addressEndpoint: ApiBind\u003cUserEndpoint, AddressEndpoint\u003e = bind {\n  $0.addressEndpoint\n}\n\nlet apiCall = usersEndpoint \u003e=\u003e firstUserEndpoint \u003e=\u003e addressEndpoint\n\n// 10. Controller\nfinal class Controller: LabelController, ContextAware {\n\n  var resolve = `default`\n\n  override func viewWillAppear(_ animated: Bool) {\n\n    let address = apiEntrypoint\n      .flatMap(apiCall)\n      .run(api)\n\n    address.onSuccess { (s: Address) in\n      label.text = messages + \"\\n\" + s\n\n    }\n  }\n}\n\n// 11. Run\nlet app = Controller()\nlet test = withContext(Controller(), MockContext())\nPlaygroundPage.current.liveView = app\n```\n\n# FAQ\n\n### What if a property needs to be resolved to the context?\n\nYou can use  `lazy var` to resolve properties directly.\n_See Tests._\n\n```swift\n\nfinal class MyClass: HasInstanceContext {\n  var resolve = `default`\n\n  lazy var contextAwareProperty = resolve[AnotherContextAwareClass()]\n}\n```\n\n\n# Installation\n\n## Carthage\n\nTo integrate Corridor into your project using Carthage, add to your `Cartfile`:\n\n```ogdl\ngithub \"symentis/Corridor\"\n```\n\nSee [Carthage](https://github.com/Carthage/Carthage) for further inststructions.\n\n# Requirements\nSwift 4\n\n# Credits \u0026 License\nCorridor is owned and maintained by [Symentis GmbH](http://symentis.com).\n\nDeveloped by: Elmar Kretzer\n[![Twitter](https://img.shields.io/badge/twitter-@elmkretzer-blue.svg?style=flat)](http://twitter.com/elmkretzer)\n\nAll modules are released under the MIT license. See LICENSE for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsymentis%2FCorridor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsymentis%2FCorridor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsymentis%2FCorridor/lists"}