podman
515 строк · 12.0 Кб
1// Copyright 2015 go-swagger maintainers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package analysis16
17import (18"fmt"19"reflect"20
21"github.com/go-openapi/spec"22)
23
24// Mixin modifies the primary swagger spec by adding the paths and
25// definitions from the mixin specs. Top level parameters and
26// responses from the mixins are also carried over. Operation id
27// collisions are avoided by appending "Mixin<N>" but only if
28// needed.
29//
30// The following parts of primary are subject to merge, filling empty details
31// - Info
32// - BasePath
33// - Host
34// - ExternalDocs
35//
36// Consider calling FixEmptyResponseDescriptions() on the modified primary
37// if you read them from storage and they are valid to start with.
38//
39// Entries in "paths", "definitions", "parameters" and "responses" are
40// added to the primary in the order of the given mixins. If the entry
41// already exists in primary it is skipped with a warning message.
42//
43// The count of skipped entries (from collisions) is returned so any
44// deviation from the number expected can flag a warning in your build
45// scripts. Carefully review the collisions before accepting them;
46// consider renaming things if possible.
47//
48// No key normalization takes place (paths, type defs,
49// etc). Ensure they are canonical if your downstream tools do
50// key normalization of any form.
51//
52// Merging schemes (http, https), and consumers/producers do not account for
53// collisions.
54func Mixin(primary *spec.Swagger, mixins ...*spec.Swagger) []string {55skipped := make([]string, 0, len(mixins))56opIds := getOpIds(primary)57initPrimary(primary)58
59for i, m := range mixins {60skipped = append(skipped, mergeSwaggerProps(primary, m)...)61
62skipped = append(skipped, mergeConsumes(primary, m)...)63
64skipped = append(skipped, mergeProduces(primary, m)...)65
66skipped = append(skipped, mergeTags(primary, m)...)67
68skipped = append(skipped, mergeSchemes(primary, m)...)69
70skipped = append(skipped, mergeSecurityDefinitions(primary, m)...)71
72skipped = append(skipped, mergeSecurityRequirements(primary, m)...)73
74skipped = append(skipped, mergeDefinitions(primary, m)...)75
76// merging paths requires a map of operationIDs to work with77skipped = append(skipped, mergePaths(primary, m, opIds, i)...)78
79skipped = append(skipped, mergeParameters(primary, m)...)80
81skipped = append(skipped, mergeResponses(primary, m)...)82}83
84return skipped85}
86
87// getOpIds extracts all the paths.<path>.operationIds from the given
88// spec and returns them as the keys in a map with 'true' values.
89func getOpIds(s *spec.Swagger) map[string]bool {90rv := make(map[string]bool)91if s.Paths == nil {92return rv93}94
95for _, v := range s.Paths.Paths {96piops := pathItemOps(v)97
98for _, op := range piops {99rv[op.ID] = true100}101}102
103return rv104}
105
106func pathItemOps(p spec.PathItem) []*spec.Operation {107var rv []*spec.Operation108rv = appendOp(rv, p.Get)109rv = appendOp(rv, p.Put)110rv = appendOp(rv, p.Post)111rv = appendOp(rv, p.Delete)112rv = appendOp(rv, p.Head)113rv = appendOp(rv, p.Patch)114
115return rv116}
117
118func appendOp(ops []*spec.Operation, op *spec.Operation) []*spec.Operation {119if op == nil {120return ops121}122
123return append(ops, op)124}
125
126func mergeSecurityDefinitions(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {127for k, v := range m.SecurityDefinitions {128if _, exists := primary.SecurityDefinitions[k]; exists {129warn := fmt.Sprintf(130"SecurityDefinitions entry '%v' already exists in primary or higher priority mixin, skipping\n", k)131skipped = append(skipped, warn)132
133continue134}135
136primary.SecurityDefinitions[k] = v137}138
139return140}
141
142func mergeSecurityRequirements(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {143for _, v := range m.Security {144found := false145for _, vv := range primary.Security {146if reflect.DeepEqual(v, vv) {147found = true148
149break150}151}152
153if found {154warn := fmt.Sprintf(155"Security requirement: '%v' already exists in primary or higher priority mixin, skipping\n", v)156skipped = append(skipped, warn)157
158continue159}160primary.Security = append(primary.Security, v)161}162
163return164}
165
166func mergeDefinitions(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {167for k, v := range m.Definitions {168// assume name collisions represent IDENTICAL type. careful.169if _, exists := primary.Definitions[k]; exists {170warn := fmt.Sprintf(171"definitions entry '%v' already exists in primary or higher priority mixin, skipping\n", k)172skipped = append(skipped, warn)173
174continue175}176primary.Definitions[k] = v177}178
179return180}
181
182func mergePaths(primary *spec.Swagger, m *spec.Swagger, opIds map[string]bool, mixIndex int) (skipped []string) {183if m.Paths != nil {184for k, v := range m.Paths.Paths {185if _, exists := primary.Paths.Paths[k]; exists {186warn := fmt.Sprintf(187"paths entry '%v' already exists in primary or higher priority mixin, skipping\n", k)188skipped = append(skipped, warn)189
190continue191}192
193// Swagger requires that operationIds be194// unique within a spec. If we find a195// collision we append "Mixin0" to the196// operatoinId we are adding, where 0 is mixin197// index. We assume that operationIds with198// all the proivded specs are already unique.199piops := pathItemOps(v)200for _, piop := range piops {201if opIds[piop.ID] {202piop.ID = fmt.Sprintf("%v%v%v", piop.ID, "Mixin", mixIndex)203}204opIds[piop.ID] = true205}206primary.Paths.Paths[k] = v207}208}209
210return211}
212
213func mergeParameters(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {214for k, v := range m.Parameters {215// could try to rename on conflict but would216// have to fix $refs in the mixin. Complain217// for now218if _, exists := primary.Parameters[k]; exists {219warn := fmt.Sprintf(220"top level parameters entry '%v' already exists in primary or higher priority mixin, skipping\n", k)221skipped = append(skipped, warn)222
223continue224}225primary.Parameters[k] = v226}227
228return229}
230
231func mergeResponses(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {232for k, v := range m.Responses {233// could try to rename on conflict but would234// have to fix $refs in the mixin. Complain235// for now236if _, exists := primary.Responses[k]; exists {237warn := fmt.Sprintf(238"top level responses entry '%v' already exists in primary or higher priority mixin, skipping\n", k)239skipped = append(skipped, warn)240
241continue242}243primary.Responses[k] = v244}245
246return skipped247}
248
249func mergeConsumes(primary *spec.Swagger, m *spec.Swagger) []string {250for _, v := range m.Consumes {251found := false252for _, vv := range primary.Consumes {253if v == vv {254found = true255
256break257}258}259
260if found {261// no warning here: we just skip it262continue263}264primary.Consumes = append(primary.Consumes, v)265}266
267return []string{}268}
269
270func mergeProduces(primary *spec.Swagger, m *spec.Swagger) []string {271for _, v := range m.Produces {272found := false273for _, vv := range primary.Produces {274if v == vv {275found = true276
277break278}279}280
281if found {282// no warning here: we just skip it283continue284}285primary.Produces = append(primary.Produces, v)286}287
288return []string{}289}
290
291func mergeTags(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {292for _, v := range m.Tags {293found := false294for _, vv := range primary.Tags {295if v.Name == vv.Name {296found = true297
298break299}300}301
302if found {303warn := fmt.Sprintf(304"top level tags entry with name '%v' already exists in primary or higher priority mixin, skipping\n",305v.Name,306)307skipped = append(skipped, warn)308
309continue310}311
312primary.Tags = append(primary.Tags, v)313}314
315return316}
317
318func mergeSchemes(primary *spec.Swagger, m *spec.Swagger) []string {319for _, v := range m.Schemes {320found := false321for _, vv := range primary.Schemes {322if v == vv {323found = true324
325break326}327}328
329if found {330// no warning here: we just skip it331continue332}333primary.Schemes = append(primary.Schemes, v)334}335
336return []string{}337}
338
339func mergeSwaggerProps(primary *spec.Swagger, m *spec.Swagger) []string {340var skipped, skippedInfo, skippedDocs []string341
342primary.Extensions, skipped = mergeExtensions(primary.Extensions, m.Extensions)343
344// merging details in swagger top properties345if primary.Host == "" {346primary.Host = m.Host347}348
349if primary.BasePath == "" {350primary.BasePath = m.BasePath351}352
353if primary.Info == nil {354primary.Info = m.Info355} else if m.Info != nil {356skippedInfo = mergeInfo(primary.Info, m.Info)357skipped = append(skipped, skippedInfo...)358}359
360if primary.ExternalDocs == nil {361primary.ExternalDocs = m.ExternalDocs362} else if m != nil {363skippedDocs = mergeExternalDocs(primary.ExternalDocs, m.ExternalDocs)364skipped = append(skipped, skippedDocs...)365}366
367return skipped368}
369
370// nolint: unparam
371func mergeExternalDocs(primary *spec.ExternalDocumentation, m *spec.ExternalDocumentation) []string {372if primary.Description == "" {373primary.Description = m.Description374}375
376if primary.URL == "" {377primary.URL = m.URL378}379
380return nil381}
382
383func mergeInfo(primary *spec.Info, m *spec.Info) []string {384var sk, skipped []string385
386primary.Extensions, sk = mergeExtensions(primary.Extensions, m.Extensions)387skipped = append(skipped, sk...)388
389if primary.Description == "" {390primary.Description = m.Description391}392
393if primary.Title == "" {394primary.Description = m.Description395}396
397if primary.TermsOfService == "" {398primary.TermsOfService = m.TermsOfService399}400
401if primary.Version == "" {402primary.Version = m.Version403}404
405if primary.Contact == nil {406primary.Contact = m.Contact407} else if m.Contact != nil {408var csk []string409primary.Contact.Extensions, csk = mergeExtensions(primary.Contact.Extensions, m.Contact.Extensions)410skipped = append(skipped, csk...)411
412if primary.Contact.Name == "" {413primary.Contact.Name = m.Contact.Name414}415
416if primary.Contact.URL == "" {417primary.Contact.URL = m.Contact.URL418}419
420if primary.Contact.Email == "" {421primary.Contact.Email = m.Contact.Email422}423}424
425if primary.License == nil {426primary.License = m.License427} else if m.License != nil {428var lsk []string429primary.License.Extensions, lsk = mergeExtensions(primary.License.Extensions, m.License.Extensions)430skipped = append(skipped, lsk...)431
432if primary.License.Name == "" {433primary.License.Name = m.License.Name434}435
436if primary.License.URL == "" {437primary.License.URL = m.License.URL438}439}440
441return skipped442}
443
444func mergeExtensions(primary spec.Extensions, m spec.Extensions) (result spec.Extensions, skipped []string) {445if primary == nil {446result = m447
448return449}450
451if m == nil {452result = primary453
454return455}456
457result = primary458for k, v := range m {459if _, found := primary[k]; found {460skipped = append(skipped, k)461
462continue463}464
465primary[k] = v466}467
468return469}
470
471func initPrimary(primary *spec.Swagger) {472if primary.SecurityDefinitions == nil {473primary.SecurityDefinitions = make(map[string]*spec.SecurityScheme)474}475
476if primary.Security == nil {477primary.Security = make([]map[string][]string, 0, 10)478}479
480if primary.Produces == nil {481primary.Produces = make([]string, 0, 10)482}483
484if primary.Consumes == nil {485primary.Consumes = make([]string, 0, 10)486}487
488if primary.Tags == nil {489primary.Tags = make([]spec.Tag, 0, 10)490}491
492if primary.Schemes == nil {493primary.Schemes = make([]string, 0, 10)494}495
496if primary.Paths == nil {497primary.Paths = &spec.Paths{Paths: make(map[string]spec.PathItem)}498}499
500if primary.Paths.Paths == nil {501primary.Paths.Paths = make(map[string]spec.PathItem)502}503
504if primary.Definitions == nil {505primary.Definitions = make(spec.Definitions)506}507
508if primary.Parameters == nil {509primary.Parameters = make(map[string]spec.Parameter)510}511
512if primary.Responses == nil {513primary.Responses = make(map[string]spec.Response)514}515}
516