structs forms

This commit is contained in:
Alex Biehl 2024-02-07 12:23:07 +01:00
parent 1aadc110ad
commit 56c76d587a
8 changed files with 89 additions and 25 deletions

View File

@ -50,21 +50,6 @@ $ git clone https://github.com/scarf-sh/tie.git
$ cd tie $ cd tie
``` ```
Then, you need to build it using cabal:
```bash
$ cabal build exe:tie
```
Finally, you can copy the resulting executable to your desired location:
```bash
$ cp $(cabal exec -- which tie) ~/.local/bin/tie
```
Alternatively, instead of manually copying the executable you can install with
cabal:
```bash ```bash
$ cabal install . $ cabal install .
``` ```

View File

@ -146,10 +146,11 @@ requiredQueryParameters style name withParam =
optionalQueryParameters :: optionalQueryParameters ::
(FromHttpApiData a) => (FromHttpApiData a) =>
Style -> Style ->
Bool ->
ByteString -> ByteString ->
(Maybe (NonEmpty.NonEmpty a) -> Wai.Application) -> (Maybe (NonEmpty.NonEmpty a) -> Wai.Application) ->
Wai.Application Wai.Application
optionalQueryParameters style name withParam = optionalQueryParameters style allowEmpty name withParam =
case style of case style of
FormStyle -> \request respond -> FormStyle -> \request respond ->
case urlDecodeForm (LBS.fromStrict (ByteString.drop 1 (Wai.rawQueryString request))) of case urlDecodeForm (LBS.fromStrict (ByteString.drop 1 (Wai.rawQueryString request))) of
@ -166,21 +167,21 @@ optionalQueryParameters style name withParam =
SpaceDelimitedStyle -> SpaceDelimitedStyle ->
optionalQueryParameter optionalQueryParameter
name name
False allowEmpty
( \xs -> ( \xs ->
withParam (xs >>= NonEmpty.nonEmpty . unSpaceDelimitedValue) withParam (xs >>= NonEmpty.nonEmpty . unSpaceDelimitedValue)
) )
PipeDelimitedStyle -> PipeDelimitedStyle ->
optionalQueryParameter optionalQueryParameter
name name
False allowEmpty
( \xs -> ( \xs ->
withParam (xs >>= NonEmpty.nonEmpty . unPipeDelimitedValue) withParam (xs >>= NonEmpty.nonEmpty . unPipeDelimitedValue)
) )
CommaDelimitedStyle -> CommaDelimitedStyle ->
optionalQueryParameter optionalQueryParameter
name name
False allowEmpty
( \xs -> ( \xs ->
withParam (xs >>= NonEmpty.nonEmpty . unCommaDelimitedValue) withParam (xs >>= NonEmpty.nonEmpty . unCommaDelimitedValue)
) )

View File

@ -0,0 +1,6 @@
data CreatePackageRequest where
CreatePackageRequest_application_json :: CreatePackage1 -> CreatePackageRequest
CreatePackageRequest_text_xml :: CreatePackage1 -> CreatePackageRequest
data CreatePackageResponse (status :: Natural) body where
CreatePackageResponse_200_application_json :: CreatePackageResponse 200

View File

@ -161,6 +161,10 @@ codegenModuleHeader moduleName =
<> "import" <> "import"
<+> "qualified" <+> "qualified"
<+> "Web.HttpApiData" <+> "Web.HttpApiData"
<> PP.line
<> "import"
<+> "qualified"
<+> "Web.FormUrlEncoded"
codegenExtraApiModuleDependencies :: ApiName -> Doc ann codegenExtraApiModuleDependencies :: ApiName -> Doc ann
codegenExtraApiModuleDependencies apiName = codegenExtraApiModuleDependencies apiName =

View File

@ -363,7 +363,7 @@ codegenQueryParamStyle explode style = case (explode, style) of
(False, StyleForm) -> Just "CommaDelimitedStyle" (False, StyleForm) -> Just "CommaDelimitedStyle"
codegenQueryParamGuard :: Param -> PP.Doc ann -> PP.Doc ann codegenQueryParamGuard :: Param -> PP.Doc ann -> PP.Doc ann
codegenQueryParamGuard Param {name, required, style, explode, schema} continuation codegenQueryParamGuard Param {name, required, style, explode, schema, allowEmpty} continuation
| Just _ <- isArrayType (namedType schema), | Just _ <- isArrayType (namedType schema),
Just style <- style, Just style <- style,
Just style <- codegenQueryParamStyle explode style = Just style <- codegenQueryParamStyle explode style =
@ -394,7 +394,7 @@ codegenQueryParamGuard Param {name, required, style, explode, schema} continuati
| otherwise = | otherwise =
"optionalQueryParameter" "optionalQueryParameter"
<+> "\"" <> toParamName name <> "\"" <+> "\"" <> toParamName name <> "\""
<+> "False" <+> if allowEmpty then "True" else "False"
<+> "(" <> "\\" <> toParamBinder name <+> "(" <> "\\" <> toParamBinder name
<+> "request" <+> "request"
<+> "respond" <+> "respond"

View File

@ -48,6 +48,7 @@ import Tie.Type
isOneOfType, isOneOfType,
namedType, namedType,
normalizeType, normalizeType,
isFitForFromFormInstance,
) )
import Prelude hiding (Type) import Prelude hiding (Type)
@ -264,7 +265,7 @@ codegenOneOfType getDiscriminator typName variants = do
pure (PP.vsep $ intersperse mempty [decl, toJson, fromJson]) pure (PP.vsep $ intersperse mempty [decl, toJson, fromJson])
codegenObjectType :: (Monad m) => Name -> ObjectType (Named Type) -> m (Doc ann) codegenObjectType :: (Monad m) => Name -> ObjectType (Named Type) -> m (Doc ann)
codegenObjectType typName ObjectType {..} codegenObjectType typName objectType@ObjectType {..}
-- for empty, free form objects, just generate a type synonym for Value. -- for empty, free form objects, just generate a type synonym for Value.
| Just FreeForm <- additionalProperties, | Just FreeForm <- additionalProperties,
null properties = null properties =
@ -508,7 +509,53 @@ codegenObjectType typName ObjectType {..}
) )
) )
) )
in pure (PP.vsep $ intersperse mempty [decl, toJson, fromJson])
parseFormForm form fieldName fieldType
| Just {} <- isArrayType (namedType fieldType) =
if is_required then
"Web.FormUrlEncoded.parseAll" <+> "\"" <> toJsonFieldName fieldName <> "\"" <+> form
else
"(" <> "either" <+> "(" <> "const" <+> "(" <> "Right" <+> "Nothing" <> ")" <> ")" <+> "(" <> "Right" <+> "." <+> "Just" <> ")" <+> "(" <> "Web.FormUrlEncoded.parseAll" <+> "\"" <> toJsonFieldName fieldName <> "\"" <+> form <> ")" <> ")"
| Just {} <- isObjectType (namedType fieldType) =
if is_required then
"Web.FormUrlEncoded.fromForm" <+> form
else
"(" <> "either" <+> "(" <> "const" <+> "(" <> "Right" <+> "Nothing" <> ")" <> ")" <+> "(" <> "Right" <+> "." <+> "Just" <> ")" <+> "(" <> "Web.FormUrlEncoded.fromForm" <+> form <> ")" <> ")"
| is_required =
"Web.FormUrlEncoded.parseUnique" <+> "\"" <> toJsonFieldName fieldName <> "\"" <+> form
| otherwise =
"Web.FormUrlEncoded.parseMaybe" <+> "\"" <> toJsonFieldName fieldName <> "\"" <+> form
where
is_required = HashSet.member fieldName requiredProperties
fromForm
| isFitForFromFormInstance (Object objectType) =
"instance"
<+> "Web.FormUrlEncoded.FromForm"
<+> toDataTypeName typName
<+> "where"
<> PP.line
<> PP.indent
4
( "fromForm" <+> "x" <+> "="
<> PP.line
<> PP.indent
4
( toConstructorName typName
<> PP.line
<> PP.indent
4
( PP.vsep
[ op <+> parseFormForm "x" fieldName fieldType
| (op, (fieldName, fieldType)) <- zip ("<$>" : repeat "<*>") orderedProperties
]
)
)
)
| otherwise =
mempty
in pure (PP.vsep $ intersperse mempty [decl, toJson, fromJson, fromForm])
codegenRequiredOptionalFieldType :: Bool -> Doc ann -> Doc ann codegenRequiredOptionalFieldType :: Bool -> Doc ann -> Doc ann
codegenRequiredOptionalFieldType True doc = doc codegenRequiredOptionalFieldType True doc = doc

View File

@ -122,7 +122,8 @@ data Param = Param
schema :: Named Type, schema :: Named Type,
required :: Bool, required :: Bool,
explode :: Bool, explode :: Bool,
style :: Maybe Style style :: Maybe Style,
allowEmpty :: Bool
} }
deriving (Eq, Ord, Show) deriving (Eq, Ord, Show)
@ -423,7 +424,8 @@ paramToParam resolver Errors {..} OpenApi.Param {..} = do
| OpenApi.ParamQuery <- _paramIn -> | OpenApi.ParamQuery <- _paramIn ->
Just StyleForm Just StyleForm
| otherwise -> Nothing, | otherwise -> Nothing,
schema = typ schema = typ,
allowEmpty = fromMaybe False _paramAllowEmptyValue
} }
headerToHeader :: headerToHeader ::

View File

@ -31,6 +31,7 @@ module Tie.Type
isArrayType, isArrayType,
isObjectType, isObjectType,
isOneOfType, isOneOfType,
isFitForFromFormInstance,
-- * Normalize types -- * Normalize types
normalizeType, normalizeType,
@ -518,6 +519,24 @@ isArrayType ty = case ty of
Array elem -> Just elem Array elem -> Just elem
_ -> Nothing _ -> Nothing
-- | Due to techinical reasons we only generate the fromFrom code for certain data types.
-- It's a matter of the instances that http-api-data providers but nothing structural that
-- would prevent us from doing all types.
isFitForFromFormInstance :: Type -> Bool
isFitForFromFormInstance ty
| Just {} <- isOneOfType ty =
False
| Just ty <- isArrayType ty =
isJust (isBasicType (namedType ty))
| Just objectTy <- isObjectType ty =
isJust (traverse (isBasicType . namedType) objectTy)
| Just {} <- isEnumType ty =
True
| Just {} <- isBasicType ty =
True
| otherwise =
False
-- | Casting a 'Type' to an 'ObjectType', if possible. `isObjectType` looks through -- | Casting a 'Type' to an 'ObjectType', if possible. `isObjectType` looks through
-- allOf, oneOf, anyOf to ensure -- allOf, oneOf, anyOf to ensure
isObjectType :: Type -> Maybe (ObjectType (Named Type)) isObjectType :: Type -> Maybe (ObjectType (Named Type))