{"action":"created","comment":{"url":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/comments/221760015","pull_request_review_id":160489650,"id":221760015,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyMTc2MDAxNQ==","diff_hunk":"@@ -0,0 +1,195 @@\n+package io.chrisdavenport.mules\n+\n+import cats.Monad\n+import cats.effect._\n+import cats.effect.concurrent.Ref\n+import cats.instances.list._\n+import cats.instances.option._\n+import cats.syntax.flatMap._\n+import cats.syntax.functor._\n+import cats.syntax.traverse._\n+import io.chrisdavenport.mules.Cache.TimeSpec\n+\n+import scala.collection.immutable.Map\n+import scala.concurrent.duration._\n+\n+class AutoFetchingCache[F[_] : Concurrent : Timer, K, V](private val values: Ref[F, Map[K, AutoFetchingCache.CacheContent[F, V]]],\n+                                                         val defaultExpiration: Option[TimeSpec],\n+                                                         private val reload: Option[(TimeSpec, Ref[F, Map[K, Fiber[F, Unit]]])],\n+                                                         val fetch: K => F[V]) {\n+\n+  def lookup(k: K): F[V] = AutoFetchingCache.lookup(this)(k)\n+}\n+\n+object AutoFetchingCache {\n+\n+  private sealed abstract class CacheContent[F[_], A]\n+\n+  private case class Fetching[F[_], A](f: Fiber[F, A]) extends CacheContent[F, A]\n+\n+\n+  private case class CacheItem[F[_], A](\n+                                         item: A,\n+                                         itemExpiration: Option[TimeSpec]\n+                                       ) extends CacheContent[F, A]\n+\n+\n+  /**\n+    * Create a new cache with a default expiration value for newly added cache items.\n+    *\n+    * Items that are added to the cache without an explicit expiration value (using insert) will be inserted with the default expiration value.\n+    *\n+    * If the specified default expiration value is None, items inserted by insert will never expire.\n+    **/\n+  def createCache[F[_] : Concurrent : Timer, K, V](defaultExpiration: Option[TimeSpec],\n+                                                   defaultReloadTime: Option[TimeSpec])\n+                                                  (fetch: K => F[V]): F[AutoFetchingCache[F, K, V]] =\n+    for {\n+      valuesRef <- Ref.of[F, Map[K, CacheContent[F, V]]](Map.empty)\n+      reloads <- defaultReloadTime.traverse(timeSpec =>\n+        Ref.of[F, Map[K, Fiber[F, Unit]]](Map.empty).map((timeSpec, _))\n+      )\n+    } yield new AutoFetchingCache[F, K, V](valuesRef, defaultExpiration, reloads, fetch)\n+\n+\n+  /**\n+    * Return the size of the cache, including expired items.\n+    **/\n+  def size[F[_] : Sync, K, V](cache: AutoFetchingCache[F, K, V]): F[Int] =\n+    cache.values.get.map(_.size)\n+\n+  /**\n+    * Return all keys present in the cache, including expired items.\n+    **/\n+  def keys[F[_] : Sync, K, V](cache: AutoFetchingCache[F, K, V]): F[List[K]] =\n+    cache.values.get.map(_.keys.toList)\n+\n+  /**\n+    * Delete an item from the cache. Won't do anything if the item is not present.\n+    **/\n+  def delete[F[_], K, V](cache: AutoFetchingCache[F, K, V])(k: K)\n+                        (implicit S: Sync[F]): F[Unit] =\n+    cache.values.modify(m =>\n+      (m - k, m.get(k) match {\n+        case Some(Fetching(f)) => f.cancel\n+        case _ => S.unit\n+      })) >> cache.reload.traverse { case (_, values) =>\n+      values.modify(m => (m - k,\n+        m.get(k) match {\n+          case Some(f) => f.cancel\n+          case None => S.unit\n+        }\n+      ))\n+    }.void\n+\n+\n+\n+  def cancelReloads[F[_] : Monad, K, V](cache: AutoFetchingCache[F, K, V]): F[Unit] =\n+    cache.reload.map { case (_, values) =>\n+      values.modify(m => (Map.empty,\n+        m.values.toList.traverse[F, Unit](_.cancel).void)\n+      ).flatten\n+    } getOrElse Monad[F].unit\n+\n+  /**\n+    * Insert an item in the cache, using the default expiration value of the cache.\n+    */\n+  private def insert[F[_] : Sync : Timer, K, V](cache: AutoFetchingCache[F, K, V])(k: K, v: V): F[Unit] =\n+    insertWithTimeout(cache)(cache.defaultExpiration)(k, v)\n+\n+\n+  /**\n+    * Insert an item in the cache, with an explicit expiration value.\n+    *\n+    * If the expiration value is None, the item will never expire. The default expiration value of the cache is ignored.\n+    *\n+    * The expiration value is relative to the current clockMonotonic time, i.e. it will be automatically added to the result of clockMonotonic for the supplied unit.\n+    **/\n+\n+  def insertWithTimeout[F[_] : Sync, K, V](cache: AutoFetchingCache[F, K, V])\n+                                          (optionTimeout: Option[TimeSpec])\n+                                          (k: K, v: V)\n+                                          (implicit T: Timer[F]): F[Unit] = {\n+    for {\n+      now <- T.clock.monotonic(NANOSECONDS)\n+      timeout = optionTimeout.map(ts => TimeSpec.unsafeFromNanos(now + ts.nanos))\n+      _ <- cache.values.update(_ + (k -> CacheItem[F, V](v, timeout)))\n+    } yield ()\n+  }\n+\n+\n+  private def insertFetching[F[_], K, V](k: K, cache: AutoFetchingCache[F, K, V])\n+                                        (f: Fiber[F, V]): F[Unit] = {\n+    cache.values.update(_ + (k -> Fetching[F, V](f)))\n+  }\n+\n+\n+  private def isExpired[F[_], A](checkAgainst: TimeSpec, cacheItem: CacheItem[F, A]): Boolean = {\n+    cacheItem.itemExpiration.fold(false) {\n+      case e if e.nanos < checkAgainst.nanos => true\n+      case _ => false\n+    }\n+  }\n+\n+  private def lookupItemSimple[F[_] : Sync, K, V](k: K, c: AutoFetchingCache[F, K, V]): F[Option[CacheContent[F, V]]] =\n+    c.values.get.map(_.get(k))\n+\n+  private def fetchAndInsert[F[_] : Sync : Timer, K, V](k: K, c: AutoFetchingCache[F, K, V]): F[V] =\n+    for {\n+      v <- c.fetch(k)\n+      _ <- insert(c)(k, v)\n+    } yield v\n+\n+\n+  private def autoReload[F[_], K, V](k: K, c: AutoFetchingCache[F, K, V])","path":"src/main/scala/io/chrisdavenport/mules/AutoFetchingCache.scala","position":144,"original_position":144,"commit_id":"0d174820c8ea1668f5c3ebd6ee46e8bfb8d944ff","original_commit_id":"0d174820c8ea1668f5c3ebd6ee46e8bfb8d944ff","user":{"login":"lgirault","id":1417074,"node_id":"MDQ6VXNlcjE0MTcwNzQ=","avatar_url":"https://avatars3.githubusercontent.com/u/1417074?v=4","gravatar_id":"","url":"https://api.github.com/users/lgirault","html_url":"https://github.com/lgirault","followers_url":"https://api.github.com/users/lgirault/followers","following_url":"https://api.github.com/users/lgirault/following{/other_user}","gists_url":"https://api.github.com/users/lgirault/gists{/gist_id}","starred_url":"https://api.github.com/users/lgirault/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lgirault/subscriptions","organizations_url":"https://api.github.com/users/lgirault/orgs","repos_url":"https://api.github.com/users/lgirault/repos","events_url":"https://api.github.com/users/lgirault/events{/privacy}","received_events_url":"https://api.github.com/users/lgirault/received_events","type":"User","site_admin":false},"body":"this function setup what I understand being a [refresh](https://github.com/google/guava/wiki/CachesExplained#refresh) behavior. It will fetch a newer value every reload period. After writing it I realized I must add the capacity to configure an upper bound of the number of reload that can be active concurrently. I'll try to add it asap but it will change this part of the code quiet a lot I think.\r\nI have a problem that forces me to check if a previous value is already set : I cannot check the existence of the key and modify the map atomically. Hence if a given key is fetched several times on the time of its first creation, several check can return true.","created_at":"2018-10-01T21:18:30Z","updated_at":"2018-10-01T21:18:30Z","html_url":"https://github.com/ChristopherDavenport/mules/pull/11#discussion_r221760015","pull_request_url":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/11","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/comments/221760015"},"html":{"href":"https://github.com/ChristopherDavenport/mules/pull/11#discussion_r221760015"},"pull_request":{"href":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/11"}},"in_reply_to_id":221679603},"pull_request":{"url":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/11","id":217793120,"node_id":"MDExOlB1bGxSZXF1ZXN0MjE3NzkzMTIw","html_url":"https://github.com/ChristopherDavenport/mules/pull/11","diff_url":"https://github.com/ChristopherDavenport/mules/pull/11.diff","patch_url":"https://github.com/ChristopherDavenport/mules/pull/11.patch","issue_url":"https://api.github.com/repos/ChristopherDavenport/mules/issues/11","number":11,"state":"open","locked":false,"title":"Add auto fetching cache","user":{"login":"lgirault","id":1417074,"node_id":"MDQ6VXNlcjE0MTcwNzQ=","avatar_url":"https://avatars3.githubusercontent.com/u/1417074?v=4","gravatar_id":"","url":"https://api.github.com/users/lgirault","html_url":"https://github.com/lgirault","followers_url":"https://api.github.com/users/lgirault/followers","following_url":"https://api.github.com/users/lgirault/following{/other_user}","gists_url":"https://api.github.com/users/lgirault/gists{/gist_id}","starred_url":"https://api.github.com/users/lgirault/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lgirault/subscriptions","organizations_url":"https://api.github.com/users/lgirault/orgs","repos_url":"https://api.github.com/users/lgirault/repos","events_url":"https://api.github.com/users/lgirault/events{/privacy}","received_events_url":"https://api.github.com/users/lgirault/received_events","type":"User","site_admin":false},"body":"This is an alternative implementation of a cache with an auto fetching feature","created_at":"2018-09-24T21:25:27Z","updated_at":"2018-10-01T21:18:30Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7502f6bd95ef5e2e1c8ba16ebe3155af510e7cdd","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/11/commits","review_comments_url":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/11/comments","review_comment_url":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ChristopherDavenport/mules/issues/11/comments","statuses_url":"https://api.github.com/repos/ChristopherDavenport/mules/statuses/0d174820c8ea1668f5c3ebd6ee46e8bfb8d944ff","head":{"label":"lgirault:unbound-auto-fetching-cache","ref":"unbound-auto-fetching-cache","sha":"0d174820c8ea1668f5c3ebd6ee46e8bfb8d944ff","user":{"login":"lgirault","id":1417074,"node_id":"MDQ6VXNlcjE0MTcwNzQ=","avatar_url":"https://avatars3.githubusercontent.com/u/1417074?v=4","gravatar_id":"","url":"https://api.github.com/users/lgirault","html_url":"https://github.com/lgirault","followers_url":"https://api.github.com/users/lgirault/followers","following_url":"https://api.github.com/users/lgirault/following{/other_user}","gists_url":"https://api.github.com/users/lgirault/gists{/gist_id}","starred_url":"https://api.github.com/users/lgirault/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lgirault/subscriptions","organizations_url":"https://api.github.com/users/lgirault/orgs","repos_url":"https://api.github.com/users/lgirault/repos","events_url":"https://api.github.com/users/lgirault/events{/privacy}","received_events_url":"https://api.github.com/users/lgirault/received_events","type":"User","site_admin":false},"repo":{"id":150168387,"node_id":"MDEwOlJlcG9zaXRvcnkxNTAxNjgzODc=","name":"mules","full_name":"lgirault/mules","private":false,"owner":{"login":"lgirault","id":1417074,"node_id":"MDQ6VXNlcjE0MTcwNzQ=","avatar_url":"https://avatars3.githubusercontent.com/u/1417074?v=4","gravatar_id":"","url":"https://api.github.com/users/lgirault","html_url":"https://github.com/lgirault","followers_url":"https://api.github.com/users/lgirault/followers","following_url":"https://api.github.com/users/lgirault/following{/other_user}","gists_url":"https://api.github.com/users/lgirault/gists{/gist_id}","starred_url":"https://api.github.com/users/lgirault/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lgirault/subscriptions","organizations_url":"https://api.github.com/users/lgirault/orgs","repos_url":"https://api.github.com/users/lgirault/repos","events_url":"https://api.github.com/users/lgirault/events{/privacy}","received_events_url":"https://api.github.com/users/lgirault/received_events","type":"User","site_admin":false},"html_url":"https://github.com/lgirault/mules","description":null,"fork":true,"url":"https://api.github.com/repos/lgirault/mules","forks_url":"https://api.github.com/repos/lgirault/mules/forks","keys_url":"https://api.github.com/repos/lgirault/mules/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lgirault/mules/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lgirault/mules/teams","hooks_url":"https://api.github.com/repos/lgirault/mules/hooks","issue_events_url":"https://api.github.com/repos/lgirault/mules/issues/events{/number}","events_url":"https://api.github.com/repos/lgirault/mules/events","assignees_url":"https://api.github.com/repos/lgirault/mules/assignees{/user}","branches_url":"https://api.github.com/repos/lgirault/mules/branches{/branch}","tags_url":"https://api.github.com/repos/lgirault/mules/tags","blobs_url":"https://api.github.com/repos/lgirault/mules/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lgirault/mules/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lgirault/mules/git/refs{/sha}","trees_url":"https://api.github.com/repos/lgirault/mules/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lgirault/mules/statuses/{sha}","languages_url":"https://api.github.com/repos/lgirault/mules/languages","stargazers_url":"https://api.github.com/repos/lgirault/mules/stargazers","contributors_url":"https://api.github.com/repos/lgirault/mules/contributors","subscribers_url":"https://api.github.com/repos/lgirault/mules/subscribers","subscription_url":"https://api.github.com/repos/lgirault/mules/subscription","commits_url":"https://api.github.com/repos/lgirault/mules/commits{/sha}","git_commits_url":"https://api.github.com/repos/lgirault/mules/git/commits{/sha}","comments_url":"https://api.github.com/repos/lgirault/mules/comments{/number}","issue_comment_url":"https://api.github.com/repos/lgirault/mules/issues/comments{/number}","contents_url":"https://api.github.com/repos/lgirault/mules/contents/{+path}","compare_url":"https://api.github.com/repos/lgirault/mules/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lgirault/mules/merges","archive_url":"https://api.github.com/repos/lgirault/mules/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lgirault/mules/downloads","issues_url":"https://api.github.com/repos/lgirault/mules/issues{/number}","pulls_url":"https://api.github.com/repos/lgirault/mules/pulls{/number}","milestones_url":"https://api.github.com/repos/lgirault/mules/milestones{/number}","notifications_url":"https://api.github.com/repos/lgirault/mules/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lgirault/mules/labels{/name}","releases_url":"https://api.github.com/repos/lgirault/mules/releases{/id}","deployments_url":"https://api.github.com/repos/lgirault/mules/deployments","created_at":"2018-09-24T21:04:54Z","updated_at":"2018-09-24T21:04:56Z","pushed_at":"2018-09-25T21:43:43Z","git_url":"git://github.com/lgirault/mules.git","ssh_url":"git@github.com:lgirault/mules.git","clone_url":"https://github.com/lgirault/mules.git","svn_url":"https://github.com/lgirault/mules","homepage":null,"size":51,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"series/0.2"}},"base":{"label":"ChristopherDavenport:series/0.2","ref":"series/0.2","sha":"42c0891c50709a9c8f8a010b12775d3820c03bc8","user":{"login":"ChristopherDavenport","id":10272700,"node_id":"MDQ6VXNlcjEwMjcyNzAw","avatar_url":"https://avatars0.githubusercontent.com/u/10272700?v=4","gravatar_id":"","url":"https://api.github.com/users/ChristopherDavenport","html_url":"https://github.com/ChristopherDavenport","followers_url":"https://api.github.com/users/ChristopherDavenport/followers","following_url":"https://api.github.com/users/ChristopherDavenport/following{/other_user}","gists_url":"https://api.github.com/users/ChristopherDavenport/gists{/gist_id}","starred_url":"https://api.github.com/users/ChristopherDavenport/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ChristopherDavenport/subscriptions","organizations_url":"https://api.github.com/users/ChristopherDavenport/orgs","repos_url":"https://api.github.com/users/ChristopherDavenport/repos","events_url":"https://api.github.com/users/ChristopherDavenport/events{/privacy}","received_events_url":"https://api.github.com/users/ChristopherDavenport/received_events","type":"User","site_admin":false},"repo":{"id":134902284,"node_id":"MDEwOlJlcG9zaXRvcnkxMzQ5MDIyODQ=","name":"mules","full_name":"ChristopherDavenport/mules","private":false,"owner":{"login":"ChristopherDavenport","id":10272700,"node_id":"MDQ6VXNlcjEwMjcyNzAw","avatar_url":"https://avatars0.githubusercontent.com/u/10272700?v=4","gravatar_id":"","url":"https://api.github.com/users/ChristopherDavenport","html_url":"https://github.com/ChristopherDavenport","followers_url":"https://api.github.com/users/ChristopherDavenport/followers","following_url":"https://api.github.com/users/ChristopherDavenport/following{/other_user}","gists_url":"https://api.github.com/users/ChristopherDavenport/gists{/gist_id}","starred_url":"https://api.github.com/users/ChristopherDavenport/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ChristopherDavenport/subscriptions","organizations_url":"https://api.github.com/users/ChristopherDavenport/orgs","repos_url":"https://api.github.com/users/ChristopherDavenport/repos","events_url":"https://api.github.com/users/ChristopherDavenport/events{/privacy}","received_events_url":"https://api.github.com/users/ChristopherDavenport/received_events","type":"User","site_admin":false},"html_url":"https://github.com/ChristopherDavenport/mules","description":null,"fork":false,"url":"https://api.github.com/repos/ChristopherDavenport/mules","forks_url":"https://api.github.com/repos/ChristopherDavenport/mules/forks","keys_url":"https://api.github.com/repos/ChristopherDavenport/mules/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ChristopherDavenport/mules/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ChristopherDavenport/mules/teams","hooks_url":"https://api.github.com/repos/ChristopherDavenport/mules/hooks","issue_events_url":"https://api.github.com/repos/ChristopherDavenport/mules/issues/events{/number}","events_url":"https://api.github.com/repos/ChristopherDavenport/mules/events","assignees_url":"https://api.github.com/repos/ChristopherDavenport/mules/assignees{/user}","branches_url":"https://api.github.com/repos/ChristopherDavenport/mules/branches{/branch}","tags_url":"https://api.github.com/repos/ChristopherDavenport/mules/tags","blobs_url":"https://api.github.com/repos/ChristopherDavenport/mules/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ChristopherDavenport/mules/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ChristopherDavenport/mules/git/refs{/sha}","trees_url":"https://api.github.com/repos/ChristopherDavenport/mules/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ChristopherDavenport/mules/statuses/{sha}","languages_url":"https://api.github.com/repos/ChristopherDavenport/mules/languages","stargazers_url":"https://api.github.com/repos/ChristopherDavenport/mules/stargazers","contributors_url":"https://api.github.com/repos/ChristopherDavenport/mules/contributors","subscribers_url":"https://api.github.com/repos/ChristopherDavenport/mules/subscribers","subscription_url":"https://api.github.com/repos/ChristopherDavenport/mules/subscription","commits_url":"https://api.github.com/repos/ChristopherDavenport/mules/commits{/sha}","git_commits_url":"https://api.github.com/repos/ChristopherDavenport/mules/git/commits{/sha}","comments_url":"https://api.github.com/repos/ChristopherDavenport/mules/comments{/number}","issue_comment_url":"https://api.github.com/repos/ChristopherDavenport/mules/issues/comments{/number}","contents_url":"https://api.github.com/repos/ChristopherDavenport/mules/contents/{+path}","compare_url":"https://api.github.com/repos/ChristopherDavenport/mules/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ChristopherDavenport/mules/merges","archive_url":"https://api.github.com/repos/ChristopherDavenport/mules/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ChristopherDavenport/mules/downloads","issues_url":"https://api.github.com/repos/ChristopherDavenport/mules/issues{/number}","pulls_url":"https://api.github.com/repos/ChristopherDavenport/mules/pulls{/number}","milestones_url":"https://api.github.com/repos/ChristopherDavenport/mules/milestones{/number}","notifications_url":"https://api.github.com/repos/ChristopherDavenport/mules/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ChristopherDavenport/mules/labels{/name}","releases_url":"https://api.github.com/repos/ChristopherDavenport/mules/releases{/id}","deployments_url":"https://api.github.com/repos/ChristopherDavenport/mules/deployments","created_at":"2018-05-25T20:34:29Z","updated_at":"2018-09-21T08:32:15Z","pushed_at":"2018-10-01T16:45:15Z","git_url":"git://github.com/ChristopherDavenport/mules.git","ssh_url":"git@github.com:ChristopherDavenport/mules.git","clone_url":"https://github.com/ChristopherDavenport/mules.git","svn_url":"https://github.com/ChristopherDavenport/mules","homepage":null,"size":42,"stargazers_count":7,"watchers_count":7,"language":"Scala","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"archived":false,"open_issues_count":1,"license":null,"forks":2,"open_issues":1,"watchers":7,"default_branch":"series/0.2"}},"_links":{"self":{"href":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/11"},"html":{"href":"https://github.com/ChristopherDavenport/mules/pull/11"},"issue":{"href":"https://api.github.com/repos/ChristopherDavenport/mules/issues/11"},"comments":{"href":"https://api.github.com/repos/ChristopherDavenport/mules/issues/11/comments"},"review_comments":{"href":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/11/comments"},"review_comment":{"href":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ChristopherDavenport/mules/pulls/11/commits"},"statuses":{"href":"https://api.github.com/repos/ChristopherDavenport/mules/statuses/0d174820c8ea1668f5c3ebd6ee46e8bfb8d944ff"}},"author_association":"NONE"}}
{"actor":{"display_login":"lgirault"}}