@@ -12,6 +12,7 @@ import (
1212 "net/url"
1313 "os"
1414 "path/filepath"
15+ "regexp"
1516 "strconv"
1617 "strings"
1718 "time"
@@ -137,21 +138,32 @@ func addParameter(flags *pflag.FlagSet, name, short string, def interface{}, des
137138
138139// getTypedExample will return an example from a given media type, if such an
139140// example exists. If multiple examples are given, then one is selected at
140- // random.
141- func getTypedExample (mt * openapi3.MediaType ) (interface {}, error ) {
141+ // random unless an "example" item exists in the Prefer header
142+ func getTypedExample (mt * openapi3.MediaType , prefer map [ string ] string ) (interface {}, error ) {
142143 if mt .Example != nil {
143144 return mt .Example , nil
144145 }
145146
146147 if len (mt .Examples ) > 0 {
148+ // If preferred example requested and it it exists, return it
149+ preferredExample := ""
150+ if mapContainsKey (prefer , "example" ) {
151+ preferredExample = prefer ["example" ]
152+ if _ , ok := mt .Examples [preferredExample ]; ok {
153+ return mt .Examples [preferredExample ].Value .Value , nil
154+ }
155+ }
156+
147157 // Choose a random example to return.
148158 keys := make ([]string , 0 , len (mt .Examples ))
149159 for k := range mt .Examples {
150160 keys = append (keys , k )
151161 }
152162
153- selected := keys [rand .Intn (len (keys ))]
154- return mt .Examples [selected ].Value .Value , nil
163+ if len (keys ) > 0 {
164+ selected := keys [rand .Intn (len (keys ))]
165+ return mt .Examples [selected ].Value .Value , nil
166+ }
155167 }
156168
157169 if mt .Schema != nil {
@@ -163,11 +175,12 @@ func getTypedExample(mt *openapi3.MediaType) (interface{}, error) {
163175}
164176
165177// getExample tries to return an example for a given operation.
166- func getExample (negotiator * ContentNegotiator , prefer string , op * openapi3.Operation ) (int , string , map [string ]* openapi3.HeaderRef , interface {}, error ) {
178+ // Using the Prefer http header, the consumer can specify the type of response they want.
179+ func getExample (negotiator * ContentNegotiator , prefer map [string ]string , op * openapi3.Operation ) (int , string , map [string ]* openapi3.HeaderRef , interface {}, error ) {
167180 var responses []string
168181 var blankHeaders = make (map [string ]* openapi3.HeaderRef )
169182
170- if prefer == "" {
183+ if ! mapContainsKey ( prefer , "status" ) {
171184 // First, make a list of responses ordered by successful (200-299 status code)
172185 // before other types.
173186 success := make ([]string , 0 )
@@ -181,10 +194,10 @@ func getExample(negotiator *ContentNegotiator, prefer string, op *openapi3.Opera
181194 }
182195 responses = append (success , other ... )
183196 } else {
184- if op .Responses [prefer ] == nil {
197+ if op .Responses [prefer [ "status" ] ] == nil {
185198 return 0 , "" , blankHeaders , nil , ErrNoExample
186199 }
187- responses = []string {prefer }
200+ responses = []string {prefer [ "status" ] }
188201 }
189202
190203 // Now try to find the first example we can and return it!
@@ -207,7 +220,7 @@ func getExample(negotiator *ContentNegotiator, prefer string, op *openapi3.Opera
207220 continue
208221 }
209222
210- example , err := getTypedExample (content )
223+ example , err := getTypedExample (content , prefer )
211224 if err == nil {
212225 return status , mt , response .Value .Headers , example , nil
213226 }
@@ -311,6 +324,66 @@ func load(uri string, data []byte) (swagger *openapi3.Swagger, router *openapi3f
311324 return
312325}
313326
327+ // parsePreferHeader takes the value of a prefer header and splits it out into key value pairs
328+ //
329+ // HTTP Prefer header specification examples:
330+ // - Prefer: status=200; example="something"
331+ // - Prefer: example=something;status=200;
332+ // - Prefer: example="somet,;hing";status=200;
333+ //
334+ // As part of the Prefer specification, it is completely valid to specify
335+ // multiple Prefer headers in a single request, however we won't be
336+ // supporting that for the moment and only the first Prefer header
337+ // will be used.
338+ func parsePreferHeader (value string ) map [string ]string {
339+ prefer := map [string ]string {}
340+ if value != "" {
341+ // In the event that something is quoted, we want to pull those items out of the string
342+ // and save them for later, so they don't conflict with other splitting logic.
343+
344+ quotedRegex := regexp .MustCompile (`"[^"]*"` )
345+ splitRegex := regexp .MustCompile (`(,|;| )` )
346+ wilcardRegex := regexp .MustCompile (`%%([0-9]+)%%` )
347+
348+ quotedStrings := quotedRegex .FindAllString (value , - 1 )
349+ if len (quotedStrings ) > 0 {
350+ // replace each quoted string with a placehoder
351+ for idx , quotedString := range quotedStrings {
352+ value = strings .Replace (value , quotedString , fmt .Sprintf ("%%%%%v%%%%" , idx ), 1 )
353+ }
354+ }
355+
356+ pairs := splitRegex .Split (value , - 1 )
357+ for _ , pair := range pairs {
358+ pair = strings .TrimSpace (pair )
359+ if pair != "" {
360+ // Put any wildcards back
361+ wildcardStrings := wilcardRegex .FindAllStringSubmatch (pair , - 1 )
362+ for _ , wildcard := range wildcardStrings {
363+ quotedIdx , _ := strconv .Atoi (wildcard [1 ])
364+ pair = strings .Replace (pair , wildcard [0 ], quotedStrings [quotedIdx ], 1 )
365+ }
366+
367+ // Determine the key and valid for this argument
368+ if strings .Contains (pair , "=" ) {
369+ eqIdx := strings .Index (pair , "=" )
370+ prefer [pair [:eqIdx ]] = strings .Trim (pair [eqIdx + 1 :], `"` )
371+ } else {
372+ prefer [pair ] = ""
373+ }
374+ }
375+ }
376+ }
377+ return prefer
378+ }
379+
380+ func mapContainsKey (dict map [string ]string , key string ) bool {
381+ if _ , ok := dict [key ]; ok {
382+ return true
383+ }
384+ return false
385+ }
386+
314387// server loads an OpenAPI file and runs a mock server using the paths and
315388// examples defined in the file.
316389func server (cmd * cobra.Command , args []string ) {
@@ -535,12 +608,7 @@ func server(cmd *cobra.Command, args []string) {
535608 }
536609 }
537610
538- prefer := req .Header .Get ("Prefer" )
539- if strings .HasPrefix (prefer , "status=" ) {
540- prefer = prefer [7 :10 ]
541- } else {
542- prefer = ""
543- }
611+ prefer := parsePreferHeader (req .Header .Get ("Prefer" ))
544612
545613 status , mediatype , headers , example , err := getExample (negotiator , prefer , route .Operation )
546614 if err != nil {
0 commit comments