Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to Union Type #123

Merged
merged 8 commits into from
May 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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()
miku1958 marked this conversation as resolved.
Show resolved Hide resolved
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))
Copy link
Collaborator

Choose a reason for hiding this comment

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

There should be more types, but I think it's ok right now, we can add more when we need. Thanks.

} 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
Loading