Skip to content
Open
2 changes: 1 addition & 1 deletion cmd/semaphore/daemon/providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func Resolve(ctx *broker.Context, mem functions.Collection, options Options) (Co
return Collection{}, err
}

err = providers.ResolveSchemas(ctx, services, schemas, flows)
err = providers.ResolveSchemaDefinitions(ctx, services, schemas, flows)
if err != nil {
return Collection{}, err
}
Expand Down
13 changes: 13 additions & 0 deletions examples/typetest/config.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
log_level = "$LOG_LEVEL"

protobuffers = ["./proto/*.proto"]

include = ["flow.hcl"]

grpc {
address = ":50051"
}

http {
address = ":8080"
}
Comment on lines +1 to +13

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it is best to exclude this example from the master branch.

23 changes: 23 additions & 0 deletions examples/typetest/flow.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
endpoint "typetest" "http" {
endpoint = "/"
method = "POST"
codec = "json"
}

endpoint "typetest" "grpc" {
package = "semaphore.typetest"
service = "Typetest"
method = "Run"
}

flow "typetest" {
input "semaphore.typetest.Request" {}

output "semaphore.typetest.Response" {
oneof = "{{ input:oneof }}"

object = "{{ input:object }}"

array = "{{ input:array }}"
}
}
61 changes: 61 additions & 0 deletions examples/typetest/proto/schema.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
syntax = "proto3";

package semaphore.typetest;

service Typetest {
rpc Run(Request) returns (Response) {}
}

enum Enum {
UNKNOWN = 0;

ON = 1;

OFF = 2;
}

message Data {
Enum enum = 1;

string string = 2;

int64 int64 = 3;

double double = 4;

repeated int64 numbers = 5;
}

message Array {
repeated Data data = 1;
}

message Request {
oneof oneof {
bool empty = 1;

Data single = 2;

Array plural = 3;
}

Data object = 4;

repeated Data array = 5;
}

message Response {
oneof oneof {
bool empty = 1;

Data single = 2;

Array plural = 3;
}

Data object = 4;

repeated Data array = 5;
}


2 changes: 2 additions & 0 deletions pkg/codec/json/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ func decode(decoder *gojay.Decoder, path string, template specs.Template, store
return Enum(template).Unmarshal(decoder, path, store, tracker)
case template.Scalar != nil:
return Scalar(template).Unmarshal(decoder, path, store, tracker)
case template.OneOf != nil:
return decoder.Object(NewOneOf(path, template, store, tracker))
}

return nil
Expand Down
4 changes: 4 additions & 0 deletions pkg/codec/json/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ func encode(encoder *gojay.Encoder, path string, template specs.Template, store
encoder.Array(NewArray(path, template, store, tracker))
case template.Enum != nil:
Enum(template).Marshal(encoder, store, tracker)
case template.OneOf != nil:
encoder.Object(NewOneOf(path, template, store, tracker))
default:
Scalar(template).Marshal(encoder, store, tracker)
}
Expand All @@ -40,6 +42,8 @@ func encodeKey(encoder *gojay.Encoder, path, key string, template specs.Template
encoder.AddArrayKey(key, NewArray(path, template, store, tracker))
case template.Enum != nil:
Enum(template).MarshalKey(encoder, key, store, tracker)
case template.OneOf != nil:
encoder.AddObjectKeyOmitEmpty(key, NewOneOf(path, template, store, tracker))
default:
Scalar(template).MarshalKey(encoder, key, store, tracker)
}
Expand Down
31 changes: 23 additions & 8 deletions pkg/codec/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ func TestMarshal(t *testing.T) {
"nested": map[string]interface{}{},
},
schema: schema,
expected: `{"message":"some message","nested":{}}`,
expected: `{"message":"some message","nested":{},"oneof":{}}`,
},
"nested": {
input: map[string]interface{}{
Expand All @@ -469,15 +469,24 @@ func TestMarshal(t *testing.T) {
},
},
schema: schema,
expected: `{"nested":{"value":"some message"}}`,
expected: `{"nested":{"value":"some message"},"oneof":{}}`,
},
"enum": {
input: map[string]interface{}{
"nested": map[string]interface{}{},
"enum": references.Enum("PENDING", 2),
},
schema: schema,
expected: `{"nested":{},"enum":"PENDING"}`,
expected: `{"nested":{},"enum":"PENDING","oneof":{}}`,
},
"oneof": {
input: map[string]interface{}{
"oneof": map[string]interface{}{
"number": int64(42),
},
},
schema: schema,
expected: `{"nested":{},"oneof":{"number":42}}`,
},
"repeating_enum": {
input: map[string]interface{}{
Expand All @@ -487,7 +496,7 @@ func TestMarshal(t *testing.T) {
},
},
schema: schema,
expected: `{"nested":{},"repeating_enum":["UNKNOWN","PENDING"]}`,
expected: `{"nested":{},"repeating_enum":["UNKNOWN","PENDING"],"oneof":{}}`,
},
"repeating objects": {
input: map[string]interface{}{
Expand All @@ -501,7 +510,7 @@ func TestMarshal(t *testing.T) {
},
},
schema: schema,
expected: `{"nested":{},"repeating":[{"value":"repeating value"},{"value":"repeating value"}]}`,
expected: `{"nested":{},"repeating":[{"value":"repeating value"},{"value":"repeating value"}],"oneof":{}}`,
},
"repeating values from reference": {
input: map[string]interface{}{
Expand All @@ -511,7 +520,7 @@ func TestMarshal(t *testing.T) {
},
},
schema: schema,
expected: `{"nested":{},"repeating_values":["repeating one","repeating two"]}`,
expected: `{"nested":{},"repeating_values":["repeating one","repeating two"],"oneof":{}}`,
},
"complex": {
input: map[string]interface{}{
Expand All @@ -527,9 +536,12 @@ func TestMarshal(t *testing.T) {
"value": "repeating value",
},
},
"oneof": map[string]interface{}{
"string": "foo",
},
},
schema: schema,
expected: `{"message":"hello world","nested":{"value":"nested value"},"repeating":[{"value":"repeating value"},{"value":"repeating value"}]}`,
expected: `{"message":"hello world","nested":{"value":"nested value"},"repeating":[{"value":"repeating value"},{"value":"repeating value"}],"oneof":{"string":"foo"}}`,
},
}

Expand Down Expand Up @@ -684,7 +696,7 @@ func TestUnmarshal(t *testing.T) {
},
},
"complex": {
input: `{"message":"hello world","nested":{"value":"hello nested world"},"repeating":[{"value":"repeating one"},{"value":"repeating two"}]}`,
input: `{"message":"hello world","nested":{"value":"hello nested world"},"repeating":[{"value":"repeating one"},{"value":"repeating two"}],"oneof":{"string":"foo"}}`,
schema: schema,
expected: map[string]tests.Expect{
"message": {
Expand All @@ -701,6 +713,9 @@ func TestUnmarshal(t *testing.T) {
"repeating[1].value": {
Scalar: "repeating two",
},
"oneof.string": {
Scalar: "foo",
},
},
},
}
Expand Down
73 changes: 73 additions & 0 deletions pkg/codec/json/oneof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package json

import (
"errors"

"github.com/francoispqt/gojay"
"github.com/jexia/semaphore/pkg/references"
"github.com/jexia/semaphore/pkg/specs"
"github.com/jexia/semaphore/pkg/specs/template"
)

// OneOf represents a JSON object
type OneOf struct {
path string
template specs.Template
store references.Store
tracker references.Tracker
isSet bool
}

// NewOneOf constructs a new object encoder/decoder for the given specs
func NewOneOf(path string, template specs.Template, store references.Store, tracker references.Tracker) *OneOf {
return &OneOf{
path: path,
template: template,
store: store,
tracker: tracker,
}
}

// MarshalJSONObject encodes the given specs object into the given gojay encoder
func (oneOf *OneOf) MarshalJSONObject(encoder *gojay.Encoder) {
for _, prop := range oneOf.template.OneOf {
encodeKey(encoder, template.JoinPath(oneOf.path, prop.Name), prop.Name, prop.Template, oneOf.store, oneOf.tracker)
}
}

// UnmarshalJSONObject unmarshals the given specs into the configured reference store
func (oneOf *OneOf) UnmarshalJSONObject(decoder *gojay.Decoder, key string) error {
if oneOf.template.OneOf == nil {
return nil
}

if oneOf.isSet {
return errors.New("only a single field is allowed to be set")
}

property, has := oneOf.template.OneOf[key]
if !has {
return nil
}

// change the state only if the property is known
oneOf.isSet = true

oneOf.store.Define(oneOf.path, len(oneOf.template.Message))

return decode(decoder, template.JoinPath(oneOf.path, key), property.Template, oneOf.store, oneOf.tracker)
}

// NKeys returns the amount of available keys inside the given oneof
func (oneOf *OneOf) NKeys() int {
if oneOf.template.OneOf == nil {
return 0
}

return len(oneOf.template.OneOf)
}

// IsNil returns whether the given oneof is null or not
func (oneOf *OneOf) IsNil() bool {
return oneOf == nil
}
2 changes: 2 additions & 0 deletions pkg/codec/json/tests/complete.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ flow "complete" {
repeating_values = "{{ input:repeating_values }}"
enum = "{{ input:enum }}"
repeating_enum = "{{ input:repeating_enum }}"

oneof = "{{ input:oneof }}"
}
}
}
10 changes: 10 additions & 0 deletions pkg/codec/json/tests/mock.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,13 @@ flow "repeating_enum" {
}
}
}

flow "one_of" {
input "com.complete.input" {}

resource "first" {
request "mock" "one_of" {
oneof = "{{ input:oneof }}"
}
}
}
Loading