Passed
Pull Request — master (#96)
by
unknown
01:59
created

application.newHealthcheckCommand   A

Complexity

Conditions 5

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 16
nop 1
dl 0
loc 26
ccs 0
cts 0
cp 0
crap 30
rs 9.1333
c 0
b 0
f 0
1
package application
2
3
import (
4
	"context"
5
	"fmt"
6
	"log"
7
	"net/http"
8
	"time"
9
10
	"github.com/muonsoft/openapi-mock/internal/application/config"
11
	"github.com/muonsoft/openapi-mock/internal/application/di"
12
	"github.com/spf13/cobra"
13
)
14
15
const descriptionTemplate = `OpenAPI Mock tool with random data generation.
16
Version %s. Build at %s.
17
18
See documentation at https://github.com/muonsoft/openapi-mock/blob/master/docs/usage_guide.md.`
19
20
type Options struct {
21
	SpecificationURL  string
22
	Version           string
23
	BuildTime         string
24
	ConfigFilename    string
25
	Arguments         []string
26
	DryRun            bool
27
	overrideArguments bool
28
}
29
30
type OptionFunc func(options *Options)
31
32
func Version(version string) OptionFunc {
33
	return func(options *Options) {
34
		options.Version = version
35
	}
36
}
37
38
func BuildTime(buildTime string) OptionFunc {
39
	return func(options *Options) {
40
		options.BuildTime = buildTime
41
	}
42
}
43 1
44 1
func Arguments(args []string) OptionFunc {
45 1
	return func(options *Options) {
46
		options.Arguments = args
47
		options.overrideArguments = true
48
	}
49
}
50 1
51 1
func Execute(options ...OptionFunc) error {
52 1
	opts := &Options{}
53
	for _, setOption := range options {
54
		setOption(opts)
55 1
	}
56 1
57 1
	mainCommand := newMainCommand(opts)
58
	if opts.overrideArguments {
59
		mainCommand.SetArgs(opts.Arguments)
60 1
	}
61
62
	return mainCommand.Execute()
63
}
64 1
65
func newMainCommand(opts *Options) *cobra.Command {
66
	mainCommand := &cobra.Command{
67
		Use:   "openapi-mock",
68
		Short: "OpenAPI Mock tool with random data generation",
69
		Long:  fmt.Sprintf(descriptionTemplate, opts.Version, opts.BuildTime),
70 1
	}
71
72
	mainCommand.PersistentFlags().StringVarP(
73
		&opts.ConfigFilename,
74
		"configuration",
75
		"c",
76
		"",
77 1
		`Configuration filename in JSON/YAML format. By default configuration is loaded from 'openapi-mock.yaml', 'openapi-mock.yml' or 'openapi-mock.json'.`,
78
	)
79
	mainCommand.PersistentFlags().StringVarP(
80
		&opts.SpecificationURL,
81
		"specification-url",
82
		"u",
83
		"",
84 1
		`URL or path to file with OpenAPI v3 specification. Overrides specification defined in configuration file or environment variable.`,
85
	)
86 1
	mainCommand.PersistentFlags().BoolVar(&opts.DryRun, "dry-run", false, `Dry run will not start a server`)
87
88
	mainCommand.AddCommand(
89
		newVersionCommand(opts),
90
		newServeCommand(opts),
91
		newValidateCommand(opts),
92 1
		newHealthcheckCommand(opts),
93
	)
94
95
	return mainCommand
96 1
}
97
98
func newVersionCommand(options *Options) *cobra.Command {
99
	return &cobra.Command{
100
		Use:   "version",
101
		Short: "Prints application version",
102
		Run: func(cmd *cobra.Command, args []string) {
103
			fmt.Printf("OpenAPI Mock tool. Version %s built at %s.\n", options.Version, options.BuildTime)
104
		},
105
	}
106 1
}
107
108
func newServeCommand(options *Options) *cobra.Command {
109
	return &cobra.Command{
110
		Use:           "serve",
111
		Short:         "Starts an HTTP server for generating mock responses by OpenAPI specification",
112 1
		SilenceUsage:  true,
113 1
		SilenceErrors: true,
114
		RunE: func(cmd *cobra.Command, args []string) error {
115
			configuration, err := config.Load(options.ConfigFilename)
116 1
			if err != nil {
117 1
				return err
118 1
			}
119
			configuration.DryRun = options.DryRun
120
			if options.SpecificationURL != "" {
121 1
				configuration.SpecificationURL = options.SpecificationURL
122 1
			}
123 1
124 1
			factory := di.NewFactory(configuration)
125
			server, err := factory.CreateHTTPServer()
126
			if err != nil {
127 1
				return err
128
			}
129
130
			if !options.DryRun {
131
				err = server.Run()
132
				if err != nil {
133
					return err
134 1
				}
135
			}
136
137
			return nil
138
		},
139
	}
140 1
}
141
142
func newValidateCommand(options *Options) *cobra.Command {
143
	return &cobra.Command{
144
		Use:           "validate",
145
		Short:         "Validates an OpenAPI specification",
146 1
		SilenceUsage:  true,
147 1
		SilenceErrors: true,
148
		RunE: func(cmd *cobra.Command, args []string) error {
149
			configuration, err := config.Load(options.ConfigFilename)
150 1
			if err != nil {
151 1
				return err
152
			}
153
			if options.SpecificationURL != "" {
154 1
				configuration.SpecificationURL = options.SpecificationURL
155 1
			}
156 1
157 1
			factory := di.NewFactory(configuration)
158
			loader := factory.CreateSpecificationLoader()
159
			specification, err := loader.LoadFromURI(configuration.SpecificationURL)
160
			if err != nil {
161 1
				return err
162 1
			}
163
164
			err = specification.Validate(context.Background())
165
			if err != nil {
166
				return fmt.Errorf(
167
					"validation of OpenAPI specification '%s' failed: %w",
168
					configuration.SpecificationURL,
169
					err,
170 1
				)
171
			}
172
173
			if !options.DryRun {
174 1
				log.Printf("OpenAPI specification '%s' is valid", configuration.SpecificationURL)
175
			}
176
177
			return nil
178
		},
179
	}
180
}
181
182
func newHealthcheckCommand(options *Options) *cobra.Command {
183
	return &cobra.Command{
184
		Use:           "healthcheck",
185
		Short:         "Performs a health check by sending an HTTP request to the server",
186
		SilenceUsage:  true,
187
		SilenceErrors: true,
188
		RunE: func(cmd *cobra.Command, args []string) error {
189
			// Create HTTP client with timeout
190
			client := &http.Client{
191
				Timeout: 2 * time.Second,
192
			}
193
194
			// Try to connect to the server on localhost:8080
195
			resp, err := client.Get("http://localhost:8080/")
196
			if err != nil {
197
				return fmt.Errorf("health check failed: %w", err)
198
			}
199
			defer resp.Body.Close()
200
201
			// Consider the server healthy if it responds with any HTTP status
202
			// (even 404 is better than no response)
203
			if resp.StatusCode >= 200 && resp.StatusCode < 500 {
204
				return nil
205
			}
206
207
			return fmt.Errorf("health check failed: server returned status %d", resp.StatusCode)
208
		},
209
	}
210
}
211