Skip to content

Commit 7f05c1f

Browse files
[kotlin-client] New generator: kotlin-jvm-spring-restclient (#17366)
* Created kotlin jvm spring restclient * Fixed kotlin jvm-spring-restclient * Fixed earlier problems * Fixed earlier problems * Updated kotlin.md
1 parent 9eb5882 commit 7f05c1f

File tree

43 files changed

+3163
-6
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+3163
-6
lines changed

.github/workflows/samples-kotlin-client.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ jobs:
6363
- samples/client/petstore/kotlin-jvm-vertx-moshi
6464
- samples/client/petstore/kotlin-jvm-spring-2-webclient
6565
- samples/client/petstore/kotlin-jvm-spring-3-webclient
66+
- samples/client/petstore/kotlin-jvm-spring-3-restclient
6667
- samples/client/petstore/kotlin-spring-cloud
6768
- samples/client/petstore/kotlin-name-parameter-mappings
6869
- samples/client/others/kotlin-jvm-okhttp-parameter-tests

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,7 @@ Here is a list of template creators:
996996
* Kotlin (MultiPlatform): @andrewemery
997997
* Kotlin (Volley): @alisters
998998
* Kotlin (jvm-spring-webclient): @stefankoppier
999+
* Kotlin (jvm-spring-restclient): @stefankoppier
9991000
* Lua: @daurnimator
10001001
* N4JS: @mmews-n4
10011002
* Nim: @hokamoto
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
generatorName: kotlin
2+
outputDir: samples/client/petstore/kotlin-jvm-spring-3-restclient
3+
library: jvm-spring-restclient
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
6+
additionalProperties:
7+
artifactId: kotlin-petstore-spring-restclient
8+
enumUnknownDefaultCase: true
9+
serializationLibrary: jackson
10+
useSpringBoot3: true

docs/generators/kotlin.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
2828
|generateRoomModels|Generate Android Room database models in addition to API models (JVM Volley library only)| |false|
2929
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
3030
|idea|Add IntellJ Idea plugin and mark Kotlin main and test folders as source folders.| |false|
31-
|library|Library template (sub-template) to use|<dl><dt>**jvm-ktor**</dt><dd>Platform: Java Virtual Machine. HTTP client: Ktor 1.6.7. JSON processing: Gson, Jackson (default).</dd><dt>**jvm-okhttp4**</dt><dd>[DEFAULT] Platform: Java Virtual Machine. HTTP client: OkHttp 4.2.0 (Android 5.0+ and Java 8+). JSON processing: Moshi 1.8.0.</dd><dt>**jvm-okhttp3**</dt><dd>Platform: Java Virtual Machine. HTTP client: OkHttp 3.12.4 (Android 2.3+ and Java 7+). JSON processing: Moshi 1.8.0. (DEPRECATED: this option will be remove in 7.x release)</dd><dt>**jvm-spring-webclient**</dt><dd>Platform: Java Virtual Machine. HTTP: Spring 5 (or 6 with useSpringBoot3 enabled) WebClient. JSON processing: Jackson.</dd><dt>**jvm-retrofit2**</dt><dd>Platform: Java Virtual Machine. HTTP client: Retrofit 2.6.2.</dd><dt>**multiplatform**</dt><dd>Platform: Kotlin multiplatform. HTTP client: Ktor 1.6.7. JSON processing: Kotlinx Serialization: 1.2.1.</dd><dt>**jvm-volley**</dt><dd>Platform: JVM for Android. HTTP client: Volley 1.2.1. JSON processing: gson 2.8.9</dd><dt>**jvm-vertx**</dt><dd>Platform: Java Virtual Machine. HTTP client: Vert.x Web Client. JSON processing: Moshi, Gson or Jackson.</dd></dl>|jvm-okhttp4|
31+
|library|Library template (sub-template) to use|<dl><dt>**jvm-ktor**</dt><dd>Platform: Java Virtual Machine. HTTP client: Ktor 1.6.7. JSON processing: Gson, Jackson (default).</dd><dt>**jvm-okhttp4**</dt><dd>[DEFAULT] Platform: Java Virtual Machine. HTTP client: OkHttp 4.2.0 (Android 5.0+ and Java 8+). JSON processing: Moshi 1.8.0.</dd><dt>**jvm-okhttp3**</dt><dd>Platform: Java Virtual Machine. HTTP client: OkHttp 3.12.4 (Android 2.3+ and Java 7+). JSON processing: Moshi 1.8.0. (DEPRECATED: this option will be remove in 7.x release)</dd><dt>**jvm-spring-webclient**</dt><dd>Platform: Java Virtual Machine. HTTP: Spring 5 (or 6 with useSpringBoot3 enabled) WebClient. JSON processing: Jackson.</dd><dt>**jvm-spring-restclient**</dt><dd>Platform: Java Virtual Machine. HTTP: Spring 6 RestClient. JSON processing: Jackson.</dd><dt>**jvm-retrofit2**</dt><dd>Platform: Java Virtual Machine. HTTP client: Retrofit 2.6.2.</dd><dt>**multiplatform**</dt><dd>Platform: Kotlin multiplatform. HTTP client: Ktor 1.6.7. JSON processing: Kotlinx Serialization: 1.2.1.</dd><dt>**jvm-volley**</dt><dd>Platform: JVM for Android. HTTP client: Volley 1.2.1. JSON processing: gson 2.8.9</dd><dt>**jvm-vertx**</dt><dd>Platform: Java Virtual Machine. HTTP client: Vert.x Web Client. JSON processing: Moshi, Gson or Jackson.</dd></dl>|jvm-okhttp4|
3232
|modelMutable|Create mutable models| |false|
3333
|moshiCodeGen|Whether to enable codegen with the Moshi library. Refer to the [official Moshi doc](https://github.com/square/moshi#codegen) for more info.| |false|
3434
|nullableReturnType|Nullable return type| |false|

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
6565
protected static final String MULTIPLATFORM = "multiplatform";
6666
protected static final String JVM_VOLLEY = "jvm-volley";
6767
protected static final String JVM_VERTX = "jvm-vertx";
68+
protected static final String JVM_SPRING = "jvm-spring";
6869
protected static final String JVM_SPRING_WEBCLIENT = "jvm-spring-webclient";
70+
protected static final String JVM_SPRING_RESTCLIENT = "jvm-spring-restclient";
6971

7072
public static final String USE_RX_JAVA3 = "useRxJava3";
7173
public static final String USE_COROUTINES = "useCoroutines";
@@ -221,6 +223,7 @@ public KotlinClientCodegen() {
221223
supportedLibraries.put(JVM_OKHTTP4, "[DEFAULT] Platform: Java Virtual Machine. HTTP client: OkHttp 4.2.0 (Android 5.0+ and Java 8+). JSON processing: Moshi 1.8.0.");
222224
supportedLibraries.put(JVM_OKHTTP3, "Platform: Java Virtual Machine. HTTP client: OkHttp 3.12.4 (Android 2.3+ and Java 7+). JSON processing: Moshi 1.8.0. (DEPRECATED: this option will be remove in 7.x release)");
223225
supportedLibraries.put(JVM_SPRING_WEBCLIENT, "Platform: Java Virtual Machine. HTTP: Spring 5 (or 6 with useSpringBoot3 enabled) WebClient. JSON processing: Jackson.");
226+
supportedLibraries.put(JVM_SPRING_RESTCLIENT, "Platform: Java Virtual Machine. HTTP: Spring 6 RestClient. JSON processing: Jackson.");
224227
supportedLibraries.put(JVM_RETROFIT2, "Platform: Java Virtual Machine. HTTP client: Retrofit 2.6.2.");
225228
supportedLibraries.put(MULTIPLATFORM, "Platform: Kotlin multiplatform. HTTP client: Ktor 1.6.7. JSON processing: Kotlinx Serialization: 1.2.1.");
226229
supportedLibraries.put(JVM_VOLLEY, "Platform: JVM for Android. HTTP client: Volley 1.2.1. JSON processing: gson 2.8.9");
@@ -457,7 +460,10 @@ public void processOpts() {
457460
processJVMRetrofit2Library(infrastructureFolder);
458461
break;
459462
case JVM_SPRING_WEBCLIENT:
460-
processJVMSpringWebClientLibrary(infrastructureFolder);
463+
processJvmSpringWebClientLibrary(infrastructureFolder);
464+
break;
465+
case JVM_SPRING_RESTCLIENT:
466+
processJvmSpringRestClientLibrary(infrastructureFolder);
461467
break;
462468
case MULTIPLATFORM:
463469
processMultiplatformLibrary(infrastructureFolder);
@@ -746,18 +752,32 @@ private void processJVMOkHttpLibrary(final String infrastructureFolder) {
746752
supportingFiles.add(new SupportingFile("infrastructure/ApiResponse.kt.mustache", infrastructureFolder, "ApiResponse.kt"));
747753
}
748754

749-
private void processJVMSpringWebClientLibrary(final String infrastructureFolder) {
755+
private void proccessJvmSpring(final String infrastructureFolder) {
750756
if (getSerializationLibrary() != SERIALIZATION_LIBRARY_TYPE.jackson) {
751757
throw new RuntimeException("This library currently only supports jackson serialization. Try adding '--additional-properties serializationLibrary=jackson' to your command.");
752758
}
753759

754760
commonJvmMultiplatformSupportingFiles(infrastructureFolder);
755761
addSupportingSerializerAdapters(infrastructureFolder);
756762

763+
additionalProperties.put(JVM_SPRING, true);
757764
additionalProperties.put(JVM, true);
765+
}
766+
767+
private void processJvmSpringWebClientLibrary(final String infrastructureFolder) {
768+
proccessJvmSpring(infrastructureFolder);
758769
additionalProperties.put(JVM_SPRING_WEBCLIENT, true);
759770
}
760771

772+
private void processJvmSpringRestClientLibrary(final String infrastructureFolder) {
773+
if (additionalProperties.getOrDefault(USE_SPRING_BOOT3, false).equals(false)) {
774+
throw new RuntimeException("This library muse use spring boot 3. Try adding '--additional-properties useSpringBoot3=true' to your command.");
775+
}
776+
777+
proccessJvmSpring(infrastructureFolder);
778+
additionalProperties.put(JVM_SPRING_RESTCLIENT, true);
779+
}
780+
761781
private void processMultiplatformLibrary(final String infrastructureFolder) {
762782
commonJvmMultiplatformSupportingFiles(infrastructureFolder);
763783
additionalProperties.put(MULTIPLATFORM, true);

modules/openapi-generator/src/main/resources/kotlin-client/build.gradle.mustache

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@ buildscript {
2828
{{#jvm-vertx}}
2929
ext.vertx_version = "4.3.3"
3030
{{/jvm-vertx}}
31-
{{#jvm-spring-webclient}}
31+
{{#jvm-spring}}
3232
{{#useSpringBoot3}}
33-
ext.spring_boot_version = "3.1.0"
33+
ext.spring_boot_version = "3.2.0"
3434
{{/useSpringBoot3}}
3535
{{^useSpringBoot3}}
3636
ext.spring_boot_version = "2.7.12"
3737
{{/useSpringBoot3}}
38+
{{#jvm-spring-webclient}}
3839
ext.reactor_version = "3.5.6"
3940
{{/jvm-spring-webclient}}
41+
{{/jvm-spring}}
4042
ext.spotless_version = "6.13.0"
4143

4244
repositories {
@@ -113,6 +115,13 @@ kotlin {
113115
}
114116
}
115117
{{/useSpringBoot3}}{{/jvm-spring-webclient}}
118+
{{#jvm-spring-restclient}}
119+
kotlin {
120+
jvmToolchain {
121+
languageVersion.set(JavaLanguageVersion.of(17))
122+
}
123+
}
124+
{{/jvm-spring-restclient}}
116125
dependencies {
117126
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
118127
{{^doNotUseRxAndCoroutines}}
@@ -165,6 +174,9 @@ dependencies {
165174
implementation "org.springframework.boot:spring-boot-starter-webflux:$spring_boot_version"
166175
implementation "io.projectreactor:reactor-core:$reactor_version"
167176
{{/jvm-spring-webclient}}
177+
{{#jvm-spring-restclient}}
178+
implementation "org.springframework.boot:spring-boot-starter-web:$spring_boot_version"
179+
{{/jvm-spring-restclient}}
168180
{{#threetenbp}}
169181
implementation "org.threeten:threetenbp:1.6.8"
170182
{{/threetenbp}}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
{{>licenseInfo}}
2+
package {{apiPackage}}
3+
4+
{{#jackson}}
5+
import com.fasterxml.jackson.annotation.JsonProperty
6+
{{/jackson}}
7+
8+
import org.springframework.web.client.RestClient
9+
import org.springframework.web.client.RestClientResponseException
10+
11+
{{#jackson}}
12+
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
13+
{{/jackson}}
14+
import org.springframework.http.ResponseEntity
15+
import org.springframework.http.MediaType
16+
17+
{{#imports}}import {{import}}
18+
{{/imports}}
19+
import {{packageName}}.infrastructure.*
20+
21+
{{#operations}}
22+
{{#nonPublicApi}}internal {{/nonPublicApi}}class {{classname}}(client: RestClient) : ApiClient(client) {
23+
24+
{{#jackson}}
25+
constructor(baseUrl: String) : this(RestClient.builder()
26+
.baseUrl(baseUrl)
27+
.messageConverters { it.add(MappingJackson2HttpMessageConverter()) }
28+
.build()
29+
)
30+
{{/jackson}}
31+
32+
{{#operation}}
33+
{{#allParams}}
34+
{{#isEnum}}
35+
/**
36+
* enum for parameter {{paramName}}
37+
*/
38+
{{#nonPublicApi}}internal {{/nonPublicApi}}enum class {{enumName}}{{operationIdCamelCase}}(val value: {{^isContainer}}{{dataType}}{{/isContainer}}{{#isContainer}}kotlin.String{{/isContainer}}) {
39+
{{^enumUnknownDefaultCase}}
40+
{{#allowableValues}}
41+
{{#enumVars}}
42+
{{#jackson}}
43+
@JsonProperty(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}
44+
{{/jackson}}
45+
{{/enumVars}}
46+
{{/allowableValues}}
47+
{{/enumUnknownDefaultCase}}
48+
{{#enumUnknownDefaultCase}}
49+
{{#allowableValues}}
50+
{{#enumVars}}
51+
{{^-last}}
52+
{{#jackson}}
53+
@JsonProperty(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}),
54+
{{/jackson}}
55+
{{/-last}}
56+
{{/enumVars}}
57+
{{/allowableValues}}
58+
{{/enumUnknownDefaultCase}}
59+
}
60+
61+
{{/isEnum}}
62+
{{/allParams}}
63+
64+
@Throws(RestClientResponseException::class)
65+
{{#isDeprecated}}
66+
@Deprecated(message = "This operation is deprecated.")
67+
{{/isDeprecated}}
68+
fun {{operationId}}({{#allParams}}{{{paramName}}}: {{#isEnum}}{{#isContainer}}kotlin.collections.List<{{enumName}}{{operationIdCamelCase}}>{{/isContainer}}{{^isContainer}}{{enumName}}{{operationIdCamelCase}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}?{{/required}}{{^-last}}, {{/-last}}{{/allParams}}): {{#returnType}}{{{returnType}}}{{#nullableReturnType}}?{{/nullableReturnType}}{{/returnType}}{{^returnType}}Unit{{/returnType}} {
69+
return {{operationId}}WithHttpInfo({{#allParams}}{{{paramName}}} = {{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}})
70+
.body!!
71+
}
72+
73+
@Throws(RestClientResponseException::class)
74+
{{#isDeprecated}}
75+
@Deprecated(message = "This operation is deprecated.")
76+
{{/isDeprecated}}
77+
fun {{operationId}}WithHttpInfo({{#allParams}}{{{paramName}}}: {{#isEnum}}{{#isContainer}}kotlin.collections.List<{{enumName}}{{operationIdCamelCase}}>{{/isContainer}}{{^isContainer}}{{enumName}}{{operationIdCamelCase}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}?{{/required}}{{^-last}}, {{/-last}}{{/allParams}}): ResponseEntity<{{#returnType}}{{{returnType}}}{{#nullableReturnType}}?{{/nullableReturnType}}{{/returnType}}{{^returnType}}Unit{{/returnType}}> {
78+
val localVariableConfig = {{operationId}}RequestConfig({{#allParams}}{{{paramName}}} = {{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}})
79+
return request<{{#hasBodyParam}}{{#bodyParams}}{{{dataType}}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{^hasFormParams}}Unit{{/hasFormParams}}{{#hasFormParams}}Map<String, PartConfig<*>>{{/hasFormParams}}{{/hasBodyParam}}, {{{returnType}}}{{^returnType}}Unit{{/returnType}}>(
80+
localVariableConfig
81+
)
82+
}
83+
84+
{{#isDeprecated}}
85+
@Deprecated(message = "This operation is deprecated.")
86+
{{/isDeprecated}}
87+
fun {{operationId}}RequestConfig({{#allParams}}{{{paramName}}}: {{#isEnum}}{{#isContainer}}kotlin.collections.List<{{enumName}}{{operationIdCamelCase}}>{{/isContainer}}{{^isContainer}}{{enumName}}{{operationIdCamelCase}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}?{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) : RequestConfig<{{#hasBodyParam}}{{#bodyParams}}{{{dataType}}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{^hasFormParams}}Unit{{/hasFormParams}}{{#hasFormParams}}Map<String, PartConfig<*>>{{/hasFormParams}}{{/hasBodyParam}}> {
88+
val localVariableBody = {{#hasBodyParam}}{{!
89+
}}{{#bodyParams}}{{{paramName}}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{!
90+
}}{{^hasFormParams}}null{{/hasFormParams}}{{!
91+
}}{{#hasFormParams}}mapOf({{#formParams}}
92+
"{{{baseName}}}" to PartConfig(body = {{{paramName}}}{{#isEnum}}.value{{/isEnum}}, headers = mutableMapOf({{#contentType}}"Content-Type" to "{{contentType}}"{{/contentType}})),{{!
93+
}}{{/formParams}}){{/hasFormParams}}{{!
94+
}}{{/hasBodyParam}}
95+
val localVariableQuery = {{^hasQueryParams}}mutableMapOf<kotlin.String, kotlin.collections.List<kotlin.String>>()
96+
{{/hasQueryParams}}{{#hasQueryParams}}mutableMapOf<kotlin.String, kotlin.collections.List<kotlin.String>>()
97+
.apply {
98+
{{#queryParams}}
99+
{{^required}}
100+
if ({{{paramName}}} != null) {
101+
put("{{baseName}}", {{#isContainer}}toMultiValue({{{paramName}}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString({{{paramName}}}){{/isDateTime}}{{#isDate}}parseDateToQueryString({{{paramName}}}){{/isDate}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.toString(){{/isDate}}{{/isDateTime}}){{/isContainer}})
102+
}
103+
{{/required}}
104+
{{#required}}
105+
{{#isNullable}}
106+
if ({{{paramName}}} != null) {
107+
put("{{baseName}}", {{#isContainer}}toMultiValue({{{paramName}}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString({{{paramName}}}){{/isDateTime}}{{#isDate}}parseDateToQueryString({{{paramName}}}){{/isDate}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.toString(){{/isDate}}{{/isDateTime}}){{/isContainer}})
108+
}
109+
{{/isNullable}}
110+
{{^isNullable}}
111+
put("{{baseName}}", {{#isContainer}}toMultiValue({{{paramName}}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString({{{paramName}}}){{/isDateTime}}{{#isDate}}parseDateToQueryString({{{paramName}}}){{/isDate}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.toString(){{/isDate}}{{/isDateTime}}){{/isContainer}})
112+
{{/isNullable}}
113+
{{/required}}
114+
{{/queryParams}}
115+
}
116+
{{/hasQueryParams}}
117+
val localVariableHeaders: MutableMap<String, String> = mutableMapOf({{#hasFormParams}}"Content-Type" to {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}}{{/hasFormParams}})
118+
{{#headerParams}}
119+
{{{paramName}}}{{^required}}?{{/required}}.apply { localVariableHeaders["{{baseName}}"] = {{#isContainer}}this.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}this.toString(){{/isContainer}} }
120+
{{/headerParams}}
121+
{{^hasFormParams}}{{#hasConsumes}}{{#consumes}}localVariableHeaders["Content-Type"] = "{{{mediaType}}}"
122+
{{/consumes}}{{/hasConsumes}}{{/hasFormParams}}{{#hasProduces}}localVariableHeaders["Accept"] = "{{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}"
123+
{{/hasProduces}}
124+
125+
return RequestConfig(
126+
method = RequestMethod.{{httpMethod}},
127+
path = "{{path}}"{{#pathParams}}.replace("{"+"{{baseName}}"+"}", encodeURIComponent({{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}{{{paramName}}}{{#isEnum}}.value{{/isEnum}}.toString(){{/isContainer}})){{/pathParams}},
128+
query = localVariableQuery,
129+
headers = localVariableHeaders,
130+
requiresAuthentication = {{#hasAuthMethods}}true{{/hasAuthMethods}}{{^hasAuthMethods}}false{{/hasAuthMethods}},
131+
body = localVariableBody
132+
)
133+
}
134+
135+
{{/operation}}
136+
}
137+
{{/operations}}

0 commit comments

Comments
 (0)