-
Notifications
You must be signed in to change notification settings - Fork 0
/
definition.go
116 lines (104 loc) · 6.19 KB
/
definition.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package definitions
import (
"os"
"path/filepath"
"strings"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
"github.com/buonotti/apisense/errors"
"github.com/buonotti/apisense/filesystem/locations/directories"
"github.com/buonotti/apisense/log"
"github.com/buonotti/apisense/validation/query"
)
// SchemaEntry is a field definition of the response
type SchemaEntry struct {
Name string `yaml:"name" json:"name" validate:"required"` // Name is the name of the field
Type string `yaml:"type" json:"type" validate:"required"` // Type is the type of the field
Minimum interface{} `yaml:"min" json:"min"` // Minimum is the minimum allowed value of the field
Maximum interface{} `yaml:"max" json:"max"` // Maximum is the maximum allowed value of the field
IsRequired bool `yaml:"required" json:"required" validate:"required"` // Required is true if the field is required (not null or not empty in case of an array)
Fields []SchemaEntry `yaml:"fields" json:"fields" validate:"required"` // Fields describe the children of this field if the field is an object or array
}
// Endpoint is the definition of an endpoint to test with all its query
// parameters, variables and its result schema
type Endpoint struct {
FileName string `yaml:"-" json:"-"` // FileName is the name of the file that contains the definition
FullPath string `yaml:"-" json:"-"` // FullPath is the full path of the file that contains the definition
Name string `yaml:"name" json:"name" validate:"required"` // Name is the name of the endpoint
IsEnabled bool `yaml:"enabled" json:"enabled"` // IsEnabled is a boolean that indicates if the endpoint is enabled (not contained in the definition)
BaseUrl string `yaml:"base_url" json:"baseUrl" validate:"required"` // BaseUrl is the base path of the endpoint
ExcludedValidators []string `yaml:"excluded_validators" json:"excludedValidators"` // ExcludedValidators is a list of validators that should not be used for this endpoint
QueryParameters []query.Definition `yaml:"query_parameters" json:"queryParameters"` // QueryParameters are all the query parameters that should be added to the call
Format string `yaml:"format" json:"format" validate:"required"` // Format is the response format of the
Variables []Variable `yaml:"variables" json:"variables"` // Variables are all the variables that should be interpolated in the base url and the query parameters
ResponseSchema []SchemaEntry `yaml:"response_schema" json:"responseSchema" validate:"required"` // ResponseSchema describes how the response should look like
}
// parseDefinition reads a given file and returns and EndpointDefinition.
// If the file could not be parsed the function returns an *errors.FileNotFoundError
func parseDefinition(filename string) (Endpoint, error) {
definitionContent, err := os.ReadFile(filepath.FromSlash(directories.DefinitionsDirectory() + "/" + filename))
if err != nil {
return Endpoint{}, errors.FileNotFoundError.Wrap(err, "cannot read definition file")
}
if !strings.HasSuffix(filename, ".apisensedef.yaml") && !strings.HasSuffix(filename, ".apisensedef.yml") {
return Endpoint{}, errors.CannotParseDefinitionFileError.Wrap(err, "found file that is not a definition file: "+filename)
}
var definition Endpoint
err = yaml.Unmarshal(definitionContent, &definition)
if err != nil {
return Endpoint{}, errors.CannotParseDefinitionFileError.Wrap(err, "cannot parse definition file")
}
definition.FileName = filename
definition.FullPath = filepath.FromSlash(directories.DefinitionsDirectory() + "/" + filename)
definition.IsEnabled = !strings.HasPrefix(filename, viper.GetString("daemon.ignore_prefix"))
return definition, nil
}
func validateDefinition(definitions []Endpoint, definition Endpoint) bool {
for _, def := range definitions {
if def.Name == definition.Name {
log.DaemonLogger.WithField("name", definition.Name).WithField("path", definition.FullPath).Warn("duplicate definition found")
return false
}
}
if definition.BaseUrl == "" {
log.DaemonLogger.WithField("name", definition.Name).WithField("path", definition.FullPath).Error("definition has no base url")
return false
}
if definition.Format == "" {
log.DaemonLogger.WithField("name", definition.Name).WithField("path", definition.FullPath).Error("definition has no format")
return false
} else if definition.Format != "json" && definition.Format != "xml" {
log.DaemonLogger.WithField("name", definition.Name).WithField("path", definition.FullPath).Errorf("definition has an invalid format: %s. Found %s expected either 'json' or 'xml'\n", definition.Format, definition.Format)
return false
}
if len(definition.ResponseSchema) == 0 {
log.DaemonLogger.WithField("name", definition.Name).WithField("path", definition.FullPath).Error("schema has no entries")
return false
}
return true
}
// Endpoints uses parseDefinition to parse all the definitions found in
// the definitions/ directory. Directories and Files that start with the
// ignorePrefix are ignored.
func Endpoints() ([]Endpoint, error) {
definitionsFiles, err := os.ReadDir(filepath.FromSlash(directories.DefinitionsDirectory()))
if err != nil {
return []Endpoint{}, errors.FileNotFoundError.Wrap(err, "cannot read definitions directory")
}
var definitions []Endpoint
for _, definitionFile := range definitionsFiles {
if definitionFile.IsDir() {
continue
}
definition, err := parseDefinition(definitionFile.Name())
if err != nil {
return []Endpoint{}, err
}
if !validateDefinition(definitions, definition) {
log.DaemonLogger.WithField("name", definition.Name).WithField("path", definition.FullPath).Error("validation failed for definition. skipping")
continue
}
definitions = append(definitions, definition)
}
return definitions, nil
}