forked from loafle/openapi-generator-original
[Ruby] Abstract Ruby Codegen (#562)
* Add AbstractRubyCodegen * Refactor constructor * Move escapeReservedWord() to AbstractRubyCodegen * Move getTypeDeclaration() to AbstractRubyCodegen * Move toDefaultValue() to AbstractRubyCodegen * Move toVarName() to AbstractRubyCodegen * Move toParamName() to AbstractRubyCodegen * Move toOperationId() to AbstractRubyCodegen * Move escapeQuotationMark() to AbstractRubyCodegen * Move escapeUnsafeCharacters() to AbstractRubyCodegen * Use super.escapeReservedWord() * RubyClientCodegen extends AbstractRubyCodegen * Add the differences with AbstractRubyCodegen to "reservedWords" * cliOptions.clear() is not a language specific matter - Rails, Sinatra requires cliOptions.clear() - Ruby client doesn't requires that * Remove duplicated statements with AbstractRubyCodegen * Remove duplicated methods with AbstractRubyCodegen * Merge toVarName() into AbstractRubyCodegen * Merge getTypeDeclaration() into AbstractRubyCodegen * Merge toDefaultValue() into AbstractRubyCodegen * Update Ruby related samples - bin/ruby-client-petstore.sh - bin/ruby-on-rails-server-petstore.sh - bin/ruby-sinatra-server-petstore.sh * Remove unnecessary 'import' * Avoid unnecessary HTML escaping
This commit is contained in:
committed by
William Cheng
parent
a055dc0351
commit
afb238814d
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
|
||||
* Copyright 2018 SmartBear Software
|
||||
*
|
||||
* 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 io.swagger.v3.oas.models.media.ArraySchema;
|
||||
import io.swagger.v3.oas.models.media.Schema;
|
||||
import org.openapitools.codegen.*;
|
||||
import org.openapitools.codegen.utils.ModelUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
abstract class AbstractRubyCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRubyCodegen.class);
|
||||
|
||||
AbstractRubyCodegen() {
|
||||
super();
|
||||
|
||||
setReservedWordsLowerCase(
|
||||
Arrays.asList(
|
||||
"__FILE__", "and", "def", "end", "in", "or", "self", "unless", "__LINE__",
|
||||
"begin", "defined?", "ensure", "module", "redo", "super", "until", "BEGIN",
|
||||
"break", "do", "false", "next", "rescue", "then", "when", "END", "case",
|
||||
"else", "for", "nil", "retry", "true", "while", "alias", "class", "elsif",
|
||||
"if", "not", "return", "undef", "yield")
|
||||
);
|
||||
|
||||
languageSpecificPrimitives.clear();
|
||||
languageSpecificPrimitives.add("String");
|
||||
languageSpecificPrimitives.add("Integer");
|
||||
languageSpecificPrimitives.add("Float");
|
||||
languageSpecificPrimitives.add("Date");
|
||||
languageSpecificPrimitives.add("DateTime");
|
||||
languageSpecificPrimitives.add("Array");
|
||||
languageSpecificPrimitives.add("Hash");
|
||||
languageSpecificPrimitives.add("File");
|
||||
languageSpecificPrimitives.add("Object");
|
||||
|
||||
typeMapping.clear();
|
||||
typeMapping.put("string", "String");
|
||||
typeMapping.put("char", "String");
|
||||
typeMapping.put("int", "Integer");
|
||||
typeMapping.put("integer", "Integer");
|
||||
typeMapping.put("long", "Integer");
|
||||
typeMapping.put("short", "Integer");
|
||||
typeMapping.put("float", "Float");
|
||||
typeMapping.put("double", "Float");
|
||||
typeMapping.put("number", "Float");
|
||||
typeMapping.put("date", "Date");
|
||||
typeMapping.put("DateTime", "DateTime");
|
||||
typeMapping.put("array", "Array");
|
||||
typeMapping.put("List", "Array");
|
||||
typeMapping.put("map", "Hash");
|
||||
typeMapping.put("object", "Object");
|
||||
typeMapping.put("file", "File");
|
||||
typeMapping.put("binary", "String");
|
||||
typeMapping.put("ByteArray", "String");
|
||||
typeMapping.put("UUID", "String");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeReservedWord(String name) {
|
||||
if (this.reservedWordsMappings().containsKey(name)) {
|
||||
return this.reservedWordsMappings().get(name);
|
||||
}
|
||||
return "_" + name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeDeclaration(Schema schema) {
|
||||
if (ModelUtils.isArraySchema(schema)) {
|
||||
Schema inner = ((ArraySchema) schema).getItems();
|
||||
return getSchemaType(schema) + "<" + getTypeDeclaration(inner) + ">";
|
||||
} else if (ModelUtils.isMapSchema(schema)) {
|
||||
Schema inner = (Schema) schema.getAdditionalProperties();
|
||||
return getSchemaType(schema) + "<String, " + getTypeDeclaration(inner) + ">";
|
||||
}
|
||||
|
||||
return super.getTypeDeclaration(schema);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toDefaultValue(Schema p) {
|
||||
if (ModelUtils.isIntegerSchema(p) || ModelUtils.isNumberSchema(p) || ModelUtils.isBooleanSchema(p)) {
|
||||
if (p.getDefault() != null) {
|
||||
return p.getDefault().toString();
|
||||
}
|
||||
} else if (ModelUtils.isStringSchema(p)) {
|
||||
if (p.getDefault() != null) {
|
||||
return "'" + escapeText((String) p.getDefault()) + "'";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toVarName(String name) {
|
||||
// sanitize name
|
||||
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
|
||||
// if it's all uppper case, convert to lower case
|
||||
if (name.matches("^[A-Z_]*$")) {
|
||||
name = name.toLowerCase();
|
||||
}
|
||||
|
||||
// camelize (lower first character) the variable name
|
||||
// petId => pet_id
|
||||
name = underscore(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) {
|
||||
// should be the same as variable name
|
||||
return toVarName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toOperationId(String operationId) {
|
||||
// method name cannot use reserved keyword, e.g. return
|
||||
if (isReservedWord(operationId)) {
|
||||
String newOperationId = underscore("call_" + operationId);
|
||||
LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + newOperationId);
|
||||
return newOperationId;
|
||||
}
|
||||
|
||||
return underscore(operationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeQuotationMark(String input) {
|
||||
// remove ' to avoid code injection
|
||||
return input.replace("'", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeUnsafeCharacters(String input) {
|
||||
return input.replace("=end", "=_end").replace("=begin", "=_begin");
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,11 @@
|
||||
package org.openapitools.codegen.languages;
|
||||
|
||||
import org.openapitools.codegen.CliOption;
|
||||
import org.openapitools.codegen.CodegenConfig;
|
||||
import org.openapitools.codegen.CodegenConstants;
|
||||
import org.openapitools.codegen.CodegenParameter;
|
||||
import org.openapitools.codegen.CodegenProperty;
|
||||
import org.openapitools.codegen.CodegenType;
|
||||
import org.openapitools.codegen.DefaultCodegen;
|
||||
import org.openapitools.codegen.SupportingFile;
|
||||
import org.openapitools.codegen.utils.ModelUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
@@ -37,7 +34,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RubyClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
public class RubyClientCodegen extends AbstractRubyCodegen {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RubyClientCodegen.class);
|
||||
public static final String GEM_NAME = "gemName";
|
||||
public static final String MODULE_NAME = "moduleName";
|
||||
@@ -89,21 +86,13 @@ public class RubyClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
// default HIDE_GENERATION_TIMESTAMP to true
|
||||
hideGenerationTimestamp = Boolean.TRUE;
|
||||
|
||||
setReservedWordsLowerCase(
|
||||
Arrays.asList(
|
||||
// local variable names used in API methods (endpoints)
|
||||
"local_var_path", "query_params", "header_params", "_header_accept", "_header_accept_result",
|
||||
"_header_content_type", "form_params", "post_body", "auth_names",
|
||||
// ruby reserved keywords
|
||||
"__FILE__", "and", "def", "end", "in", "or", "self", "unless", "__LINE__",
|
||||
"begin", "defined?", "ensure", "module", "redo", "super", "until", "BEGIN",
|
||||
"break", "do", "false", "next", "rescue", "then", "when", "END", "case",
|
||||
"else", "for", "nil", "retry", "true", "while", "alias", "class", "elsif",
|
||||
"if", "not", "return", "undef", "yield")
|
||||
);
|
||||
// local variable names used in API methods (endpoints)
|
||||
for (String word : Arrays.asList(
|
||||
"local_var_path", "query_params", "header_params", "_header_accept", "_header_accept_result",
|
||||
"_header_content_type", "form_params", "post_body", "auth_names")) {
|
||||
reservedWords.add(word.toLowerCase());
|
||||
}
|
||||
|
||||
typeMapping.clear();
|
||||
languageSpecificPrimitives.clear();
|
||||
|
||||
// primitives in ruby lang
|
||||
languageSpecificPrimitives.add("int");
|
||||
@@ -111,37 +100,8 @@ public class RubyClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
languageSpecificPrimitives.add("map");
|
||||
languageSpecificPrimitives.add("string");
|
||||
// primitives in the typeMapping
|
||||
languageSpecificPrimitives.add("String");
|
||||
languageSpecificPrimitives.add("Integer");
|
||||
languageSpecificPrimitives.add("Float");
|
||||
languageSpecificPrimitives.add("Date");
|
||||
languageSpecificPrimitives.add("DateTime");
|
||||
languageSpecificPrimitives.add("BOOLEAN");
|
||||
languageSpecificPrimitives.add("Array");
|
||||
languageSpecificPrimitives.add("Hash");
|
||||
languageSpecificPrimitives.add("File");
|
||||
languageSpecificPrimitives.add("Object");
|
||||
|
||||
typeMapping.put("string", "String");
|
||||
typeMapping.put("char", "String");
|
||||
typeMapping.put("int", "Integer");
|
||||
typeMapping.put("integer", "Integer");
|
||||
typeMapping.put("long", "Integer");
|
||||
typeMapping.put("short", "Integer");
|
||||
typeMapping.put("float", "Float");
|
||||
typeMapping.put("double", "Float");
|
||||
typeMapping.put("number", "Float");
|
||||
typeMapping.put("date", "Date");
|
||||
typeMapping.put("DateTime", "DateTime");
|
||||
typeMapping.put("boolean", "BOOLEAN");
|
||||
typeMapping.put("array", "Array");
|
||||
typeMapping.put("List", "Array");
|
||||
typeMapping.put("map", "Hash");
|
||||
typeMapping.put("object", "Object");
|
||||
typeMapping.put("file", "File");
|
||||
typeMapping.put("binary", "String");
|
||||
typeMapping.put("ByteArray", "String");
|
||||
typeMapping.put("UUID", "String");
|
||||
|
||||
// remove modelPackage and apiPackage added by default
|
||||
Iterator<CliOption> itr = cliOptions.iterator();
|
||||
@@ -308,14 +268,6 @@ public class RubyClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
return underscore(moduleName.replaceAll("[^\\w]+", ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeReservedWord(String name) {
|
||||
if (this.reservedWordsMappings().containsKey(name)) {
|
||||
return this.reservedWordsMappings().get(name);
|
||||
}
|
||||
return "_" + name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiFileFolder() {
|
||||
return outputFolder + File.separator + libFolder + File.separator + gemName + File.separator + apiPackage.replace("/", File.separator);
|
||||
@@ -346,34 +298,6 @@ public class RubyClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeDeclaration(Schema schema) {
|
||||
if (ModelUtils.isArraySchema(schema)) {
|
||||
Schema inner = ((ArraySchema) schema).getItems();
|
||||
return getSchemaType(schema) + "<" + getTypeDeclaration(inner) + ">";
|
||||
} else if (ModelUtils.isMapSchema(schema)) {
|
||||
Schema inner = (Schema) schema.getAdditionalProperties();
|
||||
return getSchemaType(schema) + "<String, " + getTypeDeclaration(inner) + ">";
|
||||
}
|
||||
|
||||
return super.getTypeDeclaration(schema);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toDefaultValue(Schema p) {
|
||||
if (ModelUtils.isIntegerSchema(p) || ModelUtils.isNumberSchema(p) || ModelUtils.isBooleanSchema(p)) {
|
||||
if (p.getDefault() != null) {
|
||||
return p.getDefault().toString();
|
||||
}
|
||||
} else if (ModelUtils.isStringSchema(p)) {
|
||||
if (p.getDefault() != null) {
|
||||
return "'" + escapeText((String) p.getDefault()) + "'";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchemaType(Schema schema) {
|
||||
String openAPIType = super.getSchemaType(schema);
|
||||
@@ -394,33 +318,6 @@ public class RubyClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
return toModelName(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toVarName(String name) {
|
||||
// sanitize name
|
||||
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
|
||||
// if it's all uppper case, convert to lower case
|
||||
if (name.matches("^[A-Z_]*$")) {
|
||||
name = name.toLowerCase();
|
||||
}
|
||||
|
||||
// camelize (lower first character) the variable name
|
||||
// petId => pet_id
|
||||
name = underscore(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) {
|
||||
// should be the same as variable name
|
||||
return toVarName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toModelName(String name) {
|
||||
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
|
||||
@@ -684,16 +581,4 @@ public class RubyClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
//
|
||||
//return super.shouldOverwrite(filename) && !filename.endsWith("_spec.rb");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeQuotationMark(String input) {
|
||||
// remove ' to avoid code injection
|
||||
return input.replace("'", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeUnsafeCharacters(String input) {
|
||||
return input.replace("=end", "=_end").replace("=begin", "=_begin");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,30 +18,19 @@
|
||||
package org.openapitools.codegen.languages;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import org.openapitools.codegen.CodegenConfig;
|
||||
import org.openapitools.codegen.CodegenType;
|
||||
import org.openapitools.codegen.DefaultCodegen;
|
||||
import org.openapitools.codegen.SupportingFile;
|
||||
import org.openapitools.codegen.utils.ModelUtils;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.media.*;
|
||||
import io.swagger.v3.core.util.Yaml;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RubyOnRailsServerCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
public class RubyOnRailsServerCodegen extends AbstractRubyCodegen {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RubyOnRailsServerCodegen.class);
|
||||
private static final SimpleDateFormat MIGRATE_FILE_NAME_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");
|
||||
@@ -87,33 +76,14 @@ public class RubyOnRailsServerCodegen extends DefaultCodegen implements CodegenC
|
||||
|
||||
embeddedTemplateDir = templateDir = "ruby-on-rails-server";
|
||||
|
||||
typeMapping.clear();
|
||||
languageSpecificPrimitives.clear();
|
||||
|
||||
setReservedWordsLowerCase(
|
||||
Arrays.asList(
|
||||
"__FILE__", "and", "def", "end", "in", "or", "self", "unless", "__LINE__",
|
||||
"begin", "defined?", "ensure", "module", "redo", "super", "until", "BEGIN",
|
||||
"break", "do", "false", "next", "rescue", "then", "when", "END", "case",
|
||||
"else", "for", "nil", "retry", "true", "while", "alias", "class", "elsif",
|
||||
"if", "not", "return", "undef", "yield")
|
||||
);
|
||||
|
||||
// In order to adapt to DB migrate script, overwrite typeMapping
|
||||
typeMapping.put("string", "string");
|
||||
typeMapping.put("char", "string");
|
||||
typeMapping.put("int", "integer");
|
||||
typeMapping.put("integer", "integer");
|
||||
typeMapping.put("long", "integer");
|
||||
typeMapping.put("short", "integer");
|
||||
typeMapping.put("float", "float");
|
||||
typeMapping.put("double", "decimal");
|
||||
typeMapping.put("number", "float");
|
||||
typeMapping.put("date", "date");
|
||||
typeMapping.put("DateTime", "datetime");
|
||||
typeMapping.put("boolean", "boolean");
|
||||
typeMapping.put("binary", "string");
|
||||
typeMapping.put("ByteArray", "string");
|
||||
typeMapping.put("UUID", "string");
|
||||
|
||||
// remove modelPackage and apiPackage added by default
|
||||
cliOptions.clear();
|
||||
@@ -203,59 +173,11 @@ public class RubyOnRailsServerCodegen extends DefaultCodegen implements CodegenC
|
||||
return "Generates a Ruby on Rails (v5) server library.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeReservedWord(String name) {
|
||||
if(this.reservedWordsMappings().containsKey(name)) {
|
||||
return this.reservedWordsMappings().get(name);
|
||||
}
|
||||
return "_" + name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiFileFolder() {
|
||||
return outputFolder + File.separator + apiPackage.replace("/", File.separator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeDeclaration(Schema p) {
|
||||
if (ModelUtils.isArraySchema(p)) {
|
||||
ArraySchema ap = (ArraySchema) p;
|
||||
Schema inner = ap.getItems();
|
||||
return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]";
|
||||
} else if (ModelUtils.isMapSchema(p)) {
|
||||
Schema inner = (Schema) p.getAdditionalProperties();
|
||||
return getSchemaType(p) + "[string," + getTypeDeclaration(inner) + "]";
|
||||
}
|
||||
return super.getTypeDeclaration(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toDefaultValue(Schema p) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toVarName(String name) {
|
||||
// replace - with _ e.g. created-at => created_at
|
||||
name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
|
||||
|
||||
// if it's all uppper case, convert to lower case
|
||||
if (name.matches("^[A-Z_]*$")) {
|
||||
name = name.toLowerCase();
|
||||
}
|
||||
|
||||
// camelize (lower first character) the variable name
|
||||
// petId => pet_id
|
||||
name = underscore(name);
|
||||
|
||||
// for reserved word or word starting with number, append _
|
||||
if (isReservedWord(name) || name.matches("^\\d.*")) {
|
||||
name = escapeReservedWord(name);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchemaType(Schema p) {
|
||||
String openAPIType = super.getSchemaType(p);
|
||||
@@ -266,12 +188,6 @@ public class RubyOnRailsServerCodegen extends DefaultCodegen implements CodegenC
|
||||
return "string";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toParamName(String name) {
|
||||
// should be the same as variable name
|
||||
return toVarName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toModelName(String name) {
|
||||
// model name cannot use reserved keyword, e.g. return
|
||||
@@ -318,32 +234,9 @@ public class RubyOnRailsServerCodegen extends DefaultCodegen implements CodegenC
|
||||
return camelize(name) + "Controller";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toOperationId(String operationId) {
|
||||
// method name cannot use reserved keyword, e.g. return
|
||||
if (isReservedWord(operationId)) {
|
||||
String newOperationId = underscore("call_" + operationId);
|
||||
LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + newOperationId);
|
||||
return newOperationId;
|
||||
}
|
||||
|
||||
return underscore(operationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
|
||||
generateYAMLSpecFile(objs);
|
||||
return super.postProcessSupportingFileData(objs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeQuotationMark(String input) {
|
||||
// remove ' to avoid code injection
|
||||
return input.replace("'", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeUnsafeCharacters(String input) {
|
||||
return input.replace("=end", "=_end").replace("=begin", "=_begin");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,29 +17,18 @@
|
||||
|
||||
package org.openapitools.codegen.languages;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import org.openapitools.codegen.CodegenConfig;
|
||||
import org.openapitools.codegen.CodegenType;
|
||||
import org.openapitools.codegen.DefaultCodegen;
|
||||
import org.openapitools.codegen.SupportingFile;
|
||||
import org.openapitools.codegen.utils.ModelUtils;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.media.*;
|
||||
import io.swagger.v3.core.util.Yaml;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RubySinatraServerCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
public class RubySinatraServerCodegen extends AbstractRubyCodegen {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RubySinatraServerCodegen.class);
|
||||
|
||||
@@ -58,35 +47,6 @@ public class RubySinatraServerCodegen extends DefaultCodegen implements CodegenC
|
||||
apiTemplateFiles.put("api.mustache", ".rb");
|
||||
embeddedTemplateDir = templateDir = "ruby-sinatra-server";
|
||||
|
||||
typeMapping.clear();
|
||||
languageSpecificPrimitives.clear();
|
||||
|
||||
setReservedWordsLowerCase(
|
||||
Arrays.asList(
|
||||
"__FILE__", "and", "def", "end", "in", "or", "self", "unless", "__LINE__",
|
||||
"begin", "defined?", "ensure", "module", "redo", "super", "until", "BEGIN",
|
||||
"break", "do", "false", "next", "rescue", "then", "when", "END", "case",
|
||||
"else", "for", "nil", "retry", "true", "while", "alias", "class", "elsif",
|
||||
"if", "not", "return", "undef", "yield")
|
||||
);
|
||||
|
||||
languageSpecificPrimitives.add("int");
|
||||
languageSpecificPrimitives.add("array");
|
||||
languageSpecificPrimitives.add("map");
|
||||
languageSpecificPrimitives.add("string");
|
||||
languageSpecificPrimitives.add("DateTime");
|
||||
|
||||
typeMapping.put("long", "int");
|
||||
typeMapping.put("integer", "int");
|
||||
typeMapping.put("Array", "array");
|
||||
typeMapping.put("String", "string");
|
||||
typeMapping.put("List", "array");
|
||||
typeMapping.put("map", "map");
|
||||
//TODO binary should be mapped to byte array
|
||||
// mapped to String as a workaround
|
||||
typeMapping.put("binary", "string");
|
||||
typeMapping.put("UUID", "string");
|
||||
|
||||
// remove modelPackage and apiPackage added by default
|
||||
cliOptions.clear();
|
||||
}
|
||||
@@ -122,32 +82,11 @@ public class RubySinatraServerCodegen extends DefaultCodegen implements CodegenC
|
||||
return "Generates a Ruby Sinatra server library.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeReservedWord(String name) {
|
||||
if (this.reservedWordsMappings().containsKey(name)) {
|
||||
return this.reservedWordsMappings().get(name);
|
||||
}
|
||||
return "_" + name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiFileFolder() {
|
||||
return outputFolder + File.separator + apiPackage.replace("/", File.separator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeDeclaration(Schema p) {
|
||||
if (ModelUtils.isArraySchema(p)) {
|
||||
ArraySchema ap = (ArraySchema) p;
|
||||
Schema inner = ap.getItems();
|
||||
return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]";
|
||||
} else if (ModelUtils.isMapSchema(p)) {
|
||||
Schema inner = (Schema) p.getAdditionalProperties();
|
||||
return getSchemaType(p) + "[string," + getTypeDeclaration(inner) + "]";
|
||||
}
|
||||
return super.getTypeDeclaration(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchemaType(Schema p) {
|
||||
String openAPIType = super.getSchemaType(p);
|
||||
@@ -166,39 +105,6 @@ public class RubySinatraServerCodegen extends DefaultCodegen implements CodegenC
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toDefaultValue(Schema p) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toVarName(String name) {
|
||||
// replace - with _ e.g. created-at => created_at
|
||||
name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
|
||||
|
||||
// if it's all uppper case, convert to lower case
|
||||
if (name.matches("^[A-Z_]*$")) {
|
||||
name = name.toLowerCase();
|
||||
}
|
||||
|
||||
// camelize (lower first character) the variable name
|
||||
// petId => pet_id
|
||||
name = underscore(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) {
|
||||
// should be the same as variable name
|
||||
return toVarName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toModelName(String name) {
|
||||
// model name cannot use reserved keyword, e.g. return
|
||||
@@ -243,32 +149,9 @@ public class RubySinatraServerCodegen extends DefaultCodegen implements CodegenC
|
||||
return camelize(name) + "Api";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toOperationId(String operationId) {
|
||||
// method name cannot use reserved keyword, e.g. return
|
||||
if (isReservedWord(operationId)) {
|
||||
String newOperationId = underscore("call_" + operationId);
|
||||
LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + newOperationId);
|
||||
return newOperationId;
|
||||
}
|
||||
|
||||
return underscore(operationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
|
||||
generateYAMLSpecFile(objs);
|
||||
return super.postProcessSupportingFileData(objs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeQuotationMark(String input) {
|
||||
// remove ' to avoid code injection
|
||||
return input.replace("'", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeUnsafeCharacters(String input) {
|
||||
return input.replace("=end", "=_end").replace("=begin", "=_begin");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ MyApp.add_route('{{httpMethod}}', '{{{basePathWithoutHost}}}{{{path}}}', {
|
||||
"resourcePath" => "/{{{baseName}}}",
|
||||
"summary" => "{{{summary}}}",
|
||||
"nickname" => "{{nickname}}",
|
||||
"responseClass" => "{{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}void{{/returnType}}",
|
||||
"responseClass" => "{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}",
|
||||
"endpoint" => "{{{path}}}",
|
||||
"notes" => "{{{notes}}}",
|
||||
"parameters" => [
|
||||
@@ -15,7 +15,7 @@ MyApp.add_route('{{httpMethod}}', '{{{basePathWithoutHost}}}{{{path}}}', {
|
||||
{
|
||||
"name" => "{{paramName}}",
|
||||
"description" => "{{description}}",
|
||||
"dataType" => "{{dataType}}",
|
||||
"dataType" => "{{{dataType}}}",
|
||||
{{#collectionFormat}}
|
||||
"collectionFormat" => "{{collectionFormat}}",
|
||||
{{/collectionFormat}}
|
||||
@@ -32,7 +32,7 @@ MyApp.add_route('{{httpMethod}}', '{{{basePathWithoutHost}}}{{{path}}}', {
|
||||
{
|
||||
"name" => "{{paramName}}",
|
||||
"description" => "{{description}}",
|
||||
"dataType" => "{{dataType}}",
|
||||
"dataType" => "{{{dataType}}}",
|
||||
"paramType" => "path",
|
||||
},
|
||||
{{/pathParams}}
|
||||
@@ -40,7 +40,7 @@ MyApp.add_route('{{httpMethod}}', '{{{basePathWithoutHost}}}{{{path}}}', {
|
||||
{
|
||||
"name" => "{{paramName}}",
|
||||
"description" => "{{description}}",
|
||||
"dataType" => "{{dataType}}",
|
||||
"dataType" => "{{{dataType}}}",
|
||||
"paramType" => "header",
|
||||
},
|
||||
{{/headerParams}}
|
||||
@@ -48,7 +48,7 @@ MyApp.add_route('{{httpMethod}}', '{{{basePathWithoutHost}}}{{{path}}}', {
|
||||
{
|
||||
"name" => "body",
|
||||
"description" => "{{description}}",
|
||||
"dataType" => "{{dataType}}",
|
||||
"dataType" => "{{{dataType}}}",
|
||||
"paramType" => "body",
|
||||
}
|
||||
{{/bodyParams}}
|
||||
|
||||
@@ -5,7 +5,7 @@ MyApp.add_route('POST', '/v2/pet', {
|
||||
"resourcePath" => "/Pet",
|
||||
"summary" => "Add a new pet to the store",
|
||||
"nickname" => "add_pet",
|
||||
"responseClass" => "void",
|
||||
"responseClass" => "void",
|
||||
"endpoint" => "/pet",
|
||||
"notes" => "",
|
||||
"parameters" => [
|
||||
@@ -27,20 +27,20 @@ MyApp.add_route('DELETE', '/v2/pet/{petId}', {
|
||||
"resourcePath" => "/Pet",
|
||||
"summary" => "Deletes a pet",
|
||||
"nickname" => "delete_pet",
|
||||
"responseClass" => "void",
|
||||
"responseClass" => "void",
|
||||
"endpoint" => "/pet/{petId}",
|
||||
"notes" => "",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "pet_id",
|
||||
"description" => "Pet id to delete",
|
||||
"dataType" => "int",
|
||||
"dataType" => "Integer",
|
||||
"paramType" => "path",
|
||||
},
|
||||
{
|
||||
"name" => "api_key",
|
||||
"description" => "",
|
||||
"dataType" => "string",
|
||||
"dataType" => "String",
|
||||
"paramType" => "header",
|
||||
},
|
||||
]}) do
|
||||
@@ -55,14 +55,14 @@ MyApp.add_route('GET', '/v2/pet/findByStatus', {
|
||||
"resourcePath" => "/Pet",
|
||||
"summary" => "Finds Pets by status",
|
||||
"nickname" => "find_pets_by_status",
|
||||
"responseClass" => "array[Pet]",
|
||||
"responseClass" => "Array<Pet>",
|
||||
"endpoint" => "/pet/findByStatus",
|
||||
"notes" => "Multiple status values can be provided with comma separated strings",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "status",
|
||||
"description" => "Status values that need to be considered for filter",
|
||||
"dataType" => "array[string]",
|
||||
"dataType" => "Array<String>",
|
||||
"collectionFormat" => "csv",
|
||||
"paramType" => "query",
|
||||
},
|
||||
@@ -78,14 +78,14 @@ MyApp.add_route('GET', '/v2/pet/findByTags', {
|
||||
"resourcePath" => "/Pet",
|
||||
"summary" => "Finds Pets by tags",
|
||||
"nickname" => "find_pets_by_tags",
|
||||
"responseClass" => "array[Pet]",
|
||||
"responseClass" => "Array<Pet>",
|
||||
"endpoint" => "/pet/findByTags",
|
||||
"notes" => "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "tags",
|
||||
"description" => "Tags to filter by",
|
||||
"dataType" => "array[string]",
|
||||
"dataType" => "Array<String>",
|
||||
"collectionFormat" => "csv",
|
||||
"paramType" => "query",
|
||||
},
|
||||
@@ -101,14 +101,14 @@ MyApp.add_route('GET', '/v2/pet/{petId}', {
|
||||
"resourcePath" => "/Pet",
|
||||
"summary" => "Find pet by ID",
|
||||
"nickname" => "get_pet_by_id",
|
||||
"responseClass" => "Pet",
|
||||
"responseClass" => "Pet",
|
||||
"endpoint" => "/pet/{petId}",
|
||||
"notes" => "Returns a single pet",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "pet_id",
|
||||
"description" => "ID of pet to return",
|
||||
"dataType" => "int",
|
||||
"dataType" => "Integer",
|
||||
"paramType" => "path",
|
||||
},
|
||||
]}) do
|
||||
@@ -123,7 +123,7 @@ MyApp.add_route('PUT', '/v2/pet', {
|
||||
"resourcePath" => "/Pet",
|
||||
"summary" => "Update an existing pet",
|
||||
"nickname" => "update_pet",
|
||||
"responseClass" => "void",
|
||||
"responseClass" => "void",
|
||||
"endpoint" => "/pet",
|
||||
"notes" => "",
|
||||
"parameters" => [
|
||||
@@ -145,14 +145,14 @@ MyApp.add_route('POST', '/v2/pet/{petId}', {
|
||||
"resourcePath" => "/Pet",
|
||||
"summary" => "Updates a pet in the store with form data",
|
||||
"nickname" => "update_pet_with_form",
|
||||
"responseClass" => "void",
|
||||
"responseClass" => "void",
|
||||
"endpoint" => "/pet/{petId}",
|
||||
"notes" => "",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "pet_id",
|
||||
"description" => "ID of pet that needs to be updated",
|
||||
"dataType" => "int",
|
||||
"dataType" => "Integer",
|
||||
"paramType" => "path",
|
||||
},
|
||||
]}) do
|
||||
@@ -167,14 +167,14 @@ MyApp.add_route('POST', '/v2/pet/{petId}/uploadImage', {
|
||||
"resourcePath" => "/Pet",
|
||||
"summary" => "uploads an image",
|
||||
"nickname" => "upload_file",
|
||||
"responseClass" => "ApiResponse",
|
||||
"responseClass" => "ApiResponse",
|
||||
"endpoint" => "/pet/{petId}/uploadImage",
|
||||
"notes" => "",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "pet_id",
|
||||
"description" => "ID of pet to update",
|
||||
"dataType" => "int",
|
||||
"dataType" => "Integer",
|
||||
"paramType" => "path",
|
||||
},
|
||||
]}) do
|
||||
|
||||
@@ -5,14 +5,14 @@ MyApp.add_route('DELETE', '/v2/store/order/{orderId}', {
|
||||
"resourcePath" => "/Store",
|
||||
"summary" => "Delete purchase order by ID",
|
||||
"nickname" => "delete_order",
|
||||
"responseClass" => "void",
|
||||
"responseClass" => "void",
|
||||
"endpoint" => "/store/order/{orderId}",
|
||||
"notes" => "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "order_id",
|
||||
"description" => "ID of the order that needs to be deleted",
|
||||
"dataType" => "string",
|
||||
"dataType" => "String",
|
||||
"paramType" => "path",
|
||||
},
|
||||
]}) do
|
||||
@@ -27,7 +27,7 @@ MyApp.add_route('GET', '/v2/store/inventory', {
|
||||
"resourcePath" => "/Store",
|
||||
"summary" => "Returns pet inventories by status",
|
||||
"nickname" => "get_inventory",
|
||||
"responseClass" => "map[string,int]",
|
||||
"responseClass" => "Hash<String, Integer>",
|
||||
"endpoint" => "/store/inventory",
|
||||
"notes" => "Returns a map of status codes to quantities",
|
||||
"parameters" => [
|
||||
@@ -43,14 +43,14 @@ MyApp.add_route('GET', '/v2/store/order/{orderId}', {
|
||||
"resourcePath" => "/Store",
|
||||
"summary" => "Find purchase order by ID",
|
||||
"nickname" => "get_order_by_id",
|
||||
"responseClass" => "Order",
|
||||
"responseClass" => "Order",
|
||||
"endpoint" => "/store/order/{orderId}",
|
||||
"notes" => "For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "order_id",
|
||||
"description" => "ID of pet that needs to be fetched",
|
||||
"dataType" => "int",
|
||||
"dataType" => "Integer",
|
||||
"paramType" => "path",
|
||||
},
|
||||
]}) do
|
||||
@@ -65,7 +65,7 @@ MyApp.add_route('POST', '/v2/store/order', {
|
||||
"resourcePath" => "/Store",
|
||||
"summary" => "Place an order for a pet",
|
||||
"nickname" => "place_order",
|
||||
"responseClass" => "Order",
|
||||
"responseClass" => "Order",
|
||||
"endpoint" => "/store/order",
|
||||
"notes" => "",
|
||||
"parameters" => [
|
||||
|
||||
@@ -5,7 +5,7 @@ MyApp.add_route('POST', '/v2/user', {
|
||||
"resourcePath" => "/User",
|
||||
"summary" => "Create user",
|
||||
"nickname" => "create_user",
|
||||
"responseClass" => "void",
|
||||
"responseClass" => "void",
|
||||
"endpoint" => "/user",
|
||||
"notes" => "This can only be done by the logged in user.",
|
||||
"parameters" => [
|
||||
@@ -27,14 +27,14 @@ MyApp.add_route('POST', '/v2/user/createWithArray', {
|
||||
"resourcePath" => "/User",
|
||||
"summary" => "Creates list of users with given input array",
|
||||
"nickname" => "create_users_with_array_input",
|
||||
"responseClass" => "void",
|
||||
"responseClass" => "void",
|
||||
"endpoint" => "/user/createWithArray",
|
||||
"notes" => "",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "body",
|
||||
"description" => "List of user object",
|
||||
"dataType" => "array[User]",
|
||||
"dataType" => "Array<User>",
|
||||
"paramType" => "body",
|
||||
}
|
||||
]}) do
|
||||
@@ -49,14 +49,14 @@ MyApp.add_route('POST', '/v2/user/createWithList', {
|
||||
"resourcePath" => "/User",
|
||||
"summary" => "Creates list of users with given input array",
|
||||
"nickname" => "create_users_with_list_input",
|
||||
"responseClass" => "void",
|
||||
"responseClass" => "void",
|
||||
"endpoint" => "/user/createWithList",
|
||||
"notes" => "",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "body",
|
||||
"description" => "List of user object",
|
||||
"dataType" => "array[User]",
|
||||
"dataType" => "Array<User>",
|
||||
"paramType" => "body",
|
||||
}
|
||||
]}) do
|
||||
@@ -71,14 +71,14 @@ MyApp.add_route('DELETE', '/v2/user/{username}', {
|
||||
"resourcePath" => "/User",
|
||||
"summary" => "Delete user",
|
||||
"nickname" => "delete_user",
|
||||
"responseClass" => "void",
|
||||
"responseClass" => "void",
|
||||
"endpoint" => "/user/{username}",
|
||||
"notes" => "This can only be done by the logged in user.",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "username",
|
||||
"description" => "The name that needs to be deleted",
|
||||
"dataType" => "string",
|
||||
"dataType" => "String",
|
||||
"paramType" => "path",
|
||||
},
|
||||
]}) do
|
||||
@@ -93,14 +93,14 @@ MyApp.add_route('GET', '/v2/user/{username}', {
|
||||
"resourcePath" => "/User",
|
||||
"summary" => "Get user by user name",
|
||||
"nickname" => "get_user_by_name",
|
||||
"responseClass" => "User",
|
||||
"responseClass" => "User",
|
||||
"endpoint" => "/user/{username}",
|
||||
"notes" => "",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "username",
|
||||
"description" => "The name that needs to be fetched. Use user1 for testing.",
|
||||
"dataType" => "string",
|
||||
"dataType" => "String",
|
||||
"paramType" => "path",
|
||||
},
|
||||
]}) do
|
||||
@@ -115,21 +115,21 @@ MyApp.add_route('GET', '/v2/user/login', {
|
||||
"resourcePath" => "/User",
|
||||
"summary" => "Logs user into the system",
|
||||
"nickname" => "login_user",
|
||||
"responseClass" => "string",
|
||||
"responseClass" => "String",
|
||||
"endpoint" => "/user/login",
|
||||
"notes" => "",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "username",
|
||||
"description" => "The user name for login",
|
||||
"dataType" => "string",
|
||||
"dataType" => "String",
|
||||
"allowableValues" => "",
|
||||
"paramType" => "query",
|
||||
},
|
||||
{
|
||||
"name" => "password",
|
||||
"description" => "The password for login in clear text",
|
||||
"dataType" => "string",
|
||||
"dataType" => "String",
|
||||
"allowableValues" => "",
|
||||
"paramType" => "query",
|
||||
},
|
||||
@@ -145,7 +145,7 @@ MyApp.add_route('GET', '/v2/user/logout', {
|
||||
"resourcePath" => "/User",
|
||||
"summary" => "Logs out current logged in user session",
|
||||
"nickname" => "logout_user",
|
||||
"responseClass" => "void",
|
||||
"responseClass" => "void",
|
||||
"endpoint" => "/user/logout",
|
||||
"notes" => "",
|
||||
"parameters" => [
|
||||
@@ -161,14 +161,14 @@ MyApp.add_route('PUT', '/v2/user/{username}', {
|
||||
"resourcePath" => "/User",
|
||||
"summary" => "Updated user",
|
||||
"nickname" => "update_user",
|
||||
"responseClass" => "void",
|
||||
"responseClass" => "void",
|
||||
"endpoint" => "/user/{username}",
|
||||
"notes" => "This can only be done by the logged in user.",
|
||||
"parameters" => [
|
||||
{
|
||||
"name" => "username",
|
||||
"description" => "name that need to be deleted",
|
||||
"dataType" => "string",
|
||||
"dataType" => "String",
|
||||
"paramType" => "path",
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user