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 scan
16
17import (
18	"fmt"
19	"regexp"
20	"strconv"
21	"strings"
22
23	"github.com/go-openapi/spec"
24)
25
26type validationBuilder interface {
27	SetMaximum(float64, bool)
28	SetMinimum(float64, bool)
29	SetMultipleOf(float64)
30
31	SetMinItems(int64)
32	SetMaxItems(int64)
33
34	SetMinLength(int64)
35	SetMaxLength(int64)
36	SetPattern(string)
37
38	SetUnique(bool)
39	SetEnum(string)
40	SetDefault(string)
41}
42
43type valueParser interface {
44	Parse([]string) error
45	Matches(string) bool
46}
47
48type setMaximum struct {
49	builder validationBuilder
50	rx      *regexp.Regexp
51}
52
53func (sm *setMaximum) Parse(lines []string) error {
54	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
55		return nil
56	}
57	matches := sm.rx.FindStringSubmatch(lines[0])
58	if len(matches) > 2 && len(matches[2]) > 0 {
59		max, err := strconv.ParseFloat(matches[2], 64)
60		if err != nil {
61			return err
62		}
63		sm.builder.SetMaximum(max, matches[1] == "<")
64	}
65	return nil
66}
67
68func (sm *setMaximum) Matches(line string) bool {
69	return sm.rx.MatchString(line)
70}
71
72type setMinimum struct {
73	builder validationBuilder
74	rx      *regexp.Regexp
75}
76
77func (sm *setMinimum) Matches(line string) bool {
78	return sm.rx.MatchString(line)
79}
80
81func (sm *setMinimum) Parse(lines []string) error {
82	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
83		return nil
84	}
85	matches := sm.rx.FindStringSubmatch(lines[0])
86	if len(matches) > 2 && len(matches[2]) > 0 {
87		min, err := strconv.ParseFloat(matches[2], 64)
88		if err != nil {
89			return err
90		}
91		sm.builder.SetMinimum(min, matches[1] == ">")
92	}
93	return nil
94}
95
96type setMultipleOf struct {
97	builder validationBuilder
98	rx      *regexp.Regexp
99}
100
101func (sm *setMultipleOf) Matches(line string) bool {
102	return sm.rx.MatchString(line)
103}
104
105func (sm *setMultipleOf) Parse(lines []string) error {
106	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
107		return nil
108	}
109	matches := sm.rx.FindStringSubmatch(lines[0])
110	if len(matches) > 2 && len(matches[1]) > 0 {
111		multipleOf, err := strconv.ParseFloat(matches[1], 64)
112		if err != nil {
113			return err
114		}
115		sm.builder.SetMultipleOf(multipleOf)
116	}
117	return nil
118}
119
120type setMaxItems struct {
121	builder validationBuilder
122	rx      *regexp.Regexp
123}
124
125func (sm *setMaxItems) Matches(line string) bool {
126	return sm.rx.MatchString(line)
127}
128
129func (sm *setMaxItems) Parse(lines []string) error {
130	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
131		return nil
132	}
133	matches := sm.rx.FindStringSubmatch(lines[0])
134	if len(matches) > 1 && len(matches[1]) > 0 {
135		maxItems, err := strconv.ParseInt(matches[1], 10, 64)
136		if err != nil {
137			return err
138		}
139		sm.builder.SetMaxItems(maxItems)
140	}
141	return nil
142}
143
144type setMinItems struct {
145	builder validationBuilder
146	rx      *regexp.Regexp
147}
148
149func (sm *setMinItems) Matches(line string) bool {
150	return sm.rx.MatchString(line)
151}
152
153func (sm *setMinItems) Parse(lines []string) error {
154	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
155		return nil
156	}
157	matches := sm.rx.FindStringSubmatch(lines[0])
158	if len(matches) > 1 && len(matches[1]) > 0 {
159		minItems, err := strconv.ParseInt(matches[1], 10, 64)
160		if err != nil {
161			return err
162		}
163		sm.builder.SetMinItems(minItems)
164	}
165	return nil
166}
167
168type setMaxLength struct {
169	builder validationBuilder
170	rx      *regexp.Regexp
171}
172
173func (sm *setMaxLength) Parse(lines []string) error {
174	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
175		return nil
176	}
177	matches := sm.rx.FindStringSubmatch(lines[0])
178	if len(matches) > 1 && len(matches[1]) > 0 {
179		maxLength, err := strconv.ParseInt(matches[1], 10, 64)
180		if err != nil {
181			return err
182		}
183		sm.builder.SetMaxLength(maxLength)
184	}
185	return nil
186}
187
188func (sm *setMaxLength) Matches(line string) bool {
189	return sm.rx.MatchString(line)
190}
191
192type setMinLength struct {
193	builder validationBuilder
194	rx      *regexp.Regexp
195}
196
197func (sm *setMinLength) Parse(lines []string) error {
198	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
199		return nil
200	}
201	matches := sm.rx.FindStringSubmatch(lines[0])
202	if len(matches) > 1 && len(matches[1]) > 0 {
203		minLength, err := strconv.ParseInt(matches[1], 10, 64)
204		if err != nil {
205			return err
206		}
207		sm.builder.SetMinLength(minLength)
208	}
209	return nil
210}
211
212func (sm *setMinLength) Matches(line string) bool {
213	return sm.rx.MatchString(line)
214}
215
216type setPattern struct {
217	builder validationBuilder
218	rx      *regexp.Regexp
219}
220
221func (sm *setPattern) Parse(lines []string) error {
222	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
223		return nil
224	}
225	matches := sm.rx.FindStringSubmatch(lines[0])
226	if len(matches) > 1 && len(matches[1]) > 0 {
227		sm.builder.SetPattern(matches[1])
228	}
229	return nil
230}
231
232func (sm *setPattern) Matches(line string) bool {
233	return sm.rx.MatchString(line)
234}
235
236type setCollectionFormat struct {
237	builder operationValidationBuilder
238	rx      *regexp.Regexp
239}
240
241func (sm *setCollectionFormat) Parse(lines []string) error {
242	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
243		return nil
244	}
245	matches := sm.rx.FindStringSubmatch(lines[0])
246	if len(matches) > 1 && len(matches[1]) > 0 {
247		sm.builder.SetCollectionFormat(matches[1])
248	}
249	return nil
250}
251
252func (sm *setCollectionFormat) Matches(line string) bool {
253	return sm.rx.MatchString(line)
254}
255
256type setUnique struct {
257	builder validationBuilder
258	rx      *regexp.Regexp
259}
260
261func (su *setUnique) Matches(line string) bool {
262	return su.rx.MatchString(line)
263}
264
265func (su *setUnique) Parse(lines []string) error {
266	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
267		return nil
268	}
269	matches := su.rx.FindStringSubmatch(lines[0])
270	if len(matches) > 1 && len(matches[1]) > 0 {
271		req, err := strconv.ParseBool(matches[1])
272		if err != nil {
273			return err
274		}
275		su.builder.SetUnique(req)
276	}
277	return nil
278}
279
280type setEnum struct {
281	builder validationBuilder
282	rx      *regexp.Regexp
283}
284
285func (se *setEnum) Matches(line string) bool {
286	return se.rx.MatchString(line)
287}
288
289func (se *setEnum) Parse(lines []string) error {
290	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
291		return nil
292	}
293	matches := se.rx.FindStringSubmatch(lines[0])
294	if len(matches) > 1 && len(matches[1]) > 0 {
295		se.builder.SetEnum(matches[1])
296	}
297	return nil
298}
299
300type setDefault struct {
301	builder validationBuilder
302	rx      *regexp.Regexp
303}
304
305func (sd *setDefault) Matches(line string) bool {
306	return sd.rx.MatchString(line)
307}
308
309func (sd *setDefault) Parse(lines []string) error {
310	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
311		return nil
312	}
313	matches := sd.rx.FindStringSubmatch(lines[0])
314	if len(matches) > 1 && len(matches[1]) > 0 {
315		sd.builder.SetDefault(matches[1])
316	}
317	return nil
318}
319
320type matchOnlyParam struct {
321	tgt *spec.Parameter
322	rx  *regexp.Regexp
323}
324
325func (mo *matchOnlyParam) Matches(line string) bool {
326	return mo.rx.MatchString(line)
327}
328
329func (mo *matchOnlyParam) Parse(lines []string) error {
330	return nil
331}
332
333type setRequiredParam struct {
334	tgt *spec.Parameter
335}
336
337func (su *setRequiredParam) Matches(line string) bool {
338	return rxRequired.MatchString(line)
339}
340
341func (su *setRequiredParam) Parse(lines []string) error {
342	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
343		return nil
344	}
345	matches := rxRequired.FindStringSubmatch(lines[0])
346	if len(matches) > 1 && len(matches[1]) > 0 {
347		req, err := strconv.ParseBool(matches[1])
348		if err != nil {
349			return err
350		}
351		su.tgt.Required = req
352	}
353	return nil
354}
355
356type setReadOnlySchema struct {
357	tgt *spec.Schema
358}
359
360func (su *setReadOnlySchema) Matches(line string) bool {
361	return rxReadOnly.MatchString(line)
362}
363
364func (su *setReadOnlySchema) Parse(lines []string) error {
365	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
366		return nil
367	}
368	matches := rxReadOnly.FindStringSubmatch(lines[0])
369	if len(matches) > 1 && len(matches[1]) > 0 {
370		req, err := strconv.ParseBool(matches[1])
371		if err != nil {
372			return err
373		}
374		su.tgt.ReadOnly = req
375	}
376	return nil
377}
378
379type setDiscriminator struct {
380	schema *spec.Schema
381	field  string
382}
383
384func (su *setDiscriminator) Matches(line string) bool {
385	return rxDiscriminator.MatchString(line)
386}
387
388func (su *setDiscriminator) Parse(lines []string) error {
389	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
390		return nil
391	}
392	matches := rxDiscriminator.FindStringSubmatch(lines[0])
393	if len(matches) > 1 && len(matches[1]) > 0 {
394		req, err := strconv.ParseBool(matches[1])
395		if err != nil {
396			return err
397		}
398		if req {
399			su.schema.Discriminator = su.field
400		} else {
401			if su.schema.Discriminator == su.field {
402				su.schema.Discriminator = ""
403			}
404		}
405	}
406	return nil
407}
408
409type setRequiredSchema struct {
410	schema *spec.Schema
411	field  string
412}
413
414func (su *setRequiredSchema) Matches(line string) bool {
415	return rxRequired.MatchString(line)
416}
417
418func (su *setRequiredSchema) Parse(lines []string) error {
419	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
420		return nil
421	}
422	matches := rxRequired.FindStringSubmatch(lines[0])
423	if len(matches) > 1 && len(matches[1]) > 0 {
424		req, err := strconv.ParseBool(matches[1])
425		if err != nil {
426			return err
427		}
428		midx := -1
429		for i, nm := range su.schema.Required {
430			if nm == su.field {
431				midx = i
432				break
433			}
434		}
435		if req {
436			if midx < 0 {
437				su.schema.Required = append(su.schema.Required, su.field)
438			}
439		} else if midx >= 0 {
440			su.schema.Required = append(su.schema.Required[:midx], su.schema.Required[midx+1:]...)
441		}
442	}
443	return nil
444}
445
446func newMultilineDropEmptyParser(rx *regexp.Regexp, set func([]string)) *multiLineDropEmptyParser {
447	return &multiLineDropEmptyParser{
448		rx:  rx,
449		set: set,
450	}
451}
452
453type multiLineDropEmptyParser struct {
454	set func([]string)
455	rx  *regexp.Regexp
456}
457
458func (m *multiLineDropEmptyParser) Matches(line string) bool {
459	return m.rx.MatchString(line)
460}
461
462func (m *multiLineDropEmptyParser) Parse(lines []string) error {
463	m.set(removeEmptyLines(lines))
464	return nil
465}
466
467func newSetSchemes(set func([]string)) *setSchemes {
468	return &setSchemes{
469		set: set,
470		rx:  rxSchemes,
471	}
472}
473
474type setSchemes struct {
475	set func([]string)
476	rx  *regexp.Regexp
477}
478
479func (ss *setSchemes) Matches(line string) bool {
480	return ss.rx.MatchString(line)
481}
482
483func (ss *setSchemes) Parse(lines []string) error {
484	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
485		return nil
486	}
487	matches := ss.rx.FindStringSubmatch(lines[0])
488	if len(matches) > 1 && len(matches[1]) > 0 {
489		sch := strings.Split(matches[1], ", ")
490
491		var schemes []string
492		for _, s := range sch {
493			ts := strings.TrimSpace(s)
494			if ts != "" {
495				schemes = append(schemes, ts)
496			}
497		}
498		ss.set(schemes)
499	}
500	return nil
501}
502
503func newSetSecurityDefinitions(rx *regexp.Regexp, setter func([]map[string][]string)) *setSecurityDefinitions {
504	return &setSecurityDefinitions{
505		set: setter,
506		rx:  rx,
507	}
508}
509
510type setSecurityDefinitions struct {
511	set func([]map[string][]string)
512	rx  *regexp.Regexp
513}
514
515func (ss *setSecurityDefinitions) Matches(line string) bool {
516	return ss.rx.MatchString(line)
517}
518
519func (ss *setSecurityDefinitions) Parse(lines []string) error {
520	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
521		return nil
522	}
523
524	var result []map[string][]string
525	for _, line := range lines {
526		kv := strings.SplitN(line, ":", 2)
527		scopes := []string{}
528		var key string
529
530		if len(kv) > 1 {
531			scs := strings.Split(kv[1], ",")
532			for _, scope := range scs {
533				tr := strings.TrimSpace(scope)
534				if tr != "" {
535					tr = strings.SplitAfter(tr, " ")[0]
536					scopes = append(scopes, strings.TrimSpace(tr))
537				}
538			}
539
540			key = strings.TrimSpace(kv[0])
541
542			result = append(result, map[string][]string{key: scopes})
543		}
544	}
545	ss.set(result)
546	return nil
547}
548
549func newSetResponses(definitions map[string]spec.Schema, responses map[string]spec.Response, setter func(*spec.Response, map[int]spec.Response)) *setOpResponses {
550	return &setOpResponses{
551		set:         setter,
552		rx:          rxResponses,
553		definitions: definitions,
554		responses:   responses,
555	}
556}
557
558type setOpResponses struct {
559	set         func(*spec.Response, map[int]spec.Response)
560	rx          *regexp.Regexp
561	definitions map[string]spec.Schema
562	responses   map[string]spec.Response
563}
564
565func (ss *setOpResponses) Matches(line string) bool {
566	return ss.rx.MatchString(line)
567}
568
569//Tag used when specifying a response to point to a defined swagger:response
570const ResponseTag = "response"
571
572//Tag used when specifying a response to point to a model/schema
573const BodyTag = "body"
574
575//Tag used when specifying a response that gives a description of the response
576const DescriptionTag = "description"
577
578func parseTags(line string) (modelOrResponse string, arrays int, isDefinitionRef bool, description string, err error) {
579	tags := strings.Split(line, " ")
580	parsedModelOrResponse := false
581	parsedDescription := false
582
583	for i, tagAndValue := range tags {
584		tagValList := strings.SplitN(tagAndValue, ":", 2)
585		var tag, value string
586		if len(tagValList) > 1 {
587			tag = tagValList[0]
588			value = tagValList[1]
589		} else {
590			//TODO: Print a warning, and in the long term, do not support not tagged values
591			//Add a default tag if none is supplied
592			if i == 0 {
593				tag = ResponseTag
594			} else {
595				tag = DescriptionTag
596			}
597			value = tagValList[0]
598		}
599
600		foundModelOrResponse := false
601		if !parsedModelOrResponse {
602			if tag == BodyTag {
603				foundModelOrResponse = true
604				isDefinitionRef = true
605			}
606			if tag == ResponseTag {
607				foundModelOrResponse = true
608				isDefinitionRef = false
609			}
610		}
611		if foundModelOrResponse {
612			//Read the model or response tag
613			parsedModelOrResponse = true
614			//Check for nested arrays
615			arrays = 0
616			for strings.HasPrefix(value, "[]") {
617				arrays++
618				value = value[2:]
619			}
620			//What's left over is the model name
621			modelOrResponse = value
622		} else {
623			foundDescription := false
624			if !parsedDescription {
625				if tag == DescriptionTag {
626					foundDescription = true
627				}
628			}
629			if foundDescription {
630				//Descriptions are special, they make they read the rest of the line
631				descriptionWords := []string{value}
632				if i < len(tags)-1 {
633					descriptionWords = append(descriptionWords, tags[i+1:len(tags)]...)
634				}
635				description = strings.Join(descriptionWords, " ")
636				parsedDescription = true
637				break
638			} else {
639				if tag == ResponseTag || tag == BodyTag || tag == DescriptionTag {
640					err = fmt.Errorf("Found valid tag %s, but not in a valid position", tag)
641				} else {
642					err = fmt.Errorf("Found invalid tag: %s", tag)
643				}
644				//return error
645				return
646			}
647		}
648	}
649
650	//TODO: Maybe do, if !parsedModelOrResponse && !parsedDescription {return some error}
651	return
652}
653
654func (ss *setOpResponses) Parse(lines []string) error {
655	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
656		return nil
657	}
658
659	var def *spec.Response
660	var scr map[int]spec.Response
661
662	for _, line := range lines {
663		kv := strings.SplitN(line, ":", 2)
664		var key, value string
665
666		if len(kv) > 1 {
667			key = strings.TrimSpace(kv[0])
668			if key == "" {
669				// this must be some weird empty line
670				continue
671			}
672			value = strings.TrimSpace(kv[1])
673			if value == "" {
674				var resp spec.Response
675				if strings.EqualFold("default", key) {
676					if def == nil {
677						def = &resp
678					}
679				} else {
680					if sc, err := strconv.Atoi(key); err == nil {
681						if scr == nil {
682							scr = make(map[int]spec.Response)
683						}
684						scr[sc] = resp
685					}
686				}
687				continue
688			}
689			refTarget, arrays, isDefinitionRef, description, err := parseTags(value)
690			if err != nil {
691				return err
692			}
693			//A possible exception for having a definition
694			if _, ok := ss.responses[refTarget]; !ok {
695				if _, ok := ss.definitions[refTarget]; ok {
696					isDefinitionRef = true
697				}
698			}
699
700			var ref spec.Ref
701			if isDefinitionRef {
702				if description == "" {
703					description = refTarget
704				}
705				ref, err = spec.NewRef("#/definitions/" + refTarget)
706			} else {
707				ref, err = spec.NewRef("#/responses/" + refTarget)
708			}
709			if err != nil {
710				return err
711			}
712
713			var resp spec.Response
714
715			if !isDefinitionRef {
716				resp.Ref = ref
717			} else {
718				resp.Schema = new(spec.Schema)
719				resp.Description = description
720				if arrays == 0 {
721					resp.Schema.Ref = ref
722				} else {
723					cs := resp.Schema
724					for i := 0; i < arrays; i++ {
725						cs.Typed("array", "")
726						cs.Items = new(spec.SchemaOrArray)
727						cs.Items.Schema = new(spec.Schema)
728						cs = cs.Items.Schema
729					}
730					cs.Ref = ref
731				}
732			}
733
734			if strings.EqualFold("default", key) {
735				if def == nil {
736					def = &resp
737				}
738			} else {
739				if sc, err := strconv.Atoi(key); err == nil {
740					if scr == nil {
741						scr = make(map[int]spec.Response)
742					}
743					scr[sc] = resp
744				}
745			}
746		}
747	}
748	ss.set(def, scr)
749	return nil
750}
751