Skip to content

Commit

Permalink
Support tagged union
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Farr <[email protected]>
  • Loading branch information
Xtansia committed Feb 28, 2024
1 parent a102fc2 commit 9f33b84
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ private void visit(Namespace parent, String className, String typedefName, OpenA
shape = objShape;
} else if (schema.isString() && schema.hasEnums()) {
shape = new EnumShape(parent, className, schema.getEnum().orElseThrow().stream().map(EnumShape.Variant::new).toList(), typedefName);
} else if (schema.getOneOf().isPresent()) {
var taggedUnion = new TaggedUnionShape(parent, className, typedefName);
schema.getOneOf()
.get()
.forEach(s -> taggedUnion.addVariant(s.resolve().getName(), mapType(s)));
shape = taggedUnion;
} else {
throw new NotImplementedException("Unsupported schema: " + schema);
}
Expand Down Expand Up @@ -232,7 +238,7 @@ private Type mapTypeInner(OpenApiSchema schema) {
if (schema.has$ref()) {
schema = schema.resolve();

if (!schema.shouldKeepRef()) {
if (!shouldKeepRef(schema)) {
return mapType(schema);
}

Expand Down Expand Up @@ -277,11 +283,23 @@ private Type mapTypeInner(OpenApiSchema schema) {

private Type mapOneOf(List<OpenApiSchema> oneOf) {
if (oneOf.size() == 2) {
var first = oneOf.get(0).resolve();
var second = oneOf.get(1).resolve();
var first = oneOf.get(0);
var second = oneOf.get(1);

if (first.isString() && second.isArray()) {
return mapType(second);
if (second.isArray()) {
var items = second.getItems().orElseThrow();

if (first.getType().equals(items.getType()) && first.get$ref().equals(items.get$ref())) {
return mapType(second);
}
}

if ((first.isString() && (second.isString() || second.isNumber())) || (first.isNumber() && second.isString())) {
return Types.Java.Lang.String;
}

if (first.isBoolean() && second.isString()) {
return Types.Primitive.Boolean;
}
}

Expand Down Expand Up @@ -313,4 +331,22 @@ private Type mapNumber(OpenApiSchema schema) {
throw new UnsupportedOperationException("Can not get type name for integer/number with format: " + format);
}
}

private boolean shouldKeepRef(OpenApiSchema schema) {
var type = schema.getType();
if (type.isEmpty()) {
if (schema.getOneOf().isPresent()) {
return schema.getOneOf().get().get(0).resolve().getType().map(OpenApiSchema.Type.OBJECT::equals).orElse(false);
}
return false;
}
switch (type.get()) {
case OBJECT:
return true;
case STRING:
return schema.hasEnums();
default:
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.client.codegen.model;

import java.util.ArrayList;
import java.util.List;

public class TaggedUnionShape extends ObjectShape {
private final List<Variant> variants = new ArrayList<>();

public TaggedUnionShape(Namespace parent, String className, String typedefName) {
super(parent, className, typedefName);
}

public void addVariant(String name, Type type) {
variants.add(new Variant(name, type));
}

public List<Variant> getVariants() {
return variants;
}

public static class Variant {
private final String name;
private final Type type;

protected Variant(String name, Type type) {
this.name = name;
this.type = type;
}

public String getName() {
return name;
}

public Type getType() {
return type;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ public static final class Json {
public static final Type JsonpSerializable = Type.builder().pkg(PACKAGE).name("JsonpSerializable").build();
public static final Type ObjectBuilderDeserializer = Type.builder().pkg(PACKAGE).name("ObjectBuilderDeserializer").build();
public static final Type ObjectDeserializer = Type.builder().pkg(PACKAGE).name("ObjectDeserializer").build();
public static final Type UnionDeserializer = Type.builder().pkg(PACKAGE).name("UnionDeserializer").build();
}

public static final class OpenSearch {
Expand Down Expand Up @@ -161,6 +162,13 @@ public static Type ObjectBuilder(Type type) {

public static final Type ObjectBuilder = Type.builder().pkg(PACKAGE).name("ObjectBuilder").build();
public static final Type ObjectBuilderBase = Type.builder().pkg(PACKAGE).name("ObjectBuilderBase").build();

public static Type TaggedUnion(Type tagType, Type baseType) {
return TaggedUnion.withGenericArgs(tagType, baseType);
}

public static final Type TaggedUnion = Type.builder().pkg(PACKAGE).name("TaggedUnion").build();
public static final Type TaggedUnionUtils = Type.builder().pkg(PACKAGE).name("TaggedUnionUtils").build();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ public boolean isString() {
return is(Type.STRING);
}

public boolean isNumber() {
return is(Type.NUMBER);
}

public boolean isBoolean() {
return is(Type.BOOLEAN);
}

public boolean isArray() {
return is(Type.ARRAY);
}
Expand All @@ -60,19 +68,6 @@ public Optional<Format> getFormat() {
return Optional.ofNullable(getInner().getFormat()).map(Format::from);
}

public boolean shouldKeepRef() {
var type = getType();
if (type.isEmpty()) return false;
switch (type.get()) {
case OBJECT:
return true;
case STRING:
return hasEnums();
default:
return false;
}
}

public Optional<List<String>> getEnum() {
return Optional.ofNullable((List<String>) getInner().getEnum());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* Serialize this object to JSON.
*/
@Override
public void serialize({{TYPES.Jakarta.Json.Stream.JsonGenerator}} generator, {{TYPES.Client.Json.JsonpMapper}} mapper) {
generator.writeStartObject();
serializeInternal(generator, mapper);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{{>ObjectShape/ClassDeclaration}} {
public enum Kind {
{{#variants}}{{name}}{{^-last}},{{/-last}}{{/variants}}
}

private final Kind _kind;
private final Object _value;

@Override
public final Kind _kind() {
return _kind;
}

@Override
public final Object _value() {
return _value;
}

private {{className}}(Kind kind, Object value) {
this._kind = kind;
this._value = value;
}

private {{className}}(Builder builder) {
this._kind = {{TYPES.Client.Util.ApiTypeHelper}}.requireNonNull(builder._kind, builder, "<variant kind>");
this._value = {{TYPES.Client.Util.ApiTypeHelper}}.requireNonNull(builder._value, builder, "<variant value>");
}

public static {{className}} of({{type.builderFnType}} fn) {
return fn.apply(new Builder()).build();
}

{{#variants}}
/**
* Is this variant instance of kind {@code {{name}}}?
*/
public boolean is{{name}}() {
return _kind == Kind.{{name}};
}

/**
* Get the {@code {{name}}} variant value.
*
* @throws IllegalStateException if the current variant is not the {@code {{name}}} kind.
*/
public {{type}} {{name}}() {
return {{TYPES.Client.Util.TaggedUnionUtils}}.get(this, Kind.{{name}});
}

{{/variants}}

@Override
public void serialize({{TYPES.Jakarta.Json.Stream.JsonGenerator}} generator, {{TYPES.Client.Json.JsonpMapper}} mapper) {
if (_value instanceof {{TYPES.Client.Json.JsonpSerializable}}) {
(({{TYPES.Client.Json.JsonpSerializable}}) _value).serialize(generator, mapper);
}
}

public static class Builder extends {{TYPES.Client.Util.ObjectBuilderBase}} implements {{TYPES.Client.Util.ObjectBuilder}}<{{className}}> {
private Kind _kind;
private Object _value;
{{#variants}}
public {{TYPES.Client.Util.ObjectBuilder}}<{{className}}> {{name}}({{type}} v) {
this._kind = Kind.{{name}};
this._value = v;
return this;
}

public {{TYPES.Client.Util.ObjectBuilder}}<{{className}}> {{name}}({{type.builderFnType}} fn) {
return this.{{name}}(fn.apply(new {{type.builderType}}()).build());
}

{{/variants}}
@Override
public {{className}} build() {
_checkSingleUse();
return new {{className}}(this);
}
}

private static {{TYPES.Client.Json.JsonpDeserializer}}<{{className}}> build{{className}}Deserializer() {
return new {{TYPES.Client.Json.UnionDeserializer.builderType}}<{{className}}, Kind, Object>({{className}}::new, false)
{{#variants}}.addMember(Kind.{{name}}, {{type}}._DESERIALIZER){{/variants}}
.build();
}

public static final {{TYPES.Client.Json.JsonpDeserializer}}<{{className}}> _DESERIALIZER = {{TYPES.Client.Json.JsonpDeserializer}}.lazy({{className}}::build{{className}}Deserializer);
}

0 comments on commit 9f33b84

Please sign in to comment.