11package com.avsystem.justworks.core.gen
22
3+ import com.avsystem.justworks.core.model.ApiKeyLocation
4+ import com.avsystem.justworks.core.model.SecurityScheme
35import com.squareup.kotlinpoet.CodeBlock
46import com.squareup.kotlinpoet.ContextParameter
57import com.squareup.kotlinpoet.ExperimentalKotlinPoetApi
@@ -30,7 +32,7 @@ object ApiClientBaseGenerator {
3032 private const val SUCCESS_BODY = " successBody"
3133 private const val SERIALIZERS_MODULE_PARAM = " serializersModule"
3234
33- fun generate (): FileSpec {
35+ fun generate (securitySchemes : List < SecurityScheme > ): FileSpec {
3436 val t = TypeVariableName (" T" ).copy(reified = true )
3537
3638 return FileSpec
@@ -39,7 +41,7 @@ object ApiClientBaseGenerator {
3941 .addFunction(buildMapToResult(t))
4042 .addFunction(buildToResult(t))
4143 .addFunction(buildToEmptyResult())
42- .addType(buildApiClientBaseClass())
44+ .addType(buildApiClientBaseClass(securitySchemes ))
4345 .build()
4446 }
4547
@@ -103,26 +105,33 @@ object ApiClientBaseGenerator {
103105 .addStatement(" return %L { Unit }" , MAP_TO_RESULT )
104106 .build()
105107
106- private fun buildApiClientBaseClass (): TypeSpec {
108+ private fun buildApiClientBaseClass (securitySchemes : List < SecurityScheme > ): TypeSpec {
107109 val tokenType = LambdaTypeName .get(returnType = STRING )
110+ val authParams = buildAuthConstructorParams(securitySchemes)
108111
109- val constructor = FunSpec
112+ val constructorBuilder = FunSpec
110113 .constructorBuilder()
111114 .addParameter(BASE_URL , STRING )
112- .addParameter( TOKEN , tokenType)
113- .build ()
115+
116+ val propertySpecs = mutableListOf< PropertySpec > ()
114117
115118 val baseUrlProp = PropertySpec
116119 .builder(BASE_URL , STRING )
117120 .initializer(BASE_URL )
118121 .addModifiers(KModifier .PROTECTED )
119122 .build()
123+ propertySpecs.add(baseUrlProp)
120124
121- val tokenProp = PropertySpec
122- .builder(TOKEN , tokenType)
123- .initializer(TOKEN )
124- .addModifiers(KModifier .PRIVATE )
125- .build()
125+ for ((paramName, _) in authParams) {
126+ constructorBuilder.addParameter(paramName, tokenType)
127+ propertySpecs.add(
128+ PropertySpec
129+ .builder(paramName, tokenType)
130+ .initializer(paramName)
131+ .addModifiers(KModifier .PRIVATE )
132+ .build(),
133+ )
134+ }
126135
127136 val clientProp = PropertySpec
128137 .builder(CLIENT , HTTP_CLIENT )
@@ -135,32 +144,125 @@ object ApiClientBaseGenerator {
135144 .addStatement(" $CLIENT .close()" )
136145 .build()
137146
138- return TypeSpec
147+ val classBuilder = TypeSpec
139148 .classBuilder(API_CLIENT_BASE )
140149 .addModifiers(KModifier .ABSTRACT )
141150 .addSuperinterface(CLOSEABLE )
142- .primaryConstructor(constructor )
143- .addProperty(baseUrlProp)
144- .addProperty(tokenProp)
151+ .primaryConstructor(constructorBuilder.build())
152+
153+ for (prop in propertySpecs) {
154+ classBuilder.addProperty(prop)
155+ }
156+
157+ return classBuilder
145158 .addProperty(clientProp)
146159 .addFunction(closeFun)
147- .addFunction(buildApplyAuth())
160+ .addFunction(buildApplyAuth(securitySchemes ))
148161 .addFunction(buildSafeCall())
149162 .addFunction(buildCreateHttpClient())
150163 .build()
151164 }
152165
153- private fun buildApplyAuth (): FunSpec = FunSpec
154- .builder(APPLY_AUTH )
155- .addModifiers(KModifier .PROTECTED )
156- .receiver(HTTP_REQUEST_BUILDER )
157- .beginControlFlow(" %M" , HEADERS_FUN )
158- .addStatement(
159- " append(%T.Authorization, %P)" ,
160- HTTP_HEADERS ,
161- CodeBlock .of($$" Bearer ${' $' } {$$TOKEN ()}" ),
162- ).endControlFlow()
163- .build()
166+ /* *
167+ * Builds the list of auth-related constructor parameter names based on security schemes.
168+ * Returns pairs of (paramName, schemeType) for each scheme.
169+ */
170+ internal fun buildAuthConstructorParams (securitySchemes : List <SecurityScheme >): List <Pair <String , SecurityScheme >> =
171+ securitySchemes.flatMap { scheme ->
172+ when (scheme) {
173+ is SecurityScheme .Bearer -> {
174+ val isSingleBearer =
175+ securitySchemes.size == 1 && securitySchemes.first() is SecurityScheme .Bearer
176+
177+ val paramName = if (isSingleBearer) TOKEN else " ${scheme.name.toCamelCase()} Token"
178+ listOf (paramName to scheme)
179+ }
180+
181+ is SecurityScheme .ApiKey -> {
182+ listOf (" ${scheme.name.toCamelCase()} Key" to scheme)
183+ }
184+
185+ is SecurityScheme .Basic -> {
186+ listOf (
187+ " ${scheme.name.toCamelCase()} Username" to scheme,
188+ " ${scheme.name.toCamelCase()} Password" to scheme,
189+ )
190+ }
191+ }
192+ }
193+
194+ private fun buildApplyAuth (securitySchemes : List <SecurityScheme >): FunSpec {
195+ val builder = FunSpec
196+ .builder(APPLY_AUTH )
197+ .addModifiers(KModifier .PROTECTED )
198+ .receiver(HTTP_REQUEST_BUILDER )
199+
200+ if (securitySchemes.isEmpty()) return builder.build()
201+
202+ val headerSchemes = securitySchemes.filter {
203+ it is SecurityScheme .Bearer ||
204+ it is SecurityScheme .Basic ||
205+ (it is SecurityScheme .ApiKey && it.location == ApiKeyLocation .HEADER )
206+ }
207+ val querySchemes = securitySchemes
208+ .filterIsInstance<SecurityScheme .ApiKey >()
209+ .filter { it.location == ApiKeyLocation .QUERY }
210+
211+ if (headerSchemes.isNotEmpty()) {
212+ builder.beginControlFlow(" %M" , HEADERS_FUN )
213+ for (scheme in headerSchemes) {
214+ when (scheme) {
215+ is SecurityScheme .Bearer -> {
216+ val isSingleBearer =
217+ securitySchemes.size == 1 && securitySchemes.first() is SecurityScheme .Bearer
218+
219+ val paramName = if (isSingleBearer) TOKEN else " ${scheme.name.toCamelCase()} Token"
220+ builder.addStatement(
221+ " append(%T.Authorization, %P)" ,
222+ HTTP_HEADERS ,
223+ CodeBlock .of(" Bearer \$ {$paramName ()}" ),
224+ )
225+ }
226+
227+ is SecurityScheme .Basic -> {
228+ val usernameParam = " ${scheme.name.toCamelCase()} Username"
229+ val passwordParam = " ${scheme.name.toCamelCase()} Password"
230+ builder.addStatement(
231+ " append(%T.Authorization, %P)" ,
232+ HTTP_HEADERS ,
233+ CodeBlock .of(
234+ " Basic \$ {%T.getEncoder().encodeToString(\" ${' $' } {$usernameParam ()}:${' $' } {$passwordParam ()}\" .toByteArray())}" ,
235+ BASE64_CLASS ,
236+ ),
237+ )
238+ }
239+
240+ is SecurityScheme .ApiKey -> {
241+ val paramName = " ${scheme.name.toCamelCase()} Key"
242+ builder.addStatement(
243+ " append(%S, $paramName ())" ,
244+ scheme.parameterName,
245+ )
246+ }
247+ }
248+ }
249+ builder.endControlFlow()
250+ }
251+
252+ if (querySchemes.isNotEmpty()) {
253+ builder.beginControlFlow(" url" )
254+ for (scheme in querySchemes) {
255+ val paramName = " ${scheme.name.toCamelCase()} Key"
256+ builder.addStatement(
257+ " parameters.append(%S, $paramName ())" ,
258+ scheme.parameterName,
259+ )
260+ }
261+ builder.endControlFlow()
262+ }
263+
264+ return builder.build()
265+ }
164266
165267 private fun buildSafeCall (): FunSpec = FunSpec
166268 .builder(SAFE_CALL )
0 commit comments