git.iko.soy/rowtype-yoga/purescript-yoga-http-api
on feat/custom-type-errors help sign in
Add missing UnionTrick import
commitbee01ac123c9bd21fc68a5a49cf7662cd8a43e48 authorMark <mark.eibes@gmail.com> dateSun 8 Feb 2026, 15:16 (8 months ago) parente12d5cd
1 file changed+62−31
src/Yoga/HTTP/API/Route/OpenAPI.purs+62−31
src/Yoga/HTTP/API/Route/OpenAPI.purs+62−31
@@ -61,7 +61,10 @@ import Prim.Row as Row
6161import Prim.RowList (class RowToList, RowList, Cons, Nil)
6262import Prim.RowList as RL
6363import Type.Proxy (Proxy(..))
64+import Literals.Undefined (Undefined)
65+import Untagged.Union (OneOf)
6466import Unsafe.Coerce (unsafeCoerce)
67+import Yoga.HTTP.API.UnionTrick (uorToMaybe, class Options, options)
6568import Yoga.HTTP.API.Route.BearerToken (BearerToken)
6669import Yoga.HTTP.API.Route.Encoding (JSON, FormData, NoBody)
6770import Yoga.HTTP.API.Route.HeaderValue (class HeaderValueType, headerValueType)
@@ -927,56 +930,84 @@ type ServerObject =
927930 , description :: Maybe String
928931 }
929932
933+type ContactInfoR =
934+ ( name :: String
935+ , url :: String
936+ , email :: String
937+ )
938+
939+type LicenseInfoR =
940+ ( name :: String
941+ , url :: String
942+ )
943+
944+type OpenAPIInfoR f =
945+ ( title :: String
946+ , version :: String
947+ , description :: f String
948+ , contact :: f { | ContactInfoR }
949+ , license :: f { | LicenseInfoR }
950+ )
951+
930952-- | Build a complete OpenAPI 3.0 spec from a type-level collection of routes.
931953-- |
932954-- | Examples:
933−-- | -- Basic usage:
934−-- | buildOpenAPISpec @(HealthRoute /\ UserRoute)
955+-- | buildOpenAPISpec @MyAPI
935956-- | { title: "My API", version: "1.0.0" }
936957-- |
937−-- | -- With servers:
938−-- | buildOpenAPISpec' @(HealthRoute /\ UserRoute)
958+-- | buildOpenAPISpec @MyAPI
939959-- | { title: "My API"
940960-- | , version: "1.0.0"
941−-- | , servers: Just
942−-- | [ { url: "https://api.example.com", description: Just "Production server" }
943−-- | , { url: "https://staging-api.example.com", description: Just "Staging server" }
944−-- | ]
961+-- | , description: "A comprehensive API"
962+-- | , contact: { name: "Support", email: "api@example.com" }
963+-- | , license: { name: "MIT" }
945964-- | }
946965buildOpenAPISpec
947− :: forall @routes
966+ :: forall @routes r
948967 . CollectOperations routes
949− => { title :: String, version :: String }
968+ => Options r (OpenAPIInfoR (OneOf Undefined))
969+ => { | r }
950970 -> OpenAPISpec
951−buildOpenAPISpec info = buildOpenAPISpec' @routes info { servers: Nothing }
971+buildOpenAPISpec given = buildOpenAPISpec' @routes (options @(OpenAPIInfoR (OneOf Undefined)) given) { servers: Nothing }
952972
953973-- | Build a complete OpenAPI 3.0 spec with optional servers configuration.
954974buildOpenAPISpec'
955975 :: forall @routes
956976 . CollectOperations routes
957− => { title :: String, version :: String }
977+ => { | OpenAPIInfoR (OneOf Undefined) }
958978 -> { servers :: Maybe (Array ServerObject) }
959979 -> OpenAPISpec
960980buildOpenAPISpec' info config =
961− let
962− ops = collectOperations (Proxy :: Proxy routes)
963− paths = groupByPath ops
964− securitySchemes = buildSecuritySchemes ops
965− components =
966− if FObject.isEmpty securitySchemes then Nothing
967− else Just { securitySchemes }
968− baseSpec = FObject.fromFoldable
969− [ Tuple "openapi" (unsafeCoerce "3.0.0")
970− , Tuple "info" (unsafeCoerce { title: info.title, version: info.version })
971− , Tuple "paths" (unsafeCoerce paths)
972− ]
973− withComponents = case components of
974− Nothing -> baseSpec
975− Just c -> FObject.insert "components" (unsafeCoerce c) baseSpec
976− withServers = case config.servers of
977− Nothing -> withComponents
978− Just servers -> FObject.insert "servers" (writeImpl servers) withComponents
979− in
981+ do
982+ let
983+ ops = collectOperations (Proxy :: Proxy routes)
984+ paths = groupByPath ops
985+ securitySchemes = buildSecuritySchemes ops
986+ components =
987+ if FObject.isEmpty securitySchemes then Nothing
988+ else Just { securitySchemes }
989+ infoBase = FObject.fromFoldable
990+ [ Tuple "title" (writeImpl info.title)
991+ , Tuple "version" (writeImpl info.version)
992+ ]
993+ infoWithDescription = maybe infoBase (\desc -> FObject.insert "description" (writeImpl desc) infoBase) (uorToMaybe info.description)
994+ infoWithContact = maybe infoWithDescription
995+ (\contact -> FObject.insert "contact" (writeImpl contact) infoWithDescription)
996+ (uorToMaybe info.contact)
997+ infoWithLicense = maybe infoWithContact
998+ (\license -> FObject.insert "license" (writeImpl license) infoWithContact)
999+ (uorToMaybe info.license)
1000+ baseSpec = FObject.fromFoldable
1001+ [ Tuple "openapi" (unsafeCoerce "3.0.0")
1002+ , Tuple "info" (unsafeCoerce infoWithLicense)
1003+ , Tuple "paths" (unsafeCoerce paths)
1004+ ]
1005+ withComponents = case components of
1006+ Nothing -> baseSpec
1007+ Just c -> FObject.insert "components" (unsafeCoerce c) baseSpec
1008+ withServers = case config.servers of
1009+ Nothing -> withComponents
1010+ Just servers -> FObject.insert "servers" (writeImpl servers) withComponents
9801011 unsafeCoerce $ withServers
9811012
9821013-- | Convert `:param` to `{param}` for OpenAPI path format