GitHup API

GitHup API

লার্নিং অনুশীলনী

ভূমিকা

JSON

JSON

JSON (JavaScript Object Notation) হলো একটি মানুষের পড়ার উপযোগী ডেটা ও ফাইল ফরম্যাট, যা সাধারণত ডেটা আদান-প্রদানের জন্য ব্যবহৃত হয়, বিশেষ করে ওয়েব অ্যাপ্লিকেশনের জন্য। তাই Elm ভাষায় এর বিশেষ একটি স্থান থাকাটাই স্বাভাবিক।

Elm-এ JSON ডেটা পার্স ও লেখার জন্য কোর মডিউল Json.Decode ও Json.Encode দেওয়া আছে। JSON ডিকোডার হলো একটি উপায়, যার মাধ্যমে আপনি ঘোষণা করেন কী ধরনের ডেটা প্রত্যাশিত এবং ডেটাটি কোন ডেটা স্ট্রাকচারে রূপান্তরিত হওয়া উচিত।

চলুন মডিউলগুলো থেকে কিছু অ্যালিয়াস ও টাইপ ইমপোর্ট করি:

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

একটি ডিকোডার আর একটি JSON স্ট্রিং নিয়ে Decode.decodeString : Decoder a -> String -> Result Error a ব্যবহার করলে, স্ট্রিংটি ডিকোডারের স্পেসিফিকেশন অনুযায়ী হলে সফল হবে, নাহলে ব্যর্থ হবে। ডিকোডার ডিফাইন করার সময় কাঁচা JSON ডেটা পরীক্ষা করার কোনো উপায় নেই, যা ইমপেরেটিভ ভাষা থেকে আসা কারও কাছে অস্বাভাবিক লাগতে পারে।

মৌলিক ডেটা টাইপ ডিকোডার

JSON-এ অল্প কিছু কোর ডেটা টাইপ আছে:

  • স্ট্রিং: ডাবল কোট দিয়ে এনকোড করা হয় "hello world"
  • বুলিয়ান: হয় true নয়তো false
  • সংখ্যা: যেমন 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 আর্গুমেন্ট হিসেবে একটি ইচ্ছামতো মান (হয়তো (), Nothing, কিংবা আপনার প্রোগ্রামের জন্য উপযুক্ত যা কিছু) দাবি করে আপনাকে সিদ্ধান্ত নিতে দেয় যে আপনার প্রোগ্রামে একটি null-কে কীভাবে মডেল করবেন।

ডিকোডার একত্র করা

JSON-এ কোর ডেটা টাইপ সংগ্রহের জন্য আরও দুটি ডেটা স্ট্রাকচার নির্ধারিত আছে:

  • অ্যারে: অন্য ডেটা টাইপের একটি সংগ্রহ [0, null, "none", []]
  • অবজেক্ট: কী (key) ও মানের জোড়ার একটি সংগ্রহ {"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) প্রত্যেকটিই আর্গুমেন্ট হিসেবে একটি ডিকোডার চায়, যা ডেটা স্ট্রাকচারের প্রতিটি এলিমেন্ট ডিকোড করবে বলে প্রত্যাশিত। আর্গুমেন্ট ডিকোডার যদি অ্যারে বা অবজেক্ট মানের কোনো একটি এলিমেন্ট ডিকোড করতে ব্যর্থ হয়, তাহলে পুরো ডিকোডারটিই ব্যর্থ হয়, যা একটি কমন প্যাটার্ন।

সরল ডিকোডারকে আরও জটিল ডিকোডারে একত্র করাই এই কৌশলের মূল ধারণা। একটি উদাহরণের মাধ্যমে আমরা আরও কিছু ফাংশন দেখব, যা দিয়ে যেকোনো জটিল বাস্তব-জগতের ডেটা ডিকোডার তৈরি করা যায়।

GeoJSON

জটিল JSON ডেটা পার্স করার জন্য মৌলিক ডিকোডার একত্র করার অনুশীলনের জন্য, চলুন GeoJSON ডেটার (একটি উপসেট) পার্স করার ডিকোডার বানাই। GeoJSON হলো JSON-ভিত্তিক একটি বিশেষায়িত ডেটা ফরম্যাট, যা ভৌগোলিক বৈশিষ্ট্য, যেমন অবস্থান, পথ বা অঞ্চল, প্রকাশ করতে ব্যবহৃত হয়; এগুলো 2D বা 3D ভৌগোলিক কোঅর্ডিনেটসহ অন্যান্য প্রপার্টি দিয়ে নির্দিষ্ট করা হয়।

জ্যামিতি অবজেক্ট

একটি GeoJSON জ্যামিতি অবজেক্ট হলো এমন একটি অবজেক্ট, যেখানে একটি type ফিল্ড থাকে যার মান একটি স্ট্রিং (সম্ভাব্য type মান ৭টি), আর একটি coordinates ফিল্ড থাকে যাতে ভৌগোলিক কোঅর্ডিনেট থাকে (যে স্ট্রাকচারে এগুলো রাখা হয় তা type-এর উপর নির্ভর করে)। উদাহরণস্বরূপ,

{
  "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]
  ]
}

এলম-এ একটি জ্যামিতি অবজেক্ট নিচের টাইপ দিয়ে প্রকাশ করা যায়

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

কোনো একটি নির্দিষ্ট ফিল্ডের মান দেখতে আমরা Decode.field : String -> Decoder a -> Decoder a ব্যবহার করতে পারি, যা একটি অবজেক্টের নির্দিষ্ট কী (key)-এর মান ডিকোড করে।

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 ফিচার অবজেক্ট কোনো স্থানিকভাবে সীমাবদ্ধ কিছু প্রকাশ করে: এতে "Feature" মানবিশিষ্ট একটি type ফিল্ড, একটি জ্যামিতি অবজেক্ট বা null-যুক্ত একটি geometry ফিল্ড, একটি ইচ্ছামতো JSON অবজেক্ট বা null-যুক্ত একটি properties ফিল্ড, আর ঐচ্ছিক একটি 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-এর সিগনেচার একই, কিন্তু তাদের আচরণ আলাদা, কারণ Decode.nullable-এর OK Nothing ফলাফল কেবল নির্দিষ্ট JSON মান null থেকেই আসবে।

সবশেষে, properties ফিল্ডে একটি ইচ্ছামতো JSON অবজেক্ট বা একটি 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-এ পাঠাতে পারেন।

আমরা এখন ফিচার অবজেক্ট পার্স করতে প্রস্তুত:

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 এনকোডার

এনকোডার দিয়ে Elm মান থেকে Encode.encode : Int -> Value -> String ফাংশন ব্যবহার করে বৈধ JSON লেখা যায়। প্রথম আর্গুমেন্টটি চূড়ান্ত ফলাফলে কতটুকু ইনডেন্টেশন থাকবে তা নির্ধারণ করে, আর দ্বিতীয় আর্গুমেন্টটি হলো লেখার জন্য 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।

নির্দেশনা

প্রোগ্রামিং সম্পর্কে যত বেশি শিখবেন, তত বেশি মনে হতে শুরু করবে যে এটা হয়তো নিছক ক্ষণিকের ঝোঁক নয়। এমনকি এতে ব্যবসার সুযোগও থাকতে পারে: মানুষ কোড লেখে, তারা সেটা সংরক্ষণ করতে চায়, শেয়ার করতে চায়, একসাথে কাজ করতে চায়, এমনকি হয়তো কিছু মতামতও পেতে চায়।

আচ্ছা, চলুন করে ফেলি, কোড লিখতে মানুষকে উৎসাহ দিতে একটি সার্ভিস বানাই: হুপ, হুপ, সবাই, git ব্যবহার শুরু করুন! এর নাম দিই... GitHup।

আপনি একটি REST JSON API বানানো শুরু করলেন: ইউজার, পুল রিকোয়েস্ট, কমেন্ট, backend-টা দ্রুতই হয়ে গেল। আপনি যখন সার্ভারের কাছে একটি পুল রিকোয়েস্টের কমেন্টের অ্যারে চান, তখন সার্ভার যে পেলোড পাঠায় তার একটি উদাহরণ এখানে:

[
  {
    "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 দিয়ে, তবে আগে আপনাকে বুঝে নিতে হবে সেই কুখ্যাত ডিকোডার/এনকোডারগুলো আসলে কী।

Note

রসিকতার গল্পটা বাদ দিলে, এই অনুশীলনীর প্রয়োজনীয়তাগুলো সরাসরি GitHub-এর REST API এন্ডপয়েন্ট থেকে নেওয়া, যা পুল রিকোয়েস্ট রিভিউ কমেন্টের জন্য। সরল রাখার জন্য JSON স্কিমা থেকে কিছু ফিল্ড বাদ দেওয়া হয়েছে, তবে বাস্তব-জগতের অ্যাপ্লিকেশনের সঙ্গে এর চেয়ে বেশি কাছাকাছি আর হওয়া সম্ভব নয়।

1. ID, দয়া করে!

আপনার তীক্ষ্ণ দৃষ্টিতে ধরা পড়েছে যে কমেন্ট আর ইউজার, দুটোরই একটি id ফিল্ড আছে, শুরু করার জন্য এটি একদম উপযুক্ত জায়গা।

decodeId ডিফাইন করুন যাতে এটি এমন একটি JSON অবজেক্ট ডিকোড করতে পারে, যেখানে id একটি ইন্টিজার।

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

2. আমার নামটা আবার কী?

উপরের উদাহরণে আপনি খেয়াল করবেন, সব ইউজারের নাম থাকে না।

decodeName ডিফাইন করুন যাতে এটি name ফিল্ডে থাকা একটি স্ট্রিং ডিকোড করতে পারে, যদি সেখানে স্ট্রিং থাকে। নাম নেই? কোনো সমস্যা নেই, শুধু Nothing রিটার্ন করুন। এই ডিকোডার কখনও ব্যর্থ হওয়া উচিত নয়।

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

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

3. ইউজার? আমি তো ওকে চিনিই না!

ইউজার ডিকোড করার কাজে আমরা প্রায় অর্ধেক পথ পেরিয়ে এসেছি, চলুন শেষ করে ফেলি।

decodeUser ডিফাইন করুন যাতে এটি একটি JSON ইউজার অবজেক্ট ডিকোড করতে পারে। decodeUser-এর ভেতরে অবশ্যই decodeId আর decodeName ব্যবহার করবেন।

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_request_review_id থাকে না। তবে স্পেসিফিকেশনে বলা আছে, এমন ক্ষেত্রেও একটি pull_request_review_id ফিল্ড থাকা উচিত, কিন্তু তার মান হবে null, অর্থাৎ গরিবের Nothing।

decodePullRequestReviewId ডিফাইন করুন যাতে এটি pull_request_review_id ফিল্ডে如果一个 ইন্টিজার থাকে, সেটি ডিকোড করতে পারে। যদি কোনো ইন্টিজার না থাকে, তাহলে সেখানে একটি null থাকা উচিত; আর যদি null না থাকে, তাহলে ডিকোডার ব্যর্থ হওয়া উচিত।

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 ফিল্ডটি। একটি পুল রিভিউতে আপনি কোডের যেসব অংশ মুছে ফেলা হয়েছে (স্ক্রিনের Left দিকে) বা যেসব অংশ যোগ করা হয়েছে (স্ক্রিনের Right দিকে), সেগুলোতে কমেন্ট করতে পারেন।

decodeSide ডিফাইন করুন যাতে এটি side ফিল্ডে "LEFT" অথবা "RIGHT" ডিকোড করে সেটিকে তার সংশ্লিষ্ট টাইপ ভ্যারিয়েন্টের সঙ্গে মিলিয়ে দিতে পারে।

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

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

6. লিংকস অ্যাওয়েকেনিং

প্রতিটি কমেন্টের একগুচ্ছ লিংক থাকে, যেগুলো সব কমেন্টে একরকম মনে হয় না।

decodeLinks ডিফাইন করুন যাতে এটি _links ফিল্ডে থাকা একগুচ্ছ URL ডিকোড করতে পারে। লিংকের নাম আর সংখ্যা বদলে যেতে পারে, তাই সেটা সামলানোর ব্যবস্থা করবেন। লিংকগুলো নিজেরা 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 কমেন্ট অবজেক্ট বা কমেন্টের একটি অ্যারে ডিকোড করতে পারে। decodeName বাদে এতক্ষণে ডিফাইন করা সব ফাংশনই decodeComment-এ ব্যবহার করবেন। অবশ্যই, decodeComments-ও decodeComment ব্যবহার করবে।

8. সব একসূত্রে গাঁথা

এই সবকিছুতেই আরও একটি ফিচার বাকি: সার্ভারে একটি নতুন কমেন্ট পাঠানোর ক্ষমতা। এর জন্য আপনাকে ডিকোডারের ঠিক উল্টোটা করতে হবে, একটি Elm Comment-কে একটি JSON Value-তে রূপান্তর করার ক্ষমতা আপনার থাকতে হবে।

encodeComment ডিফাইন করুন যাতে এটি যেকোনো বৈধ Comment এনকোড করতে পারে। ফিল্ডগুলোর ক্রম গুরুত্বপূর্ণ নয়, যতক্ষণ এনকোডার এমন একটি মান তৈরি করতে পারে যা decodeComment ডিকোড করতে পারে।

GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি একটি নতুন উইন্ডো বা ট্যাবে খোলে
Elm Exercism

GitHup API শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, Elm ট্র্যাকের 28টি কনসেপ্ট110টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।