Skip to content

Commit d69ee20

Browse files
author
Dan Fithian
committed
Detect oneof branches with single fields. When specified, name oneof variants after those fields instead of with numbers. Fix up some utilities for consistency, and add a Field type instead of recomputing property names.
1 parent db004b9 commit d69ee20

File tree

8 files changed

+140
-88
lines changed

8 files changed

+140
-88
lines changed

openapi3-code-generator/src/OpenAPI/Generate/Internal/Util.hs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ haskellifyText ::
4949
-- | The identifier to transform
5050
Text ->
5151
-- | The resulting identifier
52-
String
52+
Text
5353
haskellifyText convertToCamelCase startWithUppercase name =
5454
let casefn = if startWithUppercase then Char.toUpper else Char.toLower
5555
replaceChar '.' = '\''
@@ -94,18 +94,19 @@ haskellifyText convertToCamelCase startWithUppercase name =
9494
replacePlus ('+' : rest) = "Plus" <> replacePlus rest
9595
replacePlus (x : xs) = x : replacePlus xs
9696
replacePlus a = a
97-
in replaceReservedWord $
98-
caseFirstCharCorrectly $
99-
generateNameForEmptyIdentifier name $
100-
removeIllegalLeadingCharacters $
101-
(if convertToCamelCase then toCamelCase else id) $
102-
nameWithoutSpecialChars $
103-
replacePlus $
104-
T.unpack name
97+
in T.pack $
98+
replaceReservedWord $
99+
caseFirstCharCorrectly $
100+
generateNameForEmptyIdentifier name $
101+
removeIllegalLeadingCharacters $
102+
(if convertToCamelCase then toCamelCase else id) $
103+
nameWithoutSpecialChars $
104+
replacePlus $
105+
T.unpack name
105106

106107
-- | The same as 'haskellifyText' but transform the result to a 'Name'
107108
haskellifyName :: Bool -> Bool -> Text -> Name
108-
haskellifyName convertToCamelCase startWithUppercase name = mkName $ haskellifyText convertToCamelCase startWithUppercase name
109+
haskellifyName convertToCamelCase startWithUppercase name = mkName . T.unpack $ haskellifyText convertToCamelCase startWithUppercase name
109110

110111
-- | 'OAM.Generator' version of 'haskellifyName'
111112
haskellifyNameM :: Bool -> Text -> OAM.Generator Name

openapi3-code-generator/src/OpenAPI/Generate/Model.hs

Lines changed: 100 additions & 66 deletions
Large diffs are not rendered by default.

openapi3-code-generator/src/OpenAPI/Generate/Operation.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ defineModuleForOperation mainModuleName requestPath method operation = OAM.neste
8787
convertToCamelCase <- OAM.getSetting OAO.settingConvertToCamelCase
8888
let operationIdAsText = T.pack $ show operationIdName
8989
appendToOperationName = ((T.pack $ nameBase operationIdName) <>)
90-
moduleName = haskellifyText convertToCamelCase True operationIdAsText
90+
moduleName = T.unpack $ haskellifyText convertToCamelCase True operationIdAsText
9191
OAM.logInfo $ "Generating operation with name '" <> operationIdAsText <> "'"
9292
(bodySchema, bodyPath) <- getBodySchemaFromOperation operation
9393
(responseTypeName, responseTransformerExp, responseBodyDefinitions, responseBodyDependencies) <- OAR.getResponseDefinitions operation appendToOperationName

openapi3-code-generator/src/OpenAPI/Generate/OptParse.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,10 @@ data Settings = Settings
110110
-- in the 'FromJSON' instance.
111111
-- This setting allows to change this behavior by including all fixed value
112112
-- fields instead ("include" strategy), i.e. just not trying to do anything smart.
113-
settingFixedValueStrategy :: !FixedValueStrategy
113+
settingFixedValueStrategy :: !FixedValueStrategy,
114+
-- | Instead of numbering oneof branches as OneOfN, name oneof branches after a single field
115+
-- where possible.
116+
settingUseSingleFieldNames :: !Bool
114117
}
115118
deriving (Show, Eq)
116119

@@ -157,6 +160,7 @@ combineToSettings Flags {..} mConf configurationFilePath = do
157160
settingWhiteListedSchemas = fromMaybe [] $ flagWhiteListedSchemas <|> mc configWhiteListedSchemas
158161
settingOutputAllSchemas = fromMaybe False $ flagOutputAllSchemas <|> mc configOutputAllSchemas
159162
settingFixedValueStrategy = fromMaybe FixedValueStrategyExclude $ flagFixedValueStrategy <|> mc configFixedValueStrategy
163+
settingUseSingleFieldNames = fromMaybe False $ flagUseSingleFieldNames <|> mc configUseSingleFieldNames
160164

161165
pure Settings {..}
162166
where

openapi3-code-generator/src/OpenAPI/Generate/OptParse/Configuration.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ data Configuration = Configuration
4949
configOpaqueSchemas :: !(Maybe [Text]),
5050
configWhiteListedSchemas :: !(Maybe [Text]),
5151
configOutputAllSchemas :: !(Maybe Bool),
52-
configFixedValueStrategy :: !(Maybe FixedValueStrategy)
52+
configFixedValueStrategy :: !(Maybe FixedValueStrategy),
53+
configUseSingleFieldNames :: !(Maybe Bool)
5354
}
5455
deriving stock (Show, Eq)
5556
deriving (FromJSON, ToJSON) via (Autodocodec Configuration)
@@ -91,6 +92,7 @@ instance HasCodec Configuration where
9192
<*> optionalField "whiteListedSchemas" "A list of schema names (exactly as they are named in the components.schemas section of the corresponding OpenAPI 3 specification) which need to be generated. For all other schemas only a type alias to 'Aeson.Value' is created." .= configWhiteListedSchemas
9293
<*> optionalField "outputAllSchemas" "Output all component schemas" .= configOutputAllSchemas
9394
<*> optionalField "fixedValueStrategy" "In OpenAPI 3, fixed values can be defined as an enum with only one allowed value. If such a constant value is encountered as a required property of an object, the generator excludes this property by default ('exclude' strategy) and adds the value in the 'ToJSON' instance and expects the value to be there in the 'FromJSON' instance. This setting allows to change this behavior by including all fixed value fields instead ('include' strategy), i.e. just not trying to do anything smart." .= configFixedValueStrategy
95+
<*> optionalField "useSingleFieldNames" "Instead of numbering oneof branches as OneOfN, name oneof branches after a single field where possible" .= configUseSingleFieldNames
9496

9597
getConfiguration :: Text -> IO (Maybe Configuration)
9698
getConfiguration path = resolveFile' (T.unpack path) >>= readYamlConfigFile

openapi3-code-generator/src/OpenAPI/Generate/OptParse/Flags.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ data Flags = Flags
4646
flagOpaqueSchemas :: !(Maybe [Text]),
4747
flagWhiteListedSchemas :: !(Maybe [Text]),
4848
flagOutputAllSchemas :: !(Maybe Bool),
49-
flagFixedValueStrategy :: !(Maybe FixedValueStrategy)
49+
flagFixedValueStrategy :: !(Maybe FixedValueStrategy),
50+
flagUseSingleFieldNames :: !(Maybe Bool)
5051
}
5152
deriving (Show, Eq)
5253

@@ -87,6 +88,7 @@ parseFlags =
8788
<*> parseFlagWhiteListedSchemas
8889
<*> parseFlagOutputAllSchemas
8990
<*> parseFlagFixedValueStrategy
91+
<*> parseFlagUseSingleFieldNames
9092

9193
parseFlagConfiguration :: Parser (Maybe Text)
9294
parseFlagConfiguration =
@@ -382,3 +384,7 @@ parseFlagFixedValueStrategy =
382384
help "In OpenAPI 3, fixed values can be defined as an enum with only one allowed value. If such a constant value is encountered as a required property of an object, the generator excludes this property by default ('exclude' strategy) and adds the value in the 'ToJSON' instance and expects the value to be there in the 'FromJSON' instance. This setting allows to change this behavior by including all fixed value fields instead ('include' strategy), i.e. just not trying to do anything smart (default: 'exclude').",
383385
long "fixed-value-strategy"
384386
]
387+
388+
parseFlagUseSingleFieldNames :: Parser (Maybe Bool)
389+
parseFlagUseSingleFieldNames =
390+
booleanFlag "Instead of numbering oneof branches as OneOfN, name oneof branches after a single field where possible" "use-single-field-names" Nothing

openapi3-code-generator/src/OpenAPI/Generate/Response.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ errorSuffix = "Error"
114114

115115
-- | Create the name as 'Text' of the response type / data constructor based on a suffix
116116
createResponseNameAsText :: Bool -> (Text -> Text) -> Text -> Text
117-
createResponseNameAsText convertToCamelCase appendToOperationName = T.pack . haskellifyText convertToCamelCase True . appendToOperationName
117+
createResponseNameAsText convertToCamelCase appendToOperationName = haskellifyText convertToCamelCase True . appendToOperationName
118118

119119
-- | Create the name as 'Name' of the response type / data constructor based on a suffix
120120
createResponseName :: Bool -> (Text -> Text) -> Text -> Name

openapi3-code-generator/test/OpenAPI/Generate/Internal/UtilSpec.hs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
13
module OpenAPI.Generate.Internal.UtilSpec where
24

35
import qualified Data.Char as Char
46
import Data.GenValidity.Text ()
7+
import Data.Text (Text)
58
import qualified Data.Text as T
69
import Data.Validity.Text ()
710
import OpenAPI.Generate.Internal.Util
811
import Test.Hspec
912
import Test.Validity
1013

1114
-- See https://www.haskell.org/onlinereport/lexemes.html § 2.4
12-
isValidVarId :: String -> Bool
13-
isValidVarId "" = False
15+
isValidVarId :: Text -> Bool
1416
isValidVarId "case" = False
1517
isValidVarId "class" = False
1618
isValidVarId "data" = False
@@ -32,11 +34,14 @@ isValidVarId "of" = False
3234
isValidVarId "then" = False
3335
isValidVarId "type" = False
3436
isValidVarId "where" = False
35-
isValidVarId (x : xs) = isValidSmall x && isValidSuffix xs
37+
isValidVarId other = case T.unpack other of
38+
[] -> False
39+
(x : xs) -> isValidSmall x && isValidSuffix xs
3640

37-
isValidConId :: String -> Bool
38-
isValidConId "" = False
39-
isValidConId (x : xs) = isValidLarge x && isValidSuffix xs
41+
isValidConId :: Text -> Bool
42+
isValidConId other = case T.unpack other of
43+
[] -> False
44+
(x : xs) -> isValidLarge x && isValidSuffix xs
4045

4146
isValidSmall :: Char -> Bool
4247
isValidSmall x = x == '_' || Char.isLower x
@@ -65,4 +70,4 @@ spec = do
6570
describe "transformToModuleName" $
6671
it "should be valid module name" $
6772
forAllValid $
68-
isValidConId . T.unpack . transformToModuleName
73+
isValidConId . transformToModuleName

0 commit comments

Comments
 (0)