packages feed

openapi-hs-4.0.0: src/Data/OpenApi/Internal/Utils.hs

-- |
-- Module:      Data.OpenApi.Internal.Utils
-- Maintainer:  Nadeem Bitar <nadeem@gmail.com>
-- Stability:   experimental
--
-- Internal shared utilities (Template Haskell lens helpers, JSON/monoid
-- combinators). No API stability guarantees.
module Data.OpenApi.Internal.Utils where

import Control.Lens ((%~), (&))
import Control.Lens.TH
import Data.Aeson
import Data.Aeson.Encode.Pretty qualified as P
import Data.Aeson.Types
import Data.ByteString.Lazy qualified as BSL
import Data.Char
import Data.Data
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HashMap
import Data.HashMap.Strict.InsOrd.Compat (InsOrdHashMap)
import Data.HashMap.Strict.InsOrd.Compat qualified as InsOrdHashMap
import Data.Hashable (Hashable)
import Data.Map (Map)
import Data.Set (Set)
import Data.Text (Text)
import GHC.Generics
import Language.Haskell.TH (mkName)
import Prelude.Compat
import Prelude ()

openApiFieldRules :: LensRules
openApiFieldRules = defaultFieldRules & lensField %~ openApiFieldNamer
  where
    openApiFieldNamer namer dname fnames fname =
      map fixDefName (namer dname fnames fname)

    fixDefName (MethodName cname mname) = MethodName cname (fixName mname)
    fixDefName (TopName name) = TopName (fixName name)

    fixName = mkName . fixName' . show

    fixName' "in" = "in_" -- keyword
    fixName' "type" = "type_" -- keyword
    fixName' "default" = "default_" -- keyword
    fixName' "if" = "if_" -- keyword
    fixName' "then" = "then_" -- keyword
    fixName' "else" = "else_" -- keyword
    fixName' "minimum" = "minimum_" -- Prelude conflict
    fixName' "maximum" = "maximum_" -- Prelude conflict
    fixName' "enum" = "enum_" -- Control.Lens conflict
    fixName' "head" = "head_" -- Prelude conflict
    fixName' "not" = "not_" -- Prelude conflict
    fixName' "id" = "id_" -- Prelude conflict
    fixName' "const" = "const_" -- Prelude conflict
    fixName' "contains" = "contains_" -- Control.Lens conflict
    fixName' n = n

gunfoldEnum :: String -> [a] -> (forall b r. (Data b) => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c a
gunfoldEnum tname xs _k z c = case lookup (constrIndex c) (zip [1 ..] xs) of
  Just x -> z x
  Nothing -> error $ "Data.Data.gunfold: Constructor " ++ show c ++ " is not of type " ++ tname ++ "."

jsonPrefix :: String -> Options
jsonPrefix prefix =
  defaultOptions
    { fieldLabelModifier = modifier . drop 1,
      constructorTagModifier = modifier,
      sumEncoding = ObjectWithSingleField,
      omitNothingFields = True
    }
  where
    modifier = lowerFirstUppers . drop (length prefix)

    lowerFirstUppers s = map toLower x ++ y
      where
        (x, y) = span isUpper s

parseOneOf :: (ToJSON a) => [a] -> Value -> Parser a
parseOneOf xs js =
  case lookup js ys of
    Nothing -> fail $ "invalid json: " ++ show js ++ " (expected one of " ++ show (map fst ys) ++ ")"
    Just x -> pure x
  where
    ys = zip (map toJSON xs) xs

(<+>) :: Value -> Value -> Value
Object x <+> Object y = Object (x <> y)
_ <+> _ = error "<+>: merging non-objects"

genericMempty :: (Generic a, GMonoid (Rep a)) => a
genericMempty = to gmempty

genericMappend :: (Generic a, GMonoid (Rep a)) => a -> a -> a
genericMappend x y = to (gmappend (from x) (from y))

class GMonoid f where
  gmempty :: f p
  gmappend :: f p -> f p -> f p

instance GMonoid U1 where
  gmempty = U1
  gmappend _ _ = U1

instance (GMonoid f, GMonoid g) => GMonoid (f :*: g) where
  gmempty = gmempty :*: gmempty
  gmappend (a :*: x) (b :*: y) = gmappend a b :*: gmappend x y

instance (OpenApiMonoid a) => GMonoid (K1 i a) where
  gmempty = K1 openApiMempty
  gmappend (K1 x) (K1 y) = K1 (openApiMappend x y)

instance (GMonoid f) => GMonoid (M1 i t f) where
  gmempty = M1 gmempty
  gmappend (M1 x) (M1 y) = M1 (gmappend x y)

class OpenApiMonoid m where
  openApiMempty :: m
  openApiMappend :: m -> m -> m
  default openApiMempty :: (Monoid m) => m
  openApiMempty = mempty
  default openApiMappend :: (Monoid m) => m -> m -> m
  openApiMappend = mappend

instance OpenApiMonoid [a]

instance (Ord a) => OpenApiMonoid (Set a)

instance (Ord k) => OpenApiMonoid (Map k v)

instance (Eq k, Hashable k) => OpenApiMonoid (HashMap k v) where
  openApiMempty = mempty
  openApiMappend = HashMap.unionWith (\_old new -> new)

instance (Eq k, Hashable k) => OpenApiMonoid (InsOrdHashMap k v) where
  openApiMempty = mempty
  openApiMappend = InsOrdHashMap.unionWith (\_old new -> new)

instance OpenApiMonoid Text where
  openApiMempty = mempty
  openApiMappend x "" = x
  openApiMappend _ y = y

instance OpenApiMonoid (Maybe a) where
  openApiMempty = Nothing
  openApiMappend x Nothing = x
  openApiMappend _ y = y

encodePretty :: (ToJSON a) => a -> BSL.ByteString
encodePretty = P.encodePretty' $ P.defConfig {P.confCompare = P.compare}