Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ object JsonApiConstants {

object FileNames {
const val JSON_X_EXTENSIONS = "JsonXExtensions"
const val JSON_X_CORE_EXTENSIONS = "JsonXCoreExtensions"
const val JSON_X_SERIALIZER_MODULE = "JsonXSerializerModule"
const val TYPE_ADAPTER_FACTORY = "TypeAdapterFactory"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import com.infinum.jsonapix.processor.models.JsonApiXErrorHolder
import com.infinum.jsonapix.processor.models.JsonApiXHolder
import com.infinum.jsonapix.processor.models.JsonApiXLinksHolder
import com.infinum.jsonapix.processor.models.JsonApiXMetaHolder
import com.infinum.jsonapix.processor.specs.jsonxextensions.JsonXExtensionsSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.JsonXCoreExtensionsSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.JsonXModelExtensionsSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.JsonXSerializerModuleSpecBuilder
import com.infinum.jsonapix.processor.specs.models.ClassInfo
import com.infinum.jsonapix.processor.specs.specbuilders.IncludedSpecBuilder
import com.squareup.kotlinpoet.ClassName
import java.io.File
Expand All @@ -21,22 +24,28 @@ internal class JsonXExtensionsSpecGenerator(
) : SpecGenerator {

override fun generate(outputDir: File) {
val builder = JsonXExtensionsSpecBuilder()
val specsMap = hashMapOf<ClassName, ClassInfo>()

// Setup custom types
builder.addCustomLinks(linksHolders.map { it.className })
builder.addCustomMetas(metaHolders.map { it.className })
builder.addCustomErrors(errorHolders.associate { it.type to it.className })

// Add each holder
// 1. Write one extensions file per holder
holders.forEach { holder ->
addHolder(builder, holder)
val classInfo = buildClassInfo(holder)
specsMap[holder.className] = classInfo
JsonXModelExtensionsSpecBuilder.build(holder.className, classInfo).writeTo(outputDir)
}
Comment thread
DrakslerT marked this conversation as resolved.

builder.build().writeTo(outputDir)
// 2. Write the serializer module file (with format property)
JsonXSerializerModuleSpecBuilder.build(
specsMap = specsMap,
customLinks = linksHolders.map { it.className },
customErrors = errorHolders.associate { it.type to it.className },
customMeta = metaHolders.map { it.className },
).writeTo(outputDir)

// 3. Write the core extensions file
JsonXCoreExtensionsSpecBuilder.build().writeTo(outputDir)
}

private fun addHolder(builder: JsonXExtensionsSpecBuilder, holder: JsonApiXHolder) {
private fun buildClassInfo(holder: JsonApiXHolder): ClassInfo {
val metaInfo = metaHolders.toMetaInfo(holder.type)
val linksInfo = linksHolders.toLinksInfo(holder.type)

Expand Down Expand Up @@ -75,17 +84,16 @@ internal class JsonXExtensionsSpecGenerator(
null
}

builder.add(
return ClassInfo(
type = holder.type,
metaInfo = metaInfo,
linksInfo = linksInfo,
isNullable = holder.isNullable,
data = className,
wrapper = jsonWrapperClassName,
wrapperList = jsonWrapperListClassName,
resourceObject = resourceObjectClassName,
attributesObject = attributesClassName,
relationshipsObject = relationshipsClassName,
jsonWrapperClassName = jsonWrapperClassName,
jsonWrapperListClassName = jsonWrapperListClassName,
resourceObjectClassName = resourceObjectClassName,
attributesWrapperClassName = attributesClassName,
relationshipsObjectClassName = relationshipsClassName,
includedStatement = IncludedSpecBuilder.build(holder.oneRelationships, holder.manyRelationships),
includedListStatement = IncludedSpecBuilder.buildForList(holder.oneRelationships, holder.manyRelationships),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package com.infinum.jsonapix.processor.specs.jsonxextensions

import com.infinum.jsonapix.core.common.JsonApiConstants
import com.infinum.jsonapix.core.resources.Error
import com.infinum.jsonapix.core.resources.Errors
import com.infinum.jsonapix.processor.specs.jsonxextensions.funspecbuilders.DeserializeFunSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.funspecbuilders.DeserializeListFunSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.funspecbuilders.ManyRelationshipModelFunSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.funspecbuilders.OneRelationshipModelFunSpecBuilder
import com.infinum.jsonapix.retrofit.JsonXHttpException
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName
import retrofit2.HttpException

/**
* Builds JsonXCoreExtensions.kt containing shared utility functions:
* - toManyRelationshipModel()
* - toOneRelationshipModel()
* - decodeJsonApiError()
* - asJsonXHttpException() (conditional on retrofit module)
* - decodeJsonApiXString()
* - decodeJsonApiXListString()
*/
internal object JsonXCoreExtensionsSpecBuilder {

@SuppressWarnings("SpreadOperator")
fun build(): FileSpec {
val fileSpec = FileSpec.builder(
JsonApiConstants.Packages.JSONX,
JsonApiConstants.FileNames.JSON_X_CORE_EXTENSIONS,
)

fileSpec.addAnnotation(
AnnotationSpec.builder(JvmName::class)
.addMember("%S", JsonApiConstants.FileNames.JSON_X_EXTENSIONS)
.useSiteTarget(AnnotationSpec.UseSiteTarget.FILE).build(),
)
fileSpec.addAnnotation(
AnnotationSpec.builder(JvmMultifileClass::class)
.useSiteTarget(AnnotationSpec.UseSiteTarget.FILE).build(),
)
Comment on lines +41 to +49

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is added as before, the JsonXExtensions file used the JVM name:
Image

Now all the files have:
Image

Not sure if its even needed though @thisAAY 🤔


fileSpec.addImport(
JsonApiConstants.Packages.KOTLINX_SERIALIZATION,
*JsonApiConstants.Imports.KOTLINX,
)

fileSpec.addImport(
JsonApiConstants.Packages.CORE_DISCRIMINATORS,
*JsonApiConstants.Imports.CORE_EXTENSIONS,
)

fileSpec.addImport(
JsonApiConstants.Packages.JSONX,
*JsonApiConstants.Imports.JSON_X,
)

fileSpec.addFunction(ManyRelationshipModelFunSpecBuilder.build())
fileSpec.addFunction(OneRelationshipModelFunSpecBuilder.build())

fileSpec.addFunction(decodeJsonApiErrorFunSpec())

if (hasRetrofitModule()) {
fileSpec.addImport(
JsonApiConstants.Packages.CORE_RESOURCES,
JsonApiConstants.Imports.ERRORS,
)
fileSpec.addFunction(asJsonXHttpExceptionFunSpec())
}

fileSpec.addFunction(DeserializeFunSpecBuilder.build())
fileSpec.addFunction(DeserializeListFunSpecBuilder.build())

return fileSpec.build()
}

private fun decodeJsonApiErrorFunSpec(): FunSpec {
val typeVariableName =
TypeVariableName.invoke(JsonApiConstants.Members.GENERIC_TYPE_VARIABLE)

return FunSpec.builder(JsonApiConstants.Members.DECODE_JSON_API_ERROR)
.addParameter(
ParameterSpec(
name = JsonApiConstants.Members.ERROR_BODY,
type = String::class.asTypeName(),
),
)
.returns(List::class.asClassName().parameterizedBy(typeVariableName))
.addModifiers(KModifier.INLINE)
.beginControlFlow("return try")
.addTypeVariable(
typeVariableName.copy(
reified = true,
bounds = listOf(Error::class.asTypeName()),
),
)
.addStatement(
"format.decodeFromString<%T<%L>>(%L).errors",
Errors::class,
JsonApiConstants.Members.GENERIC_TYPE_VARIABLE,
JsonApiConstants.Members.ERROR_BODY,
)
.nextControlFlow("catch (e: %T)", IllegalArgumentException::class.asClassName())
.addStatement("emptyList()")
.endControlFlow()
.build()
}

private fun asJsonXHttpExceptionFunSpec(): FunSpec {
val typeVariableName =
TypeVariableName.invoke(JsonApiConstants.Members.GENERIC_TYPE_VARIABLE)

return FunSpec.builder(JsonApiConstants.Members.AS_JSON_X_HTTP_EXCEPTION)
.receiver(HttpException::class)
.returns(JsonXHttpException::class.asClassName().parameterizedBy(typeVariableName))
.addModifiers(KModifier.INLINE)
.addTypeVariable(
typeVariableName.copy(
reified = true,
bounds = listOf(Error::class.asTypeName()),
),
)
.addStatement("val response = response()")
.addStatement("val body = response?.errorBody()?.charStream()?.readText()")
.addStatement(
"val errors = if (body != null) %L<Model>(body) else emptyList()",
JsonApiConstants.Members.DECODE_JSON_API_ERROR,
)
.addStatement("return JsonXHttpException(response(), errors)")
Comment thread
DrakslerT marked this conversation as resolved.
Outdated
.build()
}

@Suppress("SwallowedException")
private fun hasRetrofitModule(): Boolean {
return try {
Class.forName("com.infinum.jsonapix.retrofit.JsonXHttpException")
true
} catch (e: Exception) {
false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.infinum.jsonapix.processor.specs.jsonxextensions

import com.infinum.jsonapix.core.common.JsonApiConstants
import com.infinum.jsonapix.processor.specs.jsonxextensions.funspecbuilders.ListItemResourceObjectFunSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.funspecbuilders.OriginalDataResourceObjectFunSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.funspecbuilders.ResourceObjectFunSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.funspecbuilders.SerializeFunSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.funspecbuilders.SerializeListFunSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.funspecbuilders.WrapperFunSpecBuilder
import com.infinum.jsonapix.processor.specs.jsonxextensions.funspecbuilders.WrapperListFunSpecBuilder
import com.infinum.jsonapix.processor.specs.models.ClassInfo
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec

/**
* Builds one FileSpec per model containing that model's extension functions:
* - Original.toResourceObject(meta, links)
* - Model.toResourceObject()
* - Item.toResourceObject()
* - Model.toJsonApiX()
* - List.toJsonApiXList()
* - Model.toJsonApiXString(...)
* - List.toJsonApiXString(...)
*/
@SuppressWarnings("SpreadOperator")
internal object JsonXModelExtensionsSpecBuilder {

fun build(originalClass: ClassName, classInfo: ClassInfo): FileSpec {
val fileName = "${originalClass.simpleName}Extensions"

val fileSpec = FileSpec.builder(
JsonApiConstants.Packages.JSONX,
fileName,
Comment thread
DrakslerT marked this conversation as resolved.
)

fileSpec.addAnnotation(
AnnotationSpec.builder(JvmName::class)
.addMember("%S", JsonApiConstants.FileNames.JSON_X_EXTENSIONS)
.useSiteTarget(AnnotationSpec.UseSiteTarget.FILE).build(),
)
fileSpec.addAnnotation(
AnnotationSpec.builder(JvmMultifileClass::class)
.useSiteTarget(AnnotationSpec.UseSiteTarget.FILE).build(),
)

fileSpec.addImport(
JsonApiConstants.Packages.KOTLINX_SERIALIZATION,
*JsonApiConstants.Imports.KOTLINX,
)

fileSpec.addImport(
JsonApiConstants.Packages.CORE_DISCRIMINATORS,
*JsonApiConstants.Imports.CORE_EXTENSIONS,
)

fileSpec.addImport(
JsonApiConstants.Packages.JSONX,
*JsonApiConstants.Imports.JSON_X,
)

fileSpec.addImport(
JsonApiConstants.Packages.CORE_SHARED,
JsonApiConstants.Imports.MAP_SAFE,
)

fileSpec.addImport(
JsonApiConstants.Packages.CORE_SHARED,
JsonApiConstants.Imports.FLAT_MAP_SAFE,
)

fileSpec.addFunction(
OriginalDataResourceObjectFunSpecBuilder.build(
originalClass,
classInfo.resourceObjectClassName,
classInfo.attributesWrapperClassName,
classInfo.relationshipsObjectClassName,
classInfo.metaInfo?.resourceObjectClassName,
classInfo.linksInfo?.resourceObjectLinks,
),
)
fileSpec.addFunction(
ResourceObjectFunSpecBuilder.build(
originalClass,
classInfo.resourceObjectClassName,
classInfo.attributesWrapperClassName,
classInfo.relationshipsObjectClassName,
),
)
fileSpec.addFunction(
ListItemResourceObjectFunSpecBuilder.build(
originalClass,
classInfo.resourceObjectClassName,
classInfo.attributesWrapperClassName,
classInfo.relationshipsObjectClassName,
),
)
fileSpec.addFunction(
WrapperFunSpecBuilder.build(
originalClass,
classInfo.jsonWrapperClassName,
classInfo.includedStatement?.toString(),
),
)
fileSpec.addFunction(
WrapperListFunSpecBuilder.build(
originalClass,
classInfo.jsonWrapperListClassName,
classInfo.includedListStatement?.toString(),
),
)
fileSpec.addFunction(SerializeFunSpecBuilder.build(originalClass, classInfo.isNullable))
fileSpec.addFunction(SerializeListFunSpecBuilder.build(originalClass))

return fileSpec.build()
}
}
Loading
Loading