├── LICENSE
├── README.md
├── app.go
├── app_test.go
├── context.go
├── context_test.go
├── decorators.go
├── error.go
├── examples
├── api
│ ├── README.md
│ ├── controllers
│ │ ├── error.go
│ │ └── movie.go
│ ├── main.go
│ └── models
│ │ └── movie.go
├── blog
│ ├── README.md
│ ├── controllers
│ │ └── root_controller.go
│ ├── main.go
│ ├── models
│ │ └── post.go
│ ├── static
│ │ ├── css
│ │ │ ├── blog-old-ie.css
│ │ │ └── blog.css
│ │ └── img
│ │ │ └── avatar.jpg
│ └── views
│ │ ├── index.html
│ │ └── partials
│ │ ├── _footer.html
│ │ └── _head.html
├── hello_world
│ ├── README.md
│ └── main.go
└── swagger
│ ├── README.md
│ ├── controllers
│ ├── movie.go
│ └── swagger.go
│ ├── main.go
│ ├── models
│ └── movie.go
│ └── static
│ └── swagger-ui
│ └── dist
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── index.html
│ ├── oauth2-redirect.html
│ ├── swagger-ui-bundle.js
│ ├── swagger-ui-bundle.js.map
│ ├── swagger-ui-standalone-preset.js
│ ├── swagger-ui-standalone-preset.js.map
│ ├── swagger-ui.css
│ ├── swagger-ui.css.map
│ ├── swagger-ui.js
│ └── swagger-ui.js.map
├── json.go
├── parser.go
├── parser_test.go
├── response.go
├── route.go
├── router.go
├── router_test.go
├── test_helpers.go
├── testing
├── index.html
└── partials
│ ├── _footer.html
│ └── _header.html
└── utilities.go
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2016, Zack Patrick
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fireball
2 |
3 | [](https://github.com/zpatrick/fireball/blob/master/LICENSE)
4 | [](https://goreportcard.com/report/github.com/zpatrick/fireball)
5 | [](https://godoc.org/github.com/zpatrick/fireball)
6 |
7 |
8 | ## Overview
9 | Fireball is a package for Go web applications.
10 | The primary goal of this package is to make routing, response writing, and error handling as easy as possible for developers, so they can focus more on their application logic, and less on repeated patterns.
11 |
12 | ## Installation
13 | To install this package, run:
14 | ```bash
15 | go get github.com/zpatrick/fireball
16 | ```
17 |
18 | ## Getting Started
19 | The following snipped shows a simple "Hello, World" application using Fireball:
20 | ```go
21 | package main
22 |
23 | import (
24 | "github.com/zpatrick/fireball"
25 | "net/http"
26 | )
27 |
28 | func index(c *fireball.Context) (fireball.Response, error) {
29 | return fireball.NewResponse(200, []byte("Hello, World!"), nil), nil
30 | }
31 |
32 | func main() {
33 | indexRoute := &fireball.Route{
34 | Path: "/",
35 | Handlers: fireball.Handlers{
36 | "GET": index,
37 | },
38 | }
39 |
40 | routes := []*fireball.Route{indexRoute}
41 | app := fireball.NewApp(routes)
42 | http.ListenAndServe(":8000", app)
43 | }
44 | ```
45 |
46 | This will run a new webserver at `localhost:8000`
47 |
48 | ## Handlers
49 | [Handlers](https://godoc.org/github.com/zpatrick/fireball#Handler) perform the business logic associated with requests.
50 | Handlers take a [Context](https://godoc.org/github.com/zpatrick/fireball#Context) object and returns either a [Response](https://godoc.org/github.com/zpatrick/fireball#Response) or an error.
51 |
52 | ### HTTP Response
53 | The [HTTP Response](https://godoc.org/github.com/zpatrick/fireball#HTTPResponse) is a simple object that implements the [Response](https://godoc.org/github.com/zpatrick/fireball#Response) interface.
54 | When the Write call is executed, the specified Body, Status, and Headers will be written to the http.ResponseWriter.
55 |
56 | Examples:
57 | ```go
58 | func Index(c *fireball.Context) (fireball.Response, error) {
59 | return fireball.NewResponse(200, []byte("Hello, World"), nil), nil
60 | }
61 | ```
62 |
63 | ```go
64 | func Index(c *fireball.Context) (fireball.Response, error) {
65 | html := []byte("
7 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{ with .PinnedPost }}
31 |
Pinned Post
32 |
33 |
34 |
35 |
44 |
45 |
46 |
47 | {{ .Body }}
48 |
49 |
50 |
51 |
52 | {{ end }}
53 |
54 |
55 |
Recent Posts
56 |
57 | {{ range .RecentPosts }}
58 |
59 |
68 |
69 |
70 |
71 | {{ .Body }}
72 |
73 |
74 |
75 | {{ end }}
76 |
77 | {{ template "partials/footer" }}
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/examples/blog/views/partials/_footer.html:
--------------------------------------------------------------------------------
1 | {{define "footer"}}
2 |
11 | {{end}}
12 |
--------------------------------------------------------------------------------
/examples/blog/views/partials/_head.html:
--------------------------------------------------------------------------------
1 | {{ define "head" }}
2 |
3 |
4 |
5 |
6 |
{{ . }}
7 |
8 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 | {{ end }}
22 |
--------------------------------------------------------------------------------
/examples/hello_world/README.md:
--------------------------------------------------------------------------------
1 | # Hello World
2 | This example application shows a basic "Hello, World" implementation using Fireball.
3 |
4 | ## Run Example
5 | From this directory, run:
6 | ```
7 | go run main.go
8 | ```
--------------------------------------------------------------------------------
/examples/hello_world/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net/http"
6 |
7 | "github.com/zpatrick/fireball"
8 | )
9 |
10 | func index(c *fireball.Context) (fireball.Response, error) {
11 | return fireball.NewResponse(200, []byte("Hello, World!"), nil), nil
12 | }
13 |
14 | func main() {
15 | indexRoute := &fireball.Route{
16 | Path: "/",
17 | Handlers: fireball.Handlers{
18 | "GET": index,
19 | },
20 | }
21 |
22 | routes := []*fireball.Route{indexRoute}
23 | app := fireball.NewApp(routes)
24 |
25 | log.Println("Running on port 8000")
26 | log.Fatal(http.ListenAndServe(":8000", app))
27 | }
28 |
--------------------------------------------------------------------------------
/examples/swagger/README.md:
--------------------------------------------------------------------------------
1 | # Swagger
2 | This example application adds [Swagger](http://swagger.io/) documentation to the [API example](https://github.com/zpatrick/fireball/tree/master/examples/api).
3 |
4 | ## Run Example
5 | From this directory, run:
6 | ```
7 | go run main.go
8 | ```
9 |
10 | By default, if you navigate to `http://localhost:9090/api/`, it will serve Swagger's default [Petstore](http://petstore.swagger.io/) example.
11 | To use the local configuration, enter `http://localhost:9090/swagger.json` into the **Explore** box on the top right of the page,
12 | or navigate to `http://localhost:9090/api/?url=http://localhost:9090/swagger.json`
13 |
14 | ## Getting Swagger UI
15 | The Swagger UI in the `static/swagger-ui/dist` directory was cloned from the [Swagger UI Repo](https://github.com/swagger-api/swagger-ui/tree/master/dist).
16 | The `dist` directory holds the required files to needed to serve the Swagger UI.
17 |
--------------------------------------------------------------------------------
/examples/swagger/controllers/movie.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 |
7 | "github.com/zpatrick/fireball"
8 | "github.com/zpatrick/fireball/examples/swagger/models"
9 | )
10 |
11 | type MovieController struct {
12 | Movies map[string]models.Movie
13 | }
14 |
15 | func NewMovieController() *MovieController {
16 | return &MovieController{
17 | Movies: map[string]models.Movie{},
18 | }
19 | }
20 |
21 | func (m *MovieController) Routes() []*fireball.Route {
22 | routes := []*fireball.Route{
23 | {
24 | Path: "/movies",
25 | Handlers: fireball.Handlers{
26 | "GET": m.ListMovies,
27 | "POST": m.AddMovie,
28 | },
29 | },
30 | {
31 | Path: "/movies/:title",
32 | Handlers: fireball.Handlers{
33 | "GET": m.GetMovie,
34 | "DELETE": m.DeleteMovie,
35 | },
36 | },
37 | }
38 |
39 | return routes
40 | }
41 |
42 | func (m *MovieController) ListMovies(c *fireball.Context) (fireball.Response, error) {
43 | movies := []models.Movie{}
44 | for _, movie := range m.Movies {
45 | movies = append(movies, movie)
46 | }
47 |
48 | return fireball.NewJSONResponse(200, movies)
49 | }
50 |
51 | func (m *MovieController) AddMovie(c *fireball.Context) (fireball.Response, error) {
52 | var movie models.Movie
53 | if err := json.NewDecoder(c.Request.Body).Decode(&movie); err != nil {
54 | return nil, err
55 | }
56 |
57 | m.Movies[movie.Title] = movie
58 | return fireball.NewJSONResponse(200, movie)
59 | }
60 |
61 | func (m *MovieController) GetMovie(c *fireball.Context) (fireball.Response, error) {
62 | title := c.PathVariables["title"]
63 | movie, ok := m.Movies[title]
64 | if !ok {
65 | return nil, fmt.Errorf("Movie with title '%s' does not exist", title)
66 | }
67 |
68 | return fireball.NewJSONResponse(200, movie)
69 | }
70 |
71 | func (m *MovieController) DeleteMovie(c *fireball.Context) (fireball.Response, error) {
72 | title := c.PathVariables["title"]
73 | if _, ok := m.Movies[title]; !ok {
74 | return nil, fmt.Errorf("Movie with title '%s' does not exist", title)
75 | }
76 |
77 | delete(m.Movies, title)
78 | return fireball.NewJSONResponse(200, nil)
79 | }
80 |
--------------------------------------------------------------------------------
/examples/swagger/controllers/swagger.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/zpatrick/fireball"
5 | "github.com/zpatrick/fireball/examples/swagger/models"
6 | swagger "github.com/zpatrick/go-plugin-swagger"
7 | )
8 |
9 | type SwaggerController struct{}
10 |
11 | func NewSwaggerController() *SwaggerController {
12 | return &SwaggerController{}
13 | }
14 |
15 | func (s *SwaggerController) Routes() []*fireball.Route {
16 | routes := []*fireball.Route{
17 | {
18 | Path: "/swagger.json",
19 | Handlers: fireball.Handlers{
20 | "GET": s.ServeSwaggerSpec,
21 | },
22 | },
23 | }
24 |
25 | return routes
26 | }
27 |
28 | func (s *SwaggerController) ServeSwaggerSpec(c *fireball.Context) (fireball.Response, error) {
29 | spec := swagger.Spec{
30 | SwaggerVersion: "2.0",
31 | Schemes: []string{"http"},
32 | Info: &swagger.Info{
33 | Title: "Swagger Example",
34 | Version: "0.0.1",
35 | },
36 | Definitions: map[string]swagger.Definition{
37 | "Movie": models.Movie{}.Definition(),
38 | },
39 | Tags: []swagger.Tag{
40 | {
41 | Name: "Movies",
42 | Description: "Methods related to movies",
43 | },
44 | },
45 | Paths: map[string]swagger.Path{
46 | "/movies": map[string]swagger.Method{
47 | "get": {
48 | Summary: "List all Movies",
49 | Tags: []string{"Movies"},
50 | Responses: map[string]swagger.Response{
51 | "200": {
52 | Description: "An array of movies",
53 | Schema: swagger.NewObjectSliceSchema("Movie"),
54 | },
55 | },
56 | },
57 | "post": {
58 | Summary: "Add a Movie",
59 | Tags: []string{"Movies"},
60 | Parameters: []swagger.Parameter{
61 | swagger.NewBodyParam("Movie", "Movie to add", true),
62 | },
63 | Responses: map[string]swagger.Response{
64 | "200": {
65 | Description: "The added movie",
66 | Schema: swagger.NewObjectSchema("Movie"),
67 | },
68 | },
69 | },
70 | },
71 | "/movies/{title}": map[string]swagger.Method{
72 | "get": {
73 | Summary: "Describe a Movie",
74 | Tags: []string{"Movies"},
75 | Parameters: []swagger.Parameter{
76 | swagger.NewStringPathParam("title", "Title of the movie to describe", true),
77 | },
78 | Responses: map[string]swagger.Response{
79 | "200": {
80 | Description: "The desired movie",
81 | Schema: swagger.NewObjectSchema("Movie"),
82 | },
83 | },
84 | },
85 | "delete": {
86 | Summary: "Delete a Movie",
87 | Tags: []string{"Movies"},
88 | Parameters: []swagger.Parameter{
89 | swagger.NewStringPathParam("title", "Title of the movie to delete", true),
90 | },
91 | Responses: map[string]swagger.Response{
92 | "200": {
93 | Description: "Success",
94 | },
95 | },
96 | },
97 | },
98 | },
99 | }
100 |
101 | return fireball.NewJSONResponse(200, spec)
102 | }
103 |
--------------------------------------------------------------------------------
/examples/swagger/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net/http"
6 |
7 | "github.com/zpatrick/fireball"
8 | "github.com/zpatrick/fireball/examples/swagger/controllers"
9 | )
10 |
11 | const (
12 | SWAGGER_URL = "/api/"
13 | SWAGGER_UI_PATH = "static/swagger-ui/dist"
14 | )
15 |
16 | func serveSwaggerUI(w http.ResponseWriter, r *http.Request) {
17 | dir := http.Dir(SWAGGER_UI_PATH)
18 | fileServer := http.FileServer(dir)
19 | http.StripPrefix(SWAGGER_URL, fileServer).ServeHTTP(w, r)
20 | }
21 |
22 | func main() {
23 | http.HandleFunc(SWAGGER_URL, serveSwaggerUI)
24 |
25 | routes := controllers.NewSwaggerController().Routes()
26 | routes = append(routes, controllers.NewMovieController().Routes()...)
27 | routes = fireball.Decorate(routes,
28 | fireball.LogDecorator())
29 |
30 | app := fireball.NewApp(routes)
31 | http.Handle("/", app)
32 |
33 | log.Println("Running on port 9090")
34 | log.Fatal(http.ListenAndServe(":9090", nil))
35 | }
36 |
--------------------------------------------------------------------------------
/examples/swagger/models/movie.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/zpatrick/go-plugin-swagger"
5 | )
6 |
7 | type Movie struct {
8 | Title string `json:"title"`
9 | Year int `json:"year"`
10 | }
11 |
12 | func (m Movie) Definition() swagger.Definition {
13 | return swagger.Definition{
14 | Type: "object",
15 | Properties: map[string]swagger.Property{
16 | "title": swagger.NewStringProperty(),
17 | "year": swagger.NewIntProperty(),
18 | },
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/swagger/static/swagger-ui/dist/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ridgelines/fireball/f42d3a4bbbbc36f2d8cd1a13f0ee9365c96832d6/examples/swagger/static/swagger-ui/dist/favicon-16x16.png
--------------------------------------------------------------------------------
/examples/swagger/static/swagger-ui/dist/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ridgelines/fireball/f42d3a4bbbbc36f2d8cd1a13f0ee9365c96832d6/examples/swagger/static/swagger-ui/dist/favicon-32x32.png
--------------------------------------------------------------------------------
/examples/swagger/static/swagger-ui/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Swagger UI
7 |
8 |
9 |
10 |
11 |
30 |
31 |
32 |
33 |
34 |
67 |
68 |
69 |
70 |
71 |
72 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/examples/swagger/static/swagger-ui/dist/oauth2-redirect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
84 |
--------------------------------------------------------------------------------
/examples/swagger/static/swagger-ui/dist/swagger-ui-bundle.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;AAu/FA;AA6+FA;;;;;;;;;;;;;;;;;;;;;;;;;;AA0dA;;;;;;AAoIA;AAk7FA;AAmtCA;;;;;AA0uIA;AA66IA;AA27FA;AAuwGA;AAilFA;AAikFA;AAs9CA;AA8jDA;AA2qCA;AA4tEA;AAgkIA;;;;;;;;;;;;;;AAw4GA;AAyoIA;AAiuJA;AA8kHA;AAonGA;AAukEA;AA02DA;AAyxDA;AAw6BA;;;;;;AA8vEA;AA+zFA;;;;;AA23CA;AA2qFA;AAq2CA;AA0kCA;AAs/CA;AA0wEA;AA49FA;;;;;;;;;AA20BA;AA2zIA;AAi4DA;AA6tDA","sourceRoot":""}
--------------------------------------------------------------------------------
/examples/swagger/static/swagger-ui/dist/swagger-ui-standalone-preset.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIStandalonePreset=t():e.SwaggerUIStandalonePreset=t()}(this,function(){return function(e){function t(r){if(o[r])return o[r].exports;var n=o[r]={exports:{},id:r,loaded:!1};return e[r].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var o={};return t.m=e,t.c=o,t.p="/dist",t(0)}([function(e,t,o){e.exports=o(1)},function(e,t,o){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var n=o(2),i=r(n);o(30);var a=o(34),s=r(a),l=[s.default,function(){return{components:{StandaloneLayout:i.default}}}];e.exports=l},function(e,t,o){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var o=0;o
1){for(var m=Array(b),x=0;x1){for(var h=Array(w),y=0;y>"),j={array:a("array"),bool:a("boolean"),func:a("function"),number:a("number"),object:a("object"),string:a("string"),symbol:a("symbol"),any:s(),arrayOf:l,element:p(),instanceOf:u,node:g(),objectOf:f,oneOf:c,oneOfType:d,shape:b};n.prototype=Error.prototype,e.exports=j},function(e,t){"use strict";var o="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED";e.exports=o},function(e,t){"use strict";e.exports="15.4.2"},function(e,t,o){"use strict";function r(e){return i.isValidElement(e)?void 0:n("143"),e}var n=o(8),i=o(10);o(9);e.exports=r},function(e,t,o){var r=o(31);"string"==typeof r&&(r=[[e.id,r,""]]);o(33)(r,{});r.locals&&(e.exports=r.locals)},function(e,t,o){t=e.exports=o(32)(),t.push([e.id,"@charset \"UTF-8\";.swagger-ui html{box-sizing:border-box}.swagger-ui *,.swagger-ui :after,.swagger-ui :before{box-sizing:inherit}.swagger-ui body{margin:0;background:#fafafa}.swagger-ui .wrapper{width:100%;max-width:1460px;margin:0 auto;padding:0 20px}.swagger-ui .opblock-tag-section{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .opblock-tag{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px 20px 10px 10px;cursor:pointer;-webkit-transition:all .2s;transition:all .2s;border-bottom:1px solid rgba(59,65,81,.3);-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .opblock-tag:hover{background:rgba(0,0,0,.02)}.swagger-ui .opblock-tag{font-size:24px;margin:0 0 5px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock-tag.no-desc span{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .opblock-tag svg{-webkit-transition:all .4s;transition:all .4s}.swagger-ui .opblock-tag small{font-size:14px;font-weight:400;padding:0 10px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parаmeter__type{font-size:12px;padding:5px 0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .view-line-link{position:relative;top:3px;width:20px;margin:0 5px;cursor:pointer;-webkit-transition:all .5s;transition:all .5s}.swagger-ui .opblock{margin:0 0 15px;border:1px solid #000;border-radius:4px;box-shadow:0 0 3px rgba(0,0,0,.19)}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid #000}.swagger-ui .opblock .opblock-section-header{padding:8px 20px;background:hsla(0,0%,100%,.8);box-shadow:0 1px 2px rgba(0,0,0,.1)}.swagger-ui .opblock .opblock-section-header,.swagger-ui .opblock .opblock-section-header label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .opblock .opblock-section-header label{font-size:12px;font-weight:700;margin:0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-section-header label span{padding:0 10px 0 0}.swagger-ui .opblock .opblock-section-header h4{font-size:14px;margin:0;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-summary-method{font-size:14px;font-weight:700;min-width:80px;padding:6px 15px;text-align:center;border-radius:3px;background:#000;text-shadow:0 1px 0 rgba(0,0,0,.1);font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{font-size:16px;display:-webkit-box;display:-ms-flexbox;display:flex;padding:0 10px;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .opblock .opblock-summary-path .view-line-link,.swagger-ui .opblock .opblock-summary-path__deprecated .view-line-link{position:relative;top:2px;width:0;margin:0;cursor:pointer;-webkit-transition:all .5s;transition:all .5s}.swagger-ui .opblock .opblock-summary-path:hover .view-line-link,.swagger-ui .opblock .opblock-summary-path__deprecated:hover .view-line-link{width:18px;margin:0 5px}.swagger-ui .opblock .opblock-summary-path__deprecated{text-decoration:line-through}.swagger-ui .opblock .opblock-summary-description{font-size:13px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock .opblock-summary{display:-webkit-box;display:-ms-flexbox;display:flex;padding:5px;cursor:pointer;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .opblock.opblock-post{border-color:#49cc90;background:rgba(73,204,144,.1)}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:#49cc90}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:#49cc90}.swagger-ui .opblock.opblock-put{border-color:#fca130;background:rgba(252,161,48,.1)}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:#fca130}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:#fca130}.swagger-ui .opblock.opblock-delete{border-color:#f93e3e;background:rgba(249,62,62,.1)}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:#f93e3e}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:#f93e3e}.swagger-ui .opblock.opblock-get{border-color:#61affe;background:rgba(97,175,254,.1)}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:#61affe}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:#61affe}.swagger-ui .opblock.opblock-patch{border-color:#50e3c2;background:rgba(80,227,194,.1)}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:#50e3c2}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:#50e3c2}.swagger-ui .opblock.opblock-head{border-color:#9012fe;background:rgba(144,18,254,.1)}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:#9012fe}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:#9012fe}.swagger-ui .opblock.opblock-options{border-color:#0d5aa7;background:rgba(13,90,167,.1)}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:#0d5aa7}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:#0d5aa7}.swagger-ui .opblock.opblock-deprecated{opacity:.6;border-color:#ebebeb;background:hsla(0,0%,92%,.1)}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:#ebebeb}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:#ebebeb}.swagger-ui .opblock .opblock-schemes{padding:8px 20px}.swagger-ui .opblock .opblock-schemes .schemes-title{padding:0 10px 0 0}.swagger-ui .tab{display:-webkit-box;display:-ms-flexbox;display:flex;margin:20px 0 10px;padding:0;list-style:none}.swagger-ui .tab li{font-size:12px;min-width:100px;min-width:90px;padding:0;cursor:pointer;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .tab li:first-of-type{position:relative;padding-left:0}.swagger-ui .tab li:first-of-type:after{position:absolute;top:0;right:6px;width:1px;height:100%;content:\"\";background:rgba(0,0,0,.2)}.swagger-ui .tab li.active{font-weight:700}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-title_normal{padding:15px 20px}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-title_normal,.swagger-ui .opblock-title_normal h4{font-size:12px;margin:0 0 5px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-title_normal p{font-size:14px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .execute-wrapper{padding:20px;text-align:right}.swagger-ui .execute-wrapper .btn{width:100%;padding:8px 40px}.swagger-ui .body-param-options{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.swagger-ui .body-param-options .body-param-edit{padding:10px 0}.swagger-ui .body-param-options label{padding:8px 0}.swagger-ui .body-param-options label select{margin:3px 0 0}.swagger-ui .responses-inner{padding:20px}.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{font-size:12px;margin:10px 0 5px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .response-col_status{font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .response-col_status .response-undocumented{font-size:11px;font-family:Source Code Pro,monospace;font-weight:600;color:#999}.swagger-ui .response-col_description__inner span{font-size:12px;font-style:italic;display:block;margin:10px 0;padding:10px;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .response-col_description__inner span p{margin:0}.swagger-ui .opblock-body pre{font-size:12px;margin:0;padding:10px;white-space:pre-wrap;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .opblock-body pre span{color:#fff!important}.swagger-ui .scheme-container{margin:0 0 20px;padding:30px 0;background:#fff;box-shadow:0 1px 2px 0 rgba(0,0,0,.15)}.swagger-ui .scheme-container .schemes{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .scheme-container .schemes>label{font-size:12px;font-weight:700;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:-20px 15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scheme-container .schemes>label select{min-width:130px;text-transform:uppercase}.swagger-ui .loading-container{padding:40px 0 60px}.swagger-ui .loading-container .loading{position:relative}.swagger-ui .loading-container .loading:after{font-size:10px;font-weight:700;position:absolute;top:50%;left:50%;content:\"loading\";-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);text-transform:uppercase;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .loading-container .loading:before{position:absolute;top:50%;left:50%;display:block;width:60px;height:60px;margin:-30px;content:\"\";-webkit-animation:rotation 1s infinite linear,opacity .5s;animation:rotation 1s infinite linear,opacity .5s;opacity:1;border:2px solid rgba(85,85,85,.1);border-top-color:rgba(0,0,0,.6);border-radius:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden}@-webkit-keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes blinker{50%{opacity:0}}@keyframes blinker{50%{opacity:0}}.swagger-ui .btn{font-size:14px;font-weight:700;padding:5px 23px;-webkit-transition:all .3s;transition:all .3s;border:2px solid #888;border-radius:4px;background:transparent;box-shadow:0 1px 2px rgba(0,0,0,.1);font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .btn[disabled]{cursor:not-allowed;opacity:.3}.swagger-ui .btn:hover{box-shadow:0 0 5px rgba(0,0,0,.3)}.swagger-ui .btn.cancel{border-color:#ff6060;font-family:Titillium Web,sans-serif;color:#ff6060}.swagger-ui .btn.authorize{line-height:1;display:inline;color:#49cc90;border-color:#49cc90}.swagger-ui .btn.authorize span{float:left;padding:4px 20px 0 0}.swagger-ui .btn.authorize svg{fill:#49cc90}.swagger-ui .btn.execute{-webkit-animation:pulse 2s infinite;animation:pulse 2s infinite;color:#fff;border-color:#4990e2}@-webkit-keyframes pulse{0%{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,.8)}70%{box-shadow:0 0 0 5px rgba(73,144,226,0)}to{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,0)}}@keyframes pulse{0%{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,.8)}70%{box-shadow:0 0 0 5px rgba(73,144,226,0)}to{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,0)}}.swagger-ui .btn-group{display:-webkit-box;display:-ms-flexbox;display:flex;padding:30px}.swagger-ui .btn-group .btn{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .btn-group .btn:first-child{border-radius:4px 0 0 4px}.swagger-ui .btn-group .btn:last-child{border-radius:0 4px 4px 0}.swagger-ui .authorization__btn{padding:0 10px;border:none;background:none}.swagger-ui .authorization__btn.locked{opacity:1}.swagger-ui .authorization__btn.unlocked{opacity:.4}.swagger-ui .expand-methods,.swagger-ui .expand-operation{border:none;background:none}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{width:20px;height:20px}.swagger-ui .expand-methods{padding:0 10px}.swagger-ui .expand-methods:hover svg{fill:#444}.swagger-ui .expand-methods svg{-webkit-transition:all .3s;transition:all .3s;fill:#777}.swagger-ui button{cursor:pointer;outline:none}.swagger-ui select{font-size:14px;font-weight:700;padding:5px 40px 5px 10px;border:2px solid #41444e;border-radius:4px;background:#f7f7f7 url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+ICAgIDxwYXRoIGQ9Ik0xMy40MTggNy44NTljLjI3MS0uMjY4LjcwOS0uMjY4Ljk3OCAwIC4yNy4yNjguMjcyLjcwMSAwIC45NjlsLTMuOTA4IDMuODNjLS4yNy4yNjgtLjcwNy4yNjgtLjk3OSAwbC0zLjkwOC0zLjgzYy0uMjctLjI2Ny0uMjctLjcwMSAwLS45NjkuMjcxLS4yNjguNzA5LS4yNjguOTc4IDBMMTAgMTFsMy40MTgtMy4xNDF6Ii8+PC9zdmc+) right 10px center no-repeat;background-size:20px;box-shadow:0 1px 2px 0 rgba(0,0,0,.25);font-family:Titillium Web,sans-serif;color:#3b4151;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui select[multiple]{margin:5px 0;padding:5px;background:#f7f7f7}.swagger-ui .opblock-body select{min-width:230px}.swagger-ui label{font-size:12px;font-weight:700;margin:0 0 5px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui input[type=email],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text]{min-width:100px;margin:5px 0;padding:8px 10px;border:1px solid #d9d9d9;border-radius:4px;background:#fff}.swagger-ui input[type=email].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid{-webkit-animation:shake .4s 1;animation:shake .4s 1;border-color:#f93e3e;background:#feebeb}@-webkit-keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}@keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}.swagger-ui textarea{font-size:12px;width:100%;min-height:280px;padding:10px;border:none;border-radius:4px;outline:none;background:hsla(0,0%,100%,.8);font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui textarea:focus{border:2px solid #61affe}.swagger-ui textarea.curl{font-size:12px;min-height:100px;margin:0;padding:10px;resize:none;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .checkbox{padding:5px 0 10px;-webkit-transition:opacity .5s;transition:opacity .5s;color:#333}.swagger-ui .checkbox label{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .checkbox p{font-weight:400!important;font-style:italic;margin:0!important;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .checkbox input[type=checkbox]{display:none}.swagger-ui .checkbox input[type=checkbox]+label>.item{position:relative;top:3px;display:inline-block;width:16px;height:16px;margin:0 8px 0 0;padding:5px;cursor:pointer;border-radius:1px;background:#e8e8e8;box-shadow:0 0 0 2px #e8e8e8;-webkit-box-flex:0;-ms-flex:none;flex:none}.swagger-ui .checkbox input[type=checkbox]+label>.item:active{-webkit-transform:scale(.9);transform:scale(.9)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:#e8e8e8 url(\"data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='8' viewBox='3 7 10 8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%2341474E' fill-rule='evenodd' d='M6.333 15L3 11.667l1.333-1.334 2 2L11.667 7 13 8.333z'/%3E%3C/svg%3E\") 50% no-repeat}.swagger-ui .dialog-ux{position:fixed;z-index:9999;top:0;right:0;bottom:0;left:0}.swagger-ui .dialog-ux .backdrop-ux{position:fixed;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.8)}.swagger-ui .dialog-ux .modal-ux{position:absolute;z-index:9999;top:50%;left:50%;width:100%;min-width:300px;max-width:650px;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);border:1px solid #ebebeb;border-radius:4px;background:#fff;box-shadow:0 10px 30px 0 rgba(0,0,0,.2)}.swagger-ui .dialog-ux .modal-ux-content{overflow-y:auto;max-height:540px;padding:20px}.swagger-ui .dialog-ux .modal-ux-content p{font-size:12px;margin:0 0 5px;color:#41444e;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-content h4{font-size:18px;font-weight:600;margin:15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-header{display:-webkit-box;display:-ms-flexbox;display:flex;padding:12px 0;border-bottom:1px solid #ebebeb;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .dialog-ux .modal-ux-header .close-modal{padding:0 10px;border:none;background:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui .dialog-ux .modal-ux-header h3{font-size:20px;font-weight:600;margin:0;padding:0 20px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .model{font-size:12px;font-weight:300;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .model-toggle{font-size:10px;position:relative;top:6px;display:inline-block;margin:auto .3em;cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0deg);transform:rotate(0deg)}.swagger-ui .model-toggle:after{display:block;width:20px;height:20px;content:\"\";background:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E\") 50% no-repeat;background-size:100%}.swagger-ui .model-jump-to-path{position:relative;cursor:pointer}.swagger-ui .model-jump-to-path .view-line-link{position:absolute;top:-.4em;cursor:pointer}.swagger-ui .model-title{position:relative}.swagger-ui .model-title:hover .model-hint{visibility:visible}.swagger-ui .model-hint{position:absolute;top:-1.8em;visibility:hidden;padding:.1em .5em;white-space:nowrap;color:#ebebeb;border-radius:4px;background:rgba(0,0,0,.7)}.swagger-ui section.models{margin:30px 0;border:1px solid rgba(59,65,81,.3);border-radius:4px}.swagger-ui section.models.is-open{padding:0 0 20px}.swagger-ui section.models.is-open h4{margin:0 0 5px;border-bottom:1px solid rgba(59,65,81,.3)}.swagger-ui section.models.is-open h4 svg{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.swagger-ui section.models h4{font-size:16px;display:-webkit-box;display:-ms-flexbox;display:flex;margin:0;padding:10px 20px 10px 10px;cursor:pointer;-webkit-transition:all .2s;transition:all .2s;font-family:Titillium Web,sans-serif;color:#777;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui section.models h4 svg{-webkit-transition:all .4s;transition:all .4s}.swagger-ui section.models h4 span{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui section.models h4:hover{background:rgba(0,0,0,.02)}.swagger-ui section.models h5{font-size:16px;margin:0 0 10px;font-family:Titillium Web,sans-serif;color:#777}.swagger-ui section.models .model-jump-to-path{position:relative;top:5px}.swagger-ui section.models .model-container{margin:0 20px 15px;-webkit-transition:all .5s;transition:all .5s;border-radius:4px;background:rgba(0,0,0,.05)}.swagger-ui section.models .model-container:hover{background:rgba(0,0,0,.07)}.swagger-ui section.models .model-container:first-of-type{margin:20px}.swagger-ui section.models .model-container:last-of-type{margin:0 20px}.swagger-ui section.models .model-box{background:none}.swagger-ui .model-box{padding:10px;border-radius:4px;background:rgba(0,0,0,.1)}.swagger-ui .model-box .model-jump-to-path{position:relative;top:4px}.swagger-ui .model-title{font-size:16px;font-family:Titillium Web,sans-serif;color:#555}.swagger-ui span>span.model,.swagger-ui span>span.model .brace-close{padding:0 0 0 10px}.swagger-ui .prop-type{color:#55a}.swagger-ui .prop-enum{display:block}.swagger-ui .prop-format{color:#999}.swagger-ui table{width:100%;padding:0 10px;border-collapse:collapse}.swagger-ui table.model tbody tr td{padding:0;vertical-align:top}.swagger-ui table.model tbody tr td:first-of-type{width:100px;padding:0}.swagger-ui table.headers td{font-size:12px;font-weight:300;vertical-align:middle;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui table tbody tr td{padding:10px 0 0;vertical-align:top}.swagger-ui table tbody tr td:first-of-type{width:20%;padding:10px 0}.swagger-ui table thead tr td,.swagger-ui table thead tr th{font-size:12px;font-weight:700;padding:12px 0;text-align:left;border-bottom:1px solid rgba(59,65,81,.2);font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameters-col_description p{font-size:14px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameters-col_description input[type=text]{width:100%;max-width:340px}.swagger-ui .parameter__name{font-size:16px;font-weight:400;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .parameter__name.required{font-weight:700}.swagger-ui .parameter__name.required:after{font-size:10px;position:relative;top:-6px;padding:5px;content:\"required\";color:rgba(255,0,0,.6)}.swagger-ui .parameter__in{font-size:12px;font-style:italic;font-family:Source Code Pro,monospace;font-weight:600;color:#888}.swagger-ui .table-container{padding:20px}.swagger-ui .topbar{padding:8px 30px;background-color:#89bf04}.swagger-ui .topbar .topbar-wrapper{-ms-flex-align:center}.swagger-ui .topbar .topbar-wrapper,.swagger-ui .topbar a{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;align-items:center}.swagger-ui .topbar a{font-size:1.5em;font-weight:700;text-decoration:none;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-align:center;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .topbar a span{margin:0;padding:0 10px}.swagger-ui .topbar .download-url-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .topbar .download-url-wrapper input[type=text]{min-width:350px;margin:0;border:2px solid #547f00;border-radius:4px 0 0 4px;outline:none}.swagger-ui .topbar .download-url-wrapper .download-url-button{font-size:16px;font-weight:700;padding:4px 40px;border:none;border-radius:0 4px 4px 0;background:#547f00;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .info{margin:50px 0}.swagger-ui .info hgroup.main{margin:0 0 20px}.swagger-ui .info hgroup.main a{font-size:12px}.swagger-ui .info p{font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info code{padding:3px 5px;border-radius:4px;background:rgba(0,0,0,.05);font-family:Source Code Pro,monospace;font-weight:600;color:#9012fe}.swagger-ui .info a{font-size:14px;-webkit-transition:all .4s;transition:all .4s;font-family:Open Sans,sans-serif;color:#4990e2}.swagger-ui .info a:hover{color:#1f69c0}.swagger-ui .info>div{margin:0 0 5px}.swagger-ui .info .base-url{font-size:12px;font-weight:300!important;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .info .title{font-size:36px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info .title small{font-size:10px;position:relative;top:-5px;display:inline-block;margin:0 0 0 5px;padding:2px 4px;vertical-align:super;border-radius:57px;background:#7d8492}.swagger-ui .info .title small pre{margin:0;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .auth-btn-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px 0;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.swagger-ui .auth-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .auth-wrapper .authorize{padding-right:20px}.swagger-ui .auth-container{margin:0 0 10px;padding:10px 20px;border-bottom:1px solid #ebebeb}.swagger-ui .auth-container:last-of-type{margin:0;padding:10px 20px;border:0}.swagger-ui .auth-container h4{margin:5px 0 15px!important}.swagger-ui .auth-container .wrapper{margin:0;padding:0}.swagger-ui .auth-container input[type=password],.swagger-ui .auth-container input[type=text]{min-width:230px}.swagger-ui .auth-container .errors{font-size:12px;padding:10px;border-radius:4px;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .scopes h2{font-size:14px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scope-def{padding:0 0 20px}.swagger-ui .errors-wrapper{margin:20px;padding:10px 20px;-webkit-animation:scaleUp .5s;animation:scaleUp .5s;border:2px solid #f93e3e;border-radius:4px;background:rgba(249,62,62,.1)}.swagger-ui .errors-wrapper .error-wrapper{margin:0 0 10px}.swagger-ui .errors-wrapper .errors h4{font-size:14px;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .errors-wrapper .errors small{color:#666}.swagger-ui .errors-wrapper hgroup{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .errors-wrapper hgroup h4{font-size:20px;margin:0;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}@-webkit-keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}",""]);
7 | },function(e,t){e.exports=function(){var e=[];return e.toString=function(){for(var e=[],t=0;t=0&&h.splice(t,1)}function s(e){var t=document.createElement("style");return t.type="text/css",i(e,t),t}function l(e){var t=document.createElement("link");return t.rel="stylesheet",i(e,t),t}function p(e,t){var o,r,n;if(t.singleton){var i=w++;o=x||(x=s(t)),r=u.bind(null,o,i,!1),n=u.bind(null,o,i,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(o=l(t),r=f.bind(null,o),n=function(){a(o),o.href&&URL.revokeObjectURL(o.href)}):(o=s(t),r=c.bind(null,o),n=function(){a(o)});return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else n()}}function u(e,t,o,r){var n=o?"":r.css;if(e.styleSheet)e.styleSheet.cssText=y(t,n);else{var i=document.createTextNode(n),a=e.childNodes;a[t]&&e.removeChild(a[t]),a.length?e.insertBefore(i,a[t]):e.appendChild(i)}}function c(e,t){var o=t.css,r=t.media;t.sourceMap;if(r&&e.setAttribute("media",r),e.styleSheet)e.styleSheet.cssText=o;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(o))}}function f(e,t){var o=t.css,r=(t.media,t.sourceMap);r&&(o+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(r))))+" */");var n=new Blob([o],{type:"text/css"}),i=e.href;e.href=URL.createObjectURL(n),i&&URL.revokeObjectURL(i)}var d={},g=function(e){var t;return function(){return"undefined"==typeof t&&(t=e.apply(this,arguments)),t}},b=g(function(){return/msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase())}),m=g(function(){return document.head||document.getElementsByTagName("head")[0]}),x=null,w=0,h=[];e.exports=function(e,t){t=t||{},"undefined"==typeof t.singleton&&(t.singleton=b()),"undefined"==typeof t.insertAt&&(t.insertAt="bottom");var o=n(e);return r(o,t),function(e){for(var i=[],a=0;alabel{font-size:12px;font-weight:700;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:-20px 15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scheme-container .schemes>label select{min-width:130px;text-transform:uppercase}.swagger-ui .loading-container{padding:40px 0 60px}.swagger-ui .loading-container .loading{position:relative}.swagger-ui .loading-container .loading:after{font-size:10px;font-weight:700;position:absolute;top:50%;left:50%;content:"loading";-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);text-transform:uppercase;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .loading-container .loading:before{position:absolute;top:50%;left:50%;display:block;width:60px;height:60px;margin:-30px;content:"";-webkit-animation:rotation 1s infinite linear,opacity .5s;animation:rotation 1s infinite linear,opacity .5s;opacity:1;border:2px solid rgba(85,85,85,.1);border-top-color:rgba(0,0,0,.6);border-radius:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden}@-webkit-keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotation{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes blinker{50%{opacity:0}}@keyframes blinker{50%{opacity:0}}.swagger-ui .btn{font-size:14px;font-weight:700;padding:5px 23px;-webkit-transition:all .3s;transition:all .3s;border:2px solid #888;border-radius:4px;background:transparent;box-shadow:0 1px 2px rgba(0,0,0,.1);font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .btn[disabled]{cursor:not-allowed;opacity:.3}.swagger-ui .btn:hover{box-shadow:0 0 5px rgba(0,0,0,.3)}.swagger-ui .btn.cancel{border-color:#ff6060;font-family:Titillium Web,sans-serif;color:#ff6060}.swagger-ui .btn.authorize{line-height:1;display:inline;color:#49cc90;border-color:#49cc90}.swagger-ui .btn.authorize span{float:left;padding:4px 20px 0 0}.swagger-ui .btn.authorize svg{fill:#49cc90}.swagger-ui .btn.execute{-webkit-animation:pulse 2s infinite;animation:pulse 2s infinite;color:#fff;border-color:#4990e2}@-webkit-keyframes pulse{0%{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,.8)}70%{box-shadow:0 0 0 5px rgba(73,144,226,0)}to{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,0)}}@keyframes pulse{0%{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,.8)}70%{box-shadow:0 0 0 5px rgba(73,144,226,0)}to{color:#fff;background:#4990e2;box-shadow:0 0 0 0 rgba(73,144,226,0)}}.swagger-ui .btn-group{display:-webkit-box;display:-ms-flexbox;display:flex;padding:30px}.swagger-ui .btn-group .btn{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui .btn-group .btn:first-child{border-radius:4px 0 0 4px}.swagger-ui .btn-group .btn:last-child{border-radius:0 4px 4px 0}.swagger-ui .authorization__btn{padding:0 10px;border:none;background:none}.swagger-ui .authorization__btn.locked{opacity:1}.swagger-ui .authorization__btn.unlocked{opacity:.4}.swagger-ui .expand-methods,.swagger-ui .expand-operation{border:none;background:none}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{width:20px;height:20px}.swagger-ui .expand-methods{padding:0 10px}.swagger-ui .expand-methods:hover svg{fill:#444}.swagger-ui .expand-methods svg{-webkit-transition:all .3s;transition:all .3s;fill:#777}.swagger-ui button{cursor:pointer;outline:none}.swagger-ui select{font-size:14px;font-weight:700;padding:5px 40px 5px 10px;border:2px solid #41444e;border-radius:4px;background:#f7f7f7 url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+ICAgIDxwYXRoIGQ9Ik0xMy40MTggNy44NTljLjI3MS0uMjY4LjcwOS0uMjY4Ljk3OCAwIC4yNy4yNjguMjcyLjcwMSAwIC45NjlsLTMuOTA4IDMuODNjLS4yNy4yNjgtLjcwNy4yNjgtLjk3OSAwbC0zLjkwOC0zLjgzYy0uMjctLjI2Ny0uMjctLjcwMSAwLS45NjkuMjcxLS4yNjguNzA5LS4yNjguOTc4IDBMMTAgMTFsMy40MTgtMy4xNDF6Ii8+PC9zdmc+) right 10px center no-repeat;background-size:20px;box-shadow:0 1px 2px 0 rgba(0,0,0,.25);font-family:Titillium Web,sans-serif;color:#3b4151;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui select[multiple]{margin:5px 0;padding:5px;background:#f7f7f7}.swagger-ui .opblock-body select{min-width:230px}.swagger-ui label{font-size:12px;font-weight:700;margin:0 0 5px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui input[type=email],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text]{min-width:100px;margin:5px 0;padding:8px 10px;border:1px solid #d9d9d9;border-radius:4px;background:#fff}.swagger-ui input[type=email].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid{-webkit-animation:shake .4s 1;animation:shake .4s 1;border-color:#f93e3e;background:#feebeb}@-webkit-keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}@keyframes shake{10%,90%{-webkit-transform:translate3d(-1px,0,0);transform:translate3d(-1px,0,0)}20%,80%{-webkit-transform:translate3d(2px,0,0);transform:translate3d(2px,0,0)}30%,50%,70%{-webkit-transform:translate3d(-4px,0,0);transform:translate3d(-4px,0,0)}40%,60%{-webkit-transform:translate3d(4px,0,0);transform:translate3d(4px,0,0)}}.swagger-ui textarea{font-size:12px;width:100%;min-height:280px;padding:10px;border:none;border-radius:4px;outline:none;background:hsla(0,0%,100%,.8);font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui textarea:focus{border:2px solid #61affe}.swagger-ui textarea.curl{font-size:12px;min-height:100px;margin:0;padding:10px;resize:none;border-radius:4px;background:#41444e;font-family:Source Code Pro,monospace;font-weight:600;color:#fff}.swagger-ui .checkbox{padding:5px 0 10px;-webkit-transition:opacity .5s;transition:opacity .5s;color:#333}.swagger-ui .checkbox label{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .checkbox p{font-weight:400!important;font-style:italic;margin:0!important;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .checkbox input[type=checkbox]{display:none}.swagger-ui .checkbox input[type=checkbox]+label>.item{position:relative;top:3px;display:inline-block;width:16px;height:16px;margin:0 8px 0 0;padding:5px;cursor:pointer;border-radius:1px;background:#e8e8e8;box-shadow:0 0 0 2px #e8e8e8;-webkit-box-flex:0;-ms-flex:none;flex:none}.swagger-ui .checkbox input[type=checkbox]+label>.item:active{-webkit-transform:scale(.9);transform:scale(.9)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:#e8e8e8 url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='8' viewBox='3 7 10 8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%2341474E' fill-rule='evenodd' d='M6.333 15L3 11.667l1.333-1.334 2 2L11.667 7 13 8.333z'/%3E%3C/svg%3E") 50% no-repeat}.swagger-ui .dialog-ux{position:fixed;z-index:9999;top:0;right:0;bottom:0;left:0}.swagger-ui .dialog-ux .backdrop-ux{position:fixed;top:0;right:0;bottom:0;left:0;background:rgba(0,0,0,.8)}.swagger-ui .dialog-ux .modal-ux{position:absolute;z-index:9999;top:50%;left:50%;width:100%;min-width:300px;max-width:650px;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);border:1px solid #ebebeb;border-radius:4px;background:#fff;box-shadow:0 10px 30px 0 rgba(0,0,0,.2)}.swagger-ui .dialog-ux .modal-ux-content{overflow-y:auto;max-height:540px;padding:20px}.swagger-ui .dialog-ux .modal-ux-content p{font-size:12px;margin:0 0 5px;color:#41444e;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-content h4{font-size:18px;font-weight:600;margin:15px 0 0;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .dialog-ux .modal-ux-header{display:-webkit-box;display:-ms-flexbox;display:flex;padding:12px 0;border-bottom:1px solid #ebebeb;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .dialog-ux .modal-ux-header .close-modal{padding:0 10px;border:none;background:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.swagger-ui .dialog-ux .modal-ux-header h3{font-size:20px;font-weight:600;margin:0;padding:0 20px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .model{font-size:12px;font-weight:300;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .model-toggle{font-size:10px;position:relative;top:6px;display:inline-block;margin:auto .3em;cursor:pointer;-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:50% 50%;transform-origin:50% 50%}.swagger-ui .model-toggle.collapsed{-webkit-transform:rotate(0deg);transform:rotate(0deg)}.swagger-ui .model-toggle:after{display:block;width:20px;height:20px;content:"";background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E") 50% no-repeat;background-size:100%}.swagger-ui .model-jump-to-path{position:relative;cursor:pointer}.swagger-ui .model-jump-to-path .view-line-link{position:absolute;top:-.4em;cursor:pointer}.swagger-ui .model-title{position:relative}.swagger-ui .model-title:hover .model-hint{visibility:visible}.swagger-ui .model-hint{position:absolute;top:-1.8em;visibility:hidden;padding:.1em .5em;white-space:nowrap;color:#ebebeb;border-radius:4px;background:rgba(0,0,0,.7)}.swagger-ui section.models{margin:30px 0;border:1px solid rgba(59,65,81,.3);border-radius:4px}.swagger-ui section.models.is-open{padding:0 0 20px}.swagger-ui section.models.is-open h4{margin:0 0 5px;border-bottom:1px solid rgba(59,65,81,.3)}.swagger-ui section.models.is-open h4 svg{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.swagger-ui section.models h4{font-size:16px;display:-webkit-box;display:-ms-flexbox;display:flex;margin:0;padding:10px 20px 10px 10px;cursor:pointer;-webkit-transition:all .2s;transition:all .2s;font-family:Titillium Web,sans-serif;color:#777;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui section.models h4 svg{-webkit-transition:all .4s;transition:all .4s}.swagger-ui section.models h4 span{-webkit-box-flex:1;-ms-flex:1;flex:1}.swagger-ui section.models h4:hover{background:rgba(0,0,0,.02)}.swagger-ui section.models h5{font-size:16px;margin:0 0 10px;font-family:Titillium Web,sans-serif;color:#777}.swagger-ui section.models .model-jump-to-path{position:relative;top:5px}.swagger-ui section.models .model-container{margin:0 20px 15px;-webkit-transition:all .5s;transition:all .5s;border-radius:4px;background:rgba(0,0,0,.05)}.swagger-ui section.models .model-container:hover{background:rgba(0,0,0,.07)}.swagger-ui section.models .model-container:first-of-type{margin:20px}.swagger-ui section.models .model-container:last-of-type{margin:0 20px}.swagger-ui section.models .model-box{background:none}.swagger-ui .model-box{padding:10px;border-radius:4px;background:rgba(0,0,0,.1)}.swagger-ui .model-box .model-jump-to-path{position:relative;top:4px}.swagger-ui .model-title{font-size:16px;font-family:Titillium Web,sans-serif;color:#555}.swagger-ui span>span.model,.swagger-ui span>span.model .brace-close{padding:0 0 0 10px}.swagger-ui .prop-type{color:#55a}.swagger-ui .prop-enum{display:block}.swagger-ui .prop-format{color:#999}.swagger-ui table{width:100%;padding:0 10px;border-collapse:collapse}.swagger-ui table.model tbody tr td{padding:0;vertical-align:top}.swagger-ui table.model tbody tr td:first-of-type{width:100px;padding:0}.swagger-ui table.headers td{font-size:12px;font-weight:300;vertical-align:middle;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui table tbody tr td{padding:10px 0 0;vertical-align:top}.swagger-ui table tbody tr td:first-of-type{width:20%;padding:10px 0}.swagger-ui table thead tr td,.swagger-ui table thead tr th{font-size:12px;font-weight:700;padding:12px 0;text-align:left;border-bottom:1px solid rgba(59,65,81,.2);font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameters-col_description p{font-size:14px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .parameters-col_description input[type=text]{width:100%;max-width:340px}.swagger-ui .parameter__name{font-size:16px;font-weight:400;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .parameter__name.required{font-weight:700}.swagger-ui .parameter__name.required:after{font-size:10px;position:relative;top:-6px;padding:5px;content:"required";color:rgba(255,0,0,.6)}.swagger-ui .parameter__in{font-size:12px;font-style:italic;font-family:Source Code Pro,monospace;font-weight:600;color:#888}.swagger-ui .table-container{padding:20px}.swagger-ui .topbar{padding:8px 30px;background-color:#89bf04}.swagger-ui .topbar .topbar-wrapper{-ms-flex-align:center}.swagger-ui .topbar .topbar-wrapper,.swagger-ui .topbar a{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;align-items:center}.swagger-ui .topbar a{font-size:1.5em;font-weight:700;text-decoration:none;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-align:center;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .topbar a span{margin:0;padding:0 10px}.swagger-ui .topbar .download-url-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex}.swagger-ui .topbar .download-url-wrapper input[type=text]{min-width:350px;margin:0;border:2px solid #547f00;border-radius:4px 0 0 4px;outline:none}.swagger-ui .topbar .download-url-wrapper .download-url-button{font-size:16px;font-weight:700;padding:4px 40px;border:none;border-radius:0 4px 4px 0;background:#547f00;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .info{margin:50px 0}.swagger-ui .info hgroup.main{margin:0 0 20px}.swagger-ui .info hgroup.main a{font-size:12px}.swagger-ui .info p{font-size:14px;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info code{padding:3px 5px;border-radius:4px;background:rgba(0,0,0,.05);font-family:Source Code Pro,monospace;font-weight:600;color:#9012fe}.swagger-ui .info a{font-size:14px;-webkit-transition:all .4s;transition:all .4s;font-family:Open Sans,sans-serif;color:#4990e2}.swagger-ui .info a:hover{color:#1f69c0}.swagger-ui .info>div{margin:0 0 5px}.swagger-ui .info .base-url{font-size:12px;font-weight:300!important;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .info .title{font-size:36px;margin:0;font-family:Open Sans,sans-serif;color:#3b4151}.swagger-ui .info .title small{font-size:10px;position:relative;top:-5px;display:inline-block;margin:0 0 0 5px;padding:2px 4px;vertical-align:super;border-radius:57px;background:#7d8492}.swagger-ui .info .title small pre{margin:0;font-family:Titillium Web,sans-serif;color:#fff}.swagger-ui .auth-btn-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px 0;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.swagger-ui .auth-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.swagger-ui .auth-wrapper .authorize{padding-right:20px}.swagger-ui .auth-container{margin:0 0 10px;padding:10px 20px;border-bottom:1px solid #ebebeb}.swagger-ui .auth-container:last-of-type{margin:0;padding:10px 20px;border:0}.swagger-ui .auth-container h4{margin:5px 0 15px!important}.swagger-ui .auth-container .wrapper{margin:0;padding:0}.swagger-ui .auth-container input[type=password],.swagger-ui .auth-container input[type=text]{min-width:230px}.swagger-ui .auth-container .errors{font-size:12px;padding:10px;border-radius:4px;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .scopes h2{font-size:14px;font-family:Titillium Web,sans-serif;color:#3b4151}.swagger-ui .scope-def{padding:0 0 20px}.swagger-ui .errors-wrapper{margin:20px;padding:10px 20px;-webkit-animation:scaleUp .5s;animation:scaleUp .5s;border:2px solid #f93e3e;border-radius:4px;background:rgba(249,62,62,.1)}.swagger-ui .errors-wrapper .error-wrapper{margin:0 0 10px}.swagger-ui .errors-wrapper .errors h4{font-size:14px;margin:0;font-family:Source Code Pro,monospace;font-weight:600;color:#3b4151}.swagger-ui .errors-wrapper .errors small{color:#666}.swagger-ui .errors-wrapper hgroup{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.swagger-ui .errors-wrapper hgroup h4{font-size:20px;margin:0;-webkit-box-flex:1;-ms-flex:1;flex:1;font-family:Titillium Web,sans-serif;color:#3b4151}@-webkit-keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes scaleUp{0%{-webkit-transform:scale(.8);transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.swagger-ui .Resizer.vertical.disabled{display:none}
2 | /*# sourceMappingURL=swagger-ui.css.map*/
--------------------------------------------------------------------------------
/examples/swagger/static/swagger-ui/dist/swagger-ui.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""}
--------------------------------------------------------------------------------
/examples/swagger/static/swagger-ui/dist/swagger-ui.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAuwCA;AAoyHA;AA2wHA;AA07FA;AA+nCA;AAohCA;AAghCA;AAk4BA","sourceRoot":""}
--------------------------------------------------------------------------------
/json.go:
--------------------------------------------------------------------------------
1 | package fireball
2 |
3 | import (
4 | "encoding/json"
5 | )
6 |
7 | // NewJSONResponse returns a new HTTPResponse in JSON format
8 | func NewJSONResponse(status int, data interface{}) (*HTTPResponse, error) {
9 | bytes, err := json.Marshal(data)
10 | if err != nil {
11 | return nil, err
12 | }
13 |
14 | response := NewResponse(status, bytes, JSONHeaders)
15 | return response, nil
16 | }
17 |
18 | // NewJSONError returns a new HTTPError in JSON format
19 | func NewJSONError(status int, err error) (*HTTPError, error) {
20 | e := struct {
21 | Error string
22 | }{
23 | Error: err.Error(),
24 | }
25 |
26 | bytes, err := json.Marshal(e)
27 | if err != nil {
28 | return nil, err
29 | }
30 |
31 | response := &HTTPError{
32 | HTTPResponse: NewResponse(status, bytes, JSONHeaders),
33 | Err: err,
34 | }
35 |
36 | return response, nil
37 | }
38 |
--------------------------------------------------------------------------------
/parser.go:
--------------------------------------------------------------------------------
1 | package fireball
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "html/template"
7 | "os"
8 | "path/filepath"
9 | "strings"
10 | )
11 |
12 | // TemplateParser is an interface object that is used to parse HTML templates
13 | type TemplateParser interface {
14 | Parse() (*template.Template, error)
15 | }
16 |
17 | // TemplateParserFunc is a function which implements the TemplateParser interface
18 | type TemplateParserFunc func() (*template.Template, error)
19 |
20 | func (tpf TemplateParserFunc) Parse() (*template.Template, error) {
21 | return tpf()
22 | }
23 |
24 | // GlobParser generates a template by recusively searching the specified root directory
25 | // and parses templates that match the specified glob pattern
26 | type GlobParser struct {
27 | Root string
28 | Glob string
29 | cache *template.Template
30 | }
31 |
32 | // NewGlobParser returns a GlobParser with the specified root and glob pattern
33 | func NewGlobParser(root, glob string) *GlobParser {
34 | return &GlobParser{
35 | Root: root,
36 | Glob: glob,
37 | }
38 | }
39 |
40 | // Parse recursively searches the root directory and parses templates
41 | // that match the specified glob pattern.
42 | // Template names are generated by path/from/root + filename.
43 | //
44 | // For example, if GlobParser.Root == "views", the following template names would be generated:
45 | // Files:
46 | // views/
47 | // index.html
48 | // partials/
49 | // login.html
50 | //
51 | // Template Names:
52 | // "index.html"
53 | // "partials/login.html"
54 | func (p *GlobParser) Parse() (*template.Template, error) {
55 | if p.cache != nil {
56 | return p.cache, nil
57 | }
58 |
59 | root := template.New("root")
60 |
61 | walkf := func(path string, info os.FileInfo, err error) error {
62 | if err != nil {
63 | return err
64 | }
65 |
66 | if info.IsDir() {
67 | path = filepath.Join(path, p.Glob)
68 | current, err := template.ParseGlob(path)
69 | if err != nil {
70 | return err
71 | }
72 |
73 | for _, t := range current.Templates() {
74 | name := p.generateTemplateName(path, t)
75 |
76 | if _, err := root.AddParseTree(name, t.Tree); err != nil {
77 | return err
78 | }
79 | }
80 | }
81 |
82 | return nil
83 | }
84 |
85 | if err := filepath.Walk(p.Root, walkf); err != nil {
86 | return nil, err
87 | }
88 |
89 | p.cache = root
90 | return root, nil
91 | }
92 |
93 | func (p *GlobParser) generateTemplateName(path string, t *template.Template) string {
94 | path = strings.Replace(filepath.Dir(path), "\\", "/", -1)
95 | path = fmt.Sprintf("%s/%s", path, t.Name())
96 | name := strings.TrimPrefix(path, p.Root)
97 | return name
98 | }
99 |
100 | // HTML is a helper function that returns a response generated from the given templateName and data
101 | func HTML(parser TemplateParser, status int, templateName string, data interface{}) (*HTTPResponse, error) {
102 | tmpl, err := parser.Parse()
103 | if err != nil {
104 | return nil, err
105 | }
106 |
107 | var buffer bytes.Buffer
108 | if err := tmpl.ExecuteTemplate(&buffer, templateName, data); err != nil {
109 | return nil, err
110 | }
111 |
112 | response := NewResponse(status, buffer.Bytes(), HTMLHeaders)
113 | return response, nil
114 | }
115 |
--------------------------------------------------------------------------------
/parser_test.go:
--------------------------------------------------------------------------------
1 | package fireball
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 | )
7 |
8 | var golden = `
9 |
10 |
11 | Title
12 |
13 |
14 | Header
15 | Hello, World!
16 | Footer
17 |
18 |
19 | `
20 |
21 | func TestGlobParse(t *testing.T) {
22 | parser := NewGlobParser("testing/", "*.html")
23 |
24 | tmpl, err := parser.Parse()
25 | if err != nil {
26 | t.Fatal(err)
27 | }
28 |
29 | var buffer bytes.Buffer
30 | if err := tmpl.ExecuteTemplate(&buffer, "index.html", nil); err != nil {
31 | t.Fatal(err)
32 | }
33 |
34 | if v, want := buffer.String(), golden; v != want {
35 | t.Errorf("\nExpected: %#v \nReceived: %#v", want, v)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/response.go:
--------------------------------------------------------------------------------
1 | package fireball
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | )
7 |
8 | // Response is an object that writes to an http.ResponseWriter
9 | // A Response object implements the http.Handler interface
10 | type Response interface {
11 | Write(http.ResponseWriter, *http.Request)
12 | }
13 |
14 | // ResponseFunc is a function which implements the Response interface
15 | type ResponseFunc func(http.ResponseWriter, *http.Request)
16 |
17 | func (rf ResponseFunc) Write(w http.ResponseWriter, r *http.Request) {
18 | rf(w, r)
19 | }
20 |
21 | // HTTPResponse objects write the specified status, headers, and body to
22 | // a http.ResponseWriter
23 | type HTTPResponse struct {
24 | Status int
25 | Body []byte
26 | Headers map[string]string
27 | }
28 |
29 | // NewResponse returns a new HTTPResponse with the specified status, body, and headers
30 | func NewResponse(status int, body []byte, headers map[string]string) *HTTPResponse {
31 | return &HTTPResponse{
32 | Status: status,
33 | Body: body,
34 | Headers: headers,
35 | }
36 | }
37 |
38 | // Write will write the specified status, headers, and body to the http.ResponseWriter
39 | func (h *HTTPResponse) Write(w http.ResponseWriter, r *http.Request) {
40 | for key, val := range h.Headers {
41 | w.Header().Set(key, val)
42 | }
43 |
44 | w.WriteHeader(h.Status)
45 | if _, err := w.Write(h.Body); err != nil {
46 | log.Println(err)
47 | http.Error(w, err.Error(), http.StatusInternalServerError)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/route.go:
--------------------------------------------------------------------------------
1 | package fireball
2 |
3 | // Handler performs the business logic on a request
4 | type Handler func(c *Context) (Response, error)
5 |
6 | // Handlers maps a http method to a Handler
7 | type Handlers map[string]Handler
8 |
9 | // Routes are used to map a request to a RouteMatch
10 | type Route struct {
11 | // Path is used to determine if a request's URL matches this Route
12 | Path string
13 | // Handlers map common HTTP methods to different Handlers
14 | Handlers map[string]Handler
15 | }
16 |
17 | // RouteMatch objects are returned by the router when a request is successfully matched
18 | type RouteMatch struct {
19 | Handler Handler
20 | PathVariables map[string]string
21 | }
22 |
--------------------------------------------------------------------------------
/router.go:
--------------------------------------------------------------------------------
1 | package fireball
2 |
3 | import (
4 | "net/http"
5 | "strings"
6 |
7 | "github.com/zpatrick/go-cache"
8 | )
9 |
10 | // Router is an interface that matches an *http.Request to a RouteMatch.
11 | // If no matches are found, a nil RouteMatch should be returned.
12 | type Router interface {
13 | Match(*http.Request) (*RouteMatch, error)
14 | }
15 |
16 | // RouterFunc is a function which implements the Router interface
17 | type RouterFunc func(*http.Request) (*RouteMatch, error)
18 |
19 | func (rf RouterFunc) Match(r *http.Request) (*RouteMatch, error) {
20 | return rf(r)
21 | }
22 |
23 | // BasicRouter attempts to match requests based on its Routes.
24 | // This router supports variables in the URL by using ":variable" notation in URL sections.
25 | // For example, the following are all valid Paths:
26 | // "/home"
27 | // "/movies/:id"
28 | // "/users/:userID/purchases/:purchaseID"
29 | // Matched Path Variables can be retrieved in Handlers by the Context:
30 | // func Handler(c *Context) (Response, error) {
31 | // id := c.PathVariables["id"]
32 | // ...
33 | // }
34 | type BasicRouter struct {
35 | Routes []*Route
36 | cache *cache.Cache
37 | }
38 |
39 | // NewBasicRouter returns a new BasicRouter with the specified Routes
40 | func NewBasicRouter(routes []*Route) *BasicRouter {
41 | return &BasicRouter{
42 | Routes: routes,
43 | cache: cache.New(),
44 | }
45 | }
46 |
47 | // Match attempts to match the *http.Request to a Route.
48 | // Successful matches are cached for improved performance.
49 | func (r *BasicRouter) Match(req *http.Request) (*RouteMatch, error) {
50 | key := r.cacheKey(req)
51 | if rm, ok := r.cache.GetOK(key); ok {
52 | return rm.(*RouteMatch), nil
53 | }
54 |
55 | for _, route := range r.Routes {
56 | if rm := r.matchRoute(route, req); rm != nil {
57 | r.cache.Set(key, rm)
58 | return rm, nil
59 | }
60 | }
61 |
62 | return nil, nil
63 | }
64 |
65 | func (r *BasicRouter) matchRoute(route *Route, req *http.Request) *RouteMatch {
66 | handler := route.Handlers[req.Method]
67 | if handler == nil {
68 | return nil
69 | }
70 |
71 | pathVariables, ok := r.matchPathVariables(route, req.URL.Path)
72 | if !ok {
73 | return nil
74 | }
75 |
76 | routeMatch := &RouteMatch{
77 | Handler: handler,
78 | PathVariables: pathVariables,
79 | }
80 |
81 | return routeMatch
82 | }
83 |
84 | func (r *BasicRouter) matchPathVariables(route *Route, url string) (map[string]string, bool) {
85 | if url != "/" {
86 | url = strings.TrimSuffix(url, "/")
87 | }
88 |
89 | if route.Path != "/" {
90 | route.Path = strings.TrimSuffix(route.Path, "/")
91 | }
92 |
93 | routeSections := strings.Split(route.Path, "/")
94 | urlSections := strings.Split(url, "/")
95 |
96 | if len(routeSections) != len(urlSections) {
97 | return nil, false
98 | }
99 |
100 | variables := map[string]string{}
101 | for i, routeSection := range routeSections {
102 | urlSection := urlSections[i]
103 |
104 | if strings.HasPrefix(routeSection, ":") {
105 | key := routeSection[1:]
106 | variables[key] = urlSection
107 | } else if routeSection != urlSection {
108 | return nil, false
109 | }
110 | }
111 |
112 | return variables, true
113 | }
114 |
115 | func (r *BasicRouter) cacheKey(req *http.Request) string {
116 | return req.Method + req.URL.String()
117 | }
118 |
--------------------------------------------------------------------------------
/router_test.go:
--------------------------------------------------------------------------------
1 | package fireball
2 |
3 | import (
4 | "math/rand"
5 | "net/http"
6 | "net/url"
7 | "reflect"
8 | "testing"
9 | "time"
10 | )
11 |
12 | func nilHandler(*Context) (Response, error) {
13 | return nil, nil
14 | }
15 |
16 | func newRequest(method, path string) *http.Request {
17 | return &http.Request{
18 | Method: method,
19 | URL: &url.URL{
20 | Path: path,
21 | },
22 | }
23 | }
24 |
25 | func newRouteWithNilHandler(method, path string) *Route {
26 | return &Route{
27 | Path: path,
28 | Handlers: map[string]Handler{
29 | method: nilHandler,
30 | },
31 | }
32 | }
33 |
34 | func TestMethodMatch(t *testing.T) {
35 | for _, method := range []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"} {
36 | route := newRouteWithNilHandler(method, "/")
37 | request := newRequest(method, "/")
38 | router := NewBasicRouter([]*Route{route})
39 |
40 | match, err := router.Match(request)
41 | if err != nil {
42 | t.Fatal(err)
43 | }
44 |
45 | if match == nil {
46 | t.Errorf("Error on method '%s': Match was nil", method)
47 | }
48 | }
49 | }
50 |
51 | func TestPathVariableMatch(t *testing.T) {
52 | testCases := []struct {
53 | Route *Route
54 | Request *http.Request
55 | PathVariables map[string]string
56 | }{
57 | {
58 | Route: newRouteWithNilHandler("GET", "/"),
59 | Request: newRequest("GET", "/"),
60 | PathVariables: map[string]string{},
61 | },
62 | {
63 | Route: newRouteWithNilHandler("GET", "/items"),
64 | Request: newRequest("GET", "/items"),
65 | PathVariables: map[string]string{},
66 | },
67 | {
68 | Route: newRouteWithNilHandler("GET", "/items/:itemID"),
69 | Request: newRequest("GET", "/items/item34"),
70 | PathVariables: map[string]string{
71 | "itemID": "item34",
72 | },
73 | },
74 | {
75 | Route: newRouteWithNilHandler("GET", "/items/:itemID/:count"),
76 | Request: newRequest("GET", "/items/item34/83"),
77 | PathVariables: map[string]string{
78 | "itemID": "item34",
79 | "count": "83",
80 | },
81 | },
82 | }
83 |
84 | for _, testCase := range testCases {
85 | router := NewBasicRouter([]*Route{testCase.Route})
86 |
87 | match, err := router.Match(testCase.Request)
88 | if err != nil {
89 | t.Fatal(err)
90 | }
91 |
92 | if v, want := match.PathVariables, testCase.PathVariables; !reflect.DeepEqual(v, want) {
93 | t.Errorf("\nExpected: %#v \nReceived: %#v", want, v)
94 | }
95 | }
96 | }
97 |
98 | func TestNilMatch(t *testing.T) {
99 | testCases := []struct {
100 | Route *Route
101 | Request *http.Request
102 | }{
103 | {
104 | Route: newRouteWithNilHandler("GET", "/"),
105 | Request: newRequest("PUT", "/"),
106 | },
107 | {
108 | Route: newRouteWithNilHandler("GET", "/items"),
109 | Request: newRequest("GET", "/itemss"),
110 | },
111 | {
112 | Route: newRouteWithNilHandler("GET", "/items/:itemID"),
113 | Request: newRequest("GET", "/items/item34/other"),
114 | },
115 | }
116 |
117 | for _, testCase := range testCases {
118 | router := NewBasicRouter([]*Route{testCase.Route})
119 |
120 | match, err := router.Match(testCase.Request)
121 | if err != nil {
122 | t.Fatal(err)
123 | }
124 |
125 | if match != nil {
126 | t.Errorf("Error on Route '%s': Match was not nil", testCase.Route.Path)
127 | }
128 | }
129 | }
130 |
131 | func TestConcurrentAccess(t *testing.T) {
132 | rand.Seed(time.Now().UnixNano())
133 |
134 | router := NewBasicRouter([]*Route{
135 | newRouteWithNilHandler("GET", "/"),
136 | newRouteWithNilHandler("PUT", "/"),
137 | newRouteWithNilHandler("POST", "/"),
138 | newRouteWithNilHandler("DELETE", "/"),
139 | })
140 |
141 | requests := []*http.Request{
142 | newRequest("GET", "/"),
143 | newRequest("PUT", "/"),
144 | newRequest("POST", "/"),
145 | newRequest("DELETE", "/"),
146 | }
147 |
148 | numCalls := 0
149 | done := make(chan bool)
150 | for i := 0; i < 5; i++ {
151 | go func() {
152 | for {
153 | select {
154 | case <-done:
155 | return
156 | default:
157 | i := rand.Int() % len(requests)
158 | req := requests[i]
159 | router.Match(req)
160 | numCalls++
161 | }
162 | }
163 | }()
164 | }
165 |
166 | for numCalls < 1000 {
167 | }
168 |
169 | close(done)
170 | }
171 |
--------------------------------------------------------------------------------
/test_helpers.go:
--------------------------------------------------------------------------------
1 | package fireball
2 |
3 | import (
4 | "encoding/json"
5 | "net/http/httptest"
6 | "testing"
7 |
8 | )
9 |
10 | func RecordJSONResponse(t *testing.T, resp Response, v interface{}) *httptest.ResponseRecorder {
11 | recorder := httptest.NewRecorder()
12 | resp.Write(recorder, nil)
13 | if v != nil {
14 | if err := json.Unmarshal(recorder.Body.Bytes(), v); err != nil {
15 | t.Fatal(err)
16 | }
17 | }
18 |
19 | return recorder
20 | }
21 |
--------------------------------------------------------------------------------
/testing/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Title
5 |
6 |
7 | {{ template "partials/_header" }}
8 | Hello, World!
9 | {{ template "partials/_footer" }}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/testing/partials/_footer.html:
--------------------------------------------------------------------------------
1 | {{ define "_footer" }}Footer
{{ end }}
2 |
--------------------------------------------------------------------------------
/testing/partials/_header.html:
--------------------------------------------------------------------------------
1 | {{ define "_header" }}Header
{{ end }}
2 |
--------------------------------------------------------------------------------
/utilities.go:
--------------------------------------------------------------------------------
1 | package fireball
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | // Redirect wraps http.Redirect in a ResponseFunc
8 | func Redirect(status int, url string) Response {
9 | return ResponseFunc(func(w http.ResponseWriter, r *http.Request) {
10 | http.Redirect(w, r, url, status)
11 | })
12 | }
13 |
14 | // EnableCORS decorates each route by adding CORS headers to each response
15 | // An OPTIONS Handler is added to each route if one doesn't already exist
16 | func EnableCORS(routes []*Route) []*Route {
17 | decorated := Decorate(routes, HeaderResponseDecorator(CORSHeaders))
18 |
19 | for _, route := range decorated {
20 | if _, exists := route.Handlers["OPTIONS"]; !exists {
21 | route.Handlers["OPTIONS"] = func(c *Context) (Response, error) {
22 | return NewResponse(200, nil, CORSHeaders), nil
23 | }
24 | }
25 | }
26 |
27 | return decorated
28 | }
29 |
--------------------------------------------------------------------------------