packages feed

openapi3-code-generator 0.1.0.4 → 0.1.0.6

raw patch · 11 files changed

+177/−59 lines, 11 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ OpenAPI.Common: instance OpenAPI.Common.StringifyModel Data.Text.Internal.Text
- OpenAPI.Generate.Internal.Operation: getParameterType :: Flags -> ParameterObject -> Q Type
+ OpenAPI.Generate.Internal.Operation: getParameterType :: ParameterObject -> Generator (Q Type)

Files

README.md view
@@ -2,17 +2,18 @@ [![CircleCI](https://circleci.com/gh/Haskell-OpenAPI-Code-Generator/Haskell-OpenAPI-Client-Code-Generator.svg?style=svg)](https://circleci.com/gh/Haskell-OpenAPI-Code-Generator/Haskell-OpenAPI-Client-Code-Generator)  ## How to use?-1. install [stack](https://docs.haskellstack.org/en/stable/install_and_upgrade/)-1. `stack install openapi3-code-generator`+1. install [cabal](https://www.haskell.org/cabal/)+1. `cabal update`+1. `cabal install openapi3-code-generator` 1. `openapi3-code-generator-exe my_specification.yml`  An `out` directory is created with the generated code. Hint you can use `--output-dir` to specify another output directory. You can use `openapi3-code-generator --help` to list all CLI options. -If `openapi3-code-generator-exe` is not found, you may not have added the cabal bin to your `PATH`. Execute `~/bin/openapi3-code-generator-exe` instead.+If `openapi3-code-generator-exe` is not found, you may not have added the cabal bin to your `PATH`. Execute `~/.local/bin/openapi3-code-generator-exe` instead.  ## How to run from source-1. `stack build`+1. install [stack](https://docs.haskellstack.org/en/stable/install_and_upgrade/) 1. `stack run -- --help`  ## Example package@@ -38,6 +39,7 @@ ## Troubleshooting naming conflicts Naming conflicts can happen, sometimes a little manual adjustment is needed. With the following options naming conflicts can be resolved.+It can happen, that the names get so long, that they are longer than the file system supports.  - `property-type-suffix` - `response-type-suffix`@@ -46,3 +48,26 @@ - `use-numbered-variant-constructors` - `convert-to-camel-case` +## Limitations+The following features are not supported+- links+- callbacks+- Only references to `components` are supported+- Only JSON is supported for both sending and receiving data. `application/x-www-form-urlencoded` can only be used to send data.+- Some circular references in the schemas. For example if an `allOf` contains itself+- Parameters not in `path` or `query`+- `additionalProperties`+- `not` schemas+- `writeOnly` and `readOnly`+- `multipleOf`+- `maximum`+- `exclusiveMaximum`+- `minimum`+- `exclusiveMinimum`+- `minLength`+- `maxItems`+- `minItems`+- `uniqueItems`+- `maxProperties`+- `minProperties`+- `xml`
openapi3-code-generator.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 2d7ec1c26136fb123a14fbba656b051e46a7bc9640fdb2fce0d110c00af662e2+-- hash: 5c9f55e3cb67b6324959a3a09c91ecb0c5172a5430099f91fc163132052c7a57  name:           openapi3-code-generator-version:        0.1.0.4+version:        0.1.0.6 license:        MIT license-file:   LICENSE copyright:      2020 Remo Dörig & Joel Fisch
src/OpenAPI/Common.hs view
@@ -263,6 +263,10 @@   -- stringifyModel :: String -> String   stringifyModel = id +instance StringifyModel Text where+  -- stringifyModel :: Text -> String+  stringifyModel = T.unpack+ instance {-# OVERLAPS #-} Show a => StringifyModel a where   -- stringifyModel :: Show a => a -> String   stringifyModel = show
src/OpenAPI/Generate.hs view
@@ -48,7 +48,6 @@ defineConfigurationInformation moduleName spec =   let servers' = (OAT.servers :: OAT.OpenApiSpecification -> [OAT.ServerObject]) spec       defaultURL = getServerURL servers'-      defaultURLStr = T.unpack defaultURL       defaultURLName = mkName "defaultURL"       getServerURL = maybe "/" (OAT.url :: OAT.ServerObject -> Text) . Maybe.listToMaybe    in Doc.addConfigurationModuleHeader moduleName@@ -61,7 +60,7 @@                     "@" <> defaultURL <> "@"                   ],               ppr-                <$> [d|$(varP defaultURLName) = T.pack defaultURLStr|],+                <$> [d|$(varP defaultURLName) = T.pack $(litE $ stringL $ T.unpack defaultURL)|],               pure $ Doc.generateHaddockComment ["The default configuration containing the 'defaultURL' and no authorization"],               ppr <$> [d|$(varP $ mkName "defaultConfiguration") = OC.Configuration $(varE defaultURLName) OC.AnonymousSecurityScheme|]             ]
src/OpenAPI/Generate/Doc.hs view
@@ -60,6 +60,9 @@ moduleDeclaration :: String -> String -> Doc -> Doc moduleDeclaration modulePrefix name = line ("module " <> modulePrefix <> "." <> name <> " where") +generatorNote :: Doc -> Doc+generatorNote = line "-- CHANGE WITH CAUTION: This is a generated code file generated by https://github.com/Haskell-OpenAPI-Code-Generator/Haskell-OpenAPI-Client-Code-Generator." . emptyLine+ -- | Append a 'Doc' to another inside an 'Applicative' (typically 'Language.Haskell.TH.Q') appendDoc :: Applicative f => f Doc -> f Doc -> f Doc appendDoc = Applicative.liftA2 ($$)@@ -153,7 +156,8 @@ -- | Add the module header to a module of an operation addOperationsModuleHeader :: String -> String -> String -> Doc -> Doc addOperationsModuleHeader mainModuleName moduleName operationId =-  languageExtension "OverloadedStrings"+  generatorNote+    . languageExtension "OverloadedStrings"     . languageExtension "ExplicitForAll"     . languageExtension "MultiWayIf"     . languageExtension "DeriveGeneric"@@ -178,6 +182,7 @@     . importQualified "Data.Text.Internal"     . importQualified "Data.Time.Calendar as Data.Time.Calendar.Days"     . importQualified "Data.Time.LocalTime as Data.Time.LocalTime.Internal.ZonedTime"+    . importQualified "Data.Vector"     . importQualified "GHC.Base"     . importQualified "GHC.Classes"     . importQualified "GHC.Generics"@@ -198,7 +203,8 @@ -- | Add the module header to a module of a model addModelModuleHeader :: String -> String -> [String] -> String -> Doc -> Doc addModelModuleHeader mainModuleName moduleName modelModulesToImport description =-  languageExtension "OverloadedStrings"+  generatorNote+    . languageExtension "OverloadedStrings"     . languageExtension "DeriveGeneric"     . emptyLine     . moduleDescription description@@ -232,7 +238,8 @@ -- | Add the module header to the security scheme module addSecuritySchemesModuleHeader :: String -> Doc -> Doc addSecuritySchemesModuleHeader moduleName =-  languageExtension "OverloadedStrings"+  generatorNote+    . languageExtension "OverloadedStrings"     . emptyLine     . moduleDescription "Contains all supported security schemes defined in the specification"     . moduleDeclaration moduleName "SecuritySchemes"@@ -249,7 +256,8 @@ -- | Add the module header to the configuration module addConfigurationModuleHeader :: String -> Doc -> Doc addConfigurationModuleHeader moduleName =-  languageExtension "OverloadedStrings"+  generatorNote+    . languageExtension "OverloadedStrings"     . emptyLine     . moduleDescription "Contains the default configuration"     . moduleDeclaration moduleName "Configuration"@@ -263,7 +271,7 @@ createModuleHeaderWithReexports moduleName modulesToExport description =   let exports = vcat $ fmap (text . ("module " <>) . (<> ",")) modulesToExport       imports = vcat $ fmap (text . ("import " <>)) modulesToExport-   in moduleDescription description $+   in generatorNote $ moduleDescription description $         text ("module " <> moduleName <> " (")           $$ nest             2
src/OpenAPI/Generate/Internal/Operation.hs view
@@ -38,7 +38,6 @@ import qualified Network.HTTP.Types as HT import qualified OpenAPI.Common as OC import qualified OpenAPI.Generate.Doc as Doc-import qualified OpenAPI.Generate.Flags as OAF import OpenAPI.Generate.Internal.Util import qualified OpenAPI.Generate.Model as Model import qualified OpenAPI.Generate.Monad as OAM@@ -58,10 +57,6 @@ getParametersFromOperationReference = OAT.parameters  -- | wrapper for ambigious usage-getSchemaFromParameterInner :: OAT.ParameterObject -> OAT.ParameterObjectSchema-getSchemaFromParameterInner = OAT.schema---- | wrapper for ambigious usage getRequiredFromParameter :: OAT.ParameterObject -> Bool getRequiredFromParameter = OAT.required @@ -90,17 +85,21 @@       )     . getParametersFromOperationReference --- | Reads the Schema from the ParameterObjectSchema---   Only application/json or simple Object Schema is read-getSchemaFromParameterOuter :: OAT.ParameterObjectSchema -> OAS.SchemaObject-getSchemaFromParameterOuter OAT.SimpleParameterObjectSchema {..} = case schema of-  OAT.Concrete e -> e-  _ -> error "not yet implemented"-getSchemaFromParameterOuter _ = error "not yet implemented"+getSchemaFromParameterObjectSchema :: OAT.ParameterObjectSchema -> OAM.Generator (Maybe OAS.SchemaObject)+getSchemaFromParameterObjectSchema OAT.SimpleParameterObjectSchema {..} = case schema of+  OAT.Concrete e -> pure $ Just e+  OAT.Reference ref -> do+    p <- OAM.getSchemaReferenceM ref+    when (Maybe.isNothing p) $ OAM.logWarning $+      "Reference " <> ref <> " to SchemaObject could not be found and therefore will be skipped."+    pure p+getSchemaFromParameterObjectSchema (OAT.ComplexParameterObjectSchema _) = do+  OAM.logWarning "Complex parameter schemas are not supported and therefore will be skipped."+  pure Nothing --- | Reads the Schema from the Parameter-getSchemaFromParameter :: OAT.ParameterObject -> OAS.SchemaObject-getSchemaFromParameter = getSchemaFromParameterOuter . getSchemaFromParameterInner+-- | Reads the schema from the parameter+getSchemaFromParameter :: OAT.ParameterObject -> OAM.Generator (Maybe OAS.SchemaObject)+getSchemaFromParameter OAT.ParameterObject {..} = OAM.nested name $ getSchemaFromParameterObjectSchema schema  getNameFromParameter :: OAT.ParameterObject -> Text getNameFromParameter = OAT.name@@ -139,13 +138,16 @@  -- | Get the type of a parameter depending on its schema type and the configuration options ('OAF.Flags'). -- If the parameter is not required, a 'Maybe' type is produced.-getParameterType :: OAF.Flags -> OAT.ParameterObject -> Q Type-getParameterType flags parameter =-  let paramType = varT $ Model.getSchemaType flags (getSchemaFromParameter parameter)-   in ( if getRequiredFromParameter parameter-          then paramType-          else [t|Maybe $(paramType)|]-      )+getParameterType :: OAT.ParameterObject -> OAM.Generator (Q Type)+getParameterType parameter = do+  flags <- OAM.getFlags+  schema <- getSchemaFromParameter parameter+  let paramType = varT $ maybe ''Text (Model.getSchemaType flags) schema+  pure+    ( if getRequiredFromParameter parameter+        then paramType+        else [t|Maybe $(paramType)|]+    )  -- | Get a description of a parameter object (the name and if available the description from the specification) getParameterDescription :: OAT.ParameterObject -> OAM.Generator Text@@ -188,7 +190,7 @@       namedQueryParameters = filter ((== OAT.QueryParameterObjectLocation) . getInFromParameterObject . snd) namedParameters       queryParameters = generateQueryParams namedQueryParameters       bodyName = mkName "body"-      strMethod = T.unpack method+      methodLit = litE $ stringL $ T.unpack method   pure $     ppr <$> case bodySchema of       Just RequestBodyDefinition {..} ->@@ -204,7 +206,7 @@                          then [|OC.doBodyCallWithConfiguration $(varE configArg)|]                          else [|OC.doBodyCallWithConfigurationM|]                      )-                    (T.toUpper $ T.pack strMethod)+                    (T.toUpper $ T.pack $methodLit)                     (T.pack $(request))                     $(queryParameters)                     $(if required then [|Just $(varE bodyName)|] else varE bodyName)@@ -219,7 +221,7 @@                      then [|OC.doCallWithConfiguration $(varE configArg)|]                      else [|OC.doCallWithConfigurationM|]                  )-                (T.toUpper $ T.pack strMethod)+                (T.toUpper $ T.pack $methodLit)                 (T.pack $(request))                 $(queryParameters)               )@@ -298,13 +300,13 @@ -- | Generates query params in the form of [(Text,ByteString)] generateQueryParams :: [(Name, OAT.ParameterObject)] -> Q Exp generateQueryParams ((name, param) : xs) =-  infixE (Just [|(T.pack queryName, $(expr))|]) (varE $ mkName ":") (Just $ generateQueryParams xs)+  infixE (Just [|(T.pack $(litE $ stringL queryName), $expr)|]) (varE $ mkName ":") (Just $ generateQueryParams xs)   where     queryName = T.unpack $ getNameFromParameter param     required = getRequiredFromParameter param     expr =       if required-        then [|$(varE $ mkName "GHC.Base.Just") $ OC.stringifyModel $(varE name)|]+        then [|Just $ OC.stringifyModel $(varE name)|]         else [|OC.stringifyModel <$> $(varE name)|] generateQueryParams _ = [|[]|] 
src/OpenAPI/Generate/Internal/Util.hs view
@@ -23,6 +23,22 @@ import qualified OpenAPI.Generate.Flags as OAF import qualified OpenAPI.Generate.Monad as OAM +-- | Checks if the casing of a character can be changed.+-- This is required to ensure the functions 'Char.toUpper' and 'Char.toLower' actually do something.+isCasableAlpha :: Char -> Bool+isCasableAlpha x = Char.isLower (Char.toLower x) && Char.isUpper (Char.toUpper x)++isValidCharaterInSuffixExceptUnderscore :: Char -> Bool+isValidCharaterInSuffixExceptUnderscore x = isCasableAlpha x || Char.isDigit x || x == '\''++removeIllegalLeadingCharacters :: String -> String+removeIllegalLeadingCharacters (x : xs) | not (isCasableAlpha x) = removeIllegalLeadingCharacters xs+removeIllegalLeadingCharacters x = x++generateNameForEmptyIdentifier :: Text -> String -> String+generateNameForEmptyIdentifier originalName "" = "identifier" <> show (T.foldr ((+) . Char.ord) 0 originalName)+generateNameForEmptyIdentifier _ name = name+ -- | Transform an identifier to ensure it is a valid Haskell identifier -- Additionally, this function applies style settings according to the need of the consumer. haskellifyText ::@@ -38,16 +54,17 @@   let casefn = if startWithUppercase then Char.toUpper else Char.toLower       replaceChar '.' = '\''       replaceChar '\'' = '\''-      replaceChar a = if Char.isAlphaNum a then a else '_'+      replaceChar a = if isValidCharaterInSuffixExceptUnderscore a then a else '_'       caseFirstCharCorrectly (x : xs) = casefn x : xs       caseFirstCharCorrectly x = x       nameWithoutSpecialChars a = replaceChar <$> a-      toCamelCase (x : y : xs) | not (Char.isAlphaNum x) && x /= '\'' && Char.isAlpha y = Char.toUpper y : toCamelCase xs+      toCamelCase (x : y : xs) | not (isValidCharaterInSuffixExceptUnderscore x) && isCasableAlpha y = Char.toUpper y : toCamelCase xs       toCamelCase (x : xs) = x : toCamelCase xs       toCamelCase xs = xs       replaceReservedWord "case" = "case'"       replaceReservedWord "class" = "class'"       replaceReservedWord "data" = "data'"+      replaceReservedWord "default" = "default'"       replaceReservedWord "deriving" = "deriving'"       replaceReservedWord "do" = "do'"       replaceReservedWord "else" = "else'"@@ -59,9 +76,9 @@       replaceReservedWord "infixr" = "infixr'"       replaceReservedWord "instance" = "instance'"       replaceReservedWord "let" = "let'"-      replaceReservedWord "of" = "of'"       replaceReservedWord "module" = "module'"       replaceReservedWord "newtype" = "newtype'"+      replaceReservedWord "of" = "of'"       replaceReservedWord "then" = "then'"       replaceReservedWord "type" = "type'"       replaceReservedWord "where" = "where'"@@ -73,13 +90,14 @@       replaceReservedWord "JsonByteString" = "JsonByteString'"       replaceReservedWord "JsonDateTime" = "JsonDateTime'"       replaceReservedWord "RequestBodyEncoding" = "RequestBodyEncoding'"-      replaceReservedWord ('_' : rest) = replaceReservedWord rest       replaceReservedWord a = a       replacePlus ('+' : rest) = "Plus" <> replacePlus rest       replacePlus (x : xs) = x : replacePlus xs       replacePlus a = a    in replaceReservedWord         $ caseFirstCharCorrectly+        $ generateNameForEmptyIdentifier name+        $ removeIllegalLeadingCharacters         $ (if convertToCamelCase then toCamelCase else id)         $ nameWithoutSpecialChars         $ replacePlus@@ -97,16 +115,17 @@  -- | Transform a module name to ensure it is valid for file names transformToModuleName :: Text -> Text-transformToModuleName =-  let toCamelCase (x : y : xs) | not (Char.isAlphaNum x) && Char.isAlpha y = Char.toUpper y : toCamelCase xs+transformToModuleName name =+  let toCamelCase (x : y : xs) | not (isValidCharaterInSuffixExceptUnderscore x) && isCasableAlpha y = Char.toUpper y : toCamelCase xs       toCamelCase (x : xs) = x : toCamelCase xs       toCamelCase xs = xs    in T.pack-        . toCamelCase-        . uppercaseFirst-        . T.unpack-        . T.dropWhile (== '_')-        . T.map (\c -> if Char.isAlphaNum c then c else '_')+        $ uppercaseFirst+        $ generateNameForEmptyIdentifier name+        $ removeIllegalLeadingCharacters+        $ toCamelCase+        $ T.unpack+        $ T.map (\c -> if isValidCharaterInSuffixExceptUnderscore c then c else '_') name  uppercaseFirst :: String -> String uppercaseFirst (x : xs) = Char.toUpper x : xs
src/OpenAPI/Generate/Model.hs view
@@ -378,7 +378,7 @@   case newDefs of     Just (newSchema, newDependencies) ->       addDependencies newDependencies $ defineModelForSchemaConcrete DontCreateTypeAlias schemaName newSchema-    Nothing -> pure (varT ''String, (emptyDoc, Set.empty))+    Nothing -> pure (varT ''Text, (emptyDoc, Set.empty))  -- | defines a new Schema, which properties are fused defineNewSchemaForAllOf :: Text -> Text -> [OAS.Schema] -> OAM.Generator (Maybe (OAS.SchemaObject, Dep.Models))@@ -406,7 +406,7 @@       -- not allowed by the spec       Nothing -> do         OAM.logWarning $ T.pack "items is empty for an array (assume string) " <> schemaName-        pure (varT ''String, (emptyDoc, Set.empty))+        pure (varT ''Text, (emptyDoc, Set.empty))   let arrayType = appT (varT $ mkName "[]") type'   schemaName' <- haskellifyNameM True schemaName   pure@@ -666,6 +666,6 @@ getSchemaType _ OAS.SchemaObject {type' = OAS.SchemaTypeString, format = Just "binary", ..} = ''OC.JsonByteString getSchemaType OAF.Flags {optUseDateTypesAsString = True, ..} OAS.SchemaObject {type' = OAS.SchemaTypeString, format = Just "date", ..} = ''Day getSchemaType OAF.Flags {optUseDateTypesAsString = True, ..} OAS.SchemaObject {type' = OAS.SchemaTypeString, format = Just "date-time", ..} = ''OC.JsonDateTime-getSchemaType _ OAS.SchemaObject {type' = OAS.SchemaTypeString, ..} = ''String+getSchemaType _ OAS.SchemaObject {type' = OAS.SchemaTypeString, ..} = ''Text getSchemaType _ OAS.SchemaObject {type' = OAS.SchemaTypeBool, ..} = ''Bool-getSchemaType _ OAS.SchemaObject {..} = ''String+getSchemaType _ OAS.SchemaObject {..} = ''Text
src/OpenAPI/Generate/Operation.hs view
@@ -91,7 +91,8 @@   functionBodyRawWithMonadTransformer <- defineOperationFunction False operationIdNameRawWithMonadTransformer params requestPath method bodySchema rawTransformation   (bodyType, bodyDefinition) <- getBodyType bodySchema appendToOperationName   paramDescriptions <- (<> ["The request body to send" | not $ null bodyType]) <$> mapM getParameterDescription params-  let types = (getParameterType flags <$> params) <> bodyType+  paramTypes <- mapM getParameterType params+  let types = paramTypes <> bodyType       fnType = getParametersTypeForSignature types responseTypeName monadName securitySchemeName       fnTypeRaw = getParametersTypeForSignature types ''B8.ByteString monadName securitySchemeName       fnTypeWithMonadTransformer = getParametersTypeForSignatureWithMonadTransformer types responseTypeName monadName securitySchemeName
test/OpenAPI/Generate/Internal/UtilSpec.hs view
@@ -1,11 +1,54 @@ module OpenAPI.Generate.Internal.UtilSpec where +import qualified Data.Char as Char+import Data.GenValidity.Text ()+import qualified Data.Text as T+import Data.Validity.Text () import OpenAPI.Generate.Internal.Util import Test.Hspec import Test.Validity +-- See https://www.haskell.org/onlinereport/lexemes.html § 2.4+isValidVarId :: String -> Bool+isValidVarId "" = False+isValidVarId "case" = False+isValidVarId "class" = False+isValidVarId "data" = False+isValidVarId "default" = False+isValidVarId "deriving" = False+isValidVarId "do" = False+isValidVarId "else" = False+isValidVarId "if" = False+isValidVarId "import" = False+isValidVarId "in" = False+isValidVarId "infix" = False+isValidVarId "infixl" = False+isValidVarId "infixr" = False+isValidVarId "instance" = False+isValidVarId "let" = False+isValidVarId "module" = False+isValidVarId "newtype" = False+isValidVarId "of" = False+isValidVarId "then" = False+isValidVarId "type" = False+isValidVarId "where" = False+isValidVarId (x : xs) = isValidSmall x && isValidSuffix xs++isValidConId :: String -> Bool+isValidConId "" = False+isValidConId (x : xs) = isValidLarge x && isValidSuffix xs++isValidSmall :: Char -> Bool+isValidSmall x = x == '_' || Char.isLower x++isValidLarge :: Char -> Bool+isValidLarge = Char.isUpper++isValidSuffix :: String -> Bool+isValidSuffix = all (\x -> isValidSmall x || isValidLarge x || Char.isDigit x || x == '\'')+ spec :: Spec-spec =+spec = do   describe "splitOn" $ do     it "should split string into pieces" $       splitOn 'a' "abcabca" `shouldBe` ["", "bc", "bc", ""]@@ -14,3 +57,20 @@       $ \(x, list) ->         length (splitOn (x :: Char) list)           == length (filter (== x) list) + 1+  describe "haskellifyText" $ do+    it "uppercase without CamelCase"+      $ forAllValid+      $ isValidConId . haskellifyText False True+    it "uppercase with CamelCase"+      $ forAllValid+      $ isValidConId . haskellifyText True True+    it "lowercase without CamelCase"+      $ forAllValid+      $ isValidVarId . haskellifyText False False+    it "lowercase with CamelCase"+      $ forAllValid+      $ isValidVarId . haskellifyText True False+  describe "transformToModuleName"+    $ it "should be valid module name"+    $ forAllValid+    $ isValidConId . T.unpack . transformToModuleName
test/OpenAPI/Generate/OperationTHSpec.hs view
@@ -108,5 +108,5 @@             [t|OC.Configuration $(varT securitySchemeName) -> Maybe Int -> $(responseType)|]           singleTestTH             "Two parameters"-            (getParametersTypeForSignature [conT ''Int, conT ''String] responseTypeName monadName securitySchemeName)-            [t|OC.Configuration $(varT securitySchemeName) -> Int -> String -> $(responseType)|]+            (getParametersTypeForSignature [conT ''Int, conT ''T.Text] responseTypeName monadName securitySchemeName)+            [t|OC.Configuration $(varT securitySchemeName) -> Int -> T.Text -> $(responseType)|]