1 | package schema |
||
2 | |||
3 | import ( |
||
4 | "errors" |
||
5 | "fmt" |
||
6 | "io" |
||
7 | "net/http" |
||
8 | "net/url" |
||
9 | "os" |
||
10 | "path/filepath" |
||
11 | ) |
||
12 | |||
13 | // Type defines an enumeration for different schema types. |
||
14 | type Type int |
||
15 | |||
16 | const ( |
||
17 | // URL represents a schema type for URLs. |
||
18 | URL Type = iota |
||
19 | |||
20 | // File represents a schema type for file paths. |
||
21 | File |
||
22 | |||
23 | // Inline represents a schema type for inline data. |
||
24 | Inline |
||
25 | ) |
||
26 | |||
27 | // Loader is a struct that holds a map of loader functions, each corresponding to a Type. |
||
28 | type Loader struct { |
||
29 | // loaders is a map where each Type is associated with a corresponding function |
||
30 | // that takes a string as input and returns a string and an error. |
||
31 | // These functions are responsible for loading data based on the Type. |
||
32 | loaders map[Type]func(string) (string, error) |
||
33 | } |
||
34 | |||
35 | // NewSchemaLoader initializes and returns a new Loader instance. |
||
36 | // It sets up the map of loader functions for each Type. |
||
37 | func NewSchemaLoader() *Loader { |
||
38 | return &Loader{ |
||
39 | loaders: map[Type]func(string) (string, error){ |
||
40 | // URL loader function for handling URL type schemas. |
||
41 | URL: loadFromURL, |
||
42 | |||
43 | // File loader function for handling file path type schemas. |
||
44 | File: loadFromFile, |
||
45 | |||
46 | // Inline loader function for handling inline type schemas. |
||
47 | Inline: loadInline, |
||
48 | }, |
||
49 | } |
||
50 | } |
||
51 | |||
52 | // LoadSchema loads a schema based on its type |
||
53 | func (s *Loader) LoadSchema(input string) (string, error) { |
||
54 | schemaType, err := determineSchemaType(input) |
||
55 | if err != nil { |
||
56 | return "", fmt.Errorf("error determining schema type: %w", err) |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
57 | } |
||
58 | |||
59 | loaderFunc, exists := s.loaders[schemaType] |
||
60 | if !exists { |
||
61 | return "", fmt.Errorf("loader function not found for schema type: %v", schemaType) |
||
62 | } |
||
63 | |||
64 | return loaderFunc(input) |
||
65 | } |
||
66 | |||
67 | // determineSchemaType determines the type of schema based on the input string |
||
68 | func determineSchemaType(input string) (Type, error) { |
||
69 | if isURL(input) { |
||
70 | return URL, nil |
||
71 | } |
||
72 | |||
73 | valid, err := isFilePath(input) |
||
74 | if err != nil { |
||
75 | return Inline, nil |
||
76 | } |
||
77 | if valid { |
||
78 | return File, nil |
||
79 | } |
||
80 | |||
81 | return Inline, nil |
||
82 | } |
||
83 | |||
84 | func isURL(input string) bool { |
||
85 | parsedURL, err := url.Parse(input) |
||
86 | if err != nil { |
||
87 | return false |
||
88 | } |
||
89 | |||
90 | // Check if the URL has a valid scheme and host |
||
91 | return parsedURL.Scheme != "" && parsedURL.Host != "" |
||
92 | } |
||
93 | |||
94 | func isFilePath(input string) (bool, error) { |
||
95 | _, err := os.Stat(input) |
||
96 | if err != nil { |
||
97 | if os.IsNotExist(err) { |
||
98 | return false, errors.New("file does not exist") |
||
99 | } |
||
100 | if os.IsPermission(err) { |
||
101 | return false, errors.New("permission denied") |
||
102 | } |
||
103 | return false, err |
||
104 | } |
||
105 | |||
106 | return true, nil |
||
107 | } |
||
108 | |||
109 | func loadFromURL(inputURL string) (string, error) { |
||
110 | // Parse and validate the URL |
||
111 | parsedURL, err := url.Parse(inputURL) |
||
112 | if err != nil { |
||
113 | return "", err |
||
114 | } |
||
115 | |||
116 | // Add checks here to validate the scheme, host, etc., of parsedURL |
||
117 | // For example, ensure the scheme is either http or https |
||
118 | if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { |
||
119 | return "", errors.New("invalid URL scheme") |
||
120 | } |
||
121 | |||
122 | // Perform the HTTP GET request |
||
123 | resp, err := http.Get(parsedURL.String()) |
||
124 | if err != nil { |
||
125 | return "", err |
||
126 | } |
||
127 | defer resp.Body.Close() |
||
128 | |||
129 | // Read the response body |
||
130 | body, err := io.ReadAll(resp.Body) |
||
131 | if err != nil { |
||
132 | return "", err |
||
133 | } |
||
134 | |||
135 | return string(body), nil |
||
136 | } |
||
137 | |||
138 | func loadFromFile(path string) (string, error) { |
||
139 | // Clean the path |
||
140 | cleanPath := filepath.Clean(path) |
||
141 | |||
142 | // Check if the cleaned path is trying to traverse directories |
||
143 | if filepath.IsAbs(cleanPath) || filepath.HasPrefix(cleanPath, "..") { |
||
144 | return "", errors.New("invalid file path") |
||
145 | } |
||
146 | |||
147 | content, err := os.ReadFile(path) |
||
148 | if err != nil { |
||
149 | return "", err |
||
150 | } |
||
151 | |||
152 | return string(content), nil |
||
153 | } |
||
154 | |||
155 | // loadInline is a function that handles inline schema types. |
||
156 | func loadInline(schema string) (string, error) { |
||
157 | // Add validation if necessary. For example: |
||
158 | if schema == "" { |
||
159 | return "", errors.New("schema is empty") |
||
160 | } |
||
161 | |||
162 | return schema, nil |
||
163 | } |
||
164 |