1{-# LANGUAGE RankNTypes #-}
3-- Module: Data.Swagger.Operation
4-- Copyright: (c) 2015 GetShopTV
6-- Maintainer: Nickolay Kudasov <nickolay@getshoptv.com>
7-- Stability: experimental
9-- Helper traversals and functions for Swagger operations manipulations.
10-- These might be useful when you already have Swagger specification
11-- generated by something else.
12module Data.Swagger.Operation (
13 -- * Operation traversals
35import Control.Applicative
39import qualified Data.HashMap.Strict as HashMap
41import Data.Maybe (mapMaybe)
43import qualified Data.Set as Set
44import Data.Traversable
46import Data.Swagger.Declare
47import Data.Swagger.Internal
48import Data.Swagger.Lens
49import Data.Swagger.Schema
52-- >>> import Data.Aeson
53-- >>> import Data.Proxy
54-- >>> import Data.Time
56-- | Prepend path piece to all operations of the spec.
57-- Leading and trailing slashes are trimmed/added automatically.
59-- >>> let api = (mempty :: Swagger) & paths .~ [("/info", mempty)]
60-- >>> encode $ prependPath "user/{user_id}" api ^. paths
61-- "{\"/user/{user_id}/info\":{}}"
62prependPath :: FilePath -> Swagger -> Swagger
63prependPath path = paths %~ mapKeys (path </>)
65 mapKeys f = HashMap.fromList . map (first f) . HashMap.toList
67 x </> y = case trim y of
69 y' -> "/" <> trim x <> "/" <> y'
71 trim = dropWhile (== '/') . dropWhileEnd (== '/')
73-- | All operations of a Swagger spec.
74allOperations :: Traversal' Swagger Operation
75allOperations = paths.traverse.template
77-- | @'operationsOf' sub@ will traverse only those operations
78-- that are present in @sub@. Note that @'Operation'@ is determined
79-- by both path and method.
81-- >>> let ok = (mempty :: Operation) & at 200 ?~ "OK"
82-- >>> let api = (mempty :: Swagger) & paths .~ [("/user", mempty & get ?~ ok & post ?~ ok)]
83-- >>> let sub = (mempty :: Swagger) & paths .~ [("/user", mempty & get ?~ mempty)]
85-- "{\"swagger\":\"2.0\",\"info\":{\"version\":\"\",\"title\":\"\"},\"paths\":{\"/user\":{\"post\":{\"responses\":{\"200\":{\"description\":\"OK\"}}},\"get\":{\"responses\":{\"200\":{\"description\":\"OK\"}}}}}}"
86-- >>> encode $ api & operationsOf sub . at 404 ?~ "Not found"
87-- "{\"swagger\":\"2.0\",\"info\":{\"version\":\"\",\"title\":\"\"},\"paths\":{\"/user\":{\"post\":{\"responses\":{\"200\":{\"description\":\"OK\"}}},\"get\":{\"responses\":{\"404\":{\"description\":\"Not found\"},\"200\":{\"description\":\"OK\"}}}}}}"
88operationsOf :: Swagger -> Traversal' Swagger Operation
89operationsOf sub = paths.itraversed.withIndex.subops
91 -- | Traverse operations that correspond to paths and methods of the sub API.
92 subops :: Traversal' (FilePath, PathItem) Operation
93 subops f (path, item) = case HashMap.lookup path (sub ^. paths) of
94 Just subitem -> (,) path <$> methodsOf subitem f item
95 Nothing -> pure (path, item)
97 -- | Traverse operations that exist in a given @'PathItem'@
98 -- This is used to traverse only the operations that exist in sub API.
99 methodsOf :: PathItem -> Traversal' PathItem Operation
100 methodsOf pathItem = partsOf template . itraversed . indices (`elem` ns) . _Just
102 ops = pathItem ^.. template :: [Maybe Operation]
103 ns = mapMaybe (fmap fst . sequenceA) $ zip [0..] ops
105-- | Apply tags to all operations and update the global list of tags.
108-- 'applyTags' = 'applyTagsFor' 'allOperations'
110applyTags :: [Tag] -> Swagger -> Swagger
111applyTags = applyTagsFor allOperations
113-- | Apply tags to a part of Swagger spec and update the global
115applyTagsFor :: Traversal' Swagger Operation -> [Tag] -> Swagger -> Swagger
116applyTagsFor ops ts swag = swag
117 & ops . tags %~ (<> Set.fromList (map _tagName ts))
118 & tags %~ (<> Set.fromList ts)
120-- | Construct a response with @'Schema'@ while declaring all
121-- necessary schema definitions.
123-- >>> encode $ runDeclare (declareResponse (Proxy :: Proxy Day)) mempty
124-- "[{\"Day\":{\"format\":\"date\",\"type\":\"string\"}},{\"schema\":{\"$ref\":\"#/definitions/Day\"},\"description\":\"\"}]"
125declareResponse :: ToSchema a => proxy a -> Declare (Definitions Schema) Response
126declareResponse proxy = do
127 s <- declareSchemaRef proxy
128 return (mempty & schema ?~ s)
130-- | Set response for all operations.
131-- This will also update global schema definitions.
133-- If the response already exists it will be overwritten.
136-- 'setResponse' = 'setResponseFor' 'allOperations'
141-- >>> let api = (mempty :: Swagger) & paths .~ [("/user", mempty & get ?~ mempty)]
142-- >>> let res = declareResponse (Proxy :: Proxy Day)
143-- >>> encode $ api & setResponse 200 res
144-- "{\"swagger\":\"2.0\",\"info\":{\"version\":\"\",\"title\":\"\"},\"definitions\":{\"Day\":{\"format\":\"date\",\"type\":\"string\"}},\"paths\":{\"/user\":{\"get\":{\"responses\":{\"200\":{\"schema\":{\"$ref\":\"#/definitions/Day\"},\"description\":\"\"}}}}}}"
146-- See also @'setResponseWith'@.
147setResponse :: HttpStatusCode -> Declare (Definitions Schema) Response -> Swagger -> Swagger
148setResponse = setResponseFor allOperations
150-- | Set or update response for all operations.
151-- This will also update global schema definitions.
153-- If the response already exists, but it can't be dereferenced (invalid @\$ref@),
154-- then just the new response is used.
157-- 'setResponseWith' = 'setResponseForWith' 'allOperations'
160-- See also @'setResponse'@.
161setResponseWith :: (Response -> Response -> Response) -> HttpStatusCode -> Declare (Definitions Schema) Response -> Swagger -> Swagger
162setResponseWith = setResponseForWith allOperations
164-- | Set response for specified operations.
165-- This will also update global schema definitions.
167-- If the response already exists it will be overwritten.
169-- See also @'setResponseForWith'@.
170setResponseFor :: Traversal' Swagger Operation -> HttpStatusCode -> Declare (Definitions Schema) Response -> Swagger -> Swagger
171setResponseFor ops code dres swag = swag
172 & definitions %~ (<> defs)
173 & ops . at code ?~ Inline res
175 (defs, res) = runDeclare dres mempty
177-- | Set or update response for specified operations.
178-- This will also update global schema definitions.
180-- If the response already exists, but it can't be dereferenced (invalid @\$ref@),
181-- then just the new response is used.
183-- See also @'setResponseFor'@.
184setResponseForWith :: Traversal' Swagger Operation -> (Response -> Response -> Response) -> HttpStatusCode -> Declare (Definitions Schema) Response -> Swagger -> Swagger
185setResponseForWith ops f code dres swag = swag
186 & definitions %~ (<> defs)
187 & ops . at code %~ Just . Inline . combine
189 (defs, new) = runDeclare dres mempty
191 combine (Just (Ref (Reference name))) = case swag ^. responses.at name of
192 Just old -> f old new
193 Nothing -> new -- response name can't be dereferenced, replacing with new response
194 combine (Just (Inline old)) = f old new
195 combine Nothing = new
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