{"action":"created","comment":{"url":"https://api.github.com/repos/hudl/Mjolnir/pulls/comments/46538441","id":46538441,"diff_hunk":"@@ -12,5 +12,11 @@ internal static class ConcurrentDictionaryExtensions\n             var lazy = dictionary.GetOrAdd(key, new Lazy<V>(() => valueFactory(key), LazyThreadSafetyMode.PublicationOnly));","path":"Hudl.Mjolnir/Util/ConcurrentDictionaryExtensions.cs","position":1,"original_position":1,"commit_id":"5aa2a44daaf6f5083d68690bbcd2b207dd4a8010","original_commit_id":"5aa2a44daaf6f5083d68690bbcd2b207dd4a8010","user":{"login":"lewislabs","id":12956835,"avatar_url":"https://avatars.githubusercontent.com/u/12956835?v=3","gravatar_id":"","url":"https://api.github.com/users/lewislabs","html_url":"https://github.com/lewislabs","followers_url":"https://api.github.com/users/lewislabs/followers","following_url":"https://api.github.com/users/lewislabs/following{/other_user}","gists_url":"https://api.github.com/users/lewislabs/gists{/gist_id}","starred_url":"https://api.github.com/users/lewislabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lewislabs/subscriptions","organizations_url":"https://api.github.com/users/lewislabs/orgs","repos_url":"https://api.github.com/users/lewislabs/repos","events_url":"https://api.github.com/users/lewislabs/events{/privacy}","received_events_url":"https://api.github.com/users/lewislabs/received_events","type":"User","site_admin":false},"body":"Maybe call the method below? Also I think we spoke about `PublicationOnly` before, should we make the default `ExecutionAndPublication`, since the most common use case is to not allow the `valueFactory` to be called more than once. ","created_at":"2015-12-03T11:04:57Z","updated_at":"2015-12-03T11:04:57Z","html_url":"https://github.com/hudl/Mjolnir/pull/46#discussion_r46538441","pull_request_url":"https://api.github.com/repos/hudl/Mjolnir/pulls/46","_links":{"self":{"href":"https://api.github.com/repos/hudl/Mjolnir/pulls/comments/46538441"},"html":{"href":"https://github.com/hudl/Mjolnir/pull/46#discussion_r46538441"},"pull_request":{"href":"https://api.github.com/repos/hudl/Mjolnir/pulls/46"}}},"pull_request":{"url":"https://api.github.com/repos/hudl/Mjolnir/pulls/46","id":50770205,"html_url":"https://github.com/hudl/Mjolnir/pull/46","diff_url":"https://github.com/hudl/Mjolnir/pull/46.diff","patch_url":"https://github.com/hudl/Mjolnir/pull/46.patch","issue_url":"https://api.github.com/repos/hudl/Mjolnir/issues/46","number":46,"state":"open","locked":false,"title":"Rework API to better work with async and sync calls","user":{"login":"robhruska","id":1224017,"avatar_url":"https://avatars.githubusercontent.com/u/1224017?v=3","gravatar_id":"","url":"https://api.github.com/users/robhruska","html_url":"https://github.com/robhruska","followers_url":"https://api.github.com/users/robhruska/followers","following_url":"https://api.github.com/users/robhruska/following{/other_user}","gists_url":"https://api.github.com/users/robhruska/gists{/gist_id}","starred_url":"https://api.github.com/users/robhruska/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/robhruska/subscriptions","organizations_url":"https://api.github.com/users/robhruska/orgs","repos_url":"https://api.github.com/users/robhruska/repos","events_url":"https://api.github.com/users/robhruska/events{/privacy}","received_events_url":"https://api.github.com/users/robhruska/received_events","type":"User","site_admin":false},"body":"**These changes will all be backwards compatible**, so this ought to result in a minor version update where clients can move code from the old `Command` over to the two new types at their leisure. Not sure if `Command` will get an `[Obsolete]` tag yet, though.\r\n\r\nFor an example of some of the API changes, check out [`Example.cs`](https://github.com/hudl/Mjolnir/blob/ReworkSyncAndAsyncApi/Hudl.Mjolnir.Tests/Example.cs)\r\n\r\n**Code Review**: Two good entry points for review are [`CommandInvoker.cs`](https://github.com/hudl/Mjolnir/blob/ReworkSyncAndAsyncApi/Hudl.Mjolnir/Command/CommandInvoker.cs) and [`BaseCommand.cs`](https://github.com/hudl/Mjolnir/blob/ReworkSyncAndAsyncApi/Hudl.Mjolnir/Command/BaseCommand.cs). `CommandInvoker` delegates to a couple other invokers (`BulkheadInvoker` and `BreakerInvoker`) that could use review as well.\r\n### Separate sync/async Command APIs\r\n\r\nInstead of `Invoke()` and `InvokeAsync()` on `Command`, implementations need to inherit from `SyncCommand` or `AsyncCommand`. This avoids the forced async-to-sync that's built into the library, and avoids the need for callers to make problematic async-to-sync conversions. Though convenient for callers, it's often a source of confusion, and also means that Mjolnir is more inefficient with `async` than it ought to be.\r\n\r\nCurrently, Commands are used like this:\r\n\r\n```csharp\r\n\r\n// MyCommand : Command<TResult>\r\n\r\nvar command = new MyCommand(foo, bar, baz);\r\nvar value = await command.InvokeAsync();\r\n// or\r\nvar value = command.Invoke(); // Sync equivalent.\r\n```\r\n\r\nThis PR offers an alternative API:\r\n\r\n```csharp\r\nvar invoker = new CommandInvoker(); // Injectable via ICommandInvoker\r\n\r\n// MyAsyncCommand : AsyncCommand<TResult>\r\n\r\nvar command = new MyAsyncCommand(foo, bar, baz);\r\nvar value = await invoker.InvokeThrowAsync(command, 1000); // Throws exceptions\r\n\r\nvar command = new MyAsyncCommand(fool, bar, baz);\r\nvar wrapped = await invoker.InvokeReturnAsync(command, 1000); // Wraps exceptions and returns\r\nvar value = wrapped.Value;\r\n\r\n// MySyncCommand : SyncCommand<TResult>\r\n\r\nvar command = new MySyncCommand(foo, bar, baz);\r\nvar result = invoker.InvokeReturn(command);\r\nvar value = result.Value;\r\n```\r\n\r\n**`Throw` and `Return` variants**\r\n\r\nThe `Return` variants will catch and wrap Exceptions, preferring to always return a result to the caller (even when errors occur). Since Mjolnir's intent is to introduce fast failure to avoid failure cascading in unpredictable ways, it's useful for callers to handle that failure in ways beyond just re-throwing the exception upward. This *could* be handled by the caller using `try/catch`, but baking it into the Mjolnir API helps callers realize that they need to **think about failure** and make conscious decisions about how to handle it.\r\n\r\nThe `Return` variant returns a `CommandResult<TResult>` instead of just `TResult`. The wrapper contains the result or the causing exception if the command failed. The `Throw` method overrides don't return a wrapped result since it would add an unnecessary \"unwrapping\" step for callers.\r\n\r\n**Timeout override**\r\n\r\nDefault and configured timeouts still apply, but callers can also provide a per-call timeout if they desire more lenient/rigid SLAs.\r\n\r\n**Improved testability**\r\n\r\nThis will also make commands more unit-testable, since `ICommandInvoker` will be injectable (where injecting Command behavior is currently a challenge).\r\n\r\n### Use semaphore bulkheads (without queues) by default\r\n\r\nI'm not sold on the use of thread pools as a bulkhead. Their strongest advantage is the ability to have a small queue in front that allows for absorbing some latency and bursts. However, there are a number of disadvantages.\r\n\r\n- *They're unoptimal for `async`.* Fully async code means that threads are returned to the thread pool until the async work is done. Introducing an entire thread to \"wrap\" that and limit concurrency feels nonsensical to me.\r\n- *SmartThreadPool is a bit rough.* We currently have to deliver our own forked copy of STP with Mjolnir because the main repo has gone stale and not merged PRs with functionality that we need. We've also had struggles with context flowing across STP threads, and the library isn't really well-equipped for `async` work.\r\n- *Queuing and pooling makes Mjolnir harder to reason about.* Since the message gets queued off the current thread, keeping track of it and getting the result back to the appropriate context is a bit tricky. It works, but means a fair amount of hackery around delegates and WorkItems. It also means that we've forced both synchronous and async code down the same code path, which makes the async bits a lot heavier (see bullet 1).\r\n\r\nI'd like to try using semaphores as the default bulkhead. It does mean (for now, at least) getting rid of the queues, but they can be re-introduced if needed in the future.\r\n\r\n### Better interface for hooking into metrics\r\n\r\nThe existing interface ([`IStats`](https://github.com/hudl/Mjolnir/blob/master/Hudl.Mjolnir/External/IStats.cs) <sup>[[docs]](https://github.com/hudl/Mjolnir/blob/master/Hudl.Mjolnir/External/stats-list.md)</sup>) is pretty vague and free-form, and makes it difficult to adapt events into collectors that use standardized metric types like timers and meters.\r\n\r\nA new interface, [`IMetricEvents`](https://github.com/hudl/Mjolnir/blob/41bd87c13f1612ab290ba268f185a453ceccd5bf/Hudl.Mjolnir/External/IMetricEvents.cs), is available that should make that easier. It provides specific methods for events that occur, and recommends a metric type for each. A couple of example methods:\r\n\r\n```csharp\r\nvoid CommandInvoked(string commandName, double invokeMillis, double executeMillis, string status, string failureAction);\r\nvoid RejectedByBulkhead(string bulkheadName, string commandName);\r\nvoid BulkheadConfigGauge(string bulkheadName, string bulkheadType, int maxConcurrent);\r\n```\r\n\r\nThe original `IStats` were targeted both at profiling and operational events; `IMetricEvents` focuses less on method profiling and more on relevant bulkhead, breaker, and command events. Parity between `IStats` and `IMetricEvents` wasn't a goal, so they're fairly different.\r\n\r\nThe new `BaseCommand`/`CommandInvoker` pipeline *will not* publish the old `IStats` events.\r\n\r\nThe adapter we use will also be made available (once complete).\r\n\r\n### For Release Notes\r\n\r\nA list of items that should end up in the release notes.\r\n\r\n**Differences between old and new Command**\r\n\r\n- The `Invoke` INFO log is `Invoke` for both sync and async calls (`Invoke Command={0} Breaker={1} Pool={2} Timeout={3}`). In the old Command, it's `InvokeAsync` for the async call.\r\n- Exceptions aren't wrapped in a `CommandFailedException`. Instead, the root cause exception is simply rethrown with some Command-specific data attached to it.\r\n- \"Pool\" is now \"Bulkhead\" in a handful of configs, logs, and properties. If porting old Commands over to the new base classes, be sure to duplicate any configured pool values with the \"bulkhead\" configuration key.\r\n- New commands (sync/async) have a timeout configuration key that starts with `mjolnir.command.` The old commands were inconsistent with the rest of the library and just started with `command.`. Examples:\r\n    - Old: `command.my-group.MyCommand.Timeout=1000`\r\n    - New: `mjolnir.command.my-group.MyCommand.Timeout=1000`\r\n\r\n### Possible Remaining Work\r\n\r\n- [ ] `[Command]` support for sync and async commands\r\n    - This may go into a future release that also involves pulling the invoker out of the main Mjolnir project and into its own (e.g. `Hudl.Mjolnir.Interceptor`), which would help get rid of a dependency on `Castle.Core`.\r\n- [ ] Ability to set separate bulkhead and breaker groups on new commands\r\n- [ ] Support for returning `Task` (non-generic) from execute methods\r\n    - This is tricky - if anyone's got suggestions on how to do it without heavily duplicating code paths, I'd love some ideas. Everything I've tried ends up kind of lame.\r\n- [ ] Ability to roll out commands in a \"dry-run\" or \"measure-only\" mode, where nothing will get rejected, but MetricEvents will still fire. This would allow tuning before committing to a set of failure thresholds.","created_at":"2015-11-16T04:22:33Z","updated_at":"2015-12-03T11:04:57Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8ab964464f4f894dffb7ccf257cdae1e7a384f98","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/hudl/Mjolnir/pulls/46/commits","review_comments_url":"https://api.github.com/repos/hudl/Mjolnir/pulls/46/comments","review_comment_url":"https://api.github.com/repos/hudl/Mjolnir/pulls/comments{/number}","comments_url":"https://api.github.com/repos/hudl/Mjolnir/issues/46/comments","statuses_url":"https://api.github.com/repos/hudl/Mjolnir/statuses/5aa2a44daaf6f5083d68690bbcd2b207dd4a8010","head":{"label":"hudl:ReworkSyncAndAsyncApi","ref":"ReworkSyncAndAsyncApi","sha":"5aa2a44daaf6f5083d68690bbcd2b207dd4a8010","user":{"login":"hudl","id":123340,"avatar_url":"https://avatars.githubusercontent.com/u/123340?v=3","gravatar_id":"","url":"https://api.github.com/users/hudl","html_url":"https://github.com/hudl","followers_url":"https://api.github.com/users/hudl/followers","following_url":"https://api.github.com/users/hudl/following{/other_user}","gists_url":"https://api.github.com/users/hudl/gists{/gist_id}","starred_url":"https://api.github.com/users/hudl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hudl/subscriptions","organizations_url":"https://api.github.com/users/hudl/orgs","repos_url":"https://api.github.com/users/hudl/repos","events_url":"https://api.github.com/users/hudl/events{/privacy}","received_events_url":"https://api.github.com/users/hudl/received_events","type":"Organization","site_admin":false},"repo":{"id":15709992,"name":"Mjolnir","full_name":"hudl/Mjolnir","owner":{"login":"hudl","id":123340,"avatar_url":"https://avatars.githubusercontent.com/u/123340?v=3","gravatar_id":"","url":"https://api.github.com/users/hudl","html_url":"https://github.com/hudl","followers_url":"https://api.github.com/users/hudl/followers","following_url":"https://api.github.com/users/hudl/following{/other_user}","gists_url":"https://api.github.com/users/hudl/gists{/gist_id}","starred_url":"https://api.github.com/users/hudl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hudl/subscriptions","organizations_url":"https://api.github.com/users/hudl/orgs","repos_url":"https://api.github.com/users/hudl/repos","events_url":"https://api.github.com/users/hudl/events{/privacy}","received_events_url":"https://api.github.com/users/hudl/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/hudl/Mjolnir","description":"Isolation and fail-fast fault tolerance library, modeled after https://github.com/Netflix/Hystrix.","fork":false,"url":"https://api.github.com/repos/hudl/Mjolnir","forks_url":"https://api.github.com/repos/hudl/Mjolnir/forks","keys_url":"https://api.github.com/repos/hudl/Mjolnir/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hudl/Mjolnir/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hudl/Mjolnir/teams","hooks_url":"https://api.github.com/repos/hudl/Mjolnir/hooks","issue_events_url":"https://api.github.com/repos/hudl/Mjolnir/issues/events{/number}","events_url":"https://api.github.com/repos/hudl/Mjolnir/events","assignees_url":"https://api.github.com/repos/hudl/Mjolnir/assignees{/user}","branches_url":"https://api.github.com/repos/hudl/Mjolnir/branches{/branch}","tags_url":"https://api.github.com/repos/hudl/Mjolnir/tags","blobs_url":"https://api.github.com/repos/hudl/Mjolnir/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hudl/Mjolnir/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hudl/Mjolnir/git/refs{/sha}","trees_url":"https://api.github.com/repos/hudl/Mjolnir/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hudl/Mjolnir/statuses/{sha}","languages_url":"https://api.github.com/repos/hudl/Mjolnir/languages","stargazers_url":"https://api.github.com/repos/hudl/Mjolnir/stargazers","contributors_url":"https://api.github.com/repos/hudl/Mjolnir/contributors","subscribers_url":"https://api.github.com/repos/hudl/Mjolnir/subscribers","subscription_url":"https://api.github.com/repos/hudl/Mjolnir/subscription","commits_url":"https://api.github.com/repos/hudl/Mjolnir/commits{/sha}","git_commits_url":"https://api.github.com/repos/hudl/Mjolnir/git/commits{/sha}","comments_url":"https://api.github.com/repos/hudl/Mjolnir/comments{/number}","issue_comment_url":"https://api.github.com/repos/hudl/Mjolnir/issues/comments{/number}","contents_url":"https://api.github.com/repos/hudl/Mjolnir/contents/{+path}","compare_url":"https://api.github.com/repos/hudl/Mjolnir/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hudl/Mjolnir/merges","archive_url":"https://api.github.com/repos/hudl/Mjolnir/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hudl/Mjolnir/downloads","issues_url":"https://api.github.com/repos/hudl/Mjolnir/issues{/number}","pulls_url":"https://api.github.com/repos/hudl/Mjolnir/pulls{/number}","milestones_url":"https://api.github.com/repos/hudl/Mjolnir/milestones{/number}","notifications_url":"https://api.github.com/repos/hudl/Mjolnir/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hudl/Mjolnir/labels{/name}","releases_url":"https://api.github.com/repos/hudl/Mjolnir/releases{/id}","created_at":"2014-01-07T16:36:12Z","updated_at":"2015-12-02T22:22:53Z","pushed_at":"2015-12-01T21:19:59Z","git_url":"git://github.com/hudl/Mjolnir.git","ssh_url":"git@github.com:hudl/Mjolnir.git","clone_url":"https://github.com/hudl/Mjolnir.git","svn_url":"https://github.com/hudl/Mjolnir","homepage":"","size":1025,"stargazers_count":37,"watchers_count":37,"language":"C#","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":8,"mirror_url":null,"open_issues_count":11,"forks":8,"open_issues":11,"watchers":37,"default_branch":"master"}},"base":{"label":"hudl:next","ref":"next","sha":"cfc517ee583cc7efb349caba53c1652cb4f5dfc3","user":{"login":"hudl","id":123340,"avatar_url":"https://avatars.githubusercontent.com/u/123340?v=3","gravatar_id":"","url":"https://api.github.com/users/hudl","html_url":"https://github.com/hudl","followers_url":"https://api.github.com/users/hudl/followers","following_url":"https://api.github.com/users/hudl/following{/other_user}","gists_url":"https://api.github.com/users/hudl/gists{/gist_id}","starred_url":"https://api.github.com/users/hudl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hudl/subscriptions","organizations_url":"https://api.github.com/users/hudl/orgs","repos_url":"https://api.github.com/users/hudl/repos","events_url":"https://api.github.com/users/hudl/events{/privacy}","received_events_url":"https://api.github.com/users/hudl/received_events","type":"Organization","site_admin":false},"repo":{"id":15709992,"name":"Mjolnir","full_name":"hudl/Mjolnir","owner":{"login":"hudl","id":123340,"avatar_url":"https://avatars.githubusercontent.com/u/123340?v=3","gravatar_id":"","url":"https://api.github.com/users/hudl","html_url":"https://github.com/hudl","followers_url":"https://api.github.com/users/hudl/followers","following_url":"https://api.github.com/users/hudl/following{/other_user}","gists_url":"https://api.github.com/users/hudl/gists{/gist_id}","starred_url":"https://api.github.com/users/hudl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hudl/subscriptions","organizations_url":"https://api.github.com/users/hudl/orgs","repos_url":"https://api.github.com/users/hudl/repos","events_url":"https://api.github.com/users/hudl/events{/privacy}","received_events_url":"https://api.github.com/users/hudl/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/hudl/Mjolnir","description":"Isolation and fail-fast fault tolerance library, modeled after https://github.com/Netflix/Hystrix.","fork":false,"url":"https://api.github.com/repos/hudl/Mjolnir","forks_url":"https://api.github.com/repos/hudl/Mjolnir/forks","keys_url":"https://api.github.com/repos/hudl/Mjolnir/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hudl/Mjolnir/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hudl/Mjolnir/teams","hooks_url":"https://api.github.com/repos/hudl/Mjolnir/hooks","issue_events_url":"https://api.github.com/repos/hudl/Mjolnir/issues/events{/number}","events_url":"https://api.github.com/repos/hudl/Mjolnir/events","assignees_url":"https://api.github.com/repos/hudl/Mjolnir/assignees{/user}","branches_url":"https://api.github.com/repos/hudl/Mjolnir/branches{/branch}","tags_url":"https://api.github.com/repos/hudl/Mjolnir/tags","blobs_url":"https://api.github.com/repos/hudl/Mjolnir/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hudl/Mjolnir/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hudl/Mjolnir/git/refs{/sha}","trees_url":"https://api.github.com/repos/hudl/Mjolnir/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hudl/Mjolnir/statuses/{sha}","languages_url":"https://api.github.com/repos/hudl/Mjolnir/languages","stargazers_url":"https://api.github.com/repos/hudl/Mjolnir/stargazers","contributors_url":"https://api.github.com/repos/hudl/Mjolnir/contributors","subscribers_url":"https://api.github.com/repos/hudl/Mjolnir/subscribers","subscription_url":"https://api.github.com/repos/hudl/Mjolnir/subscription","commits_url":"https://api.github.com/repos/hudl/Mjolnir/commits{/sha}","git_commits_url":"https://api.github.com/repos/hudl/Mjolnir/git/commits{/sha}","comments_url":"https://api.github.com/repos/hudl/Mjolnir/comments{/number}","issue_comment_url":"https://api.github.com/repos/hudl/Mjolnir/issues/comments{/number}","contents_url":"https://api.github.com/repos/hudl/Mjolnir/contents/{+path}","compare_url":"https://api.github.com/repos/hudl/Mjolnir/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hudl/Mjolnir/merges","archive_url":"https://api.github.com/repos/hudl/Mjolnir/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hudl/Mjolnir/downloads","issues_url":"https://api.github.com/repos/hudl/Mjolnir/issues{/number}","pulls_url":"https://api.github.com/repos/hudl/Mjolnir/pulls{/number}","milestones_url":"https://api.github.com/repos/hudl/Mjolnir/milestones{/number}","notifications_url":"https://api.github.com/repos/hudl/Mjolnir/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hudl/Mjolnir/labels{/name}","releases_url":"https://api.github.com/repos/hudl/Mjolnir/releases{/id}","created_at":"2014-01-07T16:36:12Z","updated_at":"2015-12-02T22:22:53Z","pushed_at":"2015-12-01T21:19:59Z","git_url":"git://github.com/hudl/Mjolnir.git","ssh_url":"git@github.com:hudl/Mjolnir.git","clone_url":"https://github.com/hudl/Mjolnir.git","svn_url":"https://github.com/hudl/Mjolnir","homepage":"","size":1025,"stargazers_count":37,"watchers_count":37,"language":"C#","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":8,"mirror_url":null,"open_issues_count":11,"forks":8,"open_issues":11,"watchers":37,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/hudl/Mjolnir/pulls/46"},"html":{"href":"https://github.com/hudl/Mjolnir/pull/46"},"issue":{"href":"https://api.github.com/repos/hudl/Mjolnir/issues/46"},"comments":{"href":"https://api.github.com/repos/hudl/Mjolnir/issues/46/comments"},"review_comments":{"href":"https://api.github.com/repos/hudl/Mjolnir/pulls/46/comments"},"review_comment":{"href":"https://api.github.com/repos/hudl/Mjolnir/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/hudl/Mjolnir/pulls/46/commits"},"statuses":{"href":"https://api.github.com/repos/hudl/Mjolnir/statuses/5aa2a44daaf6f5083d68690bbcd2b207dd4a8010"}}}}