This is an alternative site for discovering Elm packages. You may be looking for the official Elm package site instead.

Prismic

An Elm SDK for Prismic.io.

Initialisation

init : String -> Model

Initialise the Prismic model with the URL for your Prismic repository. Save this in your application's Model somewhere.

type alias Model =
    { prismic : Prismic.Model }

init =
    { prismic =
        Prismic.init "https://lesbonneschoses.prismic.io/api"
    }
initWith : String -> Options -> Model

Initialise with custom options.

type alias Options = { defaultRef : String }
defaultOptions : Options

Initiating a request

type Request = Request { model : Model , api : Api , config : RequestConfig }

Represents a Prismic request.

api : Model -> Task PrismicError ( Model, Api )

Go and fetch the Prismic Api metadata, if it has not already been fetched.

The Api is cached in the returned Model, so we don't have to fetch it next time.

You start every Prismic request with this function.

form : String -> Task PrismicError ( Model, Api ) -> Task PrismicError Request

Choose a form on which to base the rest of the Prismic request.

bookmark : String -> Task PrismicError ( Model, Api ) -> Task PrismicError Request

Convenience function for fetching a bookmarked document.

Customising the request

ref : String -> Task PrismicError Request -> Task PrismicError Request

Override a Form's default ref

lang : String -> Task PrismicError Request -> Task PrismicError Request

Override a Form's default lang.

query : List Predicate -> Task PrismicError Request -> Task PrismicError Request

Override a Form's default query.

See the section on Predicates below for how to construct a Predicate.

Sending the request

submit : Decoder Document docType -> Task PrismicError Request -> Task PrismicError ( Model, Response docType )

Submit the request.

Pass this function a Decoder to decode each document in the response into your own Elm document type.

Handle the response

type alias Response docType = { license : String , nextPage : Maybe String , page : Int , prevPage : Maybe String , results : List docType , resultsPerPage : Int , resultsSize : Int , totalPages : Int , totalResultsSize : Int , version : String }

Represents a Prismic response.

This type is parameterized by docType, which is determined by the Decoder you pass to submit.

cache : Model -> Model -> Model

The submit Task returns an updated Prismic Model with the request and response cached.

In your app's update function, you should merge this with the existing cache using cache.

update msg model =
    case msg of
        MyPrismicMsg (Ok ( prismic, response )) ->
            { model
                | prismic =
                    cache model.prismic prismic
            }

Predicates

type Predicate = At String String | AtL String (List String) | Any String (List String) | FullText String String

The type representing Prismic query predicates.

at : String -> String -> Predicate

at fragment value matches documents having value at fragment.

atL : String -> List String -> Predicate

atL fragment values matches documents having a list of values at fragment.

any : String -> List String -> Predicate

any fragment values matches documents having any of values at fragment.

fulltext : String -> String -> Predicate

fulltext fragment value matches documents with a full text search at fragment.

Types

Models

type Model = Model { api : Maybe Api , url : String , nextRequestId : Int , cache : Dict String (Response Document) , options : Options }

The Prismic Model keeps track of configuration and holds the response cache.

Errors

type PrismicError = FormDoesNotExist String | RefDoesNotExist String | BookmarkDoesNotExist String | FetchApiError Http.Error | SubmitRequestError Http.Error | DecodeDocumentError String

The possible errors elm-prismicio raises.

Api

type alias Api = { refs : List RefProperties , bookmarks : Dict String String , types : Dict String String , tags : List String , version : String , forms : Dict String Form , oauthInitiate : String , oauthToken : String , license : String , experiments : Experiments }

The Api for your Prismic repository.

Your app can look things up in this if you need to (for example, to resolve links using the bookmarks Dict).

type alias RefProperties = { id : String , ref : Ref , label : String , isMasterRef : Bool }

Properties representing a Prismic ref.

Most of the time you will be working with the master ref, which is added to all requests by default.

type Ref = Ref String

A type to disambiguate Refs from other Strings.

type alias Form = { method : String , enctype : String , action : String , fields : Dict String FormField , rel : Maybe String , name : Maybe String }

Properties representing a Prismic form.

These are used to construct a default query.

type alias FormField = { fieldType : FieldType , multiple : Bool , default : Maybe String }

A field in a Prismic form.

These are combined to construct query parameters for the eventual Http request.

type FieldType = String | Integer

The type of values for a Prismic form field.

type alias Experiments = { draft : List String , running : List String }

TODO: Experiments are not Strings. Fill out this type.

Decoders

Helpers for decoding various parts of a Document.

type alias Decoder val a = Internal.Decoder val a

Decoders are parameterized by the input type val (Document, Field, Group or Slice) and the result type a -- your type representing your custom Prismic document type.

Decoder combinators

The following combinators can be used with any Decoder.

succeed : a -> Decoder val a
fail : String -> Decoder val a
map : (a -> b) -> Decoder val a -> Decoder val b

Transform a decoder.

apply : Decoder val (a -> b) -> Decoder val a -> Decoder val b
andThen : (a -> Decoder val b) -> Decoder val a -> Decoder val b

Pipeline decoders

decode : a -> Decoder val a

Begin a decoding pipeline.

type alias MyDoc =
    { title : StructuredText }

myDocDecoder : Decoder Document MyDoc
myDocDecoder =
    decode MyDoc
        |> required "title" structuredText
custom : Decoder val a -> Decoder val (a -> b) -> Decoder val b

Use a standard decoder in a pipeline.

The following is equivalent to the example using required:

myDocDecoder : Decoder Document MyDoc
myDocDecoder =
    decode MyDoc
        |> custom (requiredField "title" structuredText)

Decoding documents

type alias Document = Internal.Document

Holds the Prismic document.

Documents consist of basic Fields, Groups and Slices.

You will decode this into your own document type by passing a Decoder Document MyDocType to submit.

id : Decoder Document String

The document's ID.

href : Decoder Document String

The document's href.

linkedDocuments : Decoder Document (List Field.DocumentReference)

The document's linked documents.

slugs : Decoder Document (List String)

The document's slugs.

tags : Decoder Document (List String)

The document's tags.

uid : Decoder Document (Maybe String)

The document's UID.

Decoding custom fields

requiredField : String -> Decoder Field a -> Decoder Document a

Decode a field.

Pass this function a Decoder Field a from the Prismic.Field module.

optionalField : String -> Decoder Field a -> a -> Decoder Document a

Decode a field that might be missing.

groupField : String -> Decoder Group a -> Decoder Document (List a)

Decode a group.

Pass this function a Decoder Group a from the Prismic.Group module.

Groups can contain Fields, but not other Groups or Slices.

Here is an example with a document containing a group:

type alias MyDoc =
    { albums : List Album }

type alias Album =
    { title : String
    , cover : Field.ImageViews
    }

albumDecoder : Decoder Group Album
albumDecoder =
    Prismic.decode Album
        |> Group.required "title" Field.text
        |> Group.required "cover" Field.image

myDocDecoder : Decoder Document MyDoc
myDocDecoder =
    Prismic.map MyDoc
        (Prismic.groupField "albums" albumDecoder)
sliceZoneField : String -> Decoder Slice a -> Decoder Document (List a)

Decode a SliceZone.

Pass this function a Decoder Slice a from the Prismic.Slice module.

Slices can contain Fields and Groups, but not other Slices.

type alias MyDoc =
    { sections : List Section }

type Section
    = -- The "my-content" slice has a non-repeating zone.
      MyContent Field.StructuredText
    | -- The "my-image-gallery" slice has a repeating zone.
      MyImageGallery (List Field.ImageViews)
    | -- The "my-links-section" slice has both non-repeating and repeating
      -- zones.
      MyLinksSection LinksSection

type alias LinksSection =
    { title : Field.StructuredText
    , links : List Field.Link
    }

myDocDecoder : Decoder Document MyDoc
myDocDecoder =
    Prismic.map MyDoc
        (Prismic.sliceZoneField "sections" sectionDecoder)

sectionDecoder : Decoder Slice Section
sectionDecoder =
    Slice.oneOf
        [ Slice.slice "my-content"
            -- Decode the non-repeating zone and ignore the repeating zone.
            (Group.field "text" Field.structuredText)
            (Prismic.succeed ())
            |> Prismic.map (\( content, _ ) -> MyContent content)
        , Slice.slice "my-image-gallery"
            -- Ignore the non-repeating zone and decode the repeating zone.
            (Prismic.succeed ())
            (Group.field "image" Field.image)
            |> Prismic.map (\( _, images ) -> MyImageGallery images)
        , Slice.slice "my-links-section"
            -- Decode both the non-repeating and repeating zones.
            (Group.field "title" Field.structuredText)
            (Group.field "link" Field.link)
            |> Prismic.map
                (\( title, links ) -> MyLinksSection (LinksSection title links))
        ]

Pipeline decoders

required : String -> Decoder Field a -> Decoder Document (a -> b) -> Decoder Document b

Decode a required field.

optional : String -> Decoder Field a -> a -> Decoder Document (a -> b) -> Decoder Document b

Decode a field that might be missing.

group : String -> Decoder Group a -> Decoder Document (List a -> b) -> Decoder Document b

Pipeline version of groupField.

sliceZone : String -> Decoder Slice a -> Decoder Document (List a -> b) -> Decoder Document b

Pipeline version of sliceZoneField.

module Prismic
    exposing
        ( Api
        , Decoder
        , Document
        , Experiments
        , FieldType
        , Form
        , FormField
        , Model
        , Options
        , Predicate
        , PrismicError(..)
        , Ref
        , RefProperties
        , Request
        , Response
        , andThen
        , any
        , api
        , apply
        , at
        , atL
        , bookmark
        , cache
        , custom
        , decode
        , defaultOptions
        , fail
        , form
        , fulltext
        , group
        , groupField
        , href
        , id
        , init
        , initWith
        , lang
        , linkedDocuments
        , map
        , optional
        , optionalField
        , query
        , ref
        , required
        , requiredField
        , sliceZone
        , sliceZoneField
        , slugs
        , submit
        , succeed
        , tags
        , uid
        )

{-| An Elm SDK for [Prismic.io](https://prismic.io).


# Initialisation

@docs init, initWith, Options, defaultOptions


# Initiating a request

@docs Request, api, form, bookmark


# Customising the request

@docs ref, lang, query


# Sending the request

@docs submit


# Handle the response

@docs Response, cache


# Predicates

@docs Predicate, at, atL, any, fulltext


# Types


## Models

@docs Model


## Errors

@docs PrismicError


## Api

@docs Api, RefProperties, Ref, Form, FormField, FieldType, Experiments


# Decoders

Helpers for decoding various parts of a Document.

@docs Decoder


## Decoder combinators

The following combinators can be used with any `Decoder`.

@docs succeed, fail, map, apply, andThen


## Pipeline decoders

@docs decode, custom


## Decoding documents

@docs Document
@docs id, href, linkedDocuments, slugs, tags, uid


### Decoding custom fields

@docs requiredField, optionalField
@docs groupField, sliceZoneField


### Pipeline decoders

@docs required, optional
@docs group, sliceZone

-}

import Dict exposing (Dict)
import Http
import Json.Decode as Json
import Json.Decode.Pipeline as Json
import Prismic.Field as Field exposing (Field)
import Prismic.Group as Group exposing (Group)
import Prismic.Internal as Internal
import Prismic.Slice as Slice exposing (Slice)
import Result.Extra as Result
import String
import Task exposing (Task)
import Task.Extra as Task


{-| The Prismic Model keeps track of configuration and holds the response cache.
-}
type Model
    = Model
        { api : Maybe Api
        , url : String
        , nextRequestId : Int
        , cache : Dict String (Response Document)
        , options : Options
        }



-- Types: Options


{-| -}
type alias Options =
    { defaultRef : String }


{-| -}
defaultOptions : Options
defaultOptions =
    { defaultRef = "master" }



-- Types: Errors


{-| The possible errors elm-prismicio raises.
-}
type PrismicError
    = FormDoesNotExist String
    | RefDoesNotExist String
    | BookmarkDoesNotExist String
    | FetchApiError Http.Error
    | SubmitRequestError Http.Error
    | DecodeDocumentError String



-- Types: API


{-| The `Api` for your Prismic repository.

Your app can look things up in this if you need to (for example, to resolve
links using the bookmarks `Dict`).

-}
type alias Api =
    { refs : List RefProperties
    , bookmarks : Dict String String
    , types : Dict String String
    , tags : List String
    , version : String
    , forms : Dict String Form
    , oauthInitiate : String
    , oauthToken : String
    , license : String
    , experiments : Experiments
    }


{-| Properties representing a Prismic ref.

Most of the time you will be working with the `master` ref, which is added to
all requests by default.

-}
type alias RefProperties =
    { id : String
    , ref : Ref
    , label : String
    , isMasterRef : Bool
    }


{-| A type to disambiguate `Ref`s from other `String`s.
-}
type Ref
    = Ref String


{-| Properties representing a Prismic form.

These are used to construct a default query.

-}
type alias Form =
    { method : String
    , enctype : String
    , action : String
    , fields : Dict String FormField
    , rel : Maybe String
    , name : Maybe String
    }


{-| A field in a Prismic form.

These are combined to construct query parameters for the eventual Http request.

-}
type alias FormField =
    { fieldType : FieldType
    , multiple : Bool
    , default : Maybe String
    }


{-| The type of values for a Prismic form field.
-}
type FieldType
    = String
    | Integer


{-| TODO: Experiments are not Strings. Fill out this type.
-}
type alias Experiments =
    { draft : List String
    , running : List String
    }



-- RESPONSE


{-| Represents a Prismic response.

This type is parameterized by `docType`, which is determined by the `Decoder`
you pass to `submit`.

-}
type alias Response docType =
    { license : String
    , nextPage : Maybe String
    , page : Int
    , prevPage : Maybe String
    , results : List docType
    , resultsPerPage : Int
    , resultsSize : Int
    , totalPages : Int
    , totalResultsSize : Int
    , version : String
    }



-- DECODERS


decodeRef : Json.Decoder Ref
decodeRef =
    Json.map Ref Json.string


{-| Decode an `Api` from JSON.
-}
decodeApi : Json.Decoder Api
decodeApi =
    Json.decode Api
        |> Json.required "refs" (Json.list decodeRefProperties)
        |> Json.required "bookmarks" (Json.dict Json.string)
        |> Json.required "types" (Json.dict Json.string)
        |> Json.required "tags" (Json.list Json.string)
        |> Json.required "version" Json.string
        |> Json.required "forms" (Json.dict decodeForm)
        |> Json.required "oauth_initiate" Json.string
        |> Json.required "oauth_token" Json.string
        |> Json.required "license" Json.string
        |> Json.required "experiments" decodeExperiments


decodeRefProperties : Json.Decoder RefProperties
decodeRefProperties =
    Json.decode RefProperties
        |> Json.required "id" Json.string
        |> Json.required "ref" decodeRef
        |> Json.required "label" Json.string
        |> Json.optional "isMasterRef" Json.bool False


decodeForm : Json.Decoder Form
decodeForm =
    Json.decode Form
        |> Json.required "method" Json.string
        |> Json.required "enctype" Json.string
        |> Json.required "action" Json.string
        |> Json.required "fields" (Json.dict decodeFormField)
        |> Json.optional "rel" (Json.maybe Json.string) Nothing
        |> Json.optional "name" (Json.maybe Json.string) Nothing


decodeFormField : Json.Decoder FormField
decodeFormField =
    Json.decode FormField
        |> Json.required "type" decodeFieldType
        |> Json.required "multiple" Json.bool
        |> Json.optional "default" (Json.maybe Json.string) Nothing


decodeFieldType : Json.Decoder FieldType
decodeFieldType =
    let
        decodeOnType str =
            case str of
                "String" ->
                    Json.succeed String

                "Integer" ->
                    Json.succeed Integer

                _ ->
                    Json.fail ("Unknown field type: " ++ str)
    in
    Json.string |> Json.andThen decodeOnType


decodeExperiments : Json.Decoder Experiments
decodeExperiments =
    Json.decode Experiments
        |> Json.required "draft" (Json.list Json.string)
        |> Json.required "running" (Json.list Json.string)


{-| Decode a `Response` from JSON.
-}
decodeResponse : Json.Decoder (Response Document)
decodeResponse =
    Json.decode Response
        |> Json.required "license" Json.string
        |> Json.required "next_page" (Json.nullable Json.string)
        |> Json.required "page" Json.int
        |> Json.required "prev_page" (Json.nullable Json.string)
        |> Json.required "results" (Json.list Internal.decodeSearchResult)
        |> Json.required "results_per_page" Json.int
        |> Json.required "results_size" Json.int
        |> Json.required "total_pages" Json.int
        |> Json.required "total_results_size" Json.int
        |> Json.required "version" Json.string



-- REQUEST


{-| Represents a Prismic request.
-}
type Request
    = Request
        { model : Model
        , api : Api
        , config : RequestConfig
        }


type alias RequestConfig =
    { action : String
    , ref : Ref
    , q : String
    , lang : String
    }



-- FUNCTIONS


{-| Initialise the Prismic model with the URL for your Prismic repository. Save
this in your application's Model somewhere.

    type alias Model =
        { prismic : Prismic.Model }

    init =
        { prismic =
            Prismic.init "https://lesbonneschoses.prismic.io/api"
        }

-}
init : String -> Model
init url =
    initWith url defaultOptions


{-| Initialise with custom options.
-}
initWith : String -> Options -> Model
initWith url options =
    Model
        { api = Nothing
        , url = url
        , nextRequestId = 0
        , cache = Dict.empty
        , options = options
        }


{-| Go and fetch the Prismic `Api` metadata, if it has not already been fetched.

The `Api` is cached in the returned `Model`, so we don't have to fetch it next
time.

You start every Prismic request with this function.

-}
api : Model -> Task PrismicError ( Model, Api )
api (Model model) =
    case model.api of
        Just api ->
            Task.succeed ( Model model, api )

        Nothing ->
            Http.get model.url decodeApi
                |> Http.toTask
                |> Task.mapError FetchApiError
                |> Task.map
                    (\api ->
                        ( Model { model | api = Just api }
                        , api
                        )
                    )


{-| Choose a form on which to base the rest of the Prismic request.
-}
form :
    String
    -> Task PrismicError ( Model, Api )
    -> Task PrismicError Request
form formId apiTask =
    let
        addForm ( Model model, api ) =
            let
                mForm =
                    Dict.get formId api.forms

                ref =
                    getRefById model.options.defaultRef api
                        |> Maybe.map .ref
                        |> Maybe.withDefault (Ref model.options.defaultRef)
            in
            case mForm of
                Nothing ->
                    Task.fail (FormDoesNotExist formId)

                Just form ->
                    let
                        q =
                            Maybe.withDefault ""
                                (Dict.get "q" form.fields
                                    |> Maybe.andThen .default
                                )
                    in
                    Task.succeed
                        (Request
                            { api = api
                            , model = Model model
                            , config =
                                { action = form.action
                                , ref = ref
                                , q = q
                                , lang = ""
                                }
                            }
                        )
    in
    apiTask |> Task.andThen addForm


{-| Convenience function for fetching a bookmarked document.
-}
bookmark :
    String
    -> Task PrismicError ( Model, Api )
    -> Task PrismicError Request
bookmark bookmarkId cacheTask =
    cacheTask
        |> Task.andThen
            (\( model, api ) ->
                let
                    mDocId =
                        Dict.get bookmarkId api.bookmarks
                in
                case mDocId of
                    Nothing ->
                        Task.fail (BookmarkDoesNotExist bookmarkId)

                    Just docId ->
                        Task.succeed ( model, api )
                            |> form "everything"
                            |> query [ at "document.id" docId ]
            )


{-| Override a Form's default ref
-}
ref :
    String
    -> Task PrismicError Request
    -> Task PrismicError Request
ref refId requestTask =
    let
        setRef ref (Request request) =
            let
                config =
                    request.config
            in
            Request
                { request
                    | config = { config | ref = ref.ref }
                }

        addRef (Request request) =
            case getRefById refId request.api of
                Nothing ->
                    Task.fail (RefDoesNotExist refId)

                Just r ->
                    Request request
                        |> setRef r
                        |> Task.succeed
    in
    requestTask |> Task.andThen addRef


{-| Override a Form's default lang.
-}
lang :
    String
    -> Task PrismicError Request
    -> Task PrismicError Request
lang lang requestTask =
    let
        setLang lang (Request request) =
            let
                config =
                    request.config
            in
            Request
                { request
                    | config = { config | lang = lang }
                }

        addLang request =
            request
                |> setLang lang
                |> Task.succeed
    in
    requestTask |> Task.andThen addLang


{-| Override a Form's default query.

See the section on `Predicate`s below for how to construct a `Predicate`.

-}
query :
    List Predicate
    -> Task PrismicError Request
    -> Task PrismicError Request
query predicates requestTask =
    let
        setQuery query (Request request) =
            let
                config =
                    request.config
            in
            Request
                { request
                    | config = { config | q = query }
                }

        addQuery request =
            request
                |> setQuery (predicatesToStr predicates)
                |> Task.succeed
    in
    requestTask |> Task.andThen addQuery


{-| Submit the request.

Pass this function a `Decoder` to decode each document in the response into your
own Elm document type.

-}
submit :
    Decoder Document docType
    -> Task PrismicError Request
    -> Task PrismicError ( Model, Response docType )
submit decodeDocType requestTask =
    let
        doSubmit (Request request) =
            let
                decodeResponseToUserDocType response =
                    response.results
                        |> List.map (Internal.decodeValue decodeDocType)
                        |> Result.collect
                        |> Result.map (\docs -> { response | results = docs })
                        |> Task.fromResult
                        |> Task.mapError DecodeDocumentError
            in
            case getFromCache request.config request.model of
                Just response ->
                    decodeResponseToUserDocType response
                        |> Task.map (\response -> ( request.model, response ))

                Nothing ->
                    Http.get (requestToUrl request.config) decodeResponse
                        |> Http.toTask
                        |> Task.mapError SubmitRequestError
                        |> Task.andThen
                            (\origResponse ->
                                decodeResponseToUserDocType origResponse
                                    |> Task.map
                                        (\response ->
                                            ( setInCache request.config origResponse request.model
                                            , response
                                            )
                                        )
                            )
    in
    requestTask |> Task.andThen doSubmit


{-| The `submit` `Task` returns an updated Prismic `Model` with the request and
response cached.

In your app's `update` function, you should merge this with the existing cache
using `cache`.

    update msg model =
        case msg of
            MyPrismicMsg (Ok ( prismic, response )) ->
                { model
                    | prismic =
                        cache model.prismic prismic
                }

-}
cache : Model -> Model -> Model
cache (Model model1) (Model model2) =
    Model
        { model2
            | cache = Dict.union model2.cache model1.cache
        }



-- Predicates


{-| The type representing Prismic query predicates.
-}
type Predicate
    = At String String
    | AtL String (List String)
    | Any String (List String)
    | FullText String String


{-| `at fragment value` matches documents having `value` at `fragment`.
-}
at : String -> String -> Predicate
at fragment value =
    At fragment value


{-| `atL fragment values` matches documents having a list of `values` at `fragment`.
-}
atL : String -> List String -> Predicate
atL fragment values =
    AtL fragment values


{-| `any fragment values` matches documents having any of `values` at `fragment`.
-}
any : String -> List String -> Predicate
any fragment values =
    Any fragment values


{-| `fulltext fragment value` matches documents with a full text search at `fragment`.
-}
fulltext : String -> String -> Predicate
fulltext fragment value =
    FullText fragment value



-- INTERNAL: State


withQuery : List ( String, String ) -> String -> String
withQuery params base =
    let
        sep =
            if List.isEmpty params then
                ""
            else
                "?"

        joinParamPair ( key, val ) =
            Http.encodeUri key ++ "=" ++ Http.encodeUri val

        paramsPart =
            params
                |> List.map joinParamPair
                |> String.join "&"
    in
    base ++ sep ++ paramsPart


requestToUrl : RequestConfig -> String
requestToUrl config =
    let
        (Ref refStr) =
            config.ref

        ifNotEmpty key val =
            if String.isEmpty val then
                []
            else
                [ ( key, val ) ]
    in
    config.action
        |> withQuery
            (List.concat
                [ [( "ref", refStr )]
                , ifNotEmpty "q" config.q
                , ifNotEmpty "lang" config.lang
                ]
            )


getRefById : String -> Api -> Maybe RefProperties
getRefById refId api =
    api.refs
        |> List.filter (\r -> r.id == refId)
        |> List.head


predicatesToStr : List Predicate -> String
predicatesToStr predicates =
    let
        wrapQuotes value =
            "\"" ++ value ++ "\""

        toStrList values =
            let
                valueStrs =
                    values
                        |> List.map wrapQuotes
                        |> String.join ", "
            in
            "[" ++ valueStrs ++ "]"

        predicateToStr predicate =
            let
                query =
                    case predicate of
                        At fragment value ->
                            "at(" ++ fragment ++ ", " ++ wrapQuotes value ++ ")"

                        AtL fragment values ->
                            "at(" ++ fragment ++ ", " ++ toStrList values ++ ")"

                        Any fragment values ->
                            "any(" ++ fragment ++ ", " ++ toStrList values ++ ")"

                        FullText fragment value ->
                            "fulltext(" ++ fragment ++ ", " ++ wrapQuotes value ++ ")"
            in
            "[:d = " ++ query ++ "]"
    in
    "[" ++ String.concat (List.map predicateToStr predicates) ++ "]"


getFromCache :
    RequestConfig
    -> Model
    -> Maybe (Response Document)
getFromCache request (Model model) =
    Dict.get (requestToKey request) model.cache


setInCache :
    RequestConfig
    -> Response Document
    -> Model
    -> Model
setInCache request response (Model model) =
    Model
        { model
            | cache =
                Dict.insert (requestToKey request) response model.cache
        }


requestToKey : RequestConfig -> String
requestToKey =
    toString



-- DECODERS


{-| Decoders are parameterized by the input type `val` (`Document`, `Field`,
`Group` or `Slice`) and the result type `a` -- your type representing your
custom Prismic document type.
-}
type alias Decoder val a =
    Internal.Decoder val a


{-| -}
succeed : a -> Decoder val a
succeed =
    Internal.succeed


{-| -}
fail : String -> Decoder val a
fail =
    Internal.fail


{-| Transform a decoder.
-}
map : (a -> b) -> Decoder val a -> Decoder val b
map =
    Internal.map


{-| -}
apply : Decoder val (a -> b) -> Decoder val a -> Decoder val b
apply =
    Internal.apply


{-| -}
andThen : (a -> Decoder val b) -> Decoder val a -> Decoder val b
andThen =
    Internal.andThen



-- Decoding Pipelines


{-| Begin a decoding pipeline.

    type alias MyDoc =
        { title : StructuredText }

    myDocDecoder : Decoder Document MyDoc
    myDocDecoder =
        decode MyDoc
            |> required "title" structuredText

-}
decode : a -> Decoder val a
decode =
    Internal.decode


{-| Use a standard decoder in a pipeline.

The following is equivalent to the example using `required`:

    myDocDecoder : Decoder Document MyDoc
    myDocDecoder =
        decode MyDoc
            |> custom (requiredField "title" structuredText)

-}
custom : Decoder val a -> Decoder val (a -> b) -> Decoder val b
custom =
    Internal.custom



-- Documents


{-| Holds the Prismic document.

`Documents` consist of basic `Fields`, `Groups` and `Slices`.

You will decode this into your own document type by passing a `Decoder Document
MyDocType` to `submit`.

-}
type alias Document =
    Internal.Document



--  DOCUMENT DECODERS
-- {-| A value that knows how to decode Documents.
-- Construct a `Decoder` to pass to `submit`.
-- -}
-- type alias Decoder a =
--     Internal.Decoder Document a


{-| The document's ID.
-}
id : Decoder Document String
id =
    Internal.Decoder (Ok << .id)


{-| The document's href.
-}
href : Decoder Document String
href =
    Internal.Decoder (Ok << .href)


{-| The document's linked documents.
-}
linkedDocuments : Decoder Document (List Field.DocumentReference)
linkedDocuments =
    Internal.Decoder (Ok << .linkedDocuments)


{-| The document's slugs.
-}
slugs : Decoder Document (List String)
slugs =
    Internal.Decoder (Ok << .slugs)


{-| The document's tags.
-}
tags : Decoder Document (List String)
tags =
    Internal.Decoder (Ok << .tags)


{-| The document's UID.
-}
uid : Decoder Document (Maybe String)
uid =
    Internal.Decoder (Ok << .uid)


getKey : String -> Document -> Maybe (Result String Field)
getKey key doc =
    case Dict.get key doc.data of
        Just (Internal.Field field) ->
            Just (Ok field)

        Just (Internal.SliceZone _) ->
            [ "Expected a Field but got a SliceZone."
            , "(Hint: use sliceZone to decode this.)"
            ]
                |> String.join " "
                |> Err
                |> Just

        Just (Internal.Groups _) ->
            [ "Expected a Field but got a Group."
            , "(Hint: use group to decode this.)"
            ]
                |> String.join " "
                |> Err
                |> Just

        Nothing ->
            Nothing


{-| Decode a field.

Pass this function a `Decoder Field a` from the `Prismic.Field` module.

-}
requiredField : String -> Decoder Field a -> Decoder Document a
requiredField =
    Internal.requiredField getKey


{-| Decode a field that might be missing.
-}
optionalField : String -> Decoder Field a -> a -> Decoder Document a
optionalField =
    Internal.optionalField getKey


{-| Decode a required field.
-}
required : String -> Decoder Field a -> Decoder Document (a -> b) -> Decoder Document b
required =
    Internal.required getKey


{-| Decode a field that might be missing.
-}
optional : String -> Decoder Field a -> a -> Decoder Document (a -> b) -> Decoder Document b
optional =
    Internal.optional getKey


{-| Decode a group.

Pass this function a `Decoder Group a` from the `Prismic.Group` module.

Groups can contain Fields, but not other Groups or Slices.

Here is an example with a document containing a group:

    type alias MyDoc =
        { albums : List Album }

    type alias Album =
        { title : String
        , cover : Field.ImageViews
        }

    albumDecoder : Decoder Group Album
    albumDecoder =
        Prismic.decode Album
            |> Group.required "title" Field.text
            |> Group.required "cover" Field.image

    myDocDecoder : Decoder Document MyDoc
    myDocDecoder =
        Prismic.map MyDoc
            (Prismic.groupField "albums" albumDecoder)

-}
groupField : String -> Decoder Group a -> Decoder Document (List a)
groupField key decoder =
    Internal.Decoder
        (\doc ->
            case Dict.get key doc.data of
                Just (Internal.Groups groups) ->
                    groups
                        |> List.map (Internal.decodeValue decoder)
                        |> Result.collect

                Just field ->
                    Err ("Expected a Group field, but got '" ++ toString field ++ "'.")

                Nothing ->
                    Ok []
        )


{-| Pipeline version of `groupField`.
-}
group : String -> Decoder Group a -> Decoder Document (List a -> b) -> Decoder Document b
group key decoder =
    custom (groupField key decoder)


{-| Decode a SliceZone.

Pass this function a `Decoder Slice a` from the `Prismic.Slice` module.

Slices can contain Fields and Groups, but not other Slices.

    type alias MyDoc =
        { sections : List Section }

    type Section
        = -- The "my-content" slice has a non-repeating zone.
          MyContent Field.StructuredText
        | -- The "my-image-gallery" slice has a repeating zone.
          MyImageGallery (List Field.ImageViews)
        | -- The "my-links-section" slice has both non-repeating and repeating
          -- zones.
          MyLinksSection LinksSection

    type alias LinksSection =
        { title : Field.StructuredText
        , links : List Field.Link
        }

    myDocDecoder : Decoder Document MyDoc
    myDocDecoder =
        Prismic.map MyDoc
            (Prismic.sliceZoneField "sections" sectionDecoder)

    sectionDecoder : Decoder Slice Section
    sectionDecoder =
        Slice.oneOf
            [ Slice.slice "my-content"
                -- Decode the non-repeating zone and ignore the repeating zone.
                (Group.field "text" Field.structuredText)
                (Prismic.succeed ())
                |> Prismic.map (\( content, _ ) -> MyContent content)
            , Slice.slice "my-image-gallery"
                -- Ignore the non-repeating zone and decode the repeating zone.
                (Prismic.succeed ())
                (Group.field "image" Field.image)
                |> Prismic.map (\( _, images ) -> MyImageGallery images)
            , Slice.slice "my-links-section"
                -- Decode both the non-repeating and repeating zones.
                (Group.field "title" Field.structuredText)
                (Group.field "link" Field.link)
                |> Prismic.map
                    (\( title, links ) -> MyLinksSection (LinksSection title links))
            ]

-}
sliceZoneField : String -> Decoder Slice a -> Decoder Document (List a)
sliceZoneField key sliceDecoder =
    Internal.Decoder
        (\doc ->
            case Dict.get key doc.data of
                Just (Internal.SliceZone slices) ->
                    slices
                        |> List.map (Internal.decodeValue sliceDecoder)
                        |> Result.collect

                _ ->
                    Err "Expected a SliceZone field."
        )


{-| Pipeline version of `sliceZoneField`.
-}
sliceZone : String -> Decoder Slice a -> Decoder Document (List a -> b) -> Decoder Document b
sliceZone key sliceDecoder =
    custom (sliceZoneField key sliceDecoder)