diff --git a/bundle/schema/schema.go b/bundle/schema/schema.go index 2e6f5368..1106bbcc 100644 --- a/bundle/schema/schema.go +++ b/bundle/schema/schema.go @@ -84,6 +84,7 @@ func NewSchema(golangType reflect.Type) (*Schema, error) { Properties: rootProp.Properties, AdditionalProperties: rootProp.AdditionalProperties, Items: rootProp.Items, + Required: rootProp.Required, }, nil } @@ -168,7 +169,7 @@ func safeToSchema(golangType reflect.Type, seenTypes map[reflect.Type]struct{}, func addStructFields(fields []reflect.StructField, golangType reflect.Type) []reflect.StructField { bfsQueue := list.New() - for i := golangType.NumField() - 1; i >= 0; i-- { + for i := 0; i < golangType.NumField(); i++ { bfsQueue.PushBack(golangType.Field(i)) } for bfsQueue.Len() > 0 { @@ -203,7 +204,6 @@ func addStructFields(fields []reflect.StructField, golangType reflect.Type) []re // helps log where the error originated from func toSchema(golangType reflect.Type, seenTypes map[reflect.Type]struct{}, debugTrace *list.List) (*Schema, error) { // *Struct and Struct generate identical json schemas - // TODO: add test case for pointer if golangType.Kind() == reflect.Pointer { return toSchema(golangType.Elem(), seenTypes, debugTrace) } @@ -236,6 +236,7 @@ func toSchema(golangType reflect.Type, seenTypes map[reflect.Type]struct{}, debu Properties: elemProps.Properties, AdditionalProperties: elemProps.AdditionalProperties, Items: elemProps.Items, + Required: elemProps.Required, } // TODO: what if there is an array of maps. Add additional properties to // TODO: what if there are maps of maps @@ -257,22 +258,36 @@ func toSchema(golangType reflect.Type, seenTypes map[reflect.Type]struct{}, debu // case struct properties := map[string]*Schema{} + required := []string{} if golangType.Kind() == reflect.Struct { children := []reflect.StructField{} children = addStructFields(children, golangType) for _, child := range children { // compute child properties - childJsonTag := child.Tag.Get("json") - childName := strings.Split(childJsonTag, ",")[0] - - // add current field to debug trace - debugTrace.PushBack(childName) + childJsonTag := strings.Split(child.Tag.Get("json"), ",") + childName := childJsonTag[0] // skip fields that are not annotated or annotated with "-" if childName == "" || childName == "-" { continue } + // TODO: Add test for omitempty + hasOmitEmptyTag := false + for i := 1; i < len(childJsonTag); i++ { + if childJsonTag[i] == "omitempty" { + hasOmitEmptyTag = true + } + } + if !hasOmitEmptyTag { + required = append(required, childName) + } + + // add current field to debug trace + // TODO: Add tests testing correct traversal and adding of strings + // into debugTrace + debugTrace.PushBack(childName) + // recursively compute properties for this child field fieldProps, err := safeToSchema(child.Type, seenTypes, debugTrace) if err != nil { @@ -294,5 +309,6 @@ func toSchema(golangType reflect.Type, seenTypes map[reflect.Type]struct{}, debu Items: items, Properties: properties, AdditionalProperties: additionalProperties, + Required: required, }, nil } diff --git a/bundle/schema/schema_test.go b/bundle/schema/schema_test.go index 76908950..9558d74e 100644 --- a/bundle/schema/schema_test.go +++ b/bundle/schema/schema_test.go @@ -109,7 +109,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) { assert.NoError(t, err) expected := - ` { + `{ "type": "object", "properties": { "bool_val": { @@ -155,7 +155,23 @@ func TestStructOfPrimitivesSchema(t *testing.T) { "type": "number" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "int_val", + "int8_val", + "int16_val", + "int32_val", + "int64_val", + "uint_val", + "uint8_val", + "uint16_val", + "uint32_val", + "uint64_val", + "float32_val", + "float64_val", + "string_val", + "bool_val" + ] }` t.Log("[DEBUG] actual: ", string(jsonSchema)) @@ -202,13 +218,23 @@ func TestStructOfStructsSchema(t *testing.T) { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "a", + "b" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "bar" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "foo" + ] }` t.Log("[DEBUG] actual: ", string(jsonSchema)) @@ -247,10 +273,16 @@ func TestStructOfMapsSchema(t *testing.T) { } } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "my_map" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "bar" + ] }` t.Log("[DEBUG] actual: ", string(jsonSchema)) @@ -289,10 +321,16 @@ func TestStructOfSliceSchema(t *testing.T) { } } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "my_slice" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "bar" + ] }` t.Log("[DEBUG] actual: ", string(jsonSchema)) @@ -344,7 +382,11 @@ func TestMapOfStructSchema(t *testing.T) { "my_int": { "type": "number" } - } + }, + "additionalProperties": false, + "required": [ + "my_int" + ] } }` @@ -498,7 +540,10 @@ func TestSliceOfStructSchema(t *testing.T) { "type": "number" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "my_int" + ] } }` @@ -557,7 +602,10 @@ func TestEmbeddedStructSchema(t *testing.T) { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "country" + ] }, "name": { "type": "string" @@ -583,23 +631,39 @@ func TestEmbeddedStructSchema(t *testing.T) { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "country" + ] }, "name": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "name", + "home" + ] } } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "events" + ] }, "state": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "plot", + "name", + "home", + "country" + ] }` t.Log("[DEBUG] actual: ", string(jsonSchema)) @@ -644,7 +708,10 @@ func TestNonAnnotatedFieldsAreSkipped(t *testing.T) { "type": "number" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "bar" + ] }` t.Log("[DEBUG] actual: ", string(jsonSchema)) @@ -675,7 +742,10 @@ func TestDashFieldsAreSkipped(t *testing.T) { "type": "number" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "bar" + ] }` t.Log("[DEBUG] actual: ", string(jsonSchema)) @@ -717,7 +787,10 @@ func TestPointerInStructSchema(t *testing.T) { "type": "number" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "ptr_val2" + ] }, "float_val": { "type": "number" @@ -729,7 +802,10 @@ func TestPointerInStructSchema(t *testing.T) { "type": "number" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "ptr_val2" + ] }, "ptr_int": { "type": "number" @@ -738,7 +814,14 @@ func TestPointerInStructSchema(t *testing.T) { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "ptr_int", + "ptr_string", + "float_val", + "ptr_bar", + "bar" + ] }` t.Log("[DEBUG] actual: ", string(jsonSchema)) @@ -785,7 +868,11 @@ func TestObjectSchema(t *testing.T) { "name": { "type": "string" } - } + }, + "additionalProperties": false, + "required": [ + "name" + ] }, "plot": { "type": "object", @@ -796,7 +883,11 @@ func TestObjectSchema(t *testing.T) { "type": "string" } } - } + }, + "additionalProperties": false, + "required": [ + "stakes" + ] }, "villian": { "type": "object", @@ -807,9 +898,19 @@ func TestObjectSchema(t *testing.T) { "name": { "type": "string" } - } + }, + "additionalProperties": false, + "required": [ + "name" + ] } - } + }, + "additionalProperties": false, + "required": [ + "hero", + "villian", + "plot" + ] }` t.Log("[DEBUG] actual: ", string(jsonSchema))