Skip to content

Commit

Permalink
Openapi anyof and oneof (#200)
Browse files Browse the repository at this point in the history
Co-authored-by: Jerre van Veluw <[email protected]>
  • Loading branch information
wilmveel and jerrevanveluw authored Apr 30, 2024
1 parent 6f6d8c3 commit f8358fb
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter, Emi
|
""".trimMargin()

override fun Union.emit() = "export type ${name.sanitizeSymbol()} = ${entries.joinToString(" | ") { it }}\n"
override fun Union.emit() = "export type ${name.sanitizeSymbol()} = ${entries.joinToString(" | ") { it.emit() }}\n"

private fun List<Endpoint.Segment>.emitType() = "`${joinToString("") { "/" + it.emitType() }}`"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class WirespecEmitter(logger: Logger = noLogger) : DefinitionModelEmitter, Emitt
|
""".trimMargin()

override fun Union.emit() = "type $name = ${entries.joinToString(" | ")}\n"
override fun Union.emit() = "type $name = ${entries.joinToString(" | "){it.emit()}}\n"

private fun String.fixStatus(): String = when (this) {
"default" -> "200"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ object ClassModelTransformer : Transformer {
},
supers = ast
.filterIsInstance<Union>()
.filter { it.entries.contains(name) }
.filter { union ->
union.entries
.map { when(it){
is Type.Shape.Field.Reference.Custom -> it.value
else -> error("Any Unit of Primitive cannot be part of Union")
}
}
.contains(name) }
.map { Reference.Custom(it.name) }
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,39 @@ data class Type(override val name: String, val shape: Shape) : Definition {
}
}

sealed interface Reference {
sealed interface Reference: Value<String> {
val isIterable: Boolean
val isMap: Boolean

data class Any(override val isIterable: Boolean, override val isMap: Boolean = false) : Reference
data class Unit(override val isIterable: Boolean, override val isMap: Boolean = false) : Reference
data class Any(
override val isIterable: Boolean,
override val isMap: Boolean = false,
) : Reference {
override val value: String
get() = "Any"
}

data class Unit(
override val isIterable: Boolean,
override val isMap: Boolean = false,
) : Reference {
override val value: String
get() = "Unit"
}
data class Custom(
override val value: String,
override val isIterable: Boolean,
override val isMap: Boolean = false
) : Value<String>, Reference
) : Reference

data class Primitive(
val type: Type,
override val isIterable: Boolean = false,
override val isMap: Boolean = false
) : Reference {
enum class Type { String, Integer, Number, Boolean }
override val value: String
get() = type.name
}
}
}
Expand All @@ -47,7 +62,7 @@ data class Type(override val name: String, val shape: Shape) : Definition {

data class Enum(override val name: String, val entries: Set<String>) : Definition

data class Union(override val name: String, val entries: Set<String>) : Definition
data class Union(override val name: String, val entries: Set<Field.Reference>) : Definition

data class Refined(override val name: String, val validator: Validator) : Definition {
data class Validator(override val value: String) : Value<String>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,16 @@ class TypeParser(logger: Logger) : AbstractParser(logger) {
eatToken().bind()
token.log()
when (token.type) {
is CustomType -> mutableListOf<String>().apply {
is CustomType -> mutableListOf<Type.Shape.Field.Reference>().apply {
token.shouldBeDefined().bind()
add(token.value)
add(Type.Shape.Field.Reference.Custom(token.value, false))
eatToken().bind()
while (token.type == Pipe) {
eatToken().bind()
when (token.type) {
is CustomType -> {
token.shouldBeDefined().bind()
add(token.value).also { eatToken().bind() }
add(Type.Shape.Field.Reference.Custom(token.value, false)).also { eatToken().bind() }
}
else -> raise(WrongTokenException<CustomType>(token).also { eatToken().bind() })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ class ParseTypeTest {
.also { it.size shouldBe 2 }
.let {
val (first, second) = it.toList()
first shouldBe "Bar"
second shouldBe "Bal"
first shouldBe Type.Shape.Field.Reference.Custom(value = "Bar", isIterable = false, isMap = false)
second shouldBe Type.Shape.Field.Reference.Custom(value = "Bal", isIterable = false, isMap = false)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ fun Node.produce(): WsNode =
requests = requests.produce(),
responses = responses.produce(),
)

is Enum -> WsEnum(name, entries.toTypedArray())
is Refined -> WsRefined(name, validator.value)
is Union -> WsEnum(name, entries.toTypedArray())
is Union -> WsUnion(name, entries
.map { it.produce()}
.toTypedArray())
}

fun List<Node>.produce(): Array<WsNode> =
Expand Down Expand Up @@ -108,6 +111,9 @@ data class WsEndpoint(
@JsExport
data class WsEnum(override val name: String, val entries: Array<String>) : WsNode

@JsExport
data class WsUnion(override val name: String, val entries: Array<WsReference>) : WsNode

@JsExport
data class WsRefined(override val name: String, val validator: String) : WsNode

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import community.flock.wirespec.compiler.core.parse.Node
import community.flock.wirespec.compiler.core.parse.Type
import community.flock.wirespec.compiler.core.parse.Type.Shape.Field
import community.flock.wirespec.compiler.core.parse.Type.Shape.Field.Reference
import community.flock.wirespec.compiler.core.parse.Union
import community.flock.wirespec.openapi.Common.className
import kotlinx.serialization.json.Json
import community.flock.kotlinx.openapi.bindings.v3.Type as OpenapiType
Expand Down Expand Up @@ -345,9 +346,25 @@ class OpenApiParser(private val openApi: OpenAPIObject) {
?.flatten(name)
?: emptyList()
}
oneOf != null || anyOf != null -> listOf(
Union(
name = name,
entries = oneOf!!
.mapIndexed { index, it ->
when (it) {
is ReferenceObject -> it.toReference()
is SchemaObject -> it.toReference(className(name, index.toString()))
}

oneOf != null -> TODO("oneOf is not implemented")
anyOf != null -> TODO("anyOf is not implemented")
}.toSet()
)
)
.plus(oneOf!!.flatMapIndexed() { index, it ->
when (it) {
is ReferenceObject -> emptyList()
is SchemaObject -> it.flatten(className(name, index.toString()))
}
})
allOf != null -> listOf(
Type(
name,
Expand Down
73 changes: 73 additions & 0 deletions src/converter/openapi/src/commonTest/resources/v3/oneof.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"openapi": "3.0.0",
"info": {
"title": "Object in response",
"version": "1.0.0"
},
"paths": {
"/oneof": {
"get": {
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Foo"
},
{
"$ref": "#/components/schemas/Bar"
},
{
"properties": {
"c": {
"type": "string"
}
}
},
{
"properties": {
"d": {
"type": "object",
"properties": {
"e": {
"type": "string"
}
}
}
}
}
]
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Foo": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
},
"Bar": {
"type": "object",
"required": ["b"],
"properties": {
"b": {
"type": "string"
}
}
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import community.flock.wirespec.compiler.core.parse.Enum
import community.flock.wirespec.compiler.core.parse.Type
import community.flock.wirespec.compiler.core.parse.Type.Shape.Field.Identifier
import community.flock.wirespec.compiler.core.parse.Type.Shape.Field.Reference
import community.flock.wirespec.compiler.core.parse.Union

object Expected {

Expand Down Expand Up @@ -467,6 +468,125 @@ object Expected {
)
)

val oneOf = listOf(
Endpoint(
name = "OneofGET",
method = Endpoint.Method.GET,
path = listOf(Endpoint.Segment.Literal(value = "oneof")),
query = emptyList(),
headers = emptyList(),
cookies = emptyList(),
requests = listOf(
Endpoint.Request(
content = null
)
),
responses = listOf(
Endpoint.Response(
status = "200",
headers = emptyList(),
content = Endpoint.Content(
type = "application/json",
reference = Reference.Custom(
value = "OneofGET200ApplicationJsonResponseBody",
isIterable = false,
isMap = false
),
isNullable = false
)
)
)
),
Union(
name = "OneofGET200ApplicationJsonResponseBody",
entries = setOf(
Reference.Custom(value = "Foo", isIterable = false, isMap = false),
Reference.Custom(value = "Bar", isIterable = false, isMap = false),
Reference.Custom(value = "OneofGET200ApplicationJsonResponseBody2", isIterable = false, isMap = false),
Reference.Custom(value = "OneofGET200ApplicationJsonResponseBody3", isIterable = false, isMap = false)
)
),
Type(
name = "OneofGET200ApplicationJsonResponseBody2",
shape = Type.Shape(
value = listOf(
Type.Shape.Field(
identifier = Identifier("c"),
reference = Reference.Primitive(
type = Reference.Primitive.Type.String,
isIterable = false,
isMap = false
),
isNullable = true
)
)
)
),
Type(
name = "OneofGET200ApplicationJsonResponseBody3",
shape = Type.Shape(
value = listOf(
Type.Shape.Field(
identifier = Identifier("d"),
reference = Reference.Custom(
value = "OneofGET200ApplicationJsonResponseBody3D",
isIterable = false,
isMap = false
),
isNullable = true
)
)
)
),
Type(
name = "OneofGET200ApplicationJsonResponseBody3D",
shape = Type.Shape(
value = listOf(
Type.Shape.Field(
identifier = Identifier(
"e"
), reference = Reference.Primitive(
type = Reference.Primitive.Type.String,
isIterable = false,
isMap = false
),
isNullable = true
)
)
)
),
Type(
name = "Foo",
shape = Type.Shape(
value = listOf(
Type.Shape.Field(
identifier = Identifier("a"),
reference = Reference.Primitive(
type = Reference.Primitive.Type.String,
isIterable = false
),
isNullable = true
)
)
)
),
Type(
name = "Bar",
shape = Type.Shape(
value = listOf(
Type.Shape.Field(
identifier = Identifier("b"),
reference = Reference.Primitive(
type = Reference.Primitive.Type.String,
isIterable = false
),
isNullable = false
)
)
)
)
)

val enum = listOf(
Endpoint(
name = "EnumGET",
Expand Down
Loading

0 comments on commit f8358fb

Please sign in to comment.