Skip to content

Commit

Permalink
Add support to Union Type (#123)
Browse files Browse the repository at this point in the history
* Add support to Type Union

* bump version to 0.12.0
  • Loading branch information
miku1958 authored May 11, 2024
1 parent 7f33e0c commit e94bff6
Show file tree
Hide file tree
Showing 21 changed files with 534 additions and 99 deletions.
57 changes: 57 additions & 0 deletions demo/basic/generated/kotlin/BridgeTypes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ data class OverriddenFullSize(
@JvmField val nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?,
@JvmField val numUnion1: OverriddenFullSizeMembersNumUnion1Type,
@JvmField val foo: OverriddenFullSizeMembersFooType,
@JvmField val unionType: OverriddenFullSizeMembersUnionTypeType,
@JvmField val width: Float,
@JvmField val height: Float,
@JvmField val scale: Float,
Expand Down Expand Up @@ -117,3 +118,59 @@ data class OverriddenFullSizeMembersFooType(
@JvmField val stringField: String,
@JvmField val numberField: Float,
)

sealed class OverriddenFullSizeMembersUnionTypeType(val value: Any) {
data class NumEnumValue(val value: NumEnum) : OverriddenFullSizeMembersUnionTypeType()
data class DefaultEnumValue(val value: DefaultEnum) : OverriddenFullSizeMembersUnionTypeType()
data class StringArrayValue(val value: Array<String>) : OverriddenFullSizeMembersUnionTypeType()
data class StringForStringDictionaryValue(val value: Map<String, String>) : OverriddenFullSizeMembersUnionTypeType()
data class BooleanValue(val value: Boolean) : OverriddenFullSizeMembersUnionTypeType()
data class FloatValue(val value: Float) : OverriddenFullSizeMembersUnionTypeType()
data class StringValue(val value: String) : OverriddenFullSizeMembersUnionTypeType()
}

class OverriddenFullSizeMembersUnionTypeTypeAdapter : JsonSerializer<OverriddenFullSizeMembersUnionTypeType>, JsonDeserializer<OverriddenFullSizeMembersUnionTypeType> {
override fun serialize(src: OverriddenFullSizeMembersUnionTypeType, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return context.serialize(src.value)
}

override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): OverriddenFullSizeMembersUnionTypeType {
try {
return OverriddenFullSizeMembersUnionTypeType.NumEnumValue(context.deserialize(json, NumEnum::class.java))
} catch (e: Exception) {
// Ignore the exception and try the next type
}
try {
return OverriddenFullSizeMembersUnionTypeType.DefaultEnumValue(context.deserialize(json, DefaultEnum::class.java))
} catch (e: Exception) {
// Ignore the exception and try the next type
}
try {
return OverriddenFullSizeMembersUnionTypeType.StringArrayValue(context.deserialize(json, Array<String>::class.java))
} catch (e: Exception) {
// Ignore the exception and try the next type
}
try {
return OverriddenFullSizeMembersUnionTypeType.StringForStringDictionaryValue(context.deserialize(json, Map<String, String>::class.java))
} catch (e: Exception) {
// Ignore the exception and try the next type
}
try {
return OverriddenFullSizeMembersUnionTypeType.BooleanValue(context.deserialize(json, Boolean::class.java))
} catch (e: Exception) {
// Ignore the exception and try the next type
}
try {
return OverriddenFullSizeMembersUnionTypeType.FloatValue(context.deserialize(json, Float::class.java))
} catch (e: Exception) {
// Ignore the exception and try the next type
}
try {
return OverriddenFullSizeMembersUnionTypeType.StringValue(context.deserialize(json, String::class.java))
} catch (e: Exception) {
// Ignore the exception and try the next type
}

throw IllegalArgumentException("Unexpected JSON type: ${json.javaClass}")
}
}
8 changes: 4 additions & 4 deletions demo/basic/generated/kotlin/IHtmlApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface IHtmlApiBridge {
fun getName(callback: Callback<IHtmlApiGetNameReturnType>)
fun getAge(gender: IHtmlApiGetAgeGender, callback: Callback<IHtmlApiGetAgeReturnType>)
fun testDictionaryWithAnyKey(dict: Map<String, String>)
fun testDefaultValue(bool: Boolean? = null, bool2: Boolean?, bool3: Boolean = true, num: Float = 1, string: String = "hello", callback: Callback<nterfaceWithDefeaultValue>)
fun testDefaultValue(bool: Boolean? = null, bool2: Boolean?, bool3: Boolean = true, num: Float = 1, string: String = "hello", callback: Callback<ObjectWithDefeaultValue>)
}

open class IHtmlApiBridge(editor: WebEditor, gson: Gson) : JsBridge(editor, gson, "htmlApi"), IHtmlApiBridge {
Expand Down Expand Up @@ -88,8 +88,8 @@ open class IHtmlApiBridge(editor: WebEditor, gson: Gson) : JsBridge(editor, gson
))
}

override fun testDefaultValue(bool: Boolean? = null, bool2: Boolean?, bool3: Boolean = true, num: Float = 1, string: String = "hello", callback: Callback<nterfaceWithDefeaultValue>) {
executeJsForResponse(nterfaceWithDefeaultValue::class.java, "testDefaultValue", callback, mapOf(
override fun testDefaultValue(bool: Boolean? = null, bool2: Boolean?, bool3: Boolean = true, num: Float = 1, string: String = "hello", callback: Callback<ObjectWithDefeaultValue>) {
executeJsForResponse(ObjectWithDefeaultValue::class.java, "testDefaultValue", callback, mapOf(
"bool" to bool
"bool2" to bool2
"bool3" to bool3
Expand Down Expand Up @@ -133,6 +133,6 @@ class IHtmlApiGetAgeReturnTypeTypeAdapter : JsonSerializer<IHtmlApiGetAgeReturnT
}
}

data class nterfaceWithDefeaultValue(
data class ObjectWithDefeaultValue(
@JvmField val defaultValue: Boolean? = true,
)
4 changes: 2 additions & 2 deletions demo/basic/generated/swift/IHtmlApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public class IHtmlApi {
jsExecutor.execute(with: "htmlApi", feature: "testDictionaryWithAnyKey", args: args, completion: completion)
}

public func testDefaultValue(bool: Bool? = nil, bool2: Bool?, bool3: Bool = true, num: Double = 1, string: String = "hello", completion: @escaping BridgeCompletion<nterfaceWithDefeaultValue>) {
public func testDefaultValue(bool: Bool? = nil, bool2: Bool?, bool3: Bool = true, num: Double = 1, string: String = "hello", completion: @escaping BridgeCompletion<ObjectWithDefeaultValue>) {
struct Args: Encodable {
let bool: Bool?
let bool2: Bool?
Expand Down Expand Up @@ -130,7 +130,7 @@ public enum IHtmlApiGetAgeReturnType: Int, Codable {
case _22 = 22
}

public struct nterfaceWithDefeaultValue: Codable {
public struct ObjectWithDefeaultValue: Codable {
public var defaultValue: Bool?

public init(defaultValue: Bool? = true) {
Expand Down
60 changes: 59 additions & 1 deletion demo/basic/generated/swift/SharedTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ public struct OverriddenFullSize: Codable {
public var nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?
public var numUnion1: OverriddenFullSizeMembersNumUnion1Type
public var foo: OverriddenFullSizeMembersFooType
public var unionType: OverriddenFullSizeMembersUnionTypeType
public var width: Double
public var height: Double
public var scale: Double
/// Example documentation for member
private var member: NumEnum = .one

public init(size: Double, count: Int, stringEnum: StringEnum, numEnum: NumEnum, defEnum: DefaultEnum, stringUnion: OverriddenFullSizeMembersStringUnionType, numberStringUnion: OverriddenFullSizeMembersNumberStringUnionType, nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?, numUnion1: OverriddenFullSizeMembersNumUnion1Type, foo: OverriddenFullSizeMembersFooType, width: Double, height: Double, scale: Double) {
public init(size: Double, count: Int, stringEnum: StringEnum, numEnum: NumEnum, defEnum: DefaultEnum, stringUnion: OverriddenFullSizeMembersStringUnionType, numberStringUnion: OverriddenFullSizeMembersNumberStringUnionType, nullableStringUnion: OverriddenFullSizeMembersNullableStringUnionType?, numUnion1: OverriddenFullSizeMembersNumUnion1Type, foo: OverriddenFullSizeMembersFooType, unionType: OverriddenFullSizeMembersUnionTypeType, width: Double, height: Double, scale: Double) {
self.size = size
self.count = count
self.stringEnum = stringEnum
Expand All @@ -36,6 +37,7 @@ public struct OverriddenFullSize: Codable {
self.nullableStringUnion = nullableStringUnion
self.numUnion1 = numUnion1
self.foo = foo
self.unionType = unionType
self.width = width
self.height = height
self.scale = scale
Expand Down Expand Up @@ -87,3 +89,59 @@ public struct OverriddenFullSizeMembersFooType: Codable {
self.numberField = numberField
}
}

public enum OverriddenFullSizeMembersUnionTypeType: Codable {
case numEnum(_ value: NumEnum)
case defaultEnum(_ value: DefaultEnum)
case stringArray(_ value: [String])
case stringForStringDictionary(_ value: [String: String])
case bool(_ value: Bool)
case double(_ value: Double)
case string(_ value: String)

public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(NumEnum.self) {
self = .numEnum(value)
}
else if let value = try? container.decode(DefaultEnum.self) {
self = .defaultEnum(value)
}
else if let value = try? container.decode([String].self) {
self = .stringArray(value)
}
else if let value = try? container.decode([String: String].self) {
self = .stringForStringDictionary(value)
}
else if let value = try? container.decode(Bool.self) {
self = .bool(value)
}
else if let value = try? container.decode(Double.self) {
self = .double(value)
}
else {
let value = try container.decode(String.self)
self = .string(value)
}
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .numEnum(let value):
try container.encode(value)
case .defaultEnum(let value):
try container.encode(value)
case .stringArray(let value):
try container.encode(value)
case .stringForStringDictionary(let value):
try container.encode(value)
case .bool(let value):
try container.encode(value)
case .double(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
}
}
}
1 change: 1 addition & 0 deletions demo/basic/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ interface FullSize extends BaseSize, CustomSize {
nullableStringUnion: 'A1' | 'B1' | null;
numUnion1: 11 | 21;
foo: { stringField: string } | { numberField: number };
unionType: string | number | boolean | NumEnum | DefaultEnum | string[] | DictionaryWithAnyKey;
}

interface DictionaryWithAnyKey {
Expand Down
6 changes: 4 additions & 2 deletions documentation/interface-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,10 @@ interface NumberFieldInterface {
StringFieldInterface | { numberField: number }
StringFieldInterface | NumberFieldInterface

// not allowed: unsupported union
// allowed: types union
string | number

// not allowed: mixing type and tuple
{ stringField: string } | number
```

Expand Down Expand Up @@ -216,7 +218,7 @@ ts-gyb parses tags in [JSDoc](https://jsdoc.app) documentation.
- `@shouldExport`: Specify whether an `interface` should be exported. Set it to `true` to export.
- `@overrideModuleName`: Change the name of the interface for ts-gyb. This is helpful for dropping the `I` prefix in TypeScript interface name.
- `@overrideTypeName`: Similar to `@overrideModuleName`, this is used to override the name of custom types used in method parameters or return values.
- `@default`: default value for Module Interface's function parameter,
- `@default`: default value for Module Interface's function parameter,
```typescript
/**
Expand Down
25 changes: 25 additions & 0 deletions example-templates/kotlin-named-type.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,28 @@ enum class {{typeName}} {
}
{{/isStringType}}
{{/enum}}
{{#unionType}}
sealed class {{unionTypeName}}(val value: Any) {
{{#members}}
data class {{capitalizeName}}Value(val value: {{{type}}}) : {{unionTypeName}}()
{{/members}}
}

class {{unionTypeName}}Adapter : JsonSerializer<{{unionTypeName}}>, JsonDeserializer<{{unionTypeName}}> {
override fun serialize(src: {{unionTypeName}}, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return context.serialize(src.value)
}

override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): {{unionTypeName}} {
{{#members}}
try {
return {{unionTypeName}}.{{capitalizeName}}Value(context.deserialize(json, {{type}}::class.java))
} catch (e: Exception) {
// Ignore the exception and try the next type
}
{{/members}}

throw IllegalArgumentException("Unexpected JSON type: ${json.javaClass}")
}
}
{{/unionType}}
34 changes: 34 additions & 0 deletions example-templates/swift-named-type.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,37 @@ public enum {{typeName}}: {{valueType}}, Codable {
{{/members}}
}
{{/enum}}
{{#unionType}}
public enum {{unionTypeName}}: Codable {
{{#members}}
case {{uncapitalizeName}}(_ value: {{type}})
{{/members}}

public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
{{#members}}
{{^last}}
{{^first}}else {{/first}}if let value = try? container.decode({{type}}.self) {
self = .{{uncapitalizeName}}(value)
}
{{/last}}
{{#last}}
else {
let value = try container.decode({{type}}.self)
self = .{{uncapitalizeName}}(value)
}
{{/last}}
{{/members}}
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
{{#members}}
case .{{uncapitalizeName}}(let value):
try container.encode(value)
{{/members}}
}
}
}
{{/unionType}}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-gyb",
"version": "0.11.1",
"version": "0.12.0",
"description": "Generate Native API based on TS interface",
"repository": {
"type": "git",
Expand Down
9 changes: 6 additions & 3 deletions src/generator/CodeGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
} from './named-types';
import { Parser } from '../parser/Parser';
import { renderCode } from '../renderer/renderer';
import { NamedTypeView, ModuleView, InterfaceTypeView, EnumTypeView } from '../renderer/views';
import { NamedTypeView, ModuleView, InterfaceTypeView, EnumTypeView, UnionTypeView } from '../renderer/views';
import { serializeModule, serializeNamedType } from '../serializers';
import { isInterfaceType } from '../types';
import { isEnumType, isInterfaceType } from '../types';
import { applyDefaultCustomTags } from './utils';
import { ValueTransformer, SwiftValueTransformer, KotlinValueTransformer } from '../renderer/value-transformer';

Expand Down Expand Up @@ -128,9 +128,12 @@ export class CodeGenerator {
if (isInterfaceType(namedType.type)) {
namedTypeView = new InterfaceTypeView(namedType.type, namedType.source, valueTransformer);
namedTypeView.custom = true;
} else {
} else if (isEnumType(namedType.type)) {
namedTypeView = new EnumTypeView(namedType.type, namedType.source, valueTransformer);
namedTypeView.enum = true;
} else {
namedTypeView = new UnionTypeView(namedType.type, valueTransformer);
namedTypeView.unionType = true;
}

return namedTypeView;
Expand Down
Loading

0 comments on commit e94bff6

Please sign in to comment.