diff --git a/CHANGELOG.md b/CHANGELOG.md index c501457..4394d74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for params being passed in the header - Support for `type : null` - Support for `const : ` +- Support for `application/x-www-form-urlencoded` bodies - [Support for `additionalProperties` in objects](https://github.com/wolfadex/elm-open-api-cli/pull/184). [Adam DiCarlo](https://github.com/adamdicarlo0) ## [0.7.0] - 2024-09-23 diff --git a/src/OpenApi/Generate.elm b/src/OpenApi/Generate.elm index 8d3220e..cdd19f9 100644 --- a/src/OpenApi/Generate.elm +++ b/src/OpenApi/Generate.elm @@ -15,6 +15,7 @@ import Elm.Annotation import Elm.Arg import Elm.Case import Elm.Declare +import Elm.Let import Elm.Op import FastDict import FastSet @@ -37,6 +38,7 @@ import Gen.OpenApi.Common import Gen.Result import Gen.String import Gen.Task +import Gen.Url import Gen.Url.Builder import Json.Decode import Json.Schema.Definitions @@ -70,6 +72,7 @@ type ContentSchema = EmptyContent | JsonContent Common.Type | StringContent Mime + | UrlEncodedContent Common.Type | BytesContent Mime @@ -624,7 +627,8 @@ toRequestFunctions server effectTypes method pathUrl operation = let encoded : Elm.Expression encoded = - encoder <| Elm.get "body" config + Elm.get "body" config + |> encoder in { core = Gen.Http.jsonBody encoded , elmPages = Gen.BackendTask.Http.jsonBody encoded @@ -632,6 +636,47 @@ toRequestFunctions server effectTypes method pathUrl operation = } ) + UrlEncodedContent type_ -> + SchemaUtils.typeToEncoder True type_ + |> CliMonad.map + (\encoder config -> + let + encoded : Elm.Expression + encoded = + Elm.get "body" config + |> encoder + |> Gen.Json.Decode.decodeValue (Gen.Json.Decode.dict Gen.Json.Decode.value) + |> Gen.Result.map + (\keyValues -> + keyValues + |> Gen.Dict.toList + |> Gen.List.call_.map + (Elm.fn ( "keyVal", Nothing ) + (\keyVal -> + Elm.Let.letIn + (\( key, value ) -> + Gen.String.call_.concat + (Elm.list + [ Gen.Url.call_.percentEncode key + , Elm.string "=" + , Gen.Url.call_.percentEncode (Gen.Json.Encode.encode 0 value) + ] + ) + ) + |> Elm.Let.tuple "key" "value" keyVal + |> Elm.Let.toExpression + ) + ) + |> Gen.String.call_.join (Elm.string "&") + ) + |> Gen.Result.withDefault (Elm.string "") + in + { core = Gen.Http.call_.stringBody (Elm.string "application/x-www-form-urlencoded") encoded + , elmPages = Gen.Http.call_.stringBody (Elm.string "application/x-www-form-urlencoded") encoded + , lamderaProgramTest = Gen.Http.call_.stringBody (Elm.string "application/x-www-form-urlencoded") encoded + } + ) + StringContent mime -> CliMonad.succeed <| \config -> @@ -668,6 +713,10 @@ toRequestFunctions server effectTypes method pathUrl operation = SchemaUtils.typeToAnnotationWithNullable True type_ |> CliMonad.map (\annotation -> [ ( Common.UnsafeName "body", annotation ) ]) + UrlEncodedContent type_ -> + SchemaUtils.typeToAnnotationWithNullable True type_ + |> CliMonad.map (\annotation -> [ ( Common.UnsafeName "body", annotation ) ]) + StringContent _ -> CliMonad.succeed [ ( Common.UnsafeName "body", Elm.Annotation.string ) ] @@ -1806,13 +1855,22 @@ contentToContentSchema qualify content = stringContent "text/plain" htmlSchema Nothing -> - let - msg : String - msg = - "The content doesn't have an application/json, text/html or text/plain option, it has " ++ String.join ", " (Dict.keys content) - in - fallback - |> Maybe.withDefault (CliMonad.fail msg) + case Dict.get "application/x-www-form-urlencoded" content of + Just urlEncodedSchema -> + CliMonad.succeed urlEncodedSchema + |> CliMonad.stepOrFail "The request's application/x-www-form-urlencoded content option doesn't have a schema" + (OpenApi.MediaType.schema >> Maybe.map OpenApi.Schema.get) + |> CliMonad.andThen (SchemaUtils.schemaToType qualify) + |> CliMonad.map (\{ type_ } -> UrlEncodedContent type_) + + Nothing -> + let + msg : String + msg = + "The content doesn't have an application/json, text/html, text/plain, or application/x-www-form-urlencoded option, it has " ++ String.join ", " (Dict.keys content) + in + fallback + |> Maybe.withDefault (CliMonad.fail msg) stringContent : String -> OpenApi.MediaType.MediaType -> CliMonad ContentSchema stringContent mime htmlSchema = @@ -2491,6 +2549,10 @@ operationToTypesExpectAndResolver functionName operation = SchemaUtils.typeToDecoder True type_ |> CliMonad.andThen common + UrlEncodedContent type_ -> + SchemaUtils.typeToDecoder True type_ + |> CliMonad.andThen common + StringContent _ -> CliMonad.succeed Gen.Json.Decode.string |> CliMonad.andThen common @@ -2551,6 +2613,11 @@ operationToTypesExpectAndResolver functionName operation = (SchemaUtils.typeToAnnotationWithNullable False type_) (SchemaUtils.typeToAnnotationWithNullable True type_) + UrlEncodedContent type_ -> + CliMonad.map2 Tuple.pair + (SchemaUtils.typeToAnnotationWithNullable False type_) + (SchemaUtils.typeToAnnotationWithNullable True type_) + StringContent _ -> CliMonad.succeed ( Elm.Annotation.string @@ -2647,6 +2714,24 @@ operationToTypesExpectAndResolver functionName operation = errorDecoders errorTypeDeclaration + UrlEncodedContent type_ -> + CliMonad.map3 + (\successDecoder errorDecoders_ ( errorTypeDeclaration_, errorTypeAnnotation ) -> + { successType = type_ + , bodyTypeAnnotation = Elm.Annotation.string + , errorTypeDeclaration = errorTypeDeclaration_ + , errorTypeAnnotation = errorTypeAnnotation + , expect = expectJsonBetter errorDecoders_ successDecoder + , resolver = + { core = Gen.OpenApi.Common.jsonResolverCustom errorDecoders_ successDecoder + , lamderaProgramTest = Gen.OpenApi.Common.jsonResolverCustomEffect errorDecoders_ successDecoder + } + } + ) + (SchemaUtils.typeToDecoder True type_) + errorDecoders + errorTypeDeclaration + StringContent _ -> CliMonad.map2 (\errorDecoders_ ( errorTypeDeclaration_, errorTypeAnnotation ) ->