diff --git a/bin/fsharp-giraffe-server-petstore.sh b/bin/fsharp-giraffe-server-petstore.sh
new file mode 100755
index 0000000000000000000000000000000000000000..117b6b7d782e2881f236cc5a5f0bef8f181585a2
--- /dev/null
+++ b/bin/fsharp-giraffe-server-petstore.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+SCRIPT="$0"
+echo "# START SCRIPT: $SCRIPT"
+
+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/openapi-generator-cli/target/openapi-generator-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} -Xmx1024M -DloggerPath=conf/log4j.properties"
+ags="generate -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -t modules/openapi-generator/src/main/resources/fsharp-giraffe-server -g fsharp-giraffe -o samples/server/petstore/fsharp-giraffe $@" 
+
+java ${JAVA_OPTS} -jar ${executable} ${ags}
diff --git a/bin/windows/fsharp-giraffe-server-petstore.bat b/bin/windows/fsharp-giraffe-server-petstore.bat
new file mode 100644
index 0000000000000000000000000000000000000000..f52daf05e655fc4b0906b1abf738e14d7c28d87e
--- /dev/null
+++ b/bin/windows/fsharp-giraffe-server-petstore.bat
@@ -0,0 +1,10 @@
+set executable=.\modules\openapi-generator-cli\target\openapi-generator-cli.jar
+
+If Not Exist %executable% (
+  mvn clean package
+)
+
+REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties
+set ags=generate  --artifact-id "fsharp-giraffe-petstore-server" -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g fsharp-giraffe -o samples\server\petstore\fsharp-giraffe
+
+java %JAVA_OPTS% -jar %executable% %ags%
diff --git a/docs/generators.md b/docs/generators.md
index aae6c610bb0583c79025ad2e5c20b674f5b7bd35..7d7746b508cf26bdca82413f59ad259c2532a468 100644
--- a/docs/generators.md
+++ b/docs/generators.md
@@ -71,6 +71,7 @@ The following generators are available:
     - [cpp-restbed-server](generators/cpp-restbed-server.md)
     - [csharp-nancyfx](generators/csharp-nancyfx.md)
     - [erlang-server](generators/erlang-server.md)
+    - [fsharp-giraffe](generators/fsharp-giraffe.md)
     - [go-gin-server](generators/go-gin-server.md)
     - [go-server](generators/go-server.md)
     - [graphql-nodejs-express-server](generators/graphql-nodejs-express-server.md)
diff --git a/docs/generators/fsharp-giraffe.md b/docs/generators/fsharp-giraffe.md
new file mode 100644
index 0000000000000000000000000000000000000000..473781b7c520ae9413f93d1119719067a6e96368
--- /dev/null
+++ b/docs/generators/fsharp-giraffe.md
@@ -0,0 +1,25 @@
+
+---
+id: generator-opts-server-fsharp-giraffe
+title: Config Options for fsharp-giraffe
+sidebar_label: fsharp-giraffe
+---
+
+| Option | Description | Values | Default |
+| ------ | ----------- | ------ | ------- |
+|licenseUrl|The URL of the license| |http://localhost|
+|licenseName|The name of the license| |NoLicense|
+|packageCopyright|Specifies an AssemblyCopyright for the .NET Framework global assembly attributes stored in the AssemblyInfo file.| |No Copyright|
+|packageAuthors|Specifies Authors property in the .NET Core project file.| |OpenAPI|
+|packageTitle|Specifies an AssemblyTitle for the .NET Framework global assembly attributes stored in the AssemblyInfo file.| |OpenAPI Library|
+|packageName|F# module name (convention: Title.Case).| |OpenAPI|
+|packageVersion|F# package version.| |1.0.0|
+|packageGuid|The GUID that will be associated with the C# project| |null|
+|sourceFolder|source folder for generated code| |OpenAPI/src|
+|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
+|useDateTimeOffset|Use DateTimeOffset to model date-time properties| |false|
+|useCollection|Deserialize array types to Collection<T> instead of List<T>.| |false|
+|returnICollection|Return ICollection<T> instead of the concrete type.| |false|
+|useSwashbuckle|Uses the Swashbuckle.AspNetCore NuGet package for documentation.| |false|
+|generateBody|Generates method body.| |true|
+|buildTarget|Target the build for a program or library.| |program|
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractFSharpCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractFSharpCodegen.java
new file mode 100644
index 0000000000000000000000000000000000000000..19be68a999d3bcb19a127e0555bdc9562d14fde7
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractFSharpCodegen.java
@@ -0,0 +1,1154 @@
+/*
+ * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
+ *
+ * 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.
+ */
+
+package org.openapitools.codegen.languages;
+
+import com.google.common.collect.ImmutableMap;
+import com.samskivert.mustache.Mustache;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.oas.models.media.ArraySchema;
+import io.swagger.v3.oas.models.media.Schema;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.openapitools.codegen.*;
+import org.openapitools.codegen.templating.mustache.*;
+import org.openapitools.codegen.utils.ModelUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.*;
+
+import static org.openapitools.codegen.utils.StringUtils.camelize;
+
+public abstract class AbstractFSharpCodegen extends DefaultCodegen implements CodegenConfig {
+
+    protected boolean optionalAssemblyInfoFlag = true;
+    protected boolean optionalProjectFileFlag = true;
+
+    protected boolean useDateTimeOffsetFlag = false;
+    protected boolean useCollection = false;
+    protected boolean returnICollection = false;
+    protected boolean netCoreProjectFileFlag = false;
+
+    protected String modelPropertyNaming = CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.PascalCase.name();
+
+    protected String licenseUrl = "http://localhost";
+    protected String licenseName = "NoLicense";
+
+    protected String packageVersion = "1.0.0";
+    protected String packageName = "OpenAPI";
+    protected String packageTitle = "OpenAPI Library";
+    protected String packageProductName = "OpenAPILibrary";
+    protected String packageDescription = "A library generated from a OpenAPI doc";
+    protected String packageCompany = "OpenAPI";
+    protected String packageCopyright = "No Copyright";
+    protected String packageAuthors = "OpenAPI";
+
+    protected String interfacePrefix = "I";
+
+    protected String projectFolder = packageName;
+    protected String sourceFolder = projectFolder + File.separator + "src";
+    protected String testFolder = projectFolder + ".Tests";
+
+    protected Set<String> collectionTypes;
+    protected Set<String> mapTypes;
+
+    // true if nullable types will be supported (as option) 
+    protected boolean supportNullable = Boolean.TRUE;
+
+    protected Set<String> nullableType = new HashSet<String>();
+
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractFSharpCodegen.class);
+
+    public AbstractFSharpCodegen() {
+        super();
+
+        supportsInheritance = true;
+
+        importMapping.clear();
+        importMapping.put("IDictionary", "System.Collections.Generic");
+
+        outputFolder = this.getName();
+        embeddedTemplateDir = templateDir = this.getName();
+        
+        collectionTypes = new HashSet<String>(Arrays.asList("list", "seq"));
+
+        mapTypes = new HashSet<String>(
+            Arrays.asList("IDictionary")
+        );
+
+        reservedWords.addAll(
+                Arrays.asList(
+                        // local variable names in API methods (endpoints)
+                        "localVarPath", "localVarPathParams", "localVarQueryParams", "localVarHeaderParams",
+                        "localVarFormParams", "localVarFileParams", "localVarStatusCode", "localVarResponse",
+                        "localVarPostBody", "localVarHttpHeaderAccepts", "localVarHttpHeaderAccept",
+                        "localVarHttpContentTypes", "localVarHttpContentType",
+                        "localVarStatusCode",
+                        // F# reserved words
+                        "abstract", "and",  "as", "async", "await", "assert", "base","begin", "bool", "break", "byte", "case", "catch", "char", "checked",
+                        "class", "const", "continue", "decimal", "default", "delegate", "do", "done", "double", "downcast", "downto", "dynamic",
+                        "elif", "else", "end", "enum", "event", "exception", "explicit", "extern", "false", "finally", "fixed", "float", "for",
+                        "foreach", "fun", "function", "if", "in", "inherit", "inline", "int", "interface", "internal", "is", "lazy", "let", "let!", "lock",
+                        "match", "match!", "member", "module", "mutable", "namespace", "new", "not", "null", "of", "open", "option", "or", "override", "params",
+                        "private", "public", "raise", "rec", "return", "return!", "sealed", "select", "static", "string", "struct", "then", "to", 
+                        "true", "try", "type", "upcast", "use", "use!", "val", "void", "volatile", "when", "while", "with", "yield", "yield!")
+        );
+
+        // TODO - these are based on C# generator, do we need to add any more?
+        languageSpecificPrimitives = new HashSet<String>(
+                Arrays.asList(
+                        "String",
+                        "string",
+                        "bool",
+                        "char",
+                        "decimal",
+                        "int",
+                        "int16",
+                        "int64",
+                        "nativeint",
+                        "unativeint",
+                        "uint16",
+                        "uint32",
+                        "uint64",
+                        "float",
+                        "byte[]",
+                        "ICollection",
+                        "Collection",
+                        "list",
+                        "dict",
+                        "seq",
+                        "Dictionary",
+                        "List",
+                        "DateTime",
+                        "DataTimeOffset",
+                        "Double",
+                        "Int32",
+                        "Int64",
+                        "float",
+                        "float32",
+                        "single",
+                        "double",
+                        "System.IO.Stream", // not really a primitive, we include it to avoid model import
+                        "obj")
+        );
+
+        instantiationTypes.put("array", "list");
+        instantiationTypes.put("list", "list");
+        instantiationTypes.put("map", "IDictionary");
+
+
+        typeMapping = new HashMap<String, String>();
+        typeMapping.put("string", "string");
+        typeMapping.put("binary", "byte[]");
+        typeMapping.put("ByteArray", "byte[]");
+        typeMapping.put("boolean", "bool");
+        typeMapping.put("integer", "int");
+        typeMapping.put("float", "float");
+        typeMapping.put("long", "int64");
+        typeMapping.put("double", "double");
+        typeMapping.put("number", "decimal");
+        typeMapping.put("DateTime", "DateTime");
+        typeMapping.put("date", "DateTime");
+        typeMapping.put("file", "System.IO.Stream");
+        typeMapping.put("array", "list");
+        typeMapping.put("list", "list");
+        typeMapping.put("map", "IDictionary");
+        typeMapping.put("object", "obj");
+        typeMapping.put("UUID", "Guid");
+
+        // nullable type
+        nullableType = new HashSet<String>(
+                Arrays.asList("decimal", "bool", "int", "float", "long", "double", "string", "Guid","apiKey")
+        );
+    }
+
+    public void setReturnICollection(boolean returnICollection) {
+        this.returnICollection = returnICollection;
+    }
+
+    public void setUseCollection(boolean useCollection) {
+        this.useCollection = useCollection;
+        if (useCollection) {
+            typeMapping.put("array", "seq");
+            typeMapping.put("list", "seq");
+
+            instantiationTypes.put("array", "seq");
+            instantiationTypes.put("list", "seq");
+        }
+    }
+
+    public void setNetCoreProjectFileFlag(boolean flag) {
+        this.netCoreProjectFileFlag = flag;
+    }
+
+    public void useDateTimeOffset(boolean flag) {
+        this.useDateTimeOffsetFlag = flag;
+        if (flag) {
+            typeMapping.put("DateTime", "DateTimeOffset?");
+        } else {
+            typeMapping.put("DateTime", "DateTime?");
+        }
+    }
+
+    @Override
+    public void processOpts() {
+        super.processOpts();
+
+        // License info
+        if (additionalProperties.containsKey(CodegenConstants.LICENSE_URL)) {
+            setLicenseUrl((String) additionalProperties.get(CodegenConstants.LICENSE_URL));
+        } else {
+            additionalProperties.put(CodegenConstants.LICENSE_URL, this.licenseUrl);
+        }
+
+        if (additionalProperties.containsKey(CodegenConstants.LICENSE_NAME)) {
+            setLicenseName((String) additionalProperties.get(CodegenConstants.LICENSE_NAME));
+        } else {
+            additionalProperties.put(CodegenConstants.LICENSE_NAME, this.licenseName);
+        }
+
+        // {{packageVersion}}
+        if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) {
+            setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION));
+        } else {
+            additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion);
+        }
+
+        // {{sourceFolder}}
+        if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) {
+            setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER));
+        } else {
+            additionalProperties.put(CodegenConstants.SOURCE_FOLDER, this.sourceFolder);
+        }
+
+        // {{packageName}}
+        if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
+            setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
+        } else {
+            additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
+        }
+
+        if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) {
+            LOGGER.warn(String.format(Locale.ROOT, "%s is not used by F# generators. Please use %s",
+                    CodegenConstants.INVOKER_PACKAGE, CodegenConstants.PACKAGE_NAME));
+        }
+
+        // {{packageTitle}}
+        if (additionalProperties.containsKey(CodegenConstants.PACKAGE_TITLE)) {
+            setPackageTitle((String) additionalProperties.get(CodegenConstants.PACKAGE_TITLE));
+        } else {
+            additionalProperties.put(CodegenConstants.PACKAGE_TITLE, packageTitle);
+        }
+
+        // {{packageProductName}}
+        if (additionalProperties.containsKey(CodegenConstants.PACKAGE_PRODUCTNAME)) {
+            setPackageProductName((String) additionalProperties.get(CodegenConstants.PACKAGE_PRODUCTNAME));
+        } else {
+            additionalProperties.put(CodegenConstants.PACKAGE_PRODUCTNAME, packageProductName);
+        }
+
+        // {{packageDescription}}
+        if (additionalProperties.containsKey(CodegenConstants.PACKAGE_DESCRIPTION)) {
+            setPackageDescription((String) additionalProperties.get(CodegenConstants.PACKAGE_DESCRIPTION));
+        } else {
+            additionalProperties.put(CodegenConstants.PACKAGE_DESCRIPTION, packageDescription);
+        }
+
+        // {{packageCompany}}
+        if (additionalProperties.containsKey(CodegenConstants.PACKAGE_COMPANY)) {
+            setPackageCompany((String) additionalProperties.get(CodegenConstants.PACKAGE_COMPANY));
+        } else {
+            additionalProperties.put(CodegenConstants.PACKAGE_COMPANY, packageCompany);
+        }
+
+        // {{packageCopyright}}
+        if (additionalProperties.containsKey(CodegenConstants.PACKAGE_COPYRIGHT)) {
+            setPackageCopyright((String) additionalProperties.get(CodegenConstants.PACKAGE_COPYRIGHT));
+        } else {
+            additionalProperties.put(CodegenConstants.PACKAGE_COPYRIGHT, packageCopyright);
+        }
+
+        // {{packageAuthors}}
+        if (additionalProperties.containsKey(CodegenConstants.PACKAGE_AUTHORS)) {
+            setPackageAuthors((String) additionalProperties.get(CodegenConstants.PACKAGE_AUTHORS));
+        } else {
+            additionalProperties.put(CodegenConstants.PACKAGE_AUTHORS, packageAuthors);
+        }
+
+        // {{useDateTimeOffset}}
+        if (additionalProperties.containsKey(CodegenConstants.USE_DATETIME_OFFSET)) {
+            useDateTimeOffset(convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_DATETIME_OFFSET));
+        } else {
+            additionalProperties.put(CodegenConstants.USE_DATETIME_OFFSET, useDateTimeOffsetFlag);
+        }
+
+        if (additionalProperties.containsKey(CodegenConstants.USE_COLLECTION)) {
+            setUseCollection(convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_COLLECTION));
+        } else {
+            additionalProperties.put(CodegenConstants.USE_COLLECTION, useCollection);
+        }
+
+        if (additionalProperties.containsKey(CodegenConstants.RETURN_ICOLLECTION)) {
+            setReturnICollection(convertPropertyToBooleanAndWriteBack(CodegenConstants.RETURN_ICOLLECTION));
+        } else {
+            additionalProperties.put(CodegenConstants.RETURN_ICOLLECTION, returnICollection);
+        }
+
+        if (additionalProperties.containsKey(CodegenConstants.NETCORE_PROJECT_FILE)) {
+            setNetCoreProjectFileFlag(convertPropertyToBooleanAndWriteBack(CodegenConstants.NETCORE_PROJECT_FILE));
+        } else {
+            additionalProperties.put(CodegenConstants.NETCORE_PROJECT_FILE, netCoreProjectFileFlag);
+        }
+
+        if (additionalProperties.containsKey(CodegenConstants.INTERFACE_PREFIX)) {
+            String useInterfacePrefix = additionalProperties.get(CodegenConstants.INTERFACE_PREFIX).toString();
+            if ("false".equals(useInterfacePrefix.toLowerCase(Locale.ROOT))) {
+                setInterfacePrefix("");
+            } else if (!"true".equals(useInterfacePrefix.toLowerCase(Locale.ROOT))) {
+                // NOTE: if user passes "true" explicitly, we use the default I- prefix. The other supported case here is a custom prefix.
+                setInterfacePrefix(sanitizeName(useInterfacePrefix));
+            }
+        }
+
+        // This either updates additionalProperties with the above fixes, or sets the default if the option was not specified.
+        additionalProperties.put(CodegenConstants.INTERFACE_PREFIX, interfacePrefix);
+
+        addMustacheLambdas(additionalProperties);
+    }
+
+    private void addMustacheLambdas(Map<String, Object> objs) {
+
+        Map<String, Mustache.Lambda> lambdas = new ImmutableMap.Builder<String, Mustache.Lambda>()
+                .put("lowercase", new LowercaseLambda().generator(this))
+                .put("uppercase", new UppercaseLambda())
+                .put("titlecase", new TitlecaseLambda())
+                .put("camelcase", new CamelCaseLambda().generator(this))
+                .put("camelcase_param", new CamelCaseLambda().generator(this).escapeAsParamName(true))
+                .put("indented", new IndentedLambda())
+                .put("indented_8", new IndentedLambda(8, " "))
+                .put("indented_12", new IndentedLambda(12, " "))
+                .put("indented_16", new IndentedLambda(16, " "))
+                .build();
+
+        if (objs.containsKey("lambda")) {
+            LOGGER.warn("A property named 'lambda' already exists. Mustache lambdas renamed from 'lambda' to '_lambda'. " +
+                    "You'll likely need to use a custom template, " +
+                    "see https://github.com/swagger-api/swagger-codegen#modifying-the-client-library-format. ");
+            objs.put("_lambda", lambdas);
+        } else {
+            objs.put("lambda", lambdas);
+        }
+    }
+
+    @Override
+    public Map<String, Object> postProcessModels(Map<String, Object> objs) {
+        super.postProcessModels(objs);
+        List<Object> models = (List<Object>) objs.get("models");
+        for (Object _mo : models) {
+            Map<String, Object> mo = (Map<String, Object>) _mo;
+            CodegenModel cm = (CodegenModel) mo.get("model");
+            for (CodegenProperty var : cm.vars) {
+                // check to see if model name is same as the property name
+                // which will result in compilation error
+                // if found, prepend with _ to workaround the limitation
+                if (var.name.equalsIgnoreCase(cm.name)) {
+                    var.name = "_" + var.name;
+                }
+            }
+        }
+        // process enum in models
+        return postProcessModelsEnum(objs);
+    }
+
+    /**
+     * Invoked by {@link DefaultGenerator} after all models have been post-processed, allowing for a last pass of codegen-specific model cleanup.
+     *
+     * @param objs Current state of codegen object model.
+     * @return (ew) modified state of the codegen object model.
+     */
+    @Override
+    public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
+        final Map<String, Object> processed = super.postProcessAllModels(objs);
+        postProcessEnumRefs(processed);
+        return postProcessDependencyOrders(processed);
+    }
+
+    /*
+    * F# does not allow forward declarations, so files must be imported in the correct order.
+    * Output of CodeGen models must therefore bein dependency order (rather than alphabetical order, which seems to be the default).    
+    * We achieve this by creating a comparator to check whether the first model contains any properties of the comparison model's type 
+    * This could probably be made more efficient if absolutely needed.
+    */
+    @SuppressWarnings({"unchecked"})
+    public Map<String,Object> postProcessDependencyOrders(final Map<String, Object> objs) {
+      Comparator<String> comparator = new Comparator<String>() {
+          @Override public int compare(String key1, String key2) {
+            // Get the corresponding models
+            CodegenModel model1 = ModelUtils.getModelByName(key1, objs);
+            CodegenModel model2 = ModelUtils.getModelByName(key2, objs);
+
+            List<String> complexVars1 = new ArrayList<String>();
+            List<String> complexVars2 = new ArrayList<String>();
+            
+            for(CodegenProperty prop : model1.vars) {
+                if(prop.complexType != null)
+                  complexVars1.add(prop.complexType);
+            }  
+            for(CodegenProperty prop : model2.vars) {
+                if(prop.complexType != null)
+                  complexVars2.add(prop.complexType);
+            }                
+            
+            // if first has complex vars and second has none, first is greater
+            if(complexVars1.size() > 0 && complexVars2.size() == 0)
+              return 1;
+
+            // if second has complex vars and first has none, first is lesser
+            if(complexVars1.size() == 0 && complexVars2.size() > 0)
+              return -1;
+
+            // if first has complex var that matches the second's key, first is greater
+            if(complexVars1.contains(key2))
+              return 1;
+
+            // if second has complex var that matches the first's key, first is lesser
+            if(complexVars2.contains(key1))
+              return -1;
+
+            // if none of the above, don't care
+            return 0;
+
+          }
+        };
+        PriorityQueue<String> queue = new PriorityQueue<String>(objs.size(), comparator);
+        for(Object k : objs.keySet()) {
+          queue.add(k.toString());
+        }
+
+        Map<String,Object> sorted = new LinkedHashMap<String,Object>();
+        
+        while(queue.size() > 0) {
+          String key = queue.poll();
+          sorted.put(key, objs.get(key));
+        }
+        return sorted;
+      }
+
+    /**
+     * F# differs from other languages in that Enums are not _true_ objects; enums are compiled to integral types.
+     * So, in F#, an enum is considers more like a user-defined primitive.
+     * <p>
+     * When working with enums, we can't always assume a RefModel is a nullable type (where default(YourType) == null),
+     * so this post processing runs through all models to find RefModel'd enums. Then, it runs through all vars and modifies
+     * those vars referencing RefModel'd enums to work the same as inlined enums rather than as objects.
+     *
+     * @param models processed models to be further processed for enum references
+     */
+    @SuppressWarnings({"unchecked"})
+    private void postProcessEnumRefs(final Map<String, Object> models) {
+        Map<String, CodegenModel> enumRefs = new HashMap<String, CodegenModel>();
+        for (Map.Entry<String, Object> entry : models.entrySet()) {
+            CodegenModel model = ModelUtils.getModelByName(entry.getKey(), models);
+            if (model.isEnum) {
+                enumRefs.put(entry.getKey(), model);
+            }
+        }
+
+        for (Map.Entry<String, Object> entry : models.entrySet()) {
+            String openAPIName = entry.getKey();
+            CodegenModel model = ModelUtils.getModelByName(openAPIName, models);
+            if (model != null) {
+                for (CodegenProperty var : model.allVars) {
+                    if (enumRefs.containsKey(var.dataType)) {
+                        // Handle any enum properties referred to by $ref.
+                        // This is different in F# than most other generators, because enums in C# are compiled to integral types,
+                        // while enums in many other languages are true objects.
+                        CodegenModel refModel = enumRefs.get(var.dataType);
+                        var.allowableValues = refModel.allowableValues;
+                        var.isEnum = true;
+
+                        // We do these after updateCodegenPropertyEnum to avoid generalities that don't mesh with C#.
+                        var.isPrimitiveType = true;
+                    }
+                }
+
+                // We're looping all models here.
+                if (model.isEnum) {
+                    // We now need to make allowableValues.enumVars look like the context of CodegenProperty
+                    Boolean isString = false;
+                    Boolean isInteger = false;
+                    Boolean isLong = false;
+                    Boolean isByte = false;
+
+                    if (model.dataType.startsWith("byte")) {
+                        // F# Actually supports byte and short enums, swagger spec only supports byte.
+                        isByte = true;
+                        model.vendorExtensions.put("x-enum-byte", true);
+                    } else if (model.dataType.startsWith("int32")) {
+                        isInteger = true;
+                        model.vendorExtensions.put("x-enum-integer", true);
+                    } else if (model.dataType.startsWith("int64")) {
+                        isLong = true;
+                        model.vendorExtensions.put("x-enum-long", true);
+                    } else {
+                        // F# doesn't support non-integral enums, so we need to treat everything else as strings (e.g. to not lose precision or data integrity)
+                        isString = true;
+                        model.vendorExtensions.put("x-enum-string", true);
+                    }
+
+                    // Since we iterate enumVars for modelnnerEnum and enumClass templates, and CodegenModel is missing some of CodegenProperty's properties,
+                    // we can take advantage of Mustache's contextual lookup to add the same "properties" to the model's enumVars scope rather than CodegenProperty's scope.
+                    List<Map<String, String>> enumVars = (ArrayList<Map<String, String>>) model.allowableValues.get("enumVars");
+                    List<Map<String, Object>> newEnumVars = new ArrayList<Map<String, Object>>();
+                    for (Map<String, String> enumVar : enumVars) {
+                        Map<String, Object> mixedVars = new HashMap<String, Object>();
+                        mixedVars.putAll(enumVar);
+
+                        mixedVars.put("isString", isString);
+                        mixedVars.put("isLong", isLong);
+                        mixedVars.put("isInteger", isInteger);
+                        mixedVars.put("isByte", isByte);
+
+                        newEnumVars.add(mixedVars);
+                    }
+
+                    if (!newEnumVars.isEmpty()) {
+                        model.allowableValues.put("enumVars", newEnumVars);
+                    }
+                }
+            } else {
+                LOGGER.warn("Expected to retrieve model %s by name, but no model was found. Check your -Dmodels inclusions.", openAPIName);
+            }
+        }
+    }
+
+    /**
+     * Update codegen property's enum by adding "enumVars" (with name and value)
+     *
+     * @param var list of CodegenProperty
+     */
+    @Override
+    public void updateCodegenPropertyEnum(CodegenProperty var) {
+        if (var.vendorExtensions == null) {
+            var.vendorExtensions = new HashMap<>();
+        }
+
+        super.updateCodegenPropertyEnum(var);
+
+        // Because C# uses nullable primitives for datatype, and datatype is used in DefaultCodegen for determining enum-ness, guard against weirdness here.
+        if (var.isEnum) {
+            if ("byte".equals(var.dataFormat)) {// C# Actually supports byte and short enums.
+                var.vendorExtensions.put("x-enum-byte", true);
+                var.isString = false;
+                var.isLong = false;
+                var.isInteger = false;
+            } else if ("int32".equals(var.dataFormat)) {
+                var.isInteger = true;
+                var.isString = false;
+                var.isLong = false;
+            } else if ("int64".equals(var.dataFormat)) {
+                var.isLong = true;
+                var.isString = false;
+                var.isInteger = false;
+            } else {// C# doesn't support non-integral enums, so we need to treat everything else as strings (e.g. to not lose precision or data integrity)
+                var.isString = true;
+                var.isInteger = false;
+                var.isLong = false;
+            }
+        }
+    }
+
+    @Override
+    public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
+        super.postProcessOperationsWithModels(objs, allModels);
+        if (objs != null) {
+            Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
+            if (operations != null) {
+                List<CodegenOperation> ops = (List<CodegenOperation>) operations.get("operation");
+                for (CodegenOperation operation : ops) {
+
+                    // Check return types for collection
+                    if (operation.returnType != null) {
+                        String typeMapping;
+                        int namespaceEnd = operation.returnType.lastIndexOf(".");
+                        if (namespaceEnd > 0) {
+                            typeMapping = operation.returnType.substring(namespaceEnd);
+                        } else {
+                            typeMapping = operation.returnType;
+                        }
+
+                        if (this.collectionTypes.contains(typeMapping)) {
+                            operation.isListContainer = true;
+                            operation.returnContainer = operation.returnType;
+                            if (this.returnICollection && (
+                                    typeMapping.startsWith("List") ||
+                                            typeMapping.startsWith("Collection"))) {
+                                // NOTE: ICollection works for both List<T> and Collection<T>
+                                int genericStart = typeMapping.indexOf("<");
+                                if (genericStart > 0) {
+                                    operation.returnType = "ICollection" + typeMapping.substring(genericStart);
+                                }
+                            }
+                        } else {
+                            operation.returnContainer = operation.returnType;
+                            operation.isMapContainer = this.mapTypes.contains(typeMapping);
+                        }
+                    }
+
+                    if (operation.examples != null) {
+                        for (Map<String, String> example : operation.examples) {
+                            for (Map.Entry<String, String> entry : example.entrySet()) {
+                                // Replace " with \", \r, \n with \\r, \\n
+                                String val = entry.getValue().replace("\"", "\\\"")
+                                        .replace("\r", "\\r")
+                                        .replace("\n", "\\n");
+                                entry.setValue(val);
+                            }
+                        }
+                    }
+
+                    if (!isSupportNullable()) {
+                        for (CodegenParameter parameter : operation.allParams) {
+                            CodegenModel model = null;
+                            for (Object modelHashMap : allModels) {
+                                CodegenModel codegenModel = ((HashMap<String, CodegenModel>) modelHashMap).get("model");
+                                if (codegenModel.getClassname().equals(parameter.dataType)) {
+                                    model = codegenModel;
+                                    break;
+                                }
+                            }
+
+                            if (model == null) {
+                                // Primitive data types all come already marked
+                                parameter.isNullable = true;
+                            } else {
+                                // Effectively mark enum models as enums and non-nullable
+                                if (model.isEnum) {
+                                    parameter.isEnum = true;
+                                    parameter.allowableValues = model.allowableValues;
+                                    parameter.isPrimitiveType = true;
+                                    parameter.isNullable = false;
+                                } else {
+                                    parameter.isNullable = true;
+                                }
+                            }
+                        }
+                    }
+
+                    processOperation(operation);
+                }
+            }
+        }
+
+        return objs;
+    }
+
+    protected void processOperation(CodegenOperation operation) {
+        // default noop
+    }
+
+    @Override
+    public String apiFileFolder() {
+        return outputFolder + File.separator + sourceFolder + File.separator + apiPackage();
+    }
+
+    @Override
+    public String modelFileFolder() {
+        return outputFolder + File.separator + sourceFolder + File.separator + modelPackage();
+    }
+
+    @Override
+    public String toModelFilename(String name) {
+        // should be the same as the model name
+        return toModelName(name);
+    }
+
+    @Override
+    public String toOperationId(String operationId) {
+        // throw exception if method name is empty (should not occur as an auto-generated method name will be used)
+        if (StringUtils.isEmpty(operationId)) {
+            throw new RuntimeException("Empty method name (operationId) not allowed");
+        }
+
+        // method name cannot use reserved keyword, e.g. return
+        if (isReservedWord(operationId)) {
+            LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + camelize(sanitizeName("call_" + operationId)));
+            operationId = "call_" + operationId;
+        }
+
+        // operationId starts with a number
+        if (operationId.matches("^\\d.*")) {
+            LOGGER.warn(operationId + " (starting with a number) cannot be used as method name. Renamed to " + camelize(sanitizeName("call_" + operationId)));
+            operationId = "call_" + operationId;
+        }
+
+        return camelize(sanitizeName(operationId));
+    }
+
+    @Override
+    public String toVarName(String name) {
+        // sanitize name
+        name = sanitizeName(name);
+
+        // if it's all uppper case, do nothing
+        if (name.matches("^[A-Z_]*$")) {
+            return name;
+        }
+
+        // camelize the variable name
+        // pet_id => PetId
+        name = camelize(name);
+        // for reserved word or word starting with number, append _
+        if (isReservedWord(name) || name.matches("^\\d.*")) {
+            name = escapeReservedWord(name);
+        }
+        return name;
+    }
+
+    @Override
+    public String toParamName(String name) {
+        // sanitize name
+        name = sanitizeName(name);
+
+        // replace - with _ e.g. created-at => created_at
+        name = name.replaceAll("-", "_");
+
+        // if it's all uppper case, do nothing
+        if (name.matches("^[A-Z_]*$")) {
+            return name;
+        }
+
+        // camelize(lower) the variable name
+        // pet_id => petId
+        name = camelize(name, true);
+
+        // for reserved word or word starting with number, append _
+        if (isReservedWord(name) || name.matches("^\\d.*")) {
+            name = escapeReservedWord(name);
+        }
+
+        return name;
+    }
+
+    @Override
+    public String escapeReservedWord(String name) {
+        if (this.reservedWordsMappings().containsKey(name)) {
+            return this.reservedWordsMappings().get(name);
+        }
+        return "_" + name;
+    }
+
+    /**
+     * Return the example value of the property
+     *
+     * @param p OpenAPI property object
+     * @return string presentation of the example value of the property
+     */
+    @Override
+    public String toExampleValue(Schema p) {
+        if (ModelUtils.isStringSchema(p)) {
+            if (p.getExample() != null) {
+                return "\"" + p.getExample().toString() + "\"";
+            }
+        } else if (ModelUtils.isBooleanSchema(p)) {
+            if (p.getExample() != null) {
+                return p.getExample().toString();
+            }
+        } else if (ModelUtils.isDateSchema(p)) {
+            // TODO
+        } else if (ModelUtils.isDateTimeSchema(p)) {
+            // TODO
+        } else if (ModelUtils.isNumberSchema(p)) {
+            if (p.getExample() != null) {
+                return p.getExample().toString();
+            }
+        } else if (ModelUtils.isIntegerSchema(p)) {
+            if (p.getExample() != null) {
+                return p.getExample().toString();
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the default value of the property
+     * @param p OpenAPI property object
+     * @return string presentation of the default value of the property
+     */
+    @Override
+    public String toDefaultValue(Schema p) {
+        if (ModelUtils.isBooleanSchema(p)) {
+            if (p.getDefault() != null) {
+                return p.getDefault().toString();
+            }
+        } else if (ModelUtils.isDateSchema(p)) {
+            if (p.getDefault() != null) {
+                return "\"" + p.getDefault().toString() + "\"";
+            }
+        } else if (ModelUtils.isDateTimeSchema(p)) {
+            if (p.getDefault() != null) {
+                return "\"" + p.getDefault().toString() + "\"";
+            }
+        } else if (ModelUtils.isNumberSchema(p)) {
+            if (p.getDefault() != null) {
+                if (ModelUtils.isFloatSchema(p)) { // float
+                    return p.getDefault().toString() + "F";
+                } else if (ModelUtils.isDoubleSchema(p)) { // double
+                    return p.getDefault().toString() + "D";
+                } else {    // decimal
+                    return p.getDefault().toString() + "M";
+                }
+            }
+        } else if (ModelUtils.isIntegerSchema(p)) {
+            if (p.getDefault() != null) {
+                return p.getDefault().toString();
+            }
+        } else if (ModelUtils.isStringSchema(p)) {
+            if (p.getDefault() != null) {
+                String _default = (String) p.getDefault();
+                if (p.getEnum() == null) {
+                    return "\"" + _default + "\"";
+                } else {
+                    // convert to enum var name later in postProcessModels
+                    return _default;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    protected boolean isReservedWord(String word) {
+        return reservedWords.contains(word);
+    }
+
+    
+    public String getNullableType(Schema p, String type) {
+      if (languageSpecificPrimitives.contains(type)) {
+        if (isSupportNullable() && ModelUtils.isNullable(p) && nullableType.contains(type)) {
+          return type + " option";
+        } else {
+          return type;
+        }
+      } else {
+          return null;
+      }
+    }
+
+    @Override
+    public String getSchemaType(Schema p) {
+        String openAPIType = super.getSchemaType(p);
+        String type;
+
+        if (openAPIType == null) {
+            LOGGER.error("OpenAPI Type for {} is null. Default to UNKNOWN_OPENAPI_TYPE instead.", p.getName());
+            openAPIType = "UNKNOWN_OPENAPI_TYPE";
+        }
+
+        if (typeMapping.containsKey(openAPIType)) {
+          type = typeMapping.get(openAPIType);
+          String languageType = getNullableType(p, type);
+          if (languageType != null) {
+              return languageType;
+          }
+        } else {
+            type = openAPIType;
+        }
+
+        return toModelName(type);
+    }
+
+    /**
+     * Provides F# strongly typed declaration for simple arrays of some type and arrays of arrays of some type.
+     *
+     * @param arr The input array property
+     * @return The type declaration when the type is an array of arrays.
+     */
+    private String getArrayTypeDeclaration(ArraySchema arr) {
+        // TODO: collection type here should be fully qualified namespace to avoid model conflicts
+        // This supports arrays of arrays.
+        String arrayType = typeMapping.get("array");
+        StringBuilder instantiationType = new StringBuilder(arrayType);
+        Schema items = arr.getItems();
+        String nestedType = getTypeDeclaration(items);
+        // TODO: We may want to differentiate here between generics and primitive arrays.
+        return nestedType + "[]";
+    }
+
+    @Override
+    public String toInstantiationType(Schema p) {
+        if (ModelUtils.isArraySchema(p)) {
+            return getArrayTypeDeclaration((ArraySchema) p);
+        }
+        return super.toInstantiationType(p);
+    }
+
+    @Override
+    public String getTypeDeclaration(Schema p) {
+        if (ModelUtils.isArraySchema(p)) {
+            return getArrayTypeDeclaration((ArraySchema) p);
+        } else if (ModelUtils.isMapSchema(p)) {
+            // Should we also support maps of maps?
+            Schema inner = ModelUtils.getAdditionalProperties(p);
+            return getSchemaType(p) + "<string, " + getTypeDeclaration(inner) + ">";
+        }
+        return super.getTypeDeclaration(p);
+    }
+
+    @Override
+    public String toModelName(String name) {
+        if (!StringUtils.isEmpty(modelNamePrefix)) {
+            name = modelNamePrefix + "_" + name;
+        }
+
+        if (!StringUtils.isEmpty(modelNameSuffix)) {
+            name = name + "_" + modelNameSuffix;
+        }
+
+        name = sanitizeName(name);
+
+        // model name cannot use reserved keyword, e.g. return
+        if (isReservedWord(name)) {
+            LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + camelize("model_" + name));
+            name = "model_" + name; // e.g. return => ModelReturn (after camelize)
+        }
+
+        // model name starts with number
+        if (name.matches("^\\d.*")) {
+            LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + camelize("model_" + name));
+            name = "model_" + name; // e.g. 200Response => Model200Response (after camelize)
+        }
+
+        // camelize the model name
+        // phone_number => PhoneNumber
+        return camelize(name);
+    }
+
+    @Override
+    public String apiTestFileFolder() {
+        return outputFolder + File.separator + testFolder;
+    }
+
+    @Override
+    public String modelTestFileFolder() {
+      return outputFolder + File.separator + testFolder;
+    }
+
+    @Override
+    public String toApiTestFilename(String name) {
+        return toApiName(name) + "Tests";
+    }
+
+    @Override
+    public String toModelTestFilename(String name) {
+        return toModelName(name) + "Tests";
+    }
+
+    public void setLicenseUrl(String licenseUrl) {this.licenseUrl = licenseUrl;}
+
+    public void setLicenseName(String licenseName) {this.licenseName = licenseName;}
+
+    public void setPackageName(String packageName) {
+        this.packageName = packageName;
+        this.projectFolder = packageName;
+        this.sourceFolder = projectFolder + File.separator + "src";
+        this.testFolder = projectFolder + ".Tests";
+    }
+
+    public void setPackageVersion(String packageVersion) {
+        this.packageVersion = packageVersion;
+    }
+
+    public void setPackageTitle(String packageTitle) {
+        this.packageTitle = packageTitle;
+    }
+
+    public void setPackageProductName(String packageProductName) {
+        this.packageProductName = packageProductName;
+    }
+
+    public void setPackageDescription(String packageDescription) {
+        this.packageDescription = packageDescription;
+    }
+
+    public void setPackageCompany(String packageCompany) {
+        this.packageCompany = packageCompany;
+    }
+
+    public void setPackageCopyright(String packageCopyright) {
+        this.packageCopyright = packageCopyright;
+    }
+
+    public void setPackageAuthors(String packageAuthors) {
+        this.packageAuthors = packageAuthors;
+    }
+
+    public void setSourceFolder(String sourceFolder) {
+        this.sourceFolder = sourceFolder;
+    }
+
+    public String getInterfacePrefix() {
+        return interfacePrefix;
+    }
+
+    public void setInterfacePrefix(final String interfacePrefix) {
+        this.interfacePrefix = interfacePrefix;
+    }
+
+    public boolean isSupportNullable() {
+        return supportNullable;
+    }
+
+    public void setSupportNullable(final boolean supportNullable) {
+        this.supportNullable = supportNullable;
+    }
+
+    @Override
+    public String toEnumValue(String value, String datatype) {
+        // C# only supports enums as literals for int, int?, long, long?, byte, and byte?. All else must be treated as strings.
+        // Per: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum
+        // The approved types for an enum are byte, sbyte, short, ushort, int, uint, long, or ulong.
+        // but we're not supporting unsigned integral types or shorts.
+        if (datatype.startsWith("int") || datatype.startsWith("long") || datatype.startsWith("byte")) {
+            return value;
+        }
+
+        return escapeText(value);
+    }
+
+    @Override
+    public String toEnumVarName(String name, String datatype) {
+        if (name.length() == 0) {
+            return "Empty";
+        }
+
+        // for symbol, e.g. $, #
+        if (getSymbolName(name) != null) {
+            return camelize(getSymbolName(name));
+        }
+
+        String enumName = sanitizeName(name);
+
+        enumName = enumName.replaceFirst("^_", "");
+        enumName = enumName.replaceFirst("_$", "");
+
+        enumName = camelize(enumName) + "Enum";
+
+        if (enumName.matches("\\d.*")) { // starts with number
+            return "_" + enumName;
+        } else {
+            return enumName;
+        }
+    }
+
+    @Override
+    public String toEnumName(CodegenProperty property) {
+        return sanitizeName(camelize(property.name)) + "Enum";
+    }
+
+    public String testPackageName() {
+        return this.packageName + ".Test";
+    }
+
+    @Override
+    public String escapeQuotationMark(String input) {
+        // remove " to avoid code injection
+        return input.replace("\"", "");
+    }
+
+    @Override
+    public String escapeUnsafeCharacters(String input) {
+        return input.replace("*/", "*_/").replace("/*", "/_*").replace("--", "- -");
+    }
+
+    @Override
+    public boolean isDataTypeString(String dataType) {
+        // also treat double/decimal/float as "string" in enum so that the values (e.g. 2.8) get double-quoted
+        return "String".equalsIgnoreCase(dataType) ||
+                "double?".equals(dataType) || "decimal?".equals(dataType) || "float?".equals(dataType) ||
+                "double".equals(dataType) || "decimal".equals(dataType) || "float".equals(dataType);
+    }
+
+    @Override
+    public void setParameterExampleValue(CodegenParameter codegenParameter) {
+
+        // set the example value
+        // if not specified in x-example, generate a default value
+        // TODO need to revise how to obtain the example value
+        if (codegenParameter.vendorExtensions != null && codegenParameter.vendorExtensions.containsKey("x-example")) {
+            codegenParameter.example = Json.pretty(codegenParameter.vendorExtensions.get("x-example"));
+        } else if (Boolean.TRUE.equals(codegenParameter.isBoolean)) {
+            codegenParameter.example = "true";
+        } else if (Boolean.TRUE.equals(codegenParameter.isLong)) {
+            codegenParameter.example = "789";
+        } else if (Boolean.TRUE.equals(codegenParameter.isInteger)) {
+            codegenParameter.example = "56";
+        } else if (Boolean.TRUE.equals(codegenParameter.isFloat)) {
+            codegenParameter.example = "3.4F";
+        } else if (Boolean.TRUE.equals(codegenParameter.isDouble)) {
+            codegenParameter.example = "1.2D";
+        } else if (Boolean.TRUE.equals(codegenParameter.isNumber)) {
+            codegenParameter.example = "8.14";
+        } else if (Boolean.TRUE.equals(codegenParameter.isBinary)) {
+            codegenParameter.example = "BINARY_DATA_HERE";
+        } else if (Boolean.TRUE.equals(codegenParameter.isByteArray)) {
+            codegenParameter.example = "BYTE_ARRAY_DATA_HERE";
+        } else if (Boolean.TRUE.equals(codegenParameter.isFile)) {
+            codegenParameter.example = "/path/to/file.txt";
+        } else if (Boolean.TRUE.equals(codegenParameter.isDate)) {
+            codegenParameter.example = "2013-10-20";
+        } else if (Boolean.TRUE.equals(codegenParameter.isDateTime)) {
+            codegenParameter.example = "2013-10-20T19:20:30+01:00";
+        } else if (Boolean.TRUE.equals(codegenParameter.isUuid)) {
+            codegenParameter.example = "38400000-8cf0-11bd-b23e-10b96e4ef00d";
+        } else if (Boolean.TRUE.equals(codegenParameter.isString)) {
+            codegenParameter.example = codegenParameter.paramName + "_example";
+        }
+    }
+
+    @Override
+    public void postProcessFile(File file, String fileType) {
+        if (file == null) {
+            return;
+        }
+
+        String fsharpPostProcessFile = System.getenv("FSHARP_POST_PROCESS_FILE");
+        if (StringUtils.isEmpty(fsharpPostProcessFile)) {
+            return; // skip if FSHARP_POST_PROCESS_FILE env variable is not defined
+        }
+
+        // only process files with .fs extension
+        if ("fs".equals(FilenameUtils.getExtension(file.toString()))) {
+            String command = fsharpPostProcessFile + " " + file.toString();
+            try {
+                Process p = Runtime.getRuntime().exec(command);
+                int exitValue = p.waitFor();
+                if (exitValue != 0) {
+                    LOGGER.error("Error running the command ({}). Exit code: {}", command, exitValue);
+                } else {
+                    LOGGER.info("Successfully executed: " + command);
+                }
+            } catch (Exception e) {
+                LOGGER.error("Error running the command ({}). Exception: {}", command, e.getMessage());
+            }
+        }
+    }
+}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/FsharpGiraffeServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/FsharpGiraffeServerCodegen.java
new file mode 100644
index 0000000000000000000000000000000000000000..eb9708bc119bd8cc5f0d02e460c4edd8338f468f
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/FsharpGiraffeServerCodegen.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
+ *
+ * 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.
+ */
+
+package org.openapitools.codegen.languages;
+
+import com.samskivert.mustache.Mustache;
+import io.swagger.v3.oas.models.OpenAPI;
+import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+
+import org.openapitools.codegen.CodegenConstants;
+import org.openapitools.codegen.CodegenOperation;
+import org.openapitools.codegen.CodegenType;
+import org.openapitools.codegen.SupportingFile;
+import org.openapitools.codegen.CodegenModel;
+import org.openapitools.codegen.CodegenProperty;
+import org.openapitools.codegen.utils.URLPathUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+import java.util.ArrayList;
+
+import static java.util.UUID.randomUUID;
+
+public class FsharpGiraffeServerCodegen extends AbstractFSharpCodegen {
+
+    public static final String USE_SWASHBUCKLE = "useSwashbuckle";
+    public static final String GENERATE_BODY = "generateBody";
+    public static final String BUILD_TARGET = "buildTarget";
+
+    public static final String PROJECT_SDK = "projectSdk";
+    public static final String SDK_WEB = "Microsoft.NET.Sdk.Web";
+    public static final String SDK_LIB = "Microsoft.NET.Sdk";
+
+    private String packageGuid = "{" + randomUUID().toString().toUpperCase(Locale.ROOT) + "}";
+
+    @SuppressWarnings("hiding")
+    protected Logger LOGGER = LoggerFactory.getLogger(FsharpGiraffeServerCodegen.class);
+
+    private boolean useSwashbuckle = false;
+    protected int serverPort = 8080;
+    protected String serverHost = "0.0.0.0";
+    private boolean generateBody = true;
+    private String buildTarget = "program";
+    private String projectSdk = SDK_WEB;
+    
+    public FsharpGiraffeServerCodegen() {
+        super();
+
+        modelPackage = "Model";
+
+        apiTemplateFiles.put("Handler.mustache", "Handler.fs");    
+        apiTemplateFiles.put("HandlerParams.mustache", "HandlerParams.fs"); 
+        apiTemplateFiles.put("ServiceInterface.mustache", "ServiceInterface.fs"); 
+        apiTemplateFiles.put("ServiceImpl.mustache", "Service.fs"); 
+        apiTestTemplateFiles.put("HandlerTests.mustache", ".fs"); 
+        apiTestTemplateFiles.put("HandlerTestsHelper.mustache", "Helper.fs"); 
+        modelTemplateFiles.put("Model.mustache", ".fs");
+
+        embeddedTemplateDir = templateDir = "fsharp-giraffe-server";
+
+        cliOptions.clear();
+
+        // CLI options
+        addOption(CodegenConstants.LICENSE_URL,
+                CodegenConstants.LICENSE_URL_DESC,
+                licenseUrl);
+
+        addOption(CodegenConstants.LICENSE_NAME,
+                CodegenConstants.LICENSE_NAME_DESC,
+                licenseName);
+
+        addOption(CodegenConstants.PACKAGE_COPYRIGHT,
+                CodegenConstants.PACKAGE_COPYRIGHT_DESC,
+                packageCopyright);
+
+        addOption(CodegenConstants.PACKAGE_AUTHORS,
+                CodegenConstants.PACKAGE_AUTHORS_DESC,
+                packageAuthors);
+
+        addOption(CodegenConstants.PACKAGE_TITLE,
+                CodegenConstants.PACKAGE_TITLE_DESC,
+                packageTitle);
+
+        addOption(CodegenConstants.PACKAGE_NAME,
+                "F# module name (convention: Title.Case).",
+                packageName);
+
+        addOption(CodegenConstants.PACKAGE_VERSION,
+                "F# package version.",
+                packageVersion);
+
+        addOption(CodegenConstants.OPTIONAL_PROJECT_GUID,
+                CodegenConstants.OPTIONAL_PROJECT_GUID_DESC,
+                null);
+
+        addOption(CodegenConstants.SOURCE_FOLDER,
+                CodegenConstants.SOURCE_FOLDER_DESC,
+                sourceFolder);
+
+        // CLI Switches
+        addSwitch(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG,
+                CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG_DESC,
+                sortParamsByRequiredFlag);
+
+        addSwitch(CodegenConstants.USE_DATETIME_OFFSET,
+                CodegenConstants.USE_DATETIME_OFFSET_DESC,
+                useDateTimeOffsetFlag);
+
+        addSwitch(CodegenConstants.USE_COLLECTION,
+                CodegenConstants.USE_COLLECTION_DESC,
+                useCollection);
+
+        addSwitch(CodegenConstants.RETURN_ICOLLECTION,
+                CodegenConstants.RETURN_ICOLLECTION_DESC,
+                returnICollection);
+
+        addSwitch(USE_SWASHBUCKLE,
+                "Uses the Swashbuckle.AspNetCore NuGet package for documentation.",
+                useSwashbuckle);
+
+        addSwitch(GENERATE_BODY,
+                "Generates method body.",
+                generateBody);
+
+        addOption(BUILD_TARGET,
+                "Target the build for a program or library.",
+                buildTarget);
+
+    }
+
+    @Override
+    public CodegenType getTag() {
+        return CodegenType.SERVER;
+    }
+    
+    @Override
+    public String getName() {
+        return "fsharp-giraffe";
+    }
+    
+    @Override
+    public String getHelp() {
+        return "Generates a fsharp-giraffe server.";
+    }
+
+    @Override
+    public void preprocessOpenAPI(OpenAPI openAPI) {
+        super.preprocessOpenAPI(openAPI);
+        URL url = URLPathUtils.getServerURL(openAPI);
+        additionalProperties.put("serverHost", url.getHost());
+        additionalProperties.put("serverPort", URLPathUtils.getPort(url, 8080));
+    }
+
+    @Override
+    public void processOpts() {
+        super.processOpts();
+        boolean isLibrary = false;
+
+        if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_PROJECT_GUID)) {
+            setPackageGuid((String) additionalProperties.get(CodegenConstants.OPTIONAL_PROJECT_GUID));
+        }
+        additionalProperties.put("packageGuid", packageGuid);
+
+        if (additionalProperties.containsKey(USE_SWASHBUCKLE)) {
+            useSwashbuckle = convertPropertyToBooleanAndWriteBack(USE_SWASHBUCKLE);
+        } else {
+            additionalProperties.put(USE_SWASHBUCKLE, useSwashbuckle);
+        }
+
+        additionalProperties.put(PROJECT_SDK, projectSdk);
+
+        // TODO - should we be supporting a Giraffe class library?
+        if (isLibrary) 
+            LOGGER.warn("Library flag not currently supported.");
+
+        String authFolder = sourceFolder + File.separator + "auth";
+        String serviceFolder = sourceFolder + File.separator + "services";
+        String implFolder = sourceFolder + File.separator + "impl";
+        String helperFolder = sourceFolder + File.separator + "helpers";
+
+        supportingFiles.add(new SupportingFile("build.sh.mustache", projectFolder, "build.sh"));
+        supportingFiles.add(new SupportingFile("build.bat.mustache", projectFolder, "build.bat"));
+        supportingFiles.add(new SupportingFile("README.mustache", projectFolder, "README.md"));
+        supportingFiles.add(new SupportingFile("gitignore.mustache", projectFolder, ".gitignore"));
+        supportingFiles.add(new SupportingFile("Project.fsproj.mustache", sourceFolder, packageName + ".fsproj"));
+        supportingFiles.add(new SupportingFile("Program.mustache", sourceFolder, "Program.fs"));
+        supportingFiles.add(new SupportingFile("AuthSchemes.mustache", authFolder, "AuthSchemes.fs"));
+        supportingFiles.add(new SupportingFile("Helpers.mustache", helperFolder, "Helpers.fs"));
+        supportingFiles.add(new SupportingFile("CustomHandlers.mustache", implFolder, "CustomHandlers.fs"));
+        supportingFiles.add(new SupportingFile("Project.Tests.fsproj.mustache",testFolder, packageName + "Tests.fsproj")); 
+        supportingFiles.add(new SupportingFile("TestHelper.mustache",testFolder, "TestHelper.fs")); 
+
+        // TODO - support Swashbuckle
+        if (useSwashbuckle) 
+            LOGGER.warn("Swashbuckle flag not currently supported, this will be ignored.");
+    }
+
+    public void setPackageGuid(String packageGuid) {
+        this.packageGuid = packageGuid;
+    }
+
+    @Override
+    public String modelFileFolder() {
+      return super.modelFileFolder().replace("Model","model");
+    }
+    
+    @Override
+    public String apiFileFolder() {
+        return super.apiFileFolder() + File.separator + "api";
+    }
+
+    private String implFileFolder() {
+        return outputFolder + File.separator + sourceFolder + File.separator + "impl";
+    }
+
+    @Override()
+    public String toModelImport(String name) {
+      return packageName + "." + modelPackage() + "." + name;
+    }
+
+    @Override
+    public String apiFilename(String templateName, String tag) {
+        String result = super.apiFilename(templateName, tag);
+        if (templateName.endsWith("Impl.mustache")) {
+            int ix = result.lastIndexOf(File.separatorChar);
+            result = result.substring(0, ix) + result.substring(ix, result.length() - 2) + "fs";
+            result = result.replace(apiFileFolder(), implFileFolder());
+        }
+        return result;
+    }
+
+    
+    @Override
+    public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
+        generateJSONSpecFile(objs);
+        generateYAMLSpecFile(objs);
+        return super.postProcessSupportingFileData(objs);
+    }    
+
+    @Override
+    protected void processOperation(CodegenOperation operation) {
+        super.processOperation(operation);
+
+        // HACK: Unlikely in the wild, but we need to clean operation paths for MVC Routing
+        if (operation.path != null) {
+            String original = operation.path;
+            operation.path = operation.path.replace("?", "/");
+            if (!original.equals(operation.path)) {
+                LOGGER.warn("Normalized " + original + " to " + operation.path + ". Please verify generated source.");
+            }
+        }
+
+        // Converts, for example, PUT to HttpPut for controller attributes
+        operation.httpMethod = "Http" + operation.httpMethod.substring(0, 1) + operation.httpMethod.substring(1).toLowerCase(Locale.ROOT);
+    }
+
+    @Override
+    public Mustache.Compiler processCompiler(Mustache.Compiler compiler) {
+        // To avoid unexpected behaviors when options are passed programmatically such as { "useCollection": "" }
+        return super.processCompiler(compiler).emptyStringIsFalse(true);
+    }
+
+    @Override
+    public String toRegularExpression(String pattern) {
+        return escapeText(pattern);
+    }
+}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
index 24b65ad72c326e33f26d633c1a49e95152a7c7e8..4eddf1ebea6f329d593f425886e6dea1f81260d7 100644
--- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
+++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
@@ -106,3 +106,4 @@ org.openapitools.codegen.languages.TypeScriptInversifyClientCodegen
 org.openapitools.codegen.languages.TypeScriptJqueryClientCodegen
 org.openapitools.codegen.languages.TypeScriptNodeClientCodegen
 org.openapitools.codegen.languages.TypeScriptRxjsClientCodegen
+org.openapitools.codegen.languages.FsharpGiraffeServerCodegen
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/AuthSchemes.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/AuthSchemes.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..cabb34132a618f26f799d3b9c2a6b2f167b152d2
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/AuthSchemes.mustache
@@ -0,0 +1,100 @@
+namespace {{packageName}}
+
+open Microsoft.AspNetCore.Authentication
+open Microsoft.AspNetCore.Authentication.Cookies
+open Microsoft.Extensions.DependencyInjection
+open Microsoft.AspNetCore.Http
+open Microsoft.AspNetCore.Authentication.OAuth
+open System
+open Giraffe
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Microsoft.Extensions.Configuration
+open AspNet.Security.ApiKey.Providers.Extensions
+open AspNet.Security.ApiKey.Providers.Events
+
+
+module AuthSchemes =
+
+  let accessDenied : HttpHandler = setStatusCode 401 >=> text "Access Denied"
+
+  let buildGoogle (builder:AuthenticationBuilder) name authorizationUrl scopes (settings:IConfiguration) = 
+    builder.AddGoogle(fun googleOptions -> CustomHandlers.setOAuthOptions "Google" googleOptions scopes settings)
+
+  let buildGitHub (builder:AuthenticationBuilder) name authorizationUrl scopes (settings:IConfiguration) = 
+    builder.AddGitHub(fun githubOptions -> CustomHandlers.setOAuthOptions "GitHub" githubOptions scopes settings)
+
+  let buildOAuth (builder:AuthenticationBuilder) (name:string) authorizationUrl scopes (settings:IConfiguration) = 
+    builder.AddOAuth(name, (fun (options:OAuthOptions) -> 
+      options.AuthorizationEndpoint <- authorizationUrl
+      options.TokenEndpoint <- settings.[name + "TokenUrl"]
+      options.CallbackPath <- PathString(settings.[name + "CallbackPath"])
+      CustomHandlers.setOAuthOptions "{{name}}" options scopes settings
+  ))
+
+  let OAuthBuilders = Map.empty.Add("Google", buildGoogle).Add("GitHub", buildGitHub)
+
+  let checkEnvironment (settings:IConfiguration) name =
+    if (isNull settings.[name + "ClientId"]) then
+      raise (Exception(name + "ClientId is not set."))
+    else if (isNull settings.[name + "ClientSecret"]) then
+      raise (Exception((name + "ClientSecret is not set.")))
+
+  let getOAuthBuilder settings name =  
+    // check that "xxxClientId" and "xxxClientSecret" configuration variables have been set for all OAuth providers
+    checkEnvironment settings name
+    if OAuthBuilders.ContainsKey(name) then
+      OAuthBuilders.[name]
+    else
+      buildOAuth
+
+  let configureOAuth (settings:IConfiguration) services =
+    {{#authMethods}}
+    {{#isOAuth}}
+    (getOAuthBuilder settings "{{name}}") services "{{name}}" "{{authorizationUrl}}" [{{#scopes}}"{{scope}}";{{/scopes}}] settings
+    {{/isOAuth}}
+    {{/authMethods}}
+
+  let buildApiKeyAuth name (services:AuthenticationBuilder) =
+    services.AddApiKey(fun options -> 
+      options.Header <- name
+      options.HeaderKey <- String.Empty
+      let events = ApiKeyEvents()
+      options.Events <- CustomHandlers.setApiKeyEvents name events
+    )
+
+  let configureApiKeyAuth (settings:IConfiguration) services =
+    {{#authMethods}}
+    {{#isApiKey}}
+    {{#isKeyInHeader}}
+    buildApiKeyAuth "{{name}}" services
+    {{/isKeyInHeader}}
+    {{^isKeyInHeader}}
+    raise (NotImplementedException("API key security scheme outside of header has not yet been implemented"))
+    {{/isKeyInHeader}}
+    {{/isApiKey}}
+    {{/authMethods}}
+
+
+  let configureCookie (builder:AuthenticationBuilder) =
+      builder.AddCookie(CustomHandlers.cookieAuth)
+
+  let configureServices (services:IServiceCollection) = 
+    let serviceProvider = services.BuildServiceProvider()
+    let settings = serviceProvider.GetService<IConfiguration>()
+    services.AddAuthentication(fun o -> o.DefaultScheme <- CookieAuthenticationDefaults.AuthenticationScheme)
+    |> configureOAuth settings 
+    |> configureApiKeyAuth settings
+    |> configureCookie
+    
+  let (|||) v1 v2 = 
+      match v1 with 
+      | Some v -> v1
+      | None -> v2
+
+  // this can be replaced with ctx.GetCookieValue in Giraffe >= 3.6
+  let getCookieValue (ctx:HttpContext) (key : string)  =
+        match ctx.Request.Cookies.TryGetValue key with
+        | true , cookie -> Some cookie
+        | false, _ -> None
+
+  
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/CustomHandlers.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/CustomHandlers.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..cc176cdb66edee7c6dd86e99941c3347f2b3d1a2
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/CustomHandlers.mustache
@@ -0,0 +1,112 @@
+namespace {{packageName}}
+
+open System
+open System.Net.Http
+open System.Security.Claims
+open System.Threading
+open Microsoft.AspNetCore
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.Http
+open Microsoft.AspNetCore.Http.Features
+open Microsoft.AspNetCore.Authentication
+open Microsoft.AspNetCore.Authentication.Cookies
+open Microsoft.Extensions.Configuration
+open Microsoft.Extensions.Logging
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Giraffe
+open Giraffe.GiraffeViewEngine
+open Microsoft.AspNetCore.Authentication.OAuth
+open System.Threading.Tasks
+open AspNet.Security.ApiKey.Providers.Events
+
+module CustomHandlers = 
+
+  let cookieAuth (o : CookieAuthenticationOptions) =
+    do
+        o.Cookie.HttpOnly     <- true
+        o.Cookie.SecurePolicy <- CookieSecurePolicy.SameAsRequest
+        o.SlidingExpiration   <- true
+        o.ExpireTimeSpan      <- TimeSpan.FromDays 7.0
+
+  
+  let onCreatingTicket name (ctx:OAuthCreatingTicketContext) = 
+    task {
+        // implement post-authentication logic for oAuth handlers here
+       ()
+    } :> Task
+
+  let validateApiKey key = 
+    raise (NotImplementedException("API key validation must be implemented"))
+
+  let setApiKeyEvents name (events:ApiKeyEvents) = 
+    events.OnApiKeyValidated <- (fun ctx -> 
+        task {
+          // implement your validation/authentication logic for api key handlers here
+          if validateApiKey ctx.ApiKey then  
+          // to interact properly with Giraffe's handlers, you will need to manually set the identity 
+          // let claims = ...
+          // let identity = ClaimsIdentity(claims, ApiKeyDefaults.AuthenticationScheme)
+          // ctx.HttpContext.User <- ClaimsPrincipal([|identity|])
+            ctx.Success()
+        } :> Task
+    )
+    events
+
+  let setOAuthOptions name (options:OAuthOptions) scopes (settings:IConfiguration) = 
+    options.ClientId <- settings.[name + "ClientId"]
+    options.ClientSecret <- settings.[name + "ClientSecret"]
+    for scope in scopes do
+      options.Scope.Add scope
+
+    options.Events.OnCreatingTicket <- Func<OAuthCreatingTicketContext,Tasks.Task>(onCreatingTicket name)
+    match name with
+    | "Google" ->
+      ()  
+    | "GitHub" ->
+      ()
+    | _ -> 
+      ()
+  
+  let logout = signOut CookieAuthenticationDefaults.AuthenticationScheme >=> redirectTo false "/"
+
+  let loginView =
+    html [] [
+        head [] [
+            title [] [ str "Welcome" ]
+        ]
+        body [] [
+            h1 [] [ str "Welcome" ]
+            {{#authMethods}}
+            a [_href "/login-with-{{name}}"] [ str "Login with {{name}}" ]
+            {{/authMethods}}
+        ]
+    ]
+  
+  let redirectToLogin : HttpHandler = 
+    htmlView loginView    
+  
+  let handlers : HttpHandler list = [
+    // insert your handlers here
+    GET >=> 
+      choose [
+        route "/login" >=> redirectToLogin
+        {{#authMethods}}
+        route "/login-with-{{name}}" >=> challenge "{{name}}"
+        {{/authMethods}}
+        route "/logout" >=> logout
+      ]
+  ]
+
+  let configureWebHost (builder: IWebHostBuilder)  =
+      // builder
+      //  .UseContentRoot("content")
+      //  .UseWebRoot("static")
+      builder
+
+  let configureApp (app : IApplicationBuilder) =
+    app
+
+  let configureServices (services:IServiceCollection) (authBuilder:AuthenticationBuilder) = 
+    ()
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Handler.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Handler.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..908d0faf5bfea536269ae66e3c9a9cf1d888e9d8
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Handler.mustache
@@ -0,0 +1,67 @@
+namespace {{packageName}}
+
+open System.Collections.Generic
+open Giraffe
+open Microsoft.AspNetCore.Http
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open {{classname}}HandlerParams
+open {{classname}}ServiceInterface
+open {{classname}}ServiceImplementation
+{{#imports}}
+{{#import}}
+open {{import}}
+{{/import}}
+{{/imports}}
+
+module {{classname}}Handler = 
+
+{{#operations}}
+    /// <summary>
+    /// {{description}}
+    /// </summary>
+
+    {{#operation}}
+    //#region {{operationId}}
+    /// <summary>
+    /// {{#summary}}{{summary}}{{/summary}}
+    /// </summary>
+
+    let {{operationId}} {{#hasPathParams}}(pathParams:{{operationId}}PathParams){{/hasPathParams}} : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          {{#hasQueryParams}}
+          let queryParams = ctx.TryBindQueryString<{{operationId}}QueryParams>()
+          {{/hasQueryParams}}
+          {{#hasBodyParam}}
+          let! bodyParams = 
+            ctx.BindJsonAsync<{{operationId}}BodyParams>()
+          {{/hasBodyParam}}
+          {{#hasFormParams}}
+          let! formParams = ctx.TryBindFormAsync<{{operationId}}FormParams>()
+          {{/hasFormParams}}
+          {{#hasHeaderParams}}
+          let headerParams = {
+            {{#headerParams}}
+              {{operationId}}HeaderParams.{{paramName}}={{#required}}ctx.GetRequestHeader{{/required}}{{^required}}ctx.TryGetRequestHeader{{/required}} "{{paramName}}";
+            {{/headerParams}}
+          }
+          {{/hasHeaderParams}}
+          {{#allParams}}
+          {{#-first}}
+          let serviceArgs = { {{#hasHeaderParams}}headerParams=headerParams;{{/hasHeaderParams}} {{#hasQueryParams}}queryParams=queryParams;{{/hasQueryParams}} {{#hasFormParams}}formParams=formParams;{{/hasFormParams}} {{#hasPathParams}}pathParams=pathParams;{{/hasPathParams}} {{#hasBodyParam}}bodyParams=bodyParams{{/hasBodyParam}} } : {{operationId}}Args
+          {{/-first}}
+          {{/allParams}}
+          let result = {{classname}}Service.{{operationId}} ctx {{#allParams}}{{#-first}}serviceArgs{{/-first}}{{/allParams}}
+          return! (match result with 
+                      {{#responses}}
+                      | {{operationId}}{{#isDefault}}Default{{/isDefault}}StatusCode{{^isDefault}}{{code}}{{/isDefault}} resolved ->
+                            setStatusCode {{code}} >=> {{#primitiveType}}{{^isMapContainer}}text{{/isMapContainer}}{{/primitiveType}}{{^primitiveType}}json{{/primitiveType}} resolved.content 
+                      {{/responses}}
+          ) next ctx
+        }
+    //#endregion
+
+    {{/operation}}
+{{/operations}}
+
+    
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/HandlerParams.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/HandlerParams.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..336ef2d55cf34d1c70d4bd4f0a21d8660d67a839
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/HandlerParams.mustache
@@ -0,0 +1,147 @@
+namespace {{packageName}}
+
+{{#imports}}
+{{#import}}
+open {{import}}
+{{/import}}
+{{/imports}}
+open System.Collections.Generic
+open System
+
+module {{classname}}HandlerParams = 
+
+    {{#operations}}
+    {{#operation}}
+    {{#pathParams}}
+    {{#-first}}
+    //#region Path parameters
+    [<CLIMutable>]
+    type {{operationId}}PathParams = {
+    {{/-first}}
+      {{paramName}} : {{dataType}} {{^required}}option{{/required}};
+    {{#-last}}
+    }
+    {{/-last}}  
+    //#endregion
+    {{/pathParams}}
+    {{#queryParams}}
+
+    {{#-first}}
+    //#region Query parameters
+    [<CLIMutable>]
+    type {{operationId}}QueryParams = {
+    {{/-first}}
+      {{paramName}} : {{dataType}} {{^required}}option{{/required}};
+      
+    {{#-last}}
+    }
+    //#endregion
+    {{/-last}}  
+    {{/queryParams}}
+    {{#bodyParams}}
+
+    {{#-first}}
+    //#region Body parameters
+    [<CLIMutable>]
+    {{^hasMore}}
+    type {{operationId}}BodyParams = {{dataType}} 
+    {{/hasMore}}
+    {{#hasMore}}
+    type {{operationId}}BodyParams = { 
+      {{paramName}} : {{dataType}};
+    {{/hasMore}}
+    {{/-first}}
+    {{^-first}}
+      {{paramName}} : {{dataType}};
+    {{/-first}}
+    {{#-last}}
+    {{^-first}}
+    }
+    {{/-first}}
+    //#endregion
+    {{/-last}}  
+    {{/bodyParams}}
+    {{#formParams}}
+
+    //#region Form parameters
+    {{#-first}}
+    [<CLIMutable>]
+    type {{operationId}}FormParams = {
+    {{/-first}}
+      {{paramName}} : {{dataType}} {{^required}}option{{/required}};
+    {{#-last}}
+    }
+    {{/-last}}  
+    //#endregion
+    {{/formParams}}
+    {{#headerParams}}
+
+    //#region Header parameters
+    {{#-first}}
+    [<CLIMutable>]
+    type {{operationId}}HeaderParams = {
+    {{/-first}}
+      {{paramName}} : {{dataType}} {{^required}}option{{/required}};
+    {{#-last}}
+    }
+    {{/-last}}  
+    //#endregion
+    {{/headerParams}}
+    {{#cookieParams}}
+    
+    //#region Cookie parameters
+    {{#-first}}
+    type {{operationId}}CookieParams = {
+    {{/-first}}
+      {{paramName}} : {{dataType}} {{^required}}option{{/required}};
+    {{#-last}}
+    }
+    {{/-last}}  
+    //#endregion
+    {{/cookieParams}}
+
+    {{#responses}}
+    
+    type {{operationId}}{{#isDefault}}Default{{/isDefault}}StatusCode{{^isDefault}}{{code}}{{/isDefault}}Response = {
+      content:{{#dataType}}{{{.}}}{{/dataType}}{{^dataType}}string{{/dataType}};
+      {{^code}}code:int{{/code}}
+    }
+    {{/responses}}
+    type {{operationId}}Result = {{#responses}}{{operationId}}{{#isDefault}}Default{{/isDefault}}StatusCode{{^isDefault}}{{code}}{{/isDefault}} of {{operationId}}{{#isDefault}}Default{{/isDefault}}StatusCode{{^isDefault}}{{code}}{{/isDefault}}Response{{#hasMore}}|{{/hasMore}}{{/responses}}
+
+    {{#allParams}}
+    {{#-first}}
+    type {{operationId}}Args = {
+    {{/-first}}
+    {{/allParams}}
+      {{#hasHeaderParams}}
+      headerParams:{{operationId}}HeaderParams;
+      {{/hasHeaderParams}}
+      {{#pathParams}}
+      {{#-first}}
+      pathParams:{{operationId}}PathParams;
+      {{/-first}}
+      {{/pathParams}}
+      {{#queryParams}}
+      {{#-first}}
+      queryParams:Result<{{operationId}}QueryParams,string>;
+      {{/-first}}
+      {{/queryParams}}
+      {{#bodyParams}}
+      {{#-first}}
+      bodyParams:{{operationId}}BodyParams
+      {{/-first}}
+      {{/bodyParams}}
+      {{#formParams}}
+      {{#-first}}
+      formParams:Result<{{operationId}}FormParams,string>
+      {{/-first}}
+      {{/formParams}}
+    {{#allParams}}
+    {{#-first}}
+    }
+    {{/-first}}
+    {{/allParams}}
+    {{/operation}}
+    {{/operations}}
+    
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/HandlerTests.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/HandlerTests.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..606bdb995c1028192bb3af9c1cdfe3c31e31bacc
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/HandlerTests.mustache
@@ -0,0 +1,65 @@
+namespace {{packageName}}.Tests
+
+open System
+open System.Net
+open System.Net.Http
+open System.IO
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.TestHost
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Xunit
+open System.Text
+open Newtonsoft
+open TestHelper
+open {{classname}}HandlerTestsHelper
+open {{packageName}}.{{classname}}Handler
+open {{packageName}}.{{classname}}HandlerParams
+{{#imports}}
+open {{import}}
+{{/imports}}
+
+module {{classname}}HandlerTests =
+
+  // ---------------------------------
+  // Tests
+  // ---------------------------------
+
+  {{#operations}}
+  {{#operation}}
+  {{#responses}}
+  [<Fact>]
+  let ``{{operationId}} - {{summary}} returns {{code}} {{#message}}where {{.}}{{/message}}`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "{{contextPath}}{{path}}"{{#pathParams}}.Replace("{{paramName}}", "ADDME"){{/pathParams}}{{#hasQueryParams}} + "?{{#queryParams}}{{paramName}}=ADDME{{#hasMore}}&{{/hasMore}}{{#-last}}"{{/-last}}{{/queryParams}}{{/hasQueryParams}}
+
+      {{#hasConsumes}}
+      // use an example requestBody provided by the spec
+      let examples = Map.empty{{#consumes}}.Add("{{mediaType}}", get{{operationId}}Example "{{mediaType}}"){{/consumes}}
+      // or pass a {{#bodyParams}}body of type {{dataType}}{{/bodyParams}}{{#formParams}}form{{/formParams}}
+      let body = obj() {{#bodyParams}}:?> {{dataType}}{{/bodyParams}} |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> {{httpMethod}} client path
+        |> isStatus (enum<HttpStatusCode>({{code}}))
+        |> readText
+        |> shouldEqual "TESTME"
+      {{/hasConsumes}}
+      {{^hasConsumes}}
+      {{httpMethod}} client path
+        |> isStatus (enum<HttpStatusCode>({{code}}))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      {{/hasConsumes}}
+      }
+
+  {{/responses}}
+  {{/operation}}
+  {{/operations}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/HandlerTestsHelper.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/HandlerTestsHelper.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..855b0771c2860f13877d43763dd0051360d08bc2
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/HandlerTestsHelper.mustache
@@ -0,0 +1,46 @@
+namespace {{packageName}}.Tests
+
+open System
+open System.Net
+open System.Net.Http
+open System.IO
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.TestHost
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Xunit
+open System.Text
+open TestHelper
+open {{packageName}}.{{classname}}Handler
+open {{packageName}}.{{classname}}HandlerParams
+
+module {{classname}}HandlerTestsHelper =
+
+  {{#operations}}
+  {{#operation}}
+  {{^consumes}}
+  ()
+  {{/consumes}}
+
+  {{#consumes}}
+  {{#-first}}
+  let mutable {{operationId}}Examples = Map.empty
+  let mutable {{operationId}}Body = ""
+
+  {{/-first}}
+  {{/consumes}}
+  {{#requestBodyExamples}}
+  {{operationId}}Body <- WebUtility.HtmlDecode "{{example}}"
+  {{operationId}}Examples <- {{operationId}}Examples.Add("{{contentType}}", {{operationId}}Body)
+
+  {{/requestBodyExamples}}
+  {{#consumes}}
+  {{#-first}}
+  let get{{operationId}}Example mediaType =
+    {{operationId}}Examples.[mediaType]
+      |> getConverter mediaType
+  {{/-first}}
+  {{/consumes}}
+  {{/operation}}
+  {{/operations}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Helpers.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Helpers.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..6dcbe4ad94c0481a4cbc7c8c0bf7de0d50f66701
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Helpers.mustache
@@ -0,0 +1,12 @@
+  namespace OpenAPI
+
+  module Helpers = 
+
+    let (>=>) switch1 switch2 =
+      match switch1 with 
+        | Ok v1 -> 
+          match switch2 with 
+           | Ok v2 ->
+            Ok(v1, v2)
+            | Error e -> Error e          
+        | Error e -> Error e
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Model.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Model.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..de115478e262f9ce43c288e703e9e6badddbbfa8
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Model.mustache
@@ -0,0 +1,36 @@
+namespace {{packageName}}.{{modelPackage}}
+
+open System
+open System.Collections.Generic
+{{#imports}}
+open {{import}}
+{{/imports}}
+
+module {{classname}} = 
+
+  {{#models}}
+  {{#model}}
+  //#region {{classname}}
+
+  {{#vars}}
+  {{#isEnum}}
+  //#region enums
+  type {{datatypeWithEnum}} = {{#allowableValues}}{{#enumVars}}{{name}} of {{datatype}} {{^-last}} | {{/-last}} {{/enumVars}}{{/allowableValues}}
+  //#endregion
+  {{/isEnum}}
+  {{/vars}}
+
+  type {{name}} = {
+    {{#vars}}
+    {{#isEnum}}
+    {{name}} : {{{datatypeWithEnum}}};
+    {{/isEnum}}
+    {{^isEnum}}
+    {{name}} : {{#isDateTime}}{{^required}}Nullable<{{/required}}{{/isDateTime}}{{{dataType}}}{{#isDateTime}}{{^required}}>{{/required}}{{/isDateTime}};
+    {{/isEnum}}
+    {{/vars}}
+  }
+  //#endregion
+  {{/model}}
+  {{/models}}
+  
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Program.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Program.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..74f0f7b2af9635783c150f384779d21a7d3484ea
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Program.mustache
@@ -0,0 +1,100 @@
+namespace {{packageName}}
+
+open System
+open System.Net.Http
+open System.Security.Claims
+open System.Threading
+open Microsoft.AspNetCore
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.Http
+open Microsoft.AspNetCore.Http.Features
+open Microsoft.AspNetCore.Authentication
+open Microsoft.AspNetCore.Authentication.Cookies
+open Microsoft.Extensions.Configuration
+open Microsoft.Extensions.Logging
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open System.Diagnostics
+open Giraffe.GiraffeViewEngine
+open AspNet.Security.ApiKey.Providers
+
+{{#apiInfo}}
+{{#apis}}
+open {{classFilename}}HandlerParams
+{{/apis}}
+{{/apiInfo}}
+open Giraffe
+
+module App =
+
+  // ---------------------------------
+  // Error handler
+  // ---------------------------------
+
+  let errorHandler (ex : Exception) (logger : ILogger) =
+    logger.LogError(EventId(), ex, "An unhandled exception has occurred while executing the request.")
+    clearResponse >=> setStatusCode 500 >=> text ex.Message
+
+  // ---------------------------------
+  // Web app
+  // ---------------------------------
+
+  let HttpGet = GET
+  let HttpPost = POST
+  let HttpPut = PUT
+  let HttpDelete = DELETE
+
+  let authFailure : HttpHandler = 
+    setStatusCode 401 >=> text "You must be authenticated to access this resource."
+
+  let webApp =
+    choose (CustomHandlers.handlers @ [
+      {{#apiInfo}}
+      {{#apis}}
+      {{#operations}}
+      {{#operation}}
+      {{httpMethod}} >=> {{^hasPathParams}}route{{/hasPathParams}}{{#hasPathParams}}routeBind<{{operationId}}PathParams>{{/hasPathParams}} "{{contextPath}}{{path}}" {{^pathParams}}>=>{{/pathParams}} {{#pathParams}}(fun x -> {{/pathParams}}{{#authMethods}}{{#isOAuth}}requiresAuthentication authFailure{{/isOAuth}}{{#isApiKey}}challenge ApiKeyDefaults.AuthenticationScheme >=> requiresAuthentication authFailure{{/isApiKey}} >=> {{/authMethods}} {{classname}}Handler.{{operationId}}{{#pathParams}} x){{/pathParams}};
+      {{/operation}}
+      {{/operations}}
+      {{/apis}}
+      RequestErrors.notFound (text "Not Found") 
+      {{/apiInfo}}
+    ])
+  // ---------------------------------
+  // Main
+  // ---------------------------------
+
+  let configureApp (app : IApplicationBuilder) =
+    app.UseGiraffeErrorHandler(errorHandler)
+      .UseStaticFiles()
+      .UseAuthentication()
+      .UseResponseCaching() |> ignore
+    CustomHandlers.configureApp app |> ignore
+    app.UseGiraffe webApp |> ignore
+    
+
+  let configureServices (services : IServiceCollection) =
+    services
+          .AddResponseCaching()
+          .AddGiraffe()
+          |> AuthSchemes.configureServices      
+          |> CustomHandlers.configureServices services
+          |> ignore
+    services.AddDataProtection() |> ignore
+
+  let configureLogging (loggerBuilder : ILoggingBuilder) =
+    loggerBuilder.AddFilter(fun lvl -> lvl.Equals LogLevel.Error)
+                  .AddConsole()
+                  .AddDebug() |> ignore
+
+  [<EntryPoint>]
+  let main _ =
+    let builder = WebHost.CreateDefaultBuilder()
+                    .Configure(Action<IApplicationBuilder> configureApp)
+                    .ConfigureServices(configureServices)
+                    .ConfigureLogging(configureLogging)
+                    |> CustomHandlers.configureWebHost
+    builder.Build()
+            .Run()
+    0
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Project.Tests.fsproj.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Project.Tests.fsproj.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..e1179afc5262f0cef0cd4d846bf0314d526a2790
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Project.Tests.fsproj.mustache
@@ -0,0 +1,32 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp2.2</TargetFramework>
+    <AssemblyName>{{packageName}}.Tests</AssemblyName>
+    <DebugType>portable</DebugType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.*" />
+    <PackageReference Include="Microsoft.AspNetCore.App" />
+    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.2.*" />
+    <PackageReference Include="xunit" Version="2.4.*" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.*" />
+    <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\{{packageName}}\src\{{packageName}}.fsproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Include="TestHelper.fs"/>
+    {{#apiInfo}}
+    {{#apis}}
+    <Compile Include="{{classname}}TestsHelper.fs" />
+    <Compile Include="{{classname}}Tests.fs" />
+    {{/apis}}
+    {{/apiInfo}}
+  </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Project.fsproj.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Project.fsproj.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..0576d996d867ba0b1ef54e55ff408acb7405bf18
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/Project.fsproj.mustache
@@ -0,0 +1,44 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+  <PropertyGroup>
+    <Description>{{packageName}}</Description>
+    <Copyright>{{packageName}}</Copyright>
+    <TargetFramework>netcoreapp2.2</TargetFramework>
+    <DebugType>portable</DebugType>
+    <EnableDefaultContentItems>false</EnableDefaultContentItems>
+    <RunWorkingDirectory>$(MSBuildThisFileDirectory)</RunWorkingDirectory>
+    <AssemblyName>{{packageName}}</AssemblyName>
+    <PackageId>{{packageName}}</PackageId>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.All" />
+    <PackageReference Include="Giraffe" Version="3.4.*" />
+    <PackageReference Include="TaskBuilder.fs" Version="2.1.*" />
+    <PackageReference Include="AspNet.Security.OAuth.GitHub" Version="2.0.1" />
+    <PackageReference Include="AspNet.Security.ApiKey.Providers" Version="1.1.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="openapi.yaml"/>
+    <Compile Include="helpers/Helpers.fs" />
+    {{#models}}
+    {{#model}}
+    <Compile Include="model/{{classname}}.fs" />
+    {{/model}}
+    {{/models}}
+    {{#apiInfo}}
+    {{#apis}}
+    {{#operations}}
+    <Compile Include="api/{{classname}}HandlerParams.fs" />
+    <Compile Include="api/{{classname}}ServiceInterface.fs" />
+    <Compile Include="impl/{{classname}}Service.fs" />
+    <Compile Include="api/{{classname}}Handler.fs" />
+    {{/operations}}
+    {{/apis}}
+    {{/apiInfo}}
+    <Compile Include="impl/CustomHandlers.fs" />
+    <Compile Include="auth/AuthSchemes.fs" />
+    <Compile Include="Program.fs" />
+  </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/README.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/README.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..21d41400b228258d02f5f15673472f5cafc8251d
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/README.mustache
@@ -0,0 +1,198 @@
+# {{packageName}}
+
+A [Giraffe](https://github.com/giraffe-fsharp/Giraffe) server stub for the {{packageName}} package, created via the [OpenAPI generator](https://github.com/OpenAPITools/openapi-generator/).
+
+## Models
+
+The following models have been auto-generated from the provided OpenAPI schema:
+
+{{#apiInfo}}
+{{#models}}
+{{#model}}
+- model/{{classname}}Model.fs
+{{/model}}
+{{/models}}
+{{/apiInfo}}
+
+## Operations
+
+Handlers have been auto-generated from the operations specified in the OpenAPI schema as follows:
+
+{{#apiInfo}}
+{{#operations}}
+{{#operation}}
+- api/{{classname}}Handler.fs
+{{/operation}}
+{{/operations}}
+{{/apiInfo}}
+
+## Operation Parameters
+
+Types have been generated for the URL, query, form, header and cookie parameters passed to each handler in the following files:
+
+{{#apiInfo}}
+{{#apis}}
+- api/{{classname}}HandlerParams.fs
+{{/apis}}
+{{/apiInfo}}
+
+## Service Interfaces
+
+Handlers will attempt to bind parameters to the applicable type and pass to a Service specific to that Handler. Service interfaces have been generated as follows:
+
+{{#apiInfo}}
+{{#apis}}
+- api/{{classname}}ServiceInterface.fs
+{{/apis}}
+{{/apiInfo}}
+
+Each Service contains functions for each [OperationId], each accepting a [OperationId]Params object that wraps the operation's parameters.
+
+If a requestBody is a ref type (i.e. a Model) or a single simple type, the operation parameter will be typed as the expected Model:
+
+`type AddPetBodyParams = Pet`
+
+If a requestBody is a simple type with named properties, the operation parameters will be typed to reflect those properties:
+
+`type AddFooBodyParams = {
+  Name:string;
+  Age:int
+}
+
+Each Service/operation function must accept the [OperationId]Params object and return a [OperationId]Result type. For example:
+
+`type AddPetArgs = { bodyParams:AddPetBodyParams }
+type IPetApiService = abstract member AddPet:HttpContext -> AddPetArgs->AddPetResult`
+
+[OperationId]Result is a discriminated union of all possible OpenAPI response types for that operation. 
+
+This means that service implementations can only return status codes that have been declared in the OpenAPI specification. 
+However, if the OpenAPI spec declares a default Response for an operation, the service can manually set the status code.
+
+For example:
+
+`type FindPetsByStatusDefaultStatusCodeResponse = { content:Pet[];}
+type FindPetsByStatusStatusCode400Response = { content:string; }
+type FindPetsByStatusResult = FindPetsByStatusDefaultStatusCode of FindPetsByStatusDefaultStatusCodeResponse | FindPetsByStatusStatusCode400 of FindPetsByStatusStatusCode400Response`
+
+## Note on nullable/optional properties 
+
+Currently, handler parameters and models do not distinguish between required properties and optional (or nullable) properties***.
+
+If a request body is missing a property, the parameter will be bound as null (and likewise, missing model properties will be serialized as null).
+
+This is only a temporary measure, and does need to be fixed to conform to the OpenAPI spec.
+
+Ideally, Option types would be used for all parameters not marked as required (or marked as nullable).
+
+This won't be possible until Giraffe supports binding option types in request bodies.
+
+This may cause problems with certain parameter types (e.g. map types) - please file an issue if you come across one.
+
+*** Except for DateTime, where properties not marked required are bound as Nullable<DateTime>.
+
+## Note on response codes for URL parameter binding
+
+Giraffe binds URL parameters by requiring compile-time format strings for routes  (e.g. "/foo/%s/%d) or known types (e.g. FooUrlParameters).
+
+With either approach, Giraffe will emit a 400 error response if parameter binding fails (e.g. if a string is provided where an int was expected).
+
+Currently, I am not aware of any way to customize this response, meaning if your OpenAPI schema specifies a different response code for an incorrectly formatted URL parameter, this will basically be ignored.
+
+To ensure your OpenAPI schema and implementation are consistent, I suggest ensuring that your schema only specifies return code 400 for incorrectly formatted URL parameters.
+
+If you have any suggestions for customizing this, please file an issue.
+
+## Service Implementations
+
+Stubbed service implementations of those interfaces have been generated as follows:
+
+{{#apiInfo}}
+{{#apis}}
+- impl/{{classname}}Service.fs
+{{/apis}}
+{{/apiInfo}}
+
+You should manually edit these files to implement your business logic.
+
+## Additional Handlers
+
+Additional handlers can be configured in the Customization.fs
+
+`let handlers : HttpHandler list = [
+    // insert your handlers here
+    GET >=> 
+      choose [
+        route "/login" >=> redirectToLogin
+        route "/logout" >=> logout
+      ]
+  ]`
+
+## Authentication
+
+### OAuth
+
+If your OpenAPI spec contains oAuth2 securitySchemes, these will have been auto-generated.
+
+To configure any of these, you must set the "xxxClientId" and "xxxClientSecret" environment variables (e.g. "GoogleClientId", "GoogleClientSecret") where xxx is the securityScheme ID.
+
+If you specify the securityScheme ID as "Google" or "GitHub" (note the capital "G" and "H" in the latter), the generator will default to:
+- for Google, the [ASP.NET Core providers](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-2.2)
+- for GitHub, the [aspnet-contrib provider](https://www.nuget.org/packages/AspNet.Security.OAuth.GitHub/)
+
+For any other ID (e.g. "Facebook"), a [generic ASP.NET Core oAuth provider](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.oauthextensions.addoauth?view=aspnetcore-2.2) will be configured.
+
+See impl/AuthSchemes.fs for further details.
+
+NOTE - currently, authentication against ANY defined OAuth scheme will allow access to a handler (even if the scheme was not specified as a security scheme for the particular handler).
+This is on the TODO list.
+
+### API key
+
+API key authentication is supported via the (AspNet.Security.ApiKey.Providers package)[https://github.com/jamesharling/AspNet.Security.ApiKey.Providers].
+
+You must implement your own validation logic for the key in CustomHandlers.setApiKeyEvents.
+
+
+## TODO/currently unsupported
+
+- form request bodies (URL-encoded or multipart)
+- implicit oAuth
+- limit handler access to specified oAuth scheme when multiple oAuth schemes defined
+- XML content/response types
+- http authentication
+- testing header params
+
+## .openapi-generator-ignore
+
+It is recommended to add src/impl/** and the project's .fsproj file to the .openapi-generator-ignore file. 
+
+This will allow you to regenerate model, operation and parameter files without overriding your implementations of business logic, authentication, data layers, and so on.
+
+## Build and test the application
+
+### Windows
+
+Run the `build.bat` script in order to restore, build and test (if you've selected to include tests) the application:
+
+```
+> ./build.bat
+```
+
+### Linux/macOS
+
+Run the `build.sh` script in order to restore, build and test (if you've selected to include tests) the application:
+
+```
+$ ./build.sh
+```
+
+## Run the application
+
+After a successful build you can start the web application by executing the following command in your terminal:
+
+```
+dotnet run --project src/{{packageName}
+```
+
+After the application has started visit [http://localhost:5000](http://localhost:5000) in your preferred browser.
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/ServiceImpl.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/ServiceImpl.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..19d5ba0d877766038ba1f3b7dfe2467c8490eb1a
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/ServiceImpl.mustache
@@ -0,0 +1,44 @@
+namespace {{packageName}}
+{{#imports}}
+{{#import}}
+open {{import}}
+{{/import}}
+{{/imports}}
+open {{classname}}HandlerParams
+open {{classname}}ServiceInterface
+open System.Collections.Generic
+open System
+open Giraffe
+
+module {{classname}}ServiceImplementation =
+    
+    //#region Service implementation
+    type {{classname}}ServiceImpl() = 
+      interface I{{classname}}Service with
+      
+      {{#operations}}
+      {{#operation}}
+        member this.{{operationId}} ctx {{#allParams}}{{#-first}}args{{/-first}}{{/allParams}} =
+          {{#responses}}
+          {{#-first}}
+          {{#hasMore}}
+          if true then 
+          {{/hasMore}}
+          {{/-first}}
+          {{^-first}}
+          {{#hasMore}}
+          else if true then 
+          {{/hasMore}}
+          {{^hasMore}}
+          else
+          {{/hasMore}}
+          {{/-first}}
+            let content = "{{message}}" {{#dataType}}:> obj :?> {{{.}}} // this cast is obviously wrong, and is only intended to allow generated project to compile   {{/dataType}}
+            {{operationId}}{{#isDefault}}Default{{/isDefault}}StatusCode{{^isDefault}}{{code}}{{/isDefault}} { content = content }
+          {{/responses}}
+
+      {{/operation}}
+      {{/operations}}
+      //#endregion
+
+    let {{classname}}Service = {{classname}}ServiceImpl() :> I{{classname}}Service
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/ServiceInterface.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/ServiceInterface.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..68b14c67c26405e44a2e42b2f958a8eed3d4edb2
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/ServiceInterface.mustache
@@ -0,0 +1,17 @@
+namespace {{packageName}}
+open {{classname}}HandlerParams
+open System
+open Giraffe
+open Microsoft.AspNetCore.Http
+
+
+module {{classname}}ServiceInterface =
+    
+    //#region Service interface
+    type I{{classname}}Service = 
+    {{#operations}}
+    {{#operation}}
+      abstract member {{operationId}}:HttpContext {{#allParams}}{{#-first}}-> {{operationId}}Args{{/-first}}{{/allParams}}->{{operationId}}Result
+    {{/operation}}
+    {{/operations}}
+    //#endregion
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/TestHelper.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/TestHelper.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..c8437d7002ee0a17be18f400abe7d6a62d0055c0
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/TestHelper.mustache
@@ -0,0 +1,83 @@
+namespace {{packageName}}.Tests
+
+open System
+open System.Net
+open System.Net.Http
+open System.IO
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.TestHost
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Xunit
+open System.Text
+
+module TestHelper = 
+  // ---------------------------------
+  // Test server/client setup
+  // ---------------------------------
+
+  let createHost() =
+      WebHostBuilder()
+          .UseContentRoot(Directory.GetCurrentDirectory())
+          .Configure(Action<IApplicationBuilder> {{packageName}}.App.configureApp)
+          .ConfigureServices(Action<IServiceCollection> {{packageName}}.App.configureServices)
+
+  // ---------------------------------
+  // Helper functions
+  // ---------------------------------
+
+  let HttpGet (client : HttpClient) (path : string) =
+      client.GetAsync path
+      |> Async.AwaitTask
+      |> Async.RunSynchronously
+
+  let HttpPost (client: HttpClient) (path : string) content  =
+      client.PostAsync(path, content)
+      |> Async.AwaitTask
+      |> Async.RunSynchronously
+
+  let HttpPut (client: HttpClient)  (path : string) content =
+      client.PutAsync(path, content)
+      |> Async.AwaitTask
+      |> Async.RunSynchronously
+
+  let HttpDelete (client: HttpClient)  (path : string) =
+      client.DeleteAsync(path)
+      |> Async.AwaitTask
+      |> Async.RunSynchronously
+
+  let createRequest (method : HttpMethod) (path : string) =
+      let url = "http://127.0.0.1" + path
+      new HttpRequestMessage(method, url)
+
+  let addCookiesFromResponse (response : HttpResponseMessage)
+                            (request  : HttpRequestMessage) =
+      request.Headers.Add("Cookie", response.Headers.GetValues("Set-Cookie"))
+      request
+
+  let makeRequest (client : HttpClient) request =
+      request
+      |> client.SendAsync
+
+  let isStatus (code : HttpStatusCode) (response : HttpResponseMessage) =
+      Assert.Equal(code, response.StatusCode)
+      response
+
+  let isOfType (contentType : string) (response : HttpResponseMessage) =
+      Assert.Equal(contentType, response.Content.Headers.ContentType.MediaType)
+      response
+
+  let readText (response : HttpResponseMessage) =
+      response.Content.ReadAsStringAsync()
+      |> Async.AwaitTask            
+      |> Async.RunSynchronously
+
+  let shouldEqual expected actual =
+      Assert.Equal(expected, actual)
+
+  let getConverter mediaType = 
+    (fun (x:string) -> 
+      match mediaType with
+      | "application/x-www-form-urlencoded" -> raise (NotSupportedException()) // TODO - implement FormUrlEncodedContent
+      | _ -> x |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent)
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/build.bat.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/build.bat.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..5c6ac5b1d17535267fe76b38e82a5bac419c398d
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/build.bat.mustache
@@ -0,0 +1,3 @@
+dotnet restore src/{{packageName}}.fsproj
+dotnet build src/{{packageName}}.fsproj
+
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/build.sh.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/build.sh.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..2a97c479bc9eca5725114a9e8fd7dbf74ef66950
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/build.sh.mustache
@@ -0,0 +1,4 @@
+#!/bin/sh
+dotnet restore src/{{packageName}}.fsproj
+dotnet build src/{{packageName}}.fsproj
+
diff --git a/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/gitignore.mustache b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/gitignore.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..8d6278d820fe9d9fd71b43970ed11c8eeae747b0
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/fsharp-giraffe-server/gitignore.mustache
@@ -0,0 +1,5 @@
+**/node_modules
+**/bin/
+**/obj/
+**/dist/
+**/web.config
\ No newline at end of file
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/fsharp/FSharpServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/fsharp/FSharpServerCodegenTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..53df26bb806981ed0557798f0b40cde819b9f5bf
--- /dev/null
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/fsharp/FSharpServerCodegenTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
+ *
+ * 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.
+ */
+
+package org.openapitools.codegen.csharp;
+
+import org.openapitools.codegen.CodegenConstants;
+import org.openapitools.codegen.languages.AbstractFSharpCodegen;
+import org.openapitools.codegen.languages.FsharpGiraffeServerCodegen;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import com.google.common.collect.Sets;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.media.*;
+import io.swagger.v3.parser.util.SchemaTypeUtil;
+import org.openapitools.codegen.CodegenModel;
+import org.openapitools.codegen.CodegenProperty;
+import org.openapitools.codegen.DefaultCodegen;
+import org.openapitools.codegen.TestUtils;
+import io.swagger.parser.OpenAPIParser;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.servers.Server;
+import io.swagger.v3.parser.core.models.ParseOptions;
+import org.openapitools.codegen.MockDefaultGenerator.WrittenTemplateBasedFile;
+import java.util.*;
+
+@SuppressWarnings("static-method")
+public class FSharpServerCodegenTest {
+
+  @Test(description = "sort models according to dependency order")
+  public void testModelsAreSortedAccordingToDependencyOrder() throws Exception {
+        final AbstractFSharpCodegen codegen = new P_AbstractFSharpCodegen();
+
+        // parent
+        final CodegenModel parent = new CodegenModel();
+        CodegenProperty childProp = new CodegenProperty();
+        childProp.complexType = "child";
+        childProp.name = "child";
+        parent.setVars(Collections.singletonList(childProp));
+
+        final CodegenModel child = new CodegenModel();
+        CodegenProperty carProp = new CodegenProperty();
+        carProp.complexType = "car";
+        carProp.name = "car";
+        child.setVars(Collections.singletonList(carProp));
+
+        // child
+        final CodegenModel car = new CodegenModel();
+        CodegenProperty modelProp = new CodegenProperty();
+        modelProp.name = "model";
+        car.setVars(Collections.singletonList(modelProp));
+
+        Map<String, Object> models = new HashMap<String,Object>();
+        models.put("parent", Collections.singletonMap("models", Collections.singletonList(Collections.singletonMap("model", parent))));
+        models.put("child", Collections.singletonMap("models", Collections.singletonList(Collections.singletonMap("model", child))));
+        models.put("car", Collections.singletonMap("models", Collections.singletonList(Collections.singletonMap("model", car))));
+
+        Map<String,Object> sorted = codegen.postProcessDependencyOrders(models);
+        
+        Object[] keys = sorted.keySet().toArray();
+
+        Assert.assertEquals(keys[0], "car");
+        Assert.assertEquals(keys[1], "child");
+        Assert.assertEquals(keys[2], "parent");
+    }
+
+    @Test(description = "modify model imports to explicit set namespace and package name")
+    public void testModelImportsSpecifyNamespaceAndPackageName() throws Exception {
+          final AbstractFSharpCodegen codegen = new FsharpGiraffeServerCodegen();
+          codegen.setPackageName("MyNamespace");
+          codegen.setModelPackage("Model");
+          String modified = codegen.toModelImport("Foo");
+          Assert.assertEquals(modified, "MyNamespace.Model.Foo");          
+    }
+    
+    private static class P_AbstractFSharpCodegen extends AbstractFSharpCodegen {
+     
+    }
+}
diff --git a/samples/server/petstore/fsharp-giraffe/.openapi-generator-ignore b/samples/server/petstore/fsharp-giraffe/.openapi-generator-ignore
new file mode 100644
index 0000000000000000000000000000000000000000..7484ee590a3894506cf063799b885428f95a71be
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/.openapi-generator-ignore
@@ -0,0 +1,23 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
diff --git a/samples/server/petstore/fsharp-giraffe/.openapi-generator/VERSION b/samples/server/petstore/fsharp-giraffe/.openapi-generator/VERSION
new file mode 100644
index 0000000000000000000000000000000000000000..afa6365606414bf56f925745712166af5b4a2be0
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/.openapi-generator/VERSION
@@ -0,0 +1 @@
+4.0.0-SNAPSHOT
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/OpenAPITests.fsproj b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/OpenAPITests.fsproj
new file mode 100644
index 0000000000000000000000000000000000000000..a89851bcf145fa4ec438c7989c21fefbefdd058f
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/OpenAPITests.fsproj
@@ -0,0 +1,32 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp2.2</TargetFramework>
+    <AssemblyName>OpenAPI.Tests</AssemblyName>
+    <DebugType>portable</DebugType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.*" />
+    <PackageReference Include="Microsoft.AspNetCore.App" />
+    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.2.*" />
+    <PackageReference Include="xunit" Version="2.4.*" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.*" />
+    <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\OpenAPI\src\OpenAPI.fsproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Include="TestHelper.fs"/>
+    <Compile Include="PetApiTestsHelper.fs" />
+    <Compile Include="PetApiTests.fs" />
+    <Compile Include="StoreApiTestsHelper.fs" />
+    <Compile Include="StoreApiTests.fs" />
+    <Compile Include="UserApiTestsHelper.fs" />
+    <Compile Include="UserApiTests.fs" />
+  </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/PetApiTests.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/PetApiTests.fs
new file mode 100644
index 0000000000000000000000000000000000000000..8408a25602ee116f4f9a8be24b97e7bc1e820e6f
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/PetApiTests.fs
@@ -0,0 +1,295 @@
+namespace OpenAPI.Tests
+
+open System
+open System.Net
+open System.Net.Http
+open System.IO
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.TestHost
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Xunit
+open System.Text
+open Newtonsoft
+open TestHelper
+open PetApiHandlerTestsHelper
+open OpenAPI.PetApiHandler
+open OpenAPI.PetApiHandlerParams
+open OpenAPI.Model.ApiResponse
+open OpenAPI.Model.Pet
+
+module PetApiHandlerTests =
+
+  // ---------------------------------
+  // Tests
+  // ---------------------------------
+
+  [<Fact>]
+  let ``AddPet - Add a new pet to the store returns 405 where Invalid input`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet"
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/json", getAddPetExample "application/json").Add("application/xml", getAddPetExample "application/xml")
+      // or pass a body of type Pet
+      let body = obj() :?> Pet |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPost client path
+        |> isStatus (enum<HttpStatusCode>(405))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
+  [<Fact>]
+  let ``DeletePet - Deletes a pet returns 400 where Invalid pet value`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet/{petId}".Replace("petId", "ADDME")
+
+      HttpDelete client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``FindPetsByStatus - Finds Pets by status returns 200 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet/findByStatus" + "?status=ADDME"
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(200))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``FindPetsByStatus - Finds Pets by status returns 400 where Invalid status value`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet/findByStatus" + "?status=ADDME"
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``FindPetsByTags - Finds Pets by tags returns 200 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet/findByTags" + "?tags=ADDME&maxCount=ADDME"
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(200))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``FindPetsByTags - Finds Pets by tags returns 400 where Invalid tag value`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet/findByTags" + "?tags=ADDME&maxCount=ADDME"
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``GetPetById - Find pet by ID returns 200 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet/{petId}".Replace("petId", "ADDME")
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(200))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``GetPetById - Find pet by ID returns 400 where Invalid ID supplied`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet/{petId}".Replace("petId", "ADDME")
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``GetPetById - Find pet by ID returns 404 where Pet not found`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet/{petId}".Replace("petId", "ADDME")
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(404))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``UpdatePet - Update an existing pet returns 400 where Invalid ID supplied`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet"
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/json", getUpdatePetExample "application/json").Add("application/xml", getUpdatePetExample "application/xml")
+      // or pass a body of type Pet
+      let body = obj() :?> Pet |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPut client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
+  [<Fact>]
+  let ``UpdatePet - Update an existing pet returns 404 where Pet not found`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet"
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/json", getUpdatePetExample "application/json").Add("application/xml", getUpdatePetExample "application/xml")
+      // or pass a body of type Pet
+      let body = obj() :?> Pet |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPut client path
+        |> isStatus (enum<HttpStatusCode>(404))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
+  [<Fact>]
+  let ``UpdatePet - Update an existing pet returns 405 where Validation exception`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet"
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/json", getUpdatePetExample "application/json").Add("application/xml", getUpdatePetExample "application/xml")
+      // or pass a body of type Pet
+      let body = obj() :?> Pet |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPut client path
+        |> isStatus (enum<HttpStatusCode>(405))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
+  [<Fact>]
+  let ``UpdatePetWithForm - Updates a pet in the store with form data returns 405 where Invalid input`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet/{petId}".Replace("petId", "ADDME")
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/x-www-form-urlencoded", getUpdatePetWithFormExample "application/x-www-form-urlencoded")
+      // or pass a formform
+      let body = obj()  |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPost client path
+        |> isStatus (enum<HttpStatusCode>(405))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
+  [<Fact>]
+  let ``UploadFile - uploads an image returns 200 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/pet/{petId}/uploadImage".Replace("petId", "ADDME")
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("multipart/form-data", getUploadFileExample "multipart/form-data")
+      // or pass a formform
+      let body = obj()  |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPost client path
+        |> isStatus (enum<HttpStatusCode>(200))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/PetApiTestsHelper.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/PetApiTestsHelper.fs
new file mode 100644
index 0000000000000000000000000000000000000000..37fa8b6eb82568572236810e3fc4101349930037
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/PetApiTestsHelper.fs
@@ -0,0 +1,117 @@
+namespace OpenAPI.Tests
+
+open System
+open System.Net
+open System.Net.Http
+open System.IO
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.TestHost
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Xunit
+open System.Text
+open TestHelper
+open OpenAPI.PetApiHandler
+open OpenAPI.PetApiHandlerParams
+
+module PetApiHandlerTestsHelper =
+
+
+  let mutable AddPetExamples = Map.empty
+  let mutable AddPetBody = ""
+
+  AddPetBody <- WebUtility.HtmlDecode "{
+  &quot;photoUrls&quot; : [ &quot;photoUrls&quot;, &quot;photoUrls&quot; ],
+  &quot;name&quot; : &quot;doggie&quot;,
+  &quot;id&quot; : 0,
+  &quot;category&quot; : {
+    &quot;name&quot; : &quot;name&quot;,
+    &quot;id&quot; : 6
+  },
+  &quot;tags&quot; : [ {
+    &quot;name&quot; : &quot;name&quot;,
+    &quot;id&quot; : 1
+  }, {
+    &quot;name&quot; : &quot;name&quot;,
+    &quot;id&quot; : 1
+  } ],
+  &quot;status&quot; : &quot;available&quot;
+}"
+  AddPetExamples <- AddPetExamples.Add("application/json", AddPetBody)
+
+  AddPetBody <- WebUtility.HtmlDecode "&lt;Pet&gt;
+  &lt;id&gt;123456789&lt;/id&gt;
+  &lt;name&gt;doggie&lt;/name&gt;
+  &lt;photoUrls&gt;
+    &lt;photoUrls&gt;aeiou&lt;/photoUrls&gt;
+  &lt;/photoUrls&gt;
+  &lt;tags&gt;
+  &lt;/tags&gt;
+  &lt;status&gt;aeiou&lt;/status&gt;
+&lt;/Pet&gt;"
+  AddPetExamples <- AddPetExamples.Add("application/xml", AddPetBody)
+
+  let getAddPetExample mediaType =
+    AddPetExamples.[mediaType]
+      |> getConverter mediaType
+  ()
+
+  ()
+
+  ()
+
+  ()
+
+
+  let mutable UpdatePetExamples = Map.empty
+  let mutable UpdatePetBody = ""
+
+  UpdatePetBody <- WebUtility.HtmlDecode "{
+  &quot;photoUrls&quot; : [ &quot;photoUrls&quot;, &quot;photoUrls&quot; ],
+  &quot;name&quot; : &quot;doggie&quot;,
+  &quot;id&quot; : 0,
+  &quot;category&quot; : {
+    &quot;name&quot; : &quot;name&quot;,
+    &quot;id&quot; : 6
+  },
+  &quot;tags&quot; : [ {
+    &quot;name&quot; : &quot;name&quot;,
+    &quot;id&quot; : 1
+  }, {
+    &quot;name&quot; : &quot;name&quot;,
+    &quot;id&quot; : 1
+  } ],
+  &quot;status&quot; : &quot;available&quot;
+}"
+  UpdatePetExamples <- UpdatePetExamples.Add("application/json", UpdatePetBody)
+
+  UpdatePetBody <- WebUtility.HtmlDecode "&lt;Pet&gt;
+  &lt;id&gt;123456789&lt;/id&gt;
+  &lt;name&gt;doggie&lt;/name&gt;
+  &lt;photoUrls&gt;
+    &lt;photoUrls&gt;aeiou&lt;/photoUrls&gt;
+  &lt;/photoUrls&gt;
+  &lt;tags&gt;
+  &lt;/tags&gt;
+  &lt;status&gt;aeiou&lt;/status&gt;
+&lt;/Pet&gt;"
+  UpdatePetExamples <- UpdatePetExamples.Add("application/xml", UpdatePetBody)
+
+  let getUpdatePetExample mediaType =
+    UpdatePetExamples.[mediaType]
+      |> getConverter mediaType
+
+  let mutable UpdatePetWithFormExamples = Map.empty
+  let mutable UpdatePetWithFormBody = ""
+
+  let getUpdatePetWithFormExample mediaType =
+    UpdatePetWithFormExamples.[mediaType]
+      |> getConverter mediaType
+
+  let mutable UploadFileExamples = Map.empty
+  let mutable UploadFileBody = ""
+
+  let getUploadFileExample mediaType =
+    UploadFileExamples.[mediaType]
+      |> getConverter mediaType
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/StoreApiTests.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/StoreApiTests.fs
new file mode 100644
index 0000000000000000000000000000000000000000..744309736bb9df9d45c964e1e462e3a3e6559689
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/StoreApiTests.fs
@@ -0,0 +1,173 @@
+namespace OpenAPI.Tests
+
+open System
+open System.Net
+open System.Net.Http
+open System.IO
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.TestHost
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Xunit
+open System.Text
+open Newtonsoft
+open TestHelper
+open StoreApiHandlerTestsHelper
+open OpenAPI.StoreApiHandler
+open OpenAPI.StoreApiHandlerParams
+open System.Collections.Generic
+open OpenAPI.Model.Order
+
+module StoreApiHandlerTests =
+
+  // ---------------------------------
+  // Tests
+  // ---------------------------------
+
+  [<Fact>]
+  let ``DeleteOrder - Delete purchase order by ID returns 400 where Invalid ID supplied`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/store/order/{orderId}".Replace("orderId", "ADDME")
+
+      HttpDelete client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``DeleteOrder - Delete purchase order by ID returns 404 where Order not found`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/store/order/{orderId}".Replace("orderId", "ADDME")
+
+      HttpDelete client path
+        |> isStatus (enum<HttpStatusCode>(404))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``GetInventory - Returns pet inventories by status returns 200 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/store/inventory"
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(200))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``GetOrderById - Find purchase order by ID returns 200 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/store/order/{orderId}".Replace("orderId", "ADDME")
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(200))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``GetOrderById - Find purchase order by ID returns 400 where Invalid ID supplied`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/store/order/{orderId}".Replace("orderId", "ADDME")
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``GetOrderById - Find purchase order by ID returns 404 where Order not found`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/store/order/{orderId}".Replace("orderId", "ADDME")
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(404))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``PlaceOrder - Place an order for a pet returns 200 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/store/order"
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/json", getPlaceOrderExample "application/json")
+      // or pass a body of type Order
+      let body = obj() :?> Order |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPost client path
+        |> isStatus (enum<HttpStatusCode>(200))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
+  [<Fact>]
+  let ``PlaceOrder - Place an order for a pet returns 400 where Invalid Order`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/store/order"
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/json", getPlaceOrderExample "application/json")
+      // or pass a body of type Order
+      let body = obj() :?> Order |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPost client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/StoreApiTestsHelper.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/StoreApiTestsHelper.fs
new file mode 100644
index 0000000000000000000000000000000000000000..be130a9cf426d00b37b9420564120ff56ff80f5f
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/StoreApiTestsHelper.fs
@@ -0,0 +1,42 @@
+namespace OpenAPI.Tests
+
+open System
+open System.Net
+open System.Net.Http
+open System.IO
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.TestHost
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Xunit
+open System.Text
+open TestHelper
+open OpenAPI.StoreApiHandler
+open OpenAPI.StoreApiHandlerParams
+
+module StoreApiHandlerTestsHelper =
+
+  ()
+
+  ()
+
+  ()
+
+
+  let mutable PlaceOrderExamples = Map.empty
+  let mutable PlaceOrderBody = ""
+
+  PlaceOrderBody <- WebUtility.HtmlDecode "{
+  &quot;petId&quot; : 6,
+  &quot;quantity&quot; : 1,
+  &quot;id&quot; : 0,
+  &quot;shipDate&quot; : &quot;2000-01-23T04:56:07.000+00:00&quot;,
+  &quot;complete&quot; : false,
+  &quot;status&quot; : &quot;placed&quot;
+}"
+  PlaceOrderExamples <- PlaceOrderExamples.Add("application/json", PlaceOrderBody)
+
+  let getPlaceOrderExample mediaType =
+    PlaceOrderExamples.[mediaType]
+      |> getConverter mediaType
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/TestHelper.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/TestHelper.fs
new file mode 100644
index 0000000000000000000000000000000000000000..3b5be53c2eb17ebdd877396a404e288a7591825f
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/TestHelper.fs
@@ -0,0 +1,83 @@
+namespace OpenAPI.Tests
+
+open System
+open System.Net
+open System.Net.Http
+open System.IO
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.TestHost
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Xunit
+open System.Text
+
+module TestHelper = 
+  // ---------------------------------
+  // Test server/client setup
+  // ---------------------------------
+
+  let createHost() =
+      WebHostBuilder()
+          .UseContentRoot(Directory.GetCurrentDirectory())
+          .Configure(Action<IApplicationBuilder> OpenAPI.App.configureApp)
+          .ConfigureServices(Action<IServiceCollection> OpenAPI.App.configureServices)
+
+  // ---------------------------------
+  // Helper functions
+  // ---------------------------------
+
+  let HttpGet (client : HttpClient) (path : string) =
+      client.GetAsync path
+      |> Async.AwaitTask
+      |> Async.RunSynchronously
+
+  let HttpPost (client: HttpClient) (path : string) content  =
+      client.PostAsync(path, content)
+      |> Async.AwaitTask
+      |> Async.RunSynchronously
+
+  let HttpPut (client: HttpClient)  (path : string) content =
+      client.PutAsync(path, content)
+      |> Async.AwaitTask
+      |> Async.RunSynchronously
+
+  let HttpDelete (client: HttpClient)  (path : string) =
+      client.DeleteAsync(path)
+      |> Async.AwaitTask
+      |> Async.RunSynchronously
+
+  let createRequest (method : HttpMethod) (path : string) =
+      let url = "http://127.0.0.1" + path
+      new HttpRequestMessage(method, url)
+
+  let addCookiesFromResponse (response : HttpResponseMessage)
+                            (request  : HttpRequestMessage) =
+      request.Headers.Add("Cookie", response.Headers.GetValues("Set-Cookie"))
+      request
+
+  let makeRequest (client : HttpClient) request =
+      request
+      |> client.SendAsync
+
+  let isStatus (code : HttpStatusCode) (response : HttpResponseMessage) =
+      Assert.Equal(code, response.StatusCode)
+      response
+
+  let isOfType (contentType : string) (response : HttpResponseMessage) =
+      Assert.Equal(contentType, response.Content.Headers.ContentType.MediaType)
+      response
+
+  let readText (response : HttpResponseMessage) =
+      response.Content.ReadAsStringAsync()
+      |> Async.AwaitTask            
+      |> Async.RunSynchronously
+
+  let shouldEqual expected actual =
+      Assert.Equal(expected, actual)
+
+  let getConverter mediaType = 
+    (fun (x:string) -> 
+      match mediaType with
+      | "application/x-www-form-urlencoded" -> raise (NotSupportedException()) // TODO - implement FormUrlEncodedContent
+      | _ -> x |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent)
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/UserApiTests.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/UserApiTests.fs
new file mode 100644
index 0000000000000000000000000000000000000000..829336af83548f90ade4f07a5ef232d802e2e84d
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/UserApiTests.fs
@@ -0,0 +1,272 @@
+namespace OpenAPI.Tests
+
+open System
+open System.Net
+open System.Net.Http
+open System.IO
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.TestHost
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Xunit
+open System.Text
+open Newtonsoft
+open TestHelper
+open UserApiHandlerTestsHelper
+open OpenAPI.UserApiHandler
+open OpenAPI.UserApiHandlerParams
+open OpenAPI.Model.User
+
+module UserApiHandlerTests =
+
+  // ---------------------------------
+  // Tests
+  // ---------------------------------
+
+  [<Fact>]
+  let ``CreateUser - Create user returns 0 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user"
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/json", getCreateUserExample "application/json")
+      // or pass a body of type User
+      let body = obj() :?> User |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPost client path
+        |> isStatus (enum<HttpStatusCode>(0))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
+  [<Fact>]
+  let ``CreateUsersWithArrayInput - Creates list of users with given input array returns 0 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/createWithArray"
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/json", getCreateUsersWithArrayInputExample "application/json")
+      // or pass a body of type User[]
+      let body = obj() :?> User[] |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPost client path
+        |> isStatus (enum<HttpStatusCode>(0))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
+  [<Fact>]
+  let ``CreateUsersWithListInput - Creates list of users with given input array returns 0 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/createWithList"
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/json", getCreateUsersWithListInputExample "application/json")
+      // or pass a body of type User[]
+      let body = obj() :?> User[] |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPost client path
+        |> isStatus (enum<HttpStatusCode>(0))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
+  [<Fact>]
+  let ``DeleteUser - Delete user returns 400 where Invalid username supplied`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/{username}".Replace("username", "ADDME")
+
+      HttpDelete client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``DeleteUser - Delete user returns 404 where User not found`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/{username}".Replace("username", "ADDME")
+
+      HttpDelete client path
+        |> isStatus (enum<HttpStatusCode>(404))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``GetUserByName - Get user by user name returns 200 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/{username}".Replace("username", "ADDME")
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(200))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``GetUserByName - Get user by user name returns 400 where Invalid username supplied`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/{username}".Replace("username", "ADDME")
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``GetUserByName - Get user by user name returns 404 where User not found`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/{username}".Replace("username", "ADDME")
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(404))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``LoginUser - Logs user into the system returns 200 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/login" + "?username=ADDME&password=ADDME"
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(200))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``LoginUser - Logs user into the system returns 400 where Invalid username/password supplied`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/login" + "?username=ADDME&password=ADDME"
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``LogoutUser - Logs out current logged in user session returns 0 where successful operation`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/logout"
+
+      HttpGet client path
+        |> isStatus (enum<HttpStatusCode>(0))
+        |> readText
+        |> shouldEqual "TESTME"
+        |> ignore
+      }
+
+  [<Fact>]
+  let ``UpdateUser - Updated user returns 400 where Invalid user supplied`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/{username}".Replace("username", "ADDME")
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/json", getUpdateUserExample "application/json")
+      // or pass a body of type User
+      let body = obj() :?> User |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPut client path
+        |> isStatus (enum<HttpStatusCode>(400))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
+  [<Fact>]
+  let ``UpdateUser - Updated user returns 404 where User not found`` () =
+    task {
+      use server = new TestServer(createHost())
+      use client = server.CreateClient()
+
+      // add your setup code here
+
+      let path = "/v2/user/{username}".Replace("username", "ADDME")
+
+      // use an example requestBody provided by the spec
+      let examples = Map.empty.Add("application/json", getUpdateUserExample "application/json")
+      // or pass a body of type User
+      let body = obj() :?> User |> Newtonsoft.Json.JsonConvert.SerializeObject |> Encoding.UTF8.GetBytes |> MemoryStream |> StreamContent
+
+      body
+        |> HttpPut client path
+        |> isStatus (enum<HttpStatusCode>(404))
+        |> readText
+        |> shouldEqual "TESTME"
+      }
+
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/UserApiTestsHelper.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/UserApiTestsHelper.fs
new file mode 100644
index 0000000000000000000000000000000000000000..352c553e0c02b450d406f965b9e619c3ccb92c9a
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI.Tests/UserApiTestsHelper.fs
@@ -0,0 +1,85 @@
+namespace OpenAPI.Tests
+
+open System
+open System.Net
+open System.Net.Http
+open System.IO
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.TestHost
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Xunit
+open System.Text
+open TestHelper
+open OpenAPI.UserApiHandler
+open OpenAPI.UserApiHandlerParams
+
+module UserApiHandlerTestsHelper =
+
+
+  let mutable CreateUserExamples = Map.empty
+  let mutable CreateUserBody = ""
+
+  CreateUserBody <- WebUtility.HtmlDecode "{
+  &quot;firstName&quot; : &quot;firstName&quot;,
+  &quot;lastName&quot; : &quot;lastName&quot;,
+  &quot;password&quot; : &quot;password&quot;,
+  &quot;userStatus&quot; : 6,
+  &quot;phone&quot; : &quot;phone&quot;,
+  &quot;id&quot; : 0,
+  &quot;email&quot; : &quot;email&quot;,
+  &quot;username&quot; : &quot;username&quot;
+}"
+  CreateUserExamples <- CreateUserExamples.Add("application/json", CreateUserBody)
+
+  let getCreateUserExample mediaType =
+    CreateUserExamples.[mediaType]
+      |> getConverter mediaType
+
+  let mutable CreateUsersWithArrayInputExamples = Map.empty
+  let mutable CreateUsersWithArrayInputBody = ""
+
+  CreateUsersWithArrayInputBody <- WebUtility.HtmlDecode ""
+  CreateUsersWithArrayInputExamples <- CreateUsersWithArrayInputExamples.Add("", CreateUsersWithArrayInputBody)
+
+  let getCreateUsersWithArrayInputExample mediaType =
+    CreateUsersWithArrayInputExamples.[mediaType]
+      |> getConverter mediaType
+
+  let mutable CreateUsersWithListInputExamples = Map.empty
+  let mutable CreateUsersWithListInputBody = ""
+
+  CreateUsersWithListInputBody <- WebUtility.HtmlDecode ""
+  CreateUsersWithListInputExamples <- CreateUsersWithListInputExamples.Add("", CreateUsersWithListInputBody)
+
+  let getCreateUsersWithListInputExample mediaType =
+    CreateUsersWithListInputExamples.[mediaType]
+      |> getConverter mediaType
+  ()
+
+  ()
+
+  ()
+
+  ()
+
+
+  let mutable UpdateUserExamples = Map.empty
+  let mutable UpdateUserBody = ""
+
+  UpdateUserBody <- WebUtility.HtmlDecode "{
+  &quot;firstName&quot; : &quot;firstName&quot;,
+  &quot;lastName&quot; : &quot;lastName&quot;,
+  &quot;password&quot; : &quot;password&quot;,
+  &quot;userStatus&quot; : 6,
+  &quot;phone&quot; : &quot;phone&quot;,
+  &quot;id&quot; : 0,
+  &quot;email&quot; : &quot;email&quot;,
+  &quot;username&quot; : &quot;username&quot;
+}"
+  UpdateUserExamples <- UpdateUserExamples.Add("application/json", UpdateUserBody)
+
+  let getUpdateUserExample mediaType =
+    UpdateUserExamples.[mediaType]
+      |> getConverter mediaType
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/.gitignore b/samples/server/petstore/fsharp-giraffe/OpenAPI/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..8d6278d820fe9d9fd71b43970ed11c8eeae747b0
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/.gitignore
@@ -0,0 +1,5 @@
+**/node_modules
+**/bin/
+**/obj/
+**/dist/
+**/web.config
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/README.md b/samples/server/petstore/fsharp-giraffe/OpenAPI/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..42731ee69c6582040800723b96fd2f293d1dc71d
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/README.md
@@ -0,0 +1,186 @@
+# OpenAPI
+
+A [Giraffe](https://github.com/giraffe-fsharp/Giraffe) server stub for the OpenAPI package, created via the [OpenAPI generator](https://github.com/OpenAPITools/openapi-generator/).
+
+## Models
+
+The following models have been auto-generated from the provided OpenAPI schema:
+
+- model/ApiResponseModel.fs
+- model/UserModel.fs
+- model/TagModel.fs
+- model/CategoryModel.fs
+- model/OrderModel.fs
+- model/InlineObject1Model.fs
+- model/InlineObjectModel.fs
+- model/PetModel.fs
+
+## Operations
+
+Handlers have been auto-generated from the operations specified in the OpenAPI schema as follows:
+
+
+## Operation Parameters
+
+Types have been generated for the URL, query, form, header and cookie parameters passed to each handler in the following files:
+
+- api/PetApiHandlerParams.fs
+- api/StoreApiHandlerParams.fs
+- api/UserApiHandlerParams.fs
+
+## Service Interfaces
+
+Handlers will attempt to bind parameters to the applicable type and pass to a Service specific to that Handler. Service interfaces have been generated as follows:
+
+- api/PetApiServiceInterface.fs
+- api/StoreApiServiceInterface.fs
+- api/UserApiServiceInterface.fs
+
+Each Service contains functions for each [OperationId], each accepting a [OperationId]Params object that wraps the operation's parameters.
+
+If a requestBody is a ref type (i.e. a Model) or a single simple type, the operation parameter will be typed as the expected Model:
+
+`type AddPetBodyParams = Pet`
+
+If a requestBody is a simple type with named properties, the operation parameters will be typed to reflect those properties:
+
+`type AddFooBodyParams = {
+  Name:string;
+  Age:int
+}
+
+Each Service/operation function must accept the [OperationId]Params object and return a [OperationId]Result type. For example:
+
+`type AddPetArgs = { bodyParams:AddPetBodyParams }
+type IPetApiService = abstract member AddPet:HttpContext -> AddPetArgs->AddPetResult`
+
+[OperationId]Result is a discriminated union of all possible OpenAPI response types for that operation. 
+
+This means that service implementations can only return status codes that have been declared in the OpenAPI specification. 
+However, if the OpenAPI spec declares a default Response for an operation, the service can manually set the status code.
+
+For example:
+
+`type FindPetsByStatusDefaultStatusCodeResponse = { content:Pet[];}
+type FindPetsByStatusStatusCode400Response = { content:string; }
+type FindPetsByStatusResult = FindPetsByStatusDefaultStatusCode of FindPetsByStatusDefaultStatusCodeResponse | FindPetsByStatusStatusCode400 of FindPetsByStatusStatusCode400Response`
+
+## Note on nullable/optional properties 
+
+Currently, handler parameters and models do not distinguish between required properties and optional (or nullable) properties***.
+
+If a request body is missing a property, the parameter will be bound as null (and likewise, missing model properties will be serialized as null).
+
+This is only a temporary measure, and does need to be fixed to conform to the OpenAPI spec.
+
+Ideally, Option types would be used for all parameters not marked as required (or marked as nullable).
+
+This won't be possible until Giraffe supports binding option types in request bodies.
+
+This may cause problems with certain parameter types (e.g. map types) - please file an issue if you come across one.
+
+*** Except for DateTime, where properties not marked required are bound as Nullable<DateTime>.
+
+## Note on response codes for URL parameter binding
+
+Giraffe binds URL parameters by requiring compile-time format strings for routes  (e.g. "/foo/%s/%d) or known types (e.g. FooUrlParameters).
+
+With either approach, Giraffe will emit a 400 error response if parameter binding fails (e.g. if a string is provided where an int was expected).
+
+Currently, I am not aware of any way to customize this response, meaning if your OpenAPI schema specifies a different response code for an incorrectly formatted URL parameter, this will basically be ignored.
+
+To ensure your OpenAPI schema and implementation are consistent, I suggest ensuring that your schema only specifies return code 400 for incorrectly formatted URL parameters.
+
+If you have any suggestions for customizing this, please file an issue.
+
+## Service Implementations
+
+Stubbed service implementations of those interfaces have been generated as follows:
+
+- impl/PetApiService.fs
+- impl/StoreApiService.fs
+- impl/UserApiService.fs
+
+You should manually edit these files to implement your business logic.
+
+## Additional Handlers
+
+Additional handlers can be configured in the Customization.fs
+
+`let handlers : HttpHandler list = [
+    // insert your handlers here
+    GET >=> 
+      choose [
+        route "/login" >=> redirectToLogin
+        route "/logout" >=> logout
+      ]
+  ]`
+
+## Authentication
+
+### OAuth
+
+If your OpenAPI spec contains oAuth2 securitySchemes, these will have been auto-generated.
+
+To configure any of these, you must set the "xxxClientId" and "xxxClientSecret" environment variables (e.g. "GoogleClientId", "GoogleClientSecret") where xxx is the securityScheme ID.
+
+If you specify the securityScheme ID as "Google" or "GitHub" (note the capital "G" and "H" in the latter), the generator will default to:
+- for Google, the [ASP.NET Core providers](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-2.2)
+- for GitHub, the [aspnet-contrib provider](https://www.nuget.org/packages/AspNet.Security.OAuth.GitHub/)
+
+For any other ID (e.g. "Facebook"), a [generic ASP.NET Core oAuth provider](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.oauthextensions.addoauth?view=aspnetcore-2.2) will be configured.
+
+See impl/AuthSchemes.fs for further details.
+
+NOTE - currently, authentication against ANY defined OAuth scheme will allow access to a handler (even if the scheme was not specified as a security scheme for the particular handler).
+This is on the TODO list.
+
+### API key
+
+API key authentication is supported via the (AspNet.Security.ApiKey.Providers package)[https://github.com/jamesharling/AspNet.Security.ApiKey.Providers].
+
+You must implement your own validation logic for the key in CustomHandlers.setApiKeyEvents.
+
+
+## TODO/currently unsupported
+
+- form request bodies (URL-encoded or multipart)
+- implicit oAuth
+- limit handler access to specified oAuth scheme when multiple oAuth schemes defined
+- XML content/response types
+- http authentication
+- testing header params
+
+## .openapi-generator-ignore
+
+It is recommended to add src/impl/** and the project's .fsproj file to the .openapi-generator-ignore file. 
+
+This will allow you to regenerate model, operation and parameter files without overriding your implementations of business logic, authentication, data layers, and so on.
+
+## Build and test the application
+
+### Windows
+
+Run the `build.bat` script in order to restore, build and test (if you've selected to include tests) the application:
+
+```
+> ./build.bat
+```
+
+### Linux/macOS
+
+Run the `build.sh` script in order to restore, build and test (if you've selected to include tests) the application:
+
+```
+$ ./build.sh
+```
+
+## Run the application
+
+After a successful build you can start the web application by executing the following command in your terminal:
+
+```
+dotnet run --project src/{{packageName}
+```
+
+After the application has started visit [http://localhost:5000](http://localhost:5000) in your preferred browser.
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/build.bat b/samples/server/petstore/fsharp-giraffe/OpenAPI/build.bat
new file mode 100644
index 0000000000000000000000000000000000000000..8430438ab666855404c49f6389c84f11182ccaea
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/build.bat
@@ -0,0 +1,3 @@
+dotnet restore src/OpenAPI.fsproj
+dotnet build src/OpenAPI.fsproj
+
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/build.sh b/samples/server/petstore/fsharp-giraffe/OpenAPI/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..c5c866dafda78137e7db03cbfd81c44eb309b04b
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/build.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+dotnet restore src/OpenAPI.fsproj
+dotnet build src/OpenAPI.fsproj
+
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/OpenAPI.fsproj b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/OpenAPI.fsproj
new file mode 100644
index 0000000000000000000000000000000000000000..a5d1a1bebb10d8a070156b182278d7c6753164f4
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/OpenAPI.fsproj
@@ -0,0 +1,49 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+  <PropertyGroup>
+    <Description>OpenAPI</Description>
+    <Copyright>OpenAPI</Copyright>
+    <TargetFramework>netcoreapp2.2</TargetFramework>
+    <DebugType>portable</DebugType>
+    <EnableDefaultContentItems>false</EnableDefaultContentItems>
+    <RunWorkingDirectory>$(MSBuildThisFileDirectory)</RunWorkingDirectory>
+    <AssemblyName>OpenAPI</AssemblyName>
+    <PackageId>OpenAPI</PackageId>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.All" />
+    <PackageReference Include="Giraffe" Version="3.4.*" />
+    <PackageReference Include="TaskBuilder.fs" Version="2.1.*" />
+    <PackageReference Include="AspNet.Security.OAuth.GitHub" Version="2.0.1" />
+    <PackageReference Include="AspNet.Security.ApiKey.Providers" Version="1.1.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="openapi.yaml"/>
+    <Compile Include="helpers/Helpers.fs" />
+    <Compile Include="model/ApiResponse.fs" />
+    <Compile Include="model/User.fs" />
+    <Compile Include="model/Tag.fs" />
+    <Compile Include="model/Category.fs" />
+    <Compile Include="model/Order.fs" />
+    <Compile Include="model/InlineObject1.fs" />
+    <Compile Include="model/InlineObject.fs" />
+    <Compile Include="model/Pet.fs" />
+    <Compile Include="api/PetApiHandlerParams.fs" />
+    <Compile Include="api/PetApiServiceInterface.fs" />
+    <Compile Include="impl/PetApiService.fs" />
+    <Compile Include="api/PetApiHandler.fs" />
+    <Compile Include="api/StoreApiHandlerParams.fs" />
+    <Compile Include="api/StoreApiServiceInterface.fs" />
+    <Compile Include="impl/StoreApiService.fs" />
+    <Compile Include="api/StoreApiHandler.fs" />
+    <Compile Include="api/UserApiHandlerParams.fs" />
+    <Compile Include="api/UserApiServiceInterface.fs" />
+    <Compile Include="impl/UserApiService.fs" />
+    <Compile Include="api/UserApiHandler.fs" />
+    <Compile Include="impl/CustomHandlers.fs" />
+    <Compile Include="auth/AuthSchemes.fs" />
+    <Compile Include="Program.fs" />
+  </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/Program.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/Program.fs
new file mode 100644
index 0000000000000000000000000000000000000000..59c998288534460c23e4d46c5c99d379e73ceebb
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/Program.fs
@@ -0,0 +1,109 @@
+namespace OpenAPI
+
+open System
+open System.Net.Http
+open System.Security.Claims
+open System.Threading
+open Microsoft.AspNetCore
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.Http
+open Microsoft.AspNetCore.Http.Features
+open Microsoft.AspNetCore.Authentication
+open Microsoft.AspNetCore.Authentication.Cookies
+open Microsoft.Extensions.Configuration
+open Microsoft.Extensions.Logging
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open System.Diagnostics
+open Giraffe.GiraffeViewEngine
+open AspNet.Security.ApiKey.Providers
+
+open PetApiHandlerParams
+open StoreApiHandlerParams
+open UserApiHandlerParams
+open Giraffe
+
+module App =
+
+  // ---------------------------------
+  // Error handler
+  // ---------------------------------
+
+  let errorHandler (ex : Exception) (logger : ILogger) =
+    logger.LogError(EventId(), ex, "An unhandled exception has occurred while executing the request.")
+    clearResponse >=> setStatusCode 500 >=> text ex.Message
+
+  // ---------------------------------
+  // Web app
+  // ---------------------------------
+
+  let HttpGet = GET
+  let HttpPost = POST
+  let HttpPut = PUT
+  let HttpDelete = DELETE
+
+  let authFailure : HttpHandler = 
+    setStatusCode 401 >=> text "You must be authenticated to access this resource."
+
+  let webApp =
+    choose (CustomHandlers.handlers @ [
+      HttpPost >=> route "/v2/pet" >=> requiresAuthentication authFailure >=>  PetApiHandler.AddPet;
+      HttpDelete >=> routeBind<DeletePetPathParams> "/v2/pet/{petId}"  (fun x -> requiresAuthentication authFailure >=>  PetApiHandler.DeletePet x);
+      HttpGet >=> route "/v2/pet/findByStatus" >=> requiresAuthentication authFailure >=>  PetApiHandler.FindPetsByStatus;
+      HttpGet >=> route "/v2/pet/findByTags" >=> requiresAuthentication authFailure >=>  PetApiHandler.FindPetsByTags;
+      HttpGet >=> routeBind<GetPetByIdPathParams> "/v2/pet/{petId}"  (fun x -> challenge ApiKeyDefaults.AuthenticationScheme >=> requiresAuthentication authFailure >=>  PetApiHandler.GetPetById x);
+      HttpPut >=> route "/v2/pet" >=> requiresAuthentication authFailure >=>  PetApiHandler.UpdatePet;
+      HttpPost >=> routeBind<UpdatePetWithFormPathParams> "/v2/pet/{petId}"  (fun x -> requiresAuthentication authFailure >=>  PetApiHandler.UpdatePetWithForm x);
+      HttpPost >=> routeBind<UploadFilePathParams> "/v2/pet/{petId}/uploadImage"  (fun x -> requiresAuthentication authFailure >=>  PetApiHandler.UploadFile x);
+      HttpDelete >=> routeBind<DeleteOrderPathParams> "/v2/store/order/{orderId}"  (fun x ->  StoreApiHandler.DeleteOrder x);
+      HttpGet >=> route "/v2/store/inventory" >=> challenge ApiKeyDefaults.AuthenticationScheme >=> requiresAuthentication authFailure >=>  StoreApiHandler.GetInventory;
+      HttpGet >=> routeBind<GetOrderByIdPathParams> "/v2/store/order/{orderId}"  (fun x ->  StoreApiHandler.GetOrderById x);
+      HttpPost >=> route "/v2/store/order" >=>  StoreApiHandler.PlaceOrder;
+      HttpPost >=> route "/v2/user" >=> challenge ApiKeyDefaults.AuthenticationScheme >=> requiresAuthentication authFailure >=>  UserApiHandler.CreateUser;
+      HttpPost >=> route "/v2/user/createWithArray" >=> challenge ApiKeyDefaults.AuthenticationScheme >=> requiresAuthentication authFailure >=>  UserApiHandler.CreateUsersWithArrayInput;
+      HttpPost >=> route "/v2/user/createWithList" >=> challenge ApiKeyDefaults.AuthenticationScheme >=> requiresAuthentication authFailure >=>  UserApiHandler.CreateUsersWithListInput;
+      HttpDelete >=> routeBind<DeleteUserPathParams> "/v2/user/{username}"  (fun x -> challenge ApiKeyDefaults.AuthenticationScheme >=> requiresAuthentication authFailure >=>  UserApiHandler.DeleteUser x);
+      HttpGet >=> routeBind<GetUserByNamePathParams> "/v2/user/{username}"  (fun x ->  UserApiHandler.GetUserByName x);
+      HttpGet >=> route "/v2/user/login" >=>  UserApiHandler.LoginUser;
+      HttpGet >=> route "/v2/user/logout" >=> challenge ApiKeyDefaults.AuthenticationScheme >=> requiresAuthentication authFailure >=>  UserApiHandler.LogoutUser;
+      HttpPut >=> routeBind<UpdateUserPathParams> "/v2/user/{username}"  (fun x -> challenge ApiKeyDefaults.AuthenticationScheme >=> requiresAuthentication authFailure >=>  UserApiHandler.UpdateUser x);
+      RequestErrors.notFound (text "Not Found") 
+    ])
+  // ---------------------------------
+  // Main
+  // ---------------------------------
+
+  let configureApp (app : IApplicationBuilder) =
+    app.UseGiraffeErrorHandler(errorHandler)
+      .UseStaticFiles()
+      .UseAuthentication()
+      .UseResponseCaching() |> ignore
+    CustomHandlers.configureApp app |> ignore
+    app.UseGiraffe webApp |> ignore
+    
+
+  let configureServices (services : IServiceCollection) =
+    services
+          .AddResponseCaching()
+          .AddGiraffe()
+          |> AuthSchemes.configureServices      
+          |> CustomHandlers.configureServices services
+          |> ignore
+    services.AddDataProtection() |> ignore
+
+  let configureLogging (loggerBuilder : ILoggingBuilder) =
+    loggerBuilder.AddFilter(fun lvl -> lvl.Equals LogLevel.Error)
+                  .AddConsole()
+                  .AddDebug() |> ignore
+
+  [<EntryPoint>]
+  let main _ =
+    let builder = WebHost.CreateDefaultBuilder()
+                    .Configure(Action<IApplicationBuilder> configureApp)
+                    .ConfigureServices(configureServices)
+                    .ConfigureLogging(configureLogging)
+                    |> CustomHandlers.configureWebHost
+    builder.Build()
+            .Run()
+    0
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/PetApiHandler.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/PetApiHandler.fs
new file mode 100644
index 0000000000000000000000000000000000000000..634acdf18752457b102d2b27596a1f0f2d22a34f
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/PetApiHandler.fs
@@ -0,0 +1,179 @@
+namespace OpenAPI
+
+open System.Collections.Generic
+open Giraffe
+open Microsoft.AspNetCore.Http
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open PetApiHandlerParams
+open PetApiServiceInterface
+open PetApiServiceImplementation
+open OpenAPI.Model.ApiResponse
+open OpenAPI.Model.Pet
+
+module PetApiHandler = 
+
+    /// <summary>
+    /// 
+    /// </summary>
+
+    //#region AddPet
+    /// <summary>
+    /// Add a new pet to the store
+    /// </summary>
+
+    let AddPet  : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let! bodyParams = 
+            ctx.BindJsonAsync<AddPetBodyParams>()
+          let serviceArgs = {     bodyParams=bodyParams } : AddPetArgs
+          let result = PetApiService.AddPet ctx serviceArgs
+          return! (match result with 
+                      | AddPetStatusCode405 resolved ->
+                            setStatusCode 405 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region DeletePet
+    /// <summary>
+    /// Deletes a pet
+    /// </summary>
+
+    let DeletePet (pathParams:DeletePetPathParams) : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let headerParams = {
+              DeletePetHeaderParams.apiKey=ctx.TryGetRequestHeader "apiKey";
+          }
+          let serviceArgs = { headerParams=headerParams;   pathParams=pathParams;  } : DeletePetArgs
+          let result = PetApiService.DeletePet ctx serviceArgs
+          return! (match result with 
+                      | DeletePetStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region FindPetsByStatus
+    /// <summary>
+    /// Finds Pets by status
+    /// </summary>
+
+    let FindPetsByStatus  : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let queryParams = ctx.TryBindQueryString<FindPetsByStatusQueryParams>()
+          let serviceArgs = {  queryParams=queryParams;    } : FindPetsByStatusArgs
+          let result = PetApiService.FindPetsByStatus ctx serviceArgs
+          return! (match result with 
+                      | FindPetsByStatusDefaultStatusCode resolved ->
+                            setStatusCode 200 >=> json resolved.content 
+                      | FindPetsByStatusStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region FindPetsByTags
+    /// <summary>
+    /// Finds Pets by tags
+    /// </summary>
+
+    let FindPetsByTags  : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let queryParams = ctx.TryBindQueryString<FindPetsByTagsQueryParams>()
+          let serviceArgs = {  queryParams=queryParams;    } : FindPetsByTagsArgs
+          let result = PetApiService.FindPetsByTags ctx serviceArgs
+          return! (match result with 
+                      | FindPetsByTagsDefaultStatusCode resolved ->
+                            setStatusCode 200 >=> json resolved.content 
+                      | FindPetsByTagsStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region GetPetById
+    /// <summary>
+    /// Find pet by ID
+    /// </summary>
+
+    let GetPetById (pathParams:GetPetByIdPathParams) : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let serviceArgs = {    pathParams=pathParams;  } : GetPetByIdArgs
+          let result = PetApiService.GetPetById ctx serviceArgs
+          return! (match result with 
+                      | GetPetByIdDefaultStatusCode resolved ->
+                            setStatusCode 200 >=> json resolved.content 
+                      | GetPetByIdStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+                      | GetPetByIdStatusCode404 resolved ->
+                            setStatusCode 404 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region UpdatePet
+    /// <summary>
+    /// Update an existing pet
+    /// </summary>
+
+    let UpdatePet  : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let! bodyParams = 
+            ctx.BindJsonAsync<UpdatePetBodyParams>()
+          let serviceArgs = {     bodyParams=bodyParams } : UpdatePetArgs
+          let result = PetApiService.UpdatePet ctx serviceArgs
+          return! (match result with 
+                      | UpdatePetStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+                      | UpdatePetStatusCode404 resolved ->
+                            setStatusCode 404 >=> text resolved.content 
+                      | UpdatePetStatusCode405 resolved ->
+                            setStatusCode 405 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region UpdatePetWithForm
+    /// <summary>
+    /// Updates a pet in the store with form data
+    /// </summary>
+
+    let UpdatePetWithForm (pathParams:UpdatePetWithFormPathParams) : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let! formParams = ctx.TryBindFormAsync<UpdatePetWithFormFormParams>()
+          let serviceArgs = {   formParams=formParams; pathParams=pathParams;  } : UpdatePetWithFormArgs
+          let result = PetApiService.UpdatePetWithForm ctx serviceArgs
+          return! (match result with 
+                      | UpdatePetWithFormStatusCode405 resolved ->
+                            setStatusCode 405 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region UploadFile
+    /// <summary>
+    /// uploads an image
+    /// </summary>
+
+    let UploadFile (pathParams:UploadFilePathParams) : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let! formParams = ctx.TryBindFormAsync<UploadFileFormParams>()
+          let serviceArgs = {   formParams=formParams; pathParams=pathParams;  } : UploadFileArgs
+          let result = PetApiService.UploadFile ctx serviceArgs
+          return! (match result with 
+                      | UploadFileDefaultStatusCode resolved ->
+                            setStatusCode 200 >=> json resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+
+    
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/PetApiHandlerParams.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/PetApiHandlerParams.fs
new file mode 100644
index 0000000000000000000000000000000000000000..6bc290e74a7ab9d178c9774244f4698aca60ccd8
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/PetApiHandlerParams.fs
@@ -0,0 +1,213 @@
+namespace OpenAPI
+
+open OpenAPI.Model.ApiResponse
+open OpenAPI.Model.Pet
+open System.Collections.Generic
+open System
+
+module PetApiHandlerParams = 
+
+
+    //#region Body parameters
+    [<CLIMutable>]
+    type AddPetBodyParams = Pet 
+    //#endregion
+
+    
+    type AddPetStatusCode405Response = {
+      content:string;
+      
+    }
+    type AddPetResult = AddPetStatusCode405 of AddPetStatusCode405Response
+
+    type AddPetArgs = {
+      bodyParams:AddPetBodyParams
+    }
+    //#region Path parameters
+    [<CLIMutable>]
+    type DeletePetPathParams = {
+      petId : int64 ;
+    }
+    //#endregion
+
+    //#region Header parameters
+    [<CLIMutable>]
+    type DeletePetHeaderParams = {
+      apiKey : string option;
+    }
+    //#endregion
+
+    
+    type DeletePetStatusCode400Response = {
+      content:string;
+      
+    }
+    type DeletePetResult = DeletePetStatusCode400 of DeletePetStatusCode400Response
+
+    type DeletePetArgs = {
+      headerParams:DeletePetHeaderParams;
+      pathParams:DeletePetPathParams;
+    }
+
+    //#region Query parameters
+    [<CLIMutable>]
+    type FindPetsByStatusQueryParams = {
+      status : string[] ;
+      
+    }
+    //#endregion
+
+    
+    type FindPetsByStatusDefaultStatusCodeResponse = {
+      content:Pet[];
+      
+    }
+    
+    type FindPetsByStatusStatusCode400Response = {
+      content:string;
+      
+    }
+    type FindPetsByStatusResult = FindPetsByStatusDefaultStatusCode of FindPetsByStatusDefaultStatusCodeResponse|FindPetsByStatusStatusCode400 of FindPetsByStatusStatusCode400Response
+
+    type FindPetsByStatusArgs = {
+      queryParams:Result<FindPetsByStatusQueryParams,string>;
+    }
+
+    //#region Query parameters
+    [<CLIMutable>]
+    type FindPetsByTagsQueryParams = {
+      tags : string[] ;
+      
+
+      maxCount : int option;
+      
+    }
+    //#endregion
+
+    
+    type FindPetsByTagsDefaultStatusCodeResponse = {
+      content:Pet[];
+      
+    }
+    
+    type FindPetsByTagsStatusCode400Response = {
+      content:string;
+      
+    }
+    type FindPetsByTagsResult = FindPetsByTagsDefaultStatusCode of FindPetsByTagsDefaultStatusCodeResponse|FindPetsByTagsStatusCode400 of FindPetsByTagsStatusCode400Response
+
+    type FindPetsByTagsArgs = {
+      queryParams:Result<FindPetsByTagsQueryParams,string>;
+    }
+    //#region Path parameters
+    [<CLIMutable>]
+    type GetPetByIdPathParams = {
+      petId : int64 ;
+    }
+    //#endregion
+
+    
+    type GetPetByIdDefaultStatusCodeResponse = {
+      content:Pet;
+      
+    }
+    
+    type GetPetByIdStatusCode400Response = {
+      content:string;
+      
+    }
+    
+    type GetPetByIdStatusCode404Response = {
+      content:string;
+      
+    }
+    type GetPetByIdResult = GetPetByIdDefaultStatusCode of GetPetByIdDefaultStatusCodeResponse|GetPetByIdStatusCode400 of GetPetByIdStatusCode400Response|GetPetByIdStatusCode404 of GetPetByIdStatusCode404Response
+
+    type GetPetByIdArgs = {
+      pathParams:GetPetByIdPathParams;
+    }
+
+    //#region Body parameters
+    [<CLIMutable>]
+    type UpdatePetBodyParams = Pet 
+    //#endregion
+
+    
+    type UpdatePetStatusCode400Response = {
+      content:string;
+      
+    }
+    
+    type UpdatePetStatusCode404Response = {
+      content:string;
+      
+    }
+    
+    type UpdatePetStatusCode405Response = {
+      content:string;
+      
+    }
+    type UpdatePetResult = UpdatePetStatusCode400 of UpdatePetStatusCode400Response|UpdatePetStatusCode404 of UpdatePetStatusCode404Response|UpdatePetStatusCode405 of UpdatePetStatusCode405Response
+
+    type UpdatePetArgs = {
+      bodyParams:UpdatePetBodyParams
+    }
+    //#region Path parameters
+    [<CLIMutable>]
+    type UpdatePetWithFormPathParams = {
+      petId : int64 ;
+    }
+    //#endregion
+
+    //#region Form parameters
+    [<CLIMutable>]
+    type UpdatePetWithFormFormParams = {
+      name : string option;
+    //#endregion
+
+    //#region Form parameters
+      status : string option;
+    }
+    //#endregion
+
+    
+    type UpdatePetWithFormStatusCode405Response = {
+      content:string;
+      
+    }
+    type UpdatePetWithFormResult = UpdatePetWithFormStatusCode405 of UpdatePetWithFormStatusCode405Response
+
+    type UpdatePetWithFormArgs = {
+      pathParams:UpdatePetWithFormPathParams;
+      formParams:Result<UpdatePetWithFormFormParams,string>
+    }
+    //#region Path parameters
+    [<CLIMutable>]
+    type UploadFilePathParams = {
+      petId : int64 ;
+    }
+    //#endregion
+
+    //#region Form parameters
+    [<CLIMutable>]
+    type UploadFileFormParams = {
+      additionalMetadata : string option;
+    //#endregion
+
+    //#region Form parameters
+      file : System.IO.Stream option;
+    }
+    //#endregion
+
+    
+    type UploadFileDefaultStatusCodeResponse = {
+      content:ApiResponse;
+      
+    }
+    type UploadFileResult = UploadFileDefaultStatusCode of UploadFileDefaultStatusCodeResponse
+
+    type UploadFileArgs = {
+      pathParams:UploadFilePathParams;
+      formParams:Result<UploadFileFormParams,string>
+    }
+    
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/PetApiServiceInterface.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/PetApiServiceInterface.fs
new file mode 100644
index 0000000000000000000000000000000000000000..54cffde3f3c97c29e85271e48554ba2020c1958f
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/PetApiServiceInterface.fs
@@ -0,0 +1,20 @@
+namespace OpenAPI
+open PetApiHandlerParams
+open System
+open Giraffe
+open Microsoft.AspNetCore.Http
+
+
+module PetApiServiceInterface =
+    
+    //#region Service interface
+    type IPetApiService = 
+      abstract member AddPet:HttpContext -> AddPetArgs->AddPetResult
+      abstract member DeletePet:HttpContext -> DeletePetArgs->DeletePetResult
+      abstract member FindPetsByStatus:HttpContext -> FindPetsByStatusArgs->FindPetsByStatusResult
+      abstract member FindPetsByTags:HttpContext -> FindPetsByTagsArgs->FindPetsByTagsResult
+      abstract member GetPetById:HttpContext -> GetPetByIdArgs->GetPetByIdResult
+      abstract member UpdatePet:HttpContext -> UpdatePetArgs->UpdatePetResult
+      abstract member UpdatePetWithForm:HttpContext -> UpdatePetWithFormArgs->UpdatePetWithFormResult
+      abstract member UploadFile:HttpContext -> UploadFileArgs->UploadFileResult
+    //#endregion
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/StoreApiHandler.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/StoreApiHandler.fs
new file mode 100644
index 0000000000000000000000000000000000000000..91592d5af51f7b6c4da4213a5b60b6460194b0d1
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/StoreApiHandler.fs
@@ -0,0 +1,97 @@
+namespace OpenAPI
+
+open System.Collections.Generic
+open Giraffe
+open Microsoft.AspNetCore.Http
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open StoreApiHandlerParams
+open StoreApiServiceInterface
+open StoreApiServiceImplementation
+open System.Collections.Generic
+open OpenAPI.Model.Order
+
+module StoreApiHandler = 
+
+    /// <summary>
+    /// 
+    /// </summary>
+
+    //#region DeleteOrder
+    /// <summary>
+    /// Delete purchase order by ID
+    /// </summary>
+
+    let DeleteOrder (pathParams:DeleteOrderPathParams) : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let serviceArgs = {    pathParams=pathParams;  } : DeleteOrderArgs
+          let result = StoreApiService.DeleteOrder ctx serviceArgs
+          return! (match result with 
+                      | DeleteOrderStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+                      | DeleteOrderStatusCode404 resolved ->
+                            setStatusCode 404 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region GetInventory
+    /// <summary>
+    /// Returns pet inventories by status
+    /// </summary>
+
+    let GetInventory  : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let result = StoreApiService.GetInventory ctx 
+          return! (match result with 
+                      | GetInventoryDefaultStatusCode resolved ->
+                            setStatusCode 200 >=> json resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region GetOrderById
+    /// <summary>
+    /// Find purchase order by ID
+    /// </summary>
+
+    let GetOrderById (pathParams:GetOrderByIdPathParams) : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let serviceArgs = {    pathParams=pathParams;  } : GetOrderByIdArgs
+          let result = StoreApiService.GetOrderById ctx serviceArgs
+          return! (match result with 
+                      | GetOrderByIdDefaultStatusCode resolved ->
+                            setStatusCode 200 >=> json resolved.content 
+                      | GetOrderByIdStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+                      | GetOrderByIdStatusCode404 resolved ->
+                            setStatusCode 404 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region PlaceOrder
+    /// <summary>
+    /// Place an order for a pet
+    /// </summary>
+
+    let PlaceOrder  : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let! bodyParams = 
+            ctx.BindJsonAsync<PlaceOrderBodyParams>()
+          let serviceArgs = {     bodyParams=bodyParams } : PlaceOrderArgs
+          let result = StoreApiService.PlaceOrder ctx serviceArgs
+          return! (match result with 
+                      | PlaceOrderDefaultStatusCode resolved ->
+                            setStatusCode 200 >=> json resolved.content 
+                      | PlaceOrderStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+
+    
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/StoreApiHandlerParams.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/StoreApiHandlerParams.fs
new file mode 100644
index 0000000000000000000000000000000000000000..2c9226f8abfec8758fea31396d85a750051ce317
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/StoreApiHandlerParams.fs
@@ -0,0 +1,88 @@
+namespace OpenAPI
+
+open System.Collections.Generic
+open OpenAPI.Model.Order
+open System.Collections.Generic
+open System
+
+module StoreApiHandlerParams = 
+
+    //#region Path parameters
+    [<CLIMutable>]
+    type DeleteOrderPathParams = {
+      orderId : string ;
+    }
+    //#endregion
+
+    
+    type DeleteOrderStatusCode400Response = {
+      content:string;
+      
+    }
+    
+    type DeleteOrderStatusCode404Response = {
+      content:string;
+      
+    }
+    type DeleteOrderResult = DeleteOrderStatusCode400 of DeleteOrderStatusCode400Response|DeleteOrderStatusCode404 of DeleteOrderStatusCode404Response
+
+    type DeleteOrderArgs = {
+      pathParams:DeleteOrderPathParams;
+    }
+
+    
+    type GetInventoryDefaultStatusCodeResponse = {
+      content:IDictionary<string, int>;
+      
+    }
+    type GetInventoryResult = GetInventoryDefaultStatusCode of GetInventoryDefaultStatusCodeResponse
+
+    //#region Path parameters
+    [<CLIMutable>]
+    type GetOrderByIdPathParams = {
+      orderId : int64 ;
+    }
+    //#endregion
+
+    
+    type GetOrderByIdDefaultStatusCodeResponse = {
+      content:Order;
+      
+    }
+    
+    type GetOrderByIdStatusCode400Response = {
+      content:string;
+      
+    }
+    
+    type GetOrderByIdStatusCode404Response = {
+      content:string;
+      
+    }
+    type GetOrderByIdResult = GetOrderByIdDefaultStatusCode of GetOrderByIdDefaultStatusCodeResponse|GetOrderByIdStatusCode400 of GetOrderByIdStatusCode400Response|GetOrderByIdStatusCode404 of GetOrderByIdStatusCode404Response
+
+    type GetOrderByIdArgs = {
+      pathParams:GetOrderByIdPathParams;
+    }
+
+    //#region Body parameters
+    [<CLIMutable>]
+    type PlaceOrderBodyParams = Order 
+    //#endregion
+
+    
+    type PlaceOrderDefaultStatusCodeResponse = {
+      content:Order;
+      
+    }
+    
+    type PlaceOrderStatusCode400Response = {
+      content:string;
+      
+    }
+    type PlaceOrderResult = PlaceOrderDefaultStatusCode of PlaceOrderDefaultStatusCodeResponse|PlaceOrderStatusCode400 of PlaceOrderStatusCode400Response
+
+    type PlaceOrderArgs = {
+      bodyParams:PlaceOrderBodyParams
+    }
+    
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/StoreApiServiceInterface.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/StoreApiServiceInterface.fs
new file mode 100644
index 0000000000000000000000000000000000000000..d949973eb07afa8004dd042f4e3d8b0fad510c5c
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/StoreApiServiceInterface.fs
@@ -0,0 +1,16 @@
+namespace OpenAPI
+open StoreApiHandlerParams
+open System
+open Giraffe
+open Microsoft.AspNetCore.Http
+
+
+module StoreApiServiceInterface =
+    
+    //#region Service interface
+    type IStoreApiService = 
+      abstract member DeleteOrder:HttpContext -> DeleteOrderArgs->DeleteOrderResult
+      abstract member GetInventory:HttpContext ->GetInventoryResult
+      abstract member GetOrderById:HttpContext -> GetOrderByIdArgs->GetOrderByIdResult
+      abstract member PlaceOrder:HttpContext -> PlaceOrderArgs->PlaceOrderResult
+    //#endregion
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/UserApiHandler.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/UserApiHandler.fs
new file mode 100644
index 0000000000000000000000000000000000000000..6994e23974169af90b88c2486b790305a155cd41
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/UserApiHandler.fs
@@ -0,0 +1,173 @@
+namespace OpenAPI
+
+open System.Collections.Generic
+open Giraffe
+open Microsoft.AspNetCore.Http
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open UserApiHandlerParams
+open UserApiServiceInterface
+open UserApiServiceImplementation
+open OpenAPI.Model.User
+
+module UserApiHandler = 
+
+    /// <summary>
+    /// 
+    /// </summary>
+
+    //#region CreateUser
+    /// <summary>
+    /// Create user
+    /// </summary>
+
+    let CreateUser  : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let! bodyParams = 
+            ctx.BindJsonAsync<CreateUserBodyParams>()
+          let serviceArgs = {     bodyParams=bodyParams } : CreateUserArgs
+          let result = UserApiService.CreateUser ctx serviceArgs
+          return! (match result with 
+                      | CreateUserDefaultStatusCode resolved ->
+                            setStatusCode 0 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region CreateUsersWithArrayInput
+    /// <summary>
+    /// Creates list of users with given input array
+    /// </summary>
+
+    let CreateUsersWithArrayInput  : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let! bodyParams = 
+            ctx.BindJsonAsync<CreateUsersWithArrayInputBodyParams>()
+          let serviceArgs = {     bodyParams=bodyParams } : CreateUsersWithArrayInputArgs
+          let result = UserApiService.CreateUsersWithArrayInput ctx serviceArgs
+          return! (match result with 
+                      | CreateUsersWithArrayInputDefaultStatusCode resolved ->
+                            setStatusCode 0 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region CreateUsersWithListInput
+    /// <summary>
+    /// Creates list of users with given input array
+    /// </summary>
+
+    let CreateUsersWithListInput  : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let! bodyParams = 
+            ctx.BindJsonAsync<CreateUsersWithListInputBodyParams>()
+          let serviceArgs = {     bodyParams=bodyParams } : CreateUsersWithListInputArgs
+          let result = UserApiService.CreateUsersWithListInput ctx serviceArgs
+          return! (match result with 
+                      | CreateUsersWithListInputDefaultStatusCode resolved ->
+                            setStatusCode 0 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region DeleteUser
+    /// <summary>
+    /// Delete user
+    /// </summary>
+
+    let DeleteUser (pathParams:DeleteUserPathParams) : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let serviceArgs = {    pathParams=pathParams;  } : DeleteUserArgs
+          let result = UserApiService.DeleteUser ctx serviceArgs
+          return! (match result with 
+                      | DeleteUserStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+                      | DeleteUserStatusCode404 resolved ->
+                            setStatusCode 404 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region GetUserByName
+    /// <summary>
+    /// Get user by user name
+    /// </summary>
+
+    let GetUserByName (pathParams:GetUserByNamePathParams) : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let serviceArgs = {    pathParams=pathParams;  } : GetUserByNameArgs
+          let result = UserApiService.GetUserByName ctx serviceArgs
+          return! (match result with 
+                      | GetUserByNameDefaultStatusCode resolved ->
+                            setStatusCode 200 >=> json resolved.content 
+                      | GetUserByNameStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+                      | GetUserByNameStatusCode404 resolved ->
+                            setStatusCode 404 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region LoginUser
+    /// <summary>
+    /// Logs user into the system
+    /// </summary>
+
+    let LoginUser  : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let queryParams = ctx.TryBindQueryString<LoginUserQueryParams>()
+          let serviceArgs = {  queryParams=queryParams;    } : LoginUserArgs
+          let result = UserApiService.LoginUser ctx serviceArgs
+          return! (match result with 
+                      | LoginUserDefaultStatusCode resolved ->
+                            setStatusCode 200 >=> text resolved.content 
+                      | LoginUserStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region LogoutUser
+    /// <summary>
+    /// Logs out current logged in user session
+    /// </summary>
+
+    let LogoutUser  : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let result = UserApiService.LogoutUser ctx 
+          return! (match result with 
+                      | LogoutUserDefaultStatusCode resolved ->
+                            setStatusCode 0 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+    //#region UpdateUser
+    /// <summary>
+    /// Updated user
+    /// </summary>
+
+    let UpdateUser (pathParams:UpdateUserPathParams) : HttpHandler = 
+      fun (next : HttpFunc) (ctx : HttpContext) ->
+        task {
+          let! bodyParams = 
+            ctx.BindJsonAsync<UpdateUserBodyParams>()
+          let serviceArgs = {    pathParams=pathParams; bodyParams=bodyParams } : UpdateUserArgs
+          let result = UserApiService.UpdateUser ctx serviceArgs
+          return! (match result with 
+                      | UpdateUserStatusCode400 resolved ->
+                            setStatusCode 400 >=> text resolved.content 
+                      | UpdateUserStatusCode404 resolved ->
+                            setStatusCode 404 >=> text resolved.content 
+          ) next ctx
+        }
+    //#endregion
+
+
+    
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/UserApiHandlerParams.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/UserApiHandlerParams.fs
new file mode 100644
index 0000000000000000000000000000000000000000..42c51c93a664c892c08bcf07dc4ba0e4661fbf67
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/UserApiHandlerParams.fs
@@ -0,0 +1,169 @@
+namespace OpenAPI
+
+open OpenAPI.Model.User
+open System.Collections.Generic
+open System
+
+module UserApiHandlerParams = 
+
+
+    //#region Body parameters
+    [<CLIMutable>]
+    type CreateUserBodyParams = User 
+    //#endregion
+
+    
+    type CreateUserDefaultStatusCodeResponse = {
+      content:string;
+      
+    }
+    type CreateUserResult = CreateUserDefaultStatusCode of CreateUserDefaultStatusCodeResponse
+
+    type CreateUserArgs = {
+      bodyParams:CreateUserBodyParams
+    }
+
+    //#region Body parameters
+    [<CLIMutable>]
+    type CreateUsersWithArrayInputBodyParams = User[] 
+    //#endregion
+
+    
+    type CreateUsersWithArrayInputDefaultStatusCodeResponse = {
+      content:string;
+      
+    }
+    type CreateUsersWithArrayInputResult = CreateUsersWithArrayInputDefaultStatusCode of CreateUsersWithArrayInputDefaultStatusCodeResponse
+
+    type CreateUsersWithArrayInputArgs = {
+      bodyParams:CreateUsersWithArrayInputBodyParams
+    }
+
+    //#region Body parameters
+    [<CLIMutable>]
+    type CreateUsersWithListInputBodyParams = User[] 
+    //#endregion
+
+    
+    type CreateUsersWithListInputDefaultStatusCodeResponse = {
+      content:string;
+      
+    }
+    type CreateUsersWithListInputResult = CreateUsersWithListInputDefaultStatusCode of CreateUsersWithListInputDefaultStatusCodeResponse
+
+    type CreateUsersWithListInputArgs = {
+      bodyParams:CreateUsersWithListInputBodyParams
+    }
+    //#region Path parameters
+    [<CLIMutable>]
+    type DeleteUserPathParams = {
+      username : string ;
+    }
+    //#endregion
+
+    
+    type DeleteUserStatusCode400Response = {
+      content:string;
+      
+    }
+    
+    type DeleteUserStatusCode404Response = {
+      content:string;
+      
+    }
+    type DeleteUserResult = DeleteUserStatusCode400 of DeleteUserStatusCode400Response|DeleteUserStatusCode404 of DeleteUserStatusCode404Response
+
+    type DeleteUserArgs = {
+      pathParams:DeleteUserPathParams;
+    }
+    //#region Path parameters
+    [<CLIMutable>]
+    type GetUserByNamePathParams = {
+      username : string ;
+    }
+    //#endregion
+
+    
+    type GetUserByNameDefaultStatusCodeResponse = {
+      content:User;
+      
+    }
+    
+    type GetUserByNameStatusCode400Response = {
+      content:string;
+      
+    }
+    
+    type GetUserByNameStatusCode404Response = {
+      content:string;
+      
+    }
+    type GetUserByNameResult = GetUserByNameDefaultStatusCode of GetUserByNameDefaultStatusCodeResponse|GetUserByNameStatusCode400 of GetUserByNameStatusCode400Response|GetUserByNameStatusCode404 of GetUserByNameStatusCode404Response
+
+    type GetUserByNameArgs = {
+      pathParams:GetUserByNamePathParams;
+    }
+
+    //#region Query parameters
+    [<CLIMutable>]
+    type LoginUserQueryParams = {
+      username : string ;
+      
+
+      password : string ;
+      
+    }
+    //#endregion
+
+    
+    type LoginUserDefaultStatusCodeResponse = {
+      content:string;
+      
+    }
+    
+    type LoginUserStatusCode400Response = {
+      content:string;
+      
+    }
+    type LoginUserResult = LoginUserDefaultStatusCode of LoginUserDefaultStatusCodeResponse|LoginUserStatusCode400 of LoginUserStatusCode400Response
+
+    type LoginUserArgs = {
+      queryParams:Result<LoginUserQueryParams,string>;
+    }
+
+    
+    type LogoutUserDefaultStatusCodeResponse = {
+      content:string;
+      
+    }
+    type LogoutUserResult = LogoutUserDefaultStatusCode of LogoutUserDefaultStatusCodeResponse
+
+    //#region Path parameters
+    [<CLIMutable>]
+    type UpdateUserPathParams = {
+      username : string ;
+    }
+    //#endregion
+
+    //#region Body parameters
+    [<CLIMutable>]
+    type UpdateUserBodyParams = User 
+    //#endregion
+
+    
+    type UpdateUserStatusCode400Response = {
+      content:string;
+      
+    }
+    
+    type UpdateUserStatusCode404Response = {
+      content:string;
+      
+    }
+    type UpdateUserResult = UpdateUserStatusCode400 of UpdateUserStatusCode400Response|UpdateUserStatusCode404 of UpdateUserStatusCode404Response
+
+    type UpdateUserArgs = {
+      pathParams:UpdateUserPathParams;
+      bodyParams:UpdateUserBodyParams
+    }
+    
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/UserApiServiceInterface.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/UserApiServiceInterface.fs
new file mode 100644
index 0000000000000000000000000000000000000000..1fe15fe1c6c682213d0c18d74490a263e6788425
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/api/UserApiServiceInterface.fs
@@ -0,0 +1,20 @@
+namespace OpenAPI
+open UserApiHandlerParams
+open System
+open Giraffe
+open Microsoft.AspNetCore.Http
+
+
+module UserApiServiceInterface =
+    
+    //#region Service interface
+    type IUserApiService = 
+      abstract member CreateUser:HttpContext -> CreateUserArgs->CreateUserResult
+      abstract member CreateUsersWithArrayInput:HttpContext -> CreateUsersWithArrayInputArgs->CreateUsersWithArrayInputResult
+      abstract member CreateUsersWithListInput:HttpContext -> CreateUsersWithListInputArgs->CreateUsersWithListInputResult
+      abstract member DeleteUser:HttpContext -> DeleteUserArgs->DeleteUserResult
+      abstract member GetUserByName:HttpContext -> GetUserByNameArgs->GetUserByNameResult
+      abstract member LoginUser:HttpContext -> LoginUserArgs->LoginUserResult
+      abstract member LogoutUser:HttpContext ->LogoutUserResult
+      abstract member UpdateUser:HttpContext -> UpdateUserArgs->UpdateUserResult
+    //#endregion
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/auth/AuthSchemes.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/auth/AuthSchemes.fs
new file mode 100644
index 0000000000000000000000000000000000000000..5e1bc765d48f5cca12fba82f08a9a572376526a9
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/auth/AuthSchemes.fs
@@ -0,0 +1,88 @@
+namespace OpenAPI
+
+open Microsoft.AspNetCore.Authentication
+open Microsoft.AspNetCore.Authentication.Cookies
+open Microsoft.Extensions.DependencyInjection
+open Microsoft.AspNetCore.Http
+open Microsoft.AspNetCore.Authentication.OAuth
+open System
+open Giraffe
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Microsoft.Extensions.Configuration
+open AspNet.Security.ApiKey.Providers.Extensions
+open AspNet.Security.ApiKey.Providers.Events
+
+
+module AuthSchemes =
+
+  let accessDenied : HttpHandler = setStatusCode 401 >=> text "Access Denied"
+
+  let buildGoogle (builder:AuthenticationBuilder) name authorizationUrl scopes (settings:IConfiguration) = 
+    builder.AddGoogle(fun googleOptions -> CustomHandlers.setOAuthOptions "Google" googleOptions scopes settings)
+
+  let buildGitHub (builder:AuthenticationBuilder) name authorizationUrl scopes (settings:IConfiguration) = 
+    builder.AddGitHub(fun githubOptions -> CustomHandlers.setOAuthOptions "GitHub" githubOptions scopes settings)
+
+  let buildOAuth (builder:AuthenticationBuilder) (name:string) authorizationUrl scopes (settings:IConfiguration) = 
+    builder.AddOAuth(name, (fun (options:OAuthOptions) -> 
+      options.AuthorizationEndpoint <- authorizationUrl
+      options.TokenEndpoint <- settings.[name + "TokenUrl"]
+      options.CallbackPath <- PathString(settings.[name + "CallbackPath"])
+      CustomHandlers.setOAuthOptions "" options scopes settings
+  ))
+
+  let OAuthBuilders = Map.empty.Add("Google", buildGoogle).Add("GitHub", buildGitHub)
+
+  let checkEnvironment (settings:IConfiguration) name =
+    if (isNull settings.[name + "ClientId"]) then
+      raise (Exception(name + "ClientId is not set."))
+    else if (isNull settings.[name + "ClientSecret"]) then
+      raise (Exception((name + "ClientSecret is not set.")))
+
+  let getOAuthBuilder settings name =  
+    // check that "xxxClientId" and "xxxClientSecret" configuration variables have been set for all OAuth providers
+    checkEnvironment settings name
+    if OAuthBuilders.ContainsKey(name) then
+      OAuthBuilders.[name]
+    else
+      buildOAuth
+
+  let configureOAuth (settings:IConfiguration) services =
+    (getOAuthBuilder settings "petstore_auth") services "petstore_auth" "http://petstore.swagger.io/api/oauth/dialog" ["write:pets";"read:pets";] settings
+
+  let buildApiKeyAuth name (services:AuthenticationBuilder) =
+    services.AddApiKey(fun options -> 
+      options.Header <- name
+      options.HeaderKey <- String.Empty
+      let events = ApiKeyEvents()
+      options.Events <- CustomHandlers.setApiKeyEvents name events
+    )
+
+  let configureApiKeyAuth (settings:IConfiguration) services =
+    buildApiKeyAuth "api_key" services
+    raise (NotImplementedException("API key security scheme outside of header has not yet been implemented"))
+
+
+  let configureCookie (builder:AuthenticationBuilder) =
+      builder.AddCookie(CustomHandlers.cookieAuth)
+
+  let configureServices (services:IServiceCollection) = 
+    let serviceProvider = services.BuildServiceProvider()
+    let settings = serviceProvider.GetService<IConfiguration>()
+    services.AddAuthentication(fun o -> o.DefaultScheme <- CookieAuthenticationDefaults.AuthenticationScheme)
+    |> configureOAuth settings 
+    |> configureApiKeyAuth settings
+    |> configureCookie
+    
+  let (|||) v1 v2 = 
+      match v1 with 
+      | Some v -> v1
+      | None -> v2
+
+  // this can be replaced with ctx.GetCookieValue in Giraffe >= 3.6
+  let getCookieValue (ctx:HttpContext) (key : string)  =
+        match ctx.Request.Cookies.TryGetValue key with
+        | true , cookie -> Some cookie
+        | false, _ -> None
+
+  
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/helpers/Helpers.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/helpers/Helpers.fs
new file mode 100644
index 0000000000000000000000000000000000000000..6dcbe4ad94c0481a4cbc7c8c0bf7de0d50f66701
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/helpers/Helpers.fs
@@ -0,0 +1,12 @@
+  namespace OpenAPI
+
+  module Helpers = 
+
+    let (>=>) switch1 switch2 =
+      match switch1 with 
+        | Ok v1 -> 
+          match switch2 with 
+           | Ok v2 ->
+            Ok(v1, v2)
+            | Error e -> Error e          
+        | Error e -> Error e
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/CustomHandlers.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/CustomHandlers.fs
new file mode 100644
index 0000000000000000000000000000000000000000..ef85330bb0e8fc7719f1fd8776c856a60124dc5e
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/CustomHandlers.fs
@@ -0,0 +1,112 @@
+namespace OpenAPI
+
+open System
+open System.Net.Http
+open System.Security.Claims
+open System.Threading
+open Microsoft.AspNetCore
+open Microsoft.AspNetCore.Builder
+open Microsoft.AspNetCore.Hosting
+open Microsoft.AspNetCore.Http
+open Microsoft.AspNetCore.Http.Features
+open Microsoft.AspNetCore.Authentication
+open Microsoft.AspNetCore.Authentication.Cookies
+open Microsoft.Extensions.Configuration
+open Microsoft.Extensions.Logging
+open Microsoft.Extensions.DependencyInjection
+open FSharp.Control.Tasks.V2.ContextInsensitive
+open Giraffe
+open Giraffe.GiraffeViewEngine
+open Microsoft.AspNetCore.Authentication.OAuth
+open System.Threading.Tasks
+open AspNet.Security.ApiKey.Providers.Events
+
+module CustomHandlers = 
+
+  let cookieAuth (o : CookieAuthenticationOptions) =
+    do
+        o.Cookie.HttpOnly     <- true
+        o.Cookie.SecurePolicy <- CookieSecurePolicy.SameAsRequest
+        o.SlidingExpiration   <- true
+        o.ExpireTimeSpan      <- TimeSpan.FromDays 7.0
+
+  
+  let onCreatingTicket name (ctx:OAuthCreatingTicketContext) = 
+    task {
+        // implement post-authentication logic for oAuth handlers here
+       ()
+    } :> Task
+
+  let validateApiKey key = 
+    raise (NotImplementedException("API key validation must be implemented"))
+
+  let setApiKeyEvents name (events:ApiKeyEvents) = 
+    events.OnApiKeyValidated <- (fun ctx -> 
+        task {
+          // implement your validation/authentication logic for api key handlers here
+          if validateApiKey ctx.ApiKey then  
+          // to interact properly with Giraffe's handlers, you will need to manually set the identity 
+          // let claims = ...
+          // let identity = ClaimsIdentity(claims, ApiKeyDefaults.AuthenticationScheme)
+          // ctx.HttpContext.User <- ClaimsPrincipal([|identity|])
+            ctx.Success()
+        } :> Task
+    )
+    events
+
+  let setOAuthOptions name (options:OAuthOptions) scopes (settings:IConfiguration) = 
+    options.ClientId <- settings.[name + "ClientId"]
+    options.ClientSecret <- settings.[name + "ClientSecret"]
+    for scope in scopes do
+      options.Scope.Add scope
+
+    options.Events.OnCreatingTicket <- Func<OAuthCreatingTicketContext,Tasks.Task>(onCreatingTicket name)
+    match name with
+    | "Google" ->
+      ()  
+    | "GitHub" ->
+      ()
+    | _ -> 
+      ()
+  
+  let logout = signOut CookieAuthenticationDefaults.AuthenticationScheme >=> redirectTo false "/"
+
+  let loginView =
+    html [] [
+        head [] [
+            title [] [ str "Welcome" ]
+        ]
+        body [] [
+            h1 [] [ str "Welcome" ]
+            a [_href "/login-with-api_key"] [ str "Login with api_key" ]
+            a [_href "/login-with-auth_cookie"] [ str "Login with auth_cookie" ]
+            a [_href "/login-with-petstore_auth"] [ str "Login with petstore_auth" ]
+        ]
+    ]
+  
+  let redirectToLogin : HttpHandler = 
+    htmlView loginView    
+  
+  let handlers : HttpHandler list = [
+    // insert your handlers here
+    GET >=> 
+      choose [
+        route "/login" >=> redirectToLogin
+        route "/login-with-api_key" >=> challenge "api_key"
+        route "/login-with-auth_cookie" >=> challenge "auth_cookie"
+        route "/login-with-petstore_auth" >=> challenge "petstore_auth"
+        route "/logout" >=> logout
+      ]
+  ]
+
+  let configureWebHost (builder: IWebHostBuilder)  =
+      // builder
+      //  .UseContentRoot("content")
+      //  .UseWebRoot("static")
+      builder
+
+  let configureApp (app : IApplicationBuilder) =
+    app
+
+  let configureServices (services:IServiceCollection) (authBuilder:AuthenticationBuilder) = 
+    ()
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/PetApiService.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/PetApiService.fs
new file mode 100644
index 0000000000000000000000000000000000000000..948ee5ce71a229f70078bf666d93146a6fb99f0c
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/PetApiService.fs
@@ -0,0 +1,72 @@
+namespace OpenAPI
+open OpenAPI.Model.ApiResponse
+open OpenAPI.Model.Pet
+open PetApiHandlerParams
+open PetApiServiceInterface
+open System.Collections.Generic
+open System
+open Giraffe
+
+module PetApiServiceImplementation =
+    
+    //#region Service implementation
+    type PetApiServiceImpl() = 
+      interface IPetApiService with
+      
+        member this.AddPet ctx args =
+            let content = "Invalid input" 
+            AddPetStatusCode405 { content = content }
+
+        member this.DeletePet ctx args =
+            let content = "Invalid pet value" 
+            DeletePetStatusCode400 { content = content }
+
+        member this.FindPetsByStatus ctx args =
+          if true then 
+            let content = "successful operation" :> obj :?> Pet[] // this cast is obviously wrong, and is only intended to allow generated project to compile   
+            FindPetsByStatusDefaultStatusCode { content = content }
+          else
+            let content = "Invalid status value" 
+            FindPetsByStatusStatusCode400 { content = content }
+
+        member this.FindPetsByTags ctx args =
+          if true then 
+            let content = "successful operation" :> obj :?> Pet[] // this cast is obviously wrong, and is only intended to allow generated project to compile   
+            FindPetsByTagsDefaultStatusCode { content = content }
+          else
+            let content = "Invalid tag value" 
+            FindPetsByTagsStatusCode400 { content = content }
+
+        member this.GetPetById ctx args =
+          if true then 
+            let content = "successful operation" :> obj :?> Pet // this cast is obviously wrong, and is only intended to allow generated project to compile   
+            GetPetByIdDefaultStatusCode { content = content }
+          else if true then 
+            let content = "Invalid ID supplied" 
+            GetPetByIdStatusCode400 { content = content }
+          else
+            let content = "Pet not found" 
+            GetPetByIdStatusCode404 { content = content }
+
+        member this.UpdatePet ctx args =
+          if true then 
+            let content = "Invalid ID supplied" 
+            UpdatePetStatusCode400 { content = content }
+          else if true then 
+            let content = "Pet not found" 
+            UpdatePetStatusCode404 { content = content }
+          else
+            let content = "Validation exception" 
+            UpdatePetStatusCode405 { content = content }
+
+        member this.UpdatePetWithForm ctx args =
+            let content = "Invalid input" 
+            UpdatePetWithFormStatusCode405 { content = content }
+
+        member this.UploadFile ctx args =
+            let content = "successful operation" :> obj :?> ApiResponse // this cast is obviously wrong, and is only intended to allow generated project to compile   
+            UploadFileDefaultStatusCode { content = content }
+
+      //#endregion
+
+    let PetApiService = PetApiServiceImpl() :> IPetApiService
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/StoreApiService.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/StoreApiService.fs
new file mode 100644
index 0000000000000000000000000000000000000000..ccb52b6e6e2a064f2a8859ee05e99b79cd9ae998
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/StoreApiService.fs
@@ -0,0 +1,49 @@
+namespace OpenAPI
+open System.Collections.Generic
+open OpenAPI.Model.Order
+open StoreApiHandlerParams
+open StoreApiServiceInterface
+open System.Collections.Generic
+open System
+open Giraffe
+
+module StoreApiServiceImplementation =
+    
+    //#region Service implementation
+    type StoreApiServiceImpl() = 
+      interface IStoreApiService with
+      
+        member this.DeleteOrder ctx args =
+          if true then 
+            let content = "Invalid ID supplied" 
+            DeleteOrderStatusCode400 { content = content }
+          else
+            let content = "Order not found" 
+            DeleteOrderStatusCode404 { content = content }
+
+        member this.GetInventory ctx  =
+            let content = "successful operation" :> obj :?> IDictionary<string, int> // this cast is obviously wrong, and is only intended to allow generated project to compile   
+            GetInventoryDefaultStatusCode { content = content }
+
+        member this.GetOrderById ctx args =
+          if true then 
+            let content = "successful operation" :> obj :?> Order // this cast is obviously wrong, and is only intended to allow generated project to compile   
+            GetOrderByIdDefaultStatusCode { content = content }
+          else if true then 
+            let content = "Invalid ID supplied" 
+            GetOrderByIdStatusCode400 { content = content }
+          else
+            let content = "Order not found" 
+            GetOrderByIdStatusCode404 { content = content }
+
+        member this.PlaceOrder ctx args =
+          if true then 
+            let content = "successful operation" :> obj :?> Order // this cast is obviously wrong, and is only intended to allow generated project to compile   
+            PlaceOrderDefaultStatusCode { content = content }
+          else
+            let content = "Invalid Order" 
+            PlaceOrderStatusCode400 { content = content }
+
+      //#endregion
+
+    let StoreApiService = StoreApiServiceImpl() :> IStoreApiService
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/UserApiService.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/UserApiService.fs
new file mode 100644
index 0000000000000000000000000000000000000000..853d8a3d3aeff547bac3b7b6acaa9596e1851137
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/impl/UserApiService.fs
@@ -0,0 +1,68 @@
+namespace OpenAPI
+open OpenAPI.Model.User
+open UserApiHandlerParams
+open UserApiServiceInterface
+open System.Collections.Generic
+open System
+open Giraffe
+
+module UserApiServiceImplementation =
+    
+    //#region Service implementation
+    type UserApiServiceImpl() = 
+      interface IUserApiService with
+      
+        member this.CreateUser ctx args =
+            let content = "successful operation" 
+            CreateUserDefaultStatusCode { content = content }
+
+        member this.CreateUsersWithArrayInput ctx args =
+            let content = "successful operation" 
+            CreateUsersWithArrayInputDefaultStatusCode { content = content }
+
+        member this.CreateUsersWithListInput ctx args =
+            let content = "successful operation" 
+            CreateUsersWithListInputDefaultStatusCode { content = content }
+
+        member this.DeleteUser ctx args =
+          if true then 
+            let content = "Invalid username supplied" 
+            DeleteUserStatusCode400 { content = content }
+          else
+            let content = "User not found" 
+            DeleteUserStatusCode404 { content = content }
+
+        member this.GetUserByName ctx args =
+          if true then 
+            let content = "successful operation" :> obj :?> User // this cast is obviously wrong, and is only intended to allow generated project to compile   
+            GetUserByNameDefaultStatusCode { content = content }
+          else if true then 
+            let content = "Invalid username supplied" 
+            GetUserByNameStatusCode400 { content = content }
+          else
+            let content = "User not found" 
+            GetUserByNameStatusCode404 { content = content }
+
+        member this.LoginUser ctx args =
+          if true then 
+            let content = "successful operation" :> obj :?> string // this cast is obviously wrong, and is only intended to allow generated project to compile   
+            LoginUserDefaultStatusCode { content = content }
+          else
+            let content = "Invalid username/password supplied" 
+            LoginUserStatusCode400 { content = content }
+
+        member this.LogoutUser ctx  =
+            let content = "successful operation" 
+            LogoutUserDefaultStatusCode { content = content }
+
+        member this.UpdateUser ctx args =
+          if true then 
+            let content = "Invalid user supplied" 
+            UpdateUserStatusCode400 { content = content }
+          else
+            let content = "User not found" 
+            UpdateUserStatusCode404 { content = content }
+
+      //#endregion
+
+    let UserApiService = UserApiServiceImpl() :> IUserApiService
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/ApiResponse.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/ApiResponse.fs
new file mode 100644
index 0000000000000000000000000000000000000000..d9e30631e48161196ebf65365c153b1334a83887
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/ApiResponse.fs
@@ -0,0 +1,17 @@
+namespace OpenAPI.Model
+
+open System
+open System.Collections.Generic
+
+module ApiResponse = 
+
+  //#region ApiResponse
+
+
+  type ApiResponse = {
+    Code : int;
+    Type : string;
+    Message : string;
+  }
+  //#endregion
+  
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Category.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Category.fs
new file mode 100644
index 0000000000000000000000000000000000000000..0e3de7ff3b4c3e69713f921b001534037e823f21
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Category.fs
@@ -0,0 +1,16 @@
+namespace OpenAPI.Model
+
+open System
+open System.Collections.Generic
+
+module Category = 
+
+  //#region Category
+
+
+  type Category = {
+    Id : int64;
+    Name : string;
+  }
+  //#endregion
+  
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/InlineObject.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/InlineObject.fs
new file mode 100644
index 0000000000000000000000000000000000000000..6af33f3dbdda5409e02a8223bde8d85c03ae258d
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/InlineObject.fs
@@ -0,0 +1,16 @@
+namespace OpenAPI.Model
+
+open System
+open System.Collections.Generic
+
+module InlineObject = 
+
+  //#region InlineObject
+
+
+  type inline_object = {
+    Name : string;
+    Status : string;
+  }
+  //#endregion
+  
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/InlineObject1.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/InlineObject1.fs
new file mode 100644
index 0000000000000000000000000000000000000000..b3c704a10a764789d0e9c1db30bc45c764a1441c
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/InlineObject1.fs
@@ -0,0 +1,16 @@
+namespace OpenAPI.Model
+
+open System
+open System.Collections.Generic
+
+module InlineObject1 = 
+
+  //#region InlineObject1
+
+
+  type inline_object_1 = {
+    AdditionalMetadata : string;
+    File : System.IO.Stream;
+  }
+  //#endregion
+  
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Order.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Order.fs
new file mode 100644
index 0000000000000000000000000000000000000000..5718bf1b63c2474c15cf4726a47bfa31029b3cc1
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Order.fs
@@ -0,0 +1,23 @@
+namespace OpenAPI.Model
+
+open System
+open System.Collections.Generic
+
+module Order = 
+
+  //#region Order
+
+  //#region enums
+  type StatusEnum = PlacedEnum of string  |  ApprovedEnum of string  |  DeliveredEnum of string  
+  //#endregion
+
+  type Order = {
+    Id : int64;
+    PetId : int64;
+    Quantity : int;
+    ShipDate : Nullable<DateTime>;
+    Status : StatusEnum;
+    Complete : bool;
+  }
+  //#endregion
+  
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Pet.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Pet.fs
new file mode 100644
index 0000000000000000000000000000000000000000..720d699dce2b35df52949014de419310ee623eff
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Pet.fs
@@ -0,0 +1,25 @@
+namespace OpenAPI.Model
+
+open System
+open System.Collections.Generic
+open OpenAPI.Model.Category
+open OpenAPI.Model.Tag
+
+module Pet = 
+
+  //#region Pet
+
+  //#region enums
+  type StatusEnum = AvailableEnum of string  |  PendingEnum of string  |  SoldEnum of string  
+  //#endregion
+
+  type Pet = {
+    Id : int64;
+    Category : Category;
+    Name : string;
+    PhotoUrls : string[];
+    Tags : Tag[];
+    Status : StatusEnum;
+  }
+  //#endregion
+  
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Tag.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Tag.fs
new file mode 100644
index 0000000000000000000000000000000000000000..78814a494300cb4045b3346c0a3cb3d15a3f5d7e
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/Tag.fs
@@ -0,0 +1,16 @@
+namespace OpenAPI.Model
+
+open System
+open System.Collections.Generic
+
+module Tag = 
+
+  //#region Tag
+
+
+  type Tag = {
+    Id : int64;
+    Name : string;
+  }
+  //#endregion
+  
\ No newline at end of file
diff --git a/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/User.fs b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/User.fs
new file mode 100644
index 0000000000000000000000000000000000000000..1b80bea20dae7a72e49c4c52baf4d6d1a3f78b70
--- /dev/null
+++ b/samples/server/petstore/fsharp-giraffe/OpenAPI/src/model/User.fs
@@ -0,0 +1,22 @@
+namespace OpenAPI.Model
+
+open System
+open System.Collections.Generic
+
+module User = 
+
+  //#region User
+
+
+  type User = {
+    Id : int64;
+    Username : string;
+    FirstName : string;
+    LastName : string;
+    Email : string;
+    Password : string;
+    Phone : string;
+    UserStatus : int;
+  }
+  //#endregion
+  
\ No newline at end of file