Search code examples
javascriptjsonhttperror-handlingelm

Trefle API Request using Elm


I've started with Elm and want to get images from Trefle API when searched for plant but I keep getting the "Could not fetch image" error. I do use my own access token but I replaced it here with "MY_API_TOKEN" to keep it private. Can you help me to understand the problem in my code?

I've tried to get a more detailed error message but then my code crashed because I am quite new with using elm and in the web development field.

module Main exposing (..)

import Browser
import Html exposing (Html, button, div, img, input, text)
import Html.Attributes exposing (placeholder, src, type_)
import Html.Events exposing (onClick, onInput)
import Http
import Json.Decode exposing (Decoder, field, list, string)
import Maybe exposing (withDefault, map)


type alias Model =
    { searchTerm : String
    , imageUrl : String
    , error : String
    }


init : () -> ( Model, Cmd Msg )
init _ =
    ( { searchTerm = "", imageUrl = "", error = "" }, Cmd.none )


type Msg
    = UpdateSearchTerm String
    | FetchImage
    | ReceiveImage (Result Http.Error String)


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        UpdateSearchTerm term ->
            ( { model | searchTerm = term }, Cmd.none )

        FetchImage ->
            ( model, fetchPlantImage model.searchTerm )

        ReceiveImage result ->
            case result of
                Ok url ->
                    ( { model | imageUrl = url, error = "" }, Cmd.none )

                Err _ ->
                    ( { model | error = "Failed to fetch image." }, Cmd.none )


view : Model -> Html Msg
view model =
    div []
        [ input [ placeholder "Enter plant name", onInput UpdateSearchTerm ] []
        , button [ onClick FetchImage ] [ text "Search" ]
        , if model.imageUrl /= "" then
            img [ src model.imageUrl ] []
          else
            text ""
        , if model.error /= "" then
            div [] [ text model.error ]
          else
            text ""
        ]


fetchPlantImage : String -> Cmd Msg
fetchPlantImage searchTerm =
    let
        url =
            "https://trefle.io/api/v1/plants/search?token=MY_API_TOKEN&q=" ++ searchTerm

        decodeImageUrl : Decoder String
        decodeImageUrl =
            field "data" (list (field "image_url" string))
                |> Json.Decode.map (withDefault "" << map identity << List.head)
    in
    Http.get
        { url = url
        , expect = Http.expectJson ReceiveImage decodeImageUrl
        }


main : Program () Model Msg
main =
    Browser.element
        { init = init
        , update = update
        , subscriptions = \_ -> Sub.none
        , view = view
        }

Solution

  • tl;dr

    It appears trefle.io's SSL certificate has expired, so it's likely a problem on their end.

    Details

    The first thing I did was update your HTTP response handler to be more explicit when reporting the error (instead of just "Failed to fetch image.")

    ...
    
    
    update : Msg -> Model -> ( Model, Cmd Msg )
    update msg model =
        case msg of
            ...
    
            ReceiveImage result ->
                case result of
                    Ok url ->
                        ( { model | imageUrl = url, error = "" }, Cmd.none )
    
                    Err err ->
                        ( { model
                            | error =
                                case err of
                                    Http.BadUrl errorMessage ->
                                        errorMessage
    
                                    Http.Timeout ->
                                        "Timeout"
    
                                    Http.NetworkError ->
                                        "Network error"
    
                                    Http.BadStatus status ->
                                        "Bad status " ++ String.fromInt status
    
                                    Http.BadBody errorMessage ->
                                        errorMessage
                          }
                        , Cmd.none
                        )
    
    
    ...
    

    Ellie: https://ellie-app.com/rqZy7NHmGrGa1

    Once you do this, you'll see that the actual error is:

    Network error

    Implying the service is inaccessible.

    Next I just attempted to access that URL using curl, which reports an error with the server's SSL certificat:

    % curl "https://trefle.io/api/v1/plants/search?token=MY_API_TOKEN&q=coconut"
    curl: (60) SSL certificate problem: certificate has expired
    More details here: https://curl.haxx.se/docs/sslcerts.html
    
    curl performs SSL certificate verification by default, using a "bundle"
     of Certificate Authority (CA) public keys (CA certs). If the default
     bundle file isn't adequate, you can specify an alternate file
     using the --cacert option.
    If this HTTPS server uses a certificate signed by a CA represented in
     the bundle, the certificate verification probably failed due to a
     problem with the certificate (it might be expired, or the name might
     not match the domain name in the URL).
    If you'd like to turn off curl's verification of the certificate, use
     the -k (or --insecure) option.
    HTTPS-proxy has similar options --proxy-cacert and --proxy-insecure.