Skip to content

toActionInput throws input holes, fixes #194#202

Open
seanhess wants to merge 7 commits intomainfrom
toActionInput-fix
Open

toActionInput throws input holes, fixes #194#202
seanhess wants to merge 7 commits intomainfrom
toActionInput-fix

Conversation

@seanhess
Copy link
Copy Markdown
Owner

@seanhess seanhess commented Apr 29, 2026

See #194

Instead of using undefined to serialize a partially applied constructor, we throw InputHole - we catch it during serialization and turn it into a Hole.

This inspired a refactor to remove Web.Hyperbole.Data.Param. Everything now uses ToJSON as a generic serialization format, then encodes in its own way. This changes QueryData, Session, and Encoded (ViewId, ViewAction)

@seanhess seanhess force-pushed the toActionInput-fix branch 4 times, most recently from 89d24ac to 16b6f3a Compare May 1, 2026 17:48
@seanhess
Copy link
Copy Markdown
Owner Author

seanhess commented May 1, 2026

What about forcing the user to be more explicit? It might help users avoid doing crazy things with (input -> Action view)

In the latest push, onInput (Text -> Action view) works out of the box. Other types must implement UserInput. It doesn't assume it knows how you want to parse numbers from an <input>, for example.

-- From Web.Hyperbole.Data.Argument
class UserInput a where
  parseInput :: Text -> Either String a
  default parseInput :: (FromJSON a) => Text -> Either String a
  parseInput = decodeArgument


instance UserInput Text where
  parseInput = pure


instance {-# OVERLAPPABLE #-} (UserInput a) => UserInput (Maybe a) where
  parseInput "" = pure Nothing
  parseInput t = Just <$> parseInput @a t


instance {-# OVERLAPS #-} UserInput (Maybe Text) where
  parseInput = pure . Just

-- Examples: 


-- If you want another type as an input, you must implement UserInput. We don't automatically parse integers from search fields
newtype NumInput = NumInput Int
  deriving (Generic, Show, Eq)
  deriving anyclass (UserInput, FromJSON, ToJSON)

data Custom = Custom 
  deriving (Generic, ToJSON, FromJSON, UserInput)

data NotAnInput = NotAnInput Int Text
  deriving (Generic, ToJSON, FromJSON)

data SomeAction
   = IntegerInput Int NumInput
   | OutOfOrder NumInput Int
   | TextInput Text
   | NullableInput (Mabye Text)
   | CustomInput Custom
   | BadInput Int
   | BadInput2 NotAnInput
  deriving (Generic, ViewAction)
 
 -- These work
toActionInput TextInput
toActionInput NullableInput
toActionInput (IntegerInput 4)
toActionInput (CustomInput)
toActionInput (\n -> OutOfOrder n 4)
 
 -- Throws a runtime error
 toActionInput (NullableInput . Just)
 toActionInput (Nested . NumberPair 0)
 
 -- Compile Errors
toActionInput BadInput
toActionInput (NotAnInput 3)
toActionInput (\n -> NotAnInput n "hello")
toActionInput (BadInput . (*2))
toActionInput (IntegerInput 4 . (*2))

What do you think? How could it be improved? I think this might make people think twice before doing crazy things with the partial constructor.

@seanhess
Copy link
Copy Markdown
Owner Author

seanhess commented May 5, 2026

@kschweppe do you have any thoughts on this solution? I could use a second set of eyes on this.

seanhess added 5 commits May 5, 2026 10:19
working pretty encoding using json instance. holes working too

fix querydata plus sign space encoding

all tests passing
only ViewId, ViewAction use Encoded

tests passing

comments

changelog

changelog

fix rebase

rebase conflicts
@seanhess seanhess force-pushed the toActionInput-fix branch from 93c42d9 to 5258de2 Compare May 5, 2026 17:20
@seanhess seanhess force-pushed the toActionInput-fix branch from 5258de2 to 77b3fdf Compare May 5, 2026 17:21
@kschweppe
Copy link
Copy Markdown

Sure! Sorry for the delay, I just didn't get to have a closer look over the weekend.

I generally agree that it should be more explicit, and was also thinking we need a typeclass for this.
I still need to play around a bit more, but this seems to work well and cover most cases.

Is there any drawback in providing a default implementation for numbers? I guess having number dropdowns is a common use case, for example I have dropdowns for year and month and needed to add NumInput to both.

And then I'm wondering if this can/should also work with search input, which actually was my initial use case.
So I wanted something like search (SearchUser . SearchInput False) (which now at least throws a runtime error). Do you think it makes sense to use UserInput for other events as well, like onInput, or only for onChange?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants