1package analysis 2 3import ( 4 "encoding/json" 5 "fmt" 6 "path" 7 "path/filepath" 8 "testing" 9 10 "net/http" 11 "net/http/httptest" 12 13 "github.com/go-openapi/spec" 14 "github.com/stretchr/testify/assert" 15) 16 17var knownSchemas = []*spec.Schema{ 18 spec.BoolProperty(), // 0 19 spec.StringProperty(), // 1 20 spec.Int8Property(), // 2 21 spec.Int16Property(), // 3 22 spec.Int32Property(), // 4 23 spec.Int64Property(), // 5 24 spec.Float32Property(), // 6 25 spec.Float64Property(), // 7 26 spec.DateProperty(), // 8 27 spec.DateTimeProperty(), // 9 28 (&spec.Schema{}), // 10 29 (&spec.Schema{}).Typed("object", ""), // 11 30 (&spec.Schema{}).Typed("", ""), // 12 31 (&spec.Schema{}).Typed("", "uuid"), // 13 32} 33 34func newCObj() *spec.Schema { 35 return (&spec.Schema{}).Typed("object", "").SetProperty("id", *spec.Int64Property()) 36} 37 38var complexObject = newCObj() 39 40var complexSchemas = []*spec.Schema{ 41 complexObject, 42 spec.ArrayProperty(complexObject), 43 spec.MapProperty(complexObject), 44} 45 46func knownRefs(base string) []spec.Ref { 47 urls := []string{"bool", "string", "integer", "float", "date", "object", "format"} 48 49 result := make([]spec.Ref, 0, len(urls)) 50 for _, u := range urls { 51 result = append(result, spec.MustCreateRef(fmt.Sprintf("%s/%s", base, path.Join("known", u)))) 52 } 53 return result 54} 55 56func complexRefs(base string) []spec.Ref { 57 urls := []string{"object", "array", "map"} 58 59 result := make([]spec.Ref, 0, len(urls)) 60 for _, u := range urls { 61 result = append(result, spec.MustCreateRef(fmt.Sprintf("%s/%s", base, path.Join("complex", u)))) 62 } 63 return result 64} 65 66func refServer() *httptest.Server { 67 mux := http.NewServeMux() 68 mux.Handle("/known/bool", schemaHandler(knownSchemas[0])) 69 mux.Handle("/known/string", schemaHandler(knownSchemas[1])) 70 mux.Handle("/known/integer", schemaHandler(knownSchemas[5])) 71 mux.Handle("/known/float", schemaHandler(knownSchemas[6])) 72 mux.Handle("/known/date", schemaHandler(knownSchemas[8])) 73 mux.Handle("/known/object", schemaHandler(knownSchemas[11])) 74 mux.Handle("/known/format", schemaHandler(knownSchemas[13])) 75 76 mux.Handle("/complex/object", schemaHandler(complexSchemas[0])) 77 mux.Handle("/complex/array", schemaHandler(complexSchemas[1])) 78 mux.Handle("/complex/map", schemaHandler(complexSchemas[2])) 79 80 return httptest.NewServer(mux) 81} 82 83func refSchema(ref spec.Ref) *spec.Schema { 84 return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}} 85} 86 87func schemaHandler(schema *spec.Schema) http.Handler { 88 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 89 writeJSON(w, schema) 90 }) 91} 92 93func writeJSON(w http.ResponseWriter, data interface{}) { 94 w.Header().Add("Content-Type", "application/json") 95 w.WriteHeader(http.StatusOK) 96 enc := json.NewEncoder(w) 97 if err := enc.Encode(data); err != nil { 98 panic(err) 99 } 100} 101 102func TestSchemaAnalysis_KnownTypes(t *testing.T) { 103 for i, v := range knownSchemas { 104 sch, err := Schema(SchemaOpts{Schema: v}) 105 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 106 assert.True(t, sch.IsKnownType, "item at %d should be a known type", i) 107 } 108 } 109 for i, v := range complexSchemas { 110 sch, err := Schema(SchemaOpts{Schema: v}) 111 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 112 assert.False(t, sch.IsKnownType, "item at %d should not be a known type", i) 113 } 114 } 115 116 serv := refServer() 117 defer serv.Close() 118 119 for i, ref := range knownRefs(serv.URL) { 120 sch, err := Schema(SchemaOpts{Schema: refSchema(ref)}) 121 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 122 assert.True(t, sch.IsKnownType, "item at %d should be a known type", i) 123 } 124 } 125 for i, ref := range complexRefs(serv.URL) { 126 sch, err := Schema(SchemaOpts{Schema: refSchema(ref)}) 127 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 128 assert.False(t, sch.IsKnownType, "item at %d should not be a known type", i) 129 } 130 } 131} 132 133func TestSchemaAnalysis_Array(t *testing.T) { 134 for i, v := range append(knownSchemas, (&spec.Schema{}).Typed("array", "")) { 135 sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(v)}) 136 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 137 assert.True(t, sch.IsArray, "item at %d should be an array type", i) 138 assert.True(t, sch.IsSimpleArray, "item at %d should be a simple array type", i) 139 } 140 } 141 142 for i, v := range complexSchemas { 143 sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(v)}) 144 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 145 assert.True(t, sch.IsArray, "item at %d should be an array type", i) 146 assert.False(t, sch.IsSimpleArray, "item at %d should not be a simple array type", i) 147 } 148 } 149 150 serv := refServer() 151 defer serv.Close() 152 153 for i, ref := range knownRefs(serv.URL) { 154 sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(refSchema(ref))}) 155 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 156 assert.True(t, sch.IsArray, "item at %d should be an array type", i) 157 assert.True(t, sch.IsSimpleArray, "item at %d should be a simple array type", i) 158 } 159 } 160 for i, ref := range complexRefs(serv.URL) { 161 sch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(refSchema(ref))}) 162 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 163 assert.False(t, sch.IsKnownType, "item at %d should not be a known type", i) 164 assert.True(t, sch.IsArray, "item at %d should be an array type", i) 165 assert.False(t, sch.IsSimpleArray, "item at %d should not be a simple array type", i) 166 } 167 } 168 169 // edge case: unrestricted array (beyond Swagger) 170 at := spec.ArrayProperty(nil) 171 at.Items = nil 172 sch, err := Schema(SchemaOpts{Schema: at}) 173 if assert.NoError(t, err) { 174 assert.True(t, sch.IsArray) 175 assert.False(t, sch.IsTuple) 176 assert.False(t, sch.IsKnownType) 177 assert.True(t, sch.IsSimpleSchema) 178 } 179 180 // unrestricted array with explicit empty schema 181 at = spec.ArrayProperty(nil) 182 at.Items = &spec.SchemaOrArray{} 183 sch, err = Schema(SchemaOpts{Schema: at}) 184 if assert.NoError(t, err) { 185 assert.True(t, sch.IsArray) 186 assert.False(t, sch.IsTuple) 187 assert.False(t, sch.IsKnownType) 188 assert.True(t, sch.IsSimpleSchema) 189 } 190} 191 192func TestSchemaAnalysis_Map(t *testing.T) { 193 for i, v := range append(knownSchemas, spec.MapProperty(nil)) { 194 sch, err := Schema(SchemaOpts{Schema: spec.MapProperty(v)}) 195 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 196 assert.True(t, sch.IsMap, "item at %d should be a map type", i) 197 assert.True(t, sch.IsSimpleMap, "item at %d should be a simple map type", i) 198 } 199 } 200 201 for i, v := range complexSchemas { 202 sch, err := Schema(SchemaOpts{Schema: spec.MapProperty(v)}) 203 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 204 assert.True(t, sch.IsMap, "item at %d should be a map type", i) 205 assert.False(t, sch.IsSimpleMap, "item at %d should not be a simple map type", i) 206 } 207 } 208} 209 210func TestSchemaAnalysis_ExtendedObject(t *testing.T) { 211 for i, v := range knownSchemas { 212 wex := spec.MapProperty(v).SetProperty("name", *spec.StringProperty()) 213 sch, err := Schema(SchemaOpts{Schema: wex}) 214 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 215 assert.True(t, sch.IsExtendedObject, "item at %d should be an extended map object type", i) 216 assert.False(t, sch.IsMap, "item at %d should not be a map type", i) 217 assert.False(t, sch.IsSimpleMap, "item at %d should not be a simple map type", i) 218 } 219 } 220} 221 222func TestSchemaAnalysis_Tuple(t *testing.T) { 223 at := spec.ArrayProperty(nil) 224 at.Items = &spec.SchemaOrArray{} 225 at.Items.Schemas = append(at.Items.Schemas, *spec.StringProperty(), *spec.Int64Property()) 226 227 sch, err := Schema(SchemaOpts{Schema: at}) 228 if assert.NoError(t, err) { 229 assert.True(t, sch.IsTuple) 230 assert.False(t, sch.IsTupleWithExtra) 231 assert.False(t, sch.IsKnownType) 232 assert.False(t, sch.IsSimpleSchema) 233 } 234 235 // edge case: tuple with a single element 236 at.Items = &spec.SchemaOrArray{} 237 at.Items.Schemas = append(at.Items.Schemas, *spec.StringProperty()) 238 sch, err = Schema(SchemaOpts{Schema: at}) 239 if assert.NoError(t, err) { 240 assert.True(t, sch.IsTuple) 241 assert.False(t, sch.IsTupleWithExtra) 242 assert.False(t, sch.IsKnownType) 243 assert.False(t, sch.IsSimpleSchema) 244 } 245} 246 247func TestSchemaAnalysis_TupleWithExtra(t *testing.T) { 248 at := spec.ArrayProperty(nil) 249 at.Items = &spec.SchemaOrArray{} 250 at.Items.Schemas = append(at.Items.Schemas, *spec.StringProperty(), *spec.Int64Property()) 251 at.AdditionalItems = &spec.SchemaOrBool{Allows: true} 252 at.AdditionalItems.Schema = spec.Int32Property() 253 254 sch, err := Schema(SchemaOpts{Schema: at}) 255 if assert.NoError(t, err) { 256 assert.False(t, sch.IsTuple) 257 assert.True(t, sch.IsTupleWithExtra) 258 assert.False(t, sch.IsKnownType) 259 assert.False(t, sch.IsSimpleSchema) 260 } 261} 262 263func TestSchemaAnalysis_BaseType(t *testing.T) { 264 cl := (&spec.Schema{}).Typed("object", "").SetProperty("type", *spec.StringProperty()).WithDiscriminator("type") 265 266 sch, err := Schema(SchemaOpts{Schema: cl}) 267 if assert.NoError(t, err) { 268 assert.True(t, sch.IsBaseType) 269 assert.False(t, sch.IsKnownType) 270 assert.False(t, sch.IsSimpleSchema) 271 } 272} 273 274func TestSchemaAnalysis_SimpleSchema(t *testing.T) { 275 for i, v := range append(knownSchemas, spec.ArrayProperty(nil), spec.MapProperty(nil)) { 276 sch, err := Schema(SchemaOpts{Schema: v}) 277 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 278 assert.True(t, sch.IsSimpleSchema, "item at %d should be a simple schema", i) 279 } 280 281 asch, err := Schema(SchemaOpts{Schema: spec.ArrayProperty(v)}) 282 if assert.NoError(t, err, "failed to analyze array schema at %d: %v", i, err) { 283 assert.True(t, asch.IsSimpleSchema, "array item at %d should be a simple schema", i) 284 } 285 286 msch, err := Schema(SchemaOpts{Schema: spec.MapProperty(v)}) 287 if assert.NoError(t, err, "failed to analyze map schema at %d: %v", i, err) { 288 assert.True(t, msch.IsSimpleSchema, "map item at %d should be a simple schema", i) 289 } 290 } 291 292 for i, v := range complexSchemas { 293 sch, err := Schema(SchemaOpts{Schema: v}) 294 if assert.NoError(t, err, "failed to analyze schema at %d: %v", i, err) { 295 assert.False(t, sch.IsSimpleSchema, "item at %d should not be a simple schema", i) 296 } 297 } 298} 299 300func TestSchemaAnalys_InvalidSchema(t *testing.T) { 301 // explore error cases in schema analysis: 302 // the only cause for failure is a wrong $ref at some place 303 bp := filepath.Join("fixtures", "bugs", "1602", "other-invalid-pointers.yaml") 304 sp := loadOrFail(t, bp) 305 306 // invalid ref not detected (no digging further) 307 def := sp.Definitions["invalidRefInObject"] 308 _, err := Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp}) 309 assert.NoError(t, err, "did not expect an error here, in spite of the underlying invalid $ref") 310 311 def = sp.Definitions["invalidRefInTuple"] 312 _, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp}) 313 assert.NoError(t, err, "did not expect an error here, in spite of the underlying invalid $ref") 314 315 // invalid ref detected (digging) 316 schema := refSchema(spec.MustCreateRef("#/definitions/noWhere")) 317 _, err = Schema(SchemaOpts{Schema: schema, Root: sp, BasePath: bp}) 318 assert.Error(t, err, "expected an error here") 319 320 def = sp.Definitions["invalidRefInMap"] 321 _, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp}) 322 assert.Error(t, err, "expected an error here") 323 324 def = sp.Definitions["invalidRefInArray"] 325 _, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp}) 326 assert.Error(t, err, "expected an error here") 327 328 def = sp.Definitions["indirectToInvalidRef"] 329 _, err = Schema(SchemaOpts{Schema: &def, Root: sp, BasePath: bp}) 330 assert.Error(t, err, "expected an error here") 331 332 // bbb, _ := json.MarshalIndent(def, "", " ") 333 // log.Printf(string(bbb)) 334} 335 336func TestSchemaAnalysis_EdgeCases(t *testing.T) { 337 _, err := Schema(SchemaOpts{Schema: nil}) 338 assert.Error(t, err) 339} 340