mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-05-12 12:40:53 +00:00
Merge 1a630aa03595e97d5e4847acbbd7e0545e143c21 into d6c46342693205f0dae441b45742d9c85d41cf33
This commit is contained in:
commit
43a38b25f4
251
docs/generators/scala-sttp4-jsoniter.md
Normal file
251
docs/generators/scala-sttp4-jsoniter.md
Normal file
@ -0,0 +1,251 @@
|
||||
---
|
||||
title: Documentation for the scala-sttp4-jsoniter Generator
|
||||
---
|
||||
|
||||
## METADATA
|
||||
|
||||
| Property | Value | Notes |
|
||||
| -------- | ----- | ----- |
|
||||
| generator name | scala-sttp4-jsoniter | pass this to the generate command after -g |
|
||||
| generator stability | BETA | |
|
||||
| generator type | CLIENT | |
|
||||
| generator language | Scala | |
|
||||
| generator default templating engine | mustache | |
|
||||
| helpTxt | Generates a Scala client library (beta) based on Sttp4 and Jsoniter-Scala. | |
|
||||
|
||||
## CONFIG OPTIONS
|
||||
These options may be applied as additional-properties (cli) or configOptions (plugins). Refer to [configuration docs](https://openapi-generator.tech/docs/configuration) for more details.
|
||||
|
||||
| Option | Description | Values | Default |
|
||||
| ------ | ----------- | ------ | ------- |
|
||||
|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
|
||||
|apiPackage|package for generated api classes| |null|
|
||||
|dateLibrary|Option. Date library to use|<dl><dt>**joda**</dt><dd>Joda (for legacy app)</dd><dt>**java8**</dt><dd>Java 8 native JSR310 (preferred for JDK 1.8+)</dd></dl>|java8|
|
||||
|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.</dd></dl>|true|
|
||||
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|
||||
|enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.|<dl><dt>**false**</dt><dd>No changes to the enum's are made, this is the default option.</dd><dt>**true**</dt><dd>With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.</dd></dl>|false|
|
||||
|jsoniterVersion|The version of jsoniter-scala library| |2.31.1|
|
||||
|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|<dl><dt>**true**</dt><dd>The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.</dd><dt>**false**</dt><dd>The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.</dd></dl>|true|
|
||||
|mainPackage|Top-level package name, which defines 'apiPackage', 'modelPackage', 'invokerPackage'| |org.openapitools.client|
|
||||
|modelPackage|package for generated models| |null|
|
||||
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
|
||||
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
|
||||
|separateErrorChannel|Whether to return response as F[Either[ResponseError[ErrorType], ReturnType]]] or to flatten response's error raising them through enclosing monad (F[ReturnType]).| |true|
|
||||
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|
||||
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|
||||
|sourceFolder|source folder for generated code| |null|
|
||||
|sttpClientVersion|The version of sttp client| |4.0.0-M19|
|
||||
|
||||
## IMPORT MAPPING
|
||||
|
||||
| Type/Alias | Imports |
|
||||
| ---------- | ------- |
|
||||
|Array|java.util.List|
|
||||
|ArrayList|java.util.ArrayList|
|
||||
|Date|java.util.Date|
|
||||
|DateTime|org.joda.time.*|
|
||||
|File|java.io.File|
|
||||
|HashMap|java.util.HashMap|
|
||||
|ListBuffer|scala.collection.mutable.ListBuffer|
|
||||
|ListSet|scala.collection.immutable.ListSet|
|
||||
|LocalDate|org.joda.time.*|
|
||||
|LocalDateTime|org.joda.time.*|
|
||||
|LocalTime|org.joda.time.*|
|
||||
|Seq|scala.collection.immutable.Seq|
|
||||
|Set|scala.collection.immutable.Set|
|
||||
|Timestamp|java.sql.Timestamp|
|
||||
|URI|java.net.URI|
|
||||
|UUID|java.util.UUID|
|
||||
|
||||
|
||||
## INSTANTIATION TYPES
|
||||
|
||||
| Type/Alias | Instantiated By |
|
||||
| ---------- | --------------- |
|
||||
|array|ListBuffer|
|
||||
|map|Map|
|
||||
|set|Set|
|
||||
|
||||
|
||||
## LANGUAGE PRIMITIVES
|
||||
|
||||
<ul class="column-ul">
|
||||
<li>Any</li>
|
||||
<li>Array</li>
|
||||
<li>Boolean</li>
|
||||
<li>Byte</li>
|
||||
<li>Double</li>
|
||||
<li>Float</li>
|
||||
<li>Int</li>
|
||||
<li>List</li>
|
||||
<li>Long</li>
|
||||
<li>Map</li>
|
||||
<li>Object</li>
|
||||
<li>Seq</li>
|
||||
<li>String</li>
|
||||
<li>boolean</li>
|
||||
</ul>
|
||||
|
||||
## RESERVED WORDS
|
||||
|
||||
<ul class="column-ul">
|
||||
<li>abstract</li>
|
||||
<li>case</li>
|
||||
<li>catch</li>
|
||||
<li>class</li>
|
||||
<li>clone</li>
|
||||
<li>def</li>
|
||||
<li>do</li>
|
||||
<li>else</li>
|
||||
<li>extends</li>
|
||||
<li>false</li>
|
||||
<li>final</li>
|
||||
<li>finally</li>
|
||||
<li>for</li>
|
||||
<li>forSome</li>
|
||||
<li>if</li>
|
||||
<li>implicit</li>
|
||||
<li>import</li>
|
||||
<li>lazy</li>
|
||||
<li>match</li>
|
||||
<li>new</li>
|
||||
<li>null</li>
|
||||
<li>object</li>
|
||||
<li>override</li>
|
||||
<li>package</li>
|
||||
<li>private</li>
|
||||
<li>protected</li>
|
||||
<li>return</li>
|
||||
<li>sealed</li>
|
||||
<li>super</li>
|
||||
<li>this</li>
|
||||
<li>throw</li>
|
||||
<li>trait</li>
|
||||
<li>true</li>
|
||||
<li>try</li>
|
||||
<li>type</li>
|
||||
<li>val</li>
|
||||
<li>var</li>
|
||||
<li>while</li>
|
||||
<li>with</li>
|
||||
<li>yield</li>
|
||||
</ul>
|
||||
|
||||
## FEATURE SET
|
||||
|
||||
|
||||
### Client Modification Feature
|
||||
| Name | Supported | Defined By |
|
||||
| ---- | --------- | ---------- |
|
||||
|BasePath|✓|ToolingExtension
|
||||
|Authorizations|✗|ToolingExtension
|
||||
|UserAgent|✓|ToolingExtension
|
||||
|MockServer|✗|ToolingExtension
|
||||
|
||||
### Data Type Feature
|
||||
| Name | Supported | Defined By |
|
||||
| ---- | --------- | ---------- |
|
||||
|Custom|✗|OAS2,OAS3
|
||||
|Int32|✓|OAS2,OAS3
|
||||
|Int64|✓|OAS2,OAS3
|
||||
|Float|✓|OAS2,OAS3
|
||||
|Double|✓|OAS2,OAS3
|
||||
|Decimal|✓|ToolingExtension
|
||||
|String|✓|OAS2,OAS3
|
||||
|Byte|✓|OAS2,OAS3
|
||||
|Binary|✓|OAS2,OAS3
|
||||
|Boolean|✓|OAS2,OAS3
|
||||
|Date|✓|OAS2,OAS3
|
||||
|DateTime|✓|OAS2,OAS3
|
||||
|Password|✓|OAS2,OAS3
|
||||
|File|✓|OAS2
|
||||
|Uuid|✗|
|
||||
|Array|✓|OAS2,OAS3
|
||||
|Null|✗|OAS3
|
||||
|AnyType|✗|OAS2,OAS3
|
||||
|Object|✓|OAS2,OAS3
|
||||
|Maps|✓|ToolingExtension
|
||||
|CollectionFormat|✓|OAS2
|
||||
|CollectionFormatMulti|✓|OAS2
|
||||
|Enum|✓|OAS2,OAS3
|
||||
|ArrayOfEnum|✓|ToolingExtension
|
||||
|ArrayOfModel|✓|ToolingExtension
|
||||
|ArrayOfCollectionOfPrimitives|✓|ToolingExtension
|
||||
|ArrayOfCollectionOfModel|✓|ToolingExtension
|
||||
|ArrayOfCollectionOfEnum|✓|ToolingExtension
|
||||
|MapOfEnum|✓|ToolingExtension
|
||||
|MapOfModel|✓|ToolingExtension
|
||||
|MapOfCollectionOfPrimitives|✓|ToolingExtension
|
||||
|MapOfCollectionOfModel|✓|ToolingExtension
|
||||
|MapOfCollectionOfEnum|✓|ToolingExtension
|
||||
|
||||
### Documentation Feature
|
||||
| Name | Supported | Defined By |
|
||||
| ---- | --------- | ---------- |
|
||||
|Readme|✓|ToolingExtension
|
||||
|Model|✓|ToolingExtension
|
||||
|Api|✓|ToolingExtension
|
||||
|
||||
### Global Feature
|
||||
| Name | Supported | Defined By |
|
||||
| ---- | --------- | ---------- |
|
||||
|Host|✓|OAS2,OAS3
|
||||
|BasePath|✓|OAS2,OAS3
|
||||
|Info|✓|OAS2,OAS3
|
||||
|Schemes|✗|OAS2,OAS3
|
||||
|PartialSchemes|✓|OAS2,OAS3
|
||||
|Consumes|✓|OAS2
|
||||
|Produces|✓|OAS2
|
||||
|ExternalDocumentation|✓|OAS2,OAS3
|
||||
|Examples|✓|OAS2,OAS3
|
||||
|XMLStructureDefinitions|✗|OAS2,OAS3
|
||||
|MultiServer|✗|OAS3
|
||||
|ParameterizedServer|✗|OAS3
|
||||
|ParameterStyling|✗|OAS3
|
||||
|Callbacks|✗|OAS3
|
||||
|LinkObjects|✗|OAS3
|
||||
|
||||
### Parameter Feature
|
||||
| Name | Supported | Defined By |
|
||||
| ---- | --------- | ---------- |
|
||||
|Path|✓|OAS2,OAS3
|
||||
|Query|✓|OAS2,OAS3
|
||||
|Header|✓|OAS2,OAS3
|
||||
|Body|✓|OAS2
|
||||
|FormUnencoded|✓|OAS2
|
||||
|FormMultipart|✓|OAS2
|
||||
|Cookie|✗|OAS3
|
||||
|
||||
### Schema Support Feature
|
||||
| Name | Supported | Defined By |
|
||||
| ---- | --------- | ---------- |
|
||||
|Simple|✓|OAS2,OAS3
|
||||
|Composite|✓|OAS2,OAS3
|
||||
|Polymorphism|✗|OAS2,OAS3
|
||||
|Union|✗|OAS3
|
||||
|allOf|✗|OAS2,OAS3
|
||||
|anyOf|✗|OAS3
|
||||
|oneOf|✗|OAS3
|
||||
|not|✗|OAS3
|
||||
|
||||
### Security Feature
|
||||
| Name | Supported | Defined By |
|
||||
| ---- | --------- | ---------- |
|
||||
|BasicAuth|✓|OAS2,OAS3
|
||||
|ApiKey|✓|OAS2,OAS3
|
||||
|OpenIDConnect|✗|OAS3
|
||||
|BearerToken|✓|OAS3
|
||||
|OAuth2_Implicit|✗|OAS2,OAS3
|
||||
|OAuth2_Password|✗|OAS2,OAS3
|
||||
|OAuth2_ClientCredentials|✗|OAS2,OAS3
|
||||
|OAuth2_AuthorizationCode|✗|OAS2,OAS3
|
||||
|SignatureAuth|✗|OAS3
|
||||
|AWSV4Signature|✗|ToolingExtension
|
||||
|
||||
### Wire Format Feature
|
||||
| Name | Supported | Defined By |
|
||||
| ---- | --------- | ---------- |
|
||||
|JSON|✓|OAS2,OAS3
|
||||
|XML|✓|OAS2,OAS3
|
||||
|PROTOBUF|✗|ToolingExtension
|
||||
|Custom|✓|OAS2,OAS3
|
@ -529,7 +529,16 @@ public abstract class AbstractScalaCodegen extends DefaultCodegen {
|
||||
if (identifier.matches("[a-zA-Z_$][\\w_$]+") && !isReservedWord(identifier)) {
|
||||
return identifier;
|
||||
}
|
||||
return escapeReservedWord(identifier);
|
||||
if (identifier.matches("[0-9]*")) {
|
||||
return escapeReservedWord(identifier);
|
||||
}
|
||||
if (!capitalized || StringUtils.isNumeric(name)) {
|
||||
// starts with a small letter, could be a keyword or a number
|
||||
return escapeReservedWord(identifier);
|
||||
} else {
|
||||
// no keywords start with large letter
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
|
||||
protected String stripPackageName(String input) {
|
||||
|
@ -0,0 +1,714 @@
|
||||
package org.openapitools.codegen.languages;
|
||||
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.media.Schema;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openapitools.codegen.*;
|
||||
import org.openapitools.codegen.meta.GeneratorMetadata;
|
||||
import org.openapitools.codegen.meta.Stability;
|
||||
import org.openapitools.codegen.meta.features.*;
|
||||
import org.openapitools.codegen.model.ModelMap;
|
||||
import org.openapitools.codegen.model.ModelsMap;
|
||||
import org.openapitools.codegen.model.OperationMap;
|
||||
import org.openapitools.codegen.model.OperationsMap;
|
||||
import org.openapitools.codegen.utils.ModelUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.openapitools.codegen.languages.AbstractJavaCodegen.DATE_LIBRARY;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ScalaSttp4JsoniterClientCodegen extends AbstractScalaCodegen implements CodegenConfig {
|
||||
private static final StringProperty STTP_CLIENT_VERSION = new StringProperty("sttpClientVersion",
|
||||
"The version of " +
|
||||
"sttp client",
|
||||
"4.0.0-RC1");
|
||||
private static final BooleanProperty USE_SEPARATE_ERROR_CHANNEL = new BooleanProperty("separateErrorChannel",
|
||||
"Whether to return response as " +
|
||||
"F[Either[ResponseError[ErrorType], ReturnType]]] or to flatten " +
|
||||
"response's error raising them through enclosing monad (F[ReturnType]).",
|
||||
true);
|
||||
private static final StringProperty JSONITER_VERSION = new StringProperty("jsoniterVersion",
|
||||
"The version of jsoniter-scala " +
|
||||
"library",
|
||||
"2.31.1");
|
||||
|
||||
public static final String DEFAULT_PACKAGE_NAME = "org.openapitools.client";
|
||||
private static final PackageProperty PACKAGE_PROPERTY = new PackageProperty();
|
||||
|
||||
private static final List<Property<?>> properties = Arrays.asList(
|
||||
STTP_CLIENT_VERSION, USE_SEPARATE_ERROR_CHANNEL, JSONITER_VERSION, PACKAGE_PROPERTY);
|
||||
|
||||
private static final String jsonClassBaseName = "Json";
|
||||
private static final String jsonValueClass = "io.circe.Json";
|
||||
private static final String jsonAstCodecImport = "com.github.plokhotnyuk.jsoniter_scala.circe.JsoniterScalaCodec.*";
|
||||
|
||||
private static final Set<String> NO_JSON_CODEC_TYPES = new HashSet<>(Arrays.asList(
|
||||
"UUID", "URI", "URL", "File", "Path", jsonClassBaseName, jsonValueClass, "BigDecimal"
|
||||
));
|
||||
|
||||
private final Logger LOGGER = LoggerFactory.getLogger(ScalaSttp4JsoniterClientCodegen.class);
|
||||
|
||||
protected String groupId = "org.openapitools";
|
||||
protected String artifactId = "openapi-client";
|
||||
protected String artifactVersion = "1.0.0";
|
||||
protected boolean registerNonStandardStatusCodes = true;
|
||||
protected boolean renderJavadoc = true;
|
||||
protected boolean removeOAuthSecurities = true;
|
||||
|
||||
protected Map<String, String> jsonCodecNeedingTypes = new HashMap<>();
|
||||
|
||||
Map<String, ModelsMap> enumRefs = new HashMap<>();
|
||||
|
||||
private final Map<String, String> apiNameMappings = new HashMap<>();
|
||||
private final Set<String> uniqueApiNames = new HashSet<>();
|
||||
|
||||
public ScalaSttp4JsoniterClientCodegen() {
|
||||
super();
|
||||
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
|
||||
.stability(Stability.BETA)
|
||||
.build();
|
||||
|
||||
modifyFeatureSet(features -> features
|
||||
.includeDocumentationFeatures(DocumentationFeature.Readme)
|
||||
.wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML, WireFormatFeature.Custom))
|
||||
.securityFeatures(EnumSet.of(
|
||||
SecurityFeature.BasicAuth,
|
||||
SecurityFeature.ApiKey,
|
||||
SecurityFeature.BearerToken))
|
||||
.excludeGlobalFeatures(
|
||||
GlobalFeature.XMLStructureDefinitions,
|
||||
GlobalFeature.Callbacks,
|
||||
GlobalFeature.LinkObjects)
|
||||
.excludeSchemaSupportFeatures(
|
||||
SchemaSupportFeature.Polymorphism)
|
||||
.includeClientModificationFeatures(
|
||||
ClientModificationFeature.BasePath,
|
||||
ClientModificationFeature.UserAgent));
|
||||
|
||||
outputFolder = "generated-code/scala-sttp4-jsoniter";
|
||||
modelTemplateFiles.put("model.mustache", ".scala");
|
||||
apiTemplateFiles.put("api.mustache", ".scala");
|
||||
embeddedTemplateDir = templateDir = "scala-sttp4-jsoniter";
|
||||
|
||||
// Scala 3 reserved words
|
||||
reservedWords.addAll(Arrays.asList("enum", "export", "given", "then", "using", "Request", "Method", "Either"));
|
||||
|
||||
importMapping.put(jsonValueClass, jsonAstCodecImport);
|
||||
importMapping.put("BigDecimal", "scala.math.BigDecimal");
|
||||
|
||||
additionalProperties.put(CodegenConstants.GROUP_ID, groupId);
|
||||
additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId);
|
||||
additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion);
|
||||
additionalProperties.put("jsonCodecNeedingTypes", jsonCodecNeedingTypes.entrySet());
|
||||
if (renderJavadoc) {
|
||||
additionalProperties.put("javadocRenderer", new JavadocLambda());
|
||||
}
|
||||
additionalProperties.put("fnCapitalize", new CapitalizeLambda());
|
||||
additionalProperties.put("fnCamelize", new CamelizeLambda(false));
|
||||
additionalProperties.put("fnEnumEntry", new EnumEntryLambda());
|
||||
additionalProperties.put("fnCodecName", new CodecNameLambda());
|
||||
additionalProperties.put("fnHandleDownload", new HandleDownloadLambda());
|
||||
additionalProperties.put("fnEnumLeaf", new EnumLeafLambda());
|
||||
|
||||
// TODO: there is no specific sttp mapping. All Scala Type mappings should be in
|
||||
// AbstractScala
|
||||
typeMapping = new HashMap<>();
|
||||
typeMapping.put("array", "Seq");
|
||||
typeMapping.put("set", "Set");
|
||||
typeMapping.put("boolean", "Boolean");
|
||||
typeMapping.put("string", "String");
|
||||
typeMapping.put("int", "Int");
|
||||
typeMapping.put("integer", "Int");
|
||||
typeMapping.put("long", "Long");
|
||||
typeMapping.put("float", "Float");
|
||||
typeMapping.put("byte", "Byte");
|
||||
typeMapping.put("short", "Short");
|
||||
typeMapping.put("char", "Char");
|
||||
typeMapping.put("double", "Double");
|
||||
typeMapping.put("file", "File");
|
||||
typeMapping.put("binary", "File");
|
||||
typeMapping.put("number", "Double");
|
||||
typeMapping.put("decimal", "BigDecimal");
|
||||
typeMapping.put("ByteArray", "Array[Byte]");
|
||||
|
||||
// actually, these two *are* jsoniter+circe AST specific
|
||||
typeMapping.put("object", jsonValueClass);
|
||||
typeMapping.put("AnyType", jsonValueClass);
|
||||
|
||||
instantiationTypes.put("array", "ListBuffer");
|
||||
instantiationTypes.put("map", "Map");
|
||||
|
||||
// remove DATE_LIBRARY option, we don't need it
|
||||
cliOptions.removeIf(option -> option.getOpt().equals(DATE_LIBRARY));
|
||||
|
||||
properties.stream()
|
||||
.map(Property::toCliOptions)
|
||||
.flatMap(Collection::stream)
|
||||
.forEach(option -> cliOptions.add(option));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processOpts() {
|
||||
super.processOpts();
|
||||
properties.forEach(p -> p.updateAdditionalProperties(additionalProperties));
|
||||
invokerPackage = PACKAGE_PROPERTY.getInvokerPackage(additionalProperties);
|
||||
apiPackage = PACKAGE_PROPERTY.getApiPackage(additionalProperties);
|
||||
modelPackage = PACKAGE_PROPERTY.getModelPackage(additionalProperties);
|
||||
|
||||
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
|
||||
supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt"));
|
||||
final String invokerFolder = (sourceFolder + File.separator + invokerPackage).replace(".", File.separator);
|
||||
supportingFiles.add(new SupportingFile("jsonSupport.mustache", invokerFolder, "JsonSupport.scala"));
|
||||
supportingFiles.add(new SupportingFile("additionalTypeSerializers.mustache", invokerFolder,
|
||||
"AdditionalTypeSerializers.scala"));
|
||||
supportingFiles.add(new SupportingFile("helpers.mustache", invokerFolder,
|
||||
"Helpers.scala"));
|
||||
supportingFiles.add(new SupportingFile("project/build.properties.mustache", "project", "build.properties"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "scala-sttp4-jsoniter";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelp() {
|
||||
return "Generates a Scala client library (beta) based on Sttp4 and Jsoniter-Scala.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodePath(String input) {
|
||||
String path = super.encodePath(input);
|
||||
// The parameter names in the URI must be converted to the same case as
|
||||
// the method parameter.
|
||||
StringBuffer buf = new StringBuffer(path.length());
|
||||
Matcher matcher = Pattern.compile("[{](.*?)[}]").matcher(path);
|
||||
while (matcher.find()) {
|
||||
matcher.appendReplacement(buf, "\\${" + toParamName(matcher.group(0)).replace("`", "") + "PathParam}");
|
||||
}
|
||||
matcher.appendTail(buf);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenOperation fromOperation(String path,
|
||||
String httpMethod,
|
||||
Operation operation,
|
||||
List<Server> servers) {
|
||||
CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers);
|
||||
op.path = encodePath(path);
|
||||
return op;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getTag() {
|
||||
return CodegenType.CLIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeReservedWord(String name) {
|
||||
if (this.reservedWordsMappings().containsKey(name)) {
|
||||
return this.reservedWordsMappings().get(name);
|
||||
}
|
||||
return "`" + name + "`";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toApiName(String name) {
|
||||
// first come, first served
|
||||
// if a tag name is already mapped, use that mapping
|
||||
if (apiNameMappings.containsKey(name)) {
|
||||
return apiNameMappings.get(name);
|
||||
}
|
||||
|
||||
String generatedApiName = super.toApiName(name);
|
||||
String lowerCasedApiName = generatedApiName.toLowerCase(Locale.ROOT);
|
||||
|
||||
// check if the name is unique (case-insensitive)
|
||||
// if it's unique, add it to the mappings and return the generated name
|
||||
if (!uniqueApiNames.contains(lowerCasedApiName)) {
|
||||
uniqueApiNames.add(lowerCasedApiName);
|
||||
apiNameMappings.put(name, generatedApiName);
|
||||
|
||||
return generatedApiName;
|
||||
} else {
|
||||
// if the name is not unique, generate a new name with a unique suffix
|
||||
int i = 0;
|
||||
while (true) {
|
||||
String nextGeneratedApiName = super.toApiName(name + i);
|
||||
String lowerCasedNextGeneratedApiName = nextGeneratedApiName.toLowerCase(Locale.ROOT);
|
||||
if (!uniqueApiNames.contains(lowerCasedNextGeneratedApiName)) {
|
||||
uniqueApiNames.add(lowerCasedNextGeneratedApiName);
|
||||
apiNameMappings.put(name, nextGeneratedApiName);
|
||||
|
||||
return nextGeneratedApiName;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelsMap postProcessModels(ModelsMap objs) {
|
||||
return 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 An in-place modified state of the codegen object model.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
|
||||
final Map<String, ModelsMap> processed = super.postProcessAllModels(objs);
|
||||
postProcessUpdateImports(processed);
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update/clean up model imports
|
||||
* <p>
|
||||
* append '._" if the import is a Enum class, otherwise
|
||||
* remove model imports to avoid warnings for importing class in the same
|
||||
* package in Scala
|
||||
*
|
||||
* @param models processed models to be further processed
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void postProcessUpdateImports(final Map<String, ModelsMap> models) {
|
||||
final String prefix = modelPackage() + ".";
|
||||
|
||||
enumRefs = getEnumRefs(models);
|
||||
|
||||
for (String openAPIName : models.keySet()) {
|
||||
CodegenModel model = ModelUtils.getModelByName(openAPIName, models);
|
||||
if (model == null) {
|
||||
LOGGER.warn(
|
||||
"Expected to retrieve model {} by name, but no model was found. Check your -Dmodels inclusions.",
|
||||
openAPIName);
|
||||
continue;
|
||||
}
|
||||
|
||||
ModelsMap objs = models.get(openAPIName);
|
||||
List<Map<String, String>> imports = objs.getImports();
|
||||
if (imports == null || imports.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
List<Map<String, String>> newImports = new ArrayList<>();
|
||||
|
||||
boolean foundJsonImport = false;
|
||||
|
||||
for (Map<String, String> anImport : imports) {
|
||||
String importPath = anImport.get("import");
|
||||
|
||||
Map<String, String> item = new HashMap<>();
|
||||
|
||||
// remove any imports for io.circe.Json, it's a FQCN
|
||||
// but on the first encounter, add the import for the
|
||||
// jsoniter-scala circe AST codec as it will be necessary
|
||||
// for all places where io.circe.Json is used as request body
|
||||
// or response body
|
||||
if (importPath.contains(jsonValueClass)) {
|
||||
if (!foundJsonImport) {
|
||||
foundJsonImport = true;
|
||||
item.put("import", jsonAstCodecImport);
|
||||
newImports.add(item);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (importPath.startsWith(prefix)) {
|
||||
if (isEnumClass(importPath, enumRefs)) {
|
||||
item.put("import", importPath.concat(".*"));
|
||||
newImports.add(item);
|
||||
}
|
||||
} else {
|
||||
item.put("import", importPath);
|
||||
newImports.add(item);
|
||||
}
|
||||
}
|
||||
// reset imports
|
||||
objs.setImports(newImports);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, ModelsMap> getEnumRefs(final Map<String, ModelsMap> models) {
|
||||
Map<String, ModelsMap> enums = new HashMap<>();
|
||||
for (String key : models.keySet()) {
|
||||
CodegenModel model = ModelUtils.getModelByName(key, models);
|
||||
if (model != null && model.isEnum) {
|
||||
ModelsMap objs = models.get(key);
|
||||
enums.put(key, objs);
|
||||
}
|
||||
}
|
||||
return enums;
|
||||
}
|
||||
|
||||
private boolean isEnumClass(final String importPath, final Map<String, ModelsMap> enumModels) {
|
||||
if (enumModels == null || enumModels.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (ModelsMap objs : enumModels.values()) {
|
||||
List<ModelMap> models = objs.getModels();
|
||||
if (models == null || models.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (final Map<String, Object> model : models) {
|
||||
String enumImportPath = (String) model.get("importPath");
|
||||
if (enumImportPath != null && enumImportPath.equals(importPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<ModelMap> allModels) {
|
||||
OperationMap ops = objs.getOperations();
|
||||
|
||||
for (CodegenOperation operation : ops.getOperation()) {
|
||||
if (operation.returnType != null && !NO_JSON_CODEC_TYPES.contains(operation.returnType)) {
|
||||
String identifier = formatIdentifier(operation.returnType, false) + "Codec";
|
||||
String type = operation.returnType;
|
||||
jsonCodecNeedingTypes.put(identifier, type);
|
||||
}
|
||||
|
||||
if (operation.bodyParam != null && !NO_JSON_CODEC_TYPES.contains(operation.bodyParam.dataType)) {
|
||||
String identifier = formatIdentifier(operation.bodyParam.dataType, false) + "Codec";
|
||||
String type = operation.bodyParam.dataType;
|
||||
|
||||
jsonCodecNeedingTypes.put(identifier, type);
|
||||
}
|
||||
}
|
||||
|
||||
if (registerNonStandardStatusCodes) {
|
||||
try {
|
||||
OperationMap opsMap = objs.getOperations();
|
||||
HashSet<Integer> unknownCodes = new HashSet<>();
|
||||
for (CodegenOperation operation : opsMap.getOperation()) {
|
||||
for (CodegenResponse response : operation.responses) {
|
||||
if ("default".equals(response.code)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
int code = Integer.parseInt(response.code);
|
||||
if (code >= 600) {
|
||||
unknownCodes.add(code);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
LOGGER.error("Status code is not an integer : response.code", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!unknownCodes.isEmpty()) {
|
||||
additionalProperties.put("unknownStatusCodes", unknownCodes);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Unable to find operations List", e);
|
||||
}
|
||||
}
|
||||
|
||||
// update imports for enum class
|
||||
List<Map<String, String>> newImports = new ArrayList<>();
|
||||
List<Map<String, String>> imports = objs.getImports();
|
||||
if (imports != null && !imports.isEmpty()) {
|
||||
for (Map<String, String> anImport : imports) {
|
||||
String importPath = anImport.get("import");
|
||||
Map<String, String> item = new HashMap<>();
|
||||
if (isEnumClass(importPath, enumRefs)) {
|
||||
item.put("import", importPath.concat(".*"));
|
||||
Map<String, String> enumClassImport = new HashMap<>();
|
||||
enumClassImport.put("import", importPath);
|
||||
newImports.add(item);
|
||||
newImports.add(enumClassImport);
|
||||
} else {
|
||||
item.put("import", importPath);
|
||||
newImports.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
objs.setImports(newImports);
|
||||
|
||||
return super.postProcessOperationsWithModels(objs, allModels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CodegenSecurity> fromSecurity(Map<String, SecurityScheme> schemes) {
|
||||
final List<CodegenSecurity> codegenSecurities = super.fromSecurity(schemes);
|
||||
if (!removeOAuthSecurities) {
|
||||
return codegenSecurities;
|
||||
}
|
||||
|
||||
// Remove OAuth securities
|
||||
codegenSecurities.removeIf(security -> security.isOAuth);
|
||||
if (codegenSecurities.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return codegenSecurities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toParamName(String name) {
|
||||
// obtain the name from parameterNameMapping directly if provided
|
||||
if (parameterNameMapping.containsKey(name)) {
|
||||
return parameterNameMapping.get(name);
|
||||
}
|
||||
|
||||
return formatIdentifier(name, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toEnumName(CodegenProperty property) {
|
||||
String identifier = formatIdentifier(property.baseName, true);
|
||||
|
||||
if (identifier.startsWith("`") && identifier.endsWith("`")) {
|
||||
// is it numeric?
|
||||
String unescaped = identifier.substring(1, identifier.length() - 1);
|
||||
if (StringUtils.isNumeric(unescaped)) {
|
||||
return identifier; // keep backticks
|
||||
}
|
||||
|
||||
// remove backticks because there are no capitalized reserved words in Scala
|
||||
return identifier.substring(1, identifier.length() - 1);
|
||||
} else {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toDefaultValue(Schema p) {
|
||||
if (p.getRequired() != null && p.getRequired().contains(p.getName())) {
|
||||
return "None";
|
||||
}
|
||||
|
||||
if (ModelUtils.isBooleanSchema(p)) {
|
||||
return null;
|
||||
} else if (ModelUtils.isDateSchema(p)) {
|
||||
return null;
|
||||
} else if (ModelUtils.isDateTimeSchema(p)) {
|
||||
return null;
|
||||
} else if (ModelUtils.isNumberSchema(p)) {
|
||||
return null;
|
||||
} else if (ModelUtils.isIntegerSchema(p)) {
|
||||
return null;
|
||||
} else if (ModelUtils.isMapSchema(p)) {
|
||||
String inner = getSchemaType(ModelUtils.getAdditionalProperties(p));
|
||||
return "Map[String, " + inner + "].empty ";
|
||||
} else if (ModelUtils.isArraySchema(p)) {
|
||||
String inner = getSchemaType(ModelUtils.getSchemaItems(p));
|
||||
if (ModelUtils.isSet(p)) {
|
||||
return "Set[" + inner + "].empty ";
|
||||
}
|
||||
return "Seq[" + inner + "].empty ";
|
||||
} else if (ModelUtils.isStringSchema(p)) {
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update datatypeWithEnum for array container
|
||||
*
|
||||
* @param property Codegen property
|
||||
*/
|
||||
@Override
|
||||
protected void updateDataTypeWithEnumForArray(CodegenProperty property) {
|
||||
CodegenProperty baseItem = property.items;
|
||||
while (baseItem != null && (Boolean.TRUE.equals(baseItem.isMap)
|
||||
|| Boolean.TRUE.equals(baseItem.isArray))) {
|
||||
baseItem = baseItem.items;
|
||||
}
|
||||
if (baseItem != null) {
|
||||
// set datetypeWithEnum as only the inner type is enum
|
||||
property.datatypeWithEnum = toEnumName(baseItem);
|
||||
// naming the enum with respect to the language enum naming convention
|
||||
// e.g. remove [], {} from array/map of enum
|
||||
property.enumName = toEnumName(property);
|
||||
property._enum = baseItem._enum;
|
||||
|
||||
updateCodegenPropertyEnum(property);
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class Property<T> {
|
||||
final String name;
|
||||
final String description;
|
||||
final T defaultValue;
|
||||
|
||||
public Property(String name, String description, T defaultValue) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public abstract List<CliOption> toCliOptions();
|
||||
|
||||
public abstract void updateAdditionalProperties(Map<String, Object> additionalProperties);
|
||||
|
||||
public abstract T getValue(Map<String, Object> additionalProperties);
|
||||
|
||||
public void setValue(Map<String, Object> additionalProperties, T value) {
|
||||
additionalProperties.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static class StringProperty extends Property<String> {
|
||||
public StringProperty(String name, String description, String defaultValue) {
|
||||
super(name, description, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CliOption> toCliOptions() {
|
||||
return Collections.singletonList(CliOption.newString(name, description).defaultValue(defaultValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAdditionalProperties(Map<String, Object> additionalProperties) {
|
||||
if (!additionalProperties.containsKey(name)) {
|
||||
additionalProperties.put(name, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(Map<String, Object> additionalProperties) {
|
||||
return additionalProperties.getOrDefault(name, defaultValue).toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class BooleanProperty extends Property<Boolean> {
|
||||
public BooleanProperty(String name, String description, Boolean defaultValue) {
|
||||
super(name, description, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CliOption> toCliOptions() {
|
||||
return Collections.singletonList(CliOption.newBoolean(name, description, defaultValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAdditionalProperties(Map<String, Object> additionalProperties) {
|
||||
Boolean value = getValue(additionalProperties);
|
||||
additionalProperties.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getValue(Map<String, Object> additionalProperties) {
|
||||
return Boolean.valueOf(additionalProperties.getOrDefault(name, defaultValue.toString()).toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static class PackageProperty extends StringProperty {
|
||||
|
||||
public PackageProperty() {
|
||||
super("mainPackage", "Top-level package name, which defines 'apiPackage', 'modelPackage', " +
|
||||
"'invokerPackage'", DEFAULT_PACKAGE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAdditionalProperties(Map<String, Object> additionalProperties) {
|
||||
String mainPackage = getValue(additionalProperties);
|
||||
if (!additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) {
|
||||
String apiPackage = mainPackage + ".api";
|
||||
additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage);
|
||||
}
|
||||
if (!additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE)) {
|
||||
String modelPackage = mainPackage + ".model";
|
||||
additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage);
|
||||
}
|
||||
if (!additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) {
|
||||
String invokerPackage = mainPackage + ".core";
|
||||
additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage);
|
||||
}
|
||||
}
|
||||
|
||||
public String getApiPackage(Map<String, Object> additionalProperties) {
|
||||
return additionalProperties.getOrDefault(CodegenConstants.API_PACKAGE, DEFAULT_PACKAGE_NAME + ".api")
|
||||
.toString();
|
||||
}
|
||||
|
||||
public String getModelPackage(Map<String, Object> additionalProperties) {
|
||||
return additionalProperties.getOrDefault(CodegenConstants.MODEL_PACKAGE, DEFAULT_PACKAGE_NAME + ".model")
|
||||
.toString();
|
||||
}
|
||||
|
||||
public String getInvokerPackage(Map<String, Object> additionalProperties) {
|
||||
return additionalProperties.getOrDefault(CodegenConstants.INVOKER_PACKAGE, DEFAULT_PACKAGE_NAME + ".core")
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class JavadocLambda extends CustomLambda {
|
||||
@Override
|
||||
public String formatFragment(String fragment) {
|
||||
final String[] lines = fragment.split("\\r?\\n");
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(" /**\n");
|
||||
for (String line : lines) {
|
||||
sb.append(" * ").append(line).append("\n");
|
||||
}
|
||||
sb.append(" */\n");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class CapitalizeLambda extends CustomLambda {
|
||||
@Override
|
||||
public String formatFragment(String fragment) {
|
||||
return StringUtils.capitalize(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
private static class EnumEntryLambda extends CustomLambda {
|
||||
@Override
|
||||
public String formatFragment(String fragment) {
|
||||
if (fragment.isBlank()) {
|
||||
return "NotPresent";
|
||||
}
|
||||
return "`" + fragment + "`";
|
||||
}
|
||||
}
|
||||
|
||||
private static class EnumLeafLambda extends CustomLambda {
|
||||
@Override
|
||||
public String formatFragment(String fragment) {
|
||||
if (fragment.isBlank()) {
|
||||
return "NotPresent";
|
||||
}
|
||||
return fragment.replace("`", "");
|
||||
}
|
||||
}
|
||||
|
||||
private class CodecNameLambda extends CustomLambda {
|
||||
@Override
|
||||
public String formatFragment(String fragment) {
|
||||
// remove backticks because this is used as prefix for Codec generation
|
||||
return formatIdentifier(fragment, false).replace("`", "") + "Codec";
|
||||
}
|
||||
}
|
||||
|
||||
private static class HandleDownloadLambda extends CustomLambda {
|
||||
@Override
|
||||
public String formatFragment(String fragment) {
|
||||
if (fragment.equals("asJson[File]")) {
|
||||
return "asFile(File.createTempFile(\"download\", \".tmp\")).mapWithMetadata((result, metadata) => result.left.map(errStr => ResponseException.DeserializationException(errStr, new Exception(errStr), metadata)))";
|
||||
} else {
|
||||
return fragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -135,6 +135,7 @@ org.openapitools.codegen.languages.ScalaLagomServerCodegen
|
||||
org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen
|
||||
org.openapitools.codegen.languages.ScalaSttpClientCodegen
|
||||
org.openapitools.codegen.languages.ScalaSttp4ClientCodegen
|
||||
org.openapitools.codegen.languages.ScalaSttp4JsoniterClientCodegen
|
||||
org.openapitools.codegen.languages.ScalazClientCodegen
|
||||
org.openapitools.codegen.languages.SpringCodegen
|
||||
org.openapitools.codegen.languages.StaticDocCodegen
|
||||
|
115
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/README.mustache
vendored
Normal file
115
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/README.mustache
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
# {{artifactId}}
|
||||
|
||||
{{appName}}
|
||||
- API version: {{appVersion}}
|
||||
{{^hideGenerationTimestamp}}
|
||||
- Build date: {{generatedDate}}
|
||||
{{/hideGenerationTimestamp}}
|
||||
- Generator version: {{generatorVersion}}
|
||||
|
||||
{{{appDescriptionWithNewLines}}}
|
||||
|
||||
{{#infoUrl}}
|
||||
For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})
|
||||
{{/infoUrl}}
|
||||
|
||||
*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)*
|
||||
|
||||
## Requirements
|
||||
|
||||
Building the API client library requires:
|
||||
1. Java 1.7+
|
||||
2. Maven/Gradle/SBT
|
||||
|
||||
## Installation
|
||||
|
||||
To install the API client library to your local Maven repository, simply execute:
|
||||
|
||||
```shell
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
To deploy it to a remote Maven repository instead, configure the settings of the repository and execute:
|
||||
|
||||
```shell
|
||||
mvn clean deploy
|
||||
```
|
||||
|
||||
Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information.
|
||||
|
||||
### Maven users
|
||||
|
||||
Add this dependency to your project's POM:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>{{{groupId}}}</groupId>
|
||||
<artifactId>{{{artifactId}}}</artifactId>
|
||||
<version>{{{artifactVersion}}}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle users
|
||||
|
||||
Add this dependency to your project's build file:
|
||||
|
||||
```groovy
|
||||
compile "{{{groupId}}}:{{{artifactId}}}:{{{artifactVersion}}}"
|
||||
```
|
||||
|
||||
### SBT users
|
||||
|
||||
```scala
|
||||
libraryDependencies += "{{{groupId}}}" % "{{{artifactId}}}" % "{{{artifactVersion}}}"
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
## Documentation for API Endpoints
|
||||
|
||||
All URIs are relative to *{{basePath}}*
|
||||
|
||||
Class | Method | HTTP request | Description
|
||||
------------ | ------------- | ------------- | -------------
|
||||
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | **{{operationId}}** | **{{httpMethod}}** {{path}} | {{summary}}
|
||||
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||
|
||||
## Documentation for Models
|
||||
|
||||
{{#models}}{{#model}} - [{{classname}}]({{modelDocPath}}{{classname}}.md)
|
||||
{{/model}}{{/models}}
|
||||
|
||||
<a id="documentation-for-authorization"></a>
|
||||
## Documentation for Authorization
|
||||
|
||||
{{^authMethods}}Endpoints do not require authorization.{{/authMethods}}
|
||||
{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}}
|
||||
{{#authMethods}}
|
||||
<a id="{{name}}"></a>
|
||||
### {{name}}
|
||||
|
||||
{{#isApiKey}}- **Type**: API key
|
||||
- **API key parameter name**: {{keyParamName}}
|
||||
- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}}
|
||||
{{/isApiKey}}
|
||||
{{#isBasicBasic}}- **Type**: HTTP basic authentication
|
||||
{{/isBasicBasic}}
|
||||
{{#isBasicBearer}}- **Type**: HTTP Bearer Token authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}}
|
||||
{{/isBasicBearer}}
|
||||
{{#isHttpSignature}}- **Type**: HTTP signature authentication
|
||||
{{/isHttpSignature}}
|
||||
{{#isOAuth}}- **Type**: OAuth
|
||||
- **Flow**: {{flow}}
|
||||
- **Authorization URL**: {{authorizationUrl}}
|
||||
- **Scopes**: {{^scopes}}N/A{{/scopes}}
|
||||
{{#scopes}} - {{scope}}: {{description}}
|
||||
{{/scopes}}
|
||||
{{/isOAuth}}
|
||||
|
||||
{{/authMethods}}
|
||||
|
||||
## Author
|
||||
|
||||
{{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}}
|
||||
{{/-last}}{{/apis}}{{/apiInfo}}
|
@ -0,0 +1,20 @@
|
||||
package {{invokerPackage}}
|
||||
|
||||
import java.net.{URI, URISyntaxException}
|
||||
import com.github.plokhotnyuk.jsoniter_scala.core.*
|
||||
|
||||
trait AdditionalTypeSerializers:
|
||||
|
||||
implicit final lazy val URICodec: JsonValueCodec[URI] = new JsonValueCodec[URI]:
|
||||
def nullValue: URI = null
|
||||
def decodeValue(in: JsonReader, default: URI): URI =
|
||||
try
|
||||
val uriString = in.readString(null)
|
||||
if (uriString != null) new URI(uriString) else default
|
||||
catch
|
||||
case e: URISyntaxException =>
|
||||
in.decodeError(s"Invalid URI syntax: ${e.getMessage}")
|
||||
|
||||
def encodeValue(uri: URI, out: JsonWriter): Unit =
|
||||
if (uri != null) out.writeVal(uri.toString)
|
||||
else out.writeNull()
|
74
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/api.mustache
vendored
Normal file
74
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/api.mustache
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
{{>licenseInfo}}
|
||||
package {{package}}
|
||||
|
||||
{{#imports}}
|
||||
import {{import}}
|
||||
{{/imports}}
|
||||
import {{invokerPackage}}.JsonSupport.{*, given}
|
||||
import {{invokerPackage}}.FormSerializable
|
||||
import {{invokerPackage}}.FormStyleFormat
|
||||
import {{invokerPackage}}.HeaderSerializable
|
||||
import {{invokerPackage}}.ApiKeyLocation
|
||||
import {{invokerPackage}}.PathStyleFormat
|
||||
import {{invokerPackage}}.PathSerializable
|
||||
import {{invokerPackage}}.CookieSerializable
|
||||
import {{invokerPackage}}.Helpers.*
|
||||
import sttp.client4.jsoniter.*
|
||||
import sttp.client4.*
|
||||
import sttp.model.Method
|
||||
|
||||
{{#operations}}
|
||||
object {{classname}}:
|
||||
def apply(baseUrl: String = "{{{basePath}}}"): {{classname}}[{{invokerPackage}}.Authorization.NoAuthorization.type] = {{classname}}(baseUrl, {{invokerPackage}}.Authorization.NoAuthorization)
|
||||
def withBasicAuth(baseUrl: String, username: String, password: String): {{classname}}[{{invokerPackage}}.Authorization.BasicAuth] =
|
||||
{{classname}}(baseUrl, {{invokerPackage}}.Authorization.BasicAuth(username, password))
|
||||
|
||||
def withApiKeyAuth(baseUrl: String, apiKey: String): {{classname}}[{{invokerPackage}}.Authorization.ApiKey] =
|
||||
{{classname}}(baseUrl, {{invokerPackage}}.Authorization.ApiKey(apiKey))
|
||||
|
||||
def withBearerTokenAuth(baseUrl: String, token: String): {{classname}}[{{invokerPackage}}.Authorization.BearerToken] =
|
||||
{{classname}}(baseUrl, {{invokerPackage}}.Authorization.BearerToken(token))
|
||||
|
||||
case class {{classname}}[Auth <: {{invokerPackage}}.Authorization] private (baseUrl: String, authConfig: {{invokerPackage}}.Authorization):
|
||||
def withBasicAuth(username: String, password: String): {{classname}}[{{invokerPackage}}.Authorization.BasicAuth] =
|
||||
copy(authConfig = {{invokerPackage}}.Authorization.BasicAuth(username, password))
|
||||
|
||||
def withApiKeyAuth(apiKey: String): {{classname}}[{{invokerPackage}}.Authorization.ApiKey] =
|
||||
copy(authConfig = {{invokerPackage}}.Authorization.ApiKey(apiKey))
|
||||
|
||||
def withNoAuth: {{classname}}[{{invokerPackage}}.Authorization.NoAuthorization.type] =
|
||||
copy(authConfig = {{invokerPackage}}.Authorization.NoAuthorization)
|
||||
|
||||
def withBearerTokenAuth(token: String): {{classname}}[{{invokerPackage}}.Authorization.BearerToken] =
|
||||
copy(authConfig = {{invokerPackage}}.Authorization.BearerToken(token))
|
||||
|
||||
{{#operation}}
|
||||
{{#javadocRenderer}}
|
||||
{{>javadoc}}
|
||||
{{/javadocRenderer}}
|
||||
def {{operationId}}{{>methodParameters}}: sttp.client4.Request[{{#separateErrorChannel}}Either[ResponseException[String], {{>operationReturnType}}]{{/separateErrorChannel}}{{^separateErrorChannel}}{{>operationReturnType}}{{/separateErrorChannel}}] =
|
||||
{{#pathParams}} val {{#fnEnumLeaf}}{{paramName}}PathParam{{/fnEnumLeaf}} = PathSerializable.serialize("{{baseName}}", {{{paramName}}}{{#style}}, PathStyleFormat.{{style.toUpperCase}}{{/style}}{{^style}}, PathStyleFormat.SIMPLE{{/style}}, {{isExplode}})
|
||||
{{/pathParams}}
|
||||
val requestURL =
|
||||
uri"$baseUrl{{{path}}}"{{#queryParams}}
|
||||
.addParams(FormSerializable.serialize("{{baseName}}", {{{paramName}}}{{#style}}, FormStyleFormat.{{style.toUpperCase}}{{/style}}{{^style}}, FormStyleFormat.FORM{{/style}}, {{isExplode}}): _*){{/queryParams}}
|
||||
|
||||
basicRequest
|
||||
.method(Method.{{httpMethod.toUpperCase}}, requestURL)
|
||||
.contentType({{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}}{{^consumes}}"application/json"{{/consumes}}){{#headerParams}}
|
||||
.headers(HeaderSerializable.serialize("{{baseName}}", {{paramName}}, {{isExplode}})){{/headerParams}}{{#authMethods}}{{#cookieParams}}
|
||||
.cookies(CookieSerializable.serialize("{{baseName}}", {{paramName}}, {{isExplode}})){{/cookieParams}}
|
||||
.auth(authConfig{{#isApiKey}}, {{invokerPackage}}.ApiKeyLocation.{{#isKeyInQuery}}QUERY{{/isKeyInQuery}}{{#isKeyInHeader}}HEADER{{/isKeyInHeader}}{{#isKeyInCookie}}COOKIE{{/isKeyInCookie}}, "{{keyParamName}}"{{/isApiKey}}){{/authMethods}}{{#formParams.0}}{{^isMultipart}}
|
||||
.body({{#formParams}}
|
||||
{{>paramFormCreation}}{{^-last}} ++ {{/-last}}{{/formParams}},
|
||||
"utf-8"
|
||||
){{/isMultipart}}{{#isMultipart}}
|
||||
.multipartBody(Seq({{#formParams}}
|
||||
{{>paramMultipartCreation}}{{/formParams}}
|
||||
).flatten){{/isMultipart}}{{/formParams.0}}{{#bodyParam}}
|
||||
{{^isFile}}.body(asJson({{paramName}})){{/isFile}}{{#isFile}}.fileBody({{paramName}}){{/isFile}}{{/bodyParam}}
|
||||
.response({{#separateErrorChannel}}{{^returnType}}asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))){{/returnType}}{{#fnHandleDownload}}{{#returnType}}asJson[{{>operationReturnType}}]{{/returnType}}{{/fnHandleDownload}}{{/separateErrorChannel}}{{^separateErrorChannel}}{{^returnType}}asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))).getRight{{/returnType}}{{#returnType}}asJson[{{>operationReturnType}}].getRight{{/returnType}}{{/separateErrorChannel}})
|
||||
|
||||
{{/operation}}
|
||||
{{/operations}}
|
||||
end {{classname}}
|
19
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/build.sbt.mustache
vendored
Normal file
19
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/build.sbt.mustache
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
version := "{{artifactVersion}}"
|
||||
name := "{{artifactId}}"
|
||||
organization := "{{groupId}}"
|
||||
|
||||
scalaVersion := "3.3.4"
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
"com.softwaremill.sttp.client4" %% "core" % "{{sttpClientVersion}}",
|
||||
"com.softwaremill.sttp.client4" %% "jsoniter" % "{{sttpClientVersion}}",
|
||||
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "{{jsoniterVersion}}",
|
||||
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "{{jsoniterVersion}}" % "compile-internal",
|
||||
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-circe" % "{{jsoniterVersion}}"
|
||||
)
|
||||
|
||||
scalacOptions := Seq(
|
||||
"-unchecked",
|
||||
"-deprecation",
|
||||
"-feature"
|
||||
)
|
394
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/helpers.mustache
vendored
Normal file
394
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/helpers.mustache
vendored
Normal file
@ -0,0 +1,394 @@
|
||||
{{>licenseInfo}}
|
||||
package {{invokerPackage}}
|
||||
|
||||
import scala.deriving.*
|
||||
import scala.compiletime.*
|
||||
import java.io.File
|
||||
import com.github.plokhotnyuk.jsoniter_scala.core.{JsonValueCodec, writeToString}
|
||||
|
||||
type Primitive = String | Short | Int | Long | Float | Double | BigDecimal |
|
||||
Boolean
|
||||
|
||||
enum Authorization:
|
||||
case NoAuthorization
|
||||
case BasicAuth(username: String, password: String)
|
||||
case ApiKey(apiKey: String)
|
||||
case BearerToken(token: String)
|
||||
|
||||
enum ApiKeyLocation:
|
||||
case HEADER
|
||||
case COOKIE
|
||||
case QUERY
|
||||
case NOAPIKEY
|
||||
|
||||
enum FormStyleFormat:
|
||||
case FORM
|
||||
case SPACEDELIMITED
|
||||
case PIPEDELIMITED
|
||||
case DEEPOBJECT
|
||||
|
||||
enum PathStyleFormat:
|
||||
case SIMPLE
|
||||
case LABEL
|
||||
case MATRIX
|
||||
|
||||
inline def allLabels[T <: Tuple]: List[String] =
|
||||
constValueTuple[T].toList.asInstanceOf[List[String]]
|
||||
|
||||
private inline def checkFields[T <: Tuple]: Unit =
|
||||
inline erasedValue[T] match {
|
||||
case _: EmptyTuple => ()
|
||||
case _: (t *: ts) =>
|
||||
inline erasedValue[t] match
|
||||
case _: Primitive => checkFields[ts]
|
||||
case _: Option[Primitive] => checkFields[ts]
|
||||
case _ => error("Cannot derive structure, structure must consist only of primitive fields")
|
||||
}
|
||||
|
||||
private val flattenKeyVals: Primitive | Option[Primitive] => Option[Primitive] = {
|
||||
case p: Primitive => Some(p)
|
||||
case opt: Option[Primitive] => opt
|
||||
}
|
||||
|
||||
trait FormSerializable[T]:
|
||||
inline def serialize(
|
||||
name: String,
|
||||
obj: T,
|
||||
inline format: FormStyleFormat = FormStyleFormat.FORM,
|
||||
inline explode: Boolean = true
|
||||
): Seq[(String, String)]
|
||||
|
||||
|
||||
object FormSerializable:
|
||||
inline def serialize[T](
|
||||
name: String,
|
||||
obj: T,
|
||||
inline format: FormStyleFormat = FormStyleFormat.FORM,
|
||||
inline explode: Boolean = true
|
||||
): Seq[(String, String)] =
|
||||
summonFrom {
|
||||
case t: FormSerializable[T] => t.serialize(name, obj, format, explode)
|
||||
case _ =>
|
||||
inline obj match
|
||||
case primitive: Primitive =>
|
||||
serializePrimitive(name, primitive, format, explode)
|
||||
case array: Seq[Primitive] =>
|
||||
serializeArray(name, array, format, explode)
|
||||
case optPrimitive: Option[Primitive] =>
|
||||
optPrimitive.map(value => serializePrimitive(name, value, format, explode))
|
||||
.getOrElse(Seq.empty[(String, String)])
|
||||
case optArray: Option[Seq[Primitive]] =>
|
||||
optArray.map(serializeArray(name, _, format, explode))
|
||||
.getOrElse(Seq.empty[(String, String)])
|
||||
case freeObj: Map[String, Primitive] =>
|
||||
freeObj.map((key, value) => (key, value.toString)).toSeq
|
||||
case optObj: Option[t] =>
|
||||
inline summonInline[Mirror.Of[t]] match
|
||||
case mirror: Mirror.ProductOf[t] =>
|
||||
checkFields[mirror.MirroredElemTypes]
|
||||
val labels = allLabels[mirror.MirroredElemLabels]
|
||||
optObj.map { obj =>
|
||||
val keyVals = labels.zip(obj.asInstanceOf[Product].productIterator.toSeq.asInstanceOf[Seq[Primitive | Option[Primitive]]].map(flattenKeyVals))
|
||||
.filter((_, v) => v.isDefined)
|
||||
.map((k, v) => (k, v.get))
|
||||
serializeModel(name, keyVals, format, explode)
|
||||
}.getOrElse(Seq.empty[(String, String)])
|
||||
case mirror: Mirror.SumOf[t] => optObj.map(v => (name, writeToString(v)(summonInline[JsonValueCodec[mirror.MirroredMonoType]]))).toSeq
|
||||
case obj =>
|
||||
inline summonInline[Mirror.Of[T]] match
|
||||
case _: Mirror.SumOf[T] =>
|
||||
Seq((name, writeToString(obj)(summonInline[JsonValueCodec[T]])))
|
||||
case mirror: Mirror.ProductOf[T] =>
|
||||
checkFields[mirror.MirroredElemTypes] // Stripe ma IDGAF bo używają deepObject np. tak lines[0][tax_amounts][0][amount] - mimo tego że spec na to nie pozwala
|
||||
val labels = allLabels[mirror.MirroredElemLabels]
|
||||
val keyVals = labels.zip(obj.asInstanceOf[Product].productIterator.toSeq.asInstanceOf[Seq[Primitive | Option[Primitive]]].map(flattenKeyVals))
|
||||
.filter((_, v) => v.isDefined)
|
||||
.map((k, v) => (k, v.get))
|
||||
serializeModel(name, keyVals, format, explode)
|
||||
}
|
||||
|
||||
private inline def serializePrimitive(
|
||||
paramName: String,
|
||||
value: Primitive,
|
||||
inline format: FormStyleFormat,
|
||||
inline explode: Boolean
|
||||
): Seq[(String, String)] = {
|
||||
inline format match
|
||||
case FormStyleFormat.FORM =>
|
||||
Seq(paramName -> value.toString) // for primitve values explode does not change anything
|
||||
case FormStyleFormat.SPACEDELIMITED =>
|
||||
error("FormStyleFormat.SpaceDelimited does not support primitive values")
|
||||
case FormStyleFormat.PIPEDELIMITED =>
|
||||
error("FormStyleFormat.PipeDelimited does not support primitive values")
|
||||
case FormStyleFormat.DEEPOBJECT =>
|
||||
error("FormStyleFormat.DeepObject does not support primitive values")
|
||||
|
||||
}
|
||||
private inline def serializeArray(
|
||||
paramName: String,
|
||||
values: Seq[Primitive],
|
||||
inline format: FormStyleFormat,
|
||||
inline explode: Boolean
|
||||
): Seq[(String, String)] = {
|
||||
inline format match
|
||||
case FormStyleFormat.FORM =>
|
||||
inline if explode then values.map(s => (paramName, s.toString))
|
||||
else Seq(paramName -> values.mkString(","))
|
||||
case FormStyleFormat.SPACEDELIMITED =>
|
||||
inline if explode then values.map(s => (paramName, s.toString))
|
||||
else Seq(paramName -> values.mkString(" ")) // Sttp will encode space as +, from https://swagger.io/docs/specification/v3_0/serialization/#query-parameters it is not clear if it should be + or %20
|
||||
case FormStyleFormat.PIPEDELIMITED =>
|
||||
inline if explode then values.map(s => (paramName, s.toString))
|
||||
else Seq(paramName -> values.mkString("|"))
|
||||
case FormStyleFormat.DEEPOBJECT =>
|
||||
error("FormStyleFormat.DeepObject does not support arrays")
|
||||
}
|
||||
private inline def serializeModel(
|
||||
paramName: String,
|
||||
keyValPairs: Seq[(String, Primitive)],
|
||||
inline format: FormStyleFormat,
|
||||
inline explode: Boolean
|
||||
): Seq[(String, String)] = {
|
||||
inline format match
|
||||
case FormStyleFormat.FORM =>
|
||||
inline if explode then keyValPairs.map((key, value) => (key, value.toString))
|
||||
else Seq(paramName -> keyValPairs.flatMap((key, value) => Seq(key, value.toString)).mkString(","))
|
||||
case FormStyleFormat.SPACEDELIMITED =>
|
||||
error("FormStyleFormat.SpaceDelimited does not support objects")
|
||||
case FormStyleFormat.PIPEDELIMITED =>
|
||||
error("FormStyleFormat.PipeDelimited does not support objects")
|
||||
case FormStyleFormat.DEEPOBJECT =>
|
||||
inline if explode then keyValPairs.map((key, value) => (s"$paramName[$key]", value.toString))
|
||||
else error("FormStyleFormat.DeepObject does not support explode=false")
|
||||
}
|
||||
end FormSerializable
|
||||
|
||||
trait HeaderSerializable[T]:
|
||||
inline def serialize(
|
||||
name: String,
|
||||
obj: T,
|
||||
inline explode: Boolean = true
|
||||
): Map[String, String]
|
||||
|
||||
object HeaderSerializable:
|
||||
inline def serialize[T](
|
||||
name: String,
|
||||
obj: T,
|
||||
inline explode: Boolean = true
|
||||
): Map[String, String] =
|
||||
summonFrom {
|
||||
case t: HeaderSerializable[T] => t.serialize(name, obj, explode)
|
||||
case _ => inline obj match
|
||||
case primitive: Primitive => Map(name -> primitive.toString)
|
||||
case optPrimitive: Option[Primitive] => optPrimitive.map(v => Map(name -> v.toString)).getOrElse(Map.empty[String, String])
|
||||
case seqPrimitive: Seq[Primitive] => Map(name -> seqPrimitive.map(_.toString).mkString(","))
|
||||
case optSeqPrimitive: Option[Seq[Primitive]] => optSeqPrimitive.map(v => Map(name -> v.map(_.toString).mkString(","))).getOrElse(Map.empty[String, String])
|
||||
case mapPrimitive: Map[String, Primitive] => mapPrimitive.map((k, v) => (k, v.toString))
|
||||
case optObj: Option[t] =>
|
||||
inline summonInline[Mirror.Of[t]] match
|
||||
case mirror: Mirror.ProductOf[t] =>
|
||||
checkFields[mirror.MirroredElemTypes]
|
||||
val labels = allLabels[mirror.MirroredElemLabels]
|
||||
optObj.map { obj =>
|
||||
val keyVals = labels.zip(obj.asInstanceOf[Product].productIterator.toSeq.asInstanceOf[Seq[Primitive | Option[Primitive]]].map(flattenKeyVals))
|
||||
.filter((_, v) => v.isDefined)
|
||||
.map((k, v) => (k, v.get.toString))
|
||||
inline if explode then
|
||||
Map(name ->keyVals.map((k, v) => s"$k=$v").mkString(","))
|
||||
else
|
||||
Map(name -> keyVals.flatMap((k, v) => Seq(k, v)).mkString(","))
|
||||
}.getOrElse(Map.empty[String, String])
|
||||
case mirror: Mirror.SumOf[t] => optObj.map(v => Map(name -> writeToString(v)(summonInline[JsonValueCodec[mirror.MirroredMonoType]]))).getOrElse(Map.empty[String, String])
|
||||
case obj: T =>
|
||||
inline summonInline[Mirror.Of[T]] match
|
||||
case mirror: Mirror.ProductOf[T] =>
|
||||
checkFields[mirror.MirroredElemTypes]
|
||||
val labels = allLabels[mirror.MirroredElemLabels]
|
||||
val keyVals = labels.zip(obj.asInstanceOf[Product].productIterator.toSeq.asInstanceOf[Seq[Primitive | Option[Primitive]]].map(flattenKeyVals))
|
||||
.filter((_, v) => v.isDefined)
|
||||
.map((k, v) => (k, v.get.toString))
|
||||
inline if explode then
|
||||
Map(name ->keyVals.map((k, v) => s"$k=$v").mkString(","))
|
||||
else
|
||||
Map(name -> keyVals.flatMap((k, v) => Seq(k, v)).mkString(","))
|
||||
case mirror: Mirror.SumOf[T] => Map(name -> writeToString(obj)(summonInline[JsonValueCodec[mirror.MirroredMonoType]]))
|
||||
}
|
||||
end HeaderSerializable
|
||||
|
||||
trait PathSerializer[T]:
|
||||
inline def serialize[T](name: String, obj: T, inline style: PathStyleFormat, inline explode: Boolean): String
|
||||
|
||||
object PathSerializable:
|
||||
inline def serialize[T](name: String, obj: T, inline style: PathStyleFormat, inline explode: Boolean): String =
|
||||
summonFrom {
|
||||
case t: PathSerializer[T] => t.serialize(name, obj, style, explode)
|
||||
case _ =>
|
||||
inline obj match
|
||||
case primitive: Primitive =>
|
||||
serializePrimitive(name, primitive, style, explode)
|
||||
case array: Seq[Primitive] =>
|
||||
serializeArray(name, array, style, explode)
|
||||
case optPrimitive: Option[Primitive] =>
|
||||
optPrimitive.map(value => serializePrimitive(name, value, style, explode))
|
||||
.getOrElse("")
|
||||
case optArray: Option[Seq[Primitive]] =>
|
||||
optArray.map(serializeArray(name, _, style, explode))
|
||||
.getOrElse("")
|
||||
case freeObj: Map[String, Primitive] =>
|
||||
serializeModel(name, freeObj.map((key, value) => (key, value.toString)).toSeq, style, explode)
|
||||
case optObj: Option[t] =>
|
||||
inline summonInline[Mirror.Of[t]] match
|
||||
case mirror: Mirror.ProductOf[t] =>
|
||||
checkFields[mirror.MirroredElemTypes]
|
||||
val labels = allLabels[mirror.MirroredElemLabels]
|
||||
optObj.map { obj =>
|
||||
val keyVals = labels.zip(obj.asInstanceOf[Product].productIterator.toSeq.asInstanceOf[Seq[Primitive | Option[Primitive]]].map(flattenKeyVals))
|
||||
.filter((_, v) => v.isDefined)
|
||||
.map((k, v) => (k, v.get))
|
||||
serializeModel(name, keyVals, style, explode)
|
||||
}.getOrElse("")
|
||||
case mirror: Mirror.SumOf[t] => optObj.map(writeToString(_)(summonInline[JsonValueCodec[mirror.MirroredMonoType]])).getOrElse("")
|
||||
case obj =>
|
||||
inline summonInline[Mirror.Of[T]] match
|
||||
case _: Mirror.SumOf[T] =>
|
||||
writeToString(obj)(summonInline[JsonValueCodec[T]])
|
||||
case mirror: Mirror.ProductOf[T] =>
|
||||
checkFields[mirror.MirroredElemTypes]
|
||||
val labels = allLabels[mirror.MirroredElemLabels]
|
||||
val keyVals = labels.zip(obj.asInstanceOf[Product].productIterator.toSeq.asInstanceOf[Seq[Primitive | Option[Primitive]]].map(flattenKeyVals))
|
||||
.filter((_, v) => v.isDefined)
|
||||
.map((k, v) => (k, v.get))
|
||||
serializeModel(name, keyVals, style, explode)
|
||||
}
|
||||
|
||||
private inline def serializePrimitive(
|
||||
paramName: String,
|
||||
value: Primitive,
|
||||
inline format: PathStyleFormat,
|
||||
inline explode: Boolean
|
||||
): String = inline format match
|
||||
case PathStyleFormat.SIMPLE => value.toString
|
||||
case PathStyleFormat.LABEL => s".${value.toString}"
|
||||
case PathStyleFormat.MATRIX => s";$paramName=${value.toString}"
|
||||
|
||||
private inline def serializeArray(
|
||||
paramName: String,
|
||||
values: Seq[Primitive],
|
||||
inline format: PathStyleFormat,
|
||||
inline explode: Boolean
|
||||
): String = inline format match
|
||||
case PathStyleFormat.SIMPLE => values.map(_.toString).mkString(",")
|
||||
case PathStyleFormat.LABEL => inline if explode then values.map(_.toString).mkString(".", ".", "") else values.map(_.toString).mkString(".", ",", "")
|
||||
case PathStyleFormat.MATRIX => inline if explode then values.map(v => s";$paramName=${v.toString}").mkString else s";$paramName=" + values.map(_.toString).mkString(",")
|
||||
|
||||
private inline def serializeModel(
|
||||
paramName: String,
|
||||
keyValPairs: Seq[(String, Primitive)],
|
||||
inline format: PathStyleFormat,
|
||||
inline explode: Boolean
|
||||
): String = inline format match
|
||||
case PathStyleFormat.SIMPLE =>
|
||||
inline if explode then keyValPairs.map((k, v) => s"$k=${v.toString}").mkString(",")
|
||||
else keyValPairs.map((k, v) => s"$k,${v.toString}").mkString(",")
|
||||
case PathStyleFormat.LABEL =>
|
||||
inline if explode then keyValPairs.map((k, v) => s"$k=${v.toString}").mkString(".", ".", "")
|
||||
else keyValPairs.map((k, v) => s"$k,${v.toString}").mkString(".", ",", "")
|
||||
case PathStyleFormat.MATRIX =>
|
||||
inline if explode then keyValPairs.map((k, v) => s";$k=${v.toString}").mkString
|
||||
else keyValPairs.map((k, v) => s"$k,${v.toString}").mkString(s";$paramName=", ",", "")
|
||||
end PathSerializable
|
||||
|
||||
trait CookieSerializable[T]:
|
||||
inline def serialize(
|
||||
name: String,
|
||||
obj: T,
|
||||
inline explode: Boolean = true
|
||||
): Seq[(String, String)]
|
||||
|
||||
object CookieSerializable:
|
||||
inline def serialize[T](
|
||||
name: String,
|
||||
obj: T,
|
||||
inline explode: Boolean = true
|
||||
): Seq[(String, String)] =
|
||||
summonFrom {
|
||||
case t: CookieSerializable[T] => t.serialize(name, obj, explode)
|
||||
case _ =>
|
||||
inline obj match
|
||||
case primitive: Primitive =>
|
||||
serializePrimitive(name, primitive, explode)
|
||||
case array: Seq[Primitive] =>
|
||||
serializeArray(name, array, explode)
|
||||
case optPrimitive: Option[Primitive] =>
|
||||
optPrimitive.map(value => serializePrimitive(name, value, explode))
|
||||
.getOrElse(Seq.empty[(String, String)])
|
||||
case optArray: Option[Seq[Primitive]] =>
|
||||
optArray.map(serializeArray(name, _, explode))
|
||||
.getOrElse(Seq.empty[(String, String)])
|
||||
case freeObj: Map[String, Primitive] =>
|
||||
serializeModel(name, freeObj.map((key, value) => (key, value.toString)).toSeq, explode)
|
||||
case optObj: Option[t] =>
|
||||
inline summonInline[Mirror.Of[t]] match
|
||||
case mirror: Mirror.ProductOf[t] =>
|
||||
checkFields[mirror.MirroredElemTypes]
|
||||
val labels = allLabels[mirror.MirroredElemLabels]
|
||||
optObj.map { obj =>
|
||||
val keyVals = labels.zip(obj.asInstanceOf[Product].productIterator.toSeq.asInstanceOf[Seq[Primitive | Option[Primitive]]].map(flattenKeyVals))
|
||||
.filter((_, v) => v.isDefined)
|
||||
.map((k, v) => (k, v.get))
|
||||
serializeModel(name, keyVals, explode)
|
||||
}.getOrElse(Seq.empty[(String, String)])
|
||||
case mirror: Mirror.SumOf[t] => optObj.map(v => (name, writeToString(v)(summonInline[JsonValueCodec[mirror.MirroredMonoType]]))).toSeq
|
||||
case obj =>
|
||||
inline summonInline[Mirror.Of[T]] match
|
||||
case _: Mirror.SumOf[T] =>
|
||||
Seq(name -> writeToString(obj)(summonInline[JsonValueCodec[T]]))
|
||||
case mirror: Mirror.ProductOf[T] =>
|
||||
checkFields[mirror.MirroredElemTypes]
|
||||
val labels = allLabels[mirror.MirroredElemLabels]
|
||||
val keyVals = labels.zip(obj.asInstanceOf[Product].productIterator.toSeq.asInstanceOf[Seq[Primitive | Option[Primitive]]].map(flattenKeyVals))
|
||||
.filter((_, v) => v.isDefined)
|
||||
.map((k, v) => (k, v.get))
|
||||
serializeModel(name, keyVals, explode)
|
||||
}
|
||||
|
||||
private inline def serializePrimitive(
|
||||
paramName: String,
|
||||
value: Primitive,
|
||||
inline explode: Boolean
|
||||
): Seq[(String, String)] = Seq(paramName -> value.toString)
|
||||
|
||||
private inline def serializeArray(
|
||||
paramName: String,
|
||||
values: Seq[Primitive],
|
||||
inline explode: Boolean
|
||||
): Seq[(String, String)] =
|
||||
inline if explode then error("Not supported")
|
||||
else Seq(paramName -> values.map(_.toString).mkString(","))
|
||||
|
||||
private inline def serializeModel(
|
||||
paramName: String,
|
||||
keyValPairs: Seq[(String, Primitive)],
|
||||
inline explode: Boolean
|
||||
): Seq[(String, String)] =
|
||||
inline if explode then error("Not supported")
|
||||
else Seq(paramName -> keyValPairs.map((k, v) => s"$k,$v").mkString(","))
|
||||
end CookieSerializable
|
||||
|
||||
object Helpers:
|
||||
extension (request: sttp.client4.Request[?])
|
||||
def fileBody(file: Option[File] | File): sttp.client4.Request[?] =
|
||||
file match
|
||||
case f: File => request.body(f)
|
||||
case f: Option[File] => f.map(request.body(_)).getOrElse(request)
|
||||
|
||||
def auth(authConfig: Authorization, location: ApiKeyLocation = ApiKeyLocation.NOAPIKEY, keyParamName: String = ""): sttp.client4.Request[?] =
|
||||
authConfig match
|
||||
case Authorization.NoAuthorization => request
|
||||
case Authorization.BasicAuth(username, password) => request.auth.basic(username, password)
|
||||
case Authorization.BearerToken(token) => request.auth.bearer(token)
|
||||
case Authorization.ApiKey(apiKey) =>location match
|
||||
case ApiKeyLocation.HEADER => request.header(keyParamName, apiKey)
|
||||
case ApiKeyLocation.COOKIE => request.cookie(keyParamName, apiKey)
|
||||
case ApiKeyLocation.QUERY => request.copy(uri = request.uri.addParam(keyParamName, apiKey))
|
||||
case ApiKeyLocation.NOAPIKEY => request // since it can be called multiple times in request (when there are for example 2 auth methods) we want to make this call idempotent
|
25
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/javadoc.mustache
vendored
Normal file
25
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/javadoc.mustache
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{{#notes}}
|
||||
{{{.}}}
|
||||
|
||||
{{/notes}}
|
||||
Expected answers:
|
||||
{{#responses}}
|
||||
code {{code}} : {{{dataType}}} {{#message}}({{{.}}}){{/message}}
|
||||
{{#headers}}
|
||||
{{#-first}}
|
||||
Headers :
|
||||
{{/-first}}
|
||||
{{{baseName}}} - {{{description}}}
|
||||
{{/headers}}
|
||||
{{/responses}}
|
||||
{{#authMethods.0}}
|
||||
|
||||
Available security schemes:
|
||||
{{#authMethods}}
|
||||
{{name}} ({{type}})
|
||||
{{/authMethods}}
|
||||
{{/authMethods.0}}
|
||||
|
||||
{{#allParams}}
|
||||
@param {{{paramName}}} {{{description}}}
|
||||
{{/allParams}}
|
22
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/jsonSupport.mustache
vendored
Normal file
22
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/jsonSupport.mustache
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{{>licenseInfo}}
|
||||
package {{invokerPackage}}
|
||||
|
||||
{{#models.0}}
|
||||
import {{modelPackage}}.*
|
||||
{{/models.0}}
|
||||
import java.time.*
|
||||
import com.github.plokhotnyuk.jsoniter_scala.macros.*
|
||||
import com.github.plokhotnyuk.jsoniter_scala.core.*
|
||||
import com.github.plokhotnyuk.jsoniter_scala.circe.JsoniterScalaCodec.*
|
||||
|
||||
object JsonSupport extends AdditionalTypeSerializers:
|
||||
inline given CodecMakerConfig = CodecMakerConfig.withAllowRecursiveTypes(true)
|
||||
|
||||
inline def deriveJsonCodec[A](using inline config: CodecMakerConfig): JsonValueCodec[A] =
|
||||
JsonCodecMaker.make(config)
|
||||
|
||||
{{#jsonCodecNeedingTypes}}
|
||||
given {{key}}: JsonValueCodec[{{value}}] = deriveJsonCodec
|
||||
{{/jsonCodecNeedingTypes}}
|
||||
|
||||
|
11
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/licenseInfo.mustache
vendored
Normal file
11
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/licenseInfo.mustache
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* {{{appName}}}
|
||||
* {{{appDescription}}}
|
||||
*
|
||||
* {{#version}}The version of the OpenAPI document: {{{.}}}{{/version}}
|
||||
* {{#infoEmail}}Contact: {{{.}}}{{/infoEmail}}
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
1
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/methodParameters.mustache
vendored
Normal file
1
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/methodParameters.mustache
vendored
Normal file
@ -0,0 +1 @@
|
||||
{{#allParams.0}}({{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{/required}}{{^required}}{{#isContainer}}{{dataType}}{{/isContainer}}{{^isContainer}}Option[{{dataType}}]{{/isContainer}}{{/required}}{{^defaultValue}}{{^required}}{{^isContainer}} = scala.None{{/isContainer}}{{/required}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allParams}}){{/allParams.0}}{{#authMethods.0}}(using Auth <:< {{#authMethods}}{{#isApiKey}}{{invokerPackage}}.Authorization.ApiKey{{/isApiKey}}{{#isBasic}}{{#isBasicBasic}}{{invokerPackage}}.Authorization.BasicAuth{{/isBasicBasic}}{{#isBasicBearer}}{{invokerPackage}}.Authorization.BearerToken{{/isBasicBearer}}{{/isBasic}}{{^-last}} | {{/-last}}{{/authMethods}}){{/authMethods.0}}
|
113
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/model.mustache
vendored
Normal file
113
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/model.mustache
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
{{>licenseInfo}}
|
||||
package {{package}}
|
||||
|
||||
{{#imports}}
|
||||
import {{import}}
|
||||
{{/imports}}
|
||||
import com.github.plokhotnyuk.jsoniter_scala.macros.named
|
||||
|
||||
{{#models}}
|
||||
{{#model}}
|
||||
{{#description}}
|
||||
{{#javadocRenderer}}
|
||||
{{#title}}
|
||||
{{{.}}}
|
||||
{{/title}}
|
||||
{{{description}}}
|
||||
{{/javadocRenderer}}
|
||||
{{/description}}
|
||||
{{^isEnum}}
|
||||
case class {{classname}}(
|
||||
{{#vars}}
|
||||
{{#description}}
|
||||
/* {{{.}}} */
|
||||
{{/description}}
|
||||
@named("{{baseName}}") {{{name}}}: {{^required}}Option[{{/required}}{{^isEnum}}{{dataType}}{{/isEnum}}{{#isEnum}}{{^isArray}}{{classname}}Enums.{{datatypeWithEnum}}{{/isArray}}{{#isArray}}Seq[{{classname}}Enums.{{datatypeWithEnum}}]{{/isArray}}{{/isEnum}}{{^required}}] = scala.None{{/required}}{{^-last}},{{/-last}}
|
||||
{{/vars}}
|
||||
)
|
||||
{{/isEnum}}
|
||||
|
||||
{{#isEnum}}
|
||||
enum {{classname}}:
|
||||
{{#allowableValues}}
|
||||
{{#values}}
|
||||
case {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}}
|
||||
{{/values}}
|
||||
{{/allowableValues}}
|
||||
|
||||
object {{classname}}:
|
||||
import com.github.plokhotnyuk.jsoniter_scala.macros.*
|
||||
import com.github.plokhotnyuk.jsoniter_scala.core.*
|
||||
{{#isString}}
|
||||
given {{#fnCodecName}}{{classname}}{{/fnCodecName}}: JsonValueCodec[{{classname}}] = JsonCodecMaker.make {
|
||||
CodecMakerConfig{{#allowableValues}}
|
||||
.withAdtLeafClassNameMapper { x =>
|
||||
JsonCodecMaker.simpleClassName(x) match
|
||||
{{#values}}
|
||||
case "{{#fnEnumLeaf}}{{.}}{{/fnEnumLeaf}}" => "{{.}}"
|
||||
{{/values}}
|
||||
}{{/allowableValues}}
|
||||
.withDiscriminatorFieldName(scala.None)
|
||||
}
|
||||
{{/isString}}
|
||||
{{#isNumber}}
|
||||
given {{#fnCodecName}}{{datatypeWithEnum}}{{/fnCodecName}}: JsonValueCodec[{{datatypeWithEnum}}] = new JsonValueCodec[{{datatypeWithEnum}}]:
|
||||
import scala.util.{Try, Success, Failure}
|
||||
|
||||
override val nullValue: {{datatypeWithEnum}} = null
|
||||
|
||||
override def decodeValue(in: JsonReader, default: {{datatypeWithEnum}}): {{datatypeWithEnum}} =
|
||||
val x = in.readByte()
|
||||
Try { {{datatypeWithEnum}}.fromOrdinal(x) } match
|
||||
case Success(v) => v
|
||||
case Failure(_) => in.decodeError(s"unexpected number value: $x")
|
||||
|
||||
override def encodeValue(x: {{datatypeWithEnum}}, out: JsonWriter): Unit = out.writeVal(x.ordinal)
|
||||
{{/isNumber}}
|
||||
end {{classname}}
|
||||
|
||||
{{/isEnum}}
|
||||
{{#hasEnums}}
|
||||
object {{classname}}Enums:
|
||||
{{#vars}}
|
||||
{{#isEnum}}
|
||||
enum {{datatypeWithEnum}}:
|
||||
{{#_enum}}
|
||||
case {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}}
|
||||
{{/_enum}}
|
||||
|
||||
object {{datatypeWithEnum}}:
|
||||
import com.github.plokhotnyuk.jsoniter_scala.macros.*
|
||||
import com.github.plokhotnyuk.jsoniter_scala.core.*
|
||||
{{#isString}}
|
||||
given {{#fnCodecName}}{{datatypeWithEnum}}{{/fnCodecName}}: JsonValueCodec[{{datatypeWithEnum}}] = JsonCodecMaker.make {
|
||||
CodecMakerConfig
|
||||
.withAdtLeafClassNameMapper { x =>
|
||||
JsonCodecMaker.simpleClassName(x) match
|
||||
{{#_enum}}
|
||||
case "{{#fnEnumLeaf}}{{.}}{{/fnEnumLeaf}}" => "{{.}}"
|
||||
{{/_enum}}
|
||||
}
|
||||
.withDiscriminatorFieldName(scala.None)
|
||||
}
|
||||
{{/isString}}
|
||||
{{#isNumber}}
|
||||
given {{#fnCodecName}}{{datatypeWithEnum}}{{/fnCodecName}}: JsonValueCodec[{{datatypeWithEnum}}] = new JsonValueCodec[{{datatypeWithEnum}}]:
|
||||
import scala.util.{Try, Success, Failure}
|
||||
|
||||
override val nullValue: {{datatypeWithEnum}} = null
|
||||
|
||||
override def decodeValue(in: JsonReader, default: {{datatypeWithEnum}}): {{datatypeWithEnum}} =
|
||||
val x = in.readByte()
|
||||
Try { {{datatypeWithEnum}}.fromOrdinal(x) } match
|
||||
case Success(v) => v
|
||||
case Failure(_) => in.decodeError(s"unexpected number value: $x")
|
||||
|
||||
override def encodeValue(x: {{datatypeWithEnum}}, out: JsonWriter): Unit = out.writeVal(x.ordinal)
|
||||
{{/isNumber}}
|
||||
{{/isEnum}}
|
||||
{{/vars}}
|
||||
end {{classname}}Enums
|
||||
{{/hasEnums}}
|
||||
{{/model}}
|
||||
{{/models}}
|
@ -0,0 +1 @@
|
||||
{{{returnType}}}{{^returnType}}Unit{{/returnType}}
|
1
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/paramFormCreation.mustache
vendored
Normal file
1
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/paramFormCreation.mustache
vendored
Normal file
@ -0,0 +1 @@
|
||||
FormSerializable.serialize("{{baseName}}", {{{paramName}}}{{#style}}, FormStyleFormat.{{style.toUpperCase}}{{/style}}{{^style}}, FormStyleFormat.FORM{{/style}}, {{isExplode}})
|
@ -0,0 +1,18 @@
|
||||
{{#required}}
|
||||
{{#isFile}}
|
||||
Some(multipartFile("{{baseName}}", {{paramName}}))
|
||||
{{/isFile}}
|
||||
{{^isFile}}{{#isFormParam}}{{^isPrimitiveType}}
|
||||
Some(multipart("{{baseName}}", FormSerializable.serialize("{{baseName}}", {{{paramName}}}{{#style}}, FormStyleFormat.{{style.toUpperCase}}{{/style}}{{^style}}, FormStyleFormat.FORM{{/style}}, {{isExplode}}))){{/isPrimitiveType}}{{/isFormParam}}{{#isPrimitiveType}}
|
||||
Some(multipart("{{baseName}}", {{paramName}}.toString)){{/isPrimitiveType}}
|
||||
{{/isFile}}
|
||||
{{/required}}
|
||||
{{^required}}
|
||||
{{#isFile}}
|
||||
{{paramName}}.map(multipartFile("{{baseName}}", _))
|
||||
{{/isFile}}
|
||||
{{^isFile}}{{#isFormParam}}{{^isPrimitiveType}}
|
||||
{{paramName}}.map(value => multipart("{{baseName}}", FormSerializable.serialize("{{baseName}}", value{{#style}}, FormStyleFormat.{{style.toUpperCase}}{{/style}}{{^style}}, FormStyleFormat.FORM{{/style}}, {{isExplode}}))){{/isPrimitiveType}}{{/isFormParam}}{{#isPrimitiveType}}
|
||||
{{paramName}}.map(value => multipart("{{baseName}}", value.toString)){{/isPrimitiveType}}
|
||||
{{/isFile}}
|
||||
{{/required}}{{^-last}},{{/-last}}
|
@ -0,0 +1 @@
|
||||
sbt.version=1.9.9
|
1
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/responseState.mustache
vendored
Normal file
1
modules/openapi-generator/src/main/resources/scala-sttp4-jsoniter/responseState.mustache
vendored
Normal file
@ -0,0 +1 @@
|
||||
{{#isDefault}}Success{{/isDefault}}{{^isDefault}}Error{{/isDefault}}
|
237
rebuild.sc
Executable file
237
rebuild.sc
Executable file
@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env scala
|
||||
//> using toolkit 0.6.0
|
||||
//> using scala 3.3.1
|
||||
|
||||
import os.*
|
||||
|
||||
object Debug:
|
||||
var enabled = false
|
||||
|
||||
enum Command:
|
||||
case Clean, Generate, GenerateStrict, GenerateOldGen, Strict, Exit
|
||||
|
||||
enum Target:
|
||||
case Specific(name: String)
|
||||
case All
|
||||
|
||||
def say(message: String)(using pwd: Path) =
|
||||
val command = List("say", message)
|
||||
if Debug.enabled then println(s"Running command: ${command.mkString(" ")}")
|
||||
os.proc(command).call(stdout = os.Inherit, stderr = os.Inherit)
|
||||
|
||||
case class Project(
|
||||
project: String,
|
||||
projectName: String,
|
||||
projectGroupId: String,
|
||||
projectArtifactId: String,
|
||||
projectMainPackage: String,
|
||||
projectVersion: String,
|
||||
generatorName: String,
|
||||
skipValidate: Boolean,
|
||||
additionalProps: String = "",
|
||||
schemaMappings: String = "",
|
||||
typeMappings: String = "",
|
||||
importMappings: String = ""
|
||||
)
|
||||
|
||||
val projects = Map(
|
||||
"mattermost" -> Project(
|
||||
project = "mattermost-scala",
|
||||
projectName = "mattermost-scala",
|
||||
projectGroupId = "ma.chinespirit",
|
||||
projectArtifactId = "mattermost-scala",
|
||||
projectMainPackage = "ma.chinespirit.mm",
|
||||
projectVersion = "1.0.0-SNAPSHOT",
|
||||
generatorName = "scala-sttp4-jsoniter",
|
||||
skipValidate = false
|
||||
),
|
||||
"kubernetes" -> Project(
|
||||
project = "kubeapi-scala",
|
||||
projectName = "kubeapi-scala",
|
||||
projectGroupId = "ma.chinespirit",
|
||||
projectArtifactId = "kubeapi-scala",
|
||||
projectMainPackage = "ma.chinespirit.kube",
|
||||
projectVersion = "1.0.0-SNAPSHOT",
|
||||
generatorName = "scala-sttp4-jsoniter",
|
||||
skipValidate = false,
|
||||
schemaMappings = "",
|
||||
typeMappings = "",
|
||||
importMappings = ""
|
||||
),
|
||||
"stripe" -> Project(
|
||||
project = "stripe-scala",
|
||||
projectName = "stripe-scala",
|
||||
projectGroupId = "ma.chinespirit",
|
||||
projectArtifactId = "stripe-scala",
|
||||
projectMainPackage = "ma.chinespirit.stripe",
|
||||
projectVersion = "1.0.0-SNAPSHOT",
|
||||
generatorName = "scala-sttp4-jsoniter",
|
||||
skipValidate = false
|
||||
),
|
||||
"github" -> Project(
|
||||
project = "github-scala",
|
||||
projectName = "github-scala",
|
||||
projectGroupId = "ma.chinespirit",
|
||||
projectArtifactId = "github-scala",
|
||||
projectMainPackage = "ma.chinespirit.github",
|
||||
projectVersion = "1.0.0-SNAPSHOT",
|
||||
generatorName = "scala-sttp4-jsoniter",
|
||||
skipValidate = false
|
||||
),
|
||||
"spotify" -> Project(
|
||||
project = "spotify-scala",
|
||||
projectName = "spotify-scala",
|
||||
projectGroupId = "ma.chinespirit",
|
||||
projectArtifactId = "spotify-scala",
|
||||
projectMainPackage = "ma.chinespirit.spotify",
|
||||
projectVersion = "1.0.0-SNAPSHOT",
|
||||
generatorName = "scala-sttp4-jsoniter",
|
||||
skipValidate = false
|
||||
)
|
||||
)
|
||||
|
||||
def cleanMaven(using pwd: Path) =
|
||||
val command = List("./mvnw", "clean")
|
||||
if Debug.enabled then println(s"Running command: ${command.mkString(" ")}")
|
||||
os.proc(command).call(stdout = os.Inherit, stderr = os.Inherit)
|
||||
|
||||
def installMaven(using pwd: Path) =
|
||||
val command = List("./mvnw", "install", "-DskipTests", "-Dmaven.javadoc.skip=true")
|
||||
if Debug.enabled then println(s"Running command: ${command.mkString(" ")}")
|
||||
os.proc(command).call(stdout = os.Inherit, stderr = os.Inherit)
|
||||
|
||||
def cleanupGeneratedFiles(project: Project, projectRootPath: Path) =
|
||||
val basePackage = os.SubPath(project.projectMainPackage.replace(".", "/"))
|
||||
os.remove.all(projectRootPath / "build.sbt")
|
||||
os.remove.all(projectRootPath / "target")
|
||||
os.remove.all(projectRootPath / "project")
|
||||
os.remove.all(projectRootPath / "README.md")
|
||||
os.remove.all(projectRootPath / "src" / "main" / "scala" / basePackage / "api")
|
||||
os.remove.all(projectRootPath / "src" / "main" / "scala" / basePackage / "model")
|
||||
os.remove.all(projectRootPath / "src" / "main" / "scala" / basePackage / "core")
|
||||
|
||||
def runGeneratorJsoniter(project: Project, projectRootPath: Path, strict: Boolean = false)(using pwd: Path) =
|
||||
val additionalProps = {
|
||||
val base = s"mainPackage=${project.projectMainPackage},groupId=${project.projectGroupId},artifactId=${project.projectArtifactId},artifactVersion=${project.projectVersion}"
|
||||
val extended = if project.additionalProps.nonEmpty then s"$base,${project.additionalProps}" else base
|
||||
|
||||
List(s"--additional-properties", extended)
|
||||
}
|
||||
val validateFlag = if project.skipValidate && !strict then List("--skip-validate-spec") else Nil
|
||||
val schemaMappings = if project.schemaMappings.nonEmpty then List("--schema-mappings", project.schemaMappings) else Nil
|
||||
val typeMappings = if project.typeMappings.nonEmpty then List("--type-mappings", project.typeMappings) else Nil
|
||||
val importMappings = if project.importMappings.nonEmpty then List("--import-mappings", project.importMappings) else Nil
|
||||
|
||||
val command = List(
|
||||
List("java", "-jar", "modules/openapi-generator-cli/target/openapi-generator-cli.jar", "generate",
|
||||
"-i", s"$projectRootPath/openapi.json",
|
||||
"--generator-name", project.generatorName,
|
||||
"-o", projectRootPath.toString),
|
||||
validateFlag,
|
||||
additionalProps,
|
||||
schemaMappings,
|
||||
typeMappings,
|
||||
importMappings
|
||||
).flatten
|
||||
|
||||
if Debug.enabled then println(s"Running command: ${command.mkString(" ")}")
|
||||
os.proc(command).call(stdout = os.Inherit, stderr = os.Inherit)
|
||||
|
||||
def runGeneratorOld(project: Project, projectRootPath: Path)(using pwd: Path) =
|
||||
val additionalProps = {
|
||||
val base = s"mainPackage=${project.projectMainPackage},groupId=${project.projectGroupId},artifactId=${project.projectArtifactId},artifactVersion=${project.projectVersion},jsonLibrary=circe"
|
||||
val extended = if project.additionalProps.nonEmpty then s"$base,${project.additionalProps}" else base
|
||||
|
||||
List("--additional-properties", extended)
|
||||
}
|
||||
val validateFlag = if project.skipValidate then List("--skip-validate-spec") else Nil
|
||||
val schemaMappings = if project.schemaMappings.nonEmpty then List("--schema-mappings", project.schemaMappings) else Nil
|
||||
val typeMappings = if project.typeMappings.nonEmpty then List("--type-mappings", project.typeMappings) else Nil
|
||||
val importMappings = if project.importMappings.nonEmpty then List("--import-mappings", project.importMappings) else Nil
|
||||
|
||||
val command = List(
|
||||
List("openapi-generator-cli", "generate",
|
||||
"-i", s"$projectRootPath/openapi.json",
|
||||
"--generator-name", "scala-sttp4",
|
||||
"-o", projectRootPath.toString),
|
||||
validateFlag,
|
||||
additionalProps,
|
||||
schemaMappings,
|
||||
typeMappings,
|
||||
importMappings
|
||||
).flatten
|
||||
|
||||
if Debug.enabled then println(s"Running command: ${command.mkString(" ")}")
|
||||
os.proc(command).call(stdout = os.Inherit, stderr = os.Inherit)
|
||||
|
||||
def processProject(project: Project, cmd: Command)(using pwd: Path) =
|
||||
val projectRootPath = pwd / os.up / project.project
|
||||
cmd match
|
||||
case Command.Clean =>
|
||||
cleanMaven
|
||||
cleanupGeneratedFiles(project, projectRootPath)
|
||||
case Command.Generate =>
|
||||
runGeneratorJsoniter(project, projectRootPath)
|
||||
case Command.GenerateStrict =>
|
||||
runGeneratorJsoniter(project, projectRootPath, strict = true)
|
||||
case Command.GenerateOldGen =>
|
||||
cleanupGeneratedFiles(project, projectRootPath)
|
||||
runGeneratorOld(project, projectRootPath)
|
||||
case Command.Strict =>
|
||||
cleanMaven
|
||||
cleanupGeneratedFiles(project, projectRootPath)
|
||||
installMaven
|
||||
runGeneratorJsoniter(project, projectRootPath, strict = true)
|
||||
case Command.Exit =>
|
||||
// Do nothing
|
||||
|
||||
def main(): Unit =
|
||||
given pwd: Path = os.pwd
|
||||
|
||||
println("Args: " + args.mkString(" "))
|
||||
|
||||
try
|
||||
// Handle debug flag
|
||||
val filteredArgs = args.toList.filter { arg =>
|
||||
if arg == "--debug-script" then
|
||||
Debug.enabled = true
|
||||
false
|
||||
else true
|
||||
}
|
||||
|
||||
val (cmdStr, target) = filteredArgs match
|
||||
case cmd :: "all" :: _ => (cmd, Target.All)
|
||||
case cmd :: name :: _ => (cmd, Target.Specific(name))
|
||||
case cmd :: Nil => (cmd, Target.All)
|
||||
case _ => ("exit", Target.All)
|
||||
|
||||
val cmd = cmdStr match
|
||||
case "clean" => Command.Clean
|
||||
case "generate" => Command.Generate
|
||||
case "generate-strict" => Command.GenerateStrict
|
||||
case "generate-old-gen" => Command.GenerateOldGen
|
||||
case "strict" => Command.Strict
|
||||
case "exit" => Command.Exit
|
||||
case _ => Command.Exit
|
||||
|
||||
(target, cmd) match
|
||||
case (_, Command.Exit) =>
|
||||
println("Usage: scala rebuild.sc <command> [project]")
|
||||
println("Available commands: clean, generate, generate-strict, upstream, strict")
|
||||
case (Target.All, _) =>
|
||||
projects.values.foreach(project => processProject(project, cmd))
|
||||
say("It is done")
|
||||
case (Target.Specific(projectName), _) =>
|
||||
projects.get(projectName) match
|
||||
case Some(project) =>
|
||||
processProject(project, cmd)
|
||||
say("It is done")
|
||||
case None =>
|
||||
System.err.println(s"Project '$projectName' not found. Available projects: ${projects.keys.mkString(", ")}")
|
||||
sys.exit(1)
|
||||
catch
|
||||
case e: Exception =>
|
||||
say("You dun goofed")
|
||||
throw e
|
||||
|
||||
main()
|
Loading…
x
Reference in New Issue
Block a user