diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 46d7b0f3..558f1563 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -111,6 +111,13 @@ jobs:
working-directory: client-test/rust
run: cargo test
+ - uses: actions/setup-go@v5
+ with:
+ go-version: 1.24
+ - name: Test Golang
+ working-directory: client-test/go
+ run: go get && go test
+
# clean-artifact:
# needs: [ jdk_compatibility_test, client_test ]
# runs-on: ubuntu-latest
diff --git a/.mvn/mvnd.properties b/.mvn/mvnd.properties
new file mode 100644
index 00000000..53f77170
--- /dev/null
+++ b/.mvn/mvnd.properties
@@ -0,0 +1,2 @@
+mvnd.noBuffering=true
+mvnd.threads=1
diff --git a/.run/mvnDebug.run.xml b/.run/mvnDebug.run.xml
new file mode 100644
index 00000000..416fe833
--- /dev/null
+++ b/.run/mvnDebug.run.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.run/mvnd.run.xml b/.run/mvnd.run.xml
deleted file mode 100644
index 1ad4ad13..00000000
--- a/.run/mvnd.run.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/README.md b/README.md
index 3c654ac9..998b0f0b 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ export interface User {
email: string;
}
```
-Go (Planed):
+Go:
```golang
type User struct {
Name string
diff --git a/annotation/src/main/java/online/sharedtype/SharedType.java b/annotation/src/main/java/online/sharedtype/SharedType.java
index 4bd8ba81..bcad5a28 100644
--- a/annotation/src/main/java/online/sharedtype/SharedType.java
+++ b/annotation/src/main/java/online/sharedtype/SharedType.java
@@ -59,8 +59,9 @@
*
* Cyclic Reference:
*
- *
Typescript: Cyclic referenced field will be optional.
- *
Rust: Cyclic references will be wrapped in {@code Option>}.
+ *
Typescript: optional.
+ *
Go: pointer.
+ *
Rust: wrapped in {@code Option>}.
*
*
*
@@ -68,6 +69,7 @@
* Enums are emitted as below:
*
*
Typescript: type union or enum. For simple enums, values are enum constants' names.
+ *
Go: const (no namespace, so potential name conflict across enums) or var struct (namespace suffixed with 'Enums').
*
Rust: plain enum for simple enums; impl a const fun {@code value()} for custom enum values.
*
* See {@link EnumValue} for how to mark an enum value.
@@ -95,6 +97,7 @@
* Iterables and arrays are treated as arrays and, by default, are mapped to:
*
*
Typescript: {@code T[]}
+ *
Go: {@code []T}
*
Rust: {@code Vec}
*
*
@@ -106,6 +109,7 @@
* E.g. a type {@code Optional>>} can be emitted as:
*
*
Typescript: {@code String[] | undefined}
+ *
Go: pointers
*
Rust: {@code Option>}
*
*
@@ -115,6 +119,7 @@
* Custom map types are supported, e.g. a class that extends HashMap. But the type itself is treated as a mapType, so its structure will not be emitted.
*
*
Typescript: e.g. {@code Record} where {@code T} can be a reified type. If the key is enum, it will be a {@code Partial>}
+ *
Go: e.g. {@code map[string]T}
*
Rust: e.g. {@code HashMap}, {@code HashMap}
*
*
@@ -250,6 +255,18 @@
*/
String typescriptTargetDatetimeTypeLiteral() default "";
+ /**
+ * Type literal to be emitted for date/time types. How a java type is considered a date/time type is defined by global properties.
+ * @return any literal, e.g. "string", "Date". When empty, fallback to global default.
+ */
+ String goTargetDatetimeTypeLiteral() default "";
+
+ /**
+ * Format of enum in Go.
+ * @return "const" or "struct". If empty, fallback to global default.
+ */
+ String goEnumFormat() default "";
+
/**
* Mark a method as an accessor regardless of its name.
* Getter prefixes are configured in global properties.
diff --git a/client-test/go/go.mod b/client-test/go/go.mod
new file mode 100644
index 00000000..040f7a69
--- /dev/null
+++ b/client-test/go/go.mod
@@ -0,0 +1,10 @@
+module sharedtype.online/go-client-test
+
+go 1.23.2
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/stretchr/testify v1.10.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/client-test/go/go.sum b/client-test/go/go.sum
new file mode 100644
index 00000000..fe99d711
--- /dev/null
+++ b/client-test/go/go.sum
@@ -0,0 +1,9 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/client-test/go/main.go b/client-test/go/main.go
new file mode 100644
index 00000000..70073ad5
--- /dev/null
+++ b/client-test/go/main.go
@@ -0,0 +1,125 @@
+package sharedtype
+
+var List1 = []EnumGalaxy{"Andromeda", "MilkyWay", "Triangulum"}
+var Record1 = map[EnumTShirt]int32{
+ S: 1,
+ M: 2,
+ L: 3,
+}
+var Size1 EnumSize = 1
+var TshirtSize1 EnumTShirt = S
+
+var dependencyClassC1 = &DependencyClassC{}
+
+var dependencyClassB1 = &DependencyClassB{
+ C: dependencyClassC1,
+}
+
+var dependencyClassA1 = &DependencyClassA{
+ SuperClassA: SuperClassA{
+ A: 0,
+ NotIgnoredImplementedMethod: 0,
+ Value: 0,
+ },
+ B: dependencyClassB1,
+}
+
+var Obj = JavaRecord[string]{
+ BoxedBoolean: false,
+ BoxedByte: 0,
+ BoxedChar: 'a',
+ BoxedDouble: 0,
+ BoxedFloat: 0,
+ BoxedInt: 0,
+ BoxedIntArray: []int32{},
+ BoxedLong: 0,
+ BoxedShort: 0,
+ ContainerStringList: []Container[string]{},
+ ContainerStringListCollection: [][]Container[string]{},
+ CyclicDependency: dependencyClassA1,
+ DuplicateAccessor: "",
+ EnumGalaxy: "MilkyWay",
+ EnumSize: 3,
+ GenericList: []string{},
+ GenericListSet: [][]string{},
+ GenericSet: []string{},
+ IntArray: []int32{},
+ Object: nil,
+ PrimitiveBoolean: false,
+ PrimitiveByte: 0,
+ PrimitiveChar: 'b',
+ PrimitiveDouble: 0,
+ PrimitiveFloat: 0,
+ PrimitiveInt: 0,
+ PrimitiveLong: 0,
+ PrimitiveShort: 0,
+ String: "",
+ Value: "",
+}
+
+var AnotherJavaClass1 = &AnotherJavaClass{
+ Value: 333,
+}
+
+var RecursiveClass1 = &RecursiveClass{
+ DirectRef: &RecursiveClass{
+ DirectRef: nil,
+ ArrayRef: []RecursiveClass{},
+ },
+ ArrayRef: []RecursiveClass{},
+}
+
+var MapClass1 = MapClass{
+ MapField: map[int32]string{},
+ EnumKeyMapField: map[EnumSize]string{
+ 1: "1",
+ },
+ CustomMapField: map[int32]string{
+ 55: "abc",
+ },
+ NestedMapField: map[string]map[string]int32{
+ "M1": {
+ "V": 1,
+ },
+ },
+}
+
+var OptionalMethods1 = OptionalMethod{
+ ValueOptional: nil,
+ NestedValueOptional: nil,
+ SetNestedValueOptional: nil,
+ MapNestedValueOptional: nil,
+}
+
+var ArrayClass1 = ArrayClass{
+ Arr: []string{"abc"},
+}
+
+var JavaTime1 = JavaTimeClass{
+ UtilDate: "2022-01-01T00:00:00.000+08:00",
+ SqlDate: "2022-01-01T00:00:00.000+08:00",
+ LocalDate: "2022-01-01T00:00:00.000+08:00",
+ LocalTime: "2022-01-01T00:00:00.000+08:00",
+ LocalDateTime: "2022-01-01T00:00:00.000+08:00",
+ ZonedDateTime: "2022-01-01T00:00:00.000+08:00",
+ OffsetDateTime: "2022-01-01T00:00:00.000+08:00",
+ OffsetTime: "2022-01-01T00:00:00.000+08:00",
+ Instant: "2022-01-01T00:00:00.000+08:00",
+}
+
+var JodaTime1 = JodaTimeClass{
+ JodaDateTime: "2022-01-01T00:00:00.000+08:00",
+ JodaLocalDate: "2022-01-01T00:00:00.000+08:00",
+ JodaMonthDay: "2022-01-01T00:00:00.000+08:00",
+ JodaLocalTime: "2022-01-01T00:00:00.000+08:00",
+ JodaLocalDateTime: "2022-01-01T00:00:00.000+08:00",
+ JodaOffsetDateTime: "2022-01-01T00:00:00.000+08:00",
+}
+
+var MathClass1 = MathClass{
+ BigDecimal: "1.1",
+ BigInteger: "8888888555555",
+}
+
+var EnumConstValue1 EnumConstReference = 156
+var EnumEnumValue1 EnumEnumReference = 3
diff --git a/client-test/go/main_test.go b/client-test/go/main_test.go
new file mode 100644
index 00000000..ecd4eb81
--- /dev/null
+++ b/client-test/go/main_test.go
@@ -0,0 +1,26 @@
+package sharedtype
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMain(t *testing.T) {
+ dependencyClassC1.A = dependencyClassA1
+
+ RecursiveClass1.DirectRef = &RecursiveClass{
+ DirectRef: nil,
+ ArrayRef: []RecursiveClass{},
+ }
+
+ OptionalMethods1.MapNestedValueOptional = &map[int32]string{1: "foo"}
+ OptionalMethods1.SetNestedValueOptional = &[]string{"foo", "bar"}
+ OptionalMethods1.NestedValueOptional = &List1[0]
+}
+
+func TestConstants(t *testing.T) {
+ assert.Equal(t, float32(1.888), FLOAT_VALUE)
+ assert.Equal(t, "MilkyWay", REFERENCED_ENUM_VALUE)
+ assert.Equal(t, int32(1), MyEnumConstants.INT_VALUE)
+}
diff --git a/client-test/go/setenv b/client-test/go/setenv
new file mode 100644
index 00000000..16e9753b
--- /dev/null
+++ b/client-test/go/setenv
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+export PATH=$GO_HOME/bin:$PATH
+go version
+
diff --git a/client-test/go/types.go b/client-test/go/types.go
new file mode 120000
index 00000000..47ba41b2
--- /dev/null
+++ b/client-test/go/types.go
@@ -0,0 +1 @@
+../../it/java17/target/generated-sources/types.go
\ No newline at end of file
diff --git a/client-test/rust/src/lib.rs b/client-test/rust/src/lib.rs
index 08cfc113..4206b128 100644
--- a/client-test/rust/src/lib.rs
+++ b/client-test/rust/src/lib.rs
@@ -141,7 +141,7 @@ mod tests {
assert_eq!(EnumConstReference::ReferenceConstantInOther.value(), 999);
assert_eq!(EnumConstReference::ReferenceConstantLocally.value(), 156);
assert_eq!(EnumEnumReference::ReferenceAnother.value(), 3);
- assert_eq!(EnumSimpleEnumReference::ReferenceAnother.value(), EnumGalaxy::Andromeda);
+ assert_eq!(EnumSimpleEnumReference::ReferenceAnother1.value(), EnumGalaxy::Andromeda);
assert_eq!(EnumEnumEnumReference::ReferenceAnother2.value(), EnumGalaxy::Andromeda);
}
}
diff --git a/doc/Development.md b/doc/Development.md
index 1a04e338..4131df08 100644
--- a/doc/Development.md
+++ b/doc/Development.md
@@ -27,6 +27,7 @@ Optionally mount tmpfs to save your disk by:
```bash
./mount-tmpfs.sh
```
+Optionally setup `MVND_HOME` to use [maven daemon](https://github.com/apache/maven-mvnd) by `./mvnd`
### Maven profiles
* `dev` and `release` - control whether to include test maven modules during build.
@@ -63,9 +64,9 @@ Style check:
```
Debug annotation processor by run maven build:
```bash
-./mvnd
+./mvne
```
-Then attach your debugger on it. E.g. [IDEA run config](../.run/mvnd.run.xml).
+Then attach your debugger on it. E.g. [IDEA run config](../.run/mvnDebug.run.xml).
Compile specific classes, **along with debug, this is useful for developing a specific feature**, e.g.:
```bash
diff --git a/doc/Usage.md b/doc/Usage.md
index 50a951f7..e0910cb1 100644
--- a/doc/Usage.md
+++ b/doc/Usage.md
@@ -103,3 +103,5 @@ You have to execute the annotation processing on the same classpath with source
For multiple module builds, a workaround is to execute on every module.
* Non-static inner classes are not supported. Instance class may refer to its enclosing class's generic type without the type declaration on its own,
which could break the generated code. Later version of SharedType may loosen this limitation.
+* Although SharedType does not depend on any serialization format, it assumes the contract of JSON.
+That means a client can use any format of serializations, but may not exceed JSON capacity.
diff --git a/it/java17/src/test/java/online/sharedtype/it/EnumIntegrationTest.java b/it/java17/src/test/java/online/sharedtype/it/EnumIntegrationTest.java
index e1bcae19..48f95415 100644
--- a/it/java17/src/test/java/online/sharedtype/it/EnumIntegrationTest.java
+++ b/it/java17/src/test/java/online/sharedtype/it/EnumIntegrationTest.java
@@ -112,7 +112,7 @@ void parseEnumSimpleEnumReference() {
EnumValueInfo constant1 = enumDef.components().get(0);
assertThat(constant1.value().getValue()).isEqualTo("Andromeda");
- assertThat(constant1.value().getEnumConstantName()).isEqualTo("ReferenceAnother");
+ assertThat(constant1.value().getEnumConstantName()).isEqualTo("ReferenceAnother1");
var constant1TypeInfo = (ConcreteTypeInfo)constant1.value().getValueType();
assertThat(constant1TypeInfo.qualifiedName()).isEqualTo("online.sharedtype.it.java8.EnumGalaxy");
}
diff --git a/it/java8/src/main/java/online/sharedtype/it/java8/EnumGalaxy.java b/it/java8/src/main/java/online/sharedtype/it/java8/EnumGalaxy.java
index ffb85cb8..9990c11a 100644
--- a/it/java8/src/main/java/online/sharedtype/it/java8/EnumGalaxy.java
+++ b/it/java8/src/main/java/online/sharedtype/it/java8/EnumGalaxy.java
@@ -33,7 +33,7 @@ enum EnumEnumReference {
@RequiredArgsConstructor
@SharedType
enum EnumSimpleEnumReference {
- ReferenceAnother(EnumGalaxy.Andromeda),
+ ReferenceAnother1(EnumGalaxy.Andromeda),
;
@SharedType.EnumValue
private final EnumGalaxy enumValue;
@@ -42,7 +42,7 @@ enum EnumSimpleEnumReference {
@RequiredArgsConstructor
@SharedType
enum EnumEnumEnumReference {
- ReferenceAnother2(EnumSimpleEnumReference.ReferenceAnother),
+ ReferenceAnother2(EnumSimpleEnumReference.ReferenceAnother1),
;
@SharedType.EnumValue
private final EnumSimpleEnumReference enumValue;
diff --git a/it/java8/src/main/java/online/sharedtype/it/java8/EnumSize.java b/it/java8/src/main/java/online/sharedtype/it/java8/EnumSize.java
index bdf2261f..eb6fadf7 100644
--- a/it/java8/src/main/java/online/sharedtype/it/java8/EnumSize.java
+++ b/it/java8/src/main/java/online/sharedtype/it/java8/EnumSize.java
@@ -5,7 +5,8 @@
@SharedType(
rustMacroTraits = {"PartialEq", "Eq", "Hash", "serde::Serialize", "serde::Deserialize"},
- typescriptEnumFormat = "const_enum"
+ typescriptEnumFormat = "const_enum",
+ goEnumFormat = "struct"
)
@RequiredArgsConstructor
public enum EnumSize {
diff --git a/it/sharedtype.properties b/it/sharedtype.properties
index 807a3a78..c93c1bf0 100644
--- a/it/sharedtype.properties
+++ b/it/sharedtype.properties
@@ -1,4 +1,4 @@
-sharedtype.targets=CONSOLE, JAVA_SERIALIZED, TYPESCRIPT, RUST
+sharedtype.targets=CONSOLE, JAVA_SERIALIZED, TYPESCRIPT, RUST, GO
sharedtype.ignore-annotations=online.sharedtype.it.java8.anno.ToIgnore
sharedtype.accessor-annotations=online.sharedtype.it.java8.anno.AsAccessor
sharedtype.enum-value-annotations=online.sharedtype.it.java8.anno.AsEnumValue
@@ -8,3 +8,5 @@ sharedtype.typescript.type-mappings=online.sharedtype.it.java8.types.MyType1:Arr
online.sharedtype.it.java8.types.MyType2:number
sharedtype.rust.type-mappings=online.sharedtype.it.java8.types.MyType1:Vec
+
+sharedtype.go.type-mappings=online.sharedtype.it.java8.types.MyType1:[]
diff --git a/mvnd b/mvnd
deleted file mode 100755
index af00aa6a..00000000
--- a/mvnd
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-
-export MAVEN_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"
-./mvnw "$@"
diff --git a/mvne b/mvne
new file mode 100755
index 00000000..2fccba55
--- /dev/null
+++ b/mvne
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+export MAVEN_OPTS="-Xdebug -Xnoagent -Xint -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 $MAVEN_OPTS"
+./mvnw "$@"
diff --git a/processor/src/main/java/online/sharedtype/processor/context/Config.java b/processor/src/main/java/online/sharedtype/processor/context/Config.java
index 876ffea7..c207e27b 100644
--- a/processor/src/main/java/online/sharedtype/processor/context/Config.java
+++ b/processor/src/main/java/online/sharedtype/processor/context/Config.java
@@ -31,8 +31,10 @@ public final class Config {
private final Set typescriptOptionalFieldFormats;
private final Props.Typescript.EnumFormat typescriptEnumFormat;
private final Props.Typescript.FieldReadonlyType typescriptFieldReadonly;
- private final String rustTargetDatetimeTypeLiteral;
+ private final Props.Go.EnumFormat goEnumFormat;
private final String typescriptTargetDatetimeTypeLiteral;
+ private final String goTargetDatetimeTypeLiteral;
+ private final String rustTargetDatetimeTypeLiteral;
@Retention(RetentionPolicy.RUNTIME)
@interface AnnoContainer {
@@ -56,16 +58,22 @@ public Config(TypeElement typeElement, Context ctx) {
typescriptOptionalFieldFormats = parseTsOptionalFieldFormats(anno, ctx);
typescriptEnumFormat = parseTsEnumFormat(anno, ctx);
typescriptFieldReadonly = parseTsFieldReadonlyType(anno, ctx);
- rustTargetDatetimeTypeLiteral = notEmptyOrDefault(
- anno.rustTargetDatetimeTypeLiteral(),
- ctx.getProps().getRust().getTargetDatetimeTypeLiteral(),
- () -> String.format("Loading rustTargetDatetimeTypeLiteral failed. Please check your configuration for '%s'", qualifiedName)
- );
+ goEnumFormat = parseGoEnumFormat(anno, ctx);
typescriptTargetDatetimeTypeLiteral = notEmptyOrDefault(
anno.typescriptTargetDatetimeTypeLiteral(),
ctx.getProps().getTypescript().getTargetDatetimeTypeLiteral(),
() -> String.format("Loading typescriptTargetDatetimeTypeLiteral failed. Please check your configuration for '%s'", qualifiedName)
);
+ goTargetDatetimeTypeLiteral = notEmptyOrDefault(
+ anno.goTargetDatetimeTypeLiteral(),
+ ctx.getProps().getGo().getTargetDatetimeTypeLiteral(),
+ () -> String.format("Loading goTargetDatetimeTypeLiteral failed. Please check your configuration for '%s'", qualifiedName)
+ );
+ rustTargetDatetimeTypeLiteral = notEmptyOrDefault(
+ anno.rustTargetDatetimeTypeLiteral(),
+ ctx.getProps().getRust().getTargetDatetimeTypeLiteral(),
+ () -> String.format("Loading rustTargetDatetimeTypeLiteral failed. Please check your configuration for '%s'", qualifiedName)
+ );
}
public boolean includes(SharedType.ComponentType componentType) {
@@ -122,4 +130,17 @@ private Props.Typescript.FieldReadonlyType parseTsFieldReadonlyType(SharedType a
}
return ctx.getProps().getTypescript().getFieldReadonlyType();
}
+
+ private Props.Go.EnumFormat parseGoEnumFormat(SharedType anno, Context ctx) {
+ if (anno.goEnumFormat() != null && !anno.goEnumFormat().isEmpty()) {
+ try {
+ return Props.Go.EnumFormat.fromString(anno.goEnumFormat());
+ } catch (IllegalArgumentException e) {
+ throw new SharedTypeException(String.format(
+ "Invalid value for SharedType.goEnumFormat: '%s', only 'const' or 'struct' is allowed. " +
+ "When parsing annotation for '%s'.", anno.goEnumFormat(), qualifiedName), e);
+ }
+ }
+ return ctx.getProps().getGo().getEnumFormat();
+ }
}
diff --git a/processor/src/main/java/online/sharedtype/processor/context/Props.java b/processor/src/main/java/online/sharedtype/processor/context/Props.java
index 08c73a20..1153d435 100644
--- a/processor/src/main/java/online/sharedtype/processor/context/Props.java
+++ b/processor/src/main/java/online/sharedtype/processor/context/Props.java
@@ -18,6 +18,7 @@
public final class Props {
private final Set targets;
private final Typescript typescript;
+ private final Go go;
private final Rust rust;
private final Set optionalAnnotations;
@@ -83,6 +84,26 @@ public static FieldReadonlyType fromString(String value) {
}
}
+ @Builder(access = AccessLevel.PACKAGE)
+ @Getter
+ public static final class Go {
+ private final String outputFileName;
+ private final String outputFilePackageName;
+ private final String javaObjectMapType;
+ private final String targetDatetimeTypeLiteral;
+ private final EnumFormat enumFormat;
+ private final Map typeMappings;
+ private final String customCodePath;
+
+ public enum EnumFormat {
+ CONST, STRUCT,
+ ;
+ public static EnumFormat fromString(String value) {
+ return EnumFormat.valueOf(value.toUpperCase());
+ }
+ }
+ }
+
@Builder(access = AccessLevel.PACKAGE)
@Getter
public static final class Rust {
diff --git a/processor/src/main/java/online/sharedtype/processor/context/PropsFactory.java b/processor/src/main/java/online/sharedtype/processor/context/PropsFactory.java
index 33f22c13..c26c9f10 100644
--- a/processor/src/main/java/online/sharedtype/processor/context/PropsFactory.java
+++ b/processor/src/main/java/online/sharedtype/processor/context/PropsFactory.java
@@ -69,6 +69,15 @@ private static Props loadProps(Properties properties) {
.typeMappings(parseMap(properties, "sharedtype.typescript.type-mappings"))
.customCodePath(properties.getProperty("sharedtype.typescript.custom-code-path"))
.build())
+ .go(Props.Go.builder()
+ .outputFileName(properties.getProperty("sharedtype.go.output-file-name"))
+ .outputFilePackageName(properties.getProperty("sharedtype.go.output-file-package-name"))
+ .javaObjectMapType(properties.getProperty("sharedtype.go.java-object-map-type"))
+ .targetDatetimeTypeLiteral(properties.getProperty("sharedtype.go.target-datetime-type"))
+ .enumFormat(parseEnum(properties, "sharedtype.go.enum-format", Props.Go.EnumFormat::fromString))
+ .typeMappings(parseMap(properties, "sharedtype.go.type-mappings"))
+ .customCodePath(properties.getProperty("sharedtype.go.custom-code-path"))
+ .build())
.rust(Props.Rust.builder()
.outputFileName(properties.getProperty("sharedtype.rust.output-file-name"))
.allowDeadcode(parseBoolean(properties, "sharedtype.rust.allow-deadcode"))
diff --git a/processor/src/main/java/online/sharedtype/processor/parser/type/MappableTypeInfoParser.java b/processor/src/main/java/online/sharedtype/processor/parser/type/MappableTypeInfoParser.java
index 58bb9886..953e3099 100644
--- a/processor/src/main/java/online/sharedtype/processor/parser/type/MappableTypeInfoParser.java
+++ b/processor/src/main/java/online/sharedtype/processor/parser/type/MappableTypeInfoParser.java
@@ -12,11 +12,13 @@
final class MappableTypeInfoParser implements TypeInfoParser {
private final TypeInfoParser delegate;
private final Map typescriptTypeMappings;
+ private final Map goTypeMappings;
private final Map rustTypeMappings;
MappableTypeInfoParser(Context ctx, TypeInfoParser delegate) {
this.delegate = delegate;
this.typescriptTypeMappings = ctx.getProps().getTypescript().getTypeMappings();
+ this.goTypeMappings = ctx.getProps().getGo().getTypeMappings();
this.rustTypeMappings = ctx.getProps().getRust().getTypeMappings();
}
@@ -27,6 +29,7 @@ public TypeInfo parse(TypeMirror typeMirror, TypeElement ctxTypeElement) {
if (typeInfo instanceof MappableType) {
MappableType mappableType = (MappableType) typeInfo;
updateTypeMappings(mappableType, TargetCodeType.TYPESCRIPT, typescriptTypeMappings);
+ updateTypeMappings(mappableType, TargetCodeType.GO, goTypeMappings);
updateTypeMappings(mappableType, TargetCodeType.RUST, rustTypeMappings);
}
return typeInfo;
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/TypeWriter.java b/processor/src/main/java/online/sharedtype/processor/writer/TypeWriter.java
index f3efa76f..245ec589 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/TypeWriter.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/TypeWriter.java
@@ -41,6 +41,11 @@ static TypeWriter create(Context ctx) {
ctx, renderer, RenderDataAdaptorFactory::typescript, TemplateDataConverter.typescript(ctx), ctx.getProps().getTypescript().getOutputFileName()
));
}
+ if (ctx.getProps().getTargets().contains(OutputTarget.GO)) {
+ writers.add(new TemplateTypeFileWriter(
+ ctx, renderer, RenderDataAdaptorFactory::go, TemplateDataConverter.go(ctx), ctx.getProps().getGo().getOutputFileName()
+ ));
+ }
if (ctx.getProps().getTargets().contains(OutputTarget.RUST)) {
writers.add(new TemplateTypeFileWriter(
ctx, renderer, RenderDataAdaptorFactory::rust, TemplateDataConverter.rust(ctx), ctx.getProps().getRust().getOutputFileName()
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/adaptor/AbstractDataAdaptor.java b/processor/src/main/java/online/sharedtype/processor/writer/adaptor/AbstractDataAdaptor.java
index 2e0cea47..ded1ec60 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/adaptor/AbstractDataAdaptor.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/adaptor/AbstractDataAdaptor.java
@@ -14,6 +14,7 @@
abstract class AbstractDataAdaptor implements RenderDataAdaptor {
final Context ctx;
+ @SuppressWarnings("unused")
final RenderFlags renderFlags() {
return ctx.getRenderFlags();
}
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/adaptor/GoHeaderDataAdaptor.java b/processor/src/main/java/online/sharedtype/processor/writer/adaptor/GoHeaderDataAdaptor.java
new file mode 100644
index 00000000..8bab9439
--- /dev/null
+++ b/processor/src/main/java/online/sharedtype/processor/writer/adaptor/GoHeaderDataAdaptor.java
@@ -0,0 +1,19 @@
+package online.sharedtype.processor.writer.adaptor;
+
+import online.sharedtype.processor.context.Context;
+
+final class GoHeaderDataAdaptor extends AbstractDataAdaptor {
+ public GoHeaderDataAdaptor(Context ctx) {
+ super(ctx);
+ }
+
+ @SuppressWarnings("unused")
+ String packageName() {
+ return ctx.getProps().getGo().getOutputFilePackageName();
+ }
+
+ @Override
+ String customCodeSnippet() {
+ return readCustomCodeSnippet(ctx.getProps().getGo().getCustomCodePath());
+ }
+}
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/adaptor/RenderDataAdaptorFactory.java b/processor/src/main/java/online/sharedtype/processor/writer/adaptor/RenderDataAdaptorFactory.java
index be85528a..40cc93c6 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/adaptor/RenderDataAdaptorFactory.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/adaptor/RenderDataAdaptorFactory.java
@@ -14,4 +14,8 @@ static Tuple typescript(Context ctx) {
static Tuple rust(Context ctx) {
return Tuple.of(Template.TEMPLATE_RUST_HEADER, new RustHeaderDataAdaptor(ctx));
}
+
+ static Tuple go(Context ctx) {
+ return Tuple.of(Template.TEMPLATE_GO_HEADER, new GoHeaderDataAdaptor(ctx));
+ }
}
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/AbstractEnumConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/AbstractEnumConverter.java
new file mode 100644
index 00000000..6603ec35
--- /dev/null
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/AbstractEnumConverter.java
@@ -0,0 +1,11 @@
+package online.sharedtype.processor.writer.converter;
+
+import online.sharedtype.processor.domain.def.EnumDef;
+import online.sharedtype.processor.domain.def.TypeDef;
+
+abstract class AbstractEnumConverter implements TemplateDataConverter {
+ @Override
+ public final boolean shouldAccept(TypeDef typeDef) {
+ return typeDef instanceof EnumDef && !((EnumDef) typeDef).components().isEmpty();
+ }
+}
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/AbstractStructConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/AbstractStructConverter.java
new file mode 100644
index 00000000..03ee59fc
--- /dev/null
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/AbstractStructConverter.java
@@ -0,0 +1,18 @@
+package online.sharedtype.processor.writer.converter;
+
+import online.sharedtype.processor.domain.def.ClassDef;
+import online.sharedtype.processor.domain.def.TypeDef;
+
+abstract class AbstractStructConverter implements TemplateDataConverter {
+ @Override
+ public boolean shouldAccept(TypeDef typeDef) {
+ if (!(typeDef instanceof ClassDef)) {
+ return false;
+ }
+ ClassDef classDef = (ClassDef) typeDef;
+ if (classDef.isMapType()) {
+ return false;
+ }
+ return !classDef.components().isEmpty() || classDef.isDepended();
+ }
+}
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/ConversionUtils.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/ConversionUtils.java
index 793d3d0c..0343c862 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/converter/ConversionUtils.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/ConversionUtils.java
@@ -14,6 +14,13 @@ static String toSnakeCase(String camelCase) {
return CAMEL_CASE_PATTERN.matcher(camelCase).replaceAll("$1_$2").toLowerCase();
}
+ static String capitalize(String str) {
+ if (str == null || str.isEmpty()) {
+ return str;
+ }
+ return str.substring(0, 1).toUpperCase() + str.substring(1);
+ }
+
static boolean isOptionalField(FieldComponentInfo field) {
if (field.optional()) {
return true;
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/GoEnumConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/GoEnumConverter.java
new file mode 100644
index 00000000..e5e065c2
--- /dev/null
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/GoEnumConverter.java
@@ -0,0 +1,75 @@
+package online.sharedtype.processor.writer.converter;
+
+import lombok.RequiredArgsConstructor;
+import online.sharedtype.processor.context.Config;
+import online.sharedtype.processor.context.Context;
+import online.sharedtype.processor.domain.component.EnumValueInfo;
+import online.sharedtype.processor.domain.def.EnumDef;
+import online.sharedtype.processor.domain.def.TypeDef;
+import online.sharedtype.processor.domain.type.TypeInfo;
+import online.sharedtype.processor.support.utils.Tuple;
+import online.sharedtype.processor.writer.converter.type.TypeExpressionConverter;
+import online.sharedtype.processor.writer.render.Template;
+
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static online.sharedtype.processor.context.Props.Go.EnumFormat.CONST;
+import static online.sharedtype.processor.writer.render.Template.TEMPLATE_GO_CONST_ENUM;
+import static online.sharedtype.processor.writer.render.Template.TEMPLATE_GO_STRUCT_ENUM;
+
+@RequiredArgsConstructor
+final class GoEnumConverter extends AbstractEnumConverter {
+ private final Context ctx;
+ private final TypeExpressionConverter typeExpressionConverter;
+ @Override
+ public Tuple convert(TypeDef typeDef) {
+ EnumDef enumDef = (EnumDef) typeDef;
+
+ String valueType = getValueTypeExpr(enumDef);
+
+ EnumExpr value = new EnumExpr(
+ enumDef.simpleName(),
+ enumDef.components().stream().map(comp -> buildEnumExpr(comp, enumDef.simpleName())).collect(Collectors.toList()),
+ valueType
+ );
+
+ Config config = ctx.getTypeStore().getConfig(typeDef);
+ return Tuple.of(config.getGoEnumFormat() == CONST ? TEMPLATE_GO_CONST_ENUM : TEMPLATE_GO_STRUCT_ENUM, value);
+ }
+
+ private static EnumerationExpr buildEnumExpr(EnumValueInfo component, String valueTypeExpr) {
+ return new EnumerationExpr(
+ component.name(),
+ valueTypeExpr,
+ component.value().literalValue()
+ );
+ }
+
+ private String getValueTypeExpr(EnumDef enumDef) {
+ EnumValueInfo component = enumDef.components().get(0);
+ TypeInfo enumTypeInfo = enumDef.typeInfoSet().iterator().next();
+ if (enumTypeInfo.equals(component.value().getValueType())) {
+ return "string";
+ }
+ return typeExpressionConverter.toTypeExpr(component.value().getValueType(), enumDef);
+ }
+
+ @SuppressWarnings("unused")
+ @RequiredArgsConstructor
+ static final class EnumExpr {
+ final String name;
+ final List enumerations;
+ final String valueType;
+ }
+
+ @SuppressWarnings("unused")
+ @RequiredArgsConstructor
+ static final class EnumerationExpr {
+ final String name;
+ final String enumName;
+ @Nullable
+ final String value;
+ }
+}
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/GoStructConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/GoStructConverter.java
new file mode 100644
index 00000000..6b679428
--- /dev/null
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/GoStructConverter.java
@@ -0,0 +1,81 @@
+package online.sharedtype.processor.writer.converter;
+
+import lombok.EqualsAndHashCode;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+import online.sharedtype.processor.domain.component.FieldComponentInfo;
+import online.sharedtype.processor.domain.def.ClassDef;
+import online.sharedtype.processor.domain.def.TypeDef;
+import online.sharedtype.processor.support.utils.Tuple;
+import online.sharedtype.processor.writer.converter.type.TypeExpressionConverter;
+import online.sharedtype.processor.writer.render.Template;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RequiredArgsConstructor
+final class GoStructConverter extends AbstractStructConverter {
+ private final TypeExpressionConverter typeExpressionConverter;
+
+ @Override
+ public Tuple convert(TypeDef typeDef) {
+ ClassDef classDef = (ClassDef) typeDef;
+ StructExpr value = new StructExpr(
+ classDef.simpleName(),
+ classDef.typeVariables().stream().map(typeInfo -> typeExpressionConverter.toTypeExpr(typeInfo, typeDef)).collect(Collectors.toList()),
+ classDef.directSupertypes().stream().map(typeInfo1 -> typeExpressionConverter.toTypeExpr(typeInfo1, typeDef)).collect(Collectors.toList()),
+ gatherProperties(classDef)
+ );
+ return Tuple.of(Template.TEMPLATE_GO_STRUCT, value);
+ }
+
+ private List gatherProperties(ClassDef classDef) {
+ List properties = new ArrayList<>();
+ for (FieldComponentInfo component : classDef.components()) {
+ properties.add(toPropertyExpr(component, classDef));
+ }
+ return properties;
+ }
+
+ private PropertyExpr toPropertyExpr(FieldComponentInfo field, TypeDef contextTypeDef) {
+ return new PropertyExpr(
+ ConversionUtils.capitalize(field.name()),
+ typeExpressionConverter.toTypeExpr(field.type(), contextTypeDef),
+ ConversionUtils.isOptionalField(field)
+ );
+ }
+
+ @SuppressWarnings("unused")
+ @RequiredArgsConstructor
+ static final class StructExpr {
+ final String name;
+ final List typeParameters;
+ final List supertypes;
+ final List properties;
+
+ String typeParametersExpr() {
+ if (typeParameters.isEmpty()) {
+ return null;
+ }
+ return String.format("[%s any]", String.join(", ", typeParameters));
+ }
+ }
+
+ @ToString
+ @SuppressWarnings("unused")
+ @EqualsAndHashCode(of = "name")
+ @RequiredArgsConstructor
+ static final class PropertyExpr {
+ final String name;
+ final String type;
+ final boolean optional;
+
+ String typeExpr() {
+ if (optional) {
+ return String.format("*%s", type);
+ }
+ return type;
+ }
+ }
+}
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/RustEnumConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/RustEnumConverter.java
index bdbcf17d..9c0ac4cf 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/converter/RustEnumConverter.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/RustEnumConverter.java
@@ -18,15 +18,10 @@
import java.util.Set;
@RequiredArgsConstructor
-final class RustEnumConverter implements TemplateDataConverter {
+final class RustEnumConverter extends AbstractEnumConverter {
private final TypeExpressionConverter typeExpressionConverter;
private final RustMacroTraitsGenerator rustMacroTraitsGenerator;
- @Override
- public boolean shouldAccept(TypeDef typeDef) {
- return typeDef instanceof EnumDef && !((EnumDef) typeDef).components().isEmpty();
- }
-
@Override
public Tuple convert(TypeDef typeDef) {
EnumDef enumDef = (EnumDef) typeDef;
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/RustStructConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/RustStructConverter.java
index cf85bbdb..91393a08 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/converter/RustStructConverter.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/RustStructConverter.java
@@ -22,20 +22,17 @@
import java.util.stream.Collectors;
@RequiredArgsConstructor
-final class RustStructConverter implements TemplateDataConverter {
+final class RustStructConverter extends AbstractStructConverter {
private final Context ctx;
private final TypeExpressionConverter typeExpressionConverter;
private final RustMacroTraitsGenerator rustMacroTraitsGenerator;
@Override
public boolean shouldAccept(TypeDef typeDef) {
- if (!(typeDef instanceof ClassDef)) {
+ if (!super.shouldAccept(typeDef)) {
return false;
}
ClassDef classDef = (ClassDef) typeDef;
- if (classDef.isMapType()) {
- return false;
- }
if (classDef.isAnnotated()) {
return !classDef.components().isEmpty();
}
@@ -55,7 +52,7 @@ public Tuple convert(TypeDef typeDef) {
}
private List gatherProperties(ClassDef classDef) {
- List properties = new ArrayList<>(); // TODO: init cap
+ List properties = new ArrayList<>();
Set propertyNames = new HashSet<>();
for (FieldComponentInfo component : classDef.components()) {
properties.add(toPropertyExpr(component, classDef));
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/TemplateDataConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/TemplateDataConverter.java
index 20228e90..dfc68866 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/converter/TemplateDataConverter.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/TemplateDataConverter.java
@@ -24,6 +24,15 @@ static Set typescript(Context ctx) {
return converters;
}
+ static Set go(Context ctx) {
+ Set converters = new HashSet<>(3);
+ TypeExpressionConverter typeExpressionConverter = TypeExpressionConverter.go(ctx);
+ converters.add(new GoStructConverter(typeExpressionConverter));
+ converters.add(new GoEnumConverter(ctx, typeExpressionConverter));
+ converters.add(new ConstantConverter(ctx, typeExpressionConverter, OutputTarget.GO));
+ return converters;
+ }
+
static Set rust(Context ctx) {
RustMacroTraitsGenerator rustMacroTraitsGenerator = new RustMacroTraitsGeneratorImpl(ctx);
TypeExpressionConverter rustTypeExpressionConverter = TypeExpressionConverter.rust(ctx);
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/TypescriptEnumConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/TypescriptEnumConverter.java
index d62c833a..3b1d5479 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/converter/TypescriptEnumConverter.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/TypescriptEnumConverter.java
@@ -15,12 +15,8 @@
import java.util.stream.Collectors;
@RequiredArgsConstructor
-final class TypescriptEnumConverter implements TemplateDataConverter {
+final class TypescriptEnumConverter extends AbstractEnumConverter {
private final Context ctx;
- @Override
- public boolean shouldAccept(TypeDef typeDef) {
- return typeDef instanceof EnumDef && !((EnumDef) typeDef).components().isEmpty();
- }
@Override
public Tuple convert(TypeDef typeDef) {
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/TypescriptInterfaceConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/TypescriptInterfaceConverter.java
index 8fd81b3b..9d270552 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/converter/TypescriptInterfaceConverter.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/TypescriptInterfaceConverter.java
@@ -19,7 +19,7 @@
import static online.sharedtype.processor.context.Props.Typescript.OptionalFieldFormat.QUESTION_MARK;
import static online.sharedtype.processor.context.Props.Typescript.OptionalFieldFormat.UNDEFINED;
-final class TypescriptInterfaceConverter implements TemplateDataConverter {
+final class TypescriptInterfaceConverter extends AbstractStructConverter {
private final Context ctx;
private final TypeExpressionConverter typeExpressionConverter;
private final char interfacePropertyDelimiter;
@@ -30,18 +30,6 @@ final class TypescriptInterfaceConverter implements TemplateDataConverter {
this.typeExpressionConverter = typeExpressionConverter;
}
- @Override
- public boolean shouldAccept(TypeDef typeDef) {
- if (typeDef instanceof ClassDef) {
- ClassDef classDef = (ClassDef) typeDef;
- if (classDef.isMapType()) {
- return false;
- }
- return !classDef.components().isEmpty() || classDef.isDepended();
- }
- return false;
- }
-
@Override
public Tuple convert(TypeDef typeDef) {
ClassDef classDef = (ClassDef) typeDef;
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/type/AbstractTypeExpressionConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/type/AbstractTypeExpressionConverter.java
index 28e6e3a3..17e7e63a 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/converter/type/AbstractTypeExpressionConverter.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/type/AbstractTypeExpressionConverter.java
@@ -22,6 +22,7 @@
@RequiredArgsConstructor
abstract class AbstractTypeExpressionConverter implements TypeExpressionConverter {
private static final int BUILDER_INIT_SIZE = 128; // TODO: better estimate
+ private static final TypeArgsSpec DEFAULT_TYPE_ARGS_SPEC = new TypeArgsSpec("<", ", ", ">");
final Context ctx;
@Override
@@ -38,6 +39,10 @@ void beforeVisitTypeInfo(TypeInfo typeInfo) {
abstract MapSpec mapSpec(ConcreteTypeInfo typeInfo);
+ TypeArgsSpec typeArgsSpec(ConcreteTypeInfo typeInfo) {
+ return DEFAULT_TYPE_ARGS_SPEC;
+ }
+
abstract String dateTimeTypeExpr(DateTimeInfo dateTimeInfo, Config config);
abstract String toTypeExpression(ConcreteTypeInfo typeInfo, String defaultExpr);
@@ -51,13 +56,14 @@ private void buildTypeExprRecursively(TypeInfo typeInfo, @SideEffect StringBuild
} else {
exprBuilder.append(toTypeExpression(concreteTypeInfo, concreteTypeInfo.simpleName()));
if (!concreteTypeInfo.typeArgs().isEmpty()) {
- exprBuilder.append("<");
+ TypeArgsSpec typeArgsSpec = typeArgsSpec(concreteTypeInfo);
+ exprBuilder.append(typeArgsSpec.prefix);
for (TypeInfo typeArg : concreteTypeInfo.typeArgs()) {
buildTypeExprRecursively(typeArg, exprBuilder, contextTypeDef, config);
- exprBuilder.append(", ");
+ exprBuilder.append(typeArgsSpec.delimiter);
}
- exprBuilder.setLength(exprBuilder.length() - 2);
- exprBuilder.append(">");
+ exprBuilder.setLength(exprBuilder.length() - typeArgsSpec.delimiter.length());
+ exprBuilder.append(typeArgsSpec.suffix);
}
}
} else if (typeInfo instanceof TypeVariableInfo) {
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/type/GoTypeExpressionConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/type/GoTypeExpressionConverter.java
new file mode 100644
index 00000000..d7936ffe
--- /dev/null
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/type/GoTypeExpressionConverter.java
@@ -0,0 +1,74 @@
+package online.sharedtype.processor.writer.converter.type;
+
+import online.sharedtype.processor.context.Config;
+import online.sharedtype.processor.context.Context;
+import online.sharedtype.processor.domain.Constants;
+import online.sharedtype.processor.domain.TargetCodeType;
+import online.sharedtype.processor.domain.type.ConcreteTypeInfo;
+import online.sharedtype.processor.domain.type.DateTimeInfo;
+import online.sharedtype.processor.domain.type.TypeInfo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+final class GoTypeExpressionConverter extends AbstractTypeExpressionConverter {
+ private static final String ARRAY_LITERAL = "[]";
+ private static final ArraySpec ARRAY_SPEC = new ArraySpec(ARRAY_LITERAL, "");
+ private static final MapSpec DEFAULT_MAP_SPEC = new MapSpec("map[", "]", "");
+ private static final TypeArgsSpec TYPE_ARGS_SPEC = new TypeArgsSpec("[", ", ", "]");
+ private static final TypeArgsSpec ARRAY_TYPE_ARGS_SPEC = new TypeArgsSpec("", "", "");
+ final Map typeNameMappings = new HashMap<>(32);
+
+ public GoTypeExpressionConverter(Context ctx) {
+ super(ctx);
+ typeNameMappings.put(Constants.BOOLEAN_TYPE_INFO, "bool");
+ typeNameMappings.put(Constants.BYTE_TYPE_INFO, "byte");
+ typeNameMappings.put(Constants.CHAR_TYPE_INFO, "rune");
+ typeNameMappings.put(Constants.DOUBLE_TYPE_INFO, "float64");
+ typeNameMappings.put(Constants.FLOAT_TYPE_INFO, "float32");
+ typeNameMappings.put(Constants.INT_TYPE_INFO, "int32");
+ typeNameMappings.put(Constants.LONG_TYPE_INFO, "int64");
+ typeNameMappings.put(Constants.SHORT_TYPE_INFO, "int16");
+
+ typeNameMappings.put(Constants.BOXED_BOOLEAN_TYPE_INFO, "bool");
+ typeNameMappings.put(Constants.BOXED_BYTE_TYPE_INFO, "byte");
+ typeNameMappings.put(Constants.BOXED_CHAR_TYPE_INFO, "rune");
+ typeNameMappings.put(Constants.BOXED_DOUBLE_TYPE_INFO, "float64");
+ typeNameMappings.put(Constants.BOXED_FLOAT_TYPE_INFO, "float32");
+ typeNameMappings.put(Constants.BOXED_INT_TYPE_INFO, "int32");
+ typeNameMappings.put(Constants.BOXED_LONG_TYPE_INFO, "int64");
+ typeNameMappings.put(Constants.BOXED_SHORT_TYPE_INFO, "int16");
+ typeNameMappings.put(Constants.BIG_DECIMAL_TYPE_INFO, "string");
+ typeNameMappings.put(Constants.BIG_INTEGER_TYPE_INFO, "string");
+
+ typeNameMappings.put(Constants.STRING_TYPE_INFO, "string");
+ typeNameMappings.put(Constants.OBJECT_TYPE_INFO, ctx.getProps().getGo().getJavaObjectMapType());
+ }
+ @Override
+ ArraySpec arraySpec() {
+ return ARRAY_SPEC;
+ }
+ @Override
+ MapSpec mapSpec(ConcreteTypeInfo typeInfo) {
+ return DEFAULT_MAP_SPEC;
+ }
+ @Override
+ TypeArgsSpec typeArgsSpec(ConcreteTypeInfo typeInfo) {
+ if (ARRAY_LITERAL.equals(typeInfo.mappedName(TargetCodeType.GO))) {
+ return ARRAY_TYPE_ARGS_SPEC;
+ }
+ return TYPE_ARGS_SPEC;
+ }
+ @Override
+ String dateTimeTypeExpr(DateTimeInfo dateTimeInfo, Config config) {
+ return dateTimeInfo.mappedNameOrDefault(TargetCodeType.GO, config.getGoTargetDatetimeTypeLiteral());
+ }
+ @Override
+ String toTypeExpression(ConcreteTypeInfo typeInfo, String defaultExpr) {
+ String expr = typeInfo.mappedName(TargetCodeType.GO);
+ if (expr == null) {
+ expr = typeNameMappings.getOrDefault(typeInfo, defaultExpr);
+ }
+ return expr;
+ }
+}
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/type/RustTypeNameMappings.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/type/RustTypeNameMappings.java
index d562a4e1..4b5cc55b 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/converter/type/RustTypeNameMappings.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/type/RustTypeNameMappings.java
@@ -9,7 +9,7 @@
@UtilityClass
final class RustTypeNameMappings {
- private static final Map typeNameMappings = new HashMap<>(20);
+ private static final Map typeNameMappings = new HashMap<>(32);
static {
typeNameMappings.put(Constants.BOOLEAN_TYPE_INFO, "bool");
typeNameMappings.put(Constants.BYTE_TYPE_INFO, "i8");
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/type/TypeExpressionConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/type/TypeExpressionConverter.java
index 4af64f2d..c0742f27 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/converter/type/TypeExpressionConverter.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/type/TypeExpressionConverter.java
@@ -12,6 +12,10 @@ static TypeExpressionConverter typescript(Context ctx) {
return new TypescriptTypeExpressionConverter(ctx);
}
+ static TypeExpressionConverter go(Context ctx) {
+ return new GoTypeExpressionConverter(ctx);
+ }
+
static TypeExpressionConverter rust(Context ctx) {
return new RustTypeExpressionConverter(ctx);
}
@@ -32,4 +36,11 @@ final class MapSpec {
final String delimiter;
final String suffix;
}
+
+ @RequiredArgsConstructor
+ final class TypeArgsSpec {
+ final String prefix;
+ final String delimiter;
+ final String suffix;
+ }
}
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/converter/type/TypescriptTypeExpressionConverter.java b/processor/src/main/java/online/sharedtype/processor/writer/converter/type/TypescriptTypeExpressionConverter.java
index 1aa54f97..d1802f83 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/converter/type/TypescriptTypeExpressionConverter.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/converter/type/TypescriptTypeExpressionConverter.java
@@ -16,7 +16,7 @@ final class TypescriptTypeExpressionConverter extends AbstractTypeExpressionConv
private static final ArraySpec ARRAY_SPEC = new ArraySpec("", "[]");
private static final MapSpec DEFAULT_MAP_SPEC = new MapSpec("Record<", ", ", ">");
private static final MapSpec ENUM_KEY_MAP_SPEC = new MapSpec("Partial>");
- final Map typeNameMappings = new HashMap<>(20);
+ final Map typeNameMappings = new HashMap<>(32);
TypescriptTypeExpressionConverter(Context ctx) {
super(ctx);
diff --git a/processor/src/main/java/online/sharedtype/processor/writer/render/Template.java b/processor/src/main/java/online/sharedtype/processor/writer/render/Template.java
index 539190e5..7a8fe23e 100644
--- a/processor/src/main/java/online/sharedtype/processor/writer/render/Template.java
+++ b/processor/src/main/java/online/sharedtype/processor/writer/render/Template.java
@@ -19,6 +19,10 @@ public final class Template {
public static final Template TEMPLATE_RUST_HEADER = new Template(OutputTarget.RUST, "header");
public static final Template TEMPLATE_RUST_STRUCT = new Template(OutputTarget.RUST, "struct");
public static final Template TEMPLATE_RUST_ENUM = new Template(OutputTarget.RUST, "enum");
+ public static final Template TEMPLATE_GO_HEADER = new Template(OutputTarget.GO, "header");
+ public static final Template TEMPLATE_GO_STRUCT = new Template(OutputTarget.GO, "struct");
+ public static final Template TEMPLATE_GO_CONST_ENUM = new Template(OutputTarget.GO, "const-enum");
+ public static final Template TEMPLATE_GO_STRUCT_ENUM = new Template(OutputTarget.GO, "struct-enum");
private final OutputTarget outputTarget;
private final String resourcePath;
diff --git a/processor/src/main/resources/sharedtype-default.properties b/processor/src/main/resources/sharedtype-default.properties
index 5a5945fc..0b1dc01b 100644
--- a/processor/src/main/resources/sharedtype-default.properties
+++ b/processor/src/main/resources/sharedtype-default.properties
@@ -56,7 +56,7 @@ sharedtype.constant-namespaced=true
## output file name
sharedtype.typescript.output-file-name=types.ts
-## the type literal of the java object map type, e.g. any, unknown
+## the type literal of the java object map type, e.g. "any", "unknown"
sharedtype.typescript.java-object-map-type=any
## the target type for date/time types, can be any type literal, but SharedType will not verify its validity in target code
@@ -87,6 +87,35 @@ sharedtype.typescript.type-mappings=
## Below default assumes the file is on the cmd execution path
sharedtype.typescript.custom-code-path=sharedtype-custom-code.ts
+##############################
+# Golang specific properties #
+##############################
+
+## output file name
+sharedtype.go.output-file-name=types.go
+
+## output file package name
+sharedtype.go.output-file-package-name=sharedtype
+
+## the type literal of the java object map type, e.g. "any", "interface{}"
+sharedtype.go.java-object-map-type=any
+
+## the target type for date/time types, can be any type literal, but SharedType will not verify its validity in target code
+sharedtype.go.target-datetime-type=string
+
+## enum format, can be "const" (const enum), "struct" (var struct).
+sharedtype.go.enum-format=const
+
+## arbitrary type mappings, comma separated, property can be written in multi-lines with backslash \ examples:
+## sharedtype.go.type-mappings=mypackage.MyType1:Type1,\
+## mypackage.MyType2:Type2
+sharedtype.go.type-mappings=
+
+## Path to custom code snippet file, code snippet in the file will be injected into the top of generated file
+## If the file does not exist, it will be ignored
+## Below default assumes the file is on the cmd execution path
+sharedtype.go.custom-code-path=sharedtype-custom-code.go
+
############################
# Rust specific properties #
############################
diff --git a/processor/src/main/resources/templates/go/const-enum.mustache b/processor/src/main/resources/templates/go/const-enum.mustache
new file mode 100644
index 00000000..2a5d12de
--- /dev/null
+++ b/processor/src/main/resources/templates/go/const-enum.mustache
@@ -0,0 +1,6 @@
+type {{name}} = {{valueType}}
+const (
+{{#enumerations}}
+ {{name}} {{enumName}} = {{{value}}}
+{{/enumerations}}
+)
diff --git a/processor/src/main/resources/templates/go/constant-inline.mustache b/processor/src/main/resources/templates/go/constant-inline.mustache
new file mode 100644
index 00000000..d62a4309
--- /dev/null
+++ b/processor/src/main/resources/templates/go/constant-inline.mustache
@@ -0,0 +1,5 @@
+const (
+{{#constants}}
+ {{name}} {{{type}}} = {{{value}}};
+{{/constants}}
+)
diff --git a/processor/src/main/resources/templates/go/constant.mustache b/processor/src/main/resources/templates/go/constant.mustache
new file mode 100644
index 00000000..77b0d9a0
--- /dev/null
+++ b/processor/src/main/resources/templates/go/constant.mustache
@@ -0,0 +1,9 @@
+var {{name}} = struct {
+{{#constants}}
+ {{name}} {{type}}
+{{/constants}}
+}{
+{{#constants}}
+ {{{value}}},
+{{/constants}}
+}
diff --git a/processor/src/main/resources/templates/go/header.mustache b/processor/src/main/resources/templates/go/header.mustache
new file mode 100644
index 00000000..67f1dc0c
--- /dev/null
+++ b/processor/src/main/resources/templates/go/header.mustache
@@ -0,0 +1,5 @@
+// Code generated by https://github.com/SharedType/sharedtype
+package {{packageName}}
+
+{{{customCodeSnippet}}}
+
diff --git a/processor/src/main/resources/templates/go/struct-enum.mustache b/processor/src/main/resources/templates/go/struct-enum.mustache
new file mode 100644
index 00000000..add122f7
--- /dev/null
+++ b/processor/src/main/resources/templates/go/struct-enum.mustache
@@ -0,0 +1,10 @@
+type {{name}} = {{valueType}}
+var {{name}}Enums = struct {
+{{#enumerations}}
+ {{name}} {{enumName}}
+{{/enumerations}}
+}{
+{{#enumerations}}
+ {{{value}}},
+{{/enumerations}}
+}
diff --git a/processor/src/main/resources/templates/go/struct.mustache b/processor/src/main/resources/templates/go/struct.mustache
new file mode 100644
index 00000000..64d5f39b
--- /dev/null
+++ b/processor/src/main/resources/templates/go/struct.mustache
@@ -0,0 +1,8 @@
+type {{name}}{{typeParametersExpr}} struct {
+{{#supertypes}}
+ {{.}}
+{{/supertypes}}
+{{#properties}}
+ {{name}} {{{typeExpr}}}
+{{/properties}}
+}
diff --git a/processor/src/test/java/online/sharedtype/processor/context/ConfigTest.java b/processor/src/test/java/online/sharedtype/processor/context/ConfigTest.java
index 076cc804..b55847ec 100644
--- a/processor/src/test/java/online/sharedtype/processor/context/ConfigTest.java
+++ b/processor/src/test/java/online/sharedtype/processor/context/ConfigTest.java
@@ -75,6 +75,25 @@ void invalidTsEnumFormat() {
.hasMessageContaining("Invalid value for SharedType.typescriptEnumFormat: 'abc', only one of 'union', 'const_enum', 'enum' is allowed.");
}
+ @Test
+ void parseGoEnumFormat() {
+ var typeElement = ctxMocks.typeElement("com.github.cuzfrog.Abc")
+ .withAnnotation(SharedType.class, m -> when(m.goEnumFormat()).thenReturn("const"))
+ .element();
+ Config config = new Config(typeElement, ctxMocks.getContext());
+ assertThat(config.getGoEnumFormat()).isEqualTo(Props.Go.EnumFormat.CONST);
+ }
+
+ @Test
+ void invalidGoEnumFormat() {
+ var typeElement = ctxMocks.typeElement("com.github.cuzfrog.Abc")
+ .withAnnotation(SharedType.class, m -> when(m.goEnumFormat()).thenReturn("abc"))
+ .element();
+ assertThatThrownBy(() -> new Config(typeElement, ctxMocks.getContext()))
+ .isInstanceOf(SharedTypeException.class)
+ .hasMessageContaining("Invalid value for SharedType.goEnumFormat: 'abc', only 'const' or 'struct' is allowed.");
+ }
+
@Test
void overrideTsFieldReadonly() {
var typeElement = ctxMocks.typeElement("com.github.cuzfrog.Abc")
diff --git a/processor/src/test/java/online/sharedtype/processor/context/PropsFactoryTest.java b/processor/src/test/java/online/sharedtype/processor/context/PropsFactoryTest.java
index 4a05f22a..b5ed4eb3 100644
--- a/processor/src/test/java/online/sharedtype/processor/context/PropsFactoryTest.java
+++ b/processor/src/test/java/online/sharedtype/processor/context/PropsFactoryTest.java
@@ -57,6 +57,15 @@ void loadDefaultProps() {
assertThat(typescriptProps.getTypeMappings()).isEmpty();
assertThat(typescriptProps.getCustomCodePath()).isEqualTo("sharedtype-custom-code.ts");
+ Props.Go goProps = props.getGo();
+ assertThat(goProps.getOutputFileName()).isEqualTo("types.go");
+ assertThat(goProps.getOutputFilePackageName()).isEqualTo("sharedtype");
+ assertThat(goProps.getJavaObjectMapType()).isEqualTo("any");
+ assertThat(goProps.getTargetDatetimeTypeLiteral()).isEqualTo("string");
+ assertThat(goProps.getEnumFormat()).isEqualTo(Props.Go.EnumFormat.CONST);
+ assertThat(goProps.getTypeMappings()).isEmpty();
+ assertThat(goProps.getCustomCodePath()).isEqualTo("sharedtype-custom-code.go");
+
Props.Rust rustProps = props.getRust();
assertThat(rustProps.getOutputFileName()).isEqualTo("types.rs");
assertThat(rustProps.isAllowDeadcode()).isEqualTo(true);
diff --git a/processor/src/test/java/online/sharedtype/processor/parser/type/MappableTypeInfoParserTest.java b/processor/src/test/java/online/sharedtype/processor/parser/type/MappableTypeInfoParserTest.java
index dc3fe0f2..ad87d868 100644
--- a/processor/src/test/java/online/sharedtype/processor/parser/type/MappableTypeInfoParserTest.java
+++ b/processor/src/test/java/online/sharedtype/processor/parser/type/MappableTypeInfoParserTest.java
@@ -13,6 +13,7 @@
import static org.mockito.Mockito.when;
@SetSystemProperty(key = "sharedtype.typescript.type-mappings", value = "a.b.MyDateTime:MyString")
+@SetSystemProperty(key = "sharedtype.go.type-mappings", value = "a.b.MyDateTime:MyStringG")
@SetSystemProperty(key = "sharedtype.rust.type-mappings", value = "a.b.MyDateTime:MyStringR")
final class MappableTypeInfoParserTest {
private final ContextMocks ctxMocks = new ContextMocks();
@@ -29,6 +30,7 @@ void addTypeMappings() {
var resTypeInfo = parser.parse(typeElement1.asType(), typeElement1);
assertThat(resTypeInfo).isSameAs(dateTimeInfo);
assertThat(dateTimeInfo.mappedNameOrDefault(TargetCodeType.TYPESCRIPT, "DefaultName")).isEqualTo("MyString");
+ assertThat(dateTimeInfo.mappedNameOrDefault(TargetCodeType.GO, "DefaultName")).isEqualTo("MyStringG");
assertThat(dateTimeInfo.mappedNameOrDefault(TargetCodeType.RUST, "DefaultName")).isEqualTo("MyStringR");
}
}
diff --git a/processor/src/test/java/online/sharedtype/processor/writer/converter/GoEnumConverterTest.java b/processor/src/test/java/online/sharedtype/processor/writer/converter/GoEnumConverterTest.java
new file mode 100644
index 00000000..27d43216
--- /dev/null
+++ b/processor/src/test/java/online/sharedtype/processor/writer/converter/GoEnumConverterTest.java
@@ -0,0 +1,96 @@
+package online.sharedtype.processor.writer.converter;
+
+import online.sharedtype.processor.context.Config;
+import online.sharedtype.processor.context.ContextMocks;
+import online.sharedtype.processor.context.Props;
+import online.sharedtype.processor.domain.component.EnumValueInfo;
+import online.sharedtype.processor.domain.def.EnumDef;
+import online.sharedtype.processor.domain.type.ConcreteTypeInfo;
+import online.sharedtype.processor.domain.value.ValueHolder;
+import online.sharedtype.processor.support.utils.Tuple;
+import online.sharedtype.processor.writer.converter.type.TypeExpressionConverter;
+import online.sharedtype.processor.writer.render.Template;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static online.sharedtype.processor.domain.Constants.INT_TYPE_INFO;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+final class GoEnumConverterTest {
+ private final ContextMocks ctxMocks = new ContextMocks();
+ private final TypeExpressionConverter typeExpressionConverter = mock(TypeExpressionConverter.class);
+ private final GoEnumConverter converter = new GoEnumConverter(ctxMocks.getContext(), typeExpressionConverter);
+
+ private final Config config = mock(Config.class);
+ private final ConcreteTypeInfo enumTypeInfo = ConcreteTypeInfo.builder().qualifiedName("com.github.cuzfrog.EnumA").simpleName("EnumA").build();
+
+ @Test
+ void convertEnumWithIntValueConstTemplate() {
+ EnumDef enumDef = EnumDef.builder()
+ .simpleName("EnumA")
+ .qualifiedName("com.github.cuzfrog.EnumA")
+ .typeInfo(enumTypeInfo)
+ .enumValueInfos(Arrays.asList(
+ new EnumValueInfo("Value1", ValueHolder.ofEnum("Value1", INT_TYPE_INFO, 11)),
+ new EnumValueInfo("Value2", ValueHolder.ofEnum("Value2", INT_TYPE_INFO, 22))
+ ))
+ .build();
+
+ when(typeExpressionConverter.toTypeExpr(INT_TYPE_INFO, enumDef)).thenReturn("int32");
+ when(ctxMocks.getContext().getTypeStore().getConfig(enumDef)).thenReturn(config);
+ when(config.getGoEnumFormat()).thenReturn(Props.Go.EnumFormat.CONST);
+
+ Tuple tuple = converter.convert(enumDef);
+ assertThat(tuple.a()).isEqualTo(Template.TEMPLATE_GO_CONST_ENUM);
+ GoEnumConverter.EnumExpr value = (GoEnumConverter.EnumExpr) tuple.b();
+ assertThat(value.name).isEqualTo("EnumA");
+ assertThat(value.valueType).isEqualTo("int32");
+
+ var enumConst1 = value.enumerations.get(0);
+ assertThat(enumConst1.name).isEqualTo("Value1");
+ assertThat(enumConst1.enumName).isEqualTo("EnumA");
+ assertThat(enumConst1.value).isEqualTo("11");
+
+ var enumConst2 = value.enumerations.get(1);
+ assertThat(enumConst2.name).isEqualTo("Value2");
+ assertThat(enumConst2.enumName).isEqualTo("EnumA");
+ assertThat(enumConst2.value).isEqualTo("22");
+ }
+
+ @Test
+ void convertSimpleEnumStructTemplate() {
+ EnumDef enumDef = EnumDef.builder()
+ .simpleName("EnumA")
+ .qualifiedName("com.github.cuzfrog.EnumA")
+ .typeInfo(enumTypeInfo)
+ .enumValueInfos(Arrays.asList(
+ new EnumValueInfo("Value1", ValueHolder.ofEnum("Value1", enumTypeInfo, "Value1")),
+ new EnumValueInfo("Value2", ValueHolder.ofEnum("Value2", enumTypeInfo, "Value2"))
+ ))
+ .build();
+
+ when(ctxMocks.getContext().getTypeStore().getConfig(enumDef)).thenReturn(config);
+ when(config.getGoEnumFormat()).thenReturn(Props.Go.EnumFormat.STRUCT);
+
+ Tuple tuple = converter.convert(enumDef);
+ assertThat(tuple.a()).isEqualTo(Template.TEMPLATE_GO_STRUCT_ENUM);
+ GoEnumConverter.EnumExpr value = (GoEnumConverter.EnumExpr) tuple.b();
+ assertThat(value.name).isEqualTo("EnumA");
+ assertThat(value.valueType).isEqualTo("string");
+ assertThat(value.enumerations).satisfiesExactly(
+ v1 -> {
+ assertThat(v1.name).isEqualTo("Value1");
+ assertThat(v1.enumName).isEqualTo("EnumA");
+ assertThat(v1.value).isEqualTo("\"Value1\"");
+ },
+ v2 -> {
+ assertThat(v2.name).isEqualTo("Value2");
+ assertThat(v2.enumName).isEqualTo("EnumA");
+ assertThat(v2.value).isEqualTo("\"Value2\"");
+ }
+ );
+ }
+}
diff --git a/processor/src/test/java/online/sharedtype/processor/writer/converter/GoStructConverterTest.java b/processor/src/test/java/online/sharedtype/processor/writer/converter/GoStructConverterTest.java
new file mode 100644
index 00000000..9fa72aa0
--- /dev/null
+++ b/processor/src/test/java/online/sharedtype/processor/writer/converter/GoStructConverterTest.java
@@ -0,0 +1,113 @@
+package online.sharedtype.processor.writer.converter;
+
+import online.sharedtype.processor.context.ContextMocks;
+import online.sharedtype.processor.domain.Constants;
+import online.sharedtype.processor.domain.component.FieldComponentInfo;
+import online.sharedtype.processor.domain.def.ClassDef;
+import online.sharedtype.processor.domain.type.ConcreteTypeInfo;
+import online.sharedtype.processor.domain.type.TypeVariableInfo;
+import online.sharedtype.processor.writer.converter.type.TypeExpressionConverter;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+final class GoStructConverterTest {
+ private final ContextMocks ctxMocks = new ContextMocks();
+ private final TypeExpressionConverter typeExpressionConverter = TypeExpressionConverter.go(ctxMocks.getContext());
+ private final GoStructConverter converter = new GoStructConverter(typeExpressionConverter);
+
+ @Test
+ void convert() {
+ ConcreteTypeInfo recursiveTypeInfo = ConcreteTypeInfo.builder()
+ .qualifiedName("com.github.cuzfrog.RecursiveClass")
+ .simpleName("RecursiveClass")
+ .build();
+ ClassDef recursiveTypeDef = ClassDef.builder()
+ .simpleName("RecursiveClass")
+ .qualifiedName("com.github.cuzfrog.RecursiveClass")
+ .components(List.of(
+ FieldComponentInfo.builder()
+ .name("recursiveRef")
+ .type(recursiveTypeInfo)
+ .build()
+ ))
+ .cyclicReferenced(true)
+ .build();
+ recursiveTypeInfo.markShallowResolved(recursiveTypeDef);
+
+ ClassDef classDef = ClassDef.builder()
+ .simpleName("ClassA")
+ .qualifiedName("com.github.cuzfrog.ClassA")
+ .typeVariables(List.of(
+ TypeVariableInfo.builder().name("T").contextTypeQualifiedName("com.github.cuzfrog.ClassA").build()
+ ))
+ .components(List.of(
+ FieldComponentInfo.builder()
+ .name("field1")
+ .type(Constants.INT_TYPE_INFO)
+ .build(),
+ FieldComponentInfo.builder()
+ .name("field2")
+ .type(TypeVariableInfo.builder().name("T").contextTypeQualifiedName("com.github.cuzfrog.ClassA").build())
+ .build(),
+ FieldComponentInfo.builder()
+ .name("field3")
+ .type(recursiveTypeInfo)
+ .build(),
+ FieldComponentInfo.builder()
+ .name("mapField")
+ .type(ConcreteTypeInfo.builder()
+ .qualifiedName("java.util.Map")
+ .simpleName("Map")
+ .kind(ConcreteTypeInfo.Kind.MAP)
+ .typeArgs(List.of(
+ Constants.STRING_TYPE_INFO,
+ Constants.INT_TYPE_INFO
+ ))
+ .build()
+ )
+ .build()
+ ))
+ .supertypes(List.of(
+ ConcreteTypeInfo.builder()
+ .qualifiedName("com.github.cuzfrog.SuperClassA")
+ .simpleName("SuperClassA")
+ .typeArgs(List.of(
+ Constants.STRING_TYPE_INFO
+ ))
+ .build()
+ ))
+ .build();
+
+ var data = converter.convert(classDef);
+ assertThat(data).isNotNull();
+ var model = (GoStructConverter.StructExpr) data.b();
+ assertThat(model.name).isEqualTo("ClassA");
+ assertThat(model.typeParameters).containsExactly("T");
+ assertThat(model.typeParametersExpr()).isEqualTo("[T any]");
+ assertThat(model.supertypes).containsExactly("SuperClassA[string]");
+
+ assertThat(model.properties).hasSize(4);
+ GoStructConverter.PropertyExpr prop1 = model.properties.get(0);
+ assertThat(prop1.name).isEqualTo("Field1");
+ assertThat(prop1.type).isEqualTo("int32");
+ assertThat(prop1.typeExpr()).isEqualTo("int32");
+ assertThat(prop1.optional).isFalse();
+
+ GoStructConverter.PropertyExpr prop2 = model.properties.get(1);
+ assertThat(prop2.name).isEqualTo("Field2");
+ assertThat(prop2.type).isEqualTo("T");
+
+ GoStructConverter.PropertyExpr prop3 = model.properties.get(2);
+ assertThat(prop3.name).isEqualTo("Field3");
+ assertThat(prop3.type).isEqualTo("RecursiveClass");
+ assertThat(prop3.typeExpr()).isEqualTo("*RecursiveClass");
+ assertThat(prop3.optional).isTrue();
+
+ GoStructConverter.PropertyExpr prop5 = model.properties.get(3);
+ assertThat(prop5.name).isEqualTo("MapField");
+ assertThat(prop5.type).isEqualTo("map[string]int32");
+ }
+}
diff --git a/processor/src/test/java/online/sharedtype/processor/writer/converter/RustStructConverterTest.java b/processor/src/test/java/online/sharedtype/processor/writer/converter/RustStructConverterTest.java
index 0a295106..fab9fea6 100644
--- a/processor/src/test/java/online/sharedtype/processor/writer/converter/RustStructConverterTest.java
+++ b/processor/src/test/java/online/sharedtype/processor/writer/converter/RustStructConverterTest.java
@@ -51,14 +51,12 @@ void skipMapClassDef() {
@Test
void shouldAcceptClassDefAnnotated() {
+ var components = List.of(FieldComponentInfo.builder().build());
assertThat(converter.shouldAccept(ClassDef.builder().build())).isFalse();
- assertThat(converter.shouldAccept(ClassDef.builder().annotated(true).components(
- List.of(FieldComponentInfo.builder().build())
- ).build())).isTrue();
- assertThat(converter.shouldAccept(ClassDef.builder().referencedByAnnotated(true).build())).isTrue();
+ assertThat(converter.shouldAccept(ClassDef.builder().annotated(true).components(components).build())).isTrue();
+ assertThat(converter.shouldAccept(ClassDef.builder().referencedByAnnotated(true).depended(true).build())).isTrue();
}
-
@Test
void convert() {
ConcreteTypeInfo recursiveTypeInfo = ConcreteTypeInfo.builder()
diff --git a/processor/src/test/java/online/sharedtype/processor/writer/converter/type/GoTypeExpressionConverterTest.java b/processor/src/test/java/online/sharedtype/processor/writer/converter/type/GoTypeExpressionConverterTest.java
new file mode 100644
index 00000000..1cc8ec4e
--- /dev/null
+++ b/processor/src/test/java/online/sharedtype/processor/writer/converter/type/GoTypeExpressionConverterTest.java
@@ -0,0 +1,52 @@
+package online.sharedtype.processor.writer.converter.type;
+
+import online.sharedtype.processor.context.Config;
+import online.sharedtype.processor.context.ContextMocks;
+import online.sharedtype.processor.domain.Constants;
+import online.sharedtype.processor.domain.TargetCodeType;
+import online.sharedtype.processor.domain.def.ClassDef;
+import online.sharedtype.processor.domain.type.ArrayTypeInfo;
+import online.sharedtype.processor.domain.type.DateTimeInfo;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+final class GoTypeExpressionConverterTest {
+ private final ContextMocks ctxMocks = new ContextMocks();
+ private final GoTypeExpressionConverter converter = new GoTypeExpressionConverter(ctxMocks.getContext());
+
+ private final Config config = mock(Config.class);
+ private final ClassDef contextTypeDef = ClassDef.builder().simpleName("Abc").build();
+
+ @Test
+ void typeContract() {
+ assertThat(converter.typeNameMappings.keySet()).containsAll(Constants.LITERAL_TYPES);
+ }
+
+ @Test
+ void convertArrayType() {
+ String expr = converter.toTypeExpr(new ArrayTypeInfo(Constants.INT_TYPE_INFO), contextTypeDef);
+ assertThat(expr).isEqualTo("[]int32");
+ }
+
+ @Test
+ void convertObjectType() {
+ assertThat(converter.toTypeExpr(Constants.OBJECT_TYPE_INFO, contextTypeDef)).isEqualTo("any");
+ }
+
+ @Test
+ void convertDateTimeAndTypeMappings() {
+ when(config.getGoTargetDatetimeTypeLiteral()).thenReturn("DefaultDateLiteral");
+ DateTimeInfo dateTimeInfo = new DateTimeInfo("a.b.A2");
+ assertThat(converter.dateTimeTypeExpr(dateTimeInfo, config)).isEqualTo("DefaultDateLiteral");
+ dateTimeInfo.addMappedName(TargetCodeType.GO, "BBB");
+ assertThat(converter.dateTimeTypeExpr(dateTimeInfo, config)).isEqualTo("BBB");
+ }
+
+ @Test
+ void convertPrimitiveType() {
+ assertThat(converter.toTypeExpr(Constants.INT_TYPE_INFO, contextTypeDef)).isEqualTo("int32");
+ }
+}
diff --git a/setenv b/setenv
index 479d8131..2d61d8bf 100755
--- a/setenv
+++ b/setenv
@@ -19,5 +19,12 @@ else
return
fi
-export PATH=$JAVA_HOME/bin:$PATH
-java -version
+export MAVEN_OPTS="-Xmx512m -Xms512m"
+export PATH=$JAVA_HOME/bin:$MVND_HOME/bin:$PATH
+
+if [ -z "$MVND_HOME" ];then
+ echo "MVND_HOME is not set, mvnd is recommended for local development, see https://github.com/apache/maven-mvnd"
+ java -version
+else
+ mvnd -version
+fi