Skip to content

Commit

Permalink
Merge pull request #122 from outfoxx/fix/inerited-patching
Browse files Browse the repository at this point in the history
More fixes for inheritance with patchable types
  • Loading branch information
kdubb authored Dec 2, 2024
2 parents 917ec10 + 1ca4661 commit 0d118cb
Show file tree
Hide file tree
Showing 7 changed files with 404 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@ class KotlinTypeRegistry(
}
}

if (isPatchable && options.contains(ImplementModel)) {
if (isPatchable && options.contains(ImplementModel) && !typeBuilder.modifiers.contains(KModifier.ABSTRACT)) {

val initLambdaTypeName = LambdaTypeName.get(className, listOf(), UNIT)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import io.outfoxx.sunday.generator.APIAnnotationName.*
import io.outfoxx.sunday.generator.common.DefinitionLocation
import io.outfoxx.sunday.generator.common.GenerationHeaders
import io.outfoxx.sunday.generator.common.ShapeIndex
import io.outfoxx.sunday.generator.kotlin.utils.kotlinIdentifierName
import io.outfoxx.sunday.generator.swift.SwiftTypeRegistry.Option.AddGeneratedHeader
import io.outfoxx.sunday.generator.swift.utils.*
import io.outfoxx.sunday.generator.utils.*
Expand Down Expand Up @@ -1274,8 +1275,16 @@ class SwiftTypeRegistry(

if (isPatchable) {

val typeSpec = typeBuilder.build()
val propertySpecs = typeSpec.propertySpecs.filter { it.name != DESCRIPTION_PROP_NAME }
val properties =
(inheritedDeclaredProperties + localDeclaredProperties)
.associate { property ->
val propertyTypeName = resolvePropertyTypeName(property, className, context)
.run {
val base = if (property.nullable) PATCH_OP else UPDATE_OP
base.parameterizedBy(makeNonOptional()).makeOptional()
}
property.swiftIdentifierName to propertyTypeName
}

val patchOpExt =
ExtensionSpec.builder(ANY_PATCH_OP)
Expand All @@ -1285,9 +1294,9 @@ class SwiftTypeRegistry(
.addModifiers(PUBLIC, STATIC)
.returns(SelfTypeName.INSTANCE)
.apply {
for (propertySpec in propertySpecs) {
for ((propertyName, propertyTypeName) in properties) {
addParameter(
ParameterSpec.builder(propertySpec.name, propertySpec.type)
ParameterSpec.builder(propertyName, propertyTypeName)
.defaultValue(".none")
.build(),
)
Expand All @@ -1297,7 +1306,7 @@ class SwiftTypeRegistry(
"%T.merge(%T(%L))",
SelfTypeName.INSTANCE,
className,
propertySpecs.map { CodeBlock.of("%L: %L", it.name, it.name) }.joinToCode(",%W"),
properties.keys.map { CodeBlock.of("%L: %L", it, it) }.joinToCode(",%W"),
)
.build(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ import io.outfoxx.sunday.generator.kotlin.KotlinTypeRegistry.Option.JacksonAnnot
import io.outfoxx.sunday.generator.kotlin.tools.findType
import io.outfoxx.sunday.generator.kotlin.tools.generateTypes
import io.outfoxx.sunday.test.extensions.ResourceUri
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
Expand Down Expand Up @@ -1054,6 +1051,121 @@ class RamlTypeAnnotationsTest {
)
}

@Test
fun `test discriminated patchable class generation`(
@ResourceUri("raml/type-gen/annotations/type-patchable-disc.raml") testUri: URI,
) {

val typeRegistry = KotlinTypeRegistry("io.test", null, Server, setOf(ImplementModel, JacksonAnnotations))

val generatedTypes = generateTypes(testUri, typeRegistry)
val typeSpec = findType("io.test.Test", generatedTypes)

assertEquals(
"""
package io.test
import com.fasterxml.jackson.`annotation`.JsonInclude
import com.fasterxml.jackson.`annotation`.JsonSubTypes
import com.fasterxml.jackson.`annotation`.JsonTypeInfo
import io.outfoxx.sunday.json.patch.Patch
import kotlin.Any
import kotlin.Boolean
import kotlin.String
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type",
)
@JsonSubTypes(value = [
JsonSubTypes.Type(value = Child::class)
])
public abstract class Test() : Patch {
public abstract val type: TestType
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return true
}
override fun toString(): String = ""${'"'}Test()""${'"'}
}
""".trimIndent(),
buildString {
FileSpec.get("io.test", typeSpec)
.writeTo(this)
},
)

val typeSpec2 = findType("io.test.Child", generatedTypes)

assertEquals(
"""
package io.test
import com.fasterxml.jackson.`annotation`.JsonInclude
import com.fasterxml.jackson.`annotation`.JsonTypeName
import io.outfoxx.sunday.json.patch.Patch
import io.outfoxx.sunday.json.patch.PatchOp
import io.outfoxx.sunday.json.patch.UpdateOp
import kotlin.Any
import kotlin.Boolean
import kotlin.Int
import kotlin.String
import kotlin.Unit
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonTypeName("child")
public class Child(
public var child: UpdateOp<String> = PatchOp.none(),
) : Test(),
Patch {
override val type: TestType
get() = TestType.Child
override fun hashCode(): Int {
var result = 31 * super.hashCode()
result = 31 * result + child.hashCode()
return result
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Child
if (child != other.child) return false
return true
}
override fun toString(): String = ""${'"'}Child(child='${'$'}child')""${'"'}
public companion object {
public inline fun merge(`init`: Child.() -> Unit): PatchOp.Set<Child> {
val patch = Child()
patch.init()
return PatchOp.Set(patch)
}
public inline fun patch(`init`: Child.() -> Unit): Child = merge(init).value
}
}
""".trimIndent(),
buildString {
FileSpec.get("io.test", typeSpec2)
.writeTo(this)
},
)
}

@Test
fun `test types can be generated in one mode and overridden in another`(
@ResourceUri("raml/type-gen/annotations/type-kotlin-type-dual.raml") testUri: URI,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1404,4 +1404,196 @@ class RamlTypeAnnotationsTest {
},
)
}

@Test
fun `test discriminated patchable class generation`(
compiler: SwiftCompiler,
@ResourceUri("raml/type-gen/annotations/type-patchable-disc.raml") testUri: URI,
) {

val typeRegistry = SwiftTypeRegistry(setOf())

val generatedTypes = generateTypes(testUri, typeRegistry, compiler)
val testTypeTypeSpec = findType("TestType", generatedTypes)
val testTypeSpec = findType("Test", generatedTypes)
val childTypeSpec = findType("Child", generatedTypes)

assertEquals(
"""
public enum TestType : String, CaseIterable, Codable {
case child = "child"
}
""".trimIndent(),
buildString {
FileSpec.builder("", testTypeTypeSpec.name)
.addType(testTypeTypeSpec)
.apply {
testTypeTypeSpec.tag<AssociatedExtensions>()?.forEach { addExtension(it) }
}
.build()
.writeTo(this)
},
)
assertEquals(
"""
import Sunday
public class Test : Codable, CustomDebugStringConvertible {
public var type: TestType {
fatalError("abstract type method")
}
public var debugDescription: String {
return DescriptionBuilder(Test.self)
.build()
}
public init() {
}
public required init(from decoder: Decoder) throws {
let _ = try decoder.container(keyedBy: CodingKeys.self)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.type, forKey: .type)
}
public enum AnyRef : Codable, CustomDebugStringConvertible {
case child(Child)
public var value: Test {
switch self {
case .child(let value): return value
}
}
public var debugDescription: String {
switch self {
case .child(let value): return value.debugDescription
}
}
public init(value: Test) {
switch value {
case let value as Child: self = .child(value)
default: fatalError("Invalid value type")
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(TestType.self, forKey: CodingKeys.type)
switch type {
case .child: self = .child(try Child(from: decoder))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .child(let value): try container.encode(value)
}
}
}
fileprivate enum CodingKeys : String, CodingKey {
case type = "type"
}
}
extension AnyPatchOp where Value == Test {
public static func merge() -> Self {
Self.merge(Test())
}
}
""".trimIndent(),
buildString {
FileSpec.builder("", testTypeSpec.name)
.addType(testTypeSpec)
.apply {
testTypeSpec.tag<AssociatedExtensions>()?.forEach { addExtension(it) }
}
.build()
.writeTo(this)
},
)

assertEquals(
"""
import Sunday
public class Child : Test {
public override var type: TestType {
return TestType.child
}
public var child: UpdateOp<String>?
public override var debugDescription: String {
return DescriptionBuilder(Child.self)
.add(type, named: "type")
.add(child, named: "child")
.build()
}
public init(child: UpdateOp<String>? = .none) {
self.child = child
super.init()
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.child = try container.decodeIfExists(String.self, forKey: .child)
try super.init(from: decoder)
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfExists(self.child, forKey: .child)
}
public func withChild(child: UpdateOp<String>?) -> Child {
return Child(child: child)
}
fileprivate enum CodingKeys : String, CodingKey {
case child = "child"
}
}
extension AnyPatchOp where Value == Child {
public static func merge(child: UpdateOp<String>? = .none) -> Self {
Self.merge(Child(child: child))
}
}
""".trimIndent(),
buildString {
FileSpec.builder("", childTypeSpec.name)
.addType(childTypeSpec)
.apply {
childTypeSpec.tag<AssociatedExtensions>()?.forEach { addExtension(it) }
}
.build()
.writeTo(this)
},
)
}
}
Loading

0 comments on commit 0d118cb

Please sign in to comment.