mirror of
https://github.com/scarf-sh/tie.git
synced 2024-11-26 12:46:37 +03:00
structs forms
This commit is contained in:
parent
1aadc110ad
commit
56c76d587a
15
README.md
15
README.md
@ -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 .
|
||||||
```
|
```
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
|
6
example/generated/Petstore/API/Untitled-1.hs
Normal file
6
example/generated/Petstore/API/Untitled-1.hs
Normal 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
|
@ -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 =
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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 ::
|
||||||
|
@ -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))
|
||||||
|
Loading…
Reference in New Issue
Block a user