@@ -11,10 +11,27 @@ import (
1111 "github.com/gin-gonic/gin"
1212)
1313
14+ const (
15+ responseCodeHeaderName = "X-Mock-Response-Code"
16+ responseCodeQueryParamName = "x-response-code"
17+ responseTypeQueryParamName = "x-response-type"
18+ availableResponsesHeaderName = "X-Available-Responses"
19+ )
20+
1421type RunOptions struct {
15- Host string
16- Port int
17- Delay int
22+ Host string
23+ Port int
24+ Delay int
25+ DefaultResponseCode string
26+ DefaultResponseType string
27+ }
28+
29+ // XMLNode represents a generic XML node
30+ type XMLNode struct {
31+ XMLName xml.Name
32+ Attrs []xml.Attr `xml:"attr,omitempty"`
33+ Value string `xml:",chardata"`
34+ Children []* XMLNode `xml:",any"`
1835}
1936
2037type MockServer struct {
@@ -28,8 +45,11 @@ func NewMockServer(doc *openapi3.T) *MockServer {
2845}
2946
3047func (m * MockServer ) Run (options RunOptions ) error {
48+ gin .SetMode (gin .ReleaseMode )
3149 app := gin .Default ()
3250
51+ app .Use (m .availableResponsesMiddleware ())
52+
3353 for _ , path := range m .doc .Paths .InMatchingOrder () {
3454 for method , operation := range m .doc .Paths .Find (path ).Operations () {
3555 app .Handle (method , convertPathToGinFormat (path ), m .registerHandler (operation , options ))
@@ -41,90 +61,167 @@ func (m *MockServer) Run(options RunOptions) error {
4161 "/" , func (c * gin.Context ) {
4262 var routes []gin.H
4363 for _ , route := range app .Routes () {
64+ if route .Path == "/" {
65+ continue
66+ }
67+
68+ path := m .doc .Paths .Find (convertGinPathToOpenAPI (route .Path ))
69+ responseCodeToContentType := make (map [string ]string )
70+ if op := path .GetOperation (route .Method ); op != nil {
71+ for code , resp := range op .Responses .Map () {
72+ responseCodeToContentType [code ] = "application/json"
73+ for contentType := range resp .Value .Content {
74+ responseCodeToContentType [code ] = contentType
75+ }
76+ }
77+ }
78+
4479 routes = append (
4580 routes , gin.H {
46- "method" : route .Method ,
47- "path" : route .Path ,
81+ "method" : route .Method ,
82+ "path" : route .Path ,
83+ "availableResponses" : responseCodeToContentType ,
4884 },
4985 )
5086 }
51- c .JSON (http .StatusOK , routes )
87+
88+ c .JSON (
89+ http .StatusOK , gin.H {
90+ "routes" : routes ,
91+ "usage" : gin.H {
92+ "responseCode" : gin.H {
93+ "queryParam" : fmt .Sprintf ("?%s=<status_code>" , responseCodeQueryParamName ),
94+ "header" : fmt .Sprintf ("%s: <status_code>" , responseCodeHeaderName ),
95+ "availableCodes" : fmt .Sprintf ("%s header in response" , availableResponsesHeaderName ),
96+ },
97+ "responseType" : gin.H {
98+ "queryParam" : fmt .Sprintf ("?%s=<response_type>" , responseTypeQueryParamName ),
99+ },
100+ },
101+ },
102+ )
52103 },
53104 )
54105
106+ fmt .Printf ("Mock server listening on http://%s:%d\n " , options .Host , options .Port )
55107 return app .Run (fmt .Sprintf ("%s:%d" , options .Host , options .Port ))
56108}
57109
110+ func (m * MockServer ) availableResponsesMiddleware () gin.HandlerFunc {
111+ return func (c * gin.Context ) {
112+ c .Next ()
113+
114+ // After handler execution, add header with available response codes
115+ path := m .doc .Paths .Find (convertGinPathToOpenAPI (c .FullPath ()))
116+ if path != nil {
117+ if op := path .GetOperation (c .Request .Method ); op != nil {
118+ var codes []string
119+ for code := range op .Responses .Map () {
120+ codes = append (codes , code )
121+ }
122+ if len (codes ) > 0 {
123+ c .Header (availableResponsesHeaderName , strings .Join (codes , "," ))
124+ }
125+ }
126+ }
127+ }
128+ }
129+
58130func (m * MockServer ) registerHandler (op * openapi3.Operation , options RunOptions ) gin.HandlerFunc {
59131 return func (c * gin.Context ) {
60132 // If Delay is set in options, apply it to simulate latency
61133 if options .Delay > 0 {
62134 time .Sleep (time .Duration (options .Delay ) * time .Millisecond )
63135 }
64136
65- status , response := m .findResponse (op )
137+ // Get desired response code from query param or header
138+ desiredCode := c .Query (responseCodeQueryParamName )
139+ if desiredCode == "" {
140+ desiredCode = c .GetHeader (responseCodeHeaderName )
141+ }
142+
143+ // If no code specified, use default
144+ if desiredCode == "" {
145+ desiredCode = options .DefaultResponseCode
146+ }
147+
148+ // Get desired response type from query param
149+ desiredType := c .Query (responseTypeQueryParamName )
150+ if desiredType == "" {
151+ desiredType = options .DefaultResponseType
152+ }
153+
154+ response := m .findSpecificResponse (op , desiredCode )
66155 if response == nil {
67- c .JSON (http .StatusInternalServerError , gin.H {"error" : "No response defined" })
156+ body := gin.H {
157+ "error" : fmt .Sprintf ("No response defined for status code %s" , desiredCode ),
158+ "availableCodes" : m .getAvailableResponseCodes (op ),
159+ }
160+ if desiredType == "xml" {
161+ c .XML (http .StatusBadRequest , body )
162+ return
163+ } else {
164+ c .JSON (http .StatusBadRequest , body )
165+ }
68166 return
69167 }
70168
71- // Get accepted content types from Accept header
169+ status := parseStatusCode ( desiredCode )
72170 acceptHeader := c .GetHeader ("Accept" )
73171 acceptedTypes := parseAcceptHeader (acceptHeader )
74172
75- // Find matching content type and schema
76173 var contentType string
77174 var schema * openapi3.Schema
78175 for mediaType , content := range response .Content {
79176 for _ , acceptedType := range acceptedTypes {
80- if strings .HasPrefix (mediaType , acceptedType ) {
177+ if desiredType != "" {
178+ if strings .HasSuffix (mediaType , desiredType ) {
179+ contentType = mediaType
180+ schema = content .Schema .Value
181+ break
182+ }
183+
184+ } else if strings .HasPrefix (mediaType , acceptedType ) {
81185 contentType = mediaType
82186 schema = content .Schema .Value
83187 break
84188 }
85189 }
190+
86191 if schema != nil {
87192 break
88193 }
89194 }
90195
91- // If no matching content type found, default to JSON
92196 if schema == nil {
93197 contentType = "application/json"
94198 if jsonContent , ok := response .Content ["application/json" ]; ok {
95199 schema = jsonContent .Schema .Value
96200 }
97201 }
98202
99- // Generate mock data based on schema
100203 mockData := generateMockData (schema )
101204
102- // Send response based on content type
103205 switch {
104206 case strings .Contains (contentType , "application/xml" ):
105- c .Header ("Content-Type" , "application/xml" )
106- xmlData , err := xml .Marshal (mockData )
107- if err != nil {
108- c .JSON (http .StatusInternalServerError , gin.H {"error" : "Failed to generate XML" })
109- return
110- }
111- c .String (status , string (xmlData ))
207+ c .XML (status , mapToXML (mockData , schema , "root" ))
112208 default :
113209 c .JSON (status , mockData )
114210 }
115211 }
116212}
117213
118- func (m * MockServer ) findResponse (op * openapi3.Operation ) (int , * openapi3.Response ) {
119- for statusCode , responseRef := range op .Responses .Map () {
120- status := parseStatusCode (statusCode )
121- if status >= 200 && status < 300 {
122- return status , responseRef .Value
123- }
214+ func (m * MockServer ) findSpecificResponse (op * openapi3.Operation , code string ) * openapi3.Response {
215+ if responseRef , ok := op .Responses .Map ()[code ]; ok {
216+ return responseRef .Value
124217 }
125- // Default to 200 if no successful response found
126- if defaultResponse := op .Responses .Default (); defaultResponse != nil {
127- return 200 , defaultResponse .Value
218+ return nil
219+ }
220+
221+ func (m * MockServer ) getAvailableResponseCodes (op * openapi3.Operation ) []string {
222+ var codes []string
223+ for code := range op .Responses .Map () {
224+ codes = append (codes , code )
128225 }
129- return 200 , nil
226+ return codes
130227}
0 commit comments