From 425b5712c9999a97a61507aa700161f9656b1dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniels=20=C5=A0atcs?= Date: Mon, 15 Dec 2025 20:00:49 +0200 Subject: [PATCH 01/14] Fix request bodies not being generated --- .../src/test/kotlin/ClientTest.kt | 16 ++++++++++ .../openapi2ktor/generators/TypeStore.kt | 16 +++++++++- .../generators/analyze/OpenApiAnalyzer.kt | 22 +++++++++---- .../generators/clients/KtorClientGenerator.kt | 18 ++++++++++- .../generators/clients/KtorHelpers.kt | 32 +++++++++++++++++++ .../analyze/ResponseUniquenessTest.kt | 2 +- 6 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorHelpers.kt diff --git a/e2e/polymorphism/src/test/kotlin/ClientTest.kt b/e2e/polymorphism/src/test/kotlin/ClientTest.kt index 4e17fbe..bdeb00a 100644 --- a/e2e/polymorphism/src/test/kotlin/ClientTest.kt +++ b/e2e/polymorphism/src/test/kotlin/ClientTest.kt @@ -3,10 +3,12 @@ import com.denisbrandi.netmock.NetMockRequest import com.denisbrandi.netmock.NetMockResponseBuilder import com.denisbrandi.netmock.engine.NetMockEngine import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json import sample.client.Client import sample.models.components.parameters.UserType import sample.models.components.schemas.AdminUser.AdminUser import sample.models.paths.users.get.response.GetUsersResponse200 +import sample.models.paths.users.post.requestBody.PostUsersRequest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -61,6 +63,20 @@ class ClientTest { runCatching { client.getOrders(UserType.ADMIN) } // Will fail because it's mocked. } + @Test + fun `post request body`() = runTest { + val request = PostUsersRequest() + interceptRequest( + "/users" + ) { + assertEquals( + Json.encodeToString(request), + this.body + ) + } + runCatching { client.postUsers(request) } // Response is not mocked, we only check request here. + } + private fun mockGet(relPath: String, response: String, status: Int = 200) { netMock.addMockWithCustomMatcher( requestMatcher = { interceptedRequest -> diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/TypeStore.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/TypeStore.kt index 1c0761d..e6dca3f 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/TypeStore.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/TypeStore.kt @@ -1,6 +1,5 @@ package com.dshatz.openapi2ktor.generators -import com.dshatz.openapi2ktor.generators.Type.Companion.simpleType import com.dshatz.openapi2ktor.utils.* import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy @@ -16,9 +15,15 @@ class TypeStore { private val responseMapping: MutableMap> = mutableMapOf() private val responseInterfaces: MutableMap> = mutableMapOf() + private val requestBodies: MutableMap = mutableMapOf() private val operationParameters: MutableMap> = mutableMapOf() private val exceptionTypes: MutableSet = mutableSetOf() + data class RequestBody( + val type: Type, + val mediaType: String + ) + private val typesUsedInResponses by lazy { responseMapping.asSequence() .flatMap { it.value.values.map { type -> type to it.key } } .groupBy { findConcreteType(it.first.type) } @@ -114,6 +119,15 @@ class TypeStore { responseInterfaces[path] = successInterface to errorInterface } + + fun registerRequestBody(path: PathId, requestBody: Type, mediaType: String) { + requestBodies[path] = RequestBody(requestBody, mediaType) + } + + fun getRequestBody(pathId: PathId): RequestBody? { + return requestBodies[pathId] + } + fun getResponseErrorInterface(path: PathId) = responseInterfaces[path]?.second fun getResponseSuccessInterface(path: PathId) = responseInterfaces[path]?.first diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt index aaa91bd..eb63f51 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt @@ -13,6 +13,7 @@ import com.dshatz.openapi2ktor.generators.models.KotlinxCodeGenerator import com.dshatz.openapi2ktor.kdoc.DocTemplate import com.dshatz.openapi2ktor.utils.* import com.reprezen.jsonoverlay.Overlay +import com.reprezen.kaizen.oasparser.model3.MediaType import com.reprezen.kaizen.oasparser.model3.OpenApi3 import com.reprezen.kaizen.oasparser.model3.Operation import com.reprezen.kaizen.oasparser.model3.Response @@ -115,7 +116,7 @@ open class OpenApiAnalyzer( } private suspend fun OpenApi3.gatherPathModels(): List { - return gatherPathResponseModels() + gatherPathRequestBodyModels() + calculateOperationParameters() + return gatherResponseModels() + gatherRequestModels() + calculateOperationParameters() } private suspend fun OpenApi3.calculateOperationParameters() = withContext(coroutineContext) { @@ -129,7 +130,7 @@ open class OpenApiAnalyzer( internal suspend fun calculateParameters(pathID: TypeStore.PathId, operation: Operation) { val params = operation.parameters.map { param -> param.schema.makeType( - param.name.safePropName()/*param.jsonReference.split("/").last().safePropName()*/, + param.name.safePropName(), param.jsonReference, false, operation.isParameterAReference(param.name), @@ -168,7 +169,7 @@ open class OpenApiAnalyzer( } else emptyList() } - internal suspend fun OpenApi3.gatherPathResponseModels() = withContext(coroutineContext) { + internal suspend fun OpenApi3.gatherResponseModels() = withContext(coroutineContext) { return@withContext mapPaths { pathId, operation -> processPath(pathId, operation) }.flatten() @@ -205,13 +206,20 @@ open class OpenApiAnalyzer( } } - private suspend fun OpenApi3.gatherPathRequestBodyModels() = withContext(coroutineContext) { + private suspend fun OpenApi3.gatherRequestModels() = withContext(coroutineContext) { return@withContext paths.flatMap { (pathString, path) -> path.operations.filter { it.value.requestBody.contentMediaTypes.isNotEmpty() }.map { (verb, operation) -> - val schema = operation.requestBody.contentMediaTypes.values.first().schema + val firstMediaType = operation.requestBody.contentMediaTypes.entries.first() + val schema = firstMediaType.value.schema val modelName = makeRequestBodyModelName(verb, pathString) launch { - schema.makeType(modelName, schema.jsonReference, referenceData = null, wrapMode = WrapMode.None) + val type = schema.makeType(modelName, schema.jsonReference, referenceData = null, wrapMode = WrapMode.None) + val pathID = TypeStore.PathId(pathString, verb) + typeStore.registerRequestBody( + pathID, + type, + firstMediaType.key + ) } } } @@ -252,7 +260,7 @@ open class OpenApiAnalyzer( referenceData: ReferenceMetadata?, wrapMode: WrapMode ): Type { -// println("Entering ${"component".takeIf { components } ?: ""} ${jsonReference.cleanJsonReference()}") + println("Entering ${"component".takeIf { components } ?: ""} ${jsonReference.cleanJsonReference()}") val packageName = makePackageName(jsonReference, packages.models) diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt index 82ade73..5f6c08c 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt @@ -229,6 +229,15 @@ class KtorClientGenerator(override val typeStore: TypeStore, val packages: Packa val iResponseClass = typeStore.getResponseSuccessInterface(pathID) val iErrorClass = typeStore.getResponseErrorInterface(pathID) + val requestBodyInfo = typeStore.getRequestBody(pathID) + val requestBodyParamName = "body" + val requestBodyParam = requestBodyInfo?.let { + ParameterSpec.builder( + requestBodyParamName, + it.type.makeTypeName(), + ).build() + } + val successResponseClass = if (iResponseClass != null) iResponseClass else { @@ -265,6 +274,9 @@ class KtorClientGenerator(override val typeStore: TypeStore, val packages: Packa val requestFun = FunSpec.builder(funName) .returns(libResultClass.parameterizedBy(successResponseClass, errorResponseClass)) .addModifiers(KModifier.SUSPEND) + .apply { + requestBodyParam?.let(::addParameter) + } .addParameters(paramSpecs.values) .apply { description?.let { addKdoc(DocTemplate.Builder().add(it).build().toCodeBlock(::findConcreteType)) } @@ -293,6 +305,7 @@ class KtorClientGenerator(override val typeStore: TypeStore, val packages: Packa add(".%M(%S, %N, %L)", addPathParamHelper, param.name, spec.name, spec.type.isNullable) } }.build())) + // Inside http.(...) {} block .apply { // Add query params paramSpecs.filter { it.key.where == ParamLocation.QUERY || it.key.where == ParamLocation.HEADER }.forEach { (paramInfo, paramSpec) -> @@ -310,7 +323,10 @@ class KtorClientGenerator(override val typeStore: TypeStore, val packages: Packa securitySchemes[it]?.generateApplicator(it)?.apply(::add) } } - .endControlFlow() // request config + .setRequestBody(requestBodyParam) + .setContentType(requestBodyInfo) + // end of http. block + .endControlFlow() .beginControlFlow("val result = when (response.status.value)") // begin when .apply { // Success status code mapping diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorHelpers.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorHelpers.kt new file mode 100644 index 0000000..4cd5c87 --- /dev/null +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorHelpers.kt @@ -0,0 +1,32 @@ +package com.dshatz.openapi2ktor.generators.clients + +import com.dshatz.openapi2ktor.generators.TypeStore +import com.dshatz.openapi2ktor.generators.clients.KtorHelpers.contentTypeClass +import com.dshatz.openapi2ktor.generators.clients.KtorHelpers.contentTypeExtension +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.ParameterSpec + +internal object KtorHelpers { + val setBodyExtension = MemberName("io.ktor.client.request", "setBody", isExtension = true) + val contentTypeExtension = MemberName("io.ktor.http", "contentType", isExtension = true) + val contentTypeClass = ClassName("io.ktor.http", "ContentType") +} + +internal fun CodeBlock.Builder.setRequestBody(bodyParam: ParameterSpec?) = apply { + bodyParam?.let { + addStatement("%M(%N)", KtorHelpers.setBodyExtension, it.name) + } +} + +internal fun CodeBlock.Builder.setContentType(requestBodyInfo: TypeStore.RequestBody?) = apply { + requestBodyInfo?.let { + addStatement("%M(%L)", contentTypeExtension, CodeBlock.of( + "%T.parse(%S)", + contentTypeClass, + it.mediaType + )) + } +} \ No newline at end of file diff --git a/gradle-plugin/processor/src/test/kotlin/com/dshatz/openapi2ktor/generators/analyze/ResponseUniquenessTest.kt b/gradle-plugin/processor/src/test/kotlin/com/dshatz/openapi2ktor/generators/analyze/ResponseUniquenessTest.kt index 1a2010a..ab65e49 100644 --- a/gradle-plugin/processor/src/test/kotlin/com/dshatz/openapi2ktor/generators/analyze/ResponseUniquenessTest.kt +++ b/gradle-plugin/processor/src/test/kotlin/com/dshatz/openapi2ktor/generators/analyze/ResponseUniquenessTest.kt @@ -59,7 +59,7 @@ class ResponseUniquenessTest { } } } - api.gatherPathResponseModels().joinAll() + api.gatherResponseModels().joinAll() } } From a666a48b451fc4a6014501fcf6b5d0c822645741 Mon Sep 17 00:00:00 2001 From: Wayne Date: Mon, 17 Nov 2025 19:17:39 +0000 Subject: [PATCH 02/14] Add @Serializable to subclasses of sealed class Http --- .../dshatz/openapi2ktor/generators/clients/SecurityScheme.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/SecurityScheme.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/SecurityScheme.kt index 94624e5..e04355e 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/SecurityScheme.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/SecurityScheme.kt @@ -9,7 +9,6 @@ import com.squareup.kotlinpoet.MemberName import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.asTypeName import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonClassDiscriminator @@ -17,8 +16,6 @@ import kotlinx.serialization.json.JsonContentPolymorphicSerializer import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.serializer -import kotlin.reflect.typeOf class SecuritySchemeSerializer: JsonContentPolymorphicSerializer(SecurityScheme::class) { override fun selectDeserializer(element: JsonElement): DeserializationStrategy { @@ -51,6 +48,7 @@ sealed class SecurityScheme() { @Serializable @JsonClassDiscriminator("scheme") sealed class Http(): SecurityScheme() { + @Serializable @SerialName("bearer") data class Bearer(val bearerFormat: String, override val description: String = ""): Http() { override fun generateSetter(name: String): FunSpec { @@ -64,6 +62,7 @@ sealed class Http(): SecurityScheme() { } } + @Serializable @SerialName("basic") class Basic(override val description: String = "") : Http() { override fun generateSetter(name: String): FunSpec { From b445a0fbfbcf7d36a5af4b87ee9e65dc8aa4bc27 Mon Sep 17 00:00:00 2001 From: Wayne Date: Mon, 17 Nov 2025 19:38:40 +0000 Subject: [PATCH 03/14] Add failing test from TVDB --- e2e/tvdb/src/test/kotlin/TVDBTest.kt | 2 + e2e/tvdb/src/test/resources/swagger.yml | 4264 +++++++++++++++++ .../kotlin/com/dshatz/openapi2ktor/Main.kt | 3 +- settings.gradle.kts | 1 + 4 files changed, 4268 insertions(+), 2 deletions(-) create mode 100644 e2e/tvdb/src/test/kotlin/TVDBTest.kt create mode 100644 e2e/tvdb/src/test/resources/swagger.yml diff --git a/e2e/tvdb/src/test/kotlin/TVDBTest.kt b/e2e/tvdb/src/test/kotlin/TVDBTest.kt new file mode 100644 index 0000000..a524a36 --- /dev/null +++ b/e2e/tvdb/src/test/kotlin/TVDBTest.kt @@ -0,0 +1,2 @@ +class TVDBTest { +} \ No newline at end of file diff --git a/e2e/tvdb/src/test/resources/swagger.yml b/e2e/tvdb/src/test/resources/swagger.yml new file mode 100644 index 0000000..7c7ddff --- /dev/null +++ b/e2e/tvdb/src/test/resources/swagger.yml @@ -0,0 +1,4264 @@ +openapi: 3.0.0 +info: + description: | + Documentation of [TheTVDB](https://thetvdb.com/) API V4. All related information is linked from our [Github repo](https://github.com/thetvdb/v4-api). You might also want to use our [Postman collection] (https://www.getpostman.com/collections/7a9397ce69ff246f74d0) + ## Authentication + 1. Use the /login endpoint and provide your API key as "apikey". If you have a user-supported key, also provide your subscriber PIN as "pin". Otherwise completely remove "pin" from your call. + 2. Executing this call will provide you with a bearer token, which is valid for 1 month. + 3. Provide your bearer token for subsequent API calls by clicking Authorize below or including in the header of all direct API calls: `Authorization: Bearer [your-token]` + + ## Notes + 1. "score" is a field across almost all entities. We generate scores for different types of entities in various ways, so no assumptions should be made about the meaning of this value. It is simply used to hint at relative popularity for sorting purposes. + title: TVDB API V4 + version: 4.7.10 +security: + - bearerAuth: [ ] +paths: + /login: + post: + summary: create an auth token. The token has one month validation length. + requestBody: + content: + application/json: + schema: + type: object + required: + - apikey + properties: + apikey: + type: string + pin: + type: string + required: true + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + properties: + token: + type: string + type: object + status: + type: string + type: object + '401': + description: invalid credentials + tags: + - Login + '/artwork/{id}': + get: + description: Returns a single artwork base record. + operationId: getArtworkBase + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/ArtworkBaseRecord' + status: + type: string + type: object + '400': + description: Invalid artwork id + '401': + description: Unauthorized + '404': + description: Artwork not found + tags: + - Artwork + + '/artwork/{id}/extended': + get: + description: Returns a single artwork extended record. + operationId: getArtworkExtended + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/ArtworkExtendedRecord' + status: + type: string + type: object + '400': + description: Invalid artwork id + '401': + description: Unauthorized + '404': + description: Artwork not found + tags: + - Artwork + + '/artwork/statuses': + get: + description: Returns list of artwork status records. + operationId: getAllArtworkStatuses + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/ArtworkStatus' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Artwork Statuses + + '/artwork/types': + get: + description: Returns a list of artworkType records + operationId: getAllArtworkTypes + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/ArtworkType' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Artwork Types + + /awards: + get: + description: Returns a list of award base records + operationId: getAllAwards + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/AwardBaseRecord' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Awards + + '/awards/{id}': + get: + description: Returns a single award base record + operationId: getAward + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AwardBaseRecord' + status: + type: string + type: object + '400': + description: Invalid awards id + '401': + description: Unauthorized + '404': + description: Awards not found + tags: + - Awards + + '/awards/{id}/extended': + get: + description: Returns a single award extended record + operationId: getAwardExtended + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AwardExtendedRecord' + status: + type: string + type: object + '400': + description: Invalid awards id + '401': + description: Unauthorized + '404': + description: Awards not found + tags: + - Awards + + '/awards/categories/{id}': + get: + description: Returns a single award category base record + operationId: getAwardCategory + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AwardCategoryBaseRecord' + status: + type: string + type: object + '400': + description: Invalid category id + '401': + description: Unauthorized + '404': + description: Category not found + tags: + - Award Categories + + '/awards/categories/{id}/extended': + get: + description: Returns a single award category extended record + operationId: getAwardCategoryExtended + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/AwardCategoryExtendedRecord' + status: + type: string + type: object + '400': + description: Invalid category id + '401': + description: Unauthorized + '404': + description: Category not found + tags: + - Award Categories + + '/characters/{id}': + get: + description: Returns character base record + operationId: getCharacterBase + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/Character' + status: + type: string + type: object + '400': + description: Invalid character id + '401': + description: Unauthorized + '404': + description: Character not found + tags: + - Characters + /companies: + get: + description: returns a paginated list of company records + operationId: getAllCompanies + parameters: + - description: name + in: query + name: page + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/Company' + type: array + status: + type: string + links: + $ref: '#/components/schemas/Links' + type: object + '401': + description: Unauthorized + tags: + - Companies + '/companies/types': + get: + description: returns all company type records + operationId: getCompanyTypes + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + type: array + items: + $ref: '#/components/schemas/CompanyType' + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Companies + '/companies/{id}': + get: + description: returns a company record + operationId: getCompany + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/Company' + status: + type: string + type: object + '400': + description: Invalid company id + '401': + description: Unauthorized + '404': + description: Company not found + tags: + - Companies + /content/ratings: + get: + description: returns list content rating records + operationId: getAllContentRatings + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/ContentRating' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Content Ratings + /countries: + get: + description: returns list of country records + operationId: getAllCountries + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/Country' + type: array + status: + type: string + type: object + tags: + - Countries + '/entities': + get: + description: returns the active entity types + operationId: getEntityTypes + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/EntityType' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Entity Types + '/episodes': + get: + description: Returns a list of episodes base records with the basic attributes.
Note that all episodes are returned, even those that may not be included in a series' default season order. + operationId: getAllEpisodes + parameters: + - description: page number + in: query + name: page + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/EpisodeBaseRecord' + type: array + status: + type: string + links: + $ref: '#/components/schemas/Links' + type: object + '401': + description: Unauthorized + tags: + - Episodes + '/episodes/{id}': + get: + description: Returns episode base record + operationId: getEpisodeBase + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/EpisodeBaseRecord' + status: + type: string + type: object + '400': + description: Invalid episode id + '401': + description: Unauthorized + '404': + description: Episode not found + tags: + - Episodes + '/episodes/{id}/extended': + get: + description: Returns episode extended record + operationId: getEpisodeExtended + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + - description: meta + in: query + name: meta + required: false + schema: + type: string + enum: [ translations ] + example: translations + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/EpisodeExtendedRecord' + status: + type: string + type: object + '400': + description: Invalid episode id + '401': + description: Unauthorized + '404': + description: Episode not found + tags: + - Episodes + '/episodes/{id}/translations/{language}': + get: + description: Returns episode translation record + operationId: getEpisodeTranslation + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + - description: language + in: path + name: language + required: true + schema: + type: string + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/Translation' + status: + type: string + type: object + '400': + description: Invalid episode id. Invalid language. + '401': + description: Unauthorized + '404': + description: Episode not found + tags: + - Episodes + + /genders: + get: + description: returns list of gender records + operationId: getAllGenders + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/Gender' + type: array + status: + type: string + type: object + tags: + - Genders + + /genres: + get: + description: returns list of genre records + operationId: getAllGenres + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/GenreBaseRecord' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + + tags: + - Genres + + '/genres/{id}': + get: + description: Returns genre record + operationId: getGenreBase + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/GenreBaseRecord' + status: + type: string + type: object + '400': + description: Invalid genre id + '401': + description: Unauthorized + '404': + description: Genre not found + tags: + - Genres + /inspiration/types: + get: + description: returns list of inspiration types records + operationId: getAllInspirationTypes + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/InspirationType' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - InspirationTypes + /languages: + get: + description: returns list of language records + operationId: getAllLanguages + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/Language' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Languages + /lists: + get: + description: returns list of list base records + operationId: getAllLists + parameters: + - description: page number + in: query + name: page + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/ListBaseRecord' + type: array + status: + type: string + links: + $ref: '#/components/schemas/Links' + '401': + description: Unauthorized + tags: + - Lists + + '/lists/{id}': + get: + description: returns an list base record + operationId: getList + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/ListBaseRecord' + status: + type: string + type: object + '400': + description: Invalid list id + '401': + description: Unauthorized + '404': + description: List not found + tags: + - Lists + '/lists/slug/{slug}': + get: + description: returns an list base record search by slug + operationId: getListBySlug + parameters: + - description: slug + in: path + name: slug + required: true + schema: + type: string + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/ListBaseRecord' + status: + type: string + type: object + '400': + description: Invalid list slug + '401': + description: Unauthorized + '404': + description: List not found + tags: + - Lists + '/lists/{id}/extended': + get: + description: returns a list extended record + operationId: getListExtended + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/ListExtendedRecord' + status: + type: string + type: object + '400': + description: Invalid list id + '401': + description: Unauthorized + '404': + description: Lists not found + tags: + - Lists + '/lists/{id}/translations/{language}': + get: + description: Returns list translation record + operationId: getListTranslation + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + - description: language + in: path + name: language + required: true + schema: + type: string + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/Translation' + type: array + status: + type: string + type: object + '400': + description: Invalid lists id + '401': + description: Unauthorized + '404': + description: Lists not found + tags: + - Lists + + /movies: + get: + description: returns list of movie base records + operationId: getAllMovie + parameters: + - description: page number + in: query + name: page + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/MovieBaseRecord' + type: array + status: + type: string + links: + $ref: '#/components/schemas/Links' + type: object + '401': + description: Unauthorized + tags: + - Movies + '/movies/{id}': + get: + description: Returns movie base record + operationId: getMovieBase + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/MovieBaseRecord' + status: + type: string + type: object + '400': + description: Invalid movie id + '401': + description: Unauthorized + '404': + description: Movie not found + tags: + - Movies + '/movies/{id}/extended': + get: + description: Returns movie extended record + operationId: getMovieExtended + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + - description: meta + in: query + name: meta + required: false + schema: + type: string + enum: [ translations ] + example: translations + - description: reduce the payload and returns the short version of this record without characters, artworks and trailers. + in: query + name: short + required: false + schema: + type: boolean + enum: [ true, false ] + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/MovieExtendedRecord' + status: + type: string + type: object + '400': + description: Invalid movie id + '401': + description: Unauthorized + '404': + description: Movie not found + tags: + - Movies + '/movies/filter': + get: + description: Search movies based on filter parameters + operationId: getMoviesFilter + parameters: + - description: production company + in: query + name: company + required: false + schema: + type: number + example: 1 + - description: content rating id base on a country + in: query + name: contentRating + required: false + schema: + type: number + example: 245 + - description: country of origin + in: query + name: country + required: true + schema: + type: string + example: usa + - description: genre + in: query + name: genre + required: false + schema: + type: number + example: 3 + enum: [ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36 ] + - description: original language + in: query + name: lang + required: true + schema: + type: string + example: eng + - description: sort by results + in: query + name: sort + required: false + schema: + type: string + enum: [ score,firstAired,name ] + - description: status + in: query + name: status + required: false + schema: + type: number + enum: [ 1,2,3 ] + - description: release year + in: query + name: year + required: false + schema: + type: number + example: 2020 + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/MovieBaseRecord' + type: array + status: + type: string + type: object + '400': + description: Invalid format parameter. + '401': + description: Unauthorized + tags: + - Movies + '/movies/slug/{slug}': + get: + description: Returns movie base record search by slug + operationId: getMovieBaseBySlug + parameters: + - description: slug + in: path + name: slug + required: true + schema: + type: string + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/MovieBaseRecord' + status: + type: string + type: object + '400': + description: Invalid movie slug + '401': + description: Unauthorized + '404': + description: Movie not found + tags: + - Movies + '/movies/{id}/translations/{language}': + get: + description: Returns movie translation record + operationId: getMovieTranslation + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + - description: language + in: path + name: language + required: true + schema: + type: string + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/Translation' + status: + type: string + type: object + '400': + description: Invalid movie id, invalid language. + '401': + description: Unauthorized + '404': + description: Movie not found + tags: + - Movies + /movies/statuses: + get: + description: returns list of status records + operationId: getAllMovieStatuses + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/Status' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Movie Statuses + '/people': + get: + description: Returns a list of people base records with the basic attributes. + operationId: getAllPeople + parameters: + - description: page number + in: query + name: page + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/PeopleBaseRecord' + type: array + status: + type: string + links: + $ref: '#/components/schemas/Links' + type: object + '401': + description: Unauthorized + tags: + - People + + '/people/{id}': + get: + description: Returns people base record + operationId: getPeopleBase + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/PeopleBaseRecord' + status: + type: string + type: object + '400': + description: Invalid people id + '401': + description: Unauthorized + '404': + description: People not found + tags: + - People + '/people/{id}/extended': + get: + description: Returns people extended record + operationId: getPeopleExtended + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + - description: meta + in: query + name: meta + required: false + schema: + type: string + enum: [ translations ] + example: translations + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/PeopleExtendedRecord' + status: + type: string + type: object + '400': + description: Invalid people id + '401': + description: Unauthorized + '404': + description: People not found + tags: + - People + '/people/{id}/translations/{language}': + get: + description: Returns people translation record + operationId: getPeopleTranslation + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + - description: language + in: path + name: language + required: true + schema: + type: string + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/Translation' + status: + type: string + type: object + '400': + description: Invalid people id, invalid language. + '401': + description: Unauthorized + '404': + description: People not found + tags: + - People + /people/types: + get: + description: returns list of peopleType records + operationId: getAllPeopleTypes + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/PeopleType' + type: array + status: + type: string + type: object + tags: + - People Types + + /search: + get: + description: Our search index includes series, movies, people, and companies. Search is limited to 5k results max. + operationId: getSearchResults + parameters: + - description: The primary search string, which can include the main title for a record including all translations and aliases. + in: query + name: query + schema: + type: string + - description: Alias of the "query" parameter. Recommend using query instead as this field will eventually be deprecated. + in: query + name: q + schema: + type: string + - description: Restrict results to a specific entity type. Can be movie, series, person, or company. + in: query + name: type + schema: + type: string + - description: Restrict results to a specific year. Currently only used for series and movies. + in: query + name: year + schema: + type: number + - description: Restrict results to a specific company (original network, production company, studio, etc). As an example, "The Walking Dead" would have companies of "AMC", "AMC+", and "Disney+". + in: query + name: company + schema: + type: string + - description: Restrict results to a specific country of origin. Should contain a 3 character country code. Currently only used for series and movies. + in: query + name: country + schema: + type: string + - description: Restrict results to a specific director. Generally only used for movies. Should include the full name of the director, such as "Steven Spielberg". + in: query + name: director + schema: + type: string + - description: Restrict results to a specific primary language. Should include the 3 character language code. Currently only used for series and movies. + in: query + name: language + schema: + type: string + - description: Restrict results to a specific type of company. Should include the full name of the type of company, such as "Production Company". Only used for companies. + in: query + name: primaryType + schema: + type: string + - description: Restrict results to a specific network. Used for TV and TV movies, and functions the same as the company parameter with more specificity. + in: query + name: network + schema: + type: string + + + - description: Search for a specific remote id. Allows searching for an IMDB or EIDR id, for example. + in: query + name: remote_id + schema: + type: string + + - description: Offset results. + in: query + name: offset + schema: + type: number + - description: Limit results. + in: query + name: limit + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/SearchResult' + type: array + status: + type: string + links: + $ref: '#/components/schemas/Links' + type: object + '401': + description: Unauthorized + '400': + description: Max results overflow + tags: + - Search + /search/remoteid/{remoteId}: + get: + description: Search a series, movie, people, episode, company or season by specific remote id and returns a base record for that entity. + operationId: getSearchResultsByRemoteId + parameters: + - description: Search for a specific remote id. Allows searching for an IMDB or EIDR id, for example. + in: path + required: true + name: remoteId + schema: + type: string + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/SearchByRemoteIdResult' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Search + + /seasons: + get: + description: returns list of seasons base records + operationId: getAllSeasons + parameters: + - description: page number + in: query + name: page + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/SeasonBaseRecord' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Seasons + '/seasons/{id}': + get: + description: Returns season base record + operationId: getSeasonBase + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/SeasonBaseRecord' + status: + type: string + type: object + '400': + description: Invalid season id + '401': + description: Unauthorized + '404': + description: Season not found + tags: + - Seasons + '/seasons/{id}/extended': + get: + description: Returns season extended record + operationId: getSeasonExtended + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/SeasonExtendedRecord' + status: + type: string + type: object + '400': + description: Invalid seasons id + '401': + description: Unauthorized + '404': + description: Season not found + tags: + - Seasons + '/seasons/types': + get: + description: Returns season type records + operationId: getSeasonTypes + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/SeasonType' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Seasons + '/seasons/{id}/translations/{language}': + get: + description: Returns season translation record + operationId: getSeasonTranslation + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + - description: language + in: path + name: language + required: true + schema: + type: string + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/Translation' + status: + type: string + type: object + '400': + description: Invalid season id, language not found. + '401': + description: Unauthorized + '404': + description: Season not found + tags: + - Seasons + /series: + get: + description: returns list of series base records + operationId: getAllSeries + parameters: + - description: page number + in: query + name: page + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/SeriesBaseRecord' + type: array + status: + type: string + links: + $ref: '#/components/schemas/Links' + type: object + '401': + description: Unauthorized + tags: + - Series + '/series/{id}': + get: + description: Returns series base record + operationId: getSeriesBase + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/SeriesBaseRecord' + status: + type: string + type: object + '400': + description: Invalid series id + '401': + description: Unauthorized + '404': + description: Series not found + tags: + - Series + '/series/{id}/artworks': + get: + description: Returns series artworks base on language and type.
Note: Artwork type is an id that can be found using **/artwork/types** endpoint. + operationId: getSeriesArtworks + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + - description: lang + in: query + name: lang + required: false + schema: + type: string + example: eng, spa + - description: type + in: query + name: type + required: false + schema: + type: integer + example: 1,2,3 + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/SeriesExtendedRecord' + status: + type: string + type: object + '400': + description: Invalid series id + '401': + description: Unauthorized + '404': + description: Series not found + tags: + - Series + '/series/{id}/nextAired': + get: + description: Returns series base record including the nextAired field.
Note: nextAired was included in the base record endpoint but that field will deprecated in the future so developers should use the nextAired endpoint. + operationId: getSeriesNextAired + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/SeriesBaseRecord' + status: + type: string + type: object + '400': + description: Invalid series id + '401': + description: Unauthorized + '404': + description: Series not found + tags: + - Series + '/series/{id}/extended': + get: + description: Returns series extended record + operationId: getSeriesExtended + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + - description: meta + in: query + name: meta + required: false + schema: + type: string + enum: [ translations, episodes ] + example: translations + - description: reduce the payload and returns the short version of this record without characters and artworks + in: query + name: short + required: false + schema: + type: boolean + enum: [ true, false ] + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/SeriesExtendedRecord' + status: + type: string + type: object + '400': + description: Invalid series id + '401': + description: Unauthorized + '404': + description: Series not found + tags: + - Series + '/series/{id}/episodes/{season-type}': + get: + description: Returns series episodes from the specified season type, default returns the episodes in the series default season type + operationId: getSeriesEpisodes + parameters: + - in: query + name: page + required: true + schema: + type: integer + default: 0 + - description: id + in: path + name: id + required: true + schema: + type: number + - description: season-type + in: path + name: season-type + required: true + schema: + type: string + examples: + default: + value: default + official: + value: official + dvd: + value: dvd + absolute: + value: absolute + alternate: + value: alternate + regional: + value: regional + - in: query + name: season + required: false + schema: + type: integer + default: 0 + - in: query + name: episodeNumber + required: false + schema: + type: integer + default: 0 + - description: airDate of the episode, format is yyyy-mm-dd + in: query + name: airDate + required: false + schema: + type: string + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + type: object + properties: + series: + $ref: '#/components/schemas/SeriesBaseRecord' + episodes: + type: array + items: + $ref: '#/components/schemas/EpisodeBaseRecord' + status: + type: string + type: object + '400': + description: Invalid series id, episodeNumber is not null then season must be present + '401': + description: Unauthorized + '404': + description: Series not found + tags: + - Series + '/series/{id}/episodes/{season-type}/{lang}': + get: + description: Returns series base record with episodes from the specified season type and language. Default returns the episodes in the series default season type. + operationId: getSeriesSeasonEpisodesTranslated + parameters: + - in: query + name: page + required: true + schema: + type: integer + default: 0 + - description: id + in: path + name: id + required: true + schema: + type: number + - description: season-type + in: path + name: season-type + required: true + schema: + type: string + examples: + default: + value: default + official: + value: official + dvd: + value: dvd + absolute: + value: absolute + alternate: + value: alternate + regional: + value: regional + - in: path + name: lang + required: true + schema: + type: string + + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + type: object + properties: + series: + $ref: '#/components/schemas/SeriesBaseRecord' + status: + type: string + type: object + '400': + description: Invalid series id, invalid language. + '401': + description: Unauthorized + '404': + description: Series not found + tags: + - Series + '/series/filter': + get: + description: Search series based on filter parameters + operationId: getSeriesFilter + parameters: + - description: production company + in: query + name: company + required: false + schema: + type: number + example: 1 + - description: content rating id base on a country + in: query + name: contentRating + required: false + schema: + type: number + example: 245 + - description: country of origin + in: query + name: country + required: true + schema: + type: string + example: usa + - description: Genre id. This id can be found using **/genres** endpoint. + in: query + name: genre + required: false + schema: + type: number + example: 3 + enum: [ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36 ] + - description: original language + in: query + name: lang + required: true + schema: + type: string + example: eng + - description: sort by results + in: query + name: sort + required: false + schema: + type: string + enum: [ score,firstAired,lastAired,name ] + - description: sort type ascending or descending + in: query + name: sortType + required: false + schema: + type: string + enum: [ asc,desc ] + - description: status + in: query + name: status + required: false + schema: + type: number + enum: [ 1,2,3 ] + - description: release year + in: query + name: year + required: false + schema: + type: number + example: 2020 + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/SeriesBaseRecord' + type: array + type: object + '400': + description: Invalid format parameter. + '401': + description: Unauthorized + tags: + - Series + '/series/slug/{slug}': + get: + description: Returns series base record searched by slug + operationId: getSeriesBaseBySlug + parameters: + - description: slug + in: path + name: slug + required: true + schema: + type: string + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/SeriesBaseRecord' + status: + type: string + type: object + '400': + description: Invalid series slug + '401': + description: Unauthorized + '404': + description: Series not found + tags: + - Series + '/series/{id}/translations/{language}': + get: + description: Returns series translation record + operationId: getSeriesTranslation + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + - description: language + in: path + name: language + required: true + schema: + type: string + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + $ref: '#/components/schemas/Translation' + status: + type: string + type: object + '400': + description: Invalid series id, invalid language. + '401': + description: Unauthorized + '404': + description: Series not found + tags: + - Series + /series/statuses: + get: + description: returns list of status records + operationId: getAllSeriesStatuses + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/Status' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Series Statuses + /sources/types: + get: + description: returns list of sourceType records + operationId: getAllSourceTypes + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/SourceType' + type: array + status: + type: string + type: object + '401': + description: Unauthorized + tags: + - Source Types + /updates: + get: + description: Returns updated entities. methodInt indicates a created record (1), an updated record (2), or a deleted record (3). If a record is deleted because it was a duplicate of another record, the target record's information is provided in mergeToType and mergeToId. + operationId: updates + parameters: + - in: query + name: since + required: true + schema: + type: number + - in: query + name: type + required: false + schema: + type: string + enum: [ artwork,award_nominees,companies,episodes,lists,people,seasons,series,seriespeople,artworktypes,award_categories,awards,company_types,content_ratings,countries,entity_types,genres,languages,movies,movie_genres,movie_status,peopletypes,seasontypes,sourcetypes,tag_options,tags,translatedcharacters,translatedcompanies,translatedepisodes,translatedlists,translatedmovies,translatedpeople,translatedseasons,translatedserierk ] + example: movies + - in: query + name: action + required: false + schema: + type: string + enum: [ delete, update ] + example: movies + - description: name + in: query + name: page + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/EntityUpdate' + type: array + status: + type: string + links: + $ref: '#/components/schemas/Links' + type: object + '400': + description: Invalid since, type param. + '401': + description: Unauthorized + + tags: + - Updates + /user: + get: + description: returns user info + operationId: getUserInfo + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/UserInfo' + status: + type: string + type: object + '401': + description: Unauthorized + + tags: + - User info + /user/{id}: + get: + description: returns user info by user id + operationId: getUserInfoById + parameters: + - description: id + in: path + name: id + required: true + schema: + type: number + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/UserInfo' + status: + type: string + type: object + '401': + description: Unauthorized + + tags: + - User info + /user/favorites: + get: + description: returns user favorites + operationId: getUserFavorites + responses: + '200': + description: response + content: + application/json: + schema: + properties: + data: + items: + $ref: '#/components/schemas/Favorites' + status: + type: string + type: object + '401': + description: Unauthorized + + tags: + - Favorites + post: + description: creates a new user favorite + operationId: createUserFavorites + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FavoriteRecord' + responses: + '200': + description: Ok + '400': + description: Bad format + '401': + description: Unauthorized + + tags: + - Favorites + +servers: + - url: 'https://api4.thetvdb.com/v4' +components: + securitySchemes: + bearerAuth: # arbitrary name for the security scheme + type: http + scheme: bearer + bearerFormat: JWT + schemas: + Alias: + description: An alias model, which can be associated with a series, season, movie, person, or list. + properties: + language: + type: string + maximum: 4 + description: A 3-4 character string indicating the language of the alias, as defined in Language. + name: + type: string + maximum: 100 + description: A string containing the alias itself. + type: object + ArtworkBaseRecord: + description: base artwork record + properties: + height: + format: int64 + type: integer + x-go-name: Height + id: + type: integer + image: + type: string + x-go-name: Image + includesText: + type: boolean + language: + type: string + score: + type: number + thumbnail: + type: string + x-go-name: Thumbnail + type: + format: int64 + type: integer + x-go-name: Type + description: The artwork type corresponds to the ids from the /artwork/types endpoint. + width: + format: int64 + type: integer + x-go-name: Width + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + ArtworkExtendedRecord: + description: extended artwork record + properties: + episodeId: + type: integer + height: + format: int64 + type: integer + x-go-name: Height + id: + format: int64 + type: integer + x-go-name: ID + image: + type: string + x-go-name: Image + includesText: + type: boolean + language: + type: string + movieId: + type: integer + networkId: + type: integer + peopleId: + type: integer + score: + type: number + seasonId: + type: integer + seriesId: + type: integer + seriesPeopleId: + type: integer + status: + $ref: '#/components/schemas/ArtworkStatus' + tagOptions: + items: + $ref: '#/components/schemas/TagOption' + type: array + x-go-name: TagOptions + thumbnail: + type: string + x-go-name: Thumbnail + thumbnailHeight: + format: int64 + type: integer + x-go-name: ThumbnailHeight + thumbnailWidth: + format: int64 + type: integer + x-go-name: ThumbnailWidth + type: + format: int64 + type: integer + x-go-name: Type + description: The artwork type corresponds to the ids from the /artwork/types endpoint. + updatedAt: + format: int64 + type: integer + x-go-name: UpdatedAt + width: + format: int64 + type: integer + x-go-name: Width + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + ArtworkStatus: + description: artwork status record + properties: + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + ArtworkType: + description: artwork type record + properties: + height: + format: int64 + type: integer + id: + format: int64 + type: integer + x-go-name: ID + imageFormat: + type: string + x-go-name: ImageFormat + name: + type: string + x-go-name: Name + recordType: + type: string + x-go-name: RecordType + slug: + type: string + x-go-name: Slug + thumbHeight: + format: int64 + type: integer + x-go-name: ThumbHeight + thumbWidth: + format: int64 + type: integer + x-go-name: ThumbWidth + width: + format: int64 + type: integer + x-go-name: Width + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + AwardBaseRecord: + description: base award record + properties: + id: + type: integer + name: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + AwardCategoryBaseRecord: + description: base award category record + properties: + allowCoNominees: + type: boolean + x-go-name: AllowCoNominees + award: + $ref: '#/components/schemas/AwardBaseRecord' + forMovies: + type: boolean + x-go-name: ForMovies + forSeries: + type: boolean + x-go-name: ForSeries + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + AwardCategoryExtendedRecord: + description: extended award category record + properties: + allowCoNominees: + type: boolean + x-go-name: AllowCoNominees + award: + $ref: '#/components/schemas/AwardBaseRecord' + forMovies: + type: boolean + x-go-name: ForMovies + forSeries: + type: boolean + x-go-name: ForSeries + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + nominees: + items: + $ref: '#/components/schemas/AwardNomineeBaseRecord' + type: array + x-go-name: Nominees + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + AwardExtendedRecord: + description: extended award record + properties: + categories: + items: + $ref: '#/components/schemas/AwardCategoryBaseRecord' + type: array + x-go-name: Categories + id: + type: integer + name: + type: string + score: + format: int64 + type: integer + x-go-name: Score + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + AwardNomineeBaseRecord: + description: base award nominee record + properties: + character: + $ref: '#/components/schemas/Character' + details: + type: string + episode: + $ref: '#/components/schemas/EpisodeBaseRecord' + id: + format: int64 + type: integer + x-go-name: ID + isWinner: + type: boolean + x-go-name: IsWinner + movie: + $ref: '#/components/schemas/MovieBaseRecord' + series: + $ref: '#/components/schemas/SeriesBaseRecord' + year: + type: string + category: + type: string + name: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Biography: + description: biography record + properties: + biography: + type: string + x-go-name: Biography + language: + type: string + x-go-name: Language + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Character: + description: character record + properties: + aliases: + items: + $ref: '#/components/schemas/Alias' + type: array + x-go-name: Aliases + episode: + $ref: '#/components/schemas/RecordInfo' + episodeId: + type: integer + nullable: true + id: + format: int64 + type: integer + x-go-name: ID + image: + type: string + isFeatured: + type: boolean + x-go-name: IsFeatured + movieId: + type: integer + nullable: true + movie: + $ref: '#/components/schemas/RecordInfo' + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + peopleId: + type: integer + personImgURL: + type: string + peopleType: + type: string + seriesId: + type: integer + nullable: true + series: + $ref: '#/components/schemas/RecordInfo' + sort: + format: int64 + type: integer + x-go-name: Sort + tagOptions: + items: + $ref: '#/components/schemas/TagOption' + type: array + x-go-name: TagOptions + type: + format: int64 + type: integer + x-go-name: Type + url: + type: string + x-go-name: URL + personName: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Company: + description: A company record + properties: + activeDate: + type: string + aliases: + items: + $ref: '#/components/schemas/Alias' + type: array + x-go-name: Aliases + country: + type: string + id: + format: int64 + type: integer + x-go-name: ID + inactiveDate: + type: string + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + primaryCompanyType: + format: int64 + type: integer + x-go-name: PrimaryCompanyType + nullable: true + slug: + type: string + x-go-name: Slug + parentCompany: + type: object + $ref: '#/components/schemas/ParentCompany' + tagOptions: + items: + $ref: '#/components/schemas/TagOption' + type: array + x-go-name: TagOptions + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + ParentCompany: + description: A parent company record + type: object + properties: + id: + type: integer + nullable: true + name: + type: string + relation: + type: object + $ref: '#/components/schemas/CompanyRelationShip' + CompanyRelationShip: + description: A company relationship + properties: + id: + type: integer + nullable: true + typeName: + type: string + CompanyType: + description: A company type record + type: object + properties: + companyTypeId: + type: integer + companyTypeName: + type: string + ContentRating: + description: content rating record + properties: + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + x-go-name: Name + description: + type: string + country: + type: string + contentType: + type: string + order: + type: integer + fullName: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Country: + description: country record + properties: + id: + type: string + x-go-name: ID + name: + type: string + x-go-name: Name + shortCode: + type: string + x-go-name: ShortCode + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Entity: + description: Entity record + properties: + movieId: + type: integer + order: + format: int64 + type: integer + x-go-name: Order + seriesId: + type: integer + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + EntityType: + description: Entity Type record + properties: + id: + type: integer + name: + type: string + x-go-name: Order + hasSpecials: + type: boolean + type: object + EntityUpdate: + description: entity update record + properties: + entityType: + type: string + x-go-name: EnitityType + methodInt: + type: integer + method: + type: string + x-go-name: Method + extraInfo: + type: string + userId: + type: integer + recordType: + type: string + recordId: + format: int64 + type: integer + x-go-name: RecordID + timeStamp: + format: int64 + type: integer + x-go-name: TimeStamp + seriesId: + description: Only present for episodes records + format: int64 + type: integer + x-go-name: RecordID + mergeToId: + format: int64 + type: integer + mergeToEntityType: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + EpisodeBaseRecord: + description: base episode record + properties: + absoluteNumber: + type: integer + aired: + type: string + airsAfterSeason: + type: integer + airsBeforeEpisode: + type: integer + airsBeforeSeason: + type: integer + finaleType: + description: season, midseason, or series + type: string + id: + format: int64 + type: integer + x-go-name: ID + image: + type: string + imageType: + type: integer + nullable: true + isMovie: + format: int64 + type: integer + x-go-name: IsMovie + lastUpdated: + type: string + linkedMovie: + type: integer + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + number: + type: integer + overview: + type: string + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + runtime: + type: integer + nullable: true + seasonNumber: + type: integer + seasons: + items: + $ref: '#/components/schemas/SeasonBaseRecord' + type: array + x-go-name: Seasons + seriesId: + format: int64 + type: integer + x-go-name: SeriesID + seasonName: + type: string + year: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + EpisodeExtendedRecord: + description: extended episode record + properties: + aired: + type: string + airsAfterSeason: + type: integer + airsBeforeEpisode: + type: integer + airsBeforeSeason: + type: integer + awards: + items: + $ref: '#/components/schemas/AwardBaseRecord' + type: array + x-go-name: Awards + characters: + items: + $ref: '#/components/schemas/Character' + type: array + x-go-name: Characters + companies: + items: + $ref: '#/components/schemas/Company' + type: array + contentRatings: + items: + $ref: '#/components/schemas/ContentRating' + type: array + x-go-name: ContentRatings + finaleType: + description: season, midseason, or series + type: string + id: + format: int64 + type: integer + x-go-name: ID + image: + type: string + imageType: + type: integer + nullable: true + isMovie: + format: int64 + type: integer + x-go-name: IsMovie + lastUpdated: + type: string + linkedMovie: + type: integer + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + networks: + items: + $ref: '#/components/schemas/Company' + type: array + nominations: + items: + $ref: '#/components/schemas/AwardNomineeBaseRecord' + type: array + x-go-name: Nominees + number: + type: integer + overview: + type: string + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + productionCode: + type: string + remoteIds: + items: + $ref: '#/components/schemas/RemoteID' + type: array + x-go-name: RemoteIDs + runtime: + type: integer + nullable: true + seasonNumber: + type: integer + seasons: + items: + $ref: '#/components/schemas/SeasonBaseRecord' + type: array + x-go-name: Seasons + seriesId: + format: int64 + type: integer + x-go-name: SeriesID + studios: + items: + $ref: '#/components/schemas/Company' + type: array + tagOptions: + items: + $ref: '#/components/schemas/TagOption' + type: array + x-go-name: TagOptions + trailers: + items: + $ref: '#/components/schemas/Trailer' + type: array + x-go-name: Trailers + translations: + $ref: '#/components/schemas/TranslationExtended' + year: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Favorites: + description: User favorites record + properties: + series: + items: + type: integer + type: array + x-go-name: series + movies: + items: + type: integer + type: array + x-go-name: movies + episodes: + items: + type: integer + type: array + x-go-name: episodes + artwork: + items: + type: integer + type: array + x-go-name: artwork + people: + items: + type: integer + type: array + x-go-name: people + lists: + items: + type: integer + type: array + x-go-name: list + FavoriteRecord: + description: Favorites record + properties: + series: + type: integer + x-go-name: series + movie: + type: integer + x-go-name: movies + episode: + type: integer + x-go-name: episodes + artwork: + type: integer + x-go-name: artwork + people: + type: integer + x-go-name: people + list: + type: integer + x-go-name: list + Gender: + description: gender record + properties: + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + x-go-name: Name + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + GenreBaseRecord: + description: base genre record + properties: + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + x-go-name: Name + slug: + type: string + x-go-name: Slug + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Language: + description: language record + properties: + id: + type: string + x-go-name: ID + name: + type: string + x-go-name: Name + nativeName: + type: string + x-go-name: NativeName + shortCode: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + ListBaseRecord: + description: base list record + properties: + aliases: + items: + $ref: '#/components/schemas/Alias' + type: array + x-go-name: Aliases + id: + format: int64 + type: integer + x-go-name: ID + image: + type: string + imageIsFallback: + type: boolean + isOfficial: + type: boolean + x-go-name: IsOfficial + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + overview: + type: string + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + remoteIds: + items: + $ref: '#/components/schemas/RemoteID' + type: array + x-go-name: RemoteIDs + tags: + items: + $ref: '#/components/schemas/TagOption' + type: array + x-go-name: TagOptions + score: + type: integer + url: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + ListExtendedRecord: + description: extended list record + properties: + aliases: + items: + $ref: '#/components/schemas/Alias' + type: array + x-go-name: Aliases + entities: + items: + $ref: '#/components/schemas/Entity' + type: array + x-go-name: Entities + id: + format: int64 + type: integer + x-go-name: ID + image: + type: string + imageIsFallback: + type: boolean + isOfficial: + type: boolean + x-go-name: IsOfficial + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + overview: + type: string + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + score: + format: int64 + type: integer + x-go-name: Score + url: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + MovieBaseRecord: + description: base movie record + properties: + aliases: + items: + $ref: '#/components/schemas/Alias' + type: array + x-go-name: Aliases + id: + format: int64 + type: integer + x-go-name: ID + image: + type: string + x-go-name: Image + lastUpdated: + type: string + name: + type: string + x-go-name: Name + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + score: + format: double + type: number + x-go-name: Score + slug: + type: string + x-go-name: Slug + status: + $ref: '#/components/schemas/Status' + runtime: + type: integer + nullable: true + year: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + MovieExtendedRecord: + description: extended movie record + properties: + aliases: + items: + $ref: '#/components/schemas/Alias' + type: array + x-go-name: Aliases + artworks: + items: + $ref: '#/components/schemas/ArtworkBaseRecord' + type: array + x-go-name: Artworks + audioLanguages: + items: + type: string + type: array + x-go-name: AudioLanguages + awards: + items: + $ref: '#/components/schemas/AwardBaseRecord' + type: array + x-go-name: Awards + boxOffice: + type: string + boxOfficeUS: + type: string + budget: + type: string + characters: + items: + $ref: '#/components/schemas/Character' + type: array + x-go-name: Characters + companies: + type: object + $ref: '#/components/schemas/Companies' + contentRatings: + items: + $ref: '#/components/schemas/ContentRating' + type: array + first_release: + type: object + $ref: '#/components/schemas/Release' + genres: + items: + $ref: '#/components/schemas/GenreBaseRecord' + type: array + x-go-name: Genres + id: + format: int64 + type: integer + x-go-name: ID + image: + type: string + x-go-name: Image + inspirations: + items: + $ref: '#/components/schemas/Inspiration' + type: array + x-go-name: Inspirations + lastUpdated: + type: string + lists: + items: + $ref: '#/components/schemas/ListBaseRecord' + type: array + name: + type: string + x-go-name: Name + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + originalCountry: + type: string + originalLanguage: + type: string + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + production_countries: + items: + $ref: '#/components/schemas/ProductionCountry' + type: array + x-go-name: ProductionCountries + releases: + items: + $ref: '#/components/schemas/Release' + type: array + x-go-name: Releases + remoteIds: + items: + $ref: '#/components/schemas/RemoteID' + type: array + x-go-name: RemoteIDs + runtime: + type: integer + nullable: true + score: + format: double + type: number + x-go-name: Score + slug: + type: string + x-go-name: Slug + spoken_languages: + items: + type: string + type: array + x-go-name: SpokenLanguages + status: + $ref: '#/components/schemas/Status' + studios: + items: + $ref: '#/components/schemas/StudioBaseRecord' + type: array + x-go-name: Studios + subtitleLanguages: + items: + type: string + type: array + x-go-name: SubtitleLanguages + tagOptions: + items: + $ref: '#/components/schemas/TagOption' + type: array + x-go-name: TagOptions + trailers: + items: + $ref: '#/components/schemas/Trailer' + type: array + x-go-name: Trailers + translations: + $ref: '#/components/schemas/TranslationExtended' + year: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + PeopleBaseRecord: + description: base people record + properties: + aliases: + items: + $ref: '#/components/schemas/Alias' + type: array + x-go-name: Aliases + id: + format: int64 + type: integer + x-go-name: ID + image: + type: string + lastUpdated: + type: string + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + score: + format: int64 + type: integer + x-go-name: Score + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + PeopleExtendedRecord: + description: extended people record + properties: + aliases: + items: + $ref: '#/components/schemas/Alias' + type: array + x-go-name: Aliases + awards: + items: + $ref: '#/components/schemas/AwardBaseRecord' + type: array + x-go-name: Awards + biographies: + items: + $ref: '#/components/schemas/Biography' + type: array + x-go-name: Biographies + birth: + type: string + birthPlace: + type: string + characters: + items: + $ref: '#/components/schemas/Character' + type: array + x-go-name: Characters + death: + type: string + gender: + type: integer + id: + format: int64 + type: integer + x-go-name: ID + image: + type: string + lastUpdated: + type: string + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + races: + items: + $ref: '#/components/schemas/Race' + type: array + x-go-name: Races + remoteIds: + items: + $ref: '#/components/schemas/RemoteID' + type: array + x-go-name: RemoteIDs + score: + format: int64 + type: integer + x-go-name: Score + slug: + type: string + tagOptions: + items: + $ref: '#/components/schemas/TagOption' + type: array + x-go-name: TagOptions + translations: + $ref: '#/components/schemas/TranslationExtended' + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + PeopleType: + description: people type record + properties: + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + x-go-name: Name + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Race: + description: race record + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + RecordInfo: + description: base record info + properties: + image: + type: string + x-go-name: Image + name: + type: string + x-go-name: Name + year: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Release: + description: release record + properties: + country: + type: string + date: + type: string + detail: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + RemoteID: + description: remote id record + properties: + id: + type: string + x-go-name: ID + type: + format: int64 + type: integer + x-go-name: Type + sourceName: + type: string + x-go-name: SourceName + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + SearchResult: + description: search result + properties: + aliases: + items: + type: string + type: array + companies: + items: + type: string + type: array + companyType: + type: string + country: + type: string + director: + type: string + first_air_time: + type: string + genres: + items: + type: string + type: array + id: + type: string + image_url: + type: string + name: + type: string + is_official: + type: boolean + name_translated: + type: string + network: + type: string + objectID: + type: string + officialList: + type: string + overview: + type: string + overviews: + $ref: '#/components/schemas/TranslationSimple' + overview_translated: + items: + type: string + type: array + poster: + type: string + posters: + items: + type: string + type: array + primary_language: + type: string + remote_ids: + items: + $ref: '#/components/schemas/RemoteID' + type: array + x-go-name: RemoteIDs + status: + type: string + x-go-name: Status + slug: + type: string + studios: + items: + type: string + type: array + title: + type: string + thumbnail: + type: string + translations: + $ref: '#/components/schemas/TranslationSimple' + translationsWithLang: + items: + type: string + type: array + tvdb_id: + type: string + type: + type: string + year: + type: string + type: object + SearchByRemoteIdResult: + description: search by remote reuslt is a base record for a movie, series, people, season or company search result + properties: + series: + type: object + $ref: '#/components/schemas/SeriesBaseRecord' + people: + type: object + $ref: '#/components/schemas/PeopleBaseRecord' + movie: + type: object + $ref: '#/components/schemas/MovieBaseRecord' + episode: + type: object + $ref: '#/components/schemas/EpisodeBaseRecord' + company: + type: object + $ref: '#/components/schemas/Company' + + SeasonBaseRecord: + description: season genre record + properties: + id: + type: integer + image: + type: string + imageType: + type: integer + lastUpdated: + type: string + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + number: + format: int64 + type: integer + x-go-name: Number + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + companies: + type: object + $ref: '#/components/schemas/Companies' + seriesId: + format: int64 + type: integer + x-go-name: SeriesID + type: + $ref: '#/components/schemas/SeasonType' + year: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + SeasonExtendedRecord: + description: extended season record + properties: + artwork: + items: + $ref: '#/components/schemas/ArtworkBaseRecord' + type: array + x-go-name: Artwork + companies: + type: object + $ref: '#/components/schemas/Companies' + episodes: + items: + $ref: '#/components/schemas/EpisodeBaseRecord' + type: array + x-go-name: Episodes + id: + type: integer + image: + type: string + imageType: + type: integer + lastUpdated: + type: string + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + number: + format: int64 + type: integer + x-go-name: Number + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + seriesId: + format: int64 + type: integer + x-go-name: SeriesID + trailers: + items: + $ref: '#/components/schemas/Trailer' + type: array + x-go-name: Trailers + type: + $ref: '#/components/schemas/SeasonType' + tagOptions: + items: + $ref: '#/components/schemas/TagOption' + type: array + x-go-name: TagOptions + translations: + items: + $ref: '#/components/schemas/Translation' + type: array + year: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + SeasonType: + description: season type record + properties: + alternateName: + type: string + x-go-name: Name + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + x-go-name: Name + type: + type: string + x-go-name: Type + + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + SeriesAirsDays: + description: A series airs day record + properties: + friday: + type: boolean + x-go-name: Friday + monday: + type: boolean + x-go-name: Monday + saturday: + type: boolean + x-go-name: Saturday + sunday: + type: boolean + x-go-name: Sunday + thursday: + type: boolean + x-go-name: Thursday + tuesday: + type: boolean + x-go-name: Tuesday + wednesday: + type: boolean + x-go-name: Wednesday + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + SeriesBaseRecord: + description: The base record for a series. All series airs time like firstAired, lastAired, nextAired, etc. are in US EST for US series, and for all non-US series, the time of the show’s country capital or most populous city. For streaming services, is the official release time. See https://support.thetvdb.com/kb/faq.php?id=29. + properties: + aliases: + items: + $ref: '#/components/schemas/Alias' + type: array + x-go-name: Aliases + averageRuntime: + type: integer + nullable: true + country: + type: string + defaultSeasonType: + format: int64 + type: integer + x-go-name: DefaultSeasonType + episodes: + items: + $ref: '#/components/schemas/EpisodeBaseRecord' + type: array + x-go-name: Episodes + firstAired: + type: string + id: + type: integer + image: + type: string + isOrderRandomized: + type: boolean + x-go-name: IsOrderRandomized + lastAired: + type: string + lastUpdated: + type: string + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + nextAired: + type: string + x-go-name: NextAired + originalCountry: + type: string + originalLanguage: + type: string + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + score: + format: double + type: number + x-go-name: Score + slug: + type: string + status: + $ref: '#/components/schemas/Status' + year: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + SeriesExtendedRecord: + description: The extended record for a series. All series airs time like firstAired, lastAired, nextAired, etc. are in US EST for US series, and for all non-US series, the time of the show’s country capital or most populous city. For streaming services, is the official release time. See https://support.thetvdb.com/kb/faq.php?id=29. + properties: + abbreviation: + type: string + airsDays: + $ref: '#/components/schemas/SeriesAirsDays' + airsTime: + type: string + aliases: + items: + $ref: '#/components/schemas/Alias' + type: array + x-go-name: Aliases + artworks: + items: + $ref: '#/components/schemas/ArtworkExtendedRecord' + type: array + x-go-name: Artworks + averageRuntime: + type: integer + nullable: true + characters: + items: + $ref: '#/components/schemas/Character' + type: array + x-go-name: Characters + contentRatings: + items: + $ref: '#/components/schemas/ContentRating' + type: array + country: + type: string + defaultSeasonType: + format: int64 + type: integer + x-go-name: DefaultSeasonType + episodes: + items: + $ref: '#/components/schemas/EpisodeBaseRecord' + type: array + x-go-name: Episodes + firstAired: + type: string + lists: + items: + $ref: '#/components/schemas/ListBaseRecord' + genres: + items: + $ref: '#/components/schemas/GenreBaseRecord' + type: array + x-go-name: Genres + id: + type: integer + image: + type: string + isOrderRandomized: + type: boolean + x-go-name: IsOrderRandomized + lastAired: + type: string + lastUpdated: + type: string + name: + type: string + nameTranslations: + items: + type: string + type: array + x-go-name: NameTranslations + companies: + items: + $ref: '#/components/schemas/Company' + type: array + nextAired: + type: string + x-go-name: NextAired + originalCountry: + type: string + originalLanguage: + type: string + originalNetwork: + $ref: '#/components/schemas/Company' + overview: + type: string + latestNetwork: + $ref: '#/components/schemas/Company' + overviewTranslations: + items: + type: string + type: array + x-go-name: OverviewTranslations + remoteIds: + items: + $ref: '#/components/schemas/RemoteID' + type: array + x-go-name: RemoteIDs + score: + format: double + type: number + x-go-name: Score + seasons: + items: + $ref: '#/components/schemas/SeasonBaseRecord' + type: array + x-go-name: Seasons + seasonTypes: + items: + $ref: '#/components/schemas/SeasonType' + type: array + x-go-name: Seasons + slug: + type: string + status: + $ref: '#/components/schemas/Status' + tags: + items: + $ref: '#/components/schemas/TagOption' + type: array + x-go-name: TagOptions + trailers: + items: + $ref: '#/components/schemas/Trailer' + type: array + x-go-name: Trailers + translations: + $ref: '#/components/schemas/TranslationExtended' + year: + type: string + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + SourceType: + description: source type record + properties: + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + x-go-name: Name + postfix: + type: string + prefix: + type: string + slug: + type: string + x-go-name: Slug + sort: + format: int64 + type: integer + x-go-name: Sort + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Status: + description: status record + properties: + id: + format: int64 + type: integer + x-go-name: ID + nullable: true + keepUpdated: + type: boolean + x-go-name: KeepUpdated + name: + type: string + x-go-name: Name + recordType: + type: string + x-go-name: RecordType + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + StudioBaseRecord: + description: studio record + properties: + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + x-go-name: Name + parentStudio: + type: integer + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Tag: + description: tag record + properties: + allowsMultiple: + type: boolean + x-go-name: AllowsMultiple + helpText: + type: string + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + x-go-name: Name + options: + items: + $ref: '#/components/schemas/TagOption' + type: array + x-go-name: TagOptions + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + TagOption: + description: tag option record + properties: + helpText: + type: string + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + x-go-name: Name + tag: + format: int64 + type: integer + x-go-name: Tag + tagName: + type: string + x-go-name: TagName + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Trailer: + description: trailer record + properties: + id: + format: int64 + type: integer + x-go-name: ID + language: + type: string + name: + type: string + url: + type: string + runtime: + type: integer + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + Translation: + description: translation record + properties: + aliases: + items: + type: string + type: array + isAlias: + type: boolean + isPrimary: + type: boolean + language: + type: string + x-go-name: Language + name: + type: string + overview: + type: string + tagline: + type: string + description: Only populated for movie translations. We disallow taglines without a title. + type: object + x-go-package: github.com/whip-networks/tvdb-api-v4-core/tvdb-api-v4-core/pkg/model + TranslationSimple: + description: translation simple record + additionalProperties: + type: string + example: # Ejemplo específico del objeto + ara: "تدور قصة المسلسل حول..." + ces: "Během letu č. 815 společnosti Oceanic..." + deu: "Im Bruchteil einer Sekunde gerät das Leben..." + type: object + TranslationExtended: + description: translation extended record + properties: + nameTranslations: + items: + $ref: '#/components/schemas/Translation' + type: array + overviewTranslations: + items: + $ref: '#/components/schemas/Translation' + type: array + alias: + items: + type: string + type: array + type: object + TagOptionEntity: + description: a entity with selected tag option + type: object + properties: + name: + type: string + tagName: + type: string + tagId: + type: integer + UserInfo: + description: User info record + type: object + properties: + id: + type: integer + language: + type: string + name: + type: string + type: + type: string + Inspiration: + description: Movie inspiration record + properties: + id: + format: int64 + type: integer + x-go-name: ID + type: + type: string + type_name: + type: string + url: + type: string + InspirationType: + description: Movie inspiration type record + properties: + id: + format: int64 + type: integer + x-go-name: ID + name: + type: string + description: + type: string + reference_name: + type: string + url: + type: string + ProductionCountry: + description: Production country record + properties: + id: + format: int64 + type: integer + x-go-name: ID + country: + type: string + name: + type: string + Companies: + description: Companies by type record + properties: + studio: + type: array + items: + $ref: '#/components/schemas/Company' + network: + type: array + items: + $ref: '#/components/schemas/Company' + production: + type: array + items: + $ref: '#/components/schemas/Company' + distributor: + type: array + items: + $ref: '#/components/schemas/Company' + special_effects: + type: array + items: + $ref: '#/components/schemas/Company' + Links: + description: Links for next, previous and current record + properties: + prev: + type: string + nullable: true + self: + type: string + nullable: true + next: + type: string + total_items: + type: integer + page_size: + type: integer \ No newline at end of file diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/Main.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/Main.kt index 21fd019..f204aa3 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/Main.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/Main.kt @@ -6,7 +6,6 @@ import com.dshatz.openapi2ktor.generators.clients.IClientGenerator import com.dshatz.openapi2ktor.utils.Packages import com.squareup.kotlinpoet.FileSpec import java.io.Serializable -import java.lang.RuntimeException import java.nio.file.Path /*fun main() { @@ -31,7 +30,7 @@ data class EntryPoint( ) { fun run(): Pair, List> { val parser = Parser() - val api = kotlin.runCatching { parser.fromFile(Path.of(apiFile)) }.getOrElse { throw RuntimeException("Could not read spec file at $apiFile") } + val api = kotlin.runCatching { parser.fromFile(Path.of(apiFile)) }.getOrElse { e -> throw e } val packages = Packages(config.basePackage) if (api != null) { val typeStore = TypeStore() diff --git a/settings.gradle.kts b/settings.gradle.kts index 340c096..ec6d453 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,6 +24,7 @@ rootProject.name = "openapi2ktor" include(":e2e:polymorphism") include(":e2e:binance") include(":e2e:github") +include(":e2e:tvdb") includeBuild("gradle-plugin") From 67729c29de6f499ee4acb0cf54ef85baaa1f1f63 Mon Sep 17 00:00:00 2001 From: Wayne Date: Mon, 17 Nov 2025 20:07:55 +0000 Subject: [PATCH 04/14] Add sanitization for Kdocs --- .../generators/analyze/OpenApiAnalyzer.kt | 44 ++++++++++++++++--- .../generators/clients/KtorClientGenerator.kt | 40 ++++++++++++++--- .../openapi2ktor/utils/OpenApi3Utils.kt | 4 +- 3 files changed, 75 insertions(+), 13 deletions(-) diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt index eb63f51..f618b45 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt @@ -11,7 +11,31 @@ import com.dshatz.openapi2ktor.generators.clients.IClientGenerator import com.dshatz.openapi2ktor.generators.clients.KtorClientGenerator import com.dshatz.openapi2ktor.generators.models.KotlinxCodeGenerator import com.dshatz.openapi2ktor.kdoc.DocTemplate -import com.dshatz.openapi2ktor.utils.* +import com.dshatz.openapi2ktor.utils.Packages +import com.dshatz.openapi2ktor.utils.ReferenceMetadata +import com.dshatz.openapi2ktor.utils.arrayItemRefData +import com.dshatz.openapi2ktor.utils.capitalize +import com.dshatz.openapi2ktor.utils.cleanJsonReference +import com.dshatz.openapi2ktor.utils.getComponentRef +import com.dshatz.openapi2ktor.utils.getReferenceForResponse +import com.dshatz.openapi2ktor.utils.getResponseComponentRefInfo +import com.dshatz.openapi2ktor.utils.isComponentSchemaRoot +import com.dshatz.openapi2ktor.utils.isParameterAReference +import com.dshatz.openapi2ktor.utils.isPartOfComponentSchema +import com.dshatz.openapi2ktor.utils.isReference +import com.dshatz.openapi2ktor.utils.isResponseAReference +import com.dshatz.openapi2ktor.utils.isSuccessCode +import com.dshatz.openapi2ktor.utils.jsonReference +import com.dshatz.openapi2ktor.utils.makePackageName +import com.dshatz.openapi2ktor.utils.makeRequestBodyModelName +import com.dshatz.openapi2ktor.utils.makeResponseModelName +import com.dshatz.openapi2ktor.utils.mapPaths +import com.dshatz.openapi2ktor.utils.modelPackageName +import com.dshatz.openapi2ktor.utils.oneOfRefData +import com.dshatz.openapi2ktor.utils.propRefData +import com.dshatz.openapi2ktor.utils.safeEnumEntryName +import com.dshatz.openapi2ktor.utils.safePropName +import com.dshatz.openapi2ktor.utils.sanitizeForKdoc import com.reprezen.jsonoverlay.Overlay import com.reprezen.kaizen.oasparser.model3.MediaType import com.reprezen.kaizen.oasparser.model3.OpenApi3 @@ -21,7 +45,15 @@ import com.reprezen.kaizen.oasparser.model3.Schema import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.asClassName -import kotlinx.coroutines.* +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive @@ -236,7 +268,7 @@ open class OpenApiAnalyzer( components = components, wrapMode = WrapMode.None ) - name to PropInfo(type, DocTemplate.Builder().add(schema.description).add(schema.example?.let { "\nExample: $it" }).build()) + name to PropInfo(type, DocTemplate.Builder().add(schema.description.sanitizeForKdoc()).add(schema.example?.let { "\nExample: $it" }).build()) } } } @@ -355,7 +387,7 @@ open class OpenApiAnalyzer( nameForObject.capitalize() ).copy(nullable = canBeNull), elements = enums.filterNotNull().associateWith { it.toString().safeEnumEntryName() }, - description = DocTemplate.of(description)) + description = DocTemplate.of(description.sanitizeForKdoc())) .register() } else { if (format == "date") { @@ -434,7 +466,7 @@ open class OpenApiAnalyzer( props = allProps, requiredProps = allRequired, defaultValues = defaultValues, - description = DocTemplate.of(description) + description = DocTemplate.of(description.sanitizeForKdoc()) ).register() } else if (hasAnyOfSchemas()) { // TODO: Generate an object with superset of fields but all fields optional? @@ -485,7 +517,7 @@ open class OpenApiAnalyzer( requiredProps = this.requiredFields, defaultValues = properties.mapValues { it.value.default }, description = DocTemplate.Builder() - .add(description) + .add(description.sanitizeForKdoc()) .newLine() .addMany(props.entries) { idx, (name, info) -> if (info.doc != null) { diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt index 5f6c08c..666a2f0 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt @@ -5,16 +5,44 @@ import com.dshatz.openapi2ktor.generators.Type import com.dshatz.openapi2ktor.generators.TypeStore import com.dshatz.openapi2ktor.generators.TypeStore.OperationParam.ParamLocation import com.dshatz.openapi2ktor.kdoc.DocTemplate -import com.dshatz.openapi2ktor.utils.* +import com.dshatz.openapi2ktor.utils.Packages +import com.dshatz.openapi2ktor.utils.TreeNode +import com.dshatz.openapi2ktor.utils.buildPathTree +import com.dshatz.openapi2ktor.utils.capitalize +import com.dshatz.openapi2ktor.utils.cleanJsonReference +import com.dshatz.openapi2ktor.utils.getAllPaths +import com.dshatz.openapi2ktor.utils.isSuccessCode +import com.dshatz.openapi2ktor.utils.makeRequestFunName +import com.dshatz.openapi2ktor.utils.matches +import com.dshatz.openapi2ktor.utils.removeLeadingSlash +import com.dshatz.openapi2ktor.utils.safePropName +import com.dshatz.openapi2ktor.utils.sanitizeForKdoc import com.reprezen.jsonoverlay.Overlay import com.reprezen.kaizen.oasparser.model3.OpenApi3 import com.reprezen.kaizen.oasparser.model3.Path import com.reprezen.kaizen.oasparser.model3.Response -import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.LambdaTypeName +import com.squareup.kotlinpoet.MUTABLE_MAP +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import io.ktor.client.* -import io.ktor.client.engine.* -import io.ktor.client.plugins.* +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.STAR +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.UNIT +import com.squareup.kotlinpoet.asTypeName +import io.ktor.client.HttpClient +import io.ktor.client.HttpClientConfig +import io.ktor.client.engine.HttpClientEngine +import io.ktor.client.engine.HttpClientEngineFactory +import io.ktor.client.plugins.ClientRequestException import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import net.pwall.mustache.Template @@ -263,7 +291,7 @@ class KtorClientGenerator(override val typeStore: TypeStore, val packages: Packa .build() } - val description = operation.description + val description = operation?.description?.sanitizeForKdoc() val funName = pathID.makeRequestFunName(dropPrefix = prefix) val exceptionTypeName = funName.capitalize() + "Exception" diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/utils/OpenApi3Utils.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/utils/OpenApi3Utils.kt index 3b1f72a..e4cedef 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/utils/OpenApi3Utils.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/utils/OpenApi3Utils.kt @@ -43,4 +43,6 @@ fun Response?.getResponseComponentRefInfo(): ReferenceMetadata? { data class ReferenceMetadata(val target: String) val ReferenceMetadata?.isReference get() = this != null -fun String?.reference(): ReferenceMetadata? = this?.let { ReferenceMetadata(it) } \ No newline at end of file +fun String?.reference(): ReferenceMetadata? = this?.let { ReferenceMetadata(it) } + +fun String?.sanitizeForKdoc(): String? = this?.replace("*/", "*\\/") \ No newline at end of file From 14b8ef6b5e30abfac76b4a5ca6464e3f285429c2 Mon Sep 17 00:00:00 2001 From: Wayne Date: Mon, 17 Nov 2025 20:08:14 +0000 Subject: [PATCH 05/14] Add missing build.gradle.kts for e2e:tvdb --- e2e/tvdb/build.gradle.kts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 e2e/tvdb/build.gradle.kts diff --git a/e2e/tvdb/build.gradle.kts b/e2e/tvdb/build.gradle.kts new file mode 100644 index 0000000..4c8000a --- /dev/null +++ b/e2e/tvdb/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + kotlin("jvm") + kotlin("plugin.serialization") + alias(libs.plugins.local.plugin) +} + +val input = "${project.projectDir}/src/test/resources/swagger.yml" + +dependencies { + implementation(libs.serial) + implementation(libs.bundles.ktor) + testImplementation(libs.ktor.mock) + testImplementation(libs.coroutines.test) + testImplementation(kotlin("test")) +} + +openapi3 { + generators { + create("tvdb") { + inputSpec.set(layout.projectDirectory.file("src/test/resources/swagger.yml")) + } + } +} From 9af3e522f17fb29acac7e77c985ef4d82fa1e2f5 Mon Sep 17 00:00:00 2001 From: Wayne Date: Mon, 17 Nov 2025 20:16:20 +0000 Subject: [PATCH 06/14] Remove unnecessary test class --- e2e/tvdb/src/test/kotlin/TVDBTest.kt | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 e2e/tvdb/src/test/kotlin/TVDBTest.kt diff --git a/e2e/tvdb/src/test/kotlin/TVDBTest.kt b/e2e/tvdb/src/test/kotlin/TVDBTest.kt deleted file mode 100644 index a524a36..0000000 --- a/e2e/tvdb/src/test/kotlin/TVDBTest.kt +++ /dev/null @@ -1,2 +0,0 @@ -class TVDBTest { -} \ No newline at end of file From d3dc4ac449d6c1eb4d8b8081dd78526580e64fa8 Mon Sep 17 00:00:00 2001 From: Wayne Date: Mon, 17 Nov 2025 20:21:10 +0000 Subject: [PATCH 07/14] Don't expand out wildcard imports --- .../generators/analyze/OpenApiAnalyzer.kt | 36 +----------------- .../generators/clients/KtorClientGenerator.kt | 38 +++---------------- 2 files changed, 7 insertions(+), 67 deletions(-) diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt index f618b45..2afaf55 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt @@ -11,31 +11,7 @@ import com.dshatz.openapi2ktor.generators.clients.IClientGenerator import com.dshatz.openapi2ktor.generators.clients.KtorClientGenerator import com.dshatz.openapi2ktor.generators.models.KotlinxCodeGenerator import com.dshatz.openapi2ktor.kdoc.DocTemplate -import com.dshatz.openapi2ktor.utils.Packages -import com.dshatz.openapi2ktor.utils.ReferenceMetadata -import com.dshatz.openapi2ktor.utils.arrayItemRefData -import com.dshatz.openapi2ktor.utils.capitalize -import com.dshatz.openapi2ktor.utils.cleanJsonReference -import com.dshatz.openapi2ktor.utils.getComponentRef -import com.dshatz.openapi2ktor.utils.getReferenceForResponse -import com.dshatz.openapi2ktor.utils.getResponseComponentRefInfo -import com.dshatz.openapi2ktor.utils.isComponentSchemaRoot -import com.dshatz.openapi2ktor.utils.isParameterAReference -import com.dshatz.openapi2ktor.utils.isPartOfComponentSchema -import com.dshatz.openapi2ktor.utils.isReference -import com.dshatz.openapi2ktor.utils.isResponseAReference -import com.dshatz.openapi2ktor.utils.isSuccessCode -import com.dshatz.openapi2ktor.utils.jsonReference -import com.dshatz.openapi2ktor.utils.makePackageName -import com.dshatz.openapi2ktor.utils.makeRequestBodyModelName -import com.dshatz.openapi2ktor.utils.makeResponseModelName -import com.dshatz.openapi2ktor.utils.mapPaths -import com.dshatz.openapi2ktor.utils.modelPackageName -import com.dshatz.openapi2ktor.utils.oneOfRefData -import com.dshatz.openapi2ktor.utils.propRefData -import com.dshatz.openapi2ktor.utils.safeEnumEntryName -import com.dshatz.openapi2ktor.utils.safePropName -import com.dshatz.openapi2ktor.utils.sanitizeForKdoc +import com.dshatz.openapi2ktor.utils.* import com.reprezen.jsonoverlay.Overlay import com.reprezen.kaizen.oasparser.model3.MediaType import com.reprezen.kaizen.oasparser.model3.OpenApi3 @@ -45,15 +21,7 @@ import com.reprezen.kaizen.oasparser.model3.Schema import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.asClassName -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt index 666a2f0..d46d0e9 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt @@ -5,44 +5,16 @@ import com.dshatz.openapi2ktor.generators.Type import com.dshatz.openapi2ktor.generators.TypeStore import com.dshatz.openapi2ktor.generators.TypeStore.OperationParam.ParamLocation import com.dshatz.openapi2ktor.kdoc.DocTemplate -import com.dshatz.openapi2ktor.utils.Packages -import com.dshatz.openapi2ktor.utils.TreeNode -import com.dshatz.openapi2ktor.utils.buildPathTree -import com.dshatz.openapi2ktor.utils.capitalize -import com.dshatz.openapi2ktor.utils.cleanJsonReference -import com.dshatz.openapi2ktor.utils.getAllPaths -import com.dshatz.openapi2ktor.utils.isSuccessCode -import com.dshatz.openapi2ktor.utils.makeRequestFunName -import com.dshatz.openapi2ktor.utils.matches -import com.dshatz.openapi2ktor.utils.removeLeadingSlash -import com.dshatz.openapi2ktor.utils.safePropName -import com.dshatz.openapi2ktor.utils.sanitizeForKdoc +import com.dshatz.openapi2ktor.utils.* import com.reprezen.jsonoverlay.Overlay import com.reprezen.kaizen.oasparser.model3.OpenApi3 import com.reprezen.kaizen.oasparser.model3.Path import com.reprezen.kaizen.oasparser.model3.Response -import com.squareup.kotlinpoet.AnnotationSpec -import com.squareup.kotlinpoet.ClassName -import com.squareup.kotlinpoet.CodeBlock -import com.squareup.kotlinpoet.FileSpec -import com.squareup.kotlinpoet.FunSpec -import com.squareup.kotlinpoet.KModifier -import com.squareup.kotlinpoet.LambdaTypeName -import com.squareup.kotlinpoet.MUTABLE_MAP -import com.squareup.kotlinpoet.MemberName -import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import com.squareup.kotlinpoet.PropertySpec -import com.squareup.kotlinpoet.STAR -import com.squareup.kotlinpoet.TypeName -import com.squareup.kotlinpoet.TypeSpec -import com.squareup.kotlinpoet.UNIT -import com.squareup.kotlinpoet.asTypeName -import io.ktor.client.HttpClient -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngine -import io.ktor.client.engine.HttpClientEngineFactory -import io.ktor.client.plugins.ClientRequestException +import io.ktor.client.* +import io.ktor.client.engine.* +import io.ktor.client.plugins.* import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import net.pwall.mustache.Template From 15d776d4072b4ec4cd83c5554a51228bd98e6d70 Mon Sep 17 00:00:00 2001 From: Wayne Date: Mon, 17 Nov 2025 20:23:01 +0000 Subject: [PATCH 08/14] Add test for tvdb --- e2e/tvdb/build.gradle.kts | 3 +-- e2e/tvdb/src/test/kotlin/TestTvDb.kt | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 e2e/tvdb/src/test/kotlin/TestTvDb.kt diff --git a/e2e/tvdb/build.gradle.kts b/e2e/tvdb/build.gradle.kts index 4c8000a..f22ec90 100644 --- a/e2e/tvdb/build.gradle.kts +++ b/e2e/tvdb/build.gradle.kts @@ -9,9 +9,8 @@ val input = "${project.projectDir}/src/test/resources/swagger.yml" dependencies { implementation(libs.serial) implementation(libs.bundles.ktor) - testImplementation(libs.ktor.mock) - testImplementation(libs.coroutines.test) testImplementation(kotlin("test")) + testImplementation(libs.coroutines.test) } openapi3 { diff --git a/e2e/tvdb/src/test/kotlin/TestTvDb.kt b/e2e/tvdb/src/test/kotlin/TestTvDb.kt new file mode 100644 index 0000000..61e2659 --- /dev/null +++ b/e2e/tvdb/src/test/kotlin/TestTvDb.kt @@ -0,0 +1,14 @@ +import io.ktor.client.engine.cio.CIO +import kotlinx.coroutines.test.runTest +import tvdb.client.Client +import kotlin.test.Test + +class TestTvDb { + + + @Test + fun `create`() = runTest { + val client = Client(CIO) + client.getUser() + } +} \ No newline at end of file From cd45d5d602cd164a3ee31aee8eef8bfe6a88900c Mon Sep 17 00:00:00 2001 From: Wayne Date: Mon, 17 Nov 2025 23:17:49 +0000 Subject: [PATCH 09/14] Remove duplicate source root --- .../main/kotlin/com/dshatz/openapi2ktor/plugin/Plugin.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gradle-plugin/plugin/src/main/kotlin/com/dshatz/openapi2ktor/plugin/Plugin.kt b/gradle-plugin/plugin/src/main/kotlin/com/dshatz/openapi2ktor/plugin/Plugin.kt index 74e54c3..dc10bbc 100644 --- a/gradle-plugin/plugin/src/main/kotlin/com/dshatz/openapi2ktor/plugin/Plugin.kt +++ b/gradle-plugin/plugin/src/main/kotlin/com/dshatz/openapi2ktor/plugin/Plugin.kt @@ -1,15 +1,14 @@ package com.dshatz.openapi2ktor.plugin -import com.dshatz.openapi2ktor.Cli -import com.dshatz.openapi2ktor.GeneratorConfig import com.dshatz.openapi2ktor.AdditionalPropsConfig +import com.dshatz.openapi2ktor.Cli import com.dshatz.openapi2ktor.DateLibrary +import com.dshatz.openapi2ktor.GeneratorConfig import com.dshatz.openapi2ktor.utils.capitalize import org.gradle.api.Action import org.gradle.api.DefaultTask import org.gradle.api.Named import org.gradle.api.NamedDomainObjectContainer -import org.gradle.api.NamedDomainObjectList import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.file.Directory @@ -44,7 +43,6 @@ class Plugin : Plugin { if (kotlinExtension != null) { project.afterEvaluate { kotlinExtension.sourceSets.getByName("commonMain").kotlin.srcDir(generatorExtension.outputDir.dir("src/main/kotlin")) - kotlinExtension.sourceSets.getByName("commonTest").kotlin.srcDir(generatorExtension.outputDir.dir("src/main/kotlin")) } } } From d316247195d66ec82e8f4d83bb270f8bac38795c Mon Sep 17 00:00:00 2001 From: Wayne Date: Tue, 18 Nov 2025 01:43:55 +0000 Subject: [PATCH 10/14] Add multiplatform task dependencies --- .../kotlin/com/dshatz/openapi2ktor/plugin/Plugin.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gradle-plugin/plugin/src/main/kotlin/com/dshatz/openapi2ktor/plugin/Plugin.kt b/gradle-plugin/plugin/src/main/kotlin/com/dshatz/openapi2ktor/plugin/Plugin.kt index dc10bbc..0807af2 100644 --- a/gradle-plugin/plugin/src/main/kotlin/com/dshatz/openapi2ktor/plugin/Plugin.kt +++ b/gradle-plugin/plugin/src/main/kotlin/com/dshatz/openapi2ktor/plugin/Plugin.kt @@ -21,6 +21,9 @@ import org.gradle.api.tasks.* import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon +import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile import javax.inject.Inject class Plugin : Plugin { @@ -44,6 +47,15 @@ class Plugin : Plugin { project.afterEvaluate { kotlinExtension.sourceSets.getByName("commonMain").kotlin.srcDir(generatorExtension.outputDir.dir("src/main/kotlin")) } + project.tasks.withType(KotlinCompileCommon::class.java).configureEach { + it.dependsOn(task.get()) + } + project.tasks.withType(KotlinNativeCompile::class.java).configureEach { + it.dependsOn(task.get()) + } + project.tasks.withType(KotlinJvmCompile::class.java).configureEach { + it.dependsOn(task.get()) + } } } project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { From 603dab44a1ac68e537b41b6e77a9186ecb376750 Mon Sep 17 00:00:00 2001 From: Wayne Date: Tue, 18 Nov 2025 01:44:30 +0000 Subject: [PATCH 11/14] Force usage of kotlin.Exception instead of java.lang.Exception --- .../openapi2ktor/generators/clients/KtorClientGenerator.kt | 2 +- .../openapi2ktor/generators/models/KotlinxCodeGenerator.kt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt index d46d0e9..765b8d3 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt @@ -377,7 +377,7 @@ class KtorClientGenerator(override val typeStore: TypeStore, val packages: Packa private fun buildResponseException(exceptionTypeName: String, errorResponseClass: TypeName): TypeSpec { return TypeSpec.classBuilder(exceptionTypeName) .addModifiers(KModifier.DATA) - .superclass(Exception::class) + .superclass(ClassName("kotlin", "Exception")) .addProperties(listOf( PropertySpec.builder("body", errorResponseClass) .initializer("body") diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/models/KotlinxCodeGenerator.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/models/KotlinxCodeGenerator.kt index 5e2f3f6..e03fed7 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/models/KotlinxCodeGenerator.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/models/KotlinxCodeGenerator.kt @@ -124,7 +124,7 @@ class KotlinxCodeGenerator(override val typeStore: TypeStore, private val packag .map { val superclass = responseMappings.responseSuperclasses[it] val exception = typeStore.shouldExtendException(it) - generatePrimitiveWrapper(it, superclass ?: (Exception::class.asTypeName().takeIf { exception })) + generatePrimitiveWrapper(it, superclass ?: (ClassName("kotlin", "Exception").takeIf { exception })) } } @@ -190,7 +190,7 @@ class KotlinxCodeGenerator(override val typeStore: TypeStore, private val packag .apply { type.description?.let { addKdoc(type.description.toCodeBlock(::findConcreteType)) } if (exception) { - superclass(Exception::class) + superclass(ClassName("kotlin", "Exception")) } } .addAnnotation(Serializable::class) @@ -278,7 +278,7 @@ class KotlinxCodeGenerator(override val typeStore: TypeStore, private val packag TypeSpec.classBuilder(iResponseClass).addModifiers(KModifier.SEALED).build() } val iError = iErrorClass?.let { - TypeSpec.classBuilder(iErrorClass).addModifiers(KModifier.SEALED).superclass(Exception::class).build() + TypeSpec.classBuilder(iErrorClass).addModifiers(KModifier.SEALED).superclass(ClassName("kotlin", "Exception")).build() } val successWrappedTypes = iResponseClass?.let { generatePrimitiveResponseWrappers(successTypes.values, iResponseClass) From 6fd7642ab6bb3ae86f93b0da2e21f467b9e235a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniels=20=C5=A0atcs?= Date: Mon, 15 Dec 2025 19:12:02 +0200 Subject: [PATCH 12/14] Revert "Don't expand out wildcard imports" This reverts commit b6e0a04c5d2af0cfb609adb86678b1886db7c91b. --- .../generators/analyze/OpenApiAnalyzer.kt | 36 +++++++++++++++++- .../generators/clients/KtorClientGenerator.kt | 38 ++++++++++++++++--- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt index 2afaf55..f618b45 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt @@ -11,7 +11,31 @@ import com.dshatz.openapi2ktor.generators.clients.IClientGenerator import com.dshatz.openapi2ktor.generators.clients.KtorClientGenerator import com.dshatz.openapi2ktor.generators.models.KotlinxCodeGenerator import com.dshatz.openapi2ktor.kdoc.DocTemplate -import com.dshatz.openapi2ktor.utils.* +import com.dshatz.openapi2ktor.utils.Packages +import com.dshatz.openapi2ktor.utils.ReferenceMetadata +import com.dshatz.openapi2ktor.utils.arrayItemRefData +import com.dshatz.openapi2ktor.utils.capitalize +import com.dshatz.openapi2ktor.utils.cleanJsonReference +import com.dshatz.openapi2ktor.utils.getComponentRef +import com.dshatz.openapi2ktor.utils.getReferenceForResponse +import com.dshatz.openapi2ktor.utils.getResponseComponentRefInfo +import com.dshatz.openapi2ktor.utils.isComponentSchemaRoot +import com.dshatz.openapi2ktor.utils.isParameterAReference +import com.dshatz.openapi2ktor.utils.isPartOfComponentSchema +import com.dshatz.openapi2ktor.utils.isReference +import com.dshatz.openapi2ktor.utils.isResponseAReference +import com.dshatz.openapi2ktor.utils.isSuccessCode +import com.dshatz.openapi2ktor.utils.jsonReference +import com.dshatz.openapi2ktor.utils.makePackageName +import com.dshatz.openapi2ktor.utils.makeRequestBodyModelName +import com.dshatz.openapi2ktor.utils.makeResponseModelName +import com.dshatz.openapi2ktor.utils.mapPaths +import com.dshatz.openapi2ktor.utils.modelPackageName +import com.dshatz.openapi2ktor.utils.oneOfRefData +import com.dshatz.openapi2ktor.utils.propRefData +import com.dshatz.openapi2ktor.utils.safeEnumEntryName +import com.dshatz.openapi2ktor.utils.safePropName +import com.dshatz.openapi2ktor.utils.sanitizeForKdoc import com.reprezen.jsonoverlay.Overlay import com.reprezen.kaizen.oasparser.model3.MediaType import com.reprezen.kaizen.oasparser.model3.OpenApi3 @@ -21,7 +45,15 @@ import com.reprezen.kaizen.oasparser.model3.Schema import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.asClassName -import kotlinx.coroutines.* +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt index 765b8d3..093fca5 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt @@ -5,16 +5,44 @@ import com.dshatz.openapi2ktor.generators.Type import com.dshatz.openapi2ktor.generators.TypeStore import com.dshatz.openapi2ktor.generators.TypeStore.OperationParam.ParamLocation import com.dshatz.openapi2ktor.kdoc.DocTemplate -import com.dshatz.openapi2ktor.utils.* +import com.dshatz.openapi2ktor.utils.Packages +import com.dshatz.openapi2ktor.utils.TreeNode +import com.dshatz.openapi2ktor.utils.buildPathTree +import com.dshatz.openapi2ktor.utils.capitalize +import com.dshatz.openapi2ktor.utils.cleanJsonReference +import com.dshatz.openapi2ktor.utils.getAllPaths +import com.dshatz.openapi2ktor.utils.isSuccessCode +import com.dshatz.openapi2ktor.utils.makeRequestFunName +import com.dshatz.openapi2ktor.utils.matches +import com.dshatz.openapi2ktor.utils.removeLeadingSlash +import com.dshatz.openapi2ktor.utils.safePropName +import com.dshatz.openapi2ktor.utils.sanitizeForKdoc import com.reprezen.jsonoverlay.Overlay import com.reprezen.kaizen.oasparser.model3.OpenApi3 import com.reprezen.kaizen.oasparser.model3.Path import com.reprezen.kaizen.oasparser.model3.Response -import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.LambdaTypeName +import com.squareup.kotlinpoet.MUTABLE_MAP +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import io.ktor.client.* -import io.ktor.client.engine.* -import io.ktor.client.plugins.* +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.STAR +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.UNIT +import com.squareup.kotlinpoet.asTypeName +import io.ktor.client.HttpClient +import io.ktor.client.HttpClientConfig +import io.ktor.client.engine.HttpClientEngine +import io.ktor.client.engine.HttpClientEngineFactory +import io.ktor.client.plugins.ClientRequestException import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import net.pwall.mustache.Template From f1d368bac985a819ae2903b162f11228c1fb85ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniels=20=C5=A0atcs?= Date: Mon, 15 Dec 2025 20:13:27 +0200 Subject: [PATCH 13/14] Enable all tests in ci --- .github/workflows/build.yaml | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a043f75..d8a6b45 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -39,23 +39,10 @@ jobs: with: distribution: 'temurin' java-version: '17' - cache: gradle - - - name: Restore Gradle Cache - uses: actions/cache@v4 - with: - path: | - ./gradle/wrapper - .gradle/build-cache - key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - gradle-${{ runner.os }}- - - - name: Grant Execute Permission to Gradle Wrapper - run: chmod +x gradlew - + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 - name: Run Gradle Checks and Tests - run: ./gradlew :gradle-plugin:processor:check --no-daemon --scan --stacktrace --build-cache + run: ./gradlew check --no-daemon --scan --stacktrace --build-cache continue-on-error: true # Continue even if tests fail - name: Upload Test Results @@ -88,17 +75,8 @@ jobs: with: distribution: 'temurin' java-version: '17' - cache: gradle - - - name: Restore Gradle Cache - uses: actions/cache@v4 - with: - path: | - ./gradle/wrapper - .gradle/build-cache - key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - gradle-${{ runner.os }}- + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 - id: get_version name: Get version uses: jannemattila/get-version-from-tag@v4 From 059766089cfd0b5cba611d2d119480f0259b998e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniels=20=C5=A0atcs?= Date: Mon, 15 Dec 2025 20:43:51 +0200 Subject: [PATCH 14/14] Ensure base urls have trailing slashes. --- e2e/tvdb/src/test/kotlin/TestTvDb.kt | 16 ++++++++++++++-- .../generators/analyze/OpenApiAnalyzer.kt | 2 +- .../generators/clients/KtorClientGenerator.kt | 12 ++---------- .../generators/clients/KtorHelpers.kt | 17 +++++++++++++++++ .../generators/clients/SecurityScheme.kt | 2 +- 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/e2e/tvdb/src/test/kotlin/TestTvDb.kt b/e2e/tvdb/src/test/kotlin/TestTvDb.kt index 61e2659..1052217 100644 --- a/e2e/tvdb/src/test/kotlin/TestTvDb.kt +++ b/e2e/tvdb/src/test/kotlin/TestTvDb.kt @@ -1,14 +1,26 @@ import io.ktor.client.engine.cio.CIO import kotlinx.coroutines.test.runTest import tvdb.client.Client +import tvdb.client.Servers +import tvdb.models.paths.login.post.requestBody.PostLoginRequest +import tvdb.models.paths.login.post.response.PostLoginResponse401 import kotlin.test.Test +import kotlin.test.assertIs +import kotlin.test.fail class TestTvDb { @Test - fun `create`() = runTest { + fun `try to make request`() = runTest { val client = Client(CIO) - client.getUser() + val response = client.postLogin(PostLoginRequest("weewewfweewf")) + + try { + response.dataOrThrow() + fail("Request succeeded without apikey. Something is wrong.") + } catch (e: PostLoginResponse401) { + + } } } \ No newline at end of file diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt index f618b45..eba994b 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/analyze/OpenApiAnalyzer.kt @@ -292,7 +292,7 @@ open class OpenApiAnalyzer( referenceData: ReferenceMetadata?, wrapMode: WrapMode ): Type { - println("Entering ${"component".takeIf { components } ?: ""} ${jsonReference.cleanJsonReference()}") +// println("Entering ${"component".takeIf { components } ?: ""} ${jsonReference.cleanJsonReference()}") val packageName = makePackageName(jsonReference, packages.models) diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt index 093fca5..171160b 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorClientGenerator.kt @@ -130,15 +130,7 @@ class KtorClientGenerator(override val typeStore: TypeStore, val packages: Packa } val enum = TypeSpec.enumBuilder(ClassName(packages.client, "Servers")) .primaryConstructor(FunSpec.constructorBuilder().addParameter("url", String::class).build()) - .apply { - enumNames.forEach { (server, name) -> - addEnumConstant(name, - TypeSpec.anonymousClassBuilder() - .addSuperclassConstructorParameter("%S", server.url) - .build() - ) - } - } + .addServerEnumConstants(enumNames) .addProperty(PropertySpec.builder("url", String::class).initializer("url").build()) .build() return FileSpec.builder(ClassName(packages.client, "Servers")).addType(enum).build() @@ -152,7 +144,7 @@ class KtorClientGenerator(override val typeStore: TypeStore, val packages: Packa val params = listOf( ParameterSpec.builder("engine", HttpClientEngine::class).build(), ParameterSpec.builder("baseUrl", String::class.asTypeName()) - .run { if (api.hasServers()) defaultValue("%S", api.servers.first().url) else this } + .run { if (api.hasServers()) defaultValue("%S", api.servers.first().url.ensureTrailingSlash()) else this } .build(), ParameterSpec.builder("json", Json::class.asTypeName()).defaultValue("Json { ignoreUnknownKeys = true }").build(), ParameterSpec.builder("config", configLambdaType).defaultValue(CodeBlock.of("{}")).build() diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorHelpers.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorHelpers.kt index 4cd5c87..754e346 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorHelpers.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/KtorHelpers.kt @@ -3,11 +3,13 @@ package com.dshatz.openapi2ktor.generators.clients import com.dshatz.openapi2ktor.generators.TypeStore import com.dshatz.openapi2ktor.generators.clients.KtorHelpers.contentTypeClass import com.dshatz.openapi2ktor.generators.clients.KtorHelpers.contentTypeExtension +import com.reprezen.kaizen.oasparser.model3.Server import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.MemberName import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.TypeSpec internal object KtorHelpers { val setBodyExtension = MemberName("io.ktor.client.request", "setBody", isExtension = true) @@ -29,4 +31,19 @@ internal fun CodeBlock.Builder.setContentType(requestBodyInfo: TypeStore.Request it.mediaType )) } +} + + +internal fun String.ensureTrailingSlash(): String { + return if (this.endsWith("/")) this else "$this/" +} + +internal fun TypeSpec.Builder.addServerEnumConstants(values: Map) = apply { + values.forEach { (server, name) -> + addEnumConstant(name, + TypeSpec.anonymousClassBuilder() + .addSuperclassConstructorParameter("%S", server.url.ensureTrailingSlash()) + .build() + ) + } } \ No newline at end of file diff --git a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/SecurityScheme.kt b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/SecurityScheme.kt index e04355e..100fa75 100644 --- a/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/SecurityScheme.kt +++ b/gradle-plugin/processor/src/main/kotlin/com/dshatz/openapi2ktor/generators/clients/SecurityScheme.kt @@ -58,7 +58,7 @@ sealed class Http(): SecurityScheme() { } override fun generateApplicator(name: String): CodeBlock { - return CodeBlock.of("%L?.bearer?.let { %M(it) }", generateAccessor(name), bearerMethod) + return CodeBlock.builder().addStatement("%L?.bearer?.let { %M(it) }", generateAccessor(name), bearerMethod).build() } }