Add GADT typed actions with OpenAPI routes#2665
Conversation
| , body :: Text | ||
| , published :: Bool | ||
| } | ||
| deriving (Generic) |
There was a problem hiding this comment.
Can we avoid Generic?
Generics are very slow on large records and break IHPs live reloading (because eventually it needs 10s for a reload)
With coding agents at least the boilerplate is quickly done :D
There was a problem hiding this comment.
hum, didn't know about that.
it breaks live reloading when editing the record you mean ?
Because I have 100 generic deriving types in my Application/Types and never saw a latency.
handwriten boilerplate can diverge though, could it be a macro that generates only the instance we need without the rest of generic ?
But for a json api it will also need FromJSON instance and ToSchema for the openapi..
There was a problem hiding this comment.
It only happens when a data structure has many fields or many constructors. See https://gitlab.haskell.org/ghc/ghc/-/issues/5642
I guess your 100 generic types are mostly small data structures (few fields, single constructor). For these cases it works. But in large IHP apps sometimes data structures have 30 fields or more (e.g. a users table might have 30 columns)
For the aeson JSON instances, can't we use template haskell?
There was a problem hiding this comment.
should work for aeson yes,
What about ToSchema and form url encoding parsing ?
should we add some deriving TH, like https://github.com/ilyakooo0/deriving-openapi3/tree/master ?
| <input ... value="Hello World" /> | ||
| ``` | ||
|
|
||
| ## Forms for Typed Actions |
There was a problem hiding this comment.
Can you add a motivation why as a user from formFor I would want to use formForAction?
There was a problem hiding this comment.
I added more details, it is not entirerly clear for me what drift it prevent in details compared to formfor on a model.
But it adds the possibility to do typed form without a model.
4bb83d7 to
f81c083
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces an opt-in typed routing/controller layer (GADT indexed actions with typed request bodies and typed JSON responses) and builds OpenAPI/Swagger UI output directly from the route DSL + typed action/view metadata.
Changes:
- Add typed action infrastructure (
BodySpec/body decoding, typed-route dispatch, typed form helpers, method lookup, and response/status metadata). - Add OpenAPI generation + Swagger UI controller actions, including route-level metadata (
summary,tags,operationId,success/response,private) and schema derivation. - Update routing DSL/parser and tests to support route annotations, plus package-qualified imports/wrappers around
ihp-routermodules.
Reviewed changes
Copilot reviewed 58 out of 58 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| UPGRADE.md | Updates migration guidance for JSON rendering with the new typed JsonView response payload shape. |
| ihp/Test/Test/View/FormForSpec.hs | Renames module to match filename and cleans up formatting. |
| ihp/Test/Test/Router/WebSocketSpec.hs | Adjusts router trie import to disambiguate packages. |
| ihp/Test/Test/Router/TrieSpec.hs | Uses package-qualified import for IHP.Router.Trie. |
| ihp/Test/Test/Router/MultiControllerSpec.hs | Uses package-qualified imports for router DSL and capture. |
| ihp/Test/Test/Router/MixedModeSpec.hs | Uses package-qualified imports for router DSL and capture. |
| ihp/Test/Test/Router/MiddlewareSpec.hs | Uses package-qualified imports for trie/middleware modules. |
| ihp/Test/Test/Router/DSLQuoterSpec.hs | Uses package-qualified imports for router DSL and capture. |
| ihp/Test/Test/Router/DSLParserSpec.hs | Extends parser tests for route metadata/annotations and new route AST shape. |
| ihp/Test/Test/Router/CaptureSpec.hs | Uses package-qualified import for capture module. |
| ihp/Test/Test/Router/AppBindingSpec.hs | Uses package-qualified imports for router DSL and capture. |
| ihp/Test/Test/OpenApiSupportSpec.hs | Adds end-to-end tests for typed JSON rendering, OpenAPI generation, and Swagger UI routes. |
| ihp/Test/Test/Main.hs | Wires new/renamed specs into the test runner. |
| ihp/Test/Test/Controller/TypedActionSpec.hs | Adds tests for typed body decoding, typed routing, typed forms, and OpenAPI metadata. |
| ihp/Test/RouterBench.hs | Uses package-qualified import for trie types. |
| ihp/IHP/ViewSupport.hs | Evolves JsonView to return a typed payload via JsonResponse associated type. |
| ihp/IHP/ViewPrelude.hs | Re-exports IHP.OpenApiSupport. |
| ihp/IHP/View/Types.hs | Extends FormContext with enctype, stable field IDs, validation lookup, and default submit label. |
| ihp/IHP/View/Form/TypedAction.hs | Adds compatibility re-export module for typed action form helpers. |
| ihp/IHP/View/Form/Select.hs | Adds selectFieldOptions/radioFieldOptions for plain input records; adds (Text, Text) CanSelect instance. |
| ihp/IHP/View/Form/FormFor.hs | Adds typed-action form rendering (formForAction*), form enctype, stable field IDs, and submit label wiring. |
| ihp/IHP/View/Form/Fields.hs | Refactors field helpers to use FormContext’s ID/validation functions; updates checkbox handling. |
| ihp/IHP/View/Form.hs | Re-exports IHP.View.Form.TypedAction. |
| ihp/IHP/TypedControllerPrelude.hs | Adds a prelude for typed controllers that hides conflicting classic controller functions. |
| ihp/IHP/Server.hs | Inserts request-context middleware into the standard middleware stack. |
| ihp/IHP/RouterPrelude.hs | Re-exports typed route support and Swagger UI controller type. |
| ihp/IHP/Router/UrlGenerator.hs | Adds wrapper module re-exporting ihp-router URL generator. |
| ihp/IHP/Router/Types.hs | Removes legacy ControllerRoute definition (now centralized in RouterSupport). |
| ihp/IHP/Router/TypedRoute.hs | Adds runtime support for typed GADT routing + OpenAPI render expectation validation hooks. |
| ihp/IHP/Router/Trie.hs | Adds wrapper module re-exporting ihp-router trie. |
| ihp/IHP/Router/Middleware.hs | Adds wrapper module re-exporting ihp-router middleware. |
| ihp/IHP/Router/IHP.hs | Extends TH route emitter to support typed GADT actions, metadata parsing, response/status handling, and OpenAPI docs emission. |
| ihp/IHP/Router/DSL/TH.hs | Adds wrapper module re-exporting ihp-router TH utilities. |
| ihp/IHP/Router/DSL/Runtime.hs | Adds wrapper module re-exporting ihp-router runtime DSL utilities. |
| ihp/IHP/Router/DSL/Parser.hs | Adds wrapper module re-exporting ihp-router parser. |
| ihp/IHP/Router/DSL/AST.hs | Adds wrapper module re-exporting ihp-router AST. |
| ihp/IHP/Router/Capture.hs | Adds wrapper module re-exporting ihp-router capture logic. |
| ihp/IHP/OpenApiSupport.hs | Adds OpenAPI schema derivation utilities, OpenAPI document generator, and Swagger UI controller/actions. |
| ihp/IHP/ErrorController.hs | Adds request-context exception wrapping and richer 5xx request logging (including body decode summary). |
| ihp/IHP/ControllerSupport.hs | Generalizes controller dispatch via ControllerAction + runControllerAction; wraps action/initContext in request-context handling. |
| ihp/IHP/ControllerPrelude.hs | Exposes typed-action types/support in the prelude (while avoiding method name clashes). |
| ihp/IHP/Controller/TypedAction.hs | Adds typed request body specs, decoding, request body OpenAPI docs, and the TypedController class. |
| ihp/IHP/Controller/Render.hs | Introduces renderHtmlOrJsonWithStatusCode and validates typed OpenAPI render expectations on JSON responses. |
| ihp/IHP/AutoRefresh.hs | Updates auto-refresh flow to use runControllerAction. |
| ihp/IHP/AuthSupport/Controller/Sessions.hs | Removes redundant View import now covered by prelude exports. |
| ihp/ihp.cabal | Adds new modules and dependencies for OpenAPI/typed actions and router wrappers. |
| ihp/default.nix | Adds openapi3 (and hspec) to the Nix derivation inputs. |
| ihp-router/IHP/Router/DSL/TH.hs | Extends TH metadata/reification to support GADTs, controller type vars, route annotations, and path template generation. |
| ihp-router/IHP/Router/DSL/Parser.hs | Adds parsing for indented metadata lines and improves parse errors (e.g. missing HTTP method). |
| ihp-router/IHP/Router/DSL/AST.hs | Extends route AST to include route annotations and updates documentation/comments. |
| ihp-ide/IHP/IDE/CodeGen/ApplicationGenerator.hs | Adds guidance comment for generating OpenAPI docs from routes. |
| Guide/view.markdown | Updates JSON rendering documentation to use renderHtmlOrJson and JsonView. |
| Guide/routing.markdown | Adds documentation for typed GADT routes, metadata annotations, and OpenAPI integration. |
| Guide/json-api.markdown | Documents typed request bodies and OpenAPI/Swagger UI usage. |
| Guide/form.markdown | Documents formForAction for typed actions and typed form encoding/method behavior. |
| devenv-module.nix | Adds openapi3 to the dev environment package set. |
| cabal.project | Adds wai-early-return/ as a local package. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| -- After | ||
| instance View ShowView where | ||
| html ShowView { .. } = [hsx|...|] | ||
| type JsonResponse ShowView = Post | ||
|
|
||
| instance JsonView ShowView where | ||
| json ShowView { .. } = toJSON post | ||
| html ShowView { .. } = [hsx|...|] | ||
| json ShowView { .. } = post |
| > [routes|ControllerName | ||
| > GET /posts PostsAction | ||
| > POST /posts CreatePostAction | ||
| > /posts PostsAction |
| , wai-cors | ||
| , random | ||
| , openapi3 | ||
| , hspec | ||
| , cereal |
| -- | A view that can also be rendered as JSON by 'renderHtmlOrJson'. | ||
| class JsonView theView where | ||
| json :: theView -> JSON.Value | ||
| type JsonResponse theView :: Type | ||
| type JsonResponse theView = JSON.Value | ||
|
|
||
| -- | Returns the typed JSON response payload for this view. | ||
| -- | ||
| -- IHP renders JSON views by applying 'toJSON' to this value. This keeps | ||
| -- the rendered JSON and OpenAPI response schema tied to the same | ||
| -- 'JsonResponse' type. | ||
| json :: theView -> JsonResponse theView | ||
| json _ = error "Json View for this route is not implemented" | ||
|
|
| import IHP.Router.DSL (routes) | ||
| import IHP.ControllerPrelude | ||
| import qualified IHP.WebSocket as WS | ||
| import qualified IHP.Router.Trie as Trie | ||
| import qualified "ihp-router" IHP.Router.Trie as Trie | ||
| import qualified Data.ByteString.Lazy as LBS |
f81c083 to
12a1433
Compare

Summary