Skip to content

Commit

Permalink
rest of the concept exercise
Browse files Browse the repository at this point in the history
  • Loading branch information
jiegillet committed May 20, 2024
1 parent 9b0e9b0 commit ad2ee43
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 30 deletions.
82 changes: 78 additions & 4 deletions exercises/concept/githup-api/.docs/hints.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,98 @@

## General

- General hint 1
- Make sure to read the documentation for [decoders][decode] and [encoders][encode]
- The [GitHub documentation][github] that inspired this exercise contain more information about the JSON objects
- You should be able to use [`Decode.decodeString`][decode-decodeString] to test your decoders on arbitrary input

## 1. ID, please!

- Task 1 hint
The task can be solved with the following functions:

- [`int`][decode-int]
- [`field`][decode-field]

## 2. What's my name again?

There are two main ways of solving the task:

- using [`maybe`][decode-maybe]
- using [`oneOf`][decode-oneOf] with two possible decoders, one using a combination of [`field`][decode-field], [`string`][decode-string] [`map`][decode-map], and one using [`succeed`][decode-succeed]

## 3. User? I hardly know her!

The task can be solved with the following functions:

- `decodeId` and `decodeName`
- `User` used as a type constructor
- [`map5`][decode-map5]
- [`field`][decode-field]
- [`int`][decode-int]
- [`string`][decode-string]
- [`bool`][decode-bool]

## 4. I'm a strong independent comment

The task can be solved with the following functions:

- [`field`][decode-field]
- [`int`][decode-int]
- [`nullable`][decode-nullable]

## 5. Side hustle

The task can be solved with the following functions:

- [`field`][decode-field]
- [`string`][decode-string]
- [`andThen`][decode-andThen]
- a case statement checking the content of the string being decoded
- [`succeed`][decode-succeed]
- [`fail`][decode-fail]

## 6. Links awakening

The task can be solved with the following functions:

- [`field`][decode-field]
- [`dict`][decode-dict]
- [`string`][decode-string]

## 7. Yes comments

## 8. Back to square one
`decodeComment` can be solved with the following functions:

- `decodeId`, `decodePullRequestReviewId`, `decodeUser`, `decodeSide`, `decodeLinks`
- `Comment` used as a type constructor
- [`6`][decode-6]
- [`field`][decode-field]
- [`string`][decode-string]

`decodeComments` can be solved with the following functions:

- `decodeComment`
- [`list`][decode-list]

## 8. It all ties together

All the functions required for this task are defines in [the documentation for encoders][encode], and potentially some from the [`Maybe` module][maybe]

[resource]: https://some.resource/url
[github]: https://docs.github.com/en/rest/pulls/comments?apiVersion=2022-11-28
[decode]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode
[decode-decodeString]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#decodeString
[decode-string]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#string
[decode-maybe]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#maybe
[decode-int]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#int
[decode-bool]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#bool
[decode-field]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#field
[decode-map]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#map
[decode-succeed]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#succeed
[decode-map5]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#map5
[decode-map6]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#map6
[decode-andThen]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#andThen
[decode-oneOf]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#oneOf
[decode-fail]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#fail
[decode-nullable]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#nullable
[decode-dict]: https://package.elm-lang.org/packages/elm/json/latest/Json-Decode#dict
[encode]: https://package.elm-lang.org/packages/elm/json/latest/Json-Encode
[maybe]: https://package.elm-lang.org/packages/elm/core/latest/Maybe
141 changes: 136 additions & 5 deletions exercises/concept/githup-api/.docs/instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ Here is a example of the payload being served when you ask the server for the li
"user": {
"id": 101,
"login": "octodog",
"name": "Otto Doge",
"avatar_url": "https://githup.com/images/error/octodog_happy.gif",
"site_admin": false
},
Expand All @@ -34,29 +33,161 @@ Here is a example of the payload being served when you ask the server for the li
"href": "https://api.githup.com/repos/octodog/Hello-World/pulls/1"
}
}
},
{
"id": 11,
"pull_request_review_id": null,
"user": {
"id": 2,
"login": "hexacat",
"name": "Alex Kate",
"avatar_url": "https://githup.com/images/error/hexacat_happy.gif",
"site_admin": true
},
"body": "Amazing stuff!",
"side": "LEFT",
"_links": {
"self": {
"href": "https://api.githup.com/repos/octodog/Hello-World/pulls/comments/1"
}
}
}
]
```

Of course, the frontend will be done in Elm, but first you need to figure out what those decoders/encoders are all about.
Of course, the frontend will be done in Elm, but first you need to figure out what those notorious decoders/encoders are all about.

```exercism/note
Humorous story aside, the exercise requirements were taken directly from GitHub's REST API endpoints for pull request review comments.
We removed some fields from the JSON schemas for the sake of simplicity, but this is as close to real-world applications as it gets.
Humorous story aside, the exercise requirements are taken directly from GitHub's REST API endpoints for pull request review comments.
Some field were removed from the JSON schemas for the sake of simplicity, but this is as close to real-world applications as it gets.
```

## 1. ID, please!

Your eagle eye has noticed that both comments and users have an `id` field, perfect place to start.

Define `decodeId` so that it can decode a JSON object, with an integer `id`.

```elm
Decode.decodeString decodeId """{id: 10}"""
--> Ok 10
```

## 2. What's my name again?

You notice in the example above that not all users have a name.

Define `decodeName` so that it can decode a string in the `name` field if there is one.
No name? No problem, simply return `Nothing`.
This decoder should never fail.

```elm
Decode.decodeString decodeName """{"id": 10, "name": "Otto"}"""
--> Ok (Just Otto)

Decode.decodeString decodeName """null"""
--> Ok Nothing
```

## 3. User? I hardly know her!

We are pretty much halfway there for decoding users, let's get it done.

Define `decodeUser` so that it can decode a JSON user object.
Make sure to use `decodeId` and `decodeName` in `decodeUser`.

```elm
Decode.decodeString decodeUser
"""
{
"id": 101,
"login": "octodog",
"avatar_url": "https://githup.com/images/error/octodog_happy.gif",
"site_admin": false
}
"""
--> Ok
--> { id = 101
--> , name = Nothing
--> , login = "octodog"
--> , avatarUrl = "https://githup.com/images/error/octodog_happy.gif
--> , siteAdmin = False
--> }
```

## 4. I'm a strong independent comment

Some comments in a pull review are standalone, and therefore do not have a `pull_request_review_id`.
However, the specification mention that in such a case, there should still be a `pull_request_review_id` field, but the value should be `null`, the poor man's `Nothing`.

Define `decodePullRequestReviewId` so that it can decode an integer in the `pull_request_review_id` field if there is one.
If there is no integer, there should be a `null`, if there is no `null` the decoder should fail.

```elm
Decode.decodeString decodePullRequestReviewId """{"pull_request_review_id": 3}"""
--> Ok (Just 3)

Decode.decodeString decodePullRequestReviewId """{"pull_request_review_id": null}"""
--> Ok Nothing

Decode.decodeString decodePullRequestReviewId """{"id": 3}"""
--> Err ...
```

## 5. Side hustle

Next is this interesting looking `side` field.
In a pull review, you can comment on code that was removed (on the `Left` side of the screen) or on code that was added (on the `Right` side of the screen).

Define `decodeSide` so that it can decode either `"LEFT"` or `"RIGHT"` in the `side` field and match it to its corresponding type variant.

```elm
Decode.decodeString decodeSide """{"side": "LEFT"}"""
--> Ok Left

Decode.decodeString decodeSide """{"side": "middle?"}"""
--> Err ...
```

## 6. Links awakening

Each comment has a set of links, which doesn't seem to be consistent across comments.

Define `decodeLinks` so that it can decode a collection of URLs in the `_links` field.
The names and number of links can vary, make sure you can account for that.
The links themselves are JSON objects that should always have a `href` field.

```elm
Decode.decodeString decodeLinks """
{
"_links": {
"self": { "href": "https://api.githup.com/repos/octodog/Hello-World/pulls/comments/1" },
"html": { "href": "https://githup.com/octodog/Hello-World/pull/1#discussion-diff-1" },
"pull_request": { "href": "https://api.githup.com/repos/octodog/Hello-World/pulls/1" }
}
}
"""
--> Ok
--> (Dict.fromList
--> [ ( "self", "https://api.githup.com/repos/octodog/Hello-World/pulls/comments/1" )
--> , ( "html", "https://githup.com/octodog/Hello-World/pull/1#discussion-diff-1" )
--> , ( "pull_request", "https://api.githup.com/repos/octodog/Hello-World/pulls/1" )
--> ]
--> )
```

## 7. Yes comments

## 8. Back to square one
Almost there, let's get it done.

Define `decodeComment` and `decodeComments` so that they can decode a single JSON comment object or a list of comments respectively.
Make sure to use all of the functions defined so far in `decodeComment`, with the exception of `decodeName`.
Of course, `decodeComments` should use `decodeComment` as well.

## 8. It all ties together

There is one more feature missing in all of this: being able to send a new comment to the server.
For this, you need achieve the opposite of a decoder, you need to be able to transform an Elm `Comment` into a JSON `Value`.

Define `encodeComment` so that it can encode any valid `Comment`.
The order of the fields doesn't matter, as long as the encoder can produce a value that `decodeComment` can decode.
6 changes: 3 additions & 3 deletions exercises/concept/githup-api/.meta/Exemplar.elm
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ decodeUser =
(Decode.field "site_admin" Decode.bool)


decodepullRequestReviewId : Decoder (Maybe Int)
decodepullRequestReviewId =
decodePullRequestReviewId : Decoder (Maybe Int)
decodePullRequestReviewId =
Decode.field "pull_request_review_id" (Decode.nullable Decode.int)


Expand Down Expand Up @@ -85,7 +85,7 @@ decodeComment : Decoder Comment
decodeComment =
Decode.map6 Comment
decodeId
decodepullRequestReviewId
decodePullRequestReviewId
(Decode.field "user" decodeUser)
(Decode.field "body" Decode.string)
decodeSide
Expand Down
10 changes: 7 additions & 3 deletions exercises/concept/githup-api/.meta/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"authors": [],
"contributors": [],
"authors": [
"jiegillet"
],
"contributors": [
"ceddlyburge"
],
"files": {
"solution": [
"src/GithupApi.elm"
Expand All @@ -12,5 +16,5 @@
".meta/Exemplar.elm"
]
},
"blurb": "Learn this by doing that"
"blurb": "Learn to decode and encode JSON values with Elm"
}
10 changes: 8 additions & 2 deletions exercises/concept/githup-api/.meta/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Students should be able to

- encoder aliases (`array`, `set`)
- more niche decoders (`array`, `keyValuePairs`, `oneOrMore`, `index`, `at`, `value`)
- dealing with `Error`
- dealing with `Error` specifics
- decoding recursive structures (`lazy`, a quick mention is fine)

## Concepts
Expand All @@ -35,11 +35,17 @@ The concept this exercise unlocks is:

- strings
- lists
- tuples
- dict
- records
- maybe
- custom-types
- pattern matching

## Analyzer

- ?
Make sure that

- `decodeUser` uses `decodeId` and `decodeName`
- `decodeComment` uses `decodeId`, `decodePullRequestReviewId`, `decodeUser`, `decodeSide`, `decodeLinks`
- `decodeComments` uses `decodeComment`
6 changes: 3 additions & 3 deletions exercises/concept/githup-api/src/GithupApi.elm
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ decodeUser =
Decode.fail "Please implement decodeUser"


decodepullRequestReviewId : Decoder (Maybe Int)
decodepullRequestReviewId =
Decode.fail "Please implement decodepullRequestReviewId"
decodePullRequestReviewId : Decoder (Maybe Int)
decodePullRequestReviewId =
Decode.fail "Please implement decodePullRequestReviewId"


decodeSide : Decoder Side
Expand Down
Loading

0 comments on commit ad2ee43

Please sign in to comment.