GitHup API

GitHup API

Άσκηση εκμάθησης

Εισαγωγή

JSON

JSON

Το JSON (JavaScript Object Notation) είναι μια μορφή δεδομένων και αρχείων αναγνώσιμη από τον άνθρωπο, που χρησιμοποιείται συνήθως για την ανταλλαγή δεδομένων, ειδικά για εφαρμογές ιστού. Ως εκ τούτου, δεν αποτελεί έκπληξη το γεγονός ότι κατέχει ξεχωριστή θέση στη γλώσσα Elm.

Η Elm παρέχει τα βασικά modules Json.Decode και Json.Encode για την ανάλυση και τη συγγραφή δεδομένων JSON. Οι αποκωδικοποιητές JSON είναι ένας τρόπος να δηλώσεις τι τύπο δεδομένων περιμένεις και σε ποια δομή δεδομένων πρέπει να αντιστοιχιστούν τα δεδομένα.

Ας εισάγουμε μερικά aliases και τύπους από τα modules:

import Json.Decode as Decode exposing (Decoder, Error)
import Json.Encode as Encode exposing (Value)

Χρησιμοποιώντας το Decode.decodeString : Decoder a -> String -> Result Error a με έναν αποκωδικοποιητή και μια συμβολοσειρά JSON, είτε θα πετύχει, αν η συμβολοσειρά συμφωνεί με τις προδιαγραφές του αποκωδικοποιητή, είτε θα αποτύχει. Δεν υπάρχει τρόπος να επιθεωρήσεις τα ακατέργαστα δεδομένα JSON όταν ορίζεις τον αποκωδικοποιητή, κάτι που μπορεί να φαίνεται ασυνήθιστο αν προέρχεσαι από προστακτικές γλώσσες.

Αποκωδικοποιητές βασικών τύπων δεδομένων

Το JSON έχει έναν μικρό αριθμό βασικών τύπων δεδομένων:

  • String: κωδικοποιείται με διπλά εισαγωγικά "hello world"
  • Boolean: είτε true είτε false
  • Number: όπως 34 ή 0.12
  • Null: η απουσία δεδομένων null

Κάθε ένα από αυτά συνδέεται με μια συνάρτηση του Decode:

Decode.decodeString Decode.string """\"hello\""""
    --> Ok "hello"

Decode.decodeString Decode.string """null"""
    --> Err ...

Decode.decodeString Decode.bool """true"""
    --> Ok True

Decode.decodeString Decode.bool """0"""
    --> Err ...

Decode.decodeString Decode.int """12"""
    --> Ok 12

Decode.decodeString Decode.float """3.14"""
    --> Ok 3.14

Decode.decodeString (Decode.null AnyValue) """null"""
    --> Ok AnyValue

Decode.decodeString (Decode.null AnyValue) """true"""
    --> Err ...

Σημείωσε ότι το Decode.null σου επιτρέπει να αποφασίσεις πώς θα μοντελοποιήσεις ένα null στο πρόγραμμά σου, απαιτώντας μια αυθαίρετη τιμή ως όρισμα (ίσως (), Nothing, ή ό,τι ταιριάζει στο πρόγραμμά σου).

Συνδυασμός αποκωδικοποιητών

Το JSON ορίζει επίσης δύο δομές δεδομένων για τη συλλογή βασικών τύπων δεδομένων:

  • Array: μια συλλογή άλλων τύπων δεδομένων [0, null, "none", []]
  • Object: μια συλλογή ζευγών κλειδιού-τιμής {"id": 1, "is_admin": false, "more_info": {}}

Το Decode προσφέρει επίσης συναρτήσεις για την αποκωδικοποίησή τους:

Decode.decodeString (Decode.list Decode.int) """[1, 2, 3]"""
    --> Ok [1, 2, 3]

Decode.decodeString (Decode.list Decode.int) """[1, 2, 3, "not an int"]"""
    --> Err ...

Decode.decodeString (Decode.dict Decode.int) """{ "key1": 17, "key2": 71 }"""
    --> Ok (Dict.fromList [("key1", 17), ("key2", 71)])

Decode.decodeString (Decode.dict Decode.int) """{ "key1": 17, "key2": "seventy-one" }"""
    --> Err ...

Αυτές οι συναρτήσεις Decode.list : Decoder a -> Decoder (List a) και Decode.dict : Decoder a -> Decoder (Dict String a) περιμένουν η καθεμία έναν αποκωδικοποιητή ως όρισμα, ο οποίος αναμένεται να αποκωδικοποιήσει κάθε μεμονωμένο στοιχείο της δομής δεδομένων. Αν ο αποκωδικοποιητής-όρισμα αποτύχει να αποκωδικοποιήσει οποιοδήποτε στοιχείο μιας τιμής array ή object, ολόκληρος ο αποκωδικοποιητής αποτυγχάνει, κάτι που είναι ένα συνηθισμένο μοτίβο.

Ο συνδυασμός απλών αποκωδικοποιητών σε πιο σύνθετους είναι η βασική ιδέα της τεχνικής. Θα παρουσιάσουμε αρκετές ακόμη συναρτήσεις που μπορούν να χρησιμοποιηθούν για να χτίσεις αποκωδικοποιητές δεδομένων του πραγματικού κόσμου, οσοδήποτε σύνθετους, μέσα από ένα παράδειγμα.

GeoJSON

Για να εξασκηθείς στον συνδυασμό βασικών αποκωδικοποιητών ώστε να αναλύεις σύνθετα δεδομένα JSON, ας φτιάξουμε αποκωδικοποιητές για την ανάλυση (ενός υποσυνόλου) δεδομένων GeoJSON. Το GeoJSON είναι μια εξειδικευμένη μορφή δεδομένων βασισμένη στο JSON, που χρησιμοποιείται για την αναπαράσταση γεωγραφικών χαρακτηριστικών, όπως τοποθεσίες, διαδρομές ή περιοχές, με γεωγραφικές συντεταγμένες 2D ή 3D, μαζί με άλλες ιδιότητες.

Γεωμετρικό Αντικείμενο

Ένα Γεωμετρικό Αντικείμενο GeoJSON είναι ένα αντικείμενο που περιέχει ένα πεδίο type με τιμή συμβολοσειράς (υπάρχουν 7 πιθανές τιμές για το type) και ένα πεδίο coordinates με γεωγραφικές συντεταγμένες (συγκεντρωμένες σε μια δομή που εξαρτάται από τον τύπο). Για παράδειγμα,

{
  "type": "Point",
  "coordinates": [127.831, 26.461]
}

ή

{
  "type": "LineString",
  "coordinates": [
    [127.831, 26.461],
    [127.829, 26.465],
    [127.829, 26.469],
    [127.83, 26.47]
  ]
}

Ένα Γεωμετρικό Αντικείμενο θα μπορούσε να αναπαρασταθεί στην Elm με τον ακόλουθο τύπο

type Geometry
  = Point (List Float)
  | LineString (List (List Float))
  | ...

Για να ελέγξεις την τιμή ενός συγκεκριμένου πεδίου, μπορείς να χρησιμοποιήσεις το Decode.field : String -> Decoder a -> Decoder a, το οποίο αποκωδικοποιεί την τιμή ενός συγκεκριμένου κλειδιού ενός αντικειμένου.

decodePointCoordinates : Decoder (List Float)
decodePointCoordinates =
    Decode.field "coordinates" (Decode.list Decode.float)

decodeLineStringCoordinates : Decoder (List (List Float))
decodeLineStringCoordinates =
    Decode.field "coordinates" (Decode.list (Decode.list Decode.float))

Για να μετασχηματίσεις την τιμή που αναλύθηκε με επιτυχία από έναν αποκωδικοποιητή, μπορείς να χρησιμοποιήσεις το Decode.map : (a -> value) -> Decoder a -> Decoder value, ή οποιοδήποτε από τα αδέρφια του, από το Decode.map2 (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value μέχρι το Decode.map8.

Σε αυτή την περίπτωση, θέλουμε να αποφασίσουμε ποιον αποκωδικοποιητή συντεταγμένων θα χρησιμοποιήσουμε ανάλογα με την τιμή του πεδίου type, κάτι για το οποίο είναι ιδανικό το Decode.andThen : (a -> Decoder b) -> Decoder a -> Decoder b και οι φίλοι του Decode.succeed : a -> Decoder a και Decode.fail : String -> Decoder a.

decodeGeometry : Decoder Geometry
decodeGeometry =
    Decode.field "type" Decode.string
        |> Decode.andThen
            (\value ->
                case value of
                    "Point" ->
                        decodePointCoordinates |> Decode.map Point

                    "LineString" ->
                        decodeLineStringCoordinates |> Decode.map LineString

                    _ ->
                        Decode.fail "Geometry not implemented yet, or invalid type value"
            )

Σημείωσε σε αυτό το σημείο ότι θα μπορούσαμε να χρησιμοποιήσουμε το Decode.andThen για να επικυρώσουμε τις συντεταγμένες (ελέγχοντας ότι οι συντεταγμένες έρχονται σε ζεύγη ή τριάδες και ότι οι τιμές των συντεταγμένων έχουν γεωγραφικό νόημα).

Decode.decodeString decodeGeometry """{"type": "Point", "coordinates": [127.831, 26.461]}"""
    --> Ok (Point [127.831, 26.461])

Decode.decodeString decodeGeometry """{"type": "LineString", "coordinates": [127.831, 26.461]}"""
    --> Err ...
Αντικείμενο Χαρακτηριστικού

Ένα Αντικείμενο Χαρακτηριστικού GeoJSON αναπαριστά κάτι με χωρικά όρια: έχει ένα πεδίο type με την τιμή "Feature", ένα πεδίο geometry με ένα Γεωμετρικό Αντικείμενο ή ένα null, ένα πεδίο properties με ένα αυθαίρετο JSON Object ή ένα null, και ένα προαιρετικό πεδίο id με τιμή συμβολοσειράς ή αριθμού JSON.

{
  "type": "Feature",
  "geometry": null,
  "properties": null
}

ή

{
  "type": "Feature",
  "id": "0157",
  "geometry": {
    "type": "Point",
    "coordinates": [127.831, 26.461]
  },
  "properties": {
    "country": "Japan"
  }
}

Ας ασχοληθούμε πρώτα με το πεδίο id. Αν υπάρχει, πρέπει να χωράει συμβολοσειρές ή αριθμούς, οπότε θα μπορούσαμε είτε να εισαγάγουμε έναν προσαρμοσμένο τύπο για την τιμή, είτε να την αποθηκεύουμε πάντα ως συμβολοσειρά.

Για να επιλέξεις ανάμεσα σε δύο πιθανούς αποκωδικοποιητές, μπορείς να χρησιμοποιήσεις το Decode.oneOf : List (Decoder a) -> Decoder a.

decodeFeatureId : Decoder String
decodeFeatureId =
    Decode.oneOf
        [ Decode.string
        , Decode.int |> Decode.map String.fromInt
        , Decode.float |> Decode.map String.fromFloat
        ]
        |> Decode.field "id"

Decode.decodeString decodeFeatureId """{"id": "seventeen"}"""
    --> Ok "seventeen"

Decode.decodeString decodeFeatureId """{"id": 17}"""
    --> Ok "17"

Decode.decodeString decodeFeatureId """{"type": "Point"}"""
    --> Er ...

Για να εκφράσουμε ότι το πεδίο είναι προαιρετικό, θα μπορούσαμε να χρησιμοποιήσουμε την ίδια τεχνική

decodeMaybeFeatureId : Decoder (Maybe String)
decodeMaybeFeatureId =
    Decode.oneOf
        [ decodeFeatureId |> Decode.map Just
        , Decode.succeed Nothing
        ]

Αλλά θα μπορούσαμε αντ' αυτού να χρησιμοποιήσουμε το Decode.maybe : Decoder a -> Decoder (Maybe a)

decodeMaybeFeatureId : Decoder (Maybe String)
decodeMaybeFeatureId =
    Decode.maybe decodeFeatureId

Decode.decodeString decodeMaybeFeatureId """{"id": 17}"""
    --> Ok (Just "17")

Decode.decodeString decodeMaybeFeatureId """{"type": "Point"}"""
    --> Ok Nothing

Σημείωσε ότι το decodeMaybeFeatureId (ή οποιοσδήποτε αποκωδικοποιητής τυλιγμένος με Decode.maybe) θα πετύχει πάντα, αφού μπορεί πάντα να καταφύγει στο Ok Nothing, αν και ένας αποκωδικοποιητής που το χρησιμοποιεί μπορεί να αποτύχει

Decode.decodeString (Decode.list decodeMaybeFeatureId) """{"id": 17}"""
    --> Err ...

Στη συνέχεια, ας επικεντρωθούμε στο πεδίο geometry, το οποίο μπορεί είτε να είναι ένα Γεωμετρικό Αντικείμενο είτε null. Θα μπορούσαμε να χρησιμοποιήσουμε το Decode.oneOf για άλλη μια φορά, αλλά υπάρχει καλύτερη επιλογή: το Decode.nullable : Decoder a -> Decoder (Maybe a).

decodeFeatureGeometry : Decoder (Maybe Geometry)
decodeFeatureGeometry =
    Decode.nullable decodeGeometry
        |> Decode.field "geometry"

Decode.decodeString decodeFeatureGeometry """{"geometry": {"type": "Point", "coordinates": [127.831, 26.461]}}"""
    --> Ok (Just (Point [127.831, 26.461]))

Decode.decodeString decodeFeatureGeometry """{"geometry": null}"""
    --> Ok Nothing

Decode.decodeString decodeFeatureGeometry """{"geometry": {}}"""
    --> Err ...

Τα Decode.maybe και Decode.nullable έχουν την ίδια υπογραφή, αλλά η συμπεριφορά τους είναι διαφορετική, αφού το αποτέλεσμα OK Nothing ενός Decode.nullable θα προέλθει μόνο από τη συγκεκριμένη τιμή JSON null.

Τέλος, το πεδίο properties μπορεί να περιέχει ένα αυθαίρετο JSON Object ή ένα null, κάτι που είναι δουλειά για το Decode.value : Decoder Value.

decodeProperties : Decoder (Maybe (Dict String Value))
decodeProperties =
    Decode.nullable (Decode.dict Decode.value)
        |> Decode.field "properties"


Decode.decodeString decodeProperties """{"properties": null}"""
    --> Ok Nothing

Decode.decodeString decodeProperties """{"properties": {"country": "Japan"}}"""
    --> Ok (Just (Dict.fromList [("country", <internals>)]))

Decode.decodeString decodeProperties """{"properties": 17}"""
    --> Err ...

Σημείωσε ότι το Value είναι ένας αδιαφανής τύπος, που σημαίνει ότι δεν μπορείς εύκολα να εξετάσεις το περιεχόμενό του. Για να το χρησιμοποιήσεις, μπορείς είτε να γράψεις έναν αποκωδικοποιητή γι' αυτό και να τον τρέξεις με το Decode.decodeValue : Decoder a -> Value -> Result Error a, είτε να το στείλεις απευθείας στη JavaScript μέσω ενός port.

Είμαστε έτοιμοι να αναλύσουμε Αντικείμενα Χαρακτηριστικού:

type alias Feature =
    { id : Maybe String
    , geometry : Maybe Geometry
    , properties : Maybe (Dict String Value)
    }

decodeFeature : Decoder Feature
decodeFeature =
    Decode.field "type" Decode.string
        |> Decode.andThen
            (\value ->
                if value /= "Feature" then
                    Decode.fail "not a Feature"

                else
                    Decode.map3 Feature
                        decodeMaybeFeatureId
                        decodeFeatureGeometry
                        decodeProperties
            )

Decode.decodeString decodeFeature """{"type": "Feature", "geometry": null, "properties": null}"""
    --> Ok { geometry = Nothing, id = Nothing, properties = Nothing }

"""
{
  "type": "Feature",
  "id": "0157",
  "geometry": {
    "type": "Point",
    "coordinates": [127.831, 26.461]
  },
  "properties": {
    "country": "Japan"
  }
}
"""
  |> Decode.decodeString decodeFeature
    --> Ok { geometry = Just (Point [127.831,26.461]), id = Just "0157", properties = Just (Dict.fromList [("country",<internals>)]) }

Κωδικοποιητές JSON

Οι κωδικοποιητές επιτρέπουν να γράψεις έγκυρο JSON από τιμές της Elm χρησιμοποιώντας τη συνάρτηση Encode.encode : Int -> Value -> String. Το πρώτο όρισμα καθορίζει το μέγεθος της εσοχής στο τελικό αποτέλεσμα, και το δεύτερο όρισμα είναι η τιμή JSON που θα γραφτεί.

Μια Value μπορεί είτε να προέλθει από το Decode.value είτε να παραχθεί από έναν από τους κωδικοποιητές:

Encode.encode 0 (Encode.string "hello")
    --> "hello"

Encode.encode 0 (Encode.bool True)
    --> "true"

Encode.encode 0 (Encode.int 12)
    --> "12"

Encode.encode 0 (Encode.float 3.14)
    --> "3.14"

Encode.encode 0 Encode.null
    --> "null"

Encode.encode 0 (Encode.list Encode.int [1, 2, 3])
    --> "[1,2,3]"

Encode.encode 4 (Encode.list Encode.int [1, 2, 3])
    --> "[\n    1,\n    2,\n    3\n]"

Encode.encode 0 (Encode.dict String.toLower Encode.int (Dict.fromList [("KEY1", 17), ("KEY2", 71)]))
    --> "{\"key1\":17,\"key2\":71}"

Encode.encode 4 (Encode.dict String.toLower Encode.int (Dict.fromList [("KEY1", 17), ("KEY2", 71)]))
    --> "{\n    \"key1\": 17,\n    \"key2\": 71\n}"

καθώς και με το Encode.object : List ( String, Value ) -> Value

Encode.object
    [ ( "key1", Encode.int 17 )
    , ( "key2", Encode.int 71 )
    ]
    |> Encode.encode 0
    --> "{\"key1\":17,\"key2\":71}"

Ας ορίσουμε κωδικοποιητές για τους αποκωδικοποιητές GeoJSON που ορίσαμε νωρίτερα

encodeGeometry : Geometry -> Value
encodeGeometry geometry =
    case geometry of
        Point coord ->
            Encode.object
                [ ( "type", Encode.string "Point" )
                , ( "coordinates", Encode.list Encode.float coord )
                ]

        LineString coord ->
            Encode.object
                [ ( "type", Encode.string "LineString" )
                , ( "coordinates", Encode.list (Encode.list Encode.float) coord )
                ]

encodeFeature : Feature -> Value
encodeFeature feature =
    let
        maybeId =
            case feature.id of
                Nothing ->
                    []

                Just id ->
                    [ ( "id", Encode.string id ) ]
    in
    Encode.object
        (maybeId
            ++ [ ( "type", Encode.string "Feature" )
               , ( "geometry"
                 , case feature.geometry of
                    Nothing ->
                        Encode.null

                    Just geometry ->
                        encodeGeometry geometry
                 )
               , ( "properties"
                 , case feature.properties of
                    Nothing ->
                        Encode.null

                    Just dict ->
                        Encode.dict identity identity dict
                 )
               ]
        )

Γενικά, ένας καλός έλεγχος για τα ζεύγη αποκωδικοποιητή-κωδικοποιητή σου είναι να επιβεβαιώσεις ότι elmValue |> encoder |> decoder == elmValue.

Οδηγίες

Όσο περισσότερο μαθαίνεις για τον προγραμματισμό, τόσο πιο πολύ αρχίζεις να σκέφτεσαι ότι αυτό μπορεί να μην είναι απλώς μια παροδική μόδα. Μπορεί μάλιστα να υπάρχουν και επιχειρηματικές ευκαιρίες: οι άνθρωποι γράφουν κώδικα, θέλουν να τον αποθηκεύσουν, να τον μοιραστούν, να συνεργαστούν, ίσως ακόμη και να πάρουν κάποια ανατροφοδότηση.

Εντάξει, ας το κάνουμε, ας φτιάξουμε μια υπηρεσία που να ενθαρρύνει τους ανθρώπους να γράφουν κώδικα: hup, hup, κόσμε, ώρα να αρχίσει ο καθένας να χρησιμοποιεί το git! Ας το ονομάσουμε... GitHup.

Ξεκινάς να φτιάχνεις ένα REST JSON API: χρήστες, pull requests, σχόλια, και το backend ετοιμάζεται γρήγορα. Ορίστε ένα παράδειγμα του payload που επιστρέφεται όταν ζητάς από τον server τη λίστα με τα σχόλια σε ένα pull request:

[
  {
    "id": 256,
    "pull_request_review_id": 42,
    "user": {
      "id": 101,
      "login": "octodog",
      "avatar_url": "https://githup.com/images/error/octodog_happy.gif",
      "site_admin": false
    },
    "body": "Great stuff!",
    "side": "RIGHT",
    "_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"
      }
    }
  },
  {
    "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"
      }
    }
  }
]

Φυσικά, το frontend θα γίνει σε Elm, αλλά πρώτα πρέπει να καταλάβεις περί τίνος πρόκειται αυτοί οι διαβόητοι decoders/encoders.

Note

Πέρα από το χιουμοριστικό σενάριο, οι απαιτήσεις της άσκησης προέρχονται απευθείας από τα REST API endpoints του GitHub για σχόλια σε pull request. Κάποια πεδία αφαιρέθηκαν από τα JSON schemas για λόγους απλότητας, αλλά αυτό είναι όσο πιο κοντά γίνεται σε εφαρμογές του πραγματικού κόσμου.

1. ID, παρακαλώ!

Το αετίσιο μάτι σου παρατήρησε ότι τόσο τα σχόλια όσο και οι χρήστες έχουν ένα πεδίο id, ιδανικό σημείο για να ξεκινήσεις.

Όρισε τη decodeId ώστε να μπορεί να αποκωδικοποιεί ένα JSON αντικείμενο, με έναν ακέραιο id.

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

2. Πώς με λένε, είπαμε;

Παρατηρείς στο παραπάνω παράδειγμα ότι δεν έχουν όλοι οι χρήστες όνομα.

Όρισε τη decodeName ώστε να μπορεί να αποκωδικοποιεί μια συμβολοσειρά στο πεδίο name, αν υπάρχει. Δεν υπάρχει όνομα; Κανένα πρόβλημα, απλώς επέστρεψε Nothing. Αυτός ο decoder δεν πρέπει να αποτυγχάνει ποτέ.

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

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

3. Χρήστης; Δεν τον ξέρω καν!

Έχουμε φτάσει σχεδόν στη μέση όσον αφορά την αποκωδικοποίηση των χρηστών, ας το τελειώσουμε.

Όρισε τη decodeUser ώστε να μπορεί να αποκωδικοποιεί ένα JSON αντικείμενο χρήστη. Φρόντισε να χρησιμοποιήσεις τις decodeId και decodeName μέσα στη decodeUser.

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. Είμαι ένα δυνατό και ανεξάρτητο σχόλιο

Κάποια σχόλια σε ένα pull review είναι αυτόνομα και επομένως δεν έχουν pull_request_review_id. Ωστόσο, οι προδιαγραφές αναφέρουν ότι σε μια τέτοια περίπτωση θα πρέπει να υπάρχει ακόμη ένα πεδίο pull_request_review_id, αλλά η τιμή του θα πρέπει να είναι null, το Nothing του φτωχού.

Όρισε τη decodePullRequestReviewId ώστε να μπορεί να αποκωδικοποιεί έναν ακέραιο στο πεδίο pull_request_review_id, αν υπάρχει. Αν δεν υπάρχει ακέραιος, θα πρέπει να υπάρχει ένα null, και αν δεν υπάρχει null, ο decoder θα πρέπει να αποτυγχάνει.

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. Σε ένα pull review, μπορείς να σχολιάσεις κώδικα που αφαιρέθηκε (στην πλευρά Left της οθόνης) ή κώδικα που προστέθηκε (στην πλευρά Right της οθόνης).

Όρισε τη decodeSide ώστε να μπορεί να αποκωδικοποιεί είτε "LEFT" είτε "RIGHT" στο πεδίο side και να τα αντιστοιχίζει στην αντίστοιχη παραλλαγή τύπου.

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

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

Κάθε σχόλιο έχει ένα σύνολο συνδέσμων, που δεν φαίνεται να είναι το ίδιο σε όλα τα σχόλια.

Όρισε τη decodeLinks ώστε να μπορεί να αποκωδικοποιεί μια συλλογή από URLs στο πεδίο _links. Τα ονόματα και ο αριθμός των συνδέσμων μπορεί να διαφέρουν, φρόντισε να το λάβεις υπόψη. Οι ίδιοι οι σύνδεσμοι είναι JSON αντικείμενα που θα πρέπει να έχουν πάντα ένα πεδίο href.

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. Ναι, σχόλια

Σχεδόν φτάσαμε, ας το τελειώσουμε.

Όρισε τις decodeComment και decodeComments ώστε να μπορούν να αποκωδικοποιούν ένα μεμονωμένο JSON αντικείμενο σχολίου ή μια λίστα με σχόλια αντίστοιχα. Φρόντισε να χρησιμοποιήσεις όλες τις συναρτήσεις που έχεις ορίσει μέχρι τώρα στη decodeComment, με εξαίρεση τη decodeName. Φυσικά, η decodeComments θα πρέπει να χρησιμοποιεί και τη decodeComment.

8. Όλα δένουν

Λείπει ακόμη ένα χαρακτηριστικό από όλα αυτά: η δυνατότητα να στέλνεις ένα νέο σχόλιο στον server. Γι' αυτό, πρέπει να πετύχεις το αντίθετο ενός decoder, πρέπει να μπορείς να μετατρέπεις ένα Elm Comment σε ένα JSON Value.

Όρισε τη encodeComment ώστε να μπορεί να κωδικοποιεί οποιοδήποτε έγκυρο Comment. Η σειρά των πεδίων δεν έχει σημασία, αρκεί ο encoder να παράγει μια τιμή που μπορεί να αποκωδικοποιήσει η decodeComment.

Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
Elm Exercism

Έτοιμος να ξεκινήσεις την άσκηση GitHup API;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις Elm με 28 έννοιες110 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.