forked from loafle/openapi-generator-original
Add Clojure client codegen
This commit is contained in:
parent
96f71e420a
commit
cd8cfc50ed
@ -10,10 +10,10 @@ import java.util.Set;
|
||||
|
||||
public class CodegenOperation {
|
||||
public final List<CodegenProperty> responseHeaders = new ArrayList<CodegenProperty>();
|
||||
public Boolean hasAuthMethods, hasConsumes, hasProduces, hasParams, returnTypeIsPrimitive,
|
||||
returnSimpleType, subresourceOperation, isMapContainer, isListContainer,
|
||||
hasMore = Boolean.TRUE, isMultipart, isResponseBinary = Boolean.FALSE,
|
||||
hasReference = Boolean.FALSE;
|
||||
public Boolean hasAuthMethods, hasConsumes, hasProduces, hasParams, hasOptionalParams,
|
||||
returnTypeIsPrimitive, returnSimpleType, subresourceOperation, isMapContainer,
|
||||
isListContainer, isMultipart, hasMore = Boolean.TRUE,
|
||||
isResponseBinary = Boolean.FALSE, hasReference = Boolean.FALSE;
|
||||
public String path, operationId, returnType, httpMethod, returnBaseType,
|
||||
returnContainer, summary, notes, baseName, defaultResponse;
|
||||
public List<Map<String, String>> consumes, produces;
|
||||
|
@ -1299,6 +1299,9 @@ public class DefaultCodegen {
|
||||
p.isFormParam = new Boolean(true);
|
||||
formParams.add(p.copy());
|
||||
}
|
||||
if (p.required == null || !p.required) {
|
||||
op.hasOptionalParams = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (String i : imports) {
|
||||
|
@ -0,0 +1,157 @@
|
||||
package io.swagger.codegen.languages;
|
||||
|
||||
import io.swagger.codegen.CodegenConfig;
|
||||
import io.swagger.codegen.CodegenConstants;
|
||||
import io.swagger.codegen.CodegenOperation;
|
||||
import io.swagger.codegen.CodegenType;
|
||||
import io.swagger.codegen.DefaultCodegen;
|
||||
import io.swagger.codegen.SupportingFile;
|
||||
import io.swagger.models.Info;
|
||||
import io.swagger.models.Swagger;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
public class ClojureClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
private static final String PROJECT_NAME = "projectName";
|
||||
private static final String PROJECT_DESCRIPTION = "projectDescription";
|
||||
private static final String PROJECT_VERSION = "projectVersion";
|
||||
private static final String BASE_NAMESPACE = "baseNamespace";
|
||||
|
||||
protected String projectName = null;
|
||||
protected String projectDescription = null;
|
||||
protected String projectVersion = null;
|
||||
protected String sourceFolder = "src";
|
||||
|
||||
public ClojureClientCodegen() {
|
||||
super();
|
||||
outputFolder = "generated-code" + File.separator + "clojure";
|
||||
apiTemplateFiles.put("api.mustache", ".clj");
|
||||
embeddedTemplateDir = templateDir = "clojure";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getTag() {
|
||||
return CodegenType.CLIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "clojure";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelp() {
|
||||
return "Generates a Clojure client library.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessSwagger(Swagger swagger) {
|
||||
super.preprocessSwagger(swagger);
|
||||
|
||||
if (additionalProperties.containsKey(PROJECT_NAME)) {
|
||||
projectName = ((String) additionalProperties.get(PROJECT_NAME));
|
||||
}
|
||||
if (additionalProperties.containsKey(PROJECT_DESCRIPTION)) {
|
||||
projectDescription = ((String) additionalProperties.get(PROJECT_DESCRIPTION));
|
||||
}
|
||||
if (additionalProperties.containsKey(PROJECT_VERSION)) {
|
||||
projectVersion = ((String) additionalProperties.get(PROJECT_VERSION));
|
||||
}
|
||||
|
||||
if (swagger.getInfo() != null) {
|
||||
Info info = swagger.getInfo();
|
||||
if (projectName == null && info.getTitle() != null) {
|
||||
// when projectName is not specified, generate it from info.title
|
||||
projectName = dashize(info.getTitle());
|
||||
}
|
||||
if (projectVersion == null && info.getVersion() != null) {
|
||||
// when projectVersion is not specified, use info.version
|
||||
projectVersion = info.getVersion();
|
||||
}
|
||||
if (projectDescription == null && info.getDescription() != null) {
|
||||
// when projectDescription is not specified, use info.description
|
||||
projectDescription = info.getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
// default values
|
||||
if (projectName == null) {
|
||||
projectName = "swagger-clj-client";
|
||||
}
|
||||
if (projectVersion == null) {
|
||||
projectVersion = "1.0.0";
|
||||
}
|
||||
if (projectDescription == null) {
|
||||
projectDescription = "Client library of " + projectName;
|
||||
}
|
||||
|
||||
final String baseNamespace = dashize(projectName);
|
||||
apiPackage = baseNamespace + ".api";
|
||||
|
||||
additionalProperties.put(PROJECT_NAME, projectName);
|
||||
additionalProperties.put(PROJECT_DESCRIPTION, escapeText(projectDescription));
|
||||
additionalProperties.put(PROJECT_VERSION, projectVersion);
|
||||
additionalProperties.put(BASE_NAMESPACE, baseNamespace);
|
||||
additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage);
|
||||
|
||||
final String baseNamespaceFolder = sourceFolder + File.separator + namespaceToFolder(baseNamespace);
|
||||
supportingFiles.add(new SupportingFile("project.mustache", "", "project.clj"));
|
||||
supportingFiles.add(new SupportingFile("core.mustache", baseNamespaceFolder, "core.clj"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiFileFolder() {
|
||||
return outputFolder + File.separator + sourceFolder + File.separator + namespaceToFolder(apiPackage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toOperationId(String operationId) {
|
||||
// throw exception if method name is empty
|
||||
if (StringUtils.isEmpty(operationId)) {
|
||||
throw new RuntimeException("Empty method/operation name (operationId) not allowed");
|
||||
}
|
||||
|
||||
return dashize(sanitizeName(operationId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toApiName(String name) {
|
||||
return dashize(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toParamName(String name) {
|
||||
return toVarName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toVarName(String name) {
|
||||
name = name.replaceAll("[^a-zA-Z0-9_-]+", "");
|
||||
name = dashize(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> postProcessOperations(Map<String, Object> operations) {
|
||||
Map<String, Object> objs = (Map<String, Object>) operations.get("operations");
|
||||
List<CodegenOperation> ops = (List<CodegenOperation>) objs.get("operation");
|
||||
for (CodegenOperation op : ops) {
|
||||
// Convert httpMethod to lower case, e.g. "get", "post"
|
||||
op.httpMethod = op.httpMethod.toLowerCase();
|
||||
}
|
||||
return operations;
|
||||
}
|
||||
|
||||
protected String namespaceToFolder(String ns) {
|
||||
return ns.replace(".", File.separator).replace("-", "_");
|
||||
}
|
||||
|
||||
protected String dashize(String s) {
|
||||
return underscore(s).replaceAll("[_ ]", "-");
|
||||
}
|
||||
}
|
@ -28,3 +28,4 @@ io.swagger.codegen.languages.TypeScriptAngularClientCodegen
|
||||
io.swagger.codegen.languages.TypeScriptNodeClientCodegen
|
||||
io.swagger.codegen.languages.AkkaScalaClientCodegen
|
||||
io.swagger.codegen.languages.CsharpDotNet2ClientCodegen
|
||||
io.swagger.codegen.languages.ClojureClientCodegen
|
||||
|
@ -0,0 +1,18 @@
|
||||
(ns {{{package}}}.{{{classname}}}
|
||||
(:require [{{{projectName}}}.core :refer [call-api check-required-params]]))
|
||||
{{#operations}}{{#operation}}
|
||||
(defn {{{nickname}}}
|
||||
"{{{summary}}}{{#notes}}
|
||||
{{{notes}}}{{/notes}}"{{#hasOptionalParams}}
|
||||
([{{#allParams}}{{#required}}{{{paramName}}} {{/required}}{{/allParams}}] ({{{nickname}}}{{#allParams}} {{#required}}{{{paramName}}}{{/required}}{{/allParams}} nil)){{/hasOptionalParams}}
|
||||
{{#hasOptionalParams}}({{/hasOptionalParams}}[{{#allParams}}{{#required}}{{{paramName}}} {{/required}}{{/allParams}}{{#hasOptionalParams}} {:keys [{{#allParams}}{{^required}}{{{paramName}}} {{/required}}{{/allParams}}]}{{/hasOptionalParams}}]{{#hasRequiredParams}}
|
||||
{{#hasOptionalParams}} {{/hasOptionalParams}}(check-required-params{{#allParams}}{{#required}} {{{paramName}}}{{/required}}{{/allParams}}){{/hasRequiredParams}}
|
||||
{{#hasOptionalParams}} {{/hasOptionalParams}}(call-api "{{{path}}}" :{{{httpMethod}}}
|
||||
{{#hasOptionalParams}} {{/hasOptionalParams}} {:path-params { {{#pathParams}}"{{{baseName}}}" {{{paramName}}} {{/pathParams}} }
|
||||
{{#hasOptionalParams}} {{/hasOptionalParams}} :header-params { {{#headerParams}}"{{{baseName}}}" {{{paramName}}} {{/headerParams}} }
|
||||
{{#hasOptionalParams}} {{/hasOptionalParams}} :query-params { {{#queryParams}}"{{{baseName}}}" {{{paramName}}} {{/queryParams}} }
|
||||
{{#hasOptionalParams}} {{/hasOptionalParams}} :form-params { {{#formParams}}"{{{baseName}}}" {{{paramName}}} {{/formParams}} }{{#bodyParam}}
|
||||
{{#hasOptionalParams}} {{/hasOptionalParams}} :body-param {{{paramName}}}{{/bodyParam}}
|
||||
{{#hasOptionalParams}} {{/hasOptionalParams}} :content-types [{{#consumes}}"{{mediaType}}"{{#hasMore}} {{/hasMore}}{{/consumes}}]
|
||||
{{#hasOptionalParams}} {{/hasOptionalParams}} :accepts [{{#produces}}"{{mediaType}}"{{#hasMore}} {{/hasMore}}{{/produces}}]})){{#hasOptionalParams}}){{/hasOptionalParams}}
|
||||
{{/operation}}{{/operations}}
|
151
modules/swagger-codegen/src/main/resources/clojure/core.mustache
Normal file
151
modules/swagger-codegen/src/main/resources/clojure/core.mustache
Normal file
@ -0,0 +1,151 @@
|
||||
(ns {{{baseNamespace}}}.core
|
||||
(:require [cheshire.core :refer [parse-string]]
|
||||
[clojure.string :as str]
|
||||
[clj-http.client :as client])
|
||||
(:import (com.fasterxml.jackson.core JsonParseException)
|
||||
(java.util Date TimeZone)
|
||||
(java.text SimpleDateFormat)))
|
||||
|
||||
(def default-api-context
|
||||
"Default API context."
|
||||
{:base-url "http://petstore.swagger.io/v2"
|
||||
:date-format "yyyy-MM-dd"
|
||||
:datetime-format "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"
|
||||
:debug false})
|
||||
|
||||
(def ^:dynamic *api-context*
|
||||
"Dynamic API context to be applied in API calls."
|
||||
default-api-context)
|
||||
|
||||
(defmacro with-api-context
|
||||
"A helper macro to wrap *api-context* with default values."
|
||||
[context & body]
|
||||
`(binding [*api-context* (merge *api-context* ~context)]
|
||||
~@body))
|
||||
|
||||
(defmacro check-required-params
|
||||
"Throw exception if the given parameter value is nil."
|
||||
[& params]
|
||||
(->> params
|
||||
(map (fn [p]
|
||||
`(if (nil? ~p)
|
||||
(throw (IllegalArgumentException. ~(str "The parameter \"" p "\" is required"))))))
|
||||
(list* 'do)))
|
||||
|
||||
(defn- make-date-format
|
||||
([format-str] (make-date-format format-str nil))
|
||||
([format-str time-zone]
|
||||
(let [date-format (SimpleDateFormat. format-str)]
|
||||
(when time-zone
|
||||
(.setTimeZone date-format (TimeZone/getTimeZone time-zone)))
|
||||
date-format)))
|
||||
|
||||
(defn format-date
|
||||
"Format the given Date object with the :date-format defined in *api-options*.
|
||||
NOTE: The UTC time zone is used."
|
||||
[^Date date]
|
||||
(let [{:keys [date-format]} *api-context*]
|
||||
(-> (make-date-format date-format "UTC")
|
||||
(.format date))))
|
||||
|
||||
(defn parse-date
|
||||
"Parse the given string to a Date object with the :date-format defined in *api-options*.
|
||||
NOTE: The UTC time zone is used."
|
||||
[^String s]
|
||||
(let [{:keys [date-format]} *api-context*]
|
||||
(-> (make-date-format date-format "UTC")
|
||||
(.parse s))))
|
||||
|
||||
(defn format-datetime
|
||||
"Format the given Date object with the :datetime-format defined in *api-options*.
|
||||
NOTE: The system's default time zone is used when not provided."
|
||||
([^Date date] (format-datetime date nil))
|
||||
([^Date date ^String time-zone]
|
||||
(let [{:keys [datetime-format]} *api-context*]
|
||||
(-> (make-date-format datetime-format time-zone)
|
||||
(.format date)))))
|
||||
|
||||
(defn parse-datetime
|
||||
"Parse the given string to a Date object with the :datetime-format defined in *api-options*.
|
||||
NOTE: The system's default time zone is used when not provided."
|
||||
([^String s] (parse-datetime s nil))
|
||||
([^String s ^String time-zone]
|
||||
(let [{:keys [datetime-format]} *api-context*]
|
||||
(-> (make-date-format datetime-format time-zone)
|
||||
(.parse s)))))
|
||||
|
||||
(defn param-to-str [param]
|
||||
"Format the given parameter value to string."
|
||||
(cond
|
||||
(instance? Date param) (format-datetime param)
|
||||
(sequential? param) (str/join "," param)
|
||||
:else (str param)))
|
||||
|
||||
(defn make-url
|
||||
"Make full URL by adding base URL and filling path parameters."
|
||||
[path path-params]
|
||||
(let [path (reduce (fn [p [k v]]
|
||||
(str/replace p (re-pattern (str "\\{" k "\\}")) (param-to-str v)))
|
||||
path
|
||||
path-params)]
|
||||
(str (:base-url *api-context*) path)))
|
||||
|
||||
(defn normalize-params
|
||||
"Normalize parameters values: remove nils, format to string with `param-to-str`."
|
||||
[params]
|
||||
(reduce (fn [result [k v]]
|
||||
(if (nil? v)
|
||||
result
|
||||
(assoc result k (if (sequential? v)
|
||||
(map param-to-str v)
|
||||
(param-to-str v)))))
|
||||
{}
|
||||
params))
|
||||
|
||||
(defn json-mime? [mime]
|
||||
"Check if the given MIME is a standard JSON MIME or :json."
|
||||
(if mime
|
||||
(or (= :json mime)
|
||||
(re-matches #"application/json(;.*)?" (name mime)))))
|
||||
|
||||
(defn json-preferred-mime [mimes]
|
||||
"Choose a MIME from the given MIMEs with JSON preferred,
|
||||
i.e. return JSON if included, otherwise return the first one."
|
||||
(-> (filter json-mime? mimes)
|
||||
first
|
||||
(or (first mimes))))
|
||||
|
||||
(defn deserialize
|
||||
"Deserialize the given HTTP response according to the Content-Type header."
|
||||
[{:keys [body] {:keys [content-type]} :headers}]
|
||||
(cond
|
||||
(json-mime? content-type)
|
||||
(try
|
||||
(parse-string body true)
|
||||
(catch JsonParseException e
|
||||
;; return the body string directly on JSON parsing error
|
||||
body))
|
||||
;; for non-JSON response, return the body string directly
|
||||
:else body))
|
||||
|
||||
(defn call-api
|
||||
"Call an API by making HTTP request and return its response."
|
||||
[path method {:keys [path-params query-params header-params form-params body-param content-types accepts]}]
|
||||
(let [{:keys [debug datetime-format]} *api-context*
|
||||
url (make-url path path-params)
|
||||
content-type (json-preferred-mime content-types)
|
||||
accept (or (json-preferred-mime accepts) :json)
|
||||
opts (cond-> {:url url :method method}
|
||||
content-type (assoc :content-type content-type)
|
||||
(json-mime? content-type) (assoc :json-opts {:date-format datetime-format})
|
||||
accept (assoc :accept accept)
|
||||
(seq query-params) (assoc :query-params (normalize-params query-params))
|
||||
(seq header-params) (assoc :header-params (normalize-params header-params))
|
||||
(seq form-params) (assoc :form-params (normalize-params form-params))
|
||||
(and (empty? form-params) body-param) (assoc :form-params body-param)
|
||||
debug (assoc :debug true :debug-body true))
|
||||
resp (client/request opts)]
|
||||
(when debug
|
||||
(println "Response:")
|
||||
(println resp))
|
||||
(deserialize resp)))
|
@ -0,0 +1,8 @@
|
||||
(defproject {{{projectName}}} "{{{projectVersion}}}"
|
||||
:description "{{{projectDescription}}}"
|
||||
:url "http://example.com/FIXME"
|
||||
:license {:name "Eclipse Public License"
|
||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||
:dependencies [[org.clojure/clojure "1.7.0"]
|
||||
[clj-http "2.0.0"]
|
||||
[cheshire "5.5.0"]])
|
Loading…
x
Reference in New Issue
Block a user