github.com/shogo82148/goa-v1@v1.6.2/goagen/gen_swagger/swagger_test.go (about) 1 package genswagger_test 2 3 import ( 4 "bytes" 5 "encoding/json" 6 7 "github.com/go-openapi/loads" 8 . "github.com/onsi/ginkgo" 9 . "github.com/onsi/gomega" 10 "github.com/shogo82148/goa-v1/design" 11 "github.com/shogo82148/goa-v1/design/apidsl" 12 "github.com/shogo82148/goa-v1/dslengine" 13 genschema "github.com/shogo82148/goa-v1/goagen/gen_schema" 14 genswagger "github.com/shogo82148/goa-v1/goagen/gen_swagger" 15 _ "github.com/shogo82148/goa-v1/goagen/gen_swagger/internal/design" 16 ) 17 18 // validateSwagger validates that the given swagger object represents a valid Swagger spec. 19 func validateSwagger(swagger *genswagger.Swagger) { 20 b, err := json.Marshal(swagger) 21 Ω(err).ShouldNot(HaveOccurred()) 22 doc, err := loads.Analyzed(json.RawMessage(b), "") 23 Ω(err).ShouldNot(HaveOccurred()) 24 Ω(doc).ShouldNot(BeNil()) 25 } 26 27 // validateSwaggerWithFragments validates that the given swagger object represents a valid Swagger spec 28 // and contains fragments 29 func validateSwaggerWithFragments(swagger *genswagger.Swagger, fragments [][]byte) { 30 b, err := json.Marshal(swagger) 31 Ω(err).ShouldNot(HaveOccurred()) 32 doc, err := loads.Analyzed(json.RawMessage(b), "") 33 Ω(err).ShouldNot(HaveOccurred()) 34 Ω(doc).ShouldNot(BeNil()) 35 for _, sub := range fragments { 36 Ω(bytes.Contains(b, sub)).Should(BeTrue()) 37 } 38 } 39 40 var _ = Describe("New", func() { 41 var swagger *genswagger.Swagger 42 var newErr error 43 44 BeforeEach(func() { 45 swagger = nil 46 newErr = nil 47 dslengine.Reset() 48 genschema.Definitions = make(map[string]*genschema.JSONSchema) 49 }) 50 51 JustBeforeEach(func() { 52 err := dslengine.Run() 53 Ω(err).ShouldNot(HaveOccurred()) 54 swagger, newErr = genswagger.New(design.Design) 55 }) 56 57 Context("with a valid API definition", func() { 58 const ( 59 title = "title" 60 description = "description" 61 terms = "terms" 62 contactEmail = "contactEmail@goa.design" 63 contactName = "contactName" 64 contactURL = "http://contactURL.com" 65 license = "license" 66 licenseURL = "http://licenseURL.com" 67 host = "host" 68 scheme = "https" 69 basePath = "/base" 70 tag = "tag" 71 docDesc = "doc description" 72 docURL = "http://docURL.com" 73 ) 74 75 BeforeEach(func() { 76 apidsl.API("test", func() { 77 apidsl.Title(title) 78 apidsl.Metadata("swagger:tag:" + tag) 79 apidsl.Metadata("swagger:tag:"+tag+":desc", "Tag desc.") 80 apidsl.Metadata("swagger:tag:"+tag+":url", "http://example.com/tag") 81 apidsl.Metadata("swagger:tag:"+tag+":url:desc", "Huge docs") 82 apidsl.Description(description) 83 apidsl.TermsOfService(terms) 84 apidsl.Contact(func() { 85 apidsl.Email(contactEmail) 86 apidsl.Name(contactName) 87 apidsl.URL(contactURL) 88 }) 89 apidsl.License(func() { 90 apidsl.Name(license) 91 apidsl.URL(licenseURL) 92 }) 93 apidsl.Docs(func() { 94 apidsl.Description(docDesc) 95 apidsl.URL(docURL) 96 }) 97 apidsl.Host(host) 98 apidsl.Scheme(scheme) 99 apidsl.BasePath(basePath) 100 }) 101 }) 102 103 It("sets all the basic fields", func() { 104 Ω(newErr).ShouldNot(HaveOccurred()) 105 Ω(swagger).Should(Equal(&genswagger.Swagger{ 106 Swagger: "2.0", 107 Info: &genswagger.Info{ 108 Title: title, 109 Description: description, 110 TermsOfService: terms, 111 Contact: &design.ContactDefinition{ 112 Name: contactName, 113 Email: contactEmail, 114 URL: contactURL, 115 }, 116 License: &design.LicenseDefinition{ 117 Name: license, 118 URL: licenseURL, 119 }, 120 Version: "", 121 }, 122 Host: host, 123 BasePath: basePath, 124 Schemes: []string{"https"}, 125 Paths: make(map[string]interface{}), 126 Consumes: []string{"application/json", "application/xml", "application/gob", "application/x-gob"}, 127 Produces: []string{"application/json", "application/xml", "application/gob", "application/x-gob"}, 128 Tags: []*genswagger.Tag{{Name: tag, Description: "Tag desc.", ExternalDocs: &genswagger.ExternalDocs{ 129 URL: "http://example.com/tag", Description: "Huge docs", 130 }}}, 131 ExternalDocs: &genswagger.ExternalDocs{ 132 Description: docDesc, 133 URL: docURL, 134 }, 135 })) 136 }) 137 138 It("serializes into valid swagger JSON", func() { validateSwagger(swagger) }) 139 140 Context("with base params", func() { 141 const ( 142 basePath = "/s/:strParam/i/:intParam/n/:numParam/b/:boolParam" 143 strParam = "strParam" 144 intParam = "intParam" 145 numParam = "numParam" 146 boolParam = "boolParam" 147 queryParam = "queryParam" 148 description = "description" 149 intMin = 1.0 150 floatMax = 2.4 151 enum1 = "enum1" 152 enum2 = "enum2" 153 ) 154 155 BeforeEach(func() { 156 base := design.Design.DSLFunc 157 design.Design.DSLFunc = func() { 158 base() 159 apidsl.BasePath(basePath) 160 apidsl.Params(func() { 161 apidsl.Param(strParam, design.String, func() { 162 apidsl.Description(description) 163 apidsl.Format("email") 164 }) 165 apidsl.Param(intParam, design.Integer, func() { 166 apidsl.Minimum(intMin) 167 }) 168 apidsl.Param(numParam, design.Number, func() { 169 apidsl.Maximum(floatMax) 170 }) 171 apidsl.Param(boolParam, design.Boolean) 172 apidsl.Param(queryParam, func() { 173 apidsl.Enum(enum1, enum2) 174 }) 175 }) 176 } 177 }) 178 179 It("sets the BasePath and Parameters fields", func() { 180 Ω(newErr).ShouldNot(HaveOccurred()) 181 Ω(swagger.BasePath).Should(Equal(basePath)) 182 Ω(swagger.Parameters).Should(HaveLen(5)) 183 Ω(swagger.Parameters[strParam]).ShouldNot(BeNil()) 184 Ω(swagger.Parameters[strParam].Name).Should(Equal(strParam)) 185 Ω(swagger.Parameters[strParam].In).Should(Equal("path")) 186 Ω(swagger.Parameters[strParam].Description).Should(Equal("description")) 187 Ω(swagger.Parameters[strParam].Required).Should(BeTrue()) 188 Ω(swagger.Parameters[strParam].Type).Should(Equal("string")) 189 Ω(swagger.Parameters[strParam].Format).Should(Equal("email")) 190 Ω(swagger.Parameters[intParam]).ShouldNot(BeNil()) 191 Ω(swagger.Parameters[intParam].Name).Should(Equal(intParam)) 192 Ω(swagger.Parameters[intParam].In).Should(Equal("path")) 193 Ω(swagger.Parameters[intParam].Required).Should(BeTrue()) 194 Ω(swagger.Parameters[intParam].Type).Should(Equal("integer")) 195 Ω(*swagger.Parameters[intParam].Minimum).Should(Equal(intMin)) 196 Ω(swagger.Parameters[numParam]).ShouldNot(BeNil()) 197 Ω(swagger.Parameters[numParam].Name).Should(Equal(numParam)) 198 Ω(swagger.Parameters[numParam].In).Should(Equal("path")) 199 Ω(swagger.Parameters[numParam].Required).Should(BeTrue()) 200 Ω(swagger.Parameters[numParam].Type).Should(Equal("number")) 201 Ω(*swagger.Parameters[numParam].Maximum).Should(Equal(floatMax)) 202 Ω(swagger.Parameters[boolParam]).ShouldNot(BeNil()) 203 Ω(swagger.Parameters[boolParam].Name).Should(Equal(boolParam)) 204 Ω(swagger.Parameters[boolParam].In).Should(Equal("path")) 205 Ω(swagger.Parameters[boolParam].Required).Should(BeTrue()) 206 Ω(swagger.Parameters[boolParam].Type).Should(Equal("boolean")) 207 Ω(swagger.Parameters[queryParam]).ShouldNot(BeNil()) 208 Ω(swagger.Parameters[queryParam].Name).Should(Equal(queryParam)) 209 Ω(swagger.Parameters[queryParam].In).Should(Equal("query")) 210 Ω(swagger.Parameters[queryParam].Type).Should(Equal("string")) 211 Ω(swagger.Parameters[queryParam].Enum).Should(Equal([]interface{}{enum1, enum2})) 212 }) 213 214 It("serializes into valid swagger JSON", func() { validateSwagger(swagger) }) 215 }) 216 217 Context("with required payload", func() { 218 BeforeEach(func() { 219 p := apidsl.Type("RequiredPayload", func() { 220 apidsl.Member("m1", design.String) 221 }) 222 apidsl.Resource("res", func() { 223 apidsl.Action("act", func() { 224 apidsl.Routing( 225 apidsl.PUT("/"), 226 ) 227 apidsl.Payload(p) 228 }) 229 }) 230 }) 231 232 It("serializes into valid swagger JSON", func() { 233 validateSwaggerWithFragments(swagger, [][]byte{ 234 []byte(`"required":true`), 235 }) 236 }) 237 }) 238 239 Context("with a payload of type Any", func() { 240 BeforeEach(func() { 241 apidsl.Resource("res", func() { 242 apidsl.Action("act", func() { 243 apidsl.Routing( 244 apidsl.PUT("/"), 245 ) 246 apidsl.Payload(design.Any, func() { 247 apidsl.Example("example") 248 }) 249 }) 250 }) 251 }) 252 253 It("serializes into valid swagger JSON", func() { 254 validateSwaggerWithFragments(swagger, [][]byte{ 255 []byte(`"ActResPayload":{"title":"ActResPayload","example":"example"}`), 256 }) 257 }) 258 259 }) 260 261 Context("with optional payload", func() { 262 BeforeEach(func() { 263 p := apidsl.Type("OptionalPayload", func() { 264 apidsl.Member("m1", design.String) 265 }) 266 apidsl.Resource("res", func() { 267 apidsl.Action("act", func() { 268 apidsl.Routing( 269 apidsl.PUT("/"), 270 ) 271 apidsl.OptionalPayload(p) 272 }) 273 }) 274 }) 275 276 It("serializes into valid swagger JSON", func() { 277 validateSwaggerWithFragments(swagger, [][]byte{ 278 []byte(`"required":false`), 279 }) 280 }) 281 282 }) 283 284 Context("with multipart/form-data payload", func() { 285 BeforeEach(func() { 286 f := apidsl.Type("MultipartPayload", func() { 287 apidsl.Attribute("image", design.File, "Binary image data") 288 }) 289 apidsl.Resource("res", func() { 290 apidsl.Action("act", func() { 291 apidsl.Routing( 292 apidsl.PUT("/"), 293 ) 294 apidsl.MultipartForm() 295 apidsl.Payload(f) 296 }) 297 }) 298 }) 299 300 It("does not modify the API level consumes", func() { 301 Ω(newErr).ShouldNot(HaveOccurred()) 302 Ω(swagger.Consumes).Should(HaveLen(4)) 303 Ω(swagger.Consumes).Should(ConsistOf("application/json", "application/xml", "application/gob", "application/x-gob")) 304 }) 305 306 It("adds an Action level consumes for multipart/form-data", func() { 307 Ω(newErr).ShouldNot(HaveOccurred()) 308 Ω(swagger.Paths).Should(HaveLen(1)) 309 Ω(swagger.Paths["/"]).ShouldNot(BeNil()) 310 311 a := swagger.Paths["/"].(*genswagger.Path) 312 Ω(a.Put).ShouldNot(BeNil()) 313 cs := a.Put.Consumes 314 Ω(cs).Should(HaveLen(1)) 315 Ω(cs[0]).Should(Equal("multipart/form-data")) 316 }) 317 318 It("adds an File parameter", func() { 319 Ω(newErr).ShouldNot(HaveOccurred()) 320 Ω(swagger.Paths).Should(HaveLen(1)) 321 Ω(swagger.Paths["/"]).ShouldNot(BeNil()) 322 323 a := swagger.Paths["/"].(*genswagger.Path) 324 Ω(a.Put).ShouldNot(BeNil()) 325 ps := a.Put.Parameters 326 Ω(ps).Should(HaveLen(1)) 327 Ω(ps[0]).Should(Equal(&genswagger.Parameter{In: "formData", Name: "image", Type: "file", Description: "Binary image data", Required: false})) 328 }) 329 330 It("serializes into valid swagger JSON", func() { validateSwagger(swagger) }) 331 }) 332 333 Context("with recursive payload", func() { 334 BeforeEach(func() { 335 p := apidsl.Type("RecursivePayload", func() { 336 apidsl.Member("m1", "RecursivePayload") 337 apidsl.Member("m2", apidsl.ArrayOf("RecursivePayload")) 338 apidsl.Member("m3", apidsl.HashOf(design.String, "RecursivePayload")) 339 apidsl.Member("m4", func() { 340 apidsl.Member("m5", design.String) 341 apidsl.Member("m6", "RecursivePayload") 342 }) 343 }) 344 apidsl.Resource("res", func() { 345 apidsl.Action("act", func() { 346 apidsl.Routing( 347 apidsl.PUT("/"), 348 ) 349 apidsl.Payload(p) 350 }) 351 }) 352 }) 353 354 It("serializes into valid swagger JSON", func() { validateSwagger(swagger) }) 355 }) 356 357 Context("with zero value validations", func() { 358 const ( 359 intParam = "intParam" 360 numParam = "numParam" 361 strParam = "strParam" 362 intMin = 0.0 363 floatMax = 0.0 364 ) 365 366 BeforeEach(func() { 367 PayloadWithZeroValueValidations := apidsl.Type("PayloadWithZeroValueValidations", func() { 368 apidsl.Attribute(strParam, design.String, func() { 369 apidsl.MinLength(0) 370 apidsl.MaxLength(0) 371 }) 372 }) 373 apidsl.Resource("res", func() { 374 apidsl.Action("act", func() { 375 apidsl.Routing( 376 apidsl.PUT("/"), 377 ) 378 apidsl.Params(func() { 379 apidsl.Param(intParam, design.Integer, func() { 380 apidsl.Minimum(intMin) 381 }) 382 apidsl.Param(numParam, design.Number, func() { 383 apidsl.Maximum(floatMax) 384 }) 385 }) 386 apidsl.Payload(PayloadWithZeroValueValidations) 387 }) 388 }) 389 }) 390 391 It("serializes into valid swagger JSON", func() { 392 validateSwaggerWithFragments(swagger, [][]byte{ 393 // payload 394 []byte(`"minLength":0`), 395 []byte(`"maxLength":0`), 396 // param 397 []byte(`"minimum":0`), 398 []byte(`"maximum":0`), 399 }) 400 }) 401 }) 402 403 Context("with minItems and maxItems validations in payload's attribute", func() { 404 const ( 405 arrParam = "arrParam" 406 minVal = 0 407 maxVal = 42 408 ) 409 410 BeforeEach(func() { 411 PayloadWithValidations := apidsl.Type("Payload", func() { 412 apidsl.Attribute(arrParam, apidsl.ArrayOf(design.String), func() { 413 apidsl.MinLength(minVal) 414 apidsl.MaxLength(maxVal) 415 }) 416 }) 417 apidsl.Resource("res", func() { 418 apidsl.Action("act", func() { 419 apidsl.Routing( 420 apidsl.PUT("/"), 421 ) 422 apidsl.Payload(PayloadWithValidations) 423 }) 424 }) 425 }) 426 427 It("serializes into valid swagger JSON", func() { 428 validateSwaggerWithFragments(swagger, [][]byte{ 429 // payload 430 []byte(`"minItems":0`), 431 []byte(`"maxItems":42`), 432 }) 433 }) 434 }) 435 436 Context("with minItems and maxItems validations in payload", func() { 437 const ( 438 strParam = "strParam" 439 minVal = 0 440 maxVal = 42 441 ) 442 443 BeforeEach(func() { 444 PayloadWithValidations := apidsl.Type("Payload", func() { 445 apidsl.Attribute(strParam, design.String) 446 }) 447 apidsl.Resource("res", func() { 448 apidsl.Action("act", func() { 449 apidsl.Routing( 450 apidsl.PUT("/"), 451 ) 452 apidsl.Payload(apidsl.ArrayOf(PayloadWithValidations), func() { 453 apidsl.MinLength(minVal) 454 apidsl.MaxLength(maxVal) 455 }) 456 }) 457 }) 458 }) 459 460 It("serializes into valid swagger JSON", func() { 461 validateSwaggerWithFragments(swagger, [][]byte{ 462 // payload 463 []byte(`"minItems":0`), 464 []byte(`"maxItems":42`), 465 }) 466 }) 467 }) 468 469 Context("with response templates", func() { 470 const okName = "OK" 471 const okDesc = "OK description" 472 const notFoundName = "NotFound" 473 const notFoundDesc = "NotFound description" 474 const notFoundMt = "application/json" 475 const headerName = "headerName" 476 477 BeforeEach(func() { 478 account := apidsl.MediaType("application/vnd.goa.test.account", func() { 479 apidsl.Description("Account") 480 apidsl.Attributes(func() { 481 apidsl.Attribute("id", design.Integer) 482 apidsl.Attribute("href", design.String) 483 }) 484 apidsl.View("default", func() { 485 apidsl.Attribute("id") 486 apidsl.Attribute("href") 487 }) 488 apidsl.View("link", func() { 489 apidsl.Attribute("id") 490 apidsl.Attribute("href") 491 }) 492 }) 493 mt := apidsl.MediaType("application/vnd.goa.test.bottle", func() { 494 apidsl.Description("A bottle of wine") 495 apidsl.Attributes(func() { 496 apidsl.Attribute("id", design.Integer, "ID of bottle") 497 apidsl.Attribute("href", design.String, "API href of bottle") 498 apidsl.Attribute("account", account, "Owner account") 499 apidsl.Links(func() { 500 apidsl.Link("account") // Defines a link to the Account media type 501 }) 502 apidsl.Required("id", "href") 503 }) 504 apidsl.View("default", func() { 505 apidsl.Attribute("id") 506 apidsl.Attribute("href") 507 apidsl.Attribute("links") // Default view renders links 508 }) 509 apidsl.View("extended", func() { 510 apidsl.Attribute("id") 511 apidsl.Attribute("href") 512 apidsl.Attribute("account") // Extended view renders account inline 513 apidsl.Attribute("links") // Extended view also renders links 514 }) 515 }) 516 base := design.Design.DSLFunc 517 design.Design.DSLFunc = func() { 518 base() 519 apidsl.ResponseTemplate(okName, func() { 520 apidsl.Description(okDesc) 521 apidsl.Status(404) 522 apidsl.Media(mt) 523 apidsl.Headers(func() { 524 apidsl.Header(headerName, func() { 525 apidsl.Format("hostname") 526 }) 527 }) 528 }) 529 apidsl.ResponseTemplate(notFoundName, func() { 530 apidsl.Description(notFoundDesc) 531 apidsl.Status(404) 532 533 apidsl.Media(notFoundMt) 534 }) 535 } 536 }) 537 538 It("sets the Responses fields", func() { 539 Ω(newErr).ShouldNot(HaveOccurred()) 540 Ω(swagger.Responses).Should(HaveLen(2)) 541 Ω(swagger.Responses[notFoundName]).ShouldNot(BeNil()) 542 Ω(swagger.Responses[notFoundName].Description).Should(Equal(notFoundDesc)) 543 Ω(swagger.Responses[okName]).ShouldNot(BeNil()) 544 Ω(swagger.Responses[okName].Description).Should(Equal(okDesc)) 545 }) 546 547 It("serializes into valid swagger JSON", func() { validateSwagger(swagger) }) 548 }) 549 550 Context("with resources", func() { 551 var ( 552 minLength1 = 1 553 maxLength10 = 10 554 minimum_2 = -2.0 555 maximum2 = 2.0 556 minItems1 = 1 557 maxItems5 = 5 558 ) 559 BeforeEach(func() { 560 Country := apidsl.MediaType("application/vnd.goa.example.origin", func() { 561 apidsl.Description("Origin of bottle") 562 apidsl.Attributes(func() { 563 apidsl.Attribute("id") 564 apidsl.Attribute("href") 565 apidsl.Attribute("country") 566 }) 567 apidsl.View("default", func() { 568 apidsl.Attribute("id") 569 apidsl.Attribute("href") 570 apidsl.Attribute("country") 571 }) 572 apidsl.View("tiny", func() { 573 apidsl.Attribute("id") 574 }) 575 }) 576 BottleMedia := apidsl.MediaType("application/vnd.goa.example.bottle", func() { 577 apidsl.Description("A bottle of wine") 578 apidsl.Attributes(func() { 579 apidsl.Attribute("id", design.Integer, "ID of bottle") 580 apidsl.Attribute("href", design.String, "API href of bottle") 581 apidsl.Attribute("origin", Country, "Details on wine origin") 582 apidsl.Links(func() { 583 apidsl.Link("origin", "tiny") 584 }) 585 apidsl.Required("id", "href") 586 }) 587 apidsl.View("default", func() { 588 apidsl.Attribute("id") 589 apidsl.Attribute("href") 590 apidsl.Attribute("links") 591 }) 592 apidsl.View("extended", func() { 593 apidsl.Attribute("id") 594 apidsl.Attribute("href") 595 apidsl.Attribute("origin") 596 apidsl.Attribute("links") 597 }) 598 }) 599 UpdatePayload := apidsl.Type("UpdatePayload", func() { 600 apidsl.Description("Type of create and upload action payloads") 601 apidsl.Attribute("name", design.String, "name of bottle") 602 apidsl.Attribute("origin", Country, "Details on wine origin") 603 apidsl.Required("name") 604 }) 605 apidsl.Resource("res", func() { 606 apidsl.Metadata("swagger:tag:res") 607 apidsl.Description("A wine bottle") 608 apidsl.DefaultMedia(BottleMedia) 609 apidsl.BasePath("/bottles") 610 apidsl.UseTrait("Authenticated") 611 612 apidsl.Action("Update", func() { 613 apidsl.Metadata("swagger:tag:Update") 614 apidsl.Metadata("swagger:summary", "a summary") 615 apidsl.Description("Update account") 616 apidsl.Docs(func() { 617 apidsl.Description("docs") 618 apidsl.URL("http://cellarapi.com/docs/actions/update") 619 }) 620 apidsl.Routing( 621 apidsl.PUT("/:id"), 622 apidsl.PUT("//orgs/:org/accounts/:id"), 623 ) 624 apidsl.Params(func() { 625 apidsl.Param("org", design.String) 626 apidsl.Param("id", design.Integer) 627 apidsl.Param("sort", func() { 628 apidsl.Enum("asc", "desc") 629 }) 630 }) 631 apidsl.Headers(func() { 632 apidsl.Header("Authorization", design.String) 633 apidsl.Header("X-Account", design.Integer) 634 apidsl.Header("OptionalBoolWithDefault", design.Boolean, "defaults true", func() { 635 apidsl.Default(true) 636 }) 637 apidsl.Header("OptionalRegex", design.String, func() { 638 apidsl.Pattern(`[a-z]\d+`) 639 apidsl.MinLength(minLength1) 640 apidsl.MaxLength(maxLength10) 641 }) 642 apidsl.Header("OptionalInt", design.Integer, func() { 643 apidsl.Minimum(minimum_2) 644 apidsl.Maximum(maximum2) 645 }) 646 apidsl.Header("OptionalArray", apidsl.ArrayOf(design.String), func() { 647 // interpreted as MinItems & MaxItems: 648 apidsl.MinLength(minItems1) 649 apidsl.MaxLength(maxItems5) 650 }) 651 apidsl.Header("OverrideRequiredHeader") 652 apidsl.Header("OverrideOptionalHeader") 653 apidsl.Required("Authorization", "X-Account", "OverrideOptionalHeader") 654 }) 655 apidsl.Payload(UpdatePayload) 656 apidsl.Response(design.OK, func() { 657 apidsl.Media(apidsl.CollectionOf(BottleMedia), "extended") 658 }) 659 apidsl.Response(design.NoContent) 660 apidsl.Response(design.NotFound, design.ErrorMedia) 661 apidsl.Response(design.BadRequest, design.ErrorMedia) 662 }) 663 664 apidsl.Action("hidden", func() { 665 apidsl.Description("Does not show up in Swagger spec") 666 apidsl.Metadata("swagger:generate", "false") 667 apidsl.Routing(apidsl.GET("/hidden")) 668 apidsl.Response(design.OK) 669 }) 670 }) 671 base := design.Design.DSLFunc 672 design.Design.DSLFunc = func() { 673 base() 674 apidsl.Trait("Authenticated", func() { 675 apidsl.Headers(func() { 676 apidsl.Header("header") 677 apidsl.Header("OverrideRequiredHeader", design.String, "to be overridden in Action and not marked Required") 678 apidsl.Header("OverrideOptionalHeader", design.String, "to be overridden in Action and marked Required") 679 apidsl.Header("OptionalResourceHeaderWithEnum", func() { 680 apidsl.Enum("a", "b") 681 }) 682 apidsl.Required("header", "OverrideRequiredHeader") 683 }) 684 }) 685 } 686 }) 687 688 It("sets the Path fields", func() { 689 Ω(newErr).ShouldNot(HaveOccurred()) 690 Ω(swagger.Paths).Should(HaveLen(2)) 691 Ω(swagger.Paths["/orgs/{org}/accounts/{id}"]).ShouldNot(BeNil()) 692 a := swagger.Paths["/orgs/{org}/accounts/{id}"].(*genswagger.Path) 693 Ω(a.Put).ShouldNot(BeNil()) 694 ps := a.Put.Parameters 695 Ω(ps).Should(HaveLen(14)) 696 // check Headers in detail 697 Ω(ps[3]).Should(Equal(&genswagger.Parameter{In: "header", Name: "Authorization", Type: "string", Required: true})) 698 Ω(ps[4]).Should(Equal(&genswagger.Parameter{In: "header", Name: "OptionalArray", Type: "array", CollectionFormat: "multi", 699 Items: &genswagger.Items{Type: "string"}, MinItems: &minItems1, MaxItems: &maxItems5})) 700 Ω(ps[5]).Should(Equal(&genswagger.Parameter{In: "header", Name: "OptionalBoolWithDefault", Type: "boolean", 701 Description: "defaults true", Default: true})) 702 Ω(ps[6]).Should(Equal(&genswagger.Parameter{In: "header", Name: "OptionalInt", Type: "integer", Minimum: &minimum_2, Maximum: &maximum2})) 703 Ω(ps[7]).Should(Equal(&genswagger.Parameter{In: "header", Name: "OptionalRegex", Type: "string", 704 Pattern: `[a-z]\d+`, MinLength: &minLength1, MaxLength: &maxLength10})) 705 Ω(ps[8]).Should(Equal(&genswagger.Parameter{In: "header", Name: "OptionalResourceHeaderWithEnum", Type: "string", 706 Enum: []interface{}{"a", "b"}})) 707 Ω(ps[9]).Should(Equal(&genswagger.Parameter{In: "header", Name: "OverrideOptionalHeader", Type: "string", Required: true})) 708 Ω(ps[10]).Should(Equal(&genswagger.Parameter{In: "header", Name: "OverrideRequiredHeader", Type: "string", Required: true})) 709 Ω(ps[11]).Should(Equal(&genswagger.Parameter{In: "header", Name: "X-Account", Type: "integer", Required: true})) 710 Ω(ps[12]).Should(Equal(&genswagger.Parameter{In: "header", Name: "header", Type: "string", Required: true})) 711 Ω(swagger.Paths["/base/bottles/{id}"]).ShouldNot(BeNil()) 712 b := swagger.Paths["/base/bottles/{id}"].(*genswagger.Path) 713 Ω(b.Put).ShouldNot(BeNil()) 714 Ω(b.Put.Parameters).Should(HaveLen(14)) 715 Ω(b.Put.Produces).Should(Equal([]string{"application/vnd.goa.error", "application/vnd.goa.example.bottle; type=collection"})) 716 }) 717 718 It("should set the inherited tag and the action tag", func() { 719 tags := []string{"res", "Update"} 720 a := swagger.Paths["/orgs/{org}/accounts/{id}"].(*genswagger.Path) 721 Ω(a.Put).ShouldNot(BeNil()) 722 Ω(a.Put.Tags).Should(Equal(tags)) 723 b := swagger.Paths["/base/bottles/{id}"].(*genswagger.Path) 724 Ω(b.Put.Tags).Should(Equal(tags)) 725 }) 726 727 It("sets the summary from the summary tag", func() { 728 a := swagger.Paths["/orgs/{org}/accounts/{id}"].(*genswagger.Path) 729 Ω(a.Put.Summary).Should(Equal("a summary")) 730 }) 731 732 It("generates the media type collection schema", func() { 733 Ω(swagger.Definitions).Should(HaveLen(7)) 734 Ω(swagger.Definitions).Should(HaveKey("GoaExampleBottleExtendedCollection")) 735 }) 736 737 It("serializes into valid swagger JSON", func() { validateSwagger(swagger) }) 738 }) 739 740 Context("with metadata", func() { 741 const gat = "gat" 742 const extension = `{"foo":"bar"}` 743 const stringExtension = "foo" 744 745 var ( 746 unmarshaled map[string]interface{} 747 _ = json.Unmarshal([]byte(extension), &unmarshaled) 748 ) 749 750 BeforeEach(func() { 751 apidsl.Resource("res", func() { 752 apidsl.Metadata("swagger:tag:res") 753 apidsl.Metadata("struct:tag:json", "resource") 754 apidsl.Metadata("swagger:extension:x-resource", extension) 755 apidsl.Metadata("swagger:extension:x-string", stringExtension) 756 apidsl.Action("act", func() { 757 apidsl.Metadata("swagger:tag:Update") 758 apidsl.Metadata("struct:tag:json", "action") 759 apidsl.Metadata("swagger:extension:x-action", extension) 760 apidsl.Security("password", func() { 761 apidsl.Metadata("swagger:extension:x-security", extension) 762 }) 763 apidsl.Routing( 764 apidsl.PUT("/", func() { 765 apidsl.Metadata("swagger:extension:x-put", extension) 766 }), 767 ) 768 apidsl.Params(func() { 769 apidsl.Param("param", func() { 770 apidsl.Metadata("swagger:extension:x-param", extension) 771 }) 772 }) 773 apidsl.Response(design.NoContent, func() { 774 apidsl.Metadata("swagger:extension:x-response", extension) 775 }) 776 }) 777 }) 778 base := design.Design.DSLFunc 779 design.Design.DSLFunc = func() { 780 base() 781 apidsl.Metadata("swagger:tag:" + gat) 782 apidsl.Metadata("struct:tag:json", "api") 783 apidsl.Metadata("swagger:extension:x-api", extension) 784 apidsl.BasicAuthSecurity("password") 785 } 786 }) 787 788 It("should set the swagger object tags", func() { 789 Ω(swagger.Tags).Should(HaveLen(2)) 790 tags := []*genswagger.Tag{ 791 {Name: gat, Description: "", ExternalDocs: nil, Extensions: map[string]interface{}{"x-api": unmarshaled}}, 792 {Name: tag, Description: "Tag desc.", ExternalDocs: &genswagger.ExternalDocs{URL: "http://example.com/tag", Description: "Huge docs"}, Extensions: map[string]interface{}{"x-api": unmarshaled}}, 793 } 794 Ω(swagger.Tags).Should(Equal(tags)) 795 }) 796 797 It("should set the action tags", func() { 798 p := swagger.Paths["/"].(*genswagger.Path) 799 Ω(p.Put.Tags).Should(HaveLen(2)) 800 tags := []string{"res", "Update"} 801 Ω(p.Put.Tags).Should(Equal(tags)) 802 }) 803 804 It("should set the swagger extensions", func() { 805 Ω(swagger.Info.Extensions).Should(HaveLen(1)) 806 Ω(swagger.Info.Extensions["x-api"]).Should(Equal(unmarshaled)) 807 p := swagger.Paths["/"].(*genswagger.Path) 808 Ω(p.Extensions).Should(HaveLen(1)) 809 Ω(p.Extensions["x-action"]).Should(Equal(unmarshaled)) 810 Ω(p.Put.Extensions).Should(HaveLen(1)) 811 Ω(p.Put.Extensions["x-put"]).Should(Equal(unmarshaled)) 812 Ω(p.Put.Parameters[0].Extensions).Should(HaveLen(1)) 813 Ω(p.Put.Parameters[0].Extensions["x-param"]).Should(Equal(unmarshaled)) 814 Ω(p.Put.Responses["204"].Extensions).Should(HaveLen(1)) 815 Ω(p.Put.Responses["204"].Extensions["x-response"]).Should(Equal(unmarshaled)) 816 Ω(swagger.Paths["x-resource"]).ShouldNot(BeNil()) 817 rs := swagger.Paths["x-resource"].(map[string]interface{}) 818 Ω(rs).Should(Equal(unmarshaled)) 819 rs2 := swagger.Paths["x-string"].(string) 820 Ω(rs2).Should(Equal(stringExtension)) 821 Ω(swagger.SecurityDefinitions["password"].Extensions).Should(HaveLen(1)) 822 Ω(swagger.SecurityDefinitions["password"].Extensions["x-security"]).Should(Equal(unmarshaled)) 823 }) 824 825 }) 826 }) 827 })