2
votes

I have the following code:

import Control.Lens ((&), (.~), (?~), (%~))
import Data.Swagger (Swagger)
import Data.Swagger.Lens (paths, operationId, info, description)
import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
import Data.Text (Text(..), pack)

genOpIds :: Swagger -> Swagger
genOpIds = paths %~ InsOrdHashMap.mapWithKey (\k v -> v & operationId ?~ (pack "hello"))

Generating the following compile error:

    • No instance for (Data.Swagger.Lens.HasOperationId
                         Data.Swagger.Internal.PathItem (Maybe Text))
        arising from a use of ‘operationId’
    • In the first argument of ‘(?~)’, namely ‘operationId’
      In the second argument of ‘(&)’, namely
        ‘operationId ?~ (pack "hello")’
      In the expression: v & operationId ?~ (pack "hello")
   |
12 | genOpIds = paths %~ InsOrdHashMap.mapWithKey (\k v -> v & operationId ?~ (pack "hello"))
   |                                                           ^^^^^^^^^^^

I'm finding it a bit difficult to diagnose, as I think makeLenses is used to generate the class instances. I'm struggling a bit to see how this is different to the following (which compiles fine):

writeInfoTitle :: Swagger -> Swagger
writeInfoTitle = info.description ?~ (pack "whatever description")

as the type hierarchy involved is similar at the bottom. I suspect there's a fundamental bit of understanding I'm missing here, but I've read the lens tutorial, including the section on traversals; various bits of the servant swagger code, and some other examples and haven't managed to figure this out.

To recreate the error message easily, you could clone the following repo and stack build:

https://github.com/msk-/improved-spork
1

1 Answers

2
votes

Disclaimer : I never used Swagger. However, looking at the documentation for it, I can see the following element in the Swagger type :
_swaggerPaths :: InsOrdHashMap FilePath PathItem - corresponding to the paths Lens.
Looking at the definition of PathItem reveals that it is not an instance of HasOperationId - it itself is composed of

PathItem     
  _pathItemGet :: Maybe Operation   
  _pathItemPut :: Maybe Operation   
  _pathItemPost :: Maybe Operation  
  _pathItemDelete :: Maybe Operation    
  _pathItemOptions :: Maybe Operation   
  _pathItemHead :: Maybe Operation  
  _pathItemPatch :: Maybe Operation 
  _pathItemParameters :: [Referenced Param]

which would seem to suggest that what you are missing a Lens composition in
(\k v -> v & (??? . operationId) ?~ (pack "hello")); something similar to
(\k v -> v & (itemGet . operationId) ?~ (pack "hello"))

EDIT : It looks like what you are trying to achieve (setting up a simple path operation) can be easily done by using the Monoid instance of the PathItem record, like this:

genOpsId = paths %~ InsOrdHashMap.mapWithKey (\k v -> v & get ?~ (mempty & operationId ?~ (pack "hello")))



EDIT#2: As requested, here is one way to iterate over the fields of the PathItem record and set the operationId of those that are present.
For the record, I'm pretty sure there's a way, way, way cleaner way to do this, but here goes.

dokey :: PathItem -> PathItem
dokey v = foldl 
  (\acc nv -> acc & nv %~ (fmap $ operationId ?~ (pack "hello")))
  v 
  [get, put, post, delete, options, head_, patch]  

genOpIds :: Swagger -> Swagger
genOpIds = paths %~ InsOrdHashMap.mapWithKey (\k -> doKey)


Hope it helps! :)