diff --git a/bin/all-petstore.sh b/bin/all-petstore.sh index f6156dbcee9..702c40f37b0 100755 --- a/bin/all-petstore.sh +++ b/bin/all-petstore.sh @@ -23,6 +23,7 @@ cd $APP_DIR ./bin/clojure-petstore.sh ./bin/csharp-petstore.sh ./bin/dynamic-html.sh +./bin/haskell-petstore.sh ./bin/html-petstore.sh ./bin/java-petstore.sh ./bin/java-petstore-jersey2.sh diff --git a/bin/haskell-servant-petstore.sh b/bin/haskell-servant-petstore.sh new file mode 100755 index 00000000000..a35f38251cc --- /dev/null +++ b/bin/haskell-servant-petstore.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +SCRIPT="$0" + +while [ -h "$SCRIPT" ] ; do + ls=`ls -ld "$SCRIPT"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + SCRIPT="$link" + else + SCRIPT=`dirname "$SCRIPT"`/"$link" + fi +done + +if [ ! -d "${APP_DIR}" ]; then + APP_DIR=`dirname "$SCRIPT"`/.. + APP_DIR=`cd "${APP_DIR}"; pwd` +fi + +executable="./modules/swagger-codegen-cli/target/swagger-codegen-cli.jar" + +if [ ! -f "$executable" ] +then + mvn clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="$@ generate -t modules/swagger-codegen/src/main/resources/haskell-servant -i modules/swagger-codegen/src/test/resources/2_0/petstore.json -l haskell-servant -o samples/server/petstore/haskell-servant" + +java $JAVA_OPTS -jar $executable $ags diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java new file mode 100644 index 00000000000..f70e416a299 --- /dev/null +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java @@ -0,0 +1,345 @@ +package io.swagger.codegen.languages; + +import io.swagger.codegen.*; +import io.swagger.models.properties.*; +import io.swagger.models.Model; +import io.swagger.models.Operation; +import io.swagger.models.Swagger; + +import java.util.*; +import java.io.File; + +public class HaskellServantCodegen extends DefaultCodegen implements CodegenConfig { + + // source folder where to write the files + protected String sourceFolder = "src"; + protected String apiVersion = "0.0.1"; + + /** + * Configures the type of generator. + * + * @return the CodegenType for this generator + * @see io.swagger.codegen.CodegenType + */ + public CodegenType getTag() { + return CodegenType.SERVER; + } + + /** + * Configures a friendly name for the generator. This will be used by the generator + * to select the library with the -l flag. + * + * @return the friendly name for the generator + */ + public String getName() { + return "haskell-servant"; + } + + /** + * Returns human-friendly help for the generator. Provide the consumer with help + * tips, parameters here + * + * @return A string value for the help message + */ + public String getHelp() { + return "Generates a HaskellServantCodegen library."; + } + + public HaskellServantCodegen() { + super(); + + // set the output folder here + outputFolder = "generated-code/HaskellServantCodegen"; + + /** + * Models. You can write model files using the modelTemplateFiles map. + * if you want to create one template for file, you can do so here. + * for multiple files for model, just put another entry in the `modelTemplateFiles` with + * a different extension + */ + modelTemplateFiles.put( + "model.mustache", // the template to use + ".hs"); // the extension for each file to write + + /** + * Api classes. You can write classes for each Api file with the apiTemplateFiles map. + * as with models, add multiple entries with different extensions for multiple files per + * class + */ + apiTemplateFiles.put( + "api.mustache", // the template to use + ".hs"); // the extension for each file to write + + /** + * Template Location. This is the location which templates will be read from. The generator + * will use the resource stream to attempt to read the templates. + */ + embeddedTemplateDir = templateDir = "haskell"; + + /** + * Api Package. Optional, if needed, this can be used in templates + */ + apiPackage = "Api"; + + /** + * Model Package. Optional, if needed, this can be used in templates + */ + modelPackage = "Model"; + + /** + * Reserved words. Override this with reserved words specific to your language + */ + // from https://wiki.haskell.org/Keywords + reservedWords = new HashSet( + Arrays.asList( + "as", "case", "of", + "class", "data", // "data family", "data instance", + "default", "deriving", // "deriving instance", + "do", + "forall", "foreign", "hiding", + "id", + "if", "then", "else", + "import", "infix", "infixl", "infixr", + "instance", "let", "in", + "mdo", "module", "newtype", + "proc", "qualified", "rec", + "type", // "type family", "type instance", + "where" + ) + ); + + /** + * Additional Properties. These values can be passed to the templates and + * are available in models, apis, and supporting files + */ + additionalProperties.put("apiVersion", apiVersion); + + /** + * Supporting Files. You can write single files for the generator with the + * entire object tree available. If the input file has a suffix of `.mustache + * it will be processed by the template engine. Otherwise, it will be copied + */ + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("stack.mustache", "", "stack.yaml")); + supportingFiles.add(new SupportingFile("haskell-servant-codegen.mustache", "", "haskell-servant-codegen.cabal")); + supportingFiles.add(new SupportingFile("Setup.mustache", "", "Setup.hs")); + supportingFiles.add(new SupportingFile("LICENSE", "", "LICENSE")); + supportingFiles.add(new SupportingFile("Apis.mustache", "lib", "Apis.hs")); + supportingFiles.add(new SupportingFile("Utils.mustache", "lib", "Utils.hs")); + supportingFiles.add(new SupportingFile("Client.mustache", "client", "Main.hs")); + supportingFiles.add(new SupportingFile("Server.mustache", "server", "Main.hs")); + + /** + * Language Specific Primitives. These types will not trigger imports by + * the client generator + */ + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "Bool", + "String", + "Int", + "Integer", + "Float", + "Char", + "Double", + "List", + "FilePath" + ) + ); + + typeMapping.clear(); + // typeMapping.put("enum", "NSString"); + typeMapping.put("array", "List"); + typeMapping.put("set", "Set"); + typeMapping.put("boolean", "Bool"); + typeMapping.put("string", "String"); + typeMapping.put("int", "Int"); + typeMapping.put("long", "Integer"); + typeMapping.put("float", "Float"); + // typeMapping.put("byte", "Byte"); + typeMapping.put("short", "Int"); + typeMapping.put("char", "Char"); + typeMapping.put("double", "Double"); + typeMapping.put("DateTime", "Integer"); + // typeMapping.put("object", "Map"); + typeMapping.put("file", "FilePath"); + + importMapping.clear(); + importMapping.put("Map", "qualified Data.Map as Map"); + + cliOptions.add(new CliOption(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC)); + } + + /** + * Escapes a reserved word as defined in the `reservedWords` array. Handle escaping + * those terms here. This logic is only called if a variable matches the reseved words + * + * @return the escaped term + */ + @Override + public String escapeReservedWord(String name) { + return name + "_"; + } + + /** + * Location to write model files. You can use the modelPackage() as defined when the class is + * instantiated + */ + public String modelFileFolder() { + return outputFolder + File.separatorChar + "lib" + File.separatorChar + modelPackage().replace('.', File.separatorChar); + } + + /** + * Location to write api files. You can use the apiPackage() as defined when the class is + * instantiated + */ + @Override + public String apiFileFolder() { + return outputFolder + File.separatorChar + "lib" + File.separatorChar + apiPackage().replace('.', File.separatorChar); + } + + /** + * Optional - type declaration. This is a String which is used by the templates to instantiate your + * types. There is typically special handling for different property types + * + * @return a string value used as the `dataType` field for model templates, `returnType` for api templates + */ + @Override + public String getTypeDeclaration(Property p) { + if(p instanceof ArrayProperty) { + ArrayProperty ap = (ArrayProperty) p; + Property inner = ap.getItems(); + return "[" + getTypeDeclaration(inner) + "]"; + } + else if (p instanceof MapProperty) { + MapProperty mp = (MapProperty) p; + Property inner = mp.getAdditionalProperties(); + return "Map.Map String " + getTypeDeclaration(inner); + } + return super.getTypeDeclaration(p); + } + + /** + * Optional - swagger type conversion. This is used to map swagger types in a `Property` into + * either language specific types via `typeMapping` or into complex models if there is not a mapping. + * + * @return a string value of the type or complex model for this property + * @see io.swagger.models.properties.Property + */ + @Override + public String getSwaggerType(Property p) { + String swaggerType = super.getSwaggerType(p); + String type = null; + if(typeMapping.containsKey(swaggerType)) { + type = typeMapping.get(swaggerType); + if(languageSpecificPrimitives.contains(type)) + return toModelName(type); + } + else + type = swaggerType; + return toModelName(type); + } + + private String capturePath(String path, List pathParams) { + for (CodegenParameter p : pathParams) { + String pName = "{"+p.baseName+"}"; + if (path.indexOf(pName) >= 0) { + path = path.replace(pName, "Capture " + "\""+p.baseName+"\" " + p.dataType); + } + } + return path; + } + + private String queryPath(String path, List queryParams) { + for (CodegenParameter p : queryParams) { + path += " :> QueryParam \"" + p.baseName + "\" " + p.dataType; + } + return path; + } + + private String bodyPath(String path, List bodyParams) { + for (CodegenParameter p : bodyParams) { + path += " :> ReqBody '[JSON] " + p.dataType; + } + return path; + } + + private String formPath(String path, List formParams) { + String names = "Form"; + for (CodegenParameter p : formParams) { + if(p.dataType.equals("FilePath")){ + // file data processing + } + names += p.baseName; + } + if(formParams.size() > 0){ + path += " :> ReqBody '[FormUrlEncoded] " + names; + } + return path; + } + + private String headerPath(String path, List headerParams) { + for (CodegenParameter p : headerParams) { + path += " :> Header \"" + p.baseName + "\" " + p.dataType; + } + return path; + } + + + private String filterReturnType(String rt) { + if (rt == null || rt.equals("null")) { + return "()"; + } else if (rt.indexOf(" ") >= 0) { + return "(" + rt + ")"; + } + return rt; + } + + private String addReturnPath(String path, String httpMethod, String returnType) { + return path + " :> " + upperCaseFirst(httpMethod) + " '[JSON] " + filterReturnType(returnType); + } + + private String joinStrings(String sep, List ss) { + StringBuilder sb = new StringBuilder(); + for (String s : ss) { + if (sb.length() > 0) { + sb.append(sep); + } + sb.append(s); + } + return sb.toString(); + } + + private String replacePathSplitter(String path) { + String[] ps = path.replaceFirst("/", "").split("/", 0); + List rs = new ArrayList(); + for (String p : ps) { + if (p.indexOf("{") < 0) { + rs.add("\"" + p + "\""); + } else { + rs.add(p); + } + } + return joinStrings(" :> ", rs); + } + + private String upperCaseFirst(String str) { + char[] array = str.toLowerCase().toCharArray(); + array[0] = Character.toUpperCase(array[0]); + return new String(array); + } + + private String parseScheme(String basePath) { + return "Http"; + } + + @Override + public CodegenOperation fromOperation(String resourcePath, String httpMethod, Operation operation, Map definitions, Swagger swagger){ + CodegenOperation op = super.fromOperation(resourcePath, httpMethod, operation, definitions, swagger); + String path = op.path; + op.nickname = addReturnPath(headerPath(formPath(bodyPath(queryPath(capturePath(replacePathSplitter(path), op.pathParams), op.queryParams), op.bodyParams), op.formParams), op.headerParams), op.httpMethod, op.returnType); + return op; + } + +} \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig b/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig index 5d8a4f0fc6b..9129fc33421 100644 --- a/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig +++ b/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig @@ -33,3 +33,4 @@ io.swagger.codegen.languages.TypeScriptNodeClientCodegen io.swagger.codegen.languages.AkkaScalaClientCodegen io.swagger.codegen.languages.CsharpDotNet2ClientCodegen io.swagger.codegen.languages.ClojureClientCodegen +io.swagger.codegen.languages.HaskellServantCodegen diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/Apis.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/Apis.mustache new file mode 100644 index 00000000000..3b60afafec6 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/haskell-servant/Apis.mustache @@ -0,0 +1,26 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE FlexibleInstances #-} +module Apis ( + api + , API + ) where + +{{#apiInfo}} +{{#apis}} +import {{package}}.{{classname}} ({{classname}}) +{{/apis}} +{{/apiInfo}} + +import Data.Proxy +import Servant.API +import Test.QuickCheck +import qualified Data.Map as Map +import Utils + +type API = {{#apiInfo}}{{#apis}}{{classname}}{{#hasMore}} :<|> {{/hasMore}}{{/apis}}{{/apiInfo}} + +api :: Proxy API +api = Proxy diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/Client.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/Client.mustache new file mode 100644 index 00000000000..ab48bcbc43e --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/haskell-servant/Client.mustache @@ -0,0 +1,35 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} + +module Main where + +import Control.Monad (void) +import Control.Monad.Trans.Either +import Control.Monad.IO.Class +import Servant.API +import Servant.Client + +import Data.List.Split (splitOn) +import Network.URI (URI (..), URIAuth (..), parseURI) +import Data.Maybe (fromMaybe) +import Test.QuickCheck +import Control.Monad +{{#models}} +import {{importPath}} +{{/models}} +{{#apiInfo}} +{{#apis}} +import {{package}}.{{classname}} +{{/apis}} +{{/apiInfo}} + +-- userClient :: IO () +-- userClient = do +-- users <- sample' (arbitrary :: Gen String) +-- let user = last users +-- void . runEitherT $ do +-- getUserByName user >>= (liftIO . putStrLn . show) + +main :: IO () +main = putStrLn "Hello Server!" diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/LICENSE b/modules/swagger-codegen/src/main/resources/haskell-servant/LICENSE new file mode 100644 index 00000000000..b0033f5f837 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/haskell-servant/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 Masahiro Yamauchi + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/README.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/README.mustache new file mode 100644 index 00000000000..c8f4b4bbb9d --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/haskell-servant/README.mustache @@ -0,0 +1,7 @@ +# Generated Servant Codes + +## How to use + +0. Install haskell-stack +1. stack build +2. stack exec client diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/Server.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/Server.mustache new file mode 100644 index 00000000000..68b4ff6ce33 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/haskell-servant/Server.mustache @@ -0,0 +1,13 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} + +module Main where + +import Apis +import Servant +import Servant.Mock +import qualified Network.Wai.Handler.Warp as Warp + +main :: IO () +main = Warp.run 8080 $ serve api (mock api) diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/Setup.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/Setup.mustache new file mode 100644 index 00000000000..9a994af677b --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/haskell-servant/Setup.mustache @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/Utils.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/Utils.mustache new file mode 100644 index 00000000000..f6db2602ce8 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/haskell-servant/Utils.mustache @@ -0,0 +1,27 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE FlexibleInstances #-} +module Utils where + +import GHC.Generics +import Servant.API +import Data.List (intercalate) +import Data.List.Split (splitOn) +import qualified Data.Map as Map +import qualified Data.Text as T +import Test.QuickCheck + +instance FromText [String] where + fromText = Just . splitOn "," . T.unpack + +instance ToText [String] where + toText = T.pack . intercalate "," + +lkp inputs l = case lookup l inputs of + Nothing -> Left $ "label " ++ T.unpack l ++ " not found" + Just v -> Right $ read (T.unpack v) + +instance (Ord k, Arbitrary k, Arbitrary v) => Arbitrary (Map.Map k v) where + arbitrary = Map.fromList <$> arbitrary diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/api.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/api.mustache new file mode 100644 index 00000000000..c49fcd5eff8 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/haskell-servant/api.mustache @@ -0,0 +1,82 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedStrings #-} + +{{#operations}} +module {{package}}.{{classname}} ( + {{#operation}}{{operationId}}{{#hasMore}} + , {{/hasMore}}{{/operation}} + , proxy{{classname}} + , {{classname}} + ) where +{{/operations}} + +import GHC.Generics +import Data.Proxy +import Servant.API +import Servant.Client +import Network.URI (URI (..), URIAuth (..), parseURI) +import Data.Maybe (fromMaybe) +import Servant.Common.Text +import Data.List (intercalate) +import qualified Data.Text as T +import Utils +import Test.QuickCheck +{{#imports}}import {{import}} +{{/imports}} + +{{#operations}} +{{#operation}} +{{#hasFormParams}} +data Form{{#formParams}}{{baseName}}{{/formParams}} = Form{{#formParams}}{{baseName}}{{/formParams}} + { {{#formParams}}{{baseName}} :: {{dataType}}{{#hasMore}} + , {{/hasMore}}{{/formParams}} + } deriving (Show, Eq, Generic) + +instance FromFormUrlEncoded Form{{#formParams}}{{baseName}}{{/formParams}} where + fromFormUrlEncoded inputs = Form{{#formParams}}{{baseName}}{{/formParams}} <$> {{#formParams}} lkp inputs "{{baseName}}"{{#hasMore}} <*> {{/hasMore}}{{/formParams}} +instance ToFormUrlEncoded Form{{#formParams}}{{baseName}}{{/formParams}} where + toFormUrlEncoded x = [({{#formParams}}(T.pack $ show $ {{package}}.{{classname}}.{{baseName}} x){{#hasMore}}, {{/hasMore}}{{/formParams}})] +instance Arbitrary Form{{#formParams}}{{baseName}}{{/formParams}} where + arbitrary = Form{{#formParams}}{{baseName}}{{/formParams}} <$> {{#formParams}}arbitrary{{#hasMore}} <*> {{/hasMore}}{{/formParams}} +{{/hasFormParams}} + +{{/operation}} +{{/operations}} + +{{#operations}} +type {{classname}} = {{#operation}}{{& nickname}} -- {{operationId}}{{#hasMore}} + :<|> {{/hasMore}}{{/operation}} +{{/operations}} + +proxy{{classname}} :: Proxy {{classname}} +proxy{{classname}} = Proxy + +{{#operations}} + +serverPath :: String +serverPath = "{{basePath}}" + +parseHostPort :: String -> (String, Int) +parseHostPort path = (host,port) + where + authority = case parseURI path of + Just x -> uriAuthority x + _ -> Nothing + (host, port) = case authority of + Just y -> (uriRegName y, (getPort . uriPort) y) + _ -> ("localhost", 8080) + getPort p = case (length p) of + 0 -> 80 + _ -> (read . drop 1) p + +(host, port) = parseHostPort serverPath + +{{#operation}} +{{operationId}}{{#hasMore}} + :<|> {{/hasMore}}{{/operation}} + = client proxy{{classname}} $ BaseUrl Http host port +{{/operations}} diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/haskell-servant-codegen.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/haskell-servant-codegen.mustache new file mode 100644 index 00000000000..8b7d7fc54b2 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/haskell-servant/haskell-servant-codegen.mustache @@ -0,0 +1,62 @@ +name: haskell-servant-codegen +version: 0.1.0.0 +synopsis: Swagger-codegen example for Haskell servant +description: Please see README.md +homepage: https://github.com/swagger-api/swagger-codegen#readme +license: Apache-2.0 +license-file: LICENSE +author: Masahiro Yamauchi +maintainer: sgt.yamauchi@gmail.com +copyright: 2015- Masahiro Yamauchi +category: Web +build-type: Simple +-- extra-source-files: +cabal-version: >=1.10 + +library + hs-source-dirs: lib + exposed-modules: Utils{{#models}}{{#model}} + , {{importPath}}{{/model}}{{/models}} +{{#apiInfo}} +{{#apis}} + , {{package}}.{{classname}} +{{/apis}} +{{/apiInfo}} + , Apis + ghc-options: -Wall + build-depends: base + , aeson + , text + , split + , containers + , network-uri + , QuickCheck + , servant + , servant-client + default-language: Haskell2010 + +executable client + hs-source-dirs: client + main-is: Main.hs + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: base + , either + , transformers + , split + , network-uri + , QuickCheck + , servant + , servant-client + , haskell-servant-codegen + default-language: Haskell2010 + +executable server + hs-source-dirs: server + main-is: Main.hs + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: base + , warp + , servant-server + , servant-mock + , haskell-servant-codegen + default-language: Haskell2010 diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/model.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/model.mustache new file mode 100644 index 00000000000..1f76c197ae2 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/haskell-servant/model.mustache @@ -0,0 +1,34 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +{{#models}} +{{#model}} +module {{package}}.{{classname}} + ( {{classname}} (..) + ) where +{{/model}} +{{/models}} + +import Data.Aeson +import GHC.Generics +import Test.QuickCheck +{{#imports}}import {{import}} +{{/imports}} + +{{#models}} +{{#model}} + +data {{classname}} = {{classname}} + { {{#vars}}{{& name}} :: {{datatype}}{{#hasMore}} + , {{/hasMore}}{{/vars}} + } deriving (Show, Eq, Generic) + +instance FromJSON {{classname}} +instance ToJSON {{classname}} +instance Arbitrary {{classname}} where + arbitrary = {{classname}} <$> {{#vars}}arbitrary{{#hasMore}} <*> {{/hasMore}}{{/vars}} +{{/model}} +{{/models}} diff --git a/modules/swagger-codegen/src/main/resources/haskell-servant/stack.mustache b/modules/swagger-codegen/src/main/resources/haskell-servant/stack.mustache new file mode 100644 index 00000000000..00448df3e95 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/haskell-servant/stack.mustache @@ -0,0 +1,33 @@ +# For more information, see: https://github.com/commercialhaskell/stack/blob/release/doc/yaml_configuration.md + +# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2) +resolver: lts-3.17 + +# Local packages, usually specified by relative directory name +packages: +- '.' + +# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) +extra-deps: +- servant-mock-0.4.4.6 + +# Override default flag values for local packages and extra-deps +flags: {} + +# Extra package databases containing global packages +extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +# system-ghc: true + +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: >= 0.1.10.0 + +# Override the architecture used by stack, especially useful on Windows +# arch: i386 +# arch: x86_64 + +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/haskellservant/HaskellServantOptionsTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/haskellservant/HaskellServantOptionsTest.java new file mode 100644 index 00000000000..d5848e47cae --- /dev/null +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/haskellservant/HaskellServantOptionsTest.java @@ -0,0 +1,36 @@ +package io.swagger.codegen.haskellservant; + +import io.swagger.codegen.AbstractOptionsTest; +import io.swagger.codegen.CodegenConfig; +import io.swagger.codegen.languages.HaskellServantCodegen; +import io.swagger.codegen.options.HaskellServantOptionsProvider; + +import mockit.Expectations; +import mockit.Tested; + +public class HaskellServantOptionsTest extends AbstractOptionsTest { + + @Tested + private HaskellServantCodegen clientCodegen; + + public HaskellServantOptionsTest() { + super(new HaskellServantOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + clientCodegen.setModelPackage(HaskellServantOptionsProvider.MODEL_PACKAGE_VALUE); + times = 1; + clientCodegen.setApiPackage(HaskellServantOptionsProvider.API_PACKAGE_VALUE); + times = 1; + clientCodegen.setSortParamsByRequiredFlag(Boolean.valueOf(HaskellServantOptionsProvider.SORT_PARAMS_VALUE)); + times = 1; + }}; + } +} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/haskellservant/HaskellTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/haskellservant/HaskellTest.java new file mode 100644 index 00000000000..e371a4f311e --- /dev/null +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/haskellservant/HaskellTest.java @@ -0,0 +1,20 @@ +package io.swagger.codegen.haskellservant; + +import io.swagger.codegen.CodegenModel; +import io.swagger.codegen.CodegenOperation; +import io.swagger.codegen.DefaultCodegen; +import io.swagger.codegen.languages.HaskellServantCodegen; +import io.swagger.models.Operation; +import io.swagger.models.Swagger; +import io.swagger.parser.SwaggerParser; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class HaskellTest { + + @Test(description = "convert a haskell model with dots") + public void modelTest() { + Assert.assertEquals(true, true); + } +} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/HaskellServantOptionsProvider.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/HaskellServantOptionsProvider.java new file mode 100644 index 00000000000..cc4f7f3a9b3 --- /dev/null +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/HaskellServantOptionsProvider.java @@ -0,0 +1,34 @@ +package io.swagger.codegen.options; + +import io.swagger.codegen.CodegenConstants; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class HaskellServantOptionsProvider implements OptionsProvider { + public static final String MODEL_PACKAGE_VALUE = "Model"; + public static final String API_PACKAGE_VALUE = "Api"; + public static final String SORT_PARAMS_VALUE = "false"; + public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true"; + + @Override + public String getLanguage() { + return "haskell"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder.put(CodegenConstants.MODEL_PACKAGE, MODEL_PACKAGE_VALUE) + .put(CodegenConstants.API_PACKAGE, API_PACKAGE_VALUE) + .put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, SORT_PARAMS_VALUE) + .put(CodegenConstants.ENSURE_UNIQUE_PARAMS, ENSURE_UNIQUE_PARAMS_VALUE) + .build(); + } + + @Override + public boolean isServer() { + return true; + } +} diff --git a/samples/server/petstore/haskell-servant/LICENSE b/samples/server/petstore/haskell-servant/LICENSE new file mode 100644 index 00000000000..b0033f5f837 --- /dev/null +++ b/samples/server/petstore/haskell-servant/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 Masahiro Yamauchi + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/samples/server/petstore/haskell-servant/README.md b/samples/server/petstore/haskell-servant/README.md new file mode 100644 index 00000000000..c8f4b4bbb9d --- /dev/null +++ b/samples/server/petstore/haskell-servant/README.md @@ -0,0 +1,7 @@ +# Generated Servant Codes + +## How to use + +0. Install haskell-stack +1. stack build +2. stack exec client diff --git a/samples/server/petstore/haskell-servant/Setup.hs b/samples/server/petstore/haskell-servant/Setup.hs new file mode 100644 index 00000000000..9a994af677b --- /dev/null +++ b/samples/server/petstore/haskell-servant/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/samples/server/petstore/haskell-servant/client/Main.hs b/samples/server/petstore/haskell-servant/client/Main.hs new file mode 100644 index 00000000000..d0094c5f52e --- /dev/null +++ b/samples/server/petstore/haskell-servant/client/Main.hs @@ -0,0 +1,35 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} + +module Main where + +import Control.Monad (void) +import Control.Monad.Trans.Either +import Control.Monad.IO.Class +import Servant.API +import Servant.Client + +import Data.List.Split (splitOn) +import Network.URI (URI (..), URIAuth (..), parseURI) +import Data.Maybe (fromMaybe) +import Test.QuickCheck +import Control.Monad +import Model.User +import Model.Category +import Model.Pet +import Model.Tag +import Model.Order +import Api.UserApi +import Api.StoreApi +import Api.PetApi + +-- userClient :: IO () +-- userClient = do +-- users <- sample' (arbitrary :: Gen String) +-- let user = last users +-- void . runEitherT $ do +-- getUserByName user >>= (liftIO . putStrLn . show) + +main :: IO () +main = putStrLn "Hello Server!" diff --git a/samples/server/petstore/haskell-servant/haskell-servant-codegen.cabal b/samples/server/petstore/haskell-servant/haskell-servant-codegen.cabal new file mode 100644 index 00000000000..2693b3dd93b --- /dev/null +++ b/samples/server/petstore/haskell-servant/haskell-servant-codegen.cabal @@ -0,0 +1,68 @@ +name: haskell-servant-codegen +version: 0.1.0.0 +synopsis: Swagger-codegen example for Haskell servant +description: Please see README.md +homepage: http://github.com/algas/haskell-servant-codegen#readme +license: Apache-2.0 +license-file: LICENSE +author: Masahiro Yamauchi +maintainer: sgt.yamauchi@gmail.com +copyright: 2015- Masahiro Yamauchi +category: Web +build-type: Simple +-- extra-source-files: +cabal-version: >=1.10 + +library + hs-source-dirs: lib + exposed-modules: Utils + , Model.User + , Model.Category + , Model.Pet + , Model.Tag + , Model.Order + , Api.UserApi + , Api.StoreApi + , Api.PetApi + , Apis + ghc-options: -Wall + build-depends: base + , aeson + , text + , split + , containers + , network-uri + , QuickCheck + , servant + , servant-client + default-language: Haskell2010 + +executable client + hs-source-dirs: client + main-is: Main.hs + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: base + , either + , transformers + , split + , network-uri + , QuickCheck + , servant + , servant-client + , haskell-servant-codegen + default-language: Haskell2010 + +executable server + hs-source-dirs: server + main-is: Main.hs + ghc-options: -threaded -rtsopts -with-rtsopts=-N + build-depends: base + , warp + , servant-server + , servant-mock + , haskell-servant-codegen + default-language: Haskell2010 + +source-repository head + type: git + location: https://github.com/algas/haskell-servant-codegen diff --git a/samples/server/petstore/haskell-servant/lib/Api/PetApi.hs b/samples/server/petstore/haskell-servant/lib/Api/PetApi.hs new file mode 100644 index 00000000000..497ea240577 --- /dev/null +++ b/samples/server/petstore/haskell-servant/lib/Api/PetApi.hs @@ -0,0 +1,104 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedStrings #-} + +module Api.PetApi ( + updatePet + , addPet + , findPetsByStatus + , findPetsByTags + , getPetById + , updatePetWithForm + , deletePet + , uploadFile + , proxyPetApi + , PetApi + ) where + +import GHC.Generics +import Data.Proxy +import Servant.API +import Servant.Client +import Network.URI (URI (..), URIAuth (..), parseURI) +import Data.Maybe (fromMaybe) +import Servant.Common.Text +import Data.List (intercalate) +import qualified Data.Text as T +import Utils +import Test.QuickCheck +import Model.Pet + + + + + + +data Formnamestatus = Formnamestatus + { name :: String + , status :: String + } deriving (Show, Eq, Generic) + +instance FromFormUrlEncoded Formnamestatus where + fromFormUrlEncoded inputs = Formnamestatus <$> lkp inputs "name" <*> lkp inputs "status" +instance ToFormUrlEncoded Formnamestatus where + toFormUrlEncoded x = [((T.pack $ show $ Api.PetApi.name x), (T.pack $ show $ Api.PetApi.status x))] +instance Arbitrary Formnamestatus where + arbitrary = Formnamestatus <$> arbitrary <*> arbitrary + + +data FormadditionalMetadatafile = FormadditionalMetadatafile + { additionalMetadata :: String + , file :: FilePath + } deriving (Show, Eq, Generic) + +instance FromFormUrlEncoded FormadditionalMetadatafile where + fromFormUrlEncoded inputs = FormadditionalMetadatafile <$> lkp inputs "additionalMetadata" <*> lkp inputs "file" +instance ToFormUrlEncoded FormadditionalMetadatafile where + toFormUrlEncoded x = [((T.pack $ show $ Api.PetApi.additionalMetadata x), (T.pack $ show $ Api.PetApi.file x))] +instance Arbitrary FormadditionalMetadatafile where + arbitrary = FormadditionalMetadatafile <$> arbitrary <*> arbitrary + + +type PetApi = "pet" :> ReqBody '[JSON] Pet :> Put '[JSON] () -- updatePet + :<|> "pet" :> ReqBody '[JSON] Pet :> Post '[JSON] () -- addPet + :<|> "pet" :> "findByStatus" :> QueryParam "status" [String] :> Get '[JSON] [Pet] -- findPetsByStatus + :<|> "pet" :> "findByTags" :> QueryParam "tags" [String] :> Get '[JSON] [Pet] -- findPetsByTags + :<|> "pet" :> Capture "petId" Integer :> Get '[JSON] Pet -- getPetById + :<|> "pet" :> Capture "petId" String :> ReqBody '[FormUrlEncoded] Formnamestatus :> Post '[JSON] () -- updatePetWithForm + :<|> "pet" :> Capture "petId" Integer :> Header "api_key" String :> Delete '[JSON] () -- deletePet + :<|> "pet" :> Capture "petId" Integer :> "uploadImage" :> ReqBody '[FormUrlEncoded] FormadditionalMetadatafile :> Post '[JSON] () -- uploadFile + +proxyPetApi :: Proxy PetApi +proxyPetApi = Proxy + + +serverPath :: String +serverPath = "http://petstore.swagger.io/v2" + +parseHostPort :: String -> (String, Int) +parseHostPort path = (host,port) + where + authority = case parseURI path of + Just x -> uriAuthority x + _ -> Nothing + (host, port) = case authority of + Just y -> (uriRegName y, (getPort . uriPort) y) + _ -> ("localhost", 8080) + getPort p = case (length p) of + 0 -> 80 + _ -> (read . drop 1) p + +(host, port) = parseHostPort serverPath + +updatePet + :<|> addPet + :<|> findPetsByStatus + :<|> findPetsByTags + :<|> getPetById + :<|> updatePetWithForm + :<|> deletePet + :<|> uploadFile + = client proxyPetApi $ BaseUrl Http host port diff --git a/samples/server/petstore/haskell-servant/lib/Api/StoreApi.hs b/samples/server/petstore/haskell-servant/lib/Api/StoreApi.hs new file mode 100644 index 00000000000..819da1e5a05 --- /dev/null +++ b/samples/server/petstore/haskell-servant/lib/Api/StoreApi.hs @@ -0,0 +1,67 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedStrings #-} + +module Api.StoreApi ( + getInventory + , placeOrder + , getOrderById + , deleteOrder + , proxyStoreApi + , StoreApi + ) where + +import GHC.Generics +import Data.Proxy +import Servant.API +import Servant.Client +import Network.URI (URI (..), URIAuth (..), parseURI) +import Data.Maybe (fromMaybe) +import Servant.Common.Text +import Data.List (intercalate) +import qualified Data.Text as T +import Utils +import Test.QuickCheck +import qualified Data.Map as Map +import Model.Order + + + + + + +type StoreApi = "store" :> "inventory" :> Get '[JSON] (Map.Map String Integer) -- getInventory + :<|> "store" :> "order" :> ReqBody '[JSON] Order :> Post '[JSON] Order -- placeOrder + :<|> "store" :> "order" :> Capture "orderId" String :> Get '[JSON] Order -- getOrderById + :<|> "store" :> "order" :> Capture "orderId" String :> Delete '[JSON] () -- deleteOrder + +proxyStoreApi :: Proxy StoreApi +proxyStoreApi = Proxy + + +serverPath :: String +serverPath = "http://petstore.swagger.io/v2" + +parseHostPort :: String -> (String, Int) +parseHostPort path = (host,port) + where + authority = case parseURI path of + Just x -> uriAuthority x + _ -> Nothing + (host, port) = case authority of + Just y -> (uriRegName y, (getPort . uriPort) y) + _ -> ("localhost", 8080) + getPort p = case (length p) of + 0 -> 80 + _ -> (read . drop 1) p + +(host, port) = parseHostPort serverPath + +getInventory + :<|> placeOrder + :<|> getOrderById + :<|> deleteOrder + = client proxyStoreApi $ BaseUrl Http host port diff --git a/samples/server/petstore/haskell-servant/lib/Api/UserApi.hs b/samples/server/petstore/haskell-servant/lib/Api/UserApi.hs new file mode 100644 index 00000000000..b7c0ad638c5 --- /dev/null +++ b/samples/server/petstore/haskell-servant/lib/Api/UserApi.hs @@ -0,0 +1,82 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedStrings #-} + +module Api.UserApi ( + createUser + , createUsersWithArrayInput + , createUsersWithListInput + , loginUser + , logoutUser + , getUserByName + , updateUser + , deleteUser + , proxyUserApi + , UserApi + ) where + +import GHC.Generics +import Data.Proxy +import Servant.API +import Servant.Client +import Network.URI (URI (..), URIAuth (..), parseURI) +import Data.Maybe (fromMaybe) +import Servant.Common.Text +import Data.List (intercalate) +import qualified Data.Text as T +import Utils +import Test.QuickCheck +import Model.User + + + + + + + + + + +type UserApi = "user" :> ReqBody '[JSON] User :> Post '[JSON] () -- createUser + :<|> "user" :> "createWithArray" :> ReqBody '[JSON] [User] :> Post '[JSON] () -- createUsersWithArrayInput + :<|> "user" :> "createWithList" :> ReqBody '[JSON] [User] :> Post '[JSON] () -- createUsersWithListInput + :<|> "user" :> "login" :> QueryParam "username" String :> QueryParam "password" String :> Get '[JSON] String -- loginUser + :<|> "user" :> "logout" :> Get '[JSON] () -- logoutUser + :<|> "user" :> Capture "username" String :> Get '[JSON] User -- getUserByName + :<|> "user" :> Capture "username" String :> ReqBody '[JSON] User :> Put '[JSON] () -- updateUser + :<|> "user" :> Capture "username" String :> Delete '[JSON] () -- deleteUser + +proxyUserApi :: Proxy UserApi +proxyUserApi = Proxy + + +serverPath :: String +serverPath = "http://petstore.swagger.io/v2" + +parseHostPort :: String -> (String, Int) +parseHostPort path = (host,port) + where + authority = case parseURI path of + Just x -> uriAuthority x + _ -> Nothing + (host, port) = case authority of + Just y -> (uriRegName y, (getPort . uriPort) y) + _ -> ("localhost", 8080) + getPort p = case (length p) of + 0 -> 80 + _ -> (read . drop 1) p + +(host, port) = parseHostPort serverPath + +createUser + :<|> createUsersWithArrayInput + :<|> createUsersWithListInput + :<|> loginUser + :<|> logoutUser + :<|> getUserByName + :<|> updateUser + :<|> deleteUser + = client proxyUserApi $ BaseUrl Http host port diff --git a/samples/server/petstore/haskell-servant/lib/Apis.hs b/samples/server/petstore/haskell-servant/lib/Apis.hs new file mode 100644 index 00000000000..f8d61d2608a --- /dev/null +++ b/samples/server/petstore/haskell-servant/lib/Apis.hs @@ -0,0 +1,24 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE FlexibleInstances #-} +module Apis ( + api + , API + ) where + +import Api.UserApi (UserApi) +import Api.StoreApi (StoreApi) +import Api.PetApi (PetApi) + +import Data.Proxy +import Servant.API +import Test.QuickCheck +import qualified Data.Map as Map +import Utils + +type API = UserApi :<|> StoreApi :<|> PetApi + +api :: Proxy API +api = Proxy diff --git a/samples/server/petstore/haskell-servant/lib/Model/Category.hs b/samples/server/petstore/haskell-servant/lib/Model/Category.hs new file mode 100644 index 00000000000..2d7d90776b2 --- /dev/null +++ b/samples/server/petstore/haskell-servant/lib/Model/Category.hs @@ -0,0 +1,24 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +module Model.Category + ( Category (..) + ) where + +import Data.Aeson +import GHC.Generics +import Test.QuickCheck + + +data Category = Category + { id_ :: Integer + , name :: String + } deriving (Show, Eq, Generic) + +instance FromJSON Category +instance ToJSON Category +instance Arbitrary Category where + arbitrary = Category <$> arbitrary <*> arbitrary diff --git a/samples/server/petstore/haskell-servant/lib/Model/Order.hs b/samples/server/petstore/haskell-servant/lib/Model/Order.hs new file mode 100644 index 00000000000..5c50f4ad85c --- /dev/null +++ b/samples/server/petstore/haskell-servant/lib/Model/Order.hs @@ -0,0 +1,28 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +module Model.Order + ( Order (..) + ) where + +import Data.Aeson +import GHC.Generics +import Test.QuickCheck + + +data Order = Order + { id_ :: Integer + , petId :: Integer + , quantity :: Integer + , shipDate :: Integer + , status :: String + , complete :: Bool + } deriving (Show, Eq, Generic) + +instance FromJSON Order +instance ToJSON Order +instance Arbitrary Order where + arbitrary = Order <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary diff --git a/samples/server/petstore/haskell-servant/lib/Model/Pet.hs b/samples/server/petstore/haskell-servant/lib/Model/Pet.hs new file mode 100644 index 00000000000..dfe4bb8893a --- /dev/null +++ b/samples/server/petstore/haskell-servant/lib/Model/Pet.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +module Model.Pet + ( Pet (..) + ) where + +import Data.Aeson +import GHC.Generics +import Test.QuickCheck +import Model.Category +import Model.Tag + + +data Pet = Pet + { id_ :: Integer + , category :: Category + , name :: String + , photoUrls :: [String] + , tags :: [Tag] + , status :: String + } deriving (Show, Eq, Generic) + +instance FromJSON Pet +instance ToJSON Pet +instance Arbitrary Pet where + arbitrary = Pet <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary diff --git a/samples/server/petstore/haskell-servant/lib/Model/Tag.hs b/samples/server/petstore/haskell-servant/lib/Model/Tag.hs new file mode 100644 index 00000000000..7bbf8feb9b4 --- /dev/null +++ b/samples/server/petstore/haskell-servant/lib/Model/Tag.hs @@ -0,0 +1,24 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +module Model.Tag + ( Tag (..) + ) where + +import Data.Aeson +import GHC.Generics +import Test.QuickCheck + + +data Tag = Tag + { id_ :: Integer + , name :: String + } deriving (Show, Eq, Generic) + +instance FromJSON Tag +instance ToJSON Tag +instance Arbitrary Tag where + arbitrary = Tag <$> arbitrary <*> arbitrary diff --git a/samples/server/petstore/haskell-servant/lib/Model/User.hs b/samples/server/petstore/haskell-servant/lib/Model/User.hs new file mode 100644 index 00000000000..8ccf875dc7d --- /dev/null +++ b/samples/server/petstore/haskell-servant/lib/Model/User.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +module Model.User + ( User (..) + ) where + +import Data.Aeson +import GHC.Generics +import Test.QuickCheck + + +data User = User + { id_ :: Integer + , username :: String + , firstName :: String + , lastName :: String + , email :: String + , password :: String + , phone :: String + , userStatus :: Integer + } deriving (Show, Eq, Generic) + +instance FromJSON User +instance ToJSON User +instance Arbitrary User where + arbitrary = User <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary diff --git a/samples/server/petstore/haskell-servant/lib/Utils.hs b/samples/server/petstore/haskell-servant/lib/Utils.hs new file mode 100644 index 00000000000..f6db2602ce8 --- /dev/null +++ b/samples/server/petstore/haskell-servant/lib/Utils.hs @@ -0,0 +1,27 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE FlexibleInstances #-} +module Utils where + +import GHC.Generics +import Servant.API +import Data.List (intercalate) +import Data.List.Split (splitOn) +import qualified Data.Map as Map +import qualified Data.Text as T +import Test.QuickCheck + +instance FromText [String] where + fromText = Just . splitOn "," . T.unpack + +instance ToText [String] where + toText = T.pack . intercalate "," + +lkp inputs l = case lookup l inputs of + Nothing -> Left $ "label " ++ T.unpack l ++ " not found" + Just v -> Right $ read (T.unpack v) + +instance (Ord k, Arbitrary k, Arbitrary v) => Arbitrary (Map.Map k v) where + arbitrary = Map.fromList <$> arbitrary diff --git a/samples/server/petstore/haskell-servant/server/Main.hs b/samples/server/petstore/haskell-servant/server/Main.hs new file mode 100644 index 00000000000..68b4ff6ce33 --- /dev/null +++ b/samples/server/petstore/haskell-servant/server/Main.hs @@ -0,0 +1,13 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TypeOperators #-} + +module Main where + +import Apis +import Servant +import Servant.Mock +import qualified Network.Wai.Handler.Warp as Warp + +main :: IO () +main = Warp.run 8080 $ serve api (mock api) diff --git a/samples/server/petstore/haskell-servant/stack.yaml b/samples/server/petstore/haskell-servant/stack.yaml new file mode 100644 index 00000000000..00448df3e95 --- /dev/null +++ b/samples/server/petstore/haskell-servant/stack.yaml @@ -0,0 +1,33 @@ +# For more information, see: https://github.com/commercialhaskell/stack/blob/release/doc/yaml_configuration.md + +# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2) +resolver: lts-3.17 + +# Local packages, usually specified by relative directory name +packages: +- '.' + +# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) +extra-deps: +- servant-mock-0.4.4.6 + +# Override default flag values for local packages and extra-deps +flags: {} + +# Extra package databases containing global packages +extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +# system-ghc: true + +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: >= 0.1.10.0 + +# Override the architecture used by stack, especially useful on Windows +# arch: i386 +# arch: x86_64 + +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir]