From 47c33ef8253af4747ec8073caf3de4d882a63a4a Mon Sep 17 00:00:00 2001 From: Justin Blake Date: Tue, 18 Aug 2026 14:42:27 -0400 Subject: [PATCH] POC: decoupling field names from decoders --- integration-tests/sqlite/src/Main.gren | 38 ++++---- src/Sqlite/Decode.gren | 124 ++++++++++++------------- 2 files changed, 79 insertions(+), 83 deletions(-) diff --git a/integration-tests/sqlite/src/Main.gren b/integration-tests/sqlite/src/Main.gren index bb9588a..a314705 100644 --- a/integration-tests/sqlite/src/Main.gren +++ b/integration-tests/sqlite/src/Main.gren @@ -301,15 +301,15 @@ personEncoder p = personDecoder : Decoder Person personDecoder = - Decode.string "name" <| \name -> - Decode.string "role" <| \role -> + Decode.field "name" Decode.string <| \name -> + Decode.field "role" Decode.string <| \role -> Decode.succeed { name = name, role = role } badPersonDecoder : Decoder Person badPersonDecoder = - Decode.string "namee" <| \name -> - Decode.string "role_" <| \role -> + Decode.field "namee" Decode.string <| \name -> + Decode.field "role_" Decode.string <| \role -> Decode.succeed { name = name, role = role } @@ -380,8 +380,8 @@ friendshipQ = """ , parameters = [] , rowDecoder = - Decode.string "person_a" <| \personA -> - Decode.string "person_b" <| \personB -> + Decode.field "person_a" Decode.string <| \personA -> + Decode.field "person_b" Decode.string <| \personB -> Decode.succeed { personA = personA, personB = personB } } @@ -424,8 +424,8 @@ badDecoder db = { query = "SELECT * FROM people WHERE name = :name" , parameters = [ Encode.string "name" "Robin" ] , rowDecoder = - Decode.int "name" <| \name -> - Decode.string "role" <| \role -> + Decode.field "name" Decode.int <| \name -> + Decode.field "role" Decode.string <| \role -> Decode.succeed { name = name, role = role } } @@ -502,16 +502,16 @@ allFieldTypesEncoder row = allFieldTypesDecoder : Decoder AllFieldTypes allFieldTypesDecoder = - Decode.string "string_field" <| \stringField -> - Decode.int "int_field" <| \intField -> - Decode.float "float_field" <| \floatField -> - Decode.bool "bool_true" <| \boolTrue -> - Decode.bool "bool_false" <| \boolFalse -> - Decode.json (Json.Decode.array Json.Decode.string) "json_field" <| \jsonField -> - Decode.time "time_field" <| \timeField -> - Decode.time "time_with_millis_field" <| \timeWithMillisField -> - Decode.maybe Decode.string "maybe_just_string" <| \maybeJustString -> - Decode.maybe Decode.string "maybe_nothing_string" <| \maybeNothingString -> + Decode.field "string_field" Decode.string <| \stringField -> + Decode.field "int_field" Decode.int <| \intField -> + Decode.field "float_field" Decode.float <| \floatField -> + Decode.field "bool_true" Decode.bool <| \boolTrue -> + Decode.field "bool_false" Decode.bool <| \boolFalse -> + Decode.field "json_field" (Decode.json (Json.Decode.array Json.Decode.string)) <| \jsonField -> + Decode.field "time_field" Decode.time <| \timeField -> + Decode.field "time_with_millis_field" Decode.time <| \timeWithMillisField -> + Decode.field "maybe_just_string" (Decode.maybe Decode.string) <| \maybeJustString -> + Decode.field "maybe_nothing_string" (Decode.maybe Decode.string) <| \maybeNothingString -> Decode.succeed { stringField = stringField , intField = intField @@ -612,6 +612,6 @@ allTimesQ = { query = "SELECT t FROM times ORDER BY rowid" , parameters = [] , rowDecoder = - Decode.time "t" <| \t -> + Decode.field "t" Decode.time <| \t -> Decode.succeed t } diff --git a/src/Sqlite/Decode.gren b/src/Sqlite/Decode.gren index 2ff7eb4..6465add 100644 --- a/src/Sqlite/Decode.gren +++ b/src/Sqlite/Decode.gren @@ -9,6 +9,7 @@ module Sqlite.Decode exposing , json , time , maybe + , field -- Composing , succeed @@ -66,23 +67,23 @@ type Decoder a {-| Decode a string field. -} -string : String -> (String -> Decoder a) -> Decoder a -string fieldName cont = - fieldHelper fieldName Json.Decode.string cont +string : Decoder String +string = + Json.Decode.string |> Decoder {-| Decode an integer field. -} -int : String -> (Int -> Decoder a) -> Decoder a -int fieldName cont = - fieldHelper fieldName Json.Decode.int cont +int : Decoder Int +int = + Json.Decode.int |> Decoder {-| Decode a float field. -} -float : String -> (Float -> Decoder a) -> Decoder a -float fieldName cont = - fieldHelper fieldName Json.Decode.float cont +float : Decoder Float +float = + Json.Decode.float |> Decoder {-| Decode a boolean field. @@ -90,27 +91,24 @@ float fieldName cont = Booleans in sqlite are stored as integers with 1 and 0 as True and False. See -} -bool : String -> (Bool -> Decoder a) -> Decoder a -bool fieldName cont = - let - boolDecoder = - Json.Decode.int - |> Json.Decode.andThen - (\i -> - when i is - 0 -> - Json.Decode.succeed False - - 1 -> - Json.Decode.succeed True - - n -> - Json.Decode.fail <| - "Expected 0 or 1 in boolean field, got " ++ - String.fromInt n - ) - in - fieldHelper fieldName boolDecoder cont +bool : Decoder Bool +bool = + Json.Decode.int + |> Json.Decode.andThen + (\i -> + when i is + 0 -> + Json.Decode.succeed False + + 1 -> + Json.Decode.succeed True + + n -> + Json.Decode.fail <| + "Expected 0 or 1 in boolean field, got " ++ + String.fromInt n + ) + |> Decoder {-| Decode a JSON field. @@ -122,28 +120,25 @@ Use `json()` in your SELECT to ensure you get text regardless of how the JSON wa { query = "SELECT json(tags) as tags FROM items WHERE id = 1" , parameters = [] , rowDecoder = - Sqlite.Decode.json (Json.Decode.array Json.Decode.string) "tags" <| \tags -> + Decode.field "tags" (Decode.json (Json.Decode.array Json.Decode.string)) <| \tags -> Sqlite.Decode.succeed { tags = tags } } See -} -json : Json.Decode.Decoder a -> String -> (a -> Decoder b) -> Decoder b -json jsonDecoder fieldName cont = - let - decoder = - Json.Decode.string - |> Json.Decode.andThen - (\str -> - when Json.Decode.decodeString jsonDecoder str is - Ok val -> - Json.Decode.succeed val - - Err err -> - Json.Decode.fail (Json.Decode.errorToString err) - ) - in - fieldHelper fieldName decoder cont +json : Json.Decode.Decoder a -> Decoder a +json jsonDecoder = + Json.Decode.string + |> Json.Decode.andThen + (\str -> + when Json.Decode.decodeString jsonDecoder str is + Ok val -> + Json.Decode.succeed val + + Err err -> + Json.Decode.fail (Json.Decode.errorToString err) + ) + |> Decoder {-| Decode a Time.Posix value. @@ -154,14 +149,12 @@ how both [Sqlite.Encode.time](Sqlite.Encode#time) and which aligns with SQLite's `unixepoch` function. See -} -time : String -> (Time.Posix -> Decoder a) -> Decoder a -time fieldName cont = - fieldHelper fieldName - (Json.Decode.map +time : Decoder Time.Posix +time = + Json.Decode.float + |> Json.Decode.map (\seconds -> Time.millisToPosix (Math.round (seconds * 1000.0))) - Json.Decode.float - ) - cont + |> Decoder {-| Decode a nullable field in the database. @@ -169,17 +162,15 @@ time fieldName cont = The first parameter is the field decoder function for the type if the value is not null. For example, to decode a nullable TEXT field: - Sqlite.Decode.maybe Decode.string "nickname" <| \maybeNickname -> + Decode.field (Decode.maybe Decode.string) "nickname" <| \maybeNickname -> Sqlite.Decode.succeed maybeNickname -} -maybe : (String -> (a -> Decoder b) -> Decoder b) -> String -> (Maybe a -> Decoder b) -> Decoder b -maybe decoderFn fieldName cont = +maybe : Decoder a -> Decoder (Maybe a) +maybe decoder = Decoder <| Json.Decode.oneOf - [ unwrap (decoderFn fieldName (\val -> cont (Just val))) - , Json.Decode.andThen - (\_ -> unwrap (cont Nothing)) - (Json.Decode.field fieldName (Json.Decode.null {})) + [ unwrap decoder |> Json.Decode.map Just + , Json.Decode.succeed Nothing ] @@ -218,12 +209,17 @@ toJson = unwrap -fieldHelper : String -> Json.Decode.Decoder a -> (a -> Decoder b) -> Decoder b -fieldHelper fieldName jsonDecoder cont = +fieldHelper : Json.Decode.Decoder a -> (a -> Decoder b) -> Decoder b +fieldHelper jsonDecoder cont = Decoder <| Json.Decode.andThen (\val -> unwrap (cont val)) - (Json.Decode.field fieldName jsonDecoder) + jsonDecoder + + +field : String -> Decoder a -> (a -> Decoder b) -> Decoder b +field fieldName decoder cont = + fieldHelper (Json.Decode.field fieldName (unwrap decoder)) cont unwrap : Decoder a -> Json.Decode.Decoder a