{"action":"created","issue":{"url":"https://api.github.com/repos/thoughtbot/yesod-auth-oauth2/issues/71","repository_url":"https://api.github.com/repos/thoughtbot/yesod-auth-oauth2","labels_url":"https://api.github.com/repos/thoughtbot/yesod-auth-oauth2/issues/71/labels{/name}","comments_url":"https://api.github.com/repos/thoughtbot/yesod-auth-oauth2/issues/71/comments","events_url":"https://api.github.com/repos/thoughtbot/yesod-auth-oauth2/issues/71/events","html_url":"https://github.com/thoughtbot/yesod-auth-oauth2/issues/71","id":165111440,"number":71,"title":"Encoding/Decoding Extras is a Pain","user":{"login":"jferris","id":7591,"avatar_url":"https://avatars.githubusercontent.com/u/7591?v=3","gravatar_id":"","url":"https://api.github.com/users/jferris","html_url":"https://github.com/jferris","followers_url":"https://api.github.com/users/jferris/followers","following_url":"https://api.github.com/users/jferris/following{/other_user}","gists_url":"https://api.github.com/users/jferris/gists{/gist_id}","starred_url":"https://api.github.com/users/jferris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jferris/subscriptions","organizations_url":"https://api.github.com/users/jferris/orgs","repos_url":"https://api.github.com/users/jferris/repos","events_url":"https://api.github.com/users/jferris/events{/privacy}","received_events_url":"https://api.github.com/users/jferris/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":8,"created_at":"2016-07-12T15:35:44Z","updated_at":"2016-08-30T17:42:43Z","closed_at":null,"body":"We use Yesod's `credsExtra` field on `Creds` to provide user information from remote services. This data is well-formed and includes fields that are likely required for the application, such as the user's email address.\r\n\r\nAfter decoding the remote user into a Haskell value, we de-normalize it into text pairs:\r\n\r\n``` haskell\r\ncredsExtra :: GithubUser -> [(Text, Text)]\r\ncredsExtra user =\r\n        [ (\"email\", githubUserEmailAddress user)\r\n        , (\"login\", githubUserLogin user)\r\n        , (\"avatar_url\", githubUserAvatarUrl user)\r\n        , (\"access_token\", decodeUtf8 $ accessToken token)\r\n        ]\r\n        ++ maybeExtra \"name\" (githubUserName user)\r\n        ++ maybeExtra \"email\" (githubUserPublicEmail user)\r\n        ++ maybeExtra \"location\" (githubUserLocation user)\r\n\r\nmaybeExtra :: Text -> Maybe Text -> [(Text, Text)]\r\nmaybeExtra k (Just v) = [(k, v)]\r\nmaybeExtra _ Nothing  = []\r\n```\r\n\r\nWe write a function like that for each service.\r\n\r\nOn the application side, we pairs the text pairs back into a Haskell value:\r\n\r\n``` haskell\r\ngithubProfile :: [(Text, Text)] -> Either Text Profile\r\ngithubProfile extra = Profile\r\n    <$> (lookupExtra \"name\" extra <|> lookupExtra \"login\" extra)\r\n    <*> lookupExtra \"email\" extra\r\n\r\nlookupExtra :: Text -> [(Text, b)] -> Either Text b\r\nlookupExtra k extra =\r\n    maybe (Left $ \"missing key \" ++ k) Right $ lookup k extra\r\n```\r\n\r\nThe application needs to write a mapping function like this for each service.\r\n\r\nThere are several issues with this approach:\r\n\r\n1. It requires a large amount of copy-and-paste boilerplate for each new service we support.\r\n2. It requires a large amount of boilerplate in the application.\r\n3. Each text key is a value that can be misspelled and break the application. The application will compile successfully but not work.\r\n4. If we change keys in any way on our side, we risk breaking users' applications without breaking the build.\r\n5. The process of discovering which keys are available is poor, because you get an anonymous pair of `Text` values instead of a well-formed Haskell value.\r\n\r\nI'd like to do a larger update that improves this experience."},"comment":{"url":"https://api.github.com/repos/thoughtbot/yesod-auth-oauth2/issues/comments/243520195","html_url":"https://github.com/thoughtbot/yesod-auth-oauth2/issues/71#issuecomment-243520195","issue_url":"https://api.github.com/repos/thoughtbot/yesod-auth-oauth2/issues/71","id":243520195,"user":{"login":"pbrisbin","id":50812,"avatar_url":"https://avatars.githubusercontent.com/u/50812?v=3","gravatar_id":"","url":"https://api.github.com/users/pbrisbin","html_url":"https://github.com/pbrisbin","followers_url":"https://api.github.com/users/pbrisbin/followers","following_url":"https://api.github.com/users/pbrisbin/following{/other_user}","gists_url":"https://api.github.com/users/pbrisbin/gists{/gist_id}","starred_url":"https://api.github.com/users/pbrisbin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pbrisbin/subscriptions","organizations_url":"https://api.github.com/users/pbrisbin/orgs","repos_url":"https://api.github.com/users/pbrisbin/repos","events_url":"https://api.github.com/users/pbrisbin/events{/privacy}","received_events_url":"https://api.github.com/users/pbrisbin/received_events","type":"User","site_admin":false},"created_at":"2016-08-30T17:42:43Z","updated_at":"2016-08-30T17:42:43Z","body":"I see, apologies for not having this all in my head more clearly; it's been a while since I've worked in this code base. I think I'll have to give this some more thought."}}
{"actor":{"display_login":"pbrisbin"}}