diff options
author | Matthew Heon <matthew.heon@gmail.com> | 2017-11-01 11:24:59 -0400 |
---|---|---|
committer | Matthew Heon <matthew.heon@gmail.com> | 2017-11-01 11:24:59 -0400 |
commit | a031b83a09a8628435317a03f199cdc18b78262f (patch) | |
tree | bc017a96769ce6de33745b8b0b1304ccf38e9df0 /vendor/github.com/emicklei/go-restful-swagger12 | |
parent | 2b74391cd5281f6fdf391ff8ad50fd1490f6bf89 (diff) | |
download | podman-a031b83a09a8628435317a03f199cdc18b78262f.tar.gz podman-a031b83a09a8628435317a03f199cdc18b78262f.tar.bz2 podman-a031b83a09a8628435317a03f199cdc18b78262f.zip |
Initial checkin from CRI-O repo
Signed-off-by: Matthew Heon <matthew.heon@gmail.com>
Diffstat (limited to 'vendor/github.com/emicklei/go-restful-swagger12')
12 files changed, 1621 insertions, 0 deletions
diff --git a/vendor/github.com/emicklei/go-restful-swagger12/LICENSE b/vendor/github.com/emicklei/go-restful-swagger12/LICENSE new file mode 100644 index 000000000..aeab5b440 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2017 Ernest Micklei + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file diff --git a/vendor/github.com/emicklei/go-restful-swagger12/README.md b/vendor/github.com/emicklei/go-restful-swagger12/README.md new file mode 100644 index 000000000..037b9b09c --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/README.md @@ -0,0 +1,83 @@ +# go-restful-swagger12 + +[![Build Status](https://travis-ci.org/emicklei/go-restful-swagger12.png)](https://travis-ci.org/emicklei/go-restful-swagger12) +[![GoDoc](https://godoc.org/github.com/emicklei/go-restful-swagger12?status.svg)](https://godoc.org/github.com/emicklei/go-restful-swagger12) + +How to use Swagger UI with go-restful += + +Get the Swagger UI sources (version 1.2 only) + + git clone https://github.com/wordnik/swagger-ui.git + +The project contains a "dist" folder. +Its contents has all the Swagger UI files you need. + +The `index.html` has an `url` set to `http://petstore.swagger.wordnik.com/api/api-docs`. +You need to change that to match your WebService JSON endpoint e.g. `http://localhost:8080/apidocs.json` + +Now, you can install the Swagger WebService for serving the Swagger specification in JSON. + + config := swagger.Config{ + WebServices: restful.RegisteredWebServices(), + ApiPath: "/apidocs.json", + SwaggerPath: "/apidocs/", + SwaggerFilePath: "/Users/emicklei/Projects/swagger-ui/dist"} + swagger.InstallSwaggerService(config) + + +Documenting Structs +-- + +Currently there are 2 ways to document your structs in the go-restful Swagger. + +###### By using struct tags +- Use tag "description" to annotate a struct field with a description to show in the UI +- Use tag "modelDescription" to annotate the struct itself with a description to show in the UI. The tag can be added in an field of the struct and in case that there are multiple definition, they will be appended with an empty line. + +###### By using the SwaggerDoc method +Here is an example with an `Address` struct and the documentation for each of the fields. The `""` is a special entry for **documenting the struct itself**. + + type Address struct { + Country string `json:"country,omitempty"` + PostCode int `json:"postcode,omitempty"` + } + + func (Address) SwaggerDoc() map[string]string { + return map[string]string{ + "": "Address doc", + "country": "Country doc", + "postcode": "PostCode doc", + } + } + +This example will generate a JSON like this + + { + "Address": { + "id": "Address", + "description": "Address doc", + "properties": { + "country": { + "type": "string", + "description": "Country doc" + }, + "postcode": { + "type": "integer", + "format": "int32", + "description": "PostCode doc" + } + } + } + } + +**Very Important Notes:** +- `SwaggerDoc()` is using a **NON-Pointer** receiver (e.g. func (Address) and not func (*Address)) +- The returned map should use as key the name of the field as defined in the JSON parameter (e.g. `"postcode"` and not `"PostCode"`) + +Notes +-- +- The Nickname of an Operation is automatically set by finding the name of the function. You can override it using RouteBuilder.Operation(..) +- The WebServices field of swagger.Config can be used to control which service you want to expose and document ; you can have multiple configs and therefore multiple endpoints. + +© 2017, ernestmicklei.com. MIT License. Contributions welcome.
\ No newline at end of file diff --git a/vendor/github.com/emicklei/go-restful-swagger12/api_declaration_list.go b/vendor/github.com/emicklei/go-restful-swagger12/api_declaration_list.go new file mode 100644 index 000000000..9f4c3690a --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/api_declaration_list.go @@ -0,0 +1,64 @@ +package swagger + +// Copyright 2015 Ernest Micklei. All rights reserved. +// Use of this source code is governed by a license +// that can be found in the LICENSE file. + +import ( + "bytes" + "encoding/json" +) + +// ApiDeclarationList maintains an ordered list of ApiDeclaration. +type ApiDeclarationList struct { + List []ApiDeclaration +} + +// At returns the ApiDeclaration by its path unless absent, then ok is false +func (l *ApiDeclarationList) At(path string) (a ApiDeclaration, ok bool) { + for _, each := range l.List { + if each.ResourcePath == path { + return each, true + } + } + return a, false +} + +// Put adds or replaces a ApiDeclaration with this name +func (l *ApiDeclarationList) Put(path string, a ApiDeclaration) { + // maybe replace existing + for i, each := range l.List { + if each.ResourcePath == path { + // replace + l.List[i] = a + return + } + } + // add + l.List = append(l.List, a) +} + +// Do enumerates all the properties, each with its assigned name +func (l *ApiDeclarationList) Do(block func(path string, decl ApiDeclaration)) { + for _, each := range l.List { + block(each.ResourcePath, each) + } +} + +// MarshalJSON writes the ModelPropertyList as if it was a map[string]ModelProperty +func (l ApiDeclarationList) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + buf.WriteString("{\n") + for i, each := range l.List { + buf.WriteString("\"") + buf.WriteString(each.ResourcePath) + buf.WriteString("\": ") + encoder.Encode(each) + if i < len(l.List)-1 { + buf.WriteString(",\n") + } + } + buf.WriteString("}") + return buf.Bytes(), nil +} diff --git a/vendor/github.com/emicklei/go-restful-swagger12/config.go b/vendor/github.com/emicklei/go-restful-swagger12/config.go new file mode 100644 index 000000000..18f8e57d9 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/config.go @@ -0,0 +1,46 @@ +package swagger + +import ( + "net/http" + "reflect" + + "github.com/emicklei/go-restful" +) + +// PostBuildDeclarationMapFunc can be used to modify the api declaration map. +type PostBuildDeclarationMapFunc func(apiDeclarationMap *ApiDeclarationList) + +// MapSchemaFormatFunc can be used to modify typeName at definition time. +type MapSchemaFormatFunc func(typeName string) string + +// MapModelTypeNameFunc can be used to return the desired typeName for a given +// type. It will return false if the default name should be used. +type MapModelTypeNameFunc func(t reflect.Type) (string, bool) + +type Config struct { + // url where the services are available, e.g. http://localhost:8080 + // if left empty then the basePath of Swagger is taken from the actual request + WebServicesUrl string + // path where the JSON api is avaiable , e.g. /apidocs + ApiPath string + // [optional] path where the swagger UI will be served, e.g. /swagger + SwaggerPath string + // [optional] location of folder containing Swagger HTML5 application index.html + SwaggerFilePath string + // api listing is constructed from this list of restful WebServices. + WebServices []*restful.WebService + // will serve all static content (scripts,pages,images) + StaticHandler http.Handler + // [optional] on default CORS (Cross-Origin-Resource-Sharing) is enabled. + DisableCORS bool + // Top-level API version. Is reflected in the resource listing. + ApiVersion string + // If set then call this handler after building the complete ApiDeclaration Map + PostBuildHandler PostBuildDeclarationMapFunc + // Swagger global info struct + Info Info + // [optional] If set, model builder should call this handler to get addition typename-to-swagger-format-field conversion. + SchemaFormatHandler MapSchemaFormatFunc + // [optional] If set, model builder should call this handler to retrieve the name for a given type. + ModelTypeNameHandler MapModelTypeNameFunc +} diff --git a/vendor/github.com/emicklei/go-restful-swagger12/model_builder.go b/vendor/github.com/emicklei/go-restful-swagger12/model_builder.go new file mode 100644 index 000000000..d40786f25 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/model_builder.go @@ -0,0 +1,467 @@ +package swagger + +import ( + "encoding/json" + "reflect" + "strings" +) + +// ModelBuildable is used for extending Structs that need more control over +// how the Model appears in the Swagger api declaration. +type ModelBuildable interface { + PostBuildModel(m *Model) *Model +} + +type modelBuilder struct { + Models *ModelList + Config *Config +} + +type documentable interface { + SwaggerDoc() map[string]string +} + +// Check if this structure has a method with signature func (<theModel>) SwaggerDoc() map[string]string +// If it exists, retrive the documentation and overwrite all struct tag descriptions +func getDocFromMethodSwaggerDoc2(model reflect.Type) map[string]string { + if docable, ok := reflect.New(model).Elem().Interface().(documentable); ok { + return docable.SwaggerDoc() + } + return make(map[string]string) +} + +// addModelFrom creates and adds a Model to the builder and detects and calls +// the post build hook for customizations +func (b modelBuilder) addModelFrom(sample interface{}) { + if modelOrNil := b.addModel(reflect.TypeOf(sample), ""); modelOrNil != nil { + // allow customizations + if buildable, ok := sample.(ModelBuildable); ok { + modelOrNil = buildable.PostBuildModel(modelOrNil) + b.Models.Put(modelOrNil.Id, *modelOrNil) + } + } +} + +func (b modelBuilder) addModel(st reflect.Type, nameOverride string) *Model { + // Turn pointers into simpler types so further checks are + // correct. + if st.Kind() == reflect.Ptr { + st = st.Elem() + } + + modelName := b.keyFrom(st) + if nameOverride != "" { + modelName = nameOverride + } + // no models needed for primitive types + if b.isPrimitiveType(modelName) { + return nil + } + // golang encoding/json packages says array and slice values encode as + // JSON arrays, except that []byte encodes as a base64-encoded string. + // If we see a []byte here, treat it at as a primitive type (string) + // and deal with it in buildArrayTypeProperty. + if (st.Kind() == reflect.Slice || st.Kind() == reflect.Array) && + st.Elem().Kind() == reflect.Uint8 { + return nil + } + // see if we already have visited this model + if _, ok := b.Models.At(modelName); ok { + return nil + } + sm := Model{ + Id: modelName, + Required: []string{}, + Properties: ModelPropertyList{}} + + // reference the model before further initializing (enables recursive structs) + b.Models.Put(modelName, sm) + + // check for slice or array + if st.Kind() == reflect.Slice || st.Kind() == reflect.Array { + b.addModel(st.Elem(), "") + return &sm + } + // check for structure or primitive type + if st.Kind() != reflect.Struct { + return &sm + } + + fullDoc := getDocFromMethodSwaggerDoc2(st) + modelDescriptions := []string{} + + for i := 0; i < st.NumField(); i++ { + field := st.Field(i) + jsonName, modelDescription, prop := b.buildProperty(field, &sm, modelName) + if len(modelDescription) > 0 { + modelDescriptions = append(modelDescriptions, modelDescription) + } + + // add if not omitted + if len(jsonName) != 0 { + // update description + if fieldDoc, ok := fullDoc[jsonName]; ok { + prop.Description = fieldDoc + } + // update Required + if b.isPropertyRequired(field) { + sm.Required = append(sm.Required, jsonName) + } + sm.Properties.Put(jsonName, prop) + } + } + + // We always overwrite documentation if SwaggerDoc method exists + // "" is special for documenting the struct itself + if modelDoc, ok := fullDoc[""]; ok { + sm.Description = modelDoc + } else if len(modelDescriptions) != 0 { + sm.Description = strings.Join(modelDescriptions, "\n") + } + + // update model builder with completed model + b.Models.Put(modelName, sm) + + return &sm +} + +func (b modelBuilder) isPropertyRequired(field reflect.StructField) bool { + required := true + if jsonTag := field.Tag.Get("json"); jsonTag != "" { + s := strings.Split(jsonTag, ",") + if len(s) > 1 && s[1] == "omitempty" { + return false + } + } + return required +} + +func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, modelName string) (jsonName, modelDescription string, prop ModelProperty) { + jsonName = b.jsonNameOfField(field) + if len(jsonName) == 0 { + // empty name signals skip property + return "", "", prop + } + + if field.Name == "XMLName" && field.Type.String() == "xml.Name" { + // property is metadata for the xml.Name attribute, can be skipped + return "", "", prop + } + + if tag := field.Tag.Get("modelDescription"); tag != "" { + modelDescription = tag + } + + prop.setPropertyMetadata(field) + if prop.Type != nil { + return jsonName, modelDescription, prop + } + fieldType := field.Type + + // check if type is doing its own marshalling + marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem() + if fieldType.Implements(marshalerType) { + var pType = "string" + if prop.Type == nil { + prop.Type = &pType + } + if prop.Format == "" { + prop.Format = b.jsonSchemaFormat(b.keyFrom(fieldType)) + } + return jsonName, modelDescription, prop + } + + // check if annotation says it is a string + if jsonTag := field.Tag.Get("json"); jsonTag != "" { + s := strings.Split(jsonTag, ",") + if len(s) > 1 && s[1] == "string" { + stringt := "string" + prop.Type = &stringt + return jsonName, modelDescription, prop + } + } + + fieldKind := fieldType.Kind() + switch { + case fieldKind == reflect.Struct: + jsonName, prop := b.buildStructTypeProperty(field, jsonName, model) + return jsonName, modelDescription, prop + case fieldKind == reflect.Slice || fieldKind == reflect.Array: + jsonName, prop := b.buildArrayTypeProperty(field, jsonName, modelName) + return jsonName, modelDescription, prop + case fieldKind == reflect.Ptr: + jsonName, prop := b.buildPointerTypeProperty(field, jsonName, modelName) + return jsonName, modelDescription, prop + case fieldKind == reflect.String: + stringt := "string" + prop.Type = &stringt + return jsonName, modelDescription, prop + case fieldKind == reflect.Map: + // if it's a map, it's unstructured, and swagger 1.2 can't handle it + objectType := "object" + prop.Type = &objectType + return jsonName, modelDescription, prop + } + + fieldTypeName := b.keyFrom(fieldType) + if b.isPrimitiveType(fieldTypeName) { + mapped := b.jsonSchemaType(fieldTypeName) + prop.Type = &mapped + prop.Format = b.jsonSchemaFormat(fieldTypeName) + return jsonName, modelDescription, prop + } + modelType := b.keyFrom(fieldType) + prop.Ref = &modelType + + if fieldType.Name() == "" { // override type of anonymous structs + nestedTypeName := modelName + "." + jsonName + prop.Ref = &nestedTypeName + b.addModel(fieldType, nestedTypeName) + } + return jsonName, modelDescription, prop +} + +func hasNamedJSONTag(field reflect.StructField) bool { + parts := strings.Split(field.Tag.Get("json"), ",") + if len(parts) == 0 { + return false + } + for _, s := range parts[1:] { + if s == "inline" { + return false + } + } + return len(parts[0]) > 0 +} + +func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonName string, model *Model) (nameJson string, prop ModelProperty) { + prop.setPropertyMetadata(field) + // Check for type override in tag + if prop.Type != nil { + return jsonName, prop + } + fieldType := field.Type + // check for anonymous + if len(fieldType.Name()) == 0 { + // anonymous + anonType := model.Id + "." + jsonName + b.addModel(fieldType, anonType) + prop.Ref = &anonType + return jsonName, prop + } + + if field.Name == fieldType.Name() && field.Anonymous && !hasNamedJSONTag(field) { + // embedded struct + sub := modelBuilder{new(ModelList), b.Config} + sub.addModel(fieldType, "") + subKey := sub.keyFrom(fieldType) + // merge properties from sub + subModel, _ := sub.Models.At(subKey) + subModel.Properties.Do(func(k string, v ModelProperty) { + model.Properties.Put(k, v) + // if subModel says this property is required then include it + required := false + for _, each := range subModel.Required { + if k == each { + required = true + break + } + } + if required { + model.Required = append(model.Required, k) + } + }) + // add all new referenced models + sub.Models.Do(func(key string, sub Model) { + if key != subKey { + if _, ok := b.Models.At(key); !ok { + b.Models.Put(key, sub) + } + } + }) + // empty name signals skip property + return "", prop + } + // simple struct + b.addModel(fieldType, "") + var pType = b.keyFrom(fieldType) + prop.Ref = &pType + return jsonName, prop +} + +func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) { + // check for type override in tags + prop.setPropertyMetadata(field) + if prop.Type != nil { + return jsonName, prop + } + fieldType := field.Type + if fieldType.Elem().Kind() == reflect.Uint8 { + stringt := "string" + prop.Type = &stringt + return jsonName, prop + } + var pType = "array" + prop.Type = &pType + isPrimitive := b.isPrimitiveType(fieldType.Elem().Name()) + elemTypeName := b.getElementTypeName(modelName, jsonName, fieldType.Elem()) + prop.Items = new(Item) + if isPrimitive { + mapped := b.jsonSchemaType(elemTypeName) + prop.Items.Type = &mapped + } else { + prop.Items.Ref = &elemTypeName + } + // add|overwrite model for element type + if fieldType.Elem().Kind() == reflect.Ptr { + fieldType = fieldType.Elem() + } + if !isPrimitive { + b.addModel(fieldType.Elem(), elemTypeName) + } + return jsonName, prop +} + +func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) { + prop.setPropertyMetadata(field) + // Check for type override in tags + if prop.Type != nil { + return jsonName, prop + } + fieldType := field.Type + + // override type of pointer to list-likes + if fieldType.Elem().Kind() == reflect.Slice || fieldType.Elem().Kind() == reflect.Array { + var pType = "array" + prop.Type = &pType + isPrimitive := b.isPrimitiveType(fieldType.Elem().Elem().Name()) + elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem().Elem()) + if isPrimitive { + primName := b.jsonSchemaType(elemName) + prop.Items = &Item{Ref: &primName} + } else { + prop.Items = &Item{Ref: &elemName} + } + if !isPrimitive { + // add|overwrite model for element type + b.addModel(fieldType.Elem().Elem(), elemName) + } + } else { + // non-array, pointer type + fieldTypeName := b.keyFrom(fieldType.Elem()) + var pType = b.jsonSchemaType(fieldTypeName) // no star, include pkg path + if b.isPrimitiveType(fieldTypeName) { + prop.Type = &pType + prop.Format = b.jsonSchemaFormat(fieldTypeName) + return jsonName, prop + } + prop.Ref = &pType + elemName := "" + if fieldType.Elem().Name() == "" { + elemName = modelName + "." + jsonName + prop.Ref = &elemName + } + b.addModel(fieldType.Elem(), elemName) + } + return jsonName, prop +} + +func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.Type) string { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Name() == "" { + return modelName + "." + jsonName + } + return b.keyFrom(t) +} + +func (b modelBuilder) keyFrom(st reflect.Type) string { + key := st.String() + if b.Config != nil && b.Config.ModelTypeNameHandler != nil { + if name, ok := b.Config.ModelTypeNameHandler(st); ok { + key = name + } + } + if len(st.Name()) == 0 { // unnamed type + // Swagger UI has special meaning for [ + key = strings.Replace(key, "[]", "||", -1) + } + return key +} + +// see also https://golang.org/ref/spec#Numeric_types +func (b modelBuilder) isPrimitiveType(modelName string) bool { + if len(modelName) == 0 { + return false + } + return strings.Contains("uint uint8 uint16 uint32 uint64 int int8 int16 int32 int64 float32 float64 bool string byte rune time.Time", modelName) +} + +// jsonNameOfField returns the name of the field as it should appear in JSON format +// An empty string indicates that this field is not part of the JSON representation +func (b modelBuilder) jsonNameOfField(field reflect.StructField) string { + if jsonTag := field.Tag.Get("json"); jsonTag != "" { + s := strings.Split(jsonTag, ",") + if s[0] == "-" { + // empty name signals skip property + return "" + } else if s[0] != "" { + return s[0] + } + } + return field.Name +} + +// see also http://json-schema.org/latest/json-schema-core.html#anchor8 +func (b modelBuilder) jsonSchemaType(modelName string) string { + schemaMap := map[string]string{ + "uint": "integer", + "uint8": "integer", + "uint16": "integer", + "uint32": "integer", + "uint64": "integer", + + "int": "integer", + "int8": "integer", + "int16": "integer", + "int32": "integer", + "int64": "integer", + + "byte": "integer", + "float64": "number", + "float32": "number", + "bool": "boolean", + "time.Time": "string", + } + mapped, ok := schemaMap[modelName] + if !ok { + return modelName // use as is (custom or struct) + } + return mapped +} + +func (b modelBuilder) jsonSchemaFormat(modelName string) string { + if b.Config != nil && b.Config.SchemaFormatHandler != nil { + if mapped := b.Config.SchemaFormatHandler(modelName); mapped != "" { + return mapped + } + } + schemaMap := map[string]string{ + "int": "int32", + "int32": "int32", + "int64": "int64", + "byte": "byte", + "uint": "integer", + "uint8": "byte", + "float64": "double", + "float32": "float", + "time.Time": "date-time", + "*time.Time": "date-time", + } + mapped, ok := schemaMap[modelName] + if !ok { + return "" // no format + } + return mapped +} diff --git a/vendor/github.com/emicklei/go-restful-swagger12/model_list.go b/vendor/github.com/emicklei/go-restful-swagger12/model_list.go new file mode 100644 index 000000000..9bb6cb678 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/model_list.go @@ -0,0 +1,86 @@ +package swagger + +// Copyright 2015 Ernest Micklei. All rights reserved. +// Use of this source code is governed by a license +// that can be found in the LICENSE file. + +import ( + "bytes" + "encoding/json" +) + +// NamedModel associates a name with a Model (not using its Id) +type NamedModel struct { + Name string + Model Model +} + +// ModelList encapsulates a list of NamedModel (association) +type ModelList struct { + List []NamedModel +} + +// Put adds or replaces a Model by its name +func (l *ModelList) Put(name string, model Model) { + for i, each := range l.List { + if each.Name == name { + // replace + l.List[i] = NamedModel{name, model} + return + } + } + // add + l.List = append(l.List, NamedModel{name, model}) +} + +// At returns a Model by its name, ok is false if absent +func (l *ModelList) At(name string) (m Model, ok bool) { + for _, each := range l.List { + if each.Name == name { + return each.Model, true + } + } + return m, false +} + +// Do enumerates all the models, each with its assigned name +func (l *ModelList) Do(block func(name string, value Model)) { + for _, each := range l.List { + block(each.Name, each.Model) + } +} + +// MarshalJSON writes the ModelList as if it was a map[string]Model +func (l ModelList) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + buf.WriteString("{\n") + for i, each := range l.List { + buf.WriteString("\"") + buf.WriteString(each.Name) + buf.WriteString("\": ") + encoder.Encode(each.Model) + if i < len(l.List)-1 { + buf.WriteString(",\n") + } + } + buf.WriteString("}") + return buf.Bytes(), nil +} + +// UnmarshalJSON reads back a ModelList. This is an expensive operation. +func (l *ModelList) UnmarshalJSON(data []byte) error { + raw := map[string]interface{}{} + json.NewDecoder(bytes.NewReader(data)).Decode(&raw) + for k, v := range raw { + // produces JSON bytes for each value + data, err := json.Marshal(v) + if err != nil { + return err + } + var m Model + json.NewDecoder(bytes.NewReader(data)).Decode(&m) + l.Put(k, m) + } + return nil +} diff --git a/vendor/github.com/emicklei/go-restful-swagger12/model_property_ext.go b/vendor/github.com/emicklei/go-restful-swagger12/model_property_ext.go new file mode 100644 index 000000000..a433b6b70 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/model_property_ext.go @@ -0,0 +1,81 @@ +package swagger + +import ( + "reflect" + "strings" +) + +func (prop *ModelProperty) setDescription(field reflect.StructField) { + if tag := field.Tag.Get("description"); tag != "" { + prop.Description = tag + } +} + +func (prop *ModelProperty) setDefaultValue(field reflect.StructField) { + if tag := field.Tag.Get("default"); tag != "" { + prop.DefaultValue = Special(tag) + } +} + +func (prop *ModelProperty) setEnumValues(field reflect.StructField) { + // We use | to separate the enum values. This value is chosen + // since its unlikely to be useful in actual enumeration values. + if tag := field.Tag.Get("enum"); tag != "" { + prop.Enum = strings.Split(tag, "|") + } +} + +func (prop *ModelProperty) setMaximum(field reflect.StructField) { + if tag := field.Tag.Get("maximum"); tag != "" { + prop.Maximum = tag + } +} + +func (prop *ModelProperty) setType(field reflect.StructField) { + if tag := field.Tag.Get("type"); tag != "" { + // Check if the first two characters of the type tag are + // intended to emulate slice/array behaviour. + // + // If type is intended to be a slice/array then add the + // overriden type to the array item instead of the main property + if len(tag) > 2 && tag[0:2] == "[]" { + pType := "array" + prop.Type = &pType + prop.Items = new(Item) + + iType := tag[2:] + prop.Items.Type = &iType + return + } + + prop.Type = &tag + } +} + +func (prop *ModelProperty) setMinimum(field reflect.StructField) { + if tag := field.Tag.Get("minimum"); tag != "" { + prop.Minimum = tag + } +} + +func (prop *ModelProperty) setUniqueItems(field reflect.StructField) { + tag := field.Tag.Get("unique") + switch tag { + case "true": + v := true + prop.UniqueItems = &v + case "false": + v := false + prop.UniqueItems = &v + } +} + +func (prop *ModelProperty) setPropertyMetadata(field reflect.StructField) { + prop.setDescription(field) + prop.setEnumValues(field) + prop.setMinimum(field) + prop.setMaximum(field) + prop.setUniqueItems(field) + prop.setDefaultValue(field) + prop.setType(field) +} diff --git a/vendor/github.com/emicklei/go-restful-swagger12/model_property_list.go b/vendor/github.com/emicklei/go-restful-swagger12/model_property_list.go new file mode 100644 index 000000000..3babb1944 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/model_property_list.go @@ -0,0 +1,87 @@ +package swagger + +// Copyright 2015 Ernest Micklei. All rights reserved. +// Use of this source code is governed by a license +// that can be found in the LICENSE file. + +import ( + "bytes" + "encoding/json" +) + +// NamedModelProperty associates a name to a ModelProperty +type NamedModelProperty struct { + Name string + Property ModelProperty +} + +// ModelPropertyList encapsulates a list of NamedModelProperty (association) +type ModelPropertyList struct { + List []NamedModelProperty +} + +// At returns the ModelPropety by its name unless absent, then ok is false +func (l *ModelPropertyList) At(name string) (p ModelProperty, ok bool) { + for _, each := range l.List { + if each.Name == name { + return each.Property, true + } + } + return p, false +} + +// Put adds or replaces a ModelProperty with this name +func (l *ModelPropertyList) Put(name string, prop ModelProperty) { + // maybe replace existing + for i, each := range l.List { + if each.Name == name { + // replace + l.List[i] = NamedModelProperty{Name: name, Property: prop} + return + } + } + // add + l.List = append(l.List, NamedModelProperty{Name: name, Property: prop}) +} + +// Do enumerates all the properties, each with its assigned name +func (l *ModelPropertyList) Do(block func(name string, value ModelProperty)) { + for _, each := range l.List { + block(each.Name, each.Property) + } +} + +// MarshalJSON writes the ModelPropertyList as if it was a map[string]ModelProperty +func (l ModelPropertyList) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + buf.WriteString("{\n") + for i, each := range l.List { + buf.WriteString("\"") + buf.WriteString(each.Name) + buf.WriteString("\": ") + encoder.Encode(each.Property) + if i < len(l.List)-1 { + buf.WriteString(",\n") + } + } + buf.WriteString("}") + return buf.Bytes(), nil +} + +// UnmarshalJSON reads back a ModelPropertyList. This is an expensive operation. +func (l *ModelPropertyList) UnmarshalJSON(data []byte) error { + raw := map[string]interface{}{} + json.NewDecoder(bytes.NewReader(data)).Decode(&raw) + for k, v := range raw { + // produces JSON bytes for each value + data, err := json.Marshal(v) + if err != nil { + return err + } + var m ModelProperty + json.NewDecoder(bytes.NewReader(data)).Decode(&m) + l.Put(k, m) + } + return nil +} diff --git a/vendor/github.com/emicklei/go-restful-swagger12/ordered_route_map.go b/vendor/github.com/emicklei/go-restful-swagger12/ordered_route_map.go new file mode 100644 index 000000000..b33ccfbeb --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/ordered_route_map.go @@ -0,0 +1,36 @@ +package swagger + +// Copyright 2015 Ernest Micklei. All rights reserved. +// Use of this source code is governed by a license +// that can be found in the LICENSE file. + +import "github.com/emicklei/go-restful" + +type orderedRouteMap struct { + elements map[string][]restful.Route + keys []string +} + +func newOrderedRouteMap() *orderedRouteMap { + return &orderedRouteMap{ + elements: map[string][]restful.Route{}, + keys: []string{}, + } +} + +func (o *orderedRouteMap) Add(key string, route restful.Route) { + routes, ok := o.elements[key] + if ok { + routes = append(routes, route) + o.elements[key] = routes + return + } + o.elements[key] = []restful.Route{route} + o.keys = append(o.keys, key) +} + +func (o *orderedRouteMap) Do(block func(key string, routes []restful.Route)) { + for _, k := range o.keys { + block(k, o.elements[k]) + } +} diff --git a/vendor/github.com/emicklei/go-restful-swagger12/swagger.go b/vendor/github.com/emicklei/go-restful-swagger12/swagger.go new file mode 100644 index 000000000..9c40833e7 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/swagger.go @@ -0,0 +1,185 @@ +// Package swagger implements the structures of the Swagger +// https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md +package swagger + +const swaggerVersion = "1.2" + +// 4.3.3 Data Type Fields +type DataTypeFields struct { + Type *string `json:"type,omitempty"` // if Ref not used + Ref *string `json:"$ref,omitempty"` // if Type not used + Format string `json:"format,omitempty"` + DefaultValue Special `json:"defaultValue,omitempty"` + Enum []string `json:"enum,omitempty"` + Minimum string `json:"minimum,omitempty"` + Maximum string `json:"maximum,omitempty"` + Items *Item `json:"items,omitempty"` + UniqueItems *bool `json:"uniqueItems,omitempty"` +} + +type Special string + +// 4.3.4 Items Object +type Item struct { + Type *string `json:"type,omitempty"` + Ref *string `json:"$ref,omitempty"` + Format string `json:"format,omitempty"` +} + +// 5.1 Resource Listing +type ResourceListing struct { + SwaggerVersion string `json:"swaggerVersion"` // e.g 1.2 + Apis []Resource `json:"apis"` + ApiVersion string `json:"apiVersion"` + Info Info `json:"info"` + Authorizations []Authorization `json:"authorizations,omitempty"` +} + +// 5.1.2 Resource Object +type Resource struct { + Path string `json:"path"` // relative or absolute, must start with / + Description string `json:"description"` +} + +// 5.1.3 Info Object +type Info struct { + Title string `json:"title"` + Description string `json:"description"` + TermsOfServiceUrl string `json:"termsOfServiceUrl,omitempty"` + Contact string `json:"contact,omitempty"` + License string `json:"license,omitempty"` + LicenseUrl string `json:"licenseUrl,omitempty"` +} + +// 5.1.5 +type Authorization struct { + Type string `json:"type"` + PassAs string `json:"passAs"` + Keyname string `json:"keyname"` + Scopes []Scope `json:"scopes"` + GrantTypes []GrantType `json:"grandTypes"` +} + +// 5.1.6, 5.2.11 +type Scope struct { + // Required. The name of the scope. + Scope string `json:"scope"` + // Recommended. A short description of the scope. + Description string `json:"description"` +} + +// 5.1.7 +type GrantType struct { + Implicit Implicit `json:"implicit"` + AuthorizationCode AuthorizationCode `json:"authorization_code"` +} + +// 5.1.8 Implicit Object +type Implicit struct { + // Required. The login endpoint definition. + loginEndpoint LoginEndpoint `json:"loginEndpoint"` + // An optional alternative name to standard "access_token" OAuth2 parameter. + TokenName string `json:"tokenName"` +} + +// 5.1.9 Authorization Code Object +type AuthorizationCode struct { + TokenRequestEndpoint TokenRequestEndpoint `json:"tokenRequestEndpoint"` + TokenEndpoint TokenEndpoint `json:"tokenEndpoint"` +} + +// 5.1.10 Login Endpoint Object +type LoginEndpoint struct { + // Required. The URL of the authorization endpoint for the implicit grant flow. The value SHOULD be in a URL format. + Url string `json:"url"` +} + +// 5.1.11 Token Request Endpoint Object +type TokenRequestEndpoint struct { + // Required. The URL of the authorization endpoint for the authentication code grant flow. The value SHOULD be in a URL format. + Url string `json:"url"` + // An optional alternative name to standard "client_id" OAuth2 parameter. + ClientIdName string `json:"clientIdName"` + // An optional alternative name to the standard "client_secret" OAuth2 parameter. + ClientSecretName string `json:"clientSecretName"` +} + +// 5.1.12 Token Endpoint Object +type TokenEndpoint struct { + // Required. The URL of the token endpoint for the authentication code grant flow. The value SHOULD be in a URL format. + Url string `json:"url"` + // An optional alternative name to standard "access_token" OAuth2 parameter. + TokenName string `json:"tokenName"` +} + +// 5.2 API Declaration +type ApiDeclaration struct { + SwaggerVersion string `json:"swaggerVersion"` + ApiVersion string `json:"apiVersion"` + BasePath string `json:"basePath"` + ResourcePath string `json:"resourcePath"` // must start with / + Info Info `json:"info"` + Apis []Api `json:"apis,omitempty"` + Models ModelList `json:"models,omitempty"` + Produces []string `json:"produces,omitempty"` + Consumes []string `json:"consumes,omitempty"` + Authorizations []Authorization `json:"authorizations,omitempty"` +} + +// 5.2.2 API Object +type Api struct { + Path string `json:"path"` // relative or absolute, must start with / + Description string `json:"description"` + Operations []Operation `json:"operations,omitempty"` +} + +// 5.2.3 Operation Object +type Operation struct { + DataTypeFields + Method string `json:"method"` + Summary string `json:"summary,omitempty"` + Notes string `json:"notes,omitempty"` + Nickname string `json:"nickname"` + Authorizations []Authorization `json:"authorizations,omitempty"` + Parameters []Parameter `json:"parameters"` + ResponseMessages []ResponseMessage `json:"responseMessages,omitempty"` // optional + Produces []string `json:"produces,omitempty"` + Consumes []string `json:"consumes,omitempty"` + Deprecated string `json:"deprecated,omitempty"` +} + +// 5.2.4 Parameter Object +type Parameter struct { + DataTypeFields + ParamType string `json:"paramType"` // path,query,body,header,form + Name string `json:"name"` + Description string `json:"description"` + Required bool `json:"required"` + AllowMultiple bool `json:"allowMultiple"` +} + +// 5.2.5 Response Message Object +type ResponseMessage struct { + Code int `json:"code"` + Message string `json:"message"` + ResponseModel string `json:"responseModel,omitempty"` +} + +// 5.2.6, 5.2.7 Models Object +type Model struct { + Id string `json:"id"` + Description string `json:"description,omitempty"` + Required []string `json:"required,omitempty"` + Properties ModelPropertyList `json:"properties"` + SubTypes []string `json:"subTypes,omitempty"` + Discriminator string `json:"discriminator,omitempty"` +} + +// 5.2.8 Properties Object +type ModelProperty struct { + DataTypeFields + Description string `json:"description,omitempty"` +} + +// 5.2.10 +type Authorizations map[string]Authorization diff --git a/vendor/github.com/emicklei/go-restful-swagger12/swagger_builder.go b/vendor/github.com/emicklei/go-restful-swagger12/swagger_builder.go new file mode 100644 index 000000000..05a3c7e76 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/swagger_builder.go @@ -0,0 +1,21 @@ +package swagger + +type SwaggerBuilder struct { + SwaggerService +} + +func NewSwaggerBuilder(config Config) *SwaggerBuilder { + return &SwaggerBuilder{*newSwaggerService(config)} +} + +func (sb SwaggerBuilder) ProduceListing() ResourceListing { + return sb.SwaggerService.produceListing() +} + +func (sb SwaggerBuilder) ProduceAllDeclarations() map[string]ApiDeclaration { + return sb.SwaggerService.produceAllDeclarations() +} + +func (sb SwaggerBuilder) ProduceDeclarations(route string) (*ApiDeclaration, bool) { + return sb.SwaggerService.produceDeclarations(route) +} diff --git a/vendor/github.com/emicklei/go-restful-swagger12/swagger_webservice.go b/vendor/github.com/emicklei/go-restful-swagger12/swagger_webservice.go new file mode 100644 index 000000000..d90623120 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful-swagger12/swagger_webservice.go @@ -0,0 +1,443 @@ +package swagger + +import ( + "fmt" + + "github.com/emicklei/go-restful" + // "github.com/emicklei/hopwatch" + "net/http" + "reflect" + "sort" + "strings" + + "github.com/emicklei/go-restful/log" +) + +type SwaggerService struct { + config Config + apiDeclarationMap *ApiDeclarationList +} + +func newSwaggerService(config Config) *SwaggerService { + sws := &SwaggerService{ + config: config, + apiDeclarationMap: new(ApiDeclarationList)} + + // Build all ApiDeclarations + for _, each := range config.WebServices { + rootPath := each.RootPath() + // skip the api service itself + if rootPath != config.ApiPath { + if rootPath == "" || rootPath == "/" { + // use routes + for _, route := range each.Routes() { + entry := staticPathFromRoute(route) + _, exists := sws.apiDeclarationMap.At(entry) + if !exists { + sws.apiDeclarationMap.Put(entry, sws.composeDeclaration(each, entry)) + } + } + } else { // use root path + sws.apiDeclarationMap.Put(each.RootPath(), sws.composeDeclaration(each, each.RootPath())) + } + } + } + + // if specified then call the PostBuilderHandler + if config.PostBuildHandler != nil { + config.PostBuildHandler(sws.apiDeclarationMap) + } + return sws +} + +// LogInfo is the function that is called when this package needs to log. It defaults to log.Printf +var LogInfo = func(format string, v ...interface{}) { + // use the restful package-wide logger + log.Printf(format, v...) +} + +// InstallSwaggerService add the WebService that provides the API documentation of all services +// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki). +func InstallSwaggerService(aSwaggerConfig Config) { + RegisterSwaggerService(aSwaggerConfig, restful.DefaultContainer) +} + +// RegisterSwaggerService add the WebService that provides the API documentation of all services +// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki). +func RegisterSwaggerService(config Config, wsContainer *restful.Container) { + sws := newSwaggerService(config) + ws := new(restful.WebService) + ws.Path(config.ApiPath) + ws.Produces(restful.MIME_JSON) + if config.DisableCORS { + ws.Filter(enableCORS) + } + ws.Route(ws.GET("/").To(sws.getListing)) + ws.Route(ws.GET("/{a}").To(sws.getDeclarations)) + ws.Route(ws.GET("/{a}/{b}").To(sws.getDeclarations)) + ws.Route(ws.GET("/{a}/{b}/{c}").To(sws.getDeclarations)) + ws.Route(ws.GET("/{a}/{b}/{c}/{d}").To(sws.getDeclarations)) + ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}").To(sws.getDeclarations)) + ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}").To(sws.getDeclarations)) + ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}/{g}").To(sws.getDeclarations)) + LogInfo("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath) + wsContainer.Add(ws) + + // Check paths for UI serving + if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" { + swaggerPathSlash := config.SwaggerPath + // path must end with slash / + if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] { + LogInfo("[restful/swagger] use corrected SwaggerPath ; must end with slash (/)") + swaggerPathSlash += "/" + } + + LogInfo("[restful/swagger] %v%v is mapped to folder %v", config.WebServicesUrl, swaggerPathSlash, config.SwaggerFilePath) + wsContainer.Handle(swaggerPathSlash, http.StripPrefix(swaggerPathSlash, http.FileServer(http.Dir(config.SwaggerFilePath)))) + + //if we define a custom static handler use it + } else if config.StaticHandler != nil && config.SwaggerPath != "" { + swaggerPathSlash := config.SwaggerPath + // path must end with slash / + if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] { + LogInfo("[restful/swagger] use corrected SwaggerFilePath ; must end with slash (/)") + swaggerPathSlash += "/" + + } + LogInfo("[restful/swagger] %v%v is mapped to custom Handler %T", config.WebServicesUrl, swaggerPathSlash, config.StaticHandler) + wsContainer.Handle(swaggerPathSlash, config.StaticHandler) + + } else { + LogInfo("[restful/swagger] Swagger(File)Path is empty ; no UI is served") + } +} + +func staticPathFromRoute(r restful.Route) string { + static := r.Path + bracket := strings.Index(static, "{") + if bracket <= 1 { // result cannot be empty + return static + } + if bracket != -1 { + static = r.Path[:bracket] + } + if strings.HasSuffix(static, "/") { + return static[:len(static)-1] + } else { + return static + } +} + +func enableCORS(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { + if origin := req.HeaderParameter(restful.HEADER_Origin); origin != "" { + // prevent duplicate header + if len(resp.Header().Get(restful.HEADER_AccessControlAllowOrigin)) == 0 { + resp.AddHeader(restful.HEADER_AccessControlAllowOrigin, origin) + } + } + chain.ProcessFilter(req, resp) +} + +func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Response) { + listing := sws.produceListing() + resp.WriteAsJson(listing) +} + +func (sws SwaggerService) produceListing() ResourceListing { + listing := ResourceListing{SwaggerVersion: swaggerVersion, ApiVersion: sws.config.ApiVersion, Info: sws.config.Info} + sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) { + ref := Resource{Path: k} + if len(v.Apis) > 0 { // use description of first (could still be empty) + ref.Description = v.Apis[0].Description + } + listing.Apis = append(listing.Apis, ref) + }) + return listing +} + +func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Response) { + decl, ok := sws.produceDeclarations(composeRootPath(req)) + if !ok { + resp.WriteErrorString(http.StatusNotFound, "ApiDeclaration not found") + return + } + // unless WebServicesUrl is given + if len(sws.config.WebServicesUrl) == 0 { + // update base path from the actual request + // TODO how to detect https? assume http for now + var host string + // X-Forwarded-Host or Host or Request.Host + hostvalues, ok := req.Request.Header["X-Forwarded-Host"] // apache specific? + if !ok || len(hostvalues) == 0 { + forwarded, ok := req.Request.Header["Host"] // without reverse-proxy + if !ok || len(forwarded) == 0 { + // fallback to Host field + host = req.Request.Host + } else { + host = forwarded[0] + } + } else { + host = hostvalues[0] + } + // inspect Referer for the scheme (http vs https) + scheme := "http" + if referer := req.Request.Header["Referer"]; len(referer) > 0 { + if strings.HasPrefix(referer[0], "https") { + scheme = "https" + } + } + decl.BasePath = fmt.Sprintf("%s://%s", scheme, host) + } + resp.WriteAsJson(decl) +} + +func (sws SwaggerService) produceAllDeclarations() map[string]ApiDeclaration { + decls := map[string]ApiDeclaration{} + sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) { + decls[k] = v + }) + return decls +} + +func (sws SwaggerService) produceDeclarations(route string) (*ApiDeclaration, bool) { + decl, ok := sws.apiDeclarationMap.At(route) + if !ok { + return nil, false + } + decl.BasePath = sws.config.WebServicesUrl + return &decl, true +} + +// composeDeclaration uses all routes and parameters to create a ApiDeclaration +func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix string) ApiDeclaration { + decl := ApiDeclaration{ + SwaggerVersion: swaggerVersion, + BasePath: sws.config.WebServicesUrl, + ResourcePath: pathPrefix, + Models: ModelList{}, + ApiVersion: ws.Version()} + + // collect any path parameters + rootParams := []Parameter{} + for _, param := range ws.PathParameters() { + rootParams = append(rootParams, asSwaggerParameter(param.Data())) + } + // aggregate by path + pathToRoutes := newOrderedRouteMap() + for _, other := range ws.Routes() { + if strings.HasPrefix(other.Path, pathPrefix) { + if len(pathPrefix) > 1 && len(other.Path) > len(pathPrefix) && other.Path[len(pathPrefix)] != '/' { + continue + } + pathToRoutes.Add(other.Path, other) + } + } + pathToRoutes.Do(func(path string, routes []restful.Route) { + api := Api{Path: strings.TrimSuffix(withoutWildcard(path), "/"), Description: ws.Documentation()} + voidString := "void" + for _, route := range routes { + operation := Operation{ + Method: route.Method, + Summary: route.Doc, + Notes: route.Notes, + // Type gets overwritten if there is a write sample + DataTypeFields: DataTypeFields{Type: &voidString}, + Parameters: []Parameter{}, + Nickname: route.Operation, + ResponseMessages: composeResponseMessages(route, &decl, &sws.config)} + + operation.Consumes = route.Consumes + operation.Produces = route.Produces + + // share root params if any + for _, swparam := range rootParams { + operation.Parameters = append(operation.Parameters, swparam) + } + // route specific params + for _, param := range route.ParameterDocs { + operation.Parameters = append(operation.Parameters, asSwaggerParameter(param.Data())) + } + + sws.addModelsFromRouteTo(&operation, route, &decl) + api.Operations = append(api.Operations, operation) + } + decl.Apis = append(decl.Apis, api) + }) + return decl +} + +func withoutWildcard(path string) string { + if strings.HasSuffix(path, ":*}") { + return path[0:len(path)-3] + "}" + } + return path +} + +// composeResponseMessages takes the ResponseErrors (if any) and creates ResponseMessages from them. +func composeResponseMessages(route restful.Route, decl *ApiDeclaration, config *Config) (messages []ResponseMessage) { + if route.ResponseErrors == nil { + return messages + } + // sort by code + codes := sort.IntSlice{} + for code := range route.ResponseErrors { + codes = append(codes, code) + } + codes.Sort() + for _, code := range codes { + each := route.ResponseErrors[code] + message := ResponseMessage{ + Code: code, + Message: each.Message, + } + if each.Model != nil { + st := reflect.TypeOf(each.Model) + isCollection, st := detectCollectionType(st) + // collection cannot be in responsemodel + if !isCollection { + modelName := modelBuilder{}.keyFrom(st) + modelBuilder{Models: &decl.Models, Config: config}.addModel(st, "") + message.ResponseModel = modelName + } + } + messages = append(messages, message) + } + return +} + +// addModelsFromRoute takes any read or write sample from the Route and creates a Swagger model from it. +func (sws SwaggerService) addModelsFromRouteTo(operation *Operation, route restful.Route, decl *ApiDeclaration) { + if route.ReadSample != nil { + sws.addModelFromSampleTo(operation, false, route.ReadSample, &decl.Models) + } + if route.WriteSample != nil { + sws.addModelFromSampleTo(operation, true, route.WriteSample, &decl.Models) + } +} + +func detectCollectionType(st reflect.Type) (bool, reflect.Type) { + isCollection := false + if st.Kind() == reflect.Slice || st.Kind() == reflect.Array { + st = st.Elem() + isCollection = true + } else { + if st.Kind() == reflect.Ptr { + if st.Elem().Kind() == reflect.Slice || st.Elem().Kind() == reflect.Array { + st = st.Elem().Elem() + isCollection = true + } + } + } + return isCollection, st +} + +// addModelFromSample creates and adds (or overwrites) a Model from a sample resource +func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models *ModelList) { + mb := modelBuilder{Models: models, Config: &sws.config} + if isResponse { + sampleType, items := asDataType(sample, &sws.config) + operation.Type = sampleType + operation.Items = items + } + mb.addModelFrom(sample) +} + +func asSwaggerParameter(param restful.ParameterData) Parameter { + return Parameter{ + DataTypeFields: DataTypeFields{ + Type: ¶m.DataType, + Format: asFormat(param.DataType, param.DataFormat), + DefaultValue: Special(param.DefaultValue), + }, + Name: param.Name, + Description: param.Description, + ParamType: asParamType(param.Kind), + + Required: param.Required} +} + +// Between 1..7 path parameters is supported +func composeRootPath(req *restful.Request) string { + path := "/" + req.PathParameter("a") + b := req.PathParameter("b") + if b == "" { + return path + } + path = path + "/" + b + c := req.PathParameter("c") + if c == "" { + return path + } + path = path + "/" + c + d := req.PathParameter("d") + if d == "" { + return path + } + path = path + "/" + d + e := req.PathParameter("e") + if e == "" { + return path + } + path = path + "/" + e + f := req.PathParameter("f") + if f == "" { + return path + } + path = path + "/" + f + g := req.PathParameter("g") + if g == "" { + return path + } + return path + "/" + g +} + +func asFormat(dataType string, dataFormat string) string { + if dataFormat != "" { + return dataFormat + } + return "" // TODO +} + +func asParamType(kind int) string { + switch { + case kind == restful.PathParameterKind: + return "path" + case kind == restful.QueryParameterKind: + return "query" + case kind == restful.BodyParameterKind: + return "body" + case kind == restful.HeaderParameterKind: + return "header" + case kind == restful.FormParameterKind: + return "form" + } + return "" +} + +func asDataType(any interface{}, config *Config) (*string, *Item) { + // If it's not a collection, return the suggested model name + st := reflect.TypeOf(any) + isCollection, st := detectCollectionType(st) + modelName := modelBuilder{}.keyFrom(st) + // if it's not a collection we are done + if !isCollection { + return &modelName, nil + } + + // XXX: This is not very elegant + // We create an Item object referring to the given model + models := ModelList{} + mb := modelBuilder{Models: &models, Config: config} + mb.addModelFrom(any) + + elemTypeName := mb.getElementTypeName(modelName, "", st) + item := new(Item) + if mb.isPrimitiveType(elemTypeName) { + mapped := mb.jsonSchemaType(elemTypeName) + item.Type = &mapped + } else { + item.Ref = &elemTypeName + } + tmp := "array" + return &tmp, item +} |