1module Test.BasicAPI where
5import Control.Monad.Reader.Trans (ask)
6import Data.Either (Either(..))
7import Data.Generic.Rep (class Generic)
8import Data.Maybe (Maybe(..))
9import Data.Newtype (class Newtype)
10import Data.Show.Generic (genericShow)
11import Data.Tuple (Tuple(..))
14import Effect.Aff as Aff
15import Effect.Class (liftEffect)
16import Effect.Class.Console as Console
17import Foreign (Foreign, unsafeToForeign)
18import Foreign.Object as FObject
19import Type.Proxy (Proxy(..))
20import Yoga.Fastify.Fastify (Fastify, Host(..), Port(..))
21import Yoga.Fastify.Fastify as F
22import Yoga.Fastify.Om.API (registerAPI)
23import Yoga.Fastify.Om.Route (GET, POST, Route, Request, Handler, handle, respond, reject, buildOpenAPISpec, class HeaderValueType, BearerToken, Enum, class RenderJSONSchema, class HasEnum, enum)
24import Yoga.HTTP.API.Route.OpenAPI (class CollectSchemas, class CollectSchemaNames)
25import Yoga.HTTP.API.Path (class ParseParam, parseParam, type (/), type (:), type (:?))
26import Yoga.JSON (class ReadForeign, class WriteForeign, writeJSON)
27import Yoga.JSON.Generics (genericWriteForeignEnum, genericReadForeignEnum)
28import Yoga.JSON.Generics.EnumSumRep as GenericJSON
31main = Aff.launchAff_ do
32 server <- createServer # liftEffect
33 address <- F.listen { port: Port 3000, host: Host "0.0.0.0" } server
34 Console.log $ "Server listening on " <> address
45type CreateUserRequest =
51type ErrorResponse = { error :: String }
56type HealthRoute = Route GET "health"
58 ( ok :: { body :: { status :: String } }
59 , notFound :: { body :: ErrorResponse }
62healthHandler :: Handler HealthRoute ()
63healthHandler = handle do
64 respond @"ok" { status: "healthy" }
66newtype UserId = UserId Int
68derive instance Newtype UserId _
69derive newtype instance Eq UserId
70derive newtype instance WriteForeign UserId
71derive newtype instance ReadForeign UserId
73instance RenderJSONSchema UserId where
74 renderJSONSchema _ = unsafeToForeign $ FObject.fromFoldable
75 [ Tuple "type" (unsafeToForeign "integer") ]
77instance CollectSchemas UserId where
78 collectSchemas _ = FObject.empty
80instance CollectSchemaNames UserId ()
82instance ParseParam UserId where
83 parseParam s = parseParam s >>= \n ->
84 if n > 0 then Right (UserId n)
85 else Left "UserId must be positive"
87instance HeaderValueType UserId where
88 headerValueType _ = "integer"
90-- GET /users/:id (with authentication)
91type UserRoute = Route GET
92 ("users" / "id" : UserId)
93 { headers :: { authorization :: BearerToken } }
94 ( ok :: { body :: User }
95 , notFound :: { body :: ErrorResponse }
96 , unauthorized :: { body :: ErrorResponse }
99userHandler :: Handler UserRoute ()
100userHandler = handle do
103 when (path.id /= UserId 1) do
104 reject @404 { error: "User not found" }
109 , email: "alice@example.com"
113-- GET /users?limit=10
114type UsersWithLimitRoute = Route GET
115 ("users" :? { limit :: Int })
117 ( ok :: { body :: { users :: Array User, limit :: Maybe Int } }
120usersWithLimitHandler :: Handler UsersWithLimitRoute ()
121usersWithLimitHandler = handle do
125 [ { id: UserId 1, name: "Alice", email: "alice@example.com", role: Admin }
126 , { id: UserId 2, name: "Bob", email: "bob@example.com", role: Member }
131-- POST /users (with authentication)
132type CreateUserRoute = Route POST
135 { headers :: { authorization :: BearerToken }
136 , body :: CreateUserRequest
139 ( created :: { body :: User }
140 , badRequest :: { body :: ErrorResponse }
141 , unauthorized :: { body :: ErrorResponse }
144createUserHandler :: Handler CreateUserRoute ()
145createUserHandler = handle do
147 when (body.name == "") do
148 reject @400 { error: "Name cannot be empty" }
156-- GET /openapi - Serve OpenAPI spec
157type OpenAPIRoute = Route GET
160 ( ok :: { body :: String }
163-- Define all API routes for OpenAPI generation
165 { health :: HealthRoute
167 , usersWithLimit :: UsersWithLimitRoute
168 , createUser :: CreateUserRoute
171openapiHandler :: Handler OpenAPIRoute ()
172openapiHandler = handle do
176 buildOpenAPISpec @AllApiRoutes
177 { title: "Example API"
181-- Full API type including all registered routes
183 { health :: HealthRoute
185 , usersWithLimit :: UsersWithLimitRoute
186 , createUser :: CreateUserRoute
187 , openapi :: OpenAPIRoute
192createServer :: Effect Fastify
194 fastify <- F.fastify {}
197 { health: healthHandler
199 , usersWithLimit: usersWithLimitHandler
200 , createUser: createUserHandler
201 , openapi: openapiHandler
208data UserRole = Admin | Member | Guest
210derive instance Generic UserRole _
211instance Show UserRole where
214instance WriteForeign UserRole where
215 writeImpl = genericWriteForeignEnum GenericJSON.defaultOptions
217instance ReadForeign UserRole where
218 readImpl = genericReadForeignEnum GenericJSON.defaultOptions
220instance RenderJSONSchema UserRole where
221 renderJSONSchema _ = do
223 enumValues = enum (Proxy :: Proxy (Enum UserRole))
224 baseSchema = FObject.fromFoldable
225 [ Tuple "type" (unsafeToForeign "string")
228 Nothing -> unsafeToForeign baseSchema
229 Just vals -> unsafeToForeign $ FObject.insert "enum" (unsafeToForeign vals) baseSchema
231instance CollectSchemas UserRole where
232 collectSchemas _ = FObject.empty
234instance CollectSchemaNames UserRole ()
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list