blufin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/bin/bf +5 -0
  3. data/bin/blufin +5 -0
  4. data/lib/blufin.rb +245 -0
  5. data/lib/core/code_scanners/common/scanner_common.rb +83 -0
  6. data/lib/core/code_scanners/common/scanner_java.rb +106 -0
  7. data/lib/core/code_scanners/scanner_java_embedded_objects.rb +386 -0
  8. data/lib/core/code_scanners/scanner_java_enums.rb +125 -0
  9. data/lib/core/code_scanners/scanner_java_source.rb +29 -0
  10. data/lib/core/code_scanners/scanner_java_tests.rb +157 -0
  11. data/lib/core/error_handling/schema_error.rb +9 -0
  12. data/lib/core/error_handling/sql_error.rb +21 -0
  13. data/lib/core/error_handling/sql_error_handler.rb +149 -0
  14. data/lib/core/error_handling/yml_error.rb +21 -0
  15. data/lib/core/error_handling/yml_error_handler.rb +437 -0
  16. data/lib/core/mysql.rb +347 -0
  17. data/lib/core/opt.rb +21 -0
  18. data/lib/core/site/site.rb +26 -0
  19. data/lib/core/site/site_auth.rb +88 -0
  20. data/lib/core/site/site_embedded.rb +27 -0
  21. data/lib/core/site/site_ports.rb +9 -0
  22. data/lib/core/site/site_resolver.rb +276 -0
  23. data/lib/core/site/site_services.rb +162 -0
  24. data/lib/core/site/site_ui.rb +16 -0
  25. data/lib/core/yml/config/yml_config_validator.rb +219 -0
  26. data/lib/core/yml/maven/yml_maven_validator.rb +1132 -0
  27. data/lib/core/yml/resource/yml_resource_validator.rb +154 -0
  28. data/lib/core/yml/schema/yml_schema_flags.rb +9 -0
  29. data/lib/core/yml/schema/yml_schema_validator.rb +1850 -0
  30. data/lib/core/yml/yml_cache_handler.rb +115 -0
  31. data/lib/core/yml/yml_common.rb +487 -0
  32. data/lib/core/yml/yml_meta_writer_base.rb +300 -0
  33. data/lib/core/yml/yml_outputter.rb +307 -0
  34. data/lib/core/yml/yml_validator_base.rb +630 -0
  35. data/lib/core/yml_writers/yml_configuration_writer.rb +40 -0
  36. data/lib/core/yml_writers/yml_java_api_resource_writer.rb +348 -0
  37. data/lib/core/yml_writers/yml_java_cron_type_writer.rb +113 -0
  38. data/lib/core/yml_writers/yml_java_css_dependency_writer.rb +59 -0
  39. data/lib/core/yml_writers/yml_java_dao_writer.rb +364 -0
  40. data/lib/core/yml_writers/yml_java_dto_writer.rb +251 -0
  41. data/lib/core/yml_writers/yml_java_embedded_object_writer.rb +968 -0
  42. data/lib/core/yml_writers/yml_java_enum_writer.rb +161 -0
  43. data/lib/core/yml_writers/yml_java_js_dependency_writer.rb +59 -0
  44. data/lib/core/yml_writers/yml_java_message_type_writer.rb +106 -0
  45. data/lib/core/yml_writers/yml_java_meta_writer.rb +173 -0
  46. data/lib/core/yml_writers/yml_java_model_writer.rb +510 -0
  47. data/lib/core/yml_writers/yml_java_pom_writer.rb +1050 -0
  48. data/lib/core/yml_writers/yml_java_resource_data_writer.rb +251 -0
  49. data/lib/core/yml_writers/yml_java_sdk_writer.rb +732 -0
  50. data/lib/core/yml_writers/yml_java_validator_writer.rb +280 -0
  51. data/lib/core/yml_writers/yml_java_worker_writer.rb +81 -0
  52. data/lib/core/yml_writers/yml_sql_structure_writer.rb +307 -0
  53. data/lib/core/yml_writers/yml_sql_template_writer.rb +243 -0
  54. data/lib/core/yml_writers/yml_vue_service_writer.rb +170 -0
  55. data/lib/core/yml_writers/yml_writer_base.rb +114 -0
  56. data/lib/routes/api_list.rb +35 -0
  57. data/lib/routes/api_meta.rb +59 -0
  58. data/lib/routes/build.rb +46 -0
  59. data/lib/routes/create/create_api.rb +35 -0
  60. data/lib/routes/create/create_ui.rb +84 -0
  61. data/lib/routes/export.rb +56 -0
  62. data/lib/routes/generate/generate_api.rb +225 -0
  63. data/lib/routes/generate/generate_img_favicon.rb +56 -0
  64. data/lib/routes/generate/generate_img_landing.rb +94 -0
  65. data/lib/routes/generate/generate_lambda.rb +43 -0
  66. data/lib/routes/lint.rb +35 -0
  67. data/lib/routes/mysql_reset.rb +43 -0
  68. data/lib/routes/release_blufin.rb +351 -0
  69. data/lib/routes/run.rb +35 -0
  70. data/lib/version.rb +1 -0
  71. data/opt/README.MD +2 -0
  72. data/opt/config/schema.yml +73 -0
  73. data/opt/config/template.yml +25 -0
  74. data/opt/sql/data/config/data-client.sql +7 -0
  75. data/opt/sql/data/config/data-db-configuration-property.sql +47 -0
  76. data/opt/sql/data/config/data-db-configuration.sql +9 -0
  77. data/opt/sql/data/config/data-db.sql +175 -0
  78. data/opt/sql/data/config/data-profile-api.sql +47 -0
  79. data/opt/sql/data/config/data-profile-cron.sql +0 -0
  80. data/opt/sql/data/config/data-profile-worker.sql +0 -0
  81. data/opt/sql/data/config/data-profile.sql +87 -0
  82. data/opt/sql/data/config/data-project.sql +95 -0
  83. data/opt/sql/structure/blufin-master-structure-fks.sql +65 -0
  84. data/opt/sql/structure/blufin-master-structure.sql +97 -0
  85. data/opt/sql/structure/blufin-mock-structure-fks.sql +38 -0
  86. data/opt/sql/structure/blufin-mock-structure.sql +98 -0
  87. data/opt/sql/templates/config/template-client.sql +7 -0
  88. data/opt/sql/templates/config/template-db-configuration-property.sql +11 -0
  89. data/opt/sql/templates/config/template-db-configuration.sql +9 -0
  90. data/opt/sql/templates/config/template-db.sql +21 -0
  91. data/opt/sql/templates/config/template-profile-api.sql +11 -0
  92. data/opt/sql/templates/config/template-profile-cron.sql +7 -0
  93. data/opt/sql/templates/config/template-profile-worker.sql +7 -0
  94. data/opt/sql/templates/config/template-profile.sql +21 -0
  95. data/opt/sql/templates/config/template-project.sql +23 -0
  96. data/opt/yml/api/schema/config/client.yml +14 -0
  97. data/opt/yml/api/schema/config/db.yml +45 -0
  98. data/opt/yml/api/schema/config/db_configuration.yml +22 -0
  99. data/opt/yml/api/schema/config/db_configuration_property.yml +22 -0
  100. data/opt/yml/api/schema/config/profile.yml +53 -0
  101. data/opt/yml/api/schema/config/profile_api.yml +22 -0
  102. data/opt/yml/api/schema/config/profile_cron.yml +14 -0
  103. data/opt/yml/api/schema/config/profile_worker.yml +14 -0
  104. data/opt/yml/api/schema/config/project.yml +48 -0
  105. data/opt/yml/api/schema/mock/mock.yml +99 -0
  106. data/opt/yml/api/schema/mock/mock_nested_if_enum.yml +16 -0
  107. data/opt/yml/api/schema/mock/mock_nested_if_enum_system.yml +16 -0
  108. data/opt/yml/api/schema/mock/mock_nested_linked.yml +43 -0
  109. data/opt/yml/api/schema/mock/mock_nested_multiple.yml +61 -0
  110. data/opt/yml/api/schema/mock/mock_nested_single.yml +67 -0
  111. data/opt/yml/api/schema/mock/mock_nested_single_super_deep.yml +32 -0
  112. data/opt/yml/api/schema/mock/mock_nested_single_super_super_deep.yml +17 -0
  113. metadata +240 -0
@@ -0,0 +1,154 @@
1
+ module Blufin
2
+
3
+ class YmlResourceValidator < Blufin::YmlValidatorBase
4
+
5
+ CONFIG = 'config'
6
+ CONFIG_INTERNAL = 'internal'
7
+ CONFIG_OAUTH = 'oauth'
8
+
9
+ STRUCTURE = [
10
+ {
11
+ :section_name => CONFIG,
12
+ :section_type => Blufin::YmlValidatorBase::SECTION_TYPE_FIXED,
13
+ :section_data => {
14
+ CONFIG_INTERNAL => {
15
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_NESTED_DATA,
16
+ :required => false,
17
+ :section_data => {
18
+ 'GET' => {
19
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
20
+ :required => false
21
+ },
22
+ 'POST' => {
23
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
24
+ :required => false
25
+ },
26
+ 'PATCH' => {
27
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
28
+ :required => false
29
+ },
30
+ 'DELETE' => {
31
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
32
+ :required => false
33
+ }
34
+ }
35
+ },
36
+ CONFIG_OAUTH => {
37
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_NESTED_DATA,
38
+ :required => false,
39
+ :section_data => {
40
+ 'GET' => {
41
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
42
+ :required => false
43
+ },
44
+ 'POST' => {
45
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
46
+ :required => false
47
+ },
48
+ 'PATCH' => {
49
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
50
+ :required => false
51
+ },
52
+ 'DELETE' => {
53
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
54
+ :required => false
55
+ }
56
+ }
57
+ }
58
+ }
59
+ },
60
+ { # TODO - 12/20/18 - I'm pretty sure this is all wrong...
61
+ :section_name => 'request',
62
+ :section_type => Blufin::YmlValidatorBase::SECTION_TYPE_DYNAMIC,
63
+ :section_data => {
64
+ :section_alphabetical => false,
65
+ :section_regex => /\A(app\.|common\.|config\.|mock\.)*[a-z_]+(\[\]|\[link\])*\z/,
66
+ :section_fields => {
67
+ 'type' => {
68
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
69
+ :required => false
70
+ },
71
+ 'flag' => {
72
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
73
+ :required => false
74
+ },
75
+ 'fkey' => {
76
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
77
+ :required => false
78
+ },
79
+ 'default' => {
80
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
81
+ :required => false
82
+ },
83
+ 'encrypted' => {
84
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
85
+ :required => false
86
+ },
87
+ 'description' => {
88
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
89
+ :required => false
90
+ },
91
+ 'required_if' => {
92
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
93
+ :required => false
94
+ },
95
+ }
96
+ }
97
+ }
98
+ ]
99
+
100
+ # Initialize the class.
101
+ # @return void
102
+ def initialize(site, error_handler)
103
+
104
+ @site = Blufin::SiteResolver::validate_site(site)
105
+ @site_path = Blufin::SiteResolver::get_site_location(@site)
106
+ @site_name = Blufin::SiteResolver::get_site_name(@site)
107
+
108
+ @error_handler = error_handler
109
+
110
+ # Create resource folder if it doesn't exist.
111
+ resource_folder = "#{Blufin::SiteResolver::get_site_location(@site)}/yml/api/resources"
112
+ Blufin::Files::create_directory(resource_folder) unless Blufin::Files::path_exists(resource_folder)
113
+
114
+ # Loop through the array of files.
115
+ Blufin::Files.get_files_in_dir(resource_folder).each { |file| scan_file(file) }
116
+
117
+ end
118
+
119
+ private
120
+
121
+ # The main function that does the file scanning.
122
+ # @return void
123
+ def scan_file(file)
124
+
125
+ return unless file_is_valid(file)
126
+
127
+ # Get the YML data from the file.
128
+ begin
129
+ data = YAML.load_file(File.expand_path(file))
130
+ rescue Exception => e
131
+ @error_handler.add_error(Blufin::YmlErrorHandler::FILE_INVALID, file, nil, nil, file)
132
+ return
133
+ end
134
+
135
+ # TODO - FINISH/FIX
136
+ # data = validate_single_file(@site, @config_file_app, alter_structure_for_app(STRUCTURE_APP), error_handler)
137
+
138
+ end
139
+
140
+ # Validates the file.
141
+ # @return List
142
+ def file_is_valid(file)
143
+ valid = true
144
+ # Make sure this is a '.yml.' file.
145
+ unless Blufin::YmlCommon.is_yml_file(file)
146
+ @error_handler.add_error(Blufin::YmlErrorHandler::FILE_INVALID, file, nil, nil, 'Expected .yml file.')
147
+ valid = false
148
+ end
149
+ valid
150
+ end
151
+
152
+ end
153
+
154
+ end
@@ -0,0 +1,9 @@
1
+ module Blufin
2
+
3
+ class YmlSchemaFlags
4
+
5
+ attr_accessor :flags_raw, :definition_order, :auto_increment, :auto_increment_sort_order, :auto_increment_amount, :index, :index_sort_order, :nullable, :nullable_sort_order, :primary_key, :primary_key_sort_order, :unique, :unique_sort_order
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,1850 @@
1
+ module Blufin
2
+
3
+ class YmlSchemaValidator < Blufin::YmlValidatorBase
4
+
5
+ ID = 'id'
6
+
7
+ TYPE = 'type'
8
+ TYPE_BOOLEAN = 'BOOLEAN'
9
+ TYPE_DATE = 'DATE'
10
+ TYPE_DATETIME = 'DATETIME'
11
+ TYPE_DATETIME_INSERT = 'DATETIME_INSERT'
12
+ TYPE_DATETIME_UPDATE = 'DATETIME_UPDATE'
13
+ TYPE_DECIMAL = 'DECIMAL'
14
+ TYPE_ENUM = 'ENUM'
15
+ TYPE_ENUM_CUSTOM = 'ENUM_CUSTOM'
16
+ TYPE_ENUM_SYSTEM = 'ENUM_SYSTEM'
17
+ TYPE_INT = 'INT'
18
+ TYPE_INT_AUTO = 'INT_AUTO'
19
+ TYPE_INT_TINY = 'INT_TINY'
20
+ TYPE_INT_SMALL = 'INT_SMALL'
21
+ TYPE_INT_BIG = 'INT_BIG'
22
+ TYPE_TEXT = 'TEXT'
23
+ TYPE_TEXT_LONG = 'TEXT_LONG'
24
+ TYPE_VARCHAR = 'VARCHAR'
25
+
26
+ FLAG = 'flag'
27
+ FLAG_AUTO_INCREMENT = 'AUTO_INCREMENT'
28
+ FLAG_INDEX = 'INDEX'
29
+ FLAG_NULLABLE = 'NULLABLE'
30
+ FLAG_PRIMARY_KEY = 'PRIMARY_KEY'
31
+ FLAG_UNIQUE = 'UNIQUE'
32
+
33
+ FKEY = 'fkey'
34
+ LINK = 'link'
35
+
36
+ DESCRIPTION = 'description'
37
+ DESCRIPTION_TEXT = 'description_text'
38
+ DESCRIPTION_TYPE = 'description_type'
39
+ DESCRIPTION_HITS = 'description_hits'
40
+
41
+ TRANSIENT = 'transient'
42
+ REQUIRED = 'required'
43
+ REQUIRED_IF = 'required_if'
44
+ REQUIRED_IF_ENUM = 'required_if_enum'
45
+ CHILD_OF = 'child_of'
46
+ CHILD_TYPE = 'child_type'
47
+ ENCRYPTED = 'encrypted'
48
+ CURRENCY = 'currency'
49
+ AMOUNT = 'amount'
50
+
51
+ ERROR_MULTIPLE_FILES = 'Multiple files'
52
+
53
+ VALID_SCHEMAS = %w(app common config mock)
54
+ VALID_SCHEMAS_REGEX = 'app|common|config|mock'
55
+ VALID_SCHEMAS_GENERATE = %w(app common config)
56
+
57
+ APP = 'app'
58
+ CONFIG = 'config'
59
+ COMMON = 'common'
60
+ MOCK = 'mock'
61
+
62
+ REGEX_DECIMAL = /\ADECIMAL\(\d{1,2},\d{1,2}\)\z/
63
+ REGEX_ENUM = /\AENUM\('[A-Za-z_',]+'\)\z/
64
+ REGEX_ENUM_CUSTOM = /\AENUM_CUSTOM\('[A-Za-z]+'\)\z/
65
+ REGEX_ENUM_SYSTEM = /\AENUM_SYSTEM\('[A-Za-z]+'\)\z/
66
+ REGEX_VARCHAR = /\AVARCHAR\(\d+\)\z/
67
+
68
+ RESOURCE_TYPE_PARENT = 'PARENT'
69
+ RESOURCE_TYPE_OBJECT = 'OBJECT'
70
+ RESOURCE_TYPE_OBJECT_LIST = 'OBJECT_LIST'
71
+ RESOURCE_TYPE_OBJECT_LINK = 'OBJECT_LINK'
72
+
73
+ INT_TYPES = [
74
+ TYPE_INT,
75
+ TYPE_INT_AUTO
76
+ ]
77
+
78
+ TEXT_TYPES = [
79
+ TYPE_TEXT,
80
+ TYPE_TEXT_LONG
81
+ ]
82
+
83
+ DATETIME_TYPES = [
84
+ TYPE_DATETIME,
85
+ TYPE_DATETIME_INSERT,
86
+ TYPE_DATETIME_UPDATE,
87
+ TYPE_DATE
88
+ ]
89
+
90
+ RESOURCE_SCHEMA = 'schema'
91
+ RESOURCE_TABLE = 'table'
92
+
93
+ MAX_TABLE_CHARACTERS = 64
94
+
95
+ CONFIG_INTERNAL = 'internal'
96
+ CONFIG_OAUTH = 'oauth'
97
+
98
+ PATH_TO_CONFIG_YML = "#{App::Opt::get_base_path}#{App::Opt::OPT_PATH_YML}/api/schema"
99
+
100
+ RESERVED_WORDS = %w(abstract assert boolean break byte case catch char class const continue data_type default do double else enum extends false final finally float for goto if implements import instanceof int interface list long native new null package private protected public return short static strictfp super switch synchronized this throw throws transient true try void volatile while)
101
+
102
+ STRUCTURE = [
103
+ {
104
+ :section_name => 'config',
105
+ :section_type => Blufin::YmlValidatorBase::SECTION_TYPE_FIXED,
106
+ :section_data => {
107
+ CONFIG_INTERNAL => {
108
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_NESTED_DATA,
109
+ :required => false,
110
+ :section_data => {
111
+ 'GET' => {
112
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
113
+ :required => false
114
+ },
115
+ 'POST' => {
116
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
117
+ :required => false
118
+ },
119
+ 'PUT' => {
120
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
121
+ :required => false
122
+ },
123
+ 'PATCH' => {
124
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
125
+ :required => false
126
+ },
127
+ 'DELETE' => {
128
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
129
+ :required => false
130
+ }
131
+ }
132
+ },
133
+ CONFIG_OAUTH => {
134
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_NESTED_DATA,
135
+ :required => false,
136
+ :section_data => {
137
+ 'GET' => {
138
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
139
+ :required => false
140
+ },
141
+ 'POST' => {
142
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
143
+ :required => false
144
+ },
145
+ 'PUT' => {
146
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
147
+ :required => false
148
+ },
149
+ 'PATCH' => {
150
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
151
+ :required => false
152
+ },
153
+ 'DELETE' => {
154
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BLANK,
155
+ :required => false
156
+ }
157
+ }
158
+ },
159
+ 'description' => {
160
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
161
+ :required => false
162
+ }
163
+ }
164
+ },
165
+ {
166
+ :section_name => 'schema',
167
+ :section_type => Blufin::YmlValidatorBase::SECTION_TYPE_DYNAMIC,
168
+ :section_data => {
169
+ :section_alphabetical => false,
170
+ :section_regex => /\A(app\.|common\.|config\.|mock\.|mock\.)*[a-z_]+(\[\]|\[link\])*\z/,
171
+ :section_fields => {
172
+ 'type' => {
173
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
174
+ :required => false
175
+ },
176
+ 'flag' => {
177
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
178
+ :required => false
179
+ },
180
+ 'fkey' => {
181
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
182
+ :required => false
183
+ },
184
+ 'encrypted' => {
185
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
186
+ :required => false
187
+ },
188
+ 'description' => {
189
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
190
+ :required => false
191
+ },
192
+ 'required' => {
193
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_BOOLEAN,
194
+ :required => false
195
+ },
196
+ 'required_if' => {
197
+ :type => Blufin::YmlValidatorBase::FIELD_TYPE_TEXT,
198
+ :required => false
199
+ },
200
+ }
201
+ }
202
+ }
203
+ ]
204
+
205
+ # Initialize the class.
206
+ # @return void
207
+ def initialize(site, error_handler)
208
+
209
+ begin
210
+
211
+ @site = Blufin::SiteResolver::validate_site(site)
212
+
213
+ @error_handler = error_handler
214
+
215
+ @schema_data = {}
216
+ @schema_descriptions = {}
217
+
218
+ @schema_fks = {} # app.person.id (IS REFERENCED BY) => [app.company.primary_person_id:INT, app.invoice.person_id:INT]
219
+ @schema_fks_dependencies = {} # app.purchase (HAS 'FKEY' FIELDS TO) => [app.person, app.user, app.company]
220
+ @schema_fks_placeholders = {} # app.product (HAS THE FOLLOWING PLACEHOLDERS) => [app.company[link], app.product_variation[]]
221
+ @schema_fks_links = {} # app.address (IS LINKED TO FROM) => [app.company, app.person]
222
+
223
+ @schema_resources = {}
224
+
225
+ @primary_key_count = nil
226
+
227
+ @yml_schema_outputter = Blufin::YmlOutputter.new(@site)
228
+ @yml_enum_scanner = Blufin::ScannerJavaEnums.new(@site)
229
+
230
+ # Contains more descriptive error output.
231
+ @errors_array = []
232
+
233
+ # Validate "config:" section. This must be done before looping files because we will need some of the date from it.
234
+ @schema_config = {}
235
+
236
+ validate_config_section
237
+
238
+ config_files = Blufin::Files.get_files_in_dir(PATH_TO_CONFIG_YML)
239
+ schema_files = Blufin::Files.get_files_in_dir("#{Blufin::SiteResolver::get_site_location(@site)}/#{Blufin::Site::PATH_TO_YML_API_SCHEMA}")
240
+
241
+ # Loop through the array of files.
242
+ (config_files + schema_files).each { |file| scan_file(file) }
243
+
244
+ validate_table_duplicates
245
+ validate_foreign_keys
246
+ validate_foreign_keys_placeholders
247
+ validate_required_schema_definitions
248
+
249
+ build_resources
250
+ build_fks_dependencies
251
+
252
+ validate_resources
253
+ validate_http_methods
254
+
255
+ @yml_schema_outputter.set_schema_fks_links(@schema_fks_links)
256
+
257
+ rescue Exception => e
258
+
259
+ Blufin::Terminal::print_exception(e)
260
+
261
+ end
262
+
263
+ end
264
+
265
+ # @return Array
266
+ def get_errors_array
267
+ @errors_array
268
+ end
269
+
270
+ # @return Hash
271
+ def get_schema_data
272
+ @schema_data
273
+ end
274
+
275
+ # @return Hash
276
+ def get_schema_config
277
+ @schema_config
278
+ end
279
+
280
+ # @return Hash
281
+ def get_schema_descriptions
282
+ @schema_descriptions
283
+ end
284
+
285
+ # @return Hash
286
+ def get_schema_fks
287
+ @schema_fks
288
+ end
289
+
290
+ # @return Hash
291
+ def get_schema_fks_placeholders
292
+ @schema_fks_placeholders
293
+ end
294
+
295
+ # @return Hash
296
+ def get_schema_fks_dependencies
297
+ @schema_fks_dependencies
298
+ end
299
+
300
+ # @return Hash
301
+ def get_schema_fks_links
302
+ @schema_fks_links
303
+ end
304
+
305
+ # @return Hash
306
+ def get_schema_resources
307
+ @schema_resources
308
+ end
309
+
310
+ # @return Object
311
+ def get_yml_schema_outputter
312
+ @yml_schema_outputter
313
+ end
314
+
315
+ # Use this to extract flags OUTSIDE the validator.
316
+ # @return Blufin::YmlSchemaFlags
317
+ def extract_flags(flags)
318
+ raise RuntimeError, 'Input cannot be nil in YmlSchemaValidator::extract_flags()' if flags.nil? || flags.strip == ''
319
+ Blufin::YmlCommon::extract_flags(flags)[0]
320
+ end
321
+
322
+ private
323
+
324
+ # Scans a file -- validates & extracts data.
325
+ # @return void
326
+ def scan_file(file)
327
+
328
+ schema, table, valid = validate_file(file)
329
+
330
+ # Make sure the table isn't a Java reserved word (or phrase/word that has special meaning within our stack).
331
+ if RESERVED_WORDS.include?(table)
332
+ add_error(file, schema, table, nil, 'Table name cannot be a Java reserved word or phrase/word used by our stack.', table)
333
+ return
334
+ end
335
+
336
+ # Make sure there are no uppercase letters in schema + table..
337
+ add_error(file, nil, nil, nil, 'Folder contains uppercase letters and/or numbers.', schema) if contains_uppercase_letter_or_number(schema)
338
+ add_error(file, nil, nil, nil, 'File contains uppercase letters and/or numbers.', table) if contains_uppercase_letter_or_number(table)
339
+
340
+ # Make sure that the table name is no longer than 35 characters.
341
+ add_error(file, nil, nil, nil, "Table name is too long. Maximum allowed characters is #{MAX_TABLE_CHARACTERS}.", "#{table.length} characters") if table.length > MAX_TABLE_CHARACTERS
342
+
343
+ # If file is invalid, return NULL.
344
+ return unless valid
345
+
346
+ # Do a manual check for excessive blank lines, etc.
347
+ return unless check_file_lines_manually(file, schema, table)
348
+
349
+ # Get the YML data from the file.
350
+ begin
351
+ all_data = YAML.load_file(File.expand_path(file))
352
+ rescue Exception => e
353
+ add_error(file, nil, nil, nil, 'Unable to parse file \xe2\x80\x94 invalid YML.', e.message)
354
+ return
355
+ end
356
+
357
+ data = all_data['schema']
358
+
359
+ # Make sure the file has data.
360
+ if Blufin::YmlCommon::is_empty(data)
361
+ add_error(file, nil, nil, nil, 'File is empty.', nil)
362
+ return
363
+ end
364
+
365
+ # Make sure that the first column in every table is ID.
366
+ add_error(file, schema, table, data.keys.first, "First column in table must be: #{ID.upcase}", data.keys.first) unless data.keys.first == ID
367
+
368
+ @primary_key_count = 0
369
+ @column_count = 0
370
+ @types = []
371
+ @transient_fields = []
372
+ @data = data
373
+ @booleans_finished = false
374
+
375
+ columns_with_name = []
376
+
377
+ # Loop the columns.
378
+ data.each do |column, column_data|
379
+ extract_and_validate_column(file, schema, table, column, column_data)
380
+ columns_with_name << column if column =~ /name/
381
+ end
382
+
383
+ # Make sure if there is ONLY 1 "name" field that it matches the table -- IE: user (table) -> user_name OR user_[.*]_name
384
+ if columns_with_name.length == 1
385
+ column_name = columns_with_name[0]
386
+ unless column_name =~ /#{table}_[a-z_]*name/
387
+ add_error(file, schema, table, column_name, "Name field should match regex \xe2\x86\x92 #{table}_name OR #{table}_[a-z_]name", "Found: #{column_name}")
388
+ end
389
+ end
390
+
391
+ types_more_than_one = []
392
+ @types.detect { |e| types_more_than_one << [e, @types.count(e)] if @types.count(e) > 1 }
393
+
394
+ # Make sure there are ONLY ONE of certain types of column -- INT_AUTO being 1 of them.
395
+ [TYPE_DATETIME_INSERT, TYPE_DATETIME_UPDATE, TYPE_INT_AUTO].each do |allowed_only_once|
396
+ types_more_than_one.each do |found_more_than_once|
397
+ if found_more_than_once[0] == allowed_only_once
398
+ add_error(file, schema, table, nil, "Can only have one #{allowed_only_once} field per table.", "Found: #{found_more_than_once[1]} times")
399
+ end
400
+ end
401
+ end
402
+
403
+ # Make sure that column names don't conflict with transients.
404
+ if @transient_fields.length > 0
405
+ @transient_fields.each do |transient_field|
406
+ if @schema_data[schema][table].keys.include?(transient_field)
407
+ unless @schema_data[schema][table][transient_field][TRANSIENT]
408
+ @error_handler.add_error(Blufin::YmlErrorHandler::FIELD_NAME_TRANSIENT_CONFLICT, "#{schema}/#{table}.yml", 'schema', transient_field, transient_field)
409
+ end
410
+ end
411
+ end
412
+ end
413
+
414
+ add_error(file, schema, table, nil, "No #{FLAG_PRIMARY_KEY} found.", "# of PKs: #{@primary_key_count}") if @primary_key_count < 1
415
+ add_error(file, schema, table, nil, "More than 1 #{FLAG_PRIMARY_KEY} found.", "# of PKs: #{@primary_key_count}") if @primary_key_count > 1
416
+
417
+ end
418
+
419
+ # Validates the file and extracts 'schema' + 'table' data.
420
+ # @return List
421
+ def validate_file(file)
422
+ valid = true
423
+ # Make sure file exists.
424
+ unless Blufin::Files.file_exists(file)
425
+ add_error(file, nil, nil, nil, 'File not found!', nil)
426
+ valid = false
427
+ end
428
+ # Make sure this is a '.yml.' file.
429
+ unless Blufin::YmlCommon.is_yml_file(file)
430
+ add_error(file, nil, nil, nil, 'File must have extension: .yml', nil)
431
+ valid = false
432
+ end
433
+ file_parts = file.split('/')
434
+ schema = file_parts[file_parts.length - 2]
435
+ table = File.basename(file, '.yml')
436
+ # Make sure the schema is valid.
437
+ unless VALID_SCHEMAS.include? schema
438
+ add_error(file, schema, nil, nil, "Invalid schema. Must be one of: #{VALID_SCHEMAS.join(', ')}", schema)
439
+ valid = false
440
+ end
441
+ return schema, table, valid
442
+ end
443
+
444
+ # @return void
445
+ # noinspection RubyUnusedLocalVariable
446
+ def check_file_lines_manually(file, schema, table)
447
+
448
+ file_is_valid = true
449
+
450
+ field_name = nil
451
+ field_type = nil
452
+ initial_comments_found = false
453
+ previous_was_comment = false
454
+ previous_was_blank_line = false
455
+
456
+ line_count = 0
457
+ line_contents = []
458
+
459
+ validating_config = false
460
+ validated_config = false
461
+ validating_schema = false
462
+ validated_schema = false
463
+
464
+ lines = Blufin::Files::read_file(file)
465
+ lines.each_with_index do |line, idx|
466
+
467
+ line_count = line_count + 1
468
+
469
+ if line =~ /\Aconfig:/
470
+ if validated_config
471
+ add_error(file, schema, table, nil, 'Found more than one "config:" section.', "Multiple \"config:\" sections found.")
472
+ file_is_valid = false
473
+ end
474
+ validating_config = true
475
+ validating_schema = false
476
+ validated_config = true
477
+ next
478
+ elsif line =~ /\Aschema:/
479
+ if validated_schema
480
+ add_error(file, schema, table, nil, 'Found more than one "schema:" section.', "Multiple \"schema:\" sections found.")
481
+ file_is_valid = false
482
+ end
483
+ validating_config = false
484
+ validating_schema = true
485
+ validated_schema = true
486
+ next
487
+ end
488
+
489
+ if validating_schema
490
+
491
+ if line =~ /\A#.?/ || line =~ /\A\s{2}#.?/ || line =~ /\A\s{4}#.?/
492
+ previous_was_comment = true
493
+ previous_was_blank_line = false
494
+ elsif line == "\n"
495
+ initial_comments_found = true if previous_was_comment && !initial_comments_found
496
+ if previous_was_blank_line
497
+ add_error(file, schema, table, nil, 'More than one blank (or possibly invalid) line found.', "Line: #{line_count}")
498
+ file_is_valid = false
499
+ end
500
+ previous_was_blank_line = true
501
+ elsif line =~ /\A\s{2}[a-z_]+:/
502
+ line_contents = validate_line_content(line, line_contents, file, schema, table, line_count)
503
+ field_name = line.gsub(':', '').strip
504
+ field_type = nil
505
+ previous_was_blank_line = false
506
+ elsif line =~ /\A\s{2}[a-z_.]+\[\]:/
507
+ line_contents = validate_line_content(line, line_contents, file, schema, table, line_count)
508
+ field_name = line.gsub(':', '').strip
509
+ field_type = nil
510
+ previous_was_blank_line = false
511
+ elsif line =~ /\A\s{2}[a-z_.]+\[#{LINK}\]:/
512
+ line_contents = validate_line_content(line, line_contents, file, schema, table, line_count)
513
+ field_name = line.gsub(':', '').strip
514
+ field_type = nil
515
+ previous_was_blank_line = false
516
+ elsif line =~ /\A\s{2}[a-z_.]+:/
517
+ line_contents = validate_line_content(line, line_contents, file, schema, table, line_count)
518
+ field_name = line.gsub(':', '').strip
519
+ field_type = nil
520
+ previous_was_blank_line = false
521
+ elsif line =~ /\A\s{4}[a-z_]+:/
522
+ if line =~ /\A\s{4}type:/
523
+ field_type = line.dup.split(':')
524
+ field_type = field_type[1].strip
525
+ end
526
+ if previous_was_blank_line
527
+ add_error(file, schema, table, nil, 'More than one blank (or possibly invalid) line found.', "Line: #{line_count}")
528
+ file_is_valid = false
529
+ end
530
+ previous_was_blank_line = false
531
+ else
532
+ add_error_with_multi_content(file, schema, table, nil, 'Something is wrong with this line, perhaps check format, key-capitalization or whitespace?', "Line: #{line_count}", [line])
533
+ file_is_valid = false
534
+ end
535
+
536
+ if idx == (lines.length - 1) && line[-1, 2] == "\n"
537
+ add_error(file, schema, table, nil, 'Cannot have blank, trailing lines in YML file.', "Line: #{line_count}")
538
+ file_is_valid = false
539
+ end
540
+
541
+ return unless line_contents
542
+
543
+ end
544
+
545
+ end
546
+
547
+ # Make sure that each file has a "config:" section.
548
+ unless validated_config
549
+ add_error(file, schema, table, nil, '"config:" section is missing.', "No \"config:\" section found.")
550
+ file_is_valid = false
551
+ end
552
+
553
+ # Make sure that each file has a "schema:" section.
554
+ unless validated_schema
555
+ add_error(file, schema, table, nil, '"schema:" section is missing.', "No \"schema:\" section found.")
556
+ file_is_valid = false
557
+ end
558
+
559
+ file_is_valid
560
+ end
561
+
562
+ # Makes sure when checking lines manually, that we don't have an identical definition (as these won't show up in hashes).
563
+ # @return Array
564
+ def validate_line_content(line, line_contents, file, schema, table, line_count)
565
+ if line_contents.include?(line)
566
+ add_error(file, schema, table, nil, "Duplicate definition found: #{line}", "Line: #{line_count}")
567
+ false
568
+ else
569
+ line_contents << line
570
+ line_contents
571
+ end
572
+ end
573
+
574
+ # Validates column data and extracts data to global array. Adds errors if they exist.
575
+ # @return void
576
+ def extract_and_validate_column(file, schema, table, column_name, column_data)
577
+
578
+ define_count = 0
579
+ define_order = {}
580
+
581
+ @column_count = @column_count + 1
582
+
583
+ @type = (!column_data.nil? && column_data.has_key?(TYPE)) ? column_data[TYPE] : nil
584
+ @types << @type unless @type.nil?
585
+
586
+ @booleans_finished = true if @type != TYPE_BOOLEAN && column_name.downcase != ID
587
+
588
+ # Cannot have both required && required_if properties.
589
+ add_error(file, schema, table, column_name, "Cannot have both #{REQUIRED} and #{REQUIRED_IF} properties.", column_name) if !column_data.nil? && !column_data[REQUIRED].nil? && !column_data[REQUIRED_IF].nil?
590
+
591
+ # Make sure column_name is lowercase & doesn't contain numbers.
592
+ if contains_uppercase_letter_or_number(column_name)
593
+ add_error(file, schema, table, column_name, 'Column name must be lowercase & not contain numbers.', column_name)
594
+ return
595
+ end
596
+
597
+ # Make sure the column isn't a Java reserved word (or phrase/word that has special meaning within our stack).
598
+ reserved_words = RESERVED_WORDS
599
+ reserved_words << 'mock parent_id'
600
+ if reserved_words.include?(column_name)
601
+ add_error(file, schema, table, column_name, 'Column name cannot be a Java reserved word or phrase/word used by our stack.', column_name)
602
+ return
603
+ end
604
+
605
+ # If this is a foreign key placeholder IE: app.ebay_aliases[]: or app.order_ebay: ...
606
+ if Blufin::YmlSchemaValidator::column_is_object(column_name)
607
+
608
+ fkp_key = "#{schema}.#{table}"
609
+ @schema_fks_placeholders[fkp_key] = [] if @schema_fks_placeholders[fkp_key].nil?
610
+ @schema_fks_placeholders[fkp_key] << column_name
611
+
612
+ column_data = {} unless column_data.is_a?(Hash)
613
+
614
+ column_data.each do |key, value|
615
+
616
+ case key
617
+ when DESCRIPTION
618
+ @type = 'FAKE-TYPE' # Hacky fix
619
+ validate_description(file, schema, table, column_name, value)
620
+ when TYPE
621
+ add_error(file, schema, table, column_name, "Placeholders cannot have #{TYPE} property.", "Found: #{value}")
622
+ next
623
+ when FLAG
624
+ add_error(file, schema, table, column_name, "Placeholders cannot have #{FLAG} property.", "Found: #{value}")
625
+ next
626
+ when FKEY
627
+ add_error(file, schema, table, column_name, "Placeholders cannot have #{FKEY} property.", "Found: #{value}")
628
+ next
629
+ when REQUIRED
630
+ validate_required(file, schema, table, column_name, value)
631
+ when REQUIRED_IF
632
+ validate_required_if(file, schema, table, column_name, value)
633
+ when ENCRYPTED
634
+ add_error(file, schema, table, column_name, "Placeholders cannot have #{ENCRYPTED} property.", "Found: #{value}")
635
+ next
636
+ else
637
+ add_error(file, schema, table, column_name, 'Invalid key.', key)
638
+ end
639
+
640
+ end
641
+
642
+ # Add data to @schema_data (with type, which doesn't exist in the YML file(s) as it's inferred).
643
+ if column_name =~ /^(#{VALID_SCHEMAS_REGEX})\.[\w]+$/
644
+ column_data[TYPE] = Blufin::ScannerJavaEmbeddedObjects::OBJECT
645
+ elsif column_name =~ /^(#{VALID_SCHEMAS_REGEX})\.[\w]+\[\]$/
646
+ column_data[TYPE] = Blufin::ScannerJavaEmbeddedObjects::OBJECT_LIST
647
+ elsif column_name =~ /^(#{VALID_SCHEMAS_REGEX})\.[\w]+\[#{LINK}\]$/
648
+ column_data[TYPE] = Blufin::ScannerJavaEmbeddedObjects::OBJECT_LINK
649
+ else
650
+ raise RuntimeError, "Unable to determine object type: #{column_name}"
651
+ end
652
+
653
+ if [Blufin::ScannerJavaEmbeddedObjects::OBJECT_LIST, Blufin::ScannerJavaEmbeddedObjects::OBJECT_LINK].include?(column_data[TYPE])
654
+ add_error(file, schema, table, column_name, "#{column_data[TYPE]} fields cannot have a #{REQUIRED} property.", column_name) if column_data.has_key?(REQUIRED)
655
+ add_error(file, schema, table, column_name, "#{column_data[TYPE]} fields cannot have a #{REQUIRED_IF} property.", column_name) if column_data.has_key?(REQUIRED_IF)
656
+ return if column_data.has_key?(REQUIRED) || column_data.has_key?(REQUIRED_IF)
657
+ end
658
+
659
+ add_schema_data(schema, table, column_name, column_data)
660
+
661
+ return
662
+
663
+ else
664
+ # Add data to @schema_data
665
+ add_schema_data(schema, table, column_name, column_data)
666
+ end
667
+
668
+ # Make sure the column has data.
669
+ if Blufin::YmlCommon::is_empty(column_data)
670
+ raise RuntimeError, "Expected Hash, instead got: #{column_data.class}" unless column_data.is_a?(Hash)
671
+ add_error(file, schema, table, column_name, 'Column has no defining data.', column_name)
672
+ return
673
+ end
674
+
675
+ # Make sure there are only 1 of each key (although YAML parser should never allow this).
676
+ if column_data.keys != column_data.keys.uniq
677
+ add_error(file, schema, table, column_name, 'Found duplicate key.', nil)
678
+ return
679
+ end
680
+
681
+ # Make sure there is a type. All columns need it.
682
+ if column_data[TYPE].nil?
683
+ add_error(file, schema, table, column_name, "No #{TYPE} found. All columns need to have a #{TYPE}.", nil)
684
+ return
685
+ end
686
+
687
+ # IDs are validated separately & uniquely.
688
+ if column_name.downcase == ID
689
+ validate_id_column(file, schema, table, column_name, column_data)
690
+ return
691
+ end
692
+
693
+ # Fields containing the word 'currency' are validated separately & uniquely.
694
+ if column_name.downcase =~ /#{CURRENCY}/
695
+ validate_currency_code_column(file, schema, table, column_name, column_data)
696
+ return
697
+ end
698
+
699
+ # Fields named 'amount' are validated separately & uniquely.
700
+ if column_name.downcase =~ /\A#{AMOUNT}\z/
701
+ validate_amount_column(file, schema, table, column_name, column_data)
702
+ return
703
+ end
704
+
705
+ @flags = nil
706
+ @foreign_key = false
707
+ @column_name = column_name
708
+
709
+ column_data.each do |key, value|
710
+
711
+ key_invalid = false
712
+
713
+ case key
714
+ when DESCRIPTION
715
+ validate_description(file, schema, table, column_name, value)
716
+ when TYPE
717
+ validate_type(file, schema, table, column_name, value)
718
+ when FLAG
719
+ @flags = extract_flags_for_validator(file, schema, table, column_name, value)
720
+ validate_flags(file, schema, table, column_name)
721
+ when FKEY
722
+ @foreign_key = true
723
+ validate_foreign_key(file, schema, table, column_name, value)
724
+ when REQUIRED
725
+ validate_required(file, schema, table, column_name, value)
726
+ when REQUIRED_IF
727
+ validate_required_if(file, schema, table, column_name, value)
728
+ when ENCRYPTED
729
+ validate_encrypted(file, schema, table, column_name, value)
730
+ else
731
+ key_invalid = true
732
+ add_error(file, schema, table, column_name, 'Invalid key', key)
733
+ end
734
+
735
+ unless key_invalid
736
+ define_count = define_count + 1
737
+ define_order[key] = define_count
738
+ end
739
+
740
+ end
741
+
742
+ validate_definition_order(file, schema, table, column_name, [TYPE, FLAG, FKEY, ENCRYPTED, DESCRIPTION], define_order, 'Keys')
743
+
744
+ end
745
+
746
+ # Validate the ID column. This column is special and must be validated separately.
747
+ # @return void
748
+ def validate_id_column(file, schema, table, column_name, column_data)
749
+
750
+ # Make sure ID is first column.
751
+ add_error(file, schema, table, column_name, 'ID column must be first column in table.', "Is actually ##{@column_count}") unless @column_count == 1
752
+
753
+ # Check for invalid keys.
754
+ invalid_keys = Blufin::YmlCommon.validate_keys(column_data.keys, [TYPE, FLAG])
755
+ add_error(file, schema, table, column_name, 'ID column has invalid keys.', invalid_keys.join(',')) unless invalid_keys.nil?
756
+
757
+ # Make sure the type is INT_AUTO
758
+ add_error(file, schema, table, column_name, "ID column is missing type which must be: #{TYPE_INT_AUTO}", nil) if column_data[TYPE].nil?
759
+ add_error(file, schema, table, column_name, "ID columns must be of type: #{TYPE_INT_AUTO}", column_data[TYPE]) unless column_data[TYPE] == TYPE_INT_AUTO && !column_data[TYPE].nil?
760
+
761
+ flag_error_message = "ID column must have the following flags: #{[FLAG_PRIMARY_KEY, "#{FLAG_AUTO_INCREMENT} (optional)"].join(', ')}"
762
+
763
+ # Make sure the flags are: (optional) AUTO_INCREMENT(??) & PRIMARY_KEY
764
+ if !column_data[FLAG].nil?
765
+
766
+ flags = extract_flags_for_validator(file, schema, table, column_name, column_data[FLAG])
767
+
768
+ # Make sure the 1st flag is either: AUTO_INCREMENT or there is no 1st flag.
769
+ if flags.auto_increment
770
+ add_error(file, schema, table, column_name, "ID column first flag can only be: #{FLAG_AUTO_INCREMENT}(??) and/or PRIMARY KEY", flags.flags_raw) unless flags.auto_increment && flags.auto_increment_amount != nil && flags.auto_increment_sort_order == 1
771
+ pk_order = 2
772
+ pk_text = 'second'
773
+ else
774
+ pk_order = 1
775
+ pk_text = 'first'
776
+ end
777
+
778
+ # Make sure the 2nd flag is: PRIMARY_KEY
779
+ add_error(file, schema, table, column_name, "#{ID.upcase} column #{pk_text} flag must be: #{FLAG_PRIMARY_KEY}", nil) unless flags.primary_key == true && flags.primary_key_sort_order == pk_order
780
+ @primary_key_count = @primary_key_count + 1 if flags.primary_key
781
+
782
+ # Make sure there are no other flags.
783
+ add_error(file, schema, table, column_name, "#{ID.upcase} column should not have flag: #{FLAG_INDEX} as this is already implied.", flags.flags_raw) if flags.index
784
+ add_error(file, schema, table, column_name, "#{ID.upcase} column cannot have flag: #{FLAG_NULLABLE}", flags.flags_raw) if flags.nullable
785
+ add_error(file, schema, table, column_name, "#{ID.upcase} column should not have flag: #{FLAG_UNIQUE} as this is already implied.", flags.flags_raw) if flags.unique
786
+
787
+ if flags.auto_increment
788
+ @yml_schema_outputter.add_auto_increment_indexed_table(schema, table, flags.auto_increment_amount)
789
+ else
790
+ @yml_schema_outputter.add_auto_increment_table(schema, table)
791
+ end
792
+
793
+ else
794
+ add_error(file, schema, table, column_name, flag_error_message, nil)
795
+ end
796
+
797
+ # Make sure there is no 'description', 'fkey' or 'required_if'.
798
+ add_error(file, schema, table, column_name, "#{ID.upcase} column cannot have: #{FKEY}", column_data[FKEY]) unless column_data[FKEY].nil?
799
+ add_error(file, schema, table, column_name, "#{ID.upcase} column cannot have: #{DESCRIPTION}", column_data[DESCRIPTION]) unless column_data[DESCRIPTION].nil?
800
+ add_error(file, schema, table, column_name, "#{ID.upcase} column cannot have: #{REQUIRED} (this is inferred).", column_data[REQUIRED]) unless column_data[REQUIRED].nil?
801
+ add_error(file, schema, table, column_name, "#{ID.upcase} column cannot have: #{REQUIRED_IF}", column_data[REQUIRED_IF]) unless column_data[REQUIRED_IF].nil?
802
+
803
+ end
804
+
805
+ # Validate any column with the word 'currency' in it. This column is special and must be validated separately.
806
+ # @return void
807
+ def validate_currency_code_column(file, schema, table, column_name, column_data)
808
+
809
+ # Make sure the type is ENUM_SYSTEM('Money')
810
+ add_error(file, schema, table, column_name, "#{CURRENCY.upcase} columns must be of type: #{TYPE_ENUM_SYSTEM}('Money')", column_data[TYPE]) unless column_data[TYPE] == "#{TYPE_ENUM_SYSTEM}('Money')" && !column_data[TYPE].nil?
811
+
812
+ # Make sure there is no 'fkey' or 'required_if'.
813
+ add_error(file, schema, table, column_name, "#{CURRENCY.upcase} column cannot have: #{FKEY}", column_data[FKEY]) unless column_data[FKEY].nil?
814
+ add_error(file, schema, table, column_name, "#{CURRENCY.upcase} column cannot have: #{REQUIRED} (this is inferred).", column_data[REQUIRED]) unless column_data[REQUIRED].nil?
815
+ add_error(file, schema, table, column_name, "#{CURRENCY.upcase} column cannot have: #{REQUIRED_IF}", column_data[REQUIRED_IF]) unless column_data[REQUIRED_IF].nil?
816
+
817
+ end
818
+
819
+ # Validates any column called 'amount'. This column is special and must be validated separately.
820
+ # @return void
821
+ def validate_amount_column(file, schema, table, column_name, column_data)
822
+
823
+ # Make sure 'amount' column is DECIMAL(13,2)
824
+ if column_data[TYPE].nil? || column_data[TYPE] != "#{TYPE_DECIMAL}(13,2)"
825
+ add_error(file, schema, table, column_name, "#{AMOUNT.upcase} column preceding #{CURRENCY.upcase} must be of type: #{TYPE_DECIMAL}(13,2)", column_data[TYPE])
826
+ return
827
+ end
828
+
829
+ # Make sure there is no 'description', 'fkey' or 'required_if'.
830
+ add_error(file, schema, table, column_name, "#{AMOUNT.upcase} column cannot have: #{FKEY}", column_data[FKEY]) unless column_data[FKEY].nil?
831
+ add_error(file, schema, table, column_name, "#{AMOUNT.upcase} column cannot have: #{REQUIRED} (this is inferred).", column_data[REQUIRED]) unless column_data[REQUIRED].nil?
832
+ add_error(file, schema, table, column_name, "#{AMOUNT.upcase} column cannot have: #{REQUIRED_IF}", column_data[REQUIRED_IF]) unless column_data[REQUIRED_IF].nil?
833
+
834
+ end
835
+
836
+ # @return void
837
+ def validate_description(file, schema, table, column_name, value)
838
+
839
+ if Blufin::YmlCommon::is_empty(value)
840
+ add_error(file, schema, table, column_name, 'No description text found.', "Found: #{value.nil? || value.strip == '' ? "\x1B[38;5;196mNothing\x1B[0m" : value}")
841
+ return
842
+ end
843
+
844
+ if @type.nil?
845
+ add_error(file, schema, table, column_name, "#{TYPE} must be defined before #{DESCRIPTION}, cannot validate #{DESCRIPTION}.", nil)
846
+ return
847
+ end
848
+
849
+ if @schema_descriptions[column_name].nil?
850
+ @schema_descriptions[column_name] = [
851
+ {
852
+ DESCRIPTION_TEXT => value,
853
+ DESCRIPTION_TYPE => @type,
854
+ DESCRIPTION_HITS => 1
855
+ }
856
+ ]
857
+ else
858
+ hash_matched = false
859
+ new_array_of_hashes = []
860
+ @schema_descriptions[column_name].each do |hash|
861
+ if hash[DESCRIPTION_TEXT] == value && hash[DESCRIPTION_TYPE] == @type
862
+ new_array_of_hashes << {
863
+ DESCRIPTION_TEXT => value,
864
+ DESCRIPTION_TYPE => @type,
865
+ DESCRIPTION_HITS => hash[DESCRIPTION_HITS] + 1
866
+ }
867
+ hash_matched = true
868
+ break
869
+ else
870
+ new_array_of_hashes << hash
871
+ end
872
+ end
873
+ unless hash_matched
874
+ new_array_of_hashes << {
875
+ DESCRIPTION_TEXT => value,
876
+ DESCRIPTION_TYPE => @type,
877
+ DESCRIPTION_HITS => 1
878
+ }
879
+ end
880
+ @schema_descriptions[column_name] = new_array_of_hashes
881
+ end
882
+
883
+ end
884
+
885
+ # @return void
886
+ def validate_type(file, schema, table, column_name, type)
887
+
888
+ if type == TYPE_INT_AUTO
889
+ add_error(file, schema, table, column_name, "Type: #{TYPE_INT_AUTO} should only be used for #{ID.upcase} fields.", type)
890
+ return
891
+ end
892
+
893
+ if type == TYPE_BOOLEAN && @booleans_finished
894
+ add_error(file, schema, table, column_name, "Type: #{TYPE_BOOLEAN} must be at the very top of file. Only #{ID.upcase} can come before it.", "Position: #{@column_count}")
895
+ return
896
+ end
897
+
898
+ types_to_skip = [
899
+ TYPE_BOOLEAN,
900
+ TYPE_INT,
901
+ TYPE_INT_TINY,
902
+ TYPE_INT_SMALL,
903
+ TYPE_INT_BIG,
904
+ TYPE_TEXT,
905
+ TYPE_TEXT_LONG
906
+ ]
907
+
908
+ unless types_to_skip.include?(type)
909
+ if type =~ REGEX_ENUM
910
+ begin
911
+ enum_values = Blufin::YmlCommon::enum_value_extractor(type, @site)
912
+ rescue
913
+ add_error(file, schema, table, column_name, "#{TYPE_ENUM} is invalid somehow. Please check your syntax.", type)
914
+ return
915
+ end
916
+ if enum_values.include?(nil) || enum_values.include?('')
917
+ add_error(file, schema, table, column_name, "#{TYPE_ENUM} contains nil (or blank) values.", type)
918
+ else
919
+ add_error(file, schema, table, column_name, "#{TYPE_ENUM} contains duplicate values.", type) unless enum_values.map(&:upcase).uniq.length == enum_values.length
920
+ # Make sure that system-generated enums are all capital + underscores.
921
+ enum_values.each do |enum_value|
922
+ add_error(file, schema, table, column_name, "#{type} can only contain capital letters & underscores.", enum_value) unless enum_value =~ /\A[A-Z_]+\z/
923
+ end
924
+ end
925
+ elsif type =~ REGEX_ENUM_CUSTOM
926
+ begin
927
+ enum_values = @yml_enum_scanner.get_enum_custom_values_for(Blufin::YmlCommon::enum_name_extractor(@type))
928
+ add_error(file, schema, table, column_name, "#{type} has no values defined in Java counterpart.", type) unless enum_values.any?
929
+ rescue
930
+ add_error(file, schema, table, column_name, "#{type} has no Java counterpart.", type)
931
+ end
932
+ elsif type =~ REGEX_ENUM_SYSTEM
933
+ begin
934
+ enum_values = @yml_enum_scanner.get_enum_system_values_for(Blufin::YmlCommon::enum_name_extractor(@type))
935
+ add_error(file, schema, table, column_name, "#{type} has no values defined in Java counterpart.", type) unless enum_values.any?
936
+ rescue
937
+ add_error(file, schema, table, column_name, "#{type} has no Java counterpart.", type)
938
+ end
939
+ elsif type =~ REGEX_VARCHAR
940
+ varchar_amount = Blufin::Strings::extract_using_regex(type, /\(\d+\)\z/, %w{( )})
941
+ add_error(file, schema, table, column_name, "Invalid #{TYPE_VARCHAR} definition, must be integer between 1 - 4096.", type) if varchar_amount.to_i.to_s != varchar_amount || (varchar_amount.to_i > 4096 || varchar_amount.to_i < 1)
942
+ elsif type =~ REGEX_DECIMAL
943
+ decimal_m, decimal_d = Blufin::YmlCommon::decimal_extract_values(type)
944
+ add_error(file, schema, table, column_name, 'Decimal M (1st number) must be between 1 - 65.', type) unless decimal_m.to_i > 0 && decimal_m.to_i < 66
945
+ add_error(file, schema, table, column_name, 'Decimal D (2nd number) must be between 1 - 30.', type) unless decimal_d.to_i > 0 && decimal_d.to_i < 31
946
+ add_error(file, schema, table, column_name, 'Decimal M >= D -- 1st digit must be equal or larger than 2nd.', type) if (decimal_m.to_i < decimal_d.to_i)
947
+ elsif [
948
+ Blufin::YmlSchemaValidator::TYPE_DATETIME,
949
+ Blufin::YmlSchemaValidator::TYPE_DATETIME_INSERT,
950
+ Blufin::YmlSchemaValidator::TYPE_DATETIME_UPDATE
951
+ ].include?(type)
952
+ add_error(file, schema, table, column_name, "#{type} fields must end in '_datetime'", column_name) unless column_name =~ /_datetime\z/
953
+ elsif type == TYPE_DATE
954
+ add_error(file, schema, table, column_name, "#{type} fields must end in '_date'", column_name) if column_name !~ /_date\z/ && column_name != 'date'
955
+ else
956
+ extra = ''
957
+ types_to_skip.each do |valid_type|
958
+ if type.downcase == valid_type.downcase
959
+ extra = " Perhaps you meant: #{valid_type} (capitalized)"
960
+ break
961
+ end
962
+ end
963
+ extra = " Perhaps you meant: #{TYPE_DECIMAL}(?,?)" if extra == '' && type.downcase == TYPE_DECIMAL.downcase
964
+ error_message = "Invalid #{TYPE}.#{extra}"
965
+ error_message << " #{TYPE_ENUM}s definitions cannot be blank an/or have spaces." if type =~ /\AENUM/
966
+ add_error(file, schema, table, column_name, error_message, type)
967
+ end
968
+ end
969
+
970
+ # Make sure the INSERT + UPDATE fields are named correctly.
971
+ add_error(file, schema, table, column_name, "#{TYPE_DATETIME_INSERT} fields must end in: created_datetime", @column_name) if type == TYPE_DATETIME_INSERT && !(@column_name =~ /^([a-z_]+_)?created_datetime$/)
972
+ add_error(file, schema, table, column_name, "#{TYPE_DATETIME_UPDATE} fields must end in: modified_datetime", @column_name) if type == TYPE_DATETIME_UPDATE && !(@column_name =~ /^([a-z_]+_)?modified_datetime$/)
973
+
974
+ end
975
+
976
+ # @return void
977
+ def validate_flags(file, schema, table, column_name)
978
+
979
+ return if @flags.nil?
980
+
981
+ if @type.nil?
982
+ add_error(file, schema, table, column_name, "#{TYPE} must be defined before #{FLAG}, cannot validate #{FLAG}.", nil)
983
+ return
984
+ end
985
+
986
+ raise RuntimeError, "Flags must be of type: Blufin::YmlSchemaFlags. You passed: #{@flags.class}" unless @flags.is_a? Blufin::YmlSchemaFlags
987
+
988
+ if @flags.auto_increment
989
+ add_error(file, schema, table, column_name, "Only #{ID.upcase} fields can have #{FLAG_AUTO_INCREMENT} flags.", @flags.flags_raw)
990
+ return
991
+ end
992
+
993
+ # Not validating AUTO_INCREMENT flag because that gets handles differently.
994
+ validate_definition_order(file, schema, table, column_name, [FLAG_PRIMARY_KEY, FLAG_INDEX, FLAG_UNIQUE, FLAG_NULLABLE], @flags.definition_order, 'Flags')
995
+
996
+ if @flags.primary_key
997
+ @primary_key_count = @primary_key_count + 1
998
+ add_error(file, schema, table, column_name, "#{FLAG_PRIMARY_KEY} cannot also be #{FLAG_INDEX}.", @flags.flags_raw) if @flags.index
999
+ end
1000
+
1001
+ if @flags.primary_key
1002
+ add_error(file, schema, table, column_name, "#{@type} cannot have a #{FLAG_PRIMARY_KEY} flag.", @flags.flags_raw) unless @type == TYPE_INT && column_name == ID
1003
+ end
1004
+
1005
+ if @flags.nullable && [TYPE_BOOLEAN, TYPE_DATETIME_INSERT, TYPE_DATETIME_UPDATE].include?(@type)
1006
+ add_error(file, schema, table, column_name, "#{@type} cannot have an #{FLAG_NULLABLE} flag.", @flags.flags_raw)
1007
+ end
1008
+
1009
+ if @flags.unique && [TYPE_BOOLEAN, TYPE_DATETIME_INSERT, TYPE_DATETIME_UPDATE].include?(@type)
1010
+ add_error(file, schema, table, column_name, "#{@type} cannot have an #{FLAG_UNIQUE} flag.", @flags.flags_raw)
1011
+ end
1012
+
1013
+ if @flags.unique && [TYPE_TEXT, TYPE_TEXT_LONG, TYPE_BOOLEAN].include?(@type)
1014
+ add_error(file, schema, table, column_name, "#{@type} cannot have an #{FLAG_UNIQUE} flag.", @flags.flags_raw)
1015
+ end
1016
+
1017
+ # ENUMs can only have INDEX, INDEX UNIQUE, or no flags -- nothing else.
1018
+ if (@type =~ REGEX_ENUM || @type =~ REGEX_ENUM_CUSTOM || @type =~ REGEX_ENUM_SYSTEM) && @flags != nil
1019
+ unless @flags.flags_raw == "#{FLAG_INDEX} #{FLAG_UNIQUE}" || @flags.flags_raw == "#{FLAG_INDEX}"
1020
+ add_error(file, schema, table, column_name, "#{TYPE_ENUM}s can only have NO flags, '#{FLAG_INDEX}' or '#{FLAG_INDEX} #{FLAG_UNIQUE}' flags \xe2\x80\x94 nothing else.", @flags.flags_raw)
1021
+ end
1022
+ end
1023
+
1024
+ # Certain flags require others...
1025
+ [
1026
+ [[FLAG_UNIQUE, @flags.unique], [FLAG_INDEX, @flags.index]]
1027
+ ].each do |required_combination|
1028
+ if required_combination[0][1] && required_combination[1][1].nil?
1029
+ add_error(file, schema, table, column_name, "If flag: #{required_combination[0][0]} is set then flag: #{required_combination[1][0]} must also be set.", @flags.flags_raw)
1030
+ end
1031
+ end
1032
+
1033
+ # Certain flag combinations are invalid...
1034
+ [
1035
+ [[FLAG_UNIQUE, @flags.unique], [FLAG_NULLABLE, @flags.nullable]]
1036
+ ].each do |invalid_combination|
1037
+ if invalid_combination[0][1] && invalid_combination[1][1]
1038
+ add_error(file, schema, table, column_name, "Cannot have flags: #{invalid_combination[0][0]} & #{invalid_combination[1][0]} together.", @flags.flags_raw)
1039
+ end
1040
+ end
1041
+
1042
+ end
1043
+
1044
+ # @return void
1045
+ def validate_foreign_key(file, schema, table, column_name, target_column)
1046
+
1047
+ if @type.nil?
1048
+ add_error(file, schema, table, column_name, "#{TYPE} must be defined before #{FKEY}, cannot validate #{FKEY}.", nil)
1049
+ return
1050
+ end
1051
+
1052
+ # Make sure the column matches regex format.
1053
+ unless fk_is_valid(target_column)
1054
+ add_error(file, schema, table, column_name, 'Foreign key target column not in correct format: {schema}.{table}.{column}', target_column)
1055
+ return
1056
+ end
1057
+
1058
+ target_column_split = target_column.split('.')
1059
+
1060
+ unless target_column_split[0] == schema
1061
+ add_error(file, schema, table, column_name, 'Cannot create cross-schema foreign keys.', "#{FKEY}: #{target_column}")
1062
+ end
1063
+
1064
+ unless target_column_split[2] == ID
1065
+ add_error(file, schema, table, column_name, "Foreign keys can only be to an #{ID.upcase}", "#{FKEY}: #{target_column}")
1066
+ end
1067
+
1068
+ unless @type == TYPE_INT
1069
+ add_error(file, schema, table, column_name, "Foreign key source columns must be of type #{TYPE_INT}, not:", @type)
1070
+ end
1071
+
1072
+ referencing_column = "#{schema}.#{table}.#{column_name}"
1073
+
1074
+ @schema_fks[target_column] = [] if @schema_fks[target_column].nil?
1075
+ @schema_fks[target_column] << "#{referencing_column}:#{@type}"
1076
+
1077
+ if target_column_split[2] == ID
1078
+ @transient_fields << target_column_split[1]
1079
+ # Add transient to @schema_data
1080
+ add_schema_data(schema, table, column_name.gsub(/_#{ID}$/, ''), {
1081
+ TYPE => Blufin::ScannerJavaEmbeddedObjects::OBJECT,
1082
+ TRANSIENT => [target_column_split[0], target_column_split[1]]
1083
+ })
1084
+ else
1085
+ Blufin::Terminal::output("Cannot create @Transient object for #{schema}.#{table}.#{column_name} because field name doesn't end in '#{ID}", Blufin::Terminal::MSG_WARNING)
1086
+ end
1087
+ end
1088
+
1089
+ # @return void
1090
+ def validate_required(file, schema, table, column_name, value)
1091
+
1092
+ # Make sure this is an OBJECT.
1093
+ unless column_name =~ /\A(#{VALID_SCHEMAS_REGEX})\.[a-z_]+\z/
1094
+
1095
+ add_error(file, schema, table, column_name, "Column cannot have a #{REQUIRED} property because it is not a container.", column_name)
1096
+ return
1097
+
1098
+ end
1099
+
1100
+ # Make sure the value is lowercase 'true' (and nothing else)
1101
+ add_error(file, schema, table, column_name, "#{REQUIRED} value must be lowercase 'true' or omitted.", "Found: #{value}") unless value == true && !!value == value
1102
+
1103
+ end
1104
+
1105
+ # @return void
1106
+ def validate_required_if(file, schema, table, column_name, value)
1107
+
1108
+ # Make sure value is in format: XX=YY
1109
+ unless value =~ /\A[a-z_]+=[a-zA-Z_]+\z/
1110
+ add_error(file, schema, table, column_name, "#{REQUIRED_IF} value must be in form of XX=YY where XX is an ENUM field.", "Found: #{value}")
1111
+ return
1112
+ end
1113
+
1114
+ value_split = value.split('=')
1115
+ enum_field = value_split[0]
1116
+ enum_value = value_split[1]
1117
+
1118
+ # Make sure the ENUM field exists.
1119
+ if @data[enum_field].nil?
1120
+ add_error(file, schema, table, column_name, "#{REQUIRED_IF} references non-existent ENUM field.", "#{table}.#{enum_field}")
1121
+ return
1122
+ end
1123
+
1124
+ enum_string = @data[enum_field][TYPE]
1125
+
1126
+ # Make sure the ENUM field is actually an ENUM field.
1127
+ unless enum_string =~ Blufin::YmlSchemaValidator::REGEX_ENUM || enum_string =~ Blufin::YmlSchemaValidator::REGEX_ENUM_CUSTOM || enum_string =~ REGEX_ENUM_SYSTEM
1128
+ add_error(file, schema, table, column_name, "#{REQUIRED_IF} references a field which is not a valid ENUM.", "#{table}.#{enum_field}")
1129
+ return
1130
+ end
1131
+
1132
+ # Make sure the ENUM value exists.
1133
+ add_error(file, schema, table, column_name, "#{REQUIRED_IF} uses a value not found in enum field: #{enum_string}", "#{enum_value}") unless Blufin::YmlCommon::enum_value_extractor(enum_string, @site).include?(enum_value)
1134
+
1135
+ unless column_name =~ /\A(#{VALID_SCHEMAS_REGEX})\.[a-z_]+\z/ || column_name =~ /\A(#{VALID_SCHEMAS_REGEX})\.[a-z_]+\[(link)?\]\z/
1136
+
1137
+ # Make sure that it has a nullable flag.
1138
+ add_error(file, schema, table, column_name, "Columns with a #{REQUIRED_IF} property need to have a #{FLAG_NULLABLE} flag because they might be empty.", nil) unless @flags && @flags.nullable
1139
+ end
1140
+
1141
+ end
1142
+
1143
+ # @return void
1144
+ def validate_encrypted(file, schema, table, column_name, value)
1145
+
1146
+ # Make sure the value is lowercase 'true' (and nothing else)
1147
+ add_error(file, schema, table, column_name, "#{ENCRYPTED} value must be lowercase 'true' or omitted.", "Found: #{value}") unless value == true && !!value == value
1148
+
1149
+ # Make sure the type is TEXT
1150
+ add_error(file, schema, table, column_name, "#{ENCRYPTED} field must have type: #{TYPE_TEXT}.", @type) unless @type == TYPE_TEXT
1151
+
1152
+ end
1153
+
1154
+ # Validates the order in which keys are defined for a column.
1155
+ # @return void
1156
+ def validate_definition_order(file, schema, table, column_name, correct_order, definition_order, entities)
1157
+ found_count = 0
1158
+ correct_order.each do |current_key|
1159
+ unless definition_order[current_key].nil?
1160
+ found_count = found_count + 1
1161
+ unless definition_order[current_key] == found_count
1162
+ add_error(file, schema, table, column_name, "#{entities} order must be: #{correct_order.join(',')}", definition_order.keys.join(','))
1163
+ return
1164
+ end
1165
+ end
1166
+ end
1167
+ end
1168
+
1169
+ # Extracts a YmlSchemaFlags object, throws error if a flag is unsupported.
1170
+ # @return Blufin::YmlSchemaFlags
1171
+ def extract_flags_for_validator(file, schema, table, column, flags)
1172
+
1173
+ # Check for excessive spaces.
1174
+ add_error(file, schema, table, column, "Too many spaces between 'flags'.", nil) if flags =~ /\s{2,}/
1175
+
1176
+ if flags == '' || flags.nil?
1177
+ add_error(file, schema, table, column, 'Flags cannot be empty or "NULL". Perhaps you meant "NULLABLE"?', nil)
1178
+ return
1179
+ end
1180
+
1181
+ flag_result = Blufin::YmlCommon::extract_flags(flags)
1182
+
1183
+ if flag_result[1].any?
1184
+ flag_result[1].each do |error|
1185
+ add_error(file, schema, table, column, error[0], error[1])
1186
+ end
1187
+ end
1188
+
1189
+ flag_result[0]
1190
+
1191
+ end
1192
+
1193
+ # Returns TRUE if string contains an uppercase letter.
1194
+ # @return boolean
1195
+ def contains_uppercase_letter_or_number(string)
1196
+ string =~ /[A-Z]/ || string =~/[0-9]/
1197
+ end
1198
+
1199
+ # Validates a FK string.
1200
+ # @return boolean
1201
+ def fk_is_valid(fk)
1202
+ fk =~ /\A(#{VALID_SCHEMAS_REGEX})\.[a-z_]+\.[a-z_]+\z/
1203
+ end
1204
+
1205
+ # Make sure there are no duplicate endpoint names in the various schemas, IE: app.sale & common.sale would be considered duplicates.
1206
+ # @return void
1207
+ def validate_table_duplicates
1208
+ table_names = {}
1209
+ @schema_data.each do |schema, schema_data|
1210
+ schema_data.each do |key, value|
1211
+ if table_names.keys.include?(key)
1212
+ add_error(nil, nil, nil, nil, "Duplicate table in separate schemas: #{schema}.#{key}", "Already exists: #{table_names[key]}.#{key}")
1213
+ else
1214
+ table_names[key] = schema
1215
+ end
1216
+ end
1217
+ end
1218
+ end
1219
+
1220
+ # Make sure that FKs are correct.
1221
+ # @return void
1222
+ def validate_foreign_keys
1223
+ @schema_fks.each do |fk, fk_data|
1224
+
1225
+ # Make sure the column matches regex format.
1226
+ unless fk_is_valid(fk)
1227
+ add_error(fk, nil, nil, nil, 'Foreign key target column not in correct format: app.table.column', fk)
1228
+ return
1229
+ end
1230
+
1231
+ fk_exploded = fk.split('.')
1232
+
1233
+ expected_reference_end = "#{fk_exploded[1]}_#{fk_exploded[2]}"
1234
+
1235
+ # Make sure referenced schema.table.column actually exists.
1236
+ unless @schema_data[fk_exploded[0]] != nil && @schema_data[fk_exploded[0]][fk_exploded[1]] != nil && @schema_data[fk_exploded[0]][fk_exploded[1]][fk_exploded[2]] != nil
1237
+ add_error_with_multi_content(nil, fk_exploded[0], nil, nil, "Referenced #{fk} doesn't exist, referenced by below:", "#{FKEY}: #{fk}", fk_data)
1238
+ next
1239
+ end
1240
+
1241
+ # Make sure the target column has a type and that it's an TINYINT, INT, INT_AUTO, ENUM or VARCHAR.
1242
+ if @schema_data[fk_exploded[0]][fk_exploded[1]][fk_exploded[2]][TYPE].nil?
1243
+ add_error(nil, fk_exploded[0], fk_exploded[1], fk_exploded[2], "#{FKEY} column has no type.", nil)
1244
+ next
1245
+ else
1246
+ fk_type = @schema_data[fk_exploded[0]][fk_exploded[1]][fk_exploded[2]][TYPE]
1247
+ unless [TYPE_INT_TINY, TYPE_INT, TYPE_INT_AUTO].include?(fk_type) || fk_type =~ /\A(#{TYPE_VARCHAR}|#{TYPE_ENUM}|#{TYPE_ENUM_CUSTOM}|#{TYPE_ENUM_SYSTEM})\(/
1248
+
1249
+ # TODO, CAN WE FK STRINGS - TEST THIS OUT IN YML??
1250
+ add_error(nil, fk_exploded[0], fk_exploded[1], fk_exploded[2], "Column is FK'd so must be: #{[TYPE_INT_TINY, TYPE_INT, TYPE_INT_AUTO, TYPE_ENUM, TYPE_ENUM_CUSTOM, TYPE_ENUM_SYSTEM].join(', ')} or #{TYPE_VARCHAR}.", fk_type)
1251
+ end
1252
+ end
1253
+
1254
+ fk_data.each do |referencing_column|
1255
+
1256
+ referencing_column = referencing_column.split(':') # colon is correct, we're splitting -- > app.message_ebay.message_id:INT
1257
+ referencing_column_type = referencing_column[1]
1258
+ referencing_column = referencing_column[0]
1259
+ referencing_column_parent = "#{fk_exploded[0]}.#{fk_exploded[1]}"
1260
+ rc_exploded = referencing_column.split('.')
1261
+ placeholder = nil
1262
+
1263
+ unless @schema_fks_placeholders["#{fk_exploded[0]}.#{fk_exploded[1]}"].nil?
1264
+ @schema_fks_placeholders["#{fk_exploded[0]}.#{fk_exploded[1]}"].each do |n|
1265
+ if "#{rc_exploded[0]}.#{rc_exploded[1]}" == n.gsub(/\[(link)?\]$/, '')
1266
+ placeholder = n
1267
+ break
1268
+ end
1269
+ end
1270
+ end
1271
+
1272
+ # Make sure that foreign keys with a placeholder are never null OR required_if.
1273
+ unless placeholder.nil?
1274
+ referencing_column_data = @schema_data[rc_exploded[0]][rc_exploded[1]][rc_exploded[2]]
1275
+ if referencing_column_data[FLAG].is_a?(String)
1276
+ flag_result = Blufin::YmlCommon::extract_flags(referencing_column_data[FLAG])
1277
+ add_error(nil, rc_exploded[0], rc_exploded[1], rc_exploded[2], "FK cannot have a #{FLAG_NULLABLE} #{FLAG} because it has a placeholder in: #{referencing_column_parent}", placeholder) if flag_result[0].nullable
1278
+ add_error(nil, rc_exploded[0], rc_exploded[1], rc_exploded[2], "FK cannot have a #{REQUIRED_IF} property because it has a placeholder in: #{referencing_column_parent}", placeholder) if referencing_column_data.has_key?(REQUIRED_IF)
1279
+ end
1280
+ end
1281
+
1282
+ # Make sure the reference name matches.. IE app.ebay_user.id is referenced by ebay_user_id
1283
+ unless rc_exploded[2] =~ /[a-z_]*#{expected_reference_end}/
1284
+ add_error(nil, rc_exploded[0], rc_exploded[1], rc_exploded[2], "FK naming convention not respected, expected field to end in: #{expected_reference_end}", "Got: #{rc_exploded[2]}")
1285
+ end
1286
+
1287
+ # Make sure the column matches regex format.
1288
+ unless fk_is_valid(referencing_column)
1289
+ add_error(nil, rc_exploded[0], rc_exploded[1], rc_exploded[2], 'Foreign key source column not in correct format: app.table.column', referencing_column)
1290
+ return
1291
+ end
1292
+
1293
+ # Make sure the type matches that of the parent column.
1294
+ if referencing_column_type != fk_type
1295
+ unless referencing_column_type == TYPE_INT && fk_type == TYPE_INT_AUTO
1296
+ add_error(nil, rc_exploded[0], rc_exploded[1], rc_exploded[2], "FK type mismatch. Expected: #{fk_type}", "got: #{referencing_column_type}")
1297
+ end
1298
+ end
1299
+
1300
+ unless @schema_data[rc_exploded[0]] != nil && @schema_data[rc_exploded[0]][rc_exploded[1]] != nil && @schema_data[rc_exploded[0]][rc_exploded[1]][rc_exploded[2]] != nil
1301
+ raise RuntimeError, "#{referencing_column} @schema_data not found."
1302
+ end
1303
+ end
1304
+ end
1305
+ end
1306
+
1307
+ # Validate all he Foreign Key placeholders.
1308
+ # @return void
1309
+ def validate_foreign_keys_placeholders
1310
+ if !@schema_fks_placeholders.nil? && @schema_fks_placeholders.any?
1311
+ placeholder_error = false
1312
+ placeholder_error_view = []
1313
+ placeholder_circular_error = false
1314
+ placeholder_circular_dependencies = []
1315
+ # Make sure that FK place-holders are correct.
1316
+ @schema_fks_placeholders.each do |parent, placeholders|
1317
+ parent_split = parent.split('.')
1318
+ parent_schema = parent_split[0]
1319
+ parent_table = parent_split[1]
1320
+ # Loop Placeholders.
1321
+ placeholders.each do |placeholder|
1322
+ placeholder_type = RESOURCE_TYPE_OBJECT
1323
+ placeholder_type = RESOURCE_TYPE_OBJECT_LIST if placeholder =~ /\A[a-z_.]+\[\]/
1324
+ placeholder_type = RESOURCE_TYPE_OBJECT_LINK if placeholder =~ /\A[a-z_.]+\[#{LINK}\]/
1325
+ # Remove '[]' or '[link]' for multi-nested placeholders.
1326
+ placeholder_dup = remove_placeholder_trailing_braces(placeholder)
1327
+ child = placeholder_dup.split('.')
1328
+ child_schema = child[0]
1329
+ child_table = child[1]
1330
+ if @schema_data[child_schema] != nil && @schema_data[child_schema][child_table] != nil
1331
+ if placeholder =~ /\A[a-z_.]+\[#{LINK}\]/
1332
+
1333
+ # TODO - FINISH (OR REMOVE)
1334
+
1335
+ else
1336
+ # Add error if CHILD TABLE doesn't match PARENT TABLE (IE: person_customer → ebay_user = NO MATCH)
1337
+ if child_table =~ /\A#{parent_table}_/
1338
+ placeholder_error_view << " \x1B[38;5;240m#{parent_table} \xe2\x86\x92 #{child_table}\x1B[0m"
1339
+ else
1340
+ placeholder_error = true
1341
+ placeholder_error_view << " \x1B[38;5;196m#{parent_table} \xe2\x86\x92 #{child_table}\x1B[0m"
1342
+ next
1343
+ end
1344
+ fk_found = false
1345
+ @schema_data[child_schema][child_table].each do |referenced_table_data|
1346
+ # Skip Placeholders (in referenced table)
1347
+ next if referenced_table_data[0] =~ /\A(#{VALID_SCHEMAS_REGEX})\.[a-z_]+\[\]\z/ || referenced_table_data[0] =~ /\A(#{VALID_SCHEMAS_REGEX})\.[a-z_]+\z/
1348
+ unless referenced_table_data[1][FKEY].nil?
1349
+ if "#{parent}.#{ID}" == referenced_table_data[1][FKEY]
1350
+ fk_found = true
1351
+ # If OBJECT or OBJECT_LIST, add CHILD_OF and CHILD_TYPE properties (that will end up in metadata).
1352
+ if [RESOURCE_TYPE_OBJECT, RESOURCE_TYPE_OBJECT_LIST].include?(placeholder_type)
1353
+ @schema_data[child_schema][child_table][referenced_table_data[0]][CHILD_OF] = "#{parent_table}"
1354
+ @schema_data[child_schema][child_table][referenced_table_data[0]][CHILD_TYPE] = "DataType.#{placeholder_type}"
1355
+ end
1356
+ end
1357
+ end
1358
+ end
1359
+ # Add error if FK not found.
1360
+ add_error(nil, parent_schema, parent_table, nil, "Unreferenced placeholder. #{child_schema}.#{child_table} doesn't have FK to #{parent}", "#{placeholder}:") unless fk_found
1361
+ end
1362
+ else
1363
+ add_error(nil, parent_schema, parent_table, nil, "Placeholder reference doesn't exist, no such table.", "#{placeholder}:")
1364
+ end
1365
+
1366
+ # Checks for circular dependencies.
1367
+ unless @schema_fks_placeholders[placeholder_dup].nil?
1368
+ @schema_fks_placeholders[placeholder_dup].each do |check_value|
1369
+ check_value = remove_placeholder_trailing_braces(check_value)
1370
+ if check_value == parent
1371
+ placeholder_circular_error = true
1372
+ placeholder_circular_dependencies << " \x1B[38;5;196m#{check_value} \xe2\x86\x92 #{placeholder_dup} \xe2\x86\x92 #{parent}\x1B[0m"
1373
+ else
1374
+ placeholder_circular_dependencies << " \x1B[38;5;240m#{check_value} \xe2\x86\x92 #{placeholder_dup} \xe2\x86\x92 #{parent}\x1B[0m"
1375
+ end
1376
+ end
1377
+ end
1378
+
1379
+ end
1380
+
1381
+ end
1382
+ @errors_array.push({"Incorrect Placeholders! Child tables must follow convention: \x1B[38;5;220mparent \xe2\x86\x92 parent_child\x1B[0m" => placeholder_error_view}) if placeholder_error
1383
+ @errors_array.push({"You have circular dependencies within your placeholders: \x1B[38;5;220mparent \xe2\x86\x92 child \xe2\x86\x92 parent\x1B[0m" => placeholder_circular_dependencies}) if placeholder_circular_error
1384
+ end
1385
+ end
1386
+
1387
+ # Checks if there are any hard-coded schema definitions required and validates they exist (correctly).
1388
+ # @return void
1389
+ def validate_required_schema_definitions
1390
+
1391
+ begin
1392
+
1393
+ embedded_data = Blufin::SiteEmbedded::get_data
1394
+
1395
+ auth_level = Blufin::SiteAuth::get_auth_level
1396
+ auth_level_objects = Blufin::SiteAuth::AUTHENTICATION_LEVELS[auth_level]
1397
+ auth_level_objects.each do |auth_level_object|
1398
+
1399
+ embedded_object = embedded_data[auth_level_object]
1400
+ expected_schema = embedded_object[:schema]
1401
+ expected_table = embedded_object[:table]
1402
+ expected_data = embedded_object[:data]
1403
+
1404
+ # Must check here if embedded objects actually exist.
1405
+ if @schema_data[expected_schema].nil? || @schema_data[expected_schema][expected_table].nil?
1406
+ @yml_error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_NO_TABLE, nil, nil, nil, "#{expected_schema}.#{expected_table}")
1407
+ next
1408
+ end
1409
+
1410
+ actual_data = @schema_data[expected_schema][expected_table]
1411
+
1412
+ expected_data.each do |field, data|
1413
+
1414
+ field_type = data[:type]
1415
+ field_key = field
1416
+ field_detail = "#{expected_schema}/#{expected_table}.yml - #{field_key}"
1417
+ field_is_object = false
1418
+
1419
+ case field_type
1420
+ when Blufin::ScannerJavaEmbeddedObjects::OBJECT
1421
+ field_key = "#{expected_schema}.#{field}"
1422
+ field_is_object = true
1423
+ when Blufin::ScannerJavaEmbeddedObjects::OBJECT_LIST
1424
+ field_key = "#{expected_schema}.#{field}[]"
1425
+ field_is_object = true
1426
+ when Blufin::ScannerJavaEmbeddedObjects::OBJECT_LINK
1427
+ field_key = "#{expected_schema}.#{field}[link]"
1428
+ field_is_object = true
1429
+ when Blufin::YmlSchemaValidator::TYPE_ENUM_SYSTEM
1430
+ field_type = "#{data[:type]}('#{data[:type_java]}')"
1431
+ else
1432
+
1433
+ end
1434
+
1435
+ # Check field exists.
1436
+ unless actual_data.keys.include?(field_key)
1437
+ # If field doesn't exist.
1438
+ @yml_error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_FIELD_MISSING, "#{expected_schema}/#{expected_table}.yml", nil, nil, "#{field_key} \xe2\x86\x92 #{data.inspect}}")
1439
+ next
1440
+ end
1441
+
1442
+ unless field_is_object
1443
+
1444
+ actual_type = actual_data[field_key]['type']
1445
+ actual_flag = actual_data[field_key]['flag']
1446
+ actual_fkey = actual_data[field_key]['fkey']
1447
+
1448
+ # Check type.
1449
+ @yml_error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_FIELD_WRONG_TYPE, field_detail, nil, nil, Blufin::YmlErrorHandler::error_expected_actual(data[:type], actual_type)) unless actual_type == field_type
1450
+
1451
+ # Check flags.
1452
+ if data[:flag].nil?
1453
+ @yml_error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_FLAGS_INVALID, field_detail, nil, nil, Blufin::YmlErrorHandler::error_expected_actual('[NO FLAGS]', actual_flag)) unless actual_flag.nil?
1454
+ else
1455
+ unless actual_flag == data[:flag].join(' ')
1456
+ @yml_error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_FLAGS_INVALID, field_detail, nil, nil, Blufin::YmlErrorHandler::error_expected_actual(data[:flag].join(' '), actual_flag))
1457
+ end
1458
+ end
1459
+
1460
+ # Check fkey.
1461
+ if data[:fkey].nil?
1462
+ @yml_error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_FKEY_INVALID, field_detail, nil, nil, Blufin::YmlErrorHandler::error_expected_actual('[NO FKEY]', actual_fkey)) unless actual_fkey.nil?
1463
+ else
1464
+ @yml_error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_FKEY_INVALID, field_detail, nil, nil, Blufin::YmlErrorHandler::error_expected_actual(data[:fkey], actual_fkey)) unless actual_fkey == data[:fkey]
1465
+ end
1466
+
1467
+ # Check encrypted.
1468
+ if data[:encrypted]
1469
+ @yml_error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_MUST_BE_ENCRYPTED, field_detail, nil, nil, 'Currently not encrypted.') if data[:encrypted] && (actual_data[field_key]['encrypted'].nil? || !actual_data[field_key]['encrypted'])
1470
+ end
1471
+
1472
+ end
1473
+
1474
+ end
1475
+
1476
+ end
1477
+
1478
+ rescue Exception => e
1479
+
1480
+ # TODO - Was originally (intentionally) left blank but now re-activated even though it throws a "Double" Exception. - 6/6/17.
1481
+ # Hits here when config/config.yml has errors but needs to be handled better.
1482
+ # Maybe work on this once this exception hits again and you re-read this comment :) - 3/17/19.
1483
+ Blufin::Terminal::print_exception(e)
1484
+
1485
+ end
1486
+
1487
+ end
1488
+
1489
+ # Checks HTTP methods are correct.
1490
+ # @return void
1491
+ def validate_http_methods
1492
+
1493
+ # Make sure that REQUIRED and REQUIRED_IF objects don't have a POST.
1494
+ @schema_resources.each do |key, resource|
1495
+ schema = resource[:schema]
1496
+ table = resource[:table]
1497
+ parent = resource[:parent]
1498
+ methods_internal = resource[:methods_internal].keys
1499
+ methods_oauth = resource[:methods_oauth].keys
1500
+ if resource[:type] == RESOURCE_TYPE_OBJECT
1501
+ parent_definition_data = @schema_data[schema][parent]["#{schema}.#{table}"]
1502
+ raise RuntimeError, 'parent_definition_data not found.' if parent_definition_data.nil? || parent_definition_data == ''
1503
+ if parent_definition_data.has_key?(REQUIRED) || parent_definition_data.has_key?(REQUIRED_IF)
1504
+ @error_handler.add_error(Blufin::YmlErrorHandler::API_METHOD_INVALID_FOR_REQUIRED_OBJECT, "#{schema}/#{table}.yml", 'config', 'internal', methods_internal.join(', ')) if methods_internal.include?(Blufin::YmlConfigValidator::POST) || methods_internal.include?(Blufin::YmlConfigValidator::DELETE)
1505
+ @error_handler.add_error(Blufin::YmlErrorHandler::API_METHOD_INVALID_FOR_REQUIRED_OBJECT, "#{schema}/#{table}.yml", 'config', 'oauth', methods_internal.join(', ')) if methods_oauth.include?(Blufin::YmlConfigValidator::POST) || methods_oauth.include?(Blufin::YmlConfigValidator::DELETE)
1506
+ end
1507
+ end
1508
+ end
1509
+
1510
+ end
1511
+
1512
+ # Validate config: section.
1513
+ # @return void
1514
+ def validate_config_section
1515
+ result = validate(@site, %W(#{Blufin::Site::PATH_TO_YML_API_SCHEMA}/#{APP} #{Blufin::Site::PATH_TO_YML_API_SCHEMA}/#{COMMON}), STRUCTURE, @error_handler)
1516
+ result.each do |key, data|
1517
+ key_split = key.split('/')
1518
+ schema = key_split[key_split.length - 2]
1519
+ table = key_split[key_split.length - 1].gsub(/(\.)[A-Za-z0-9]+\z/, '')
1520
+ @schema_config[schema] = {} if @schema_config[schema].nil?
1521
+ @schema_config[schema][table] = data['config'].nil? ? {} : data['config']
1522
+ end
1523
+ result = validate(@site, %W(#{PATH_TO_CONFIG_YML}), STRUCTURE, @error_handler, true)
1524
+ result.each do |key, data|
1525
+ if data.is_a?(Hash) && !data[CONFIG].nil?
1526
+ key_split = key.split('/')
1527
+ schema = key_split[key_split.length - 2]
1528
+ table = key_split[key_split.length - 1].gsub(/(\.)[A-Za-z0-9]+\z/, '')
1529
+ @schema_config[schema] = {} if @schema_config[schema].nil?
1530
+ @schema_config[schema][table] = data[CONFIG].nil? ? {} : data[CONFIG]
1531
+ end
1532
+ end
1533
+ end
1534
+
1535
+ # Builds the final end-point hash that is used to generate all the end-points, etc.
1536
+ # @return void
1537
+ def build_resources
1538
+
1539
+ schema_table_array = []
1540
+ @schema_data.each do |schema, schema_data|
1541
+ unless schema_data.nil?
1542
+ schema_data.each do |table_name, table_data|
1543
+ 1 == 1 if table_data # Stop IDE complaining.
1544
+ unless table_name.nil?
1545
+ schema_table_array << "#{schema}.#{table_name}"
1546
+ end
1547
+ end
1548
+ end
1549
+ end
1550
+
1551
+ # List of tables which have "parent-tables".
1552
+ tables_with_parents = {}
1553
+
1554
+ # Start populating @schema_end_points with tables that are CHILD or CHILD_MULTI.
1555
+ # This also generates the 'resource' string: IE -> sale/shipment/component
1556
+ @schema_fks.each do |key, value|
1557
+ key_split = key.split('.')
1558
+ key_schema = key_split[0]
1559
+ key_table = key_split[1]
1560
+ value.each do |fk_source|
1561
+ fks_split = fk_source.split(':')
1562
+ fks_split = fks_split[0].split('.')
1563
+ fks_schema = fks_split[0]
1564
+ fks_table = fks_split[1]
1565
+ # Checks if [sale_ebay] has [sale_] in it...
1566
+ if fks_table =~ /\A#{key_table}_/
1567
+ # Turns [sale_ebay] into [ebay]...
1568
+ child_table = fks_table.gsub(/\A#{key_table}_/, '')
1569
+ # Turns [sale_ebay] into [sale]...
1570
+ parent_table = fks_table.gsub(/_#{child_table}\z/, '')
1571
+ # Figures out what the end-point source is (to put in resource hash)...
1572
+ end_point_source = (tables_with_parents.keys.include?(parent_table)) ? tables_with_parents[parent_table][:resource] : key_table.gsub('_', '-')
1573
+ placeholder_key = "#{fks_schema}.#{parent_table}"
1574
+ if schema_table_array.include?(placeholder_key)
1575
+ placeholder_type = nil
1576
+ placeholder_matches = []
1577
+ if !@schema_fks_placeholders[placeholder_key].nil? && @schema_fks_placeholders[placeholder_key].any?
1578
+ @schema_fks_placeholders[placeholder_key].each do |placeholder|
1579
+ schema_table = "#{fks_schema}.#{fks_table}"
1580
+ if placeholder =~ /\A#{schema_table}\z/
1581
+ placeholder_matches << placeholder
1582
+ placeholder_type = RESOURCE_TYPE_OBJECT
1583
+ elsif placeholder =~ /\A#{schema_table}\[\]\z/
1584
+ placeholder_matches << placeholder
1585
+ placeholder_type = RESOURCE_TYPE_OBJECT_LIST
1586
+ end
1587
+ end
1588
+ end
1589
+ # Something like: sale/shipment/component ...
1590
+ resource_string = "#{end_point_source}/#{child_table.gsub('_', '-')}"
1591
+ add_error_with_multi_content(nil, key_schema, key_table, nil, "More than one type of placeholder found within #{key_schema}.#{key_table} for: #{resource_string}", 'Can only have 1!', placeholder_matches) if placeholder_matches.length > 1
1592
+ # Adds [sale_ebay] to an Array of tables which have "parent-tables".
1593
+ tables_with_parents[fks_table] = build_resource_hash_start(placeholder_type, fks_schema, fks_table, resource_string) unless placeholder_type.nil?
1594
+ end
1595
+ end
1596
+ end
1597
+ end
1598
+
1599
+ # Add LINKED and PARENT tables to @schema_end_points.
1600
+ @schema_data.each do |schema, table_data|
1601
+ table_data.each do |table, data|
1602
+ 1 == 1 if data # Suppresses IDE error.
1603
+ st = "#{schema}.#{table}"
1604
+ if tables_with_parents.keys.include?(table)
1605
+ @schema_resources[st] = tables_with_parents[table].dup
1606
+ else
1607
+ if !@schema_fks_placeholders.nil? && @schema_fks_placeholders.any?
1608
+ @schema_fks_placeholders.each do |key, data|
1609
+ if !data.nil? && data.any?
1610
+ data.each do |ph|
1611
+ if ph =~ /#{schema}.#{table}\[#{LINK}\]/
1612
+ unless @schema_resources[st].nil?
1613
+ if @schema_resources[st][:type] != RESOURCE_TYPE_OBJECT_LINK
1614
+ raise RuntimeError, "This statement should never be reached. It means that table '#{st}' was expected to be be 'LINKED' but was also defined as: #{@schema_resources[st][:type]}"
1615
+ end
1616
+ end
1617
+ @schema_resources[st] = build_resource_hash_start(RESOURCE_TYPE_OBJECT_LINK, schema, table, table.gsub('_', '-'))
1618
+ ph_clean = remove_placeholder_trailing_braces(ph)
1619
+ # Whilst we're looping, might as well create the @schema_fks_links HASH.
1620
+ @schema_fks_links[ph_clean] = [] if @schema_fks_links[ph_clean].nil?
1621
+ @schema_fks_links[ph_clean] << key
1622
+ end
1623
+ end
1624
+ end
1625
+ end
1626
+ end
1627
+ @schema_resources[st] = build_resource_hash_start(RESOURCE_TYPE_PARENT, schema, table, table.gsub('_', '-')) if @schema_resources[st].nil?
1628
+ end
1629
+ end
1630
+ end
1631
+
1632
+ # Add :tree & :depth key to @schema_end_points.
1633
+ @schema_resources.each do |key, data|
1634
+ tree = []
1635
+ item_last = nil
1636
+ data_resource_split = data[:resource].split('/')
1637
+ data_resource_split.each_with_index do |fragment, idx|
1638
+ item_frag = fragment.gsub('-', '_')
1639
+ if idx > 0
1640
+ @schema_resources[key][:parent] = item_last if idx == (data_resource_split.length - 1)
1641
+ item_last = "#{tree[idx - 1].gsub('-', '_')}_#{item_frag}"
1642
+ tree << item_last
1643
+ else
1644
+ @schema_resources[key][:parent] = nil
1645
+ item_last = item_frag
1646
+ tree << item_last
1647
+ end
1648
+ end
1649
+ @schema_resources[key][:tree] = tree
1650
+ @schema_resources[key][:depth] = data_resource_split.length
1651
+ end
1652
+
1653
+ # Add :dependents key to @schema_end_points.
1654
+ @schema_resources.each do |key, data|
1655
+ deps = data[:tree].dup
1656
+ deps.pop
1657
+ if deps.any?
1658
+ deps.each do |dep|
1659
+ key = "#{data[:schema]}.#{dep}"
1660
+ @schema_resources[key][:dependents] = [] if @schema_resources[key][:dependents].nil?
1661
+ @schema_resources[key][:dependents] << data[:table]
1662
+ end
1663
+ end
1664
+ end
1665
+
1666
+ # Build @resource hash (for YmlSchemaOutputter)
1667
+ @schema_resources.each { |key, data| @yml_schema_outputter.add_resource(key, data) }
1668
+
1669
+ # Check that placeholders are correct (IE: Something this is a CHILD is not also LINKED somewhere else).
1670
+ if !@schema_fks_placeholders.nil? && @schema_fks_placeholders.any?
1671
+ placeholder_type_error = false
1672
+ placeholder_type_error_view = []
1673
+ @schema_fks_placeholders.each do |key, data|
1674
+ if !data.nil? && data.any?
1675
+ data.each do |ph|
1676
+ # Remove '[]' or '[link]' for multi-nested placeholders.
1677
+ schema_table = remove_placeholder_trailing_braces(ph)
1678
+ unless @schema_resources[schema_table].nil?
1679
+ if ph =~ /\A#{schema_table}\z/
1680
+ placeholder_type = RESOURCE_TYPE_OBJECT
1681
+ elsif ph =~ /\A#{schema_table}\[\]\z/
1682
+ placeholder_type = RESOURCE_TYPE_OBJECT_LIST
1683
+ elsif ph =~ /\A#{schema_table}\[#{LINK}\]\z/
1684
+ placeholder_type = RESOURCE_TYPE_OBJECT_LINK
1685
+ else
1686
+ raise RuntimeError, "#{schema_table} \xe2\x86\x92 All tables should match at least one placeholder 'type'."
1687
+ end
1688
+ if placeholder_type != @schema_resources[schema_table][:type]
1689
+ placeholder_type_error = true
1690
+ key_split = key.split('.')
1691
+ add_error(nil, key_split[0], key_split[1], nil, "Attempted to define as '#{placeholder_type}' but was already defined as '#{@schema_resources[schema_table][:type]}'.", ph)
1692
+ placeholder_type_error_view << "\x1B[38;5;196m#{key} \xe2\x86\x92 #{ph} \xe2\x86\x92 #{@schema_resources[schema_table][:type].upcase} \xe2\x80\x94 ERROR!\x1B[0m"
1693
+ else
1694
+ placeholder_type_error_view << "\x1B[38;5;84m#{key}\x1B[0m \xe2\x86\x92 #{ph} \xe2\x80\x94 #{@schema_resources[schema_table][:type].upcase} \xe2\x80\x94 OK!"
1695
+ end
1696
+ end
1697
+ end
1698
+ end
1699
+ end
1700
+ # Add errors (if any).
1701
+ @errors_array.push({"Cannot mix placeholder 'types'." => placeholder_type_error_view}) if placeholder_type_error
1702
+ end
1703
+
1704
+ @schema_config.each do |schema, schema_data|
1705
+ schema_data.each do |table, table_data|
1706
+ begin
1707
+ @schema_resources["#{schema}.#{table}"][:methods_internal] = {}
1708
+ @schema_resources["#{schema}.#{table}"][:methods_oauth] = {}
1709
+ unless @schema_resources["#{schema}.#{table}"].nil?
1710
+ table_data[CONFIG_INTERNAL].each { |key, value| @schema_resources["#{schema}.#{table}"][:methods_internal][key] = value } if table_data.has_key?(CONFIG_INTERNAL)
1711
+ table_data[CONFIG_OAUTH].each { |key, value| @schema_resources["#{schema}.#{table}"][:methods_oauth][key] = value } if table_data.has_key?(CONFIG_OAUTH)
1712
+ end
1713
+ rescue
1714
+ end
1715
+ end
1716
+ end
1717
+
1718
+ end
1719
+
1720
+ # Build the initial end-point hash but more work is done to it after.
1721
+ # @return Hash
1722
+ def build_resource_hash_start(type, schema, table, end_point)
1723
+ {
1724
+ :type => type,
1725
+ :schema => schema,
1726
+ :table => table,
1727
+ :resource => end_point
1728
+ }
1729
+ end
1730
+
1731
+ # Adds 'dependencies' to end-point array (and validates there are no duplicates in a table).
1732
+ # @return void
1733
+ def build_fks_dependencies
1734
+ @schema_resources.each do |key, data|
1735
+ dependencies = []
1736
+ @schema_fks.each do |sfk_key, sfk_data|
1737
+ sfk_data.each do |sfk_data_inner|
1738
+ sfk_split = sfk_data_inner.split('.')
1739
+ sfk = "#{sfk_split[0]}.#{sfk_split[1]}"
1740
+ if key == sfk
1741
+ sfk_key_split = sfk_key.split('.')
1742
+ dependency = "#{sfk_key_split[0]}.#{sfk_key_split[1]}"
1743
+ dependencies << dependency unless dependencies.include?(dependency)
1744
+ end
1745
+ end
1746
+ end
1747
+ @schema_fks_dependencies[key] = dependencies
1748
+ end
1749
+ end
1750
+
1751
+ # Validates @schema_resources.
1752
+ # Checks that all tables which are "Embedded" have all HTTP methods enabled -> GET, POST, PATCH, DELETE
1753
+ # @return void
1754
+ def validate_resources
1755
+ embedded_data = Blufin::SiteEmbedded::get_data
1756
+ auth_level = Blufin::SiteAuth::get_auth_level
1757
+ auth_level_objects = Blufin::SiteAuth::AUTHENTICATION_LEVELS[auth_level]
1758
+ raise 'This error occurred because you forgot to run Blufin::SiteAuth::init() somewhere...' if auth_level_objects.nil?
1759
+ auth_level_objects.each do |auth_level_object|
1760
+ schema = embedded_data[auth_level_object][:schema]
1761
+ table = embedded_data[auth_level_object][:table]
1762
+ return if @schema_resources["#{schema}.#{table}"].nil?
1763
+ http_methods = @schema_resources["#{schema}.#{table}"][:methods_internal].keys
1764
+ # Validate HTTP methods depending on schema.
1765
+ if schema == CONFIG
1766
+ @error_handler.add_error(Blufin::YmlErrorHandler::API_ENDPOINTS_NOT_ALLOWED, "#{schema}/#{table}.yml", 'config', 'internal', http_methods.join(', ')) if http_methods.include?(Blufin::YmlConfigValidator::POST) && http_methods.include?(Blufin::YmlConfigValidator::PATCH) && http_methods.include?(Blufin::YmlConfigValidator::DELETE)
1767
+ else
1768
+ @error_handler.add_error(Blufin::YmlErrorHandler::API_ENDPOINTS_MISSING, "#{schema}/#{table}.yml", 'config', 'internal', nil) unless http_methods.include?(Blufin::YmlConfigValidator::GET) && http_methods.include?(Blufin::YmlConfigValidator::POST) && http_methods.include?(Blufin::YmlConfigValidator::PATCH) && http_methods.include?(Blufin::YmlConfigValidator::DELETE)
1769
+ end
1770
+ end
1771
+ end
1772
+
1773
+ # Add a 'section:schema' error.
1774
+ # @return void
1775
+ def add_error(error_file, schema, table, column, error_message, error_detail)
1776
+ add_error_abstract(error_file, schema, table, column, error_message, error_detail)
1777
+ end
1778
+
1779
+ # Add a 'section:schema' error with multi-line content).
1780
+ # @return void
1781
+ def add_error_with_multi_content(error_file, schema, table, column, error_message, error_detail, multi_line_content)
1782
+ add_error_abstract(error_file, schema, table, column, error_message, error_detail, multi_line_content)
1783
+ end
1784
+
1785
+ # The abstract method for adding errors.
1786
+ # @return
1787
+ def add_error_abstract(error_file, schema, table, column, error_message, error_detail, multi_line_content = nil, section = 'schema')
1788
+
1789
+ error_object = Blufin::SchemaError.new
1790
+ error_object.multi_line_content = multi_line_content
1791
+ error_object.section = section
1792
+
1793
+ if schema.nil? && table.nil? && column.nil?
1794
+ error_object.schema_address = "\xe2\x80\x94"
1795
+ else
1796
+ schema_address = []
1797
+ schema_address << (schema.nil? ? nil : schema)
1798
+ schema_address << (table.nil? ? nil : table)
1799
+ schema_address << (column.nil? ? nil : column)
1800
+ schema_address.compact!
1801
+ error_object.schema_address = schema_address
1802
+ end
1803
+
1804
+ if error_file.nil?
1805
+ if !table.nil?
1806
+ error_object.file = "#{table}.yml"
1807
+ else
1808
+ error_object.file = "\xe2\x80\x94"
1809
+ end
1810
+ else
1811
+ if error_file == ERROR_MULTIPLE_FILES
1812
+ error_object.file = ERROR_MULTIPLE_FILES
1813
+ else
1814
+ file_parts = error_file.split('/')
1815
+ error_object.file = file_parts[file_parts.length - 1]
1816
+ end
1817
+ end
1818
+
1819
+ error_object.error_message = (error_message.nil? ? "\xe2\x80\x94" : error_message)
1820
+ error_object.error_detail = (error_detail.nil? ? "\xe2\x80\x94" : error_detail)
1821
+
1822
+ @error_handler.add_error_schema(error_object)
1823
+
1824
+ end
1825
+
1826
+ # Removed trailing [] or [link] from placeholders strings.
1827
+ # @return String
1828
+ def remove_placeholder_trailing_braces(placeholder)
1829
+ (placeholder =~ /\A[a-z_.]+\[#{LINK}\]/) ? placeholder.gsub("[#{LINK}]", '') : placeholder.gsub('[]', '')
1830
+ end
1831
+
1832
+ # Add to the global @schema_data Array.
1833
+ # @return void
1834
+ def add_schema_data(schema, table, column, data)
1835
+
1836
+ @schema_data[schema] = {} if @schema_data[schema].nil?
1837
+ @schema_data[schema][table] = {} if @schema_data[schema][table].nil?
1838
+ @schema_data[schema][table][column] = data
1839
+
1840
+ end
1841
+
1842
+ # Returns true if the column name matches regex for OBJECT, OBJECT_LIST or OBJECT_LINK
1843
+ # @return bool
1844
+ def self.column_is_object(column_name)
1845
+ column_name =~ /^(#{VALID_SCHEMAS_REGEX})\.[\w]+/ || column_name =~ /^(#{VALID_SCHEMAS_REGEX})\.[\w]+\[\]$/ || column_name =~ /^(#{VALID_SCHEMAS_REGEX})\.[\w]+\[#{LINK}\]$/
1846
+ end
1847
+
1848
+ end
1849
+
1850
+ end