rsmp_schema 0.1.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aeef79729314dc2a8440a0697560f02608622a0c46ffaa635d6ec18b7afc3a43
4
- data.tar.gz: 04c0d1bbc1039a924ca63e70bb29bafceb0dbbc5d8a95e54d9a39686f7d34847
3
+ metadata.gz: 8c5718eb96e00289525b0997cc296fcc93b029966d40c80a544d7b075d4c870b
4
+ data.tar.gz: c64cabf0d6cf081bd1434401eb9794691ac126dcf2fe64316ddbfe3fbef31f5e
5
5
  SHA512:
6
- metadata.gz: d8905cc3b11e5fc4aa9721e07a4b4f0de852f872af7e7dc364e9fa63e09720870bbc1e2cd3cb5efd816d35c641e128e829a2ed2b7de079084f7d3aff96b13688
7
- data.tar.gz: efea6b4d539ba3bd686900beb2595cf72b17c890208193204fab0fe040faa671eb49ec67ba0973bc1f7077a8a1be392d6d4f4c281a57c78e633fa2eebd26653d
6
+ metadata.gz: 454414a51cf1e8ffa4f978a997ca702726058d82a0e1e0e6645fc74122e945d42584b38926a97a464ecbb591ec3afaf23e85a4639f704815abf431895c3e2f52
7
+ data.tar.gz: a48f7a8aa43283cd5448376c7f26f7c060d10fda7caf7e95f1b0f6e54c81432af867a9cbfa25ff6cb7608d1868e04fa1c9b6b5677e7550358e11073b18539634
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rsmp_schema (0.1.1)
4
+ rsmp_schema (0.2.2)
5
5
  json_schemer (~> 0.2.21)
6
6
  thor (~> 1.2.1)
7
7
 
@@ -21,67 +21,61 @@ module RSMP
21
21
  JSON.generate(item,@@json_options)
22
22
  end
23
23
 
24
+ # convert a yaml item to json schema
24
25
  def self.build_value item
25
26
  out = {}
27
+ out['description'] = item['description'] if item['description']
26
28
 
27
- if item['description']
28
- out["description"] = item['description']
29
+ if item['type'] =~/_list$/
30
+ handle_string_list item, out
31
+ else
32
+ handle_types item, out
33
+ handle_enum item, out
34
+ handle_pattern item, out
29
35
  end
36
+ wrap_refs out
37
+ end
30
38
 
31
- if item['list']
32
- case item['type']
33
- when "boolean"
34
- out["$ref"] = "../../../core/3.1.1/definitions.json#/boolean_list"
35
- when "integer", "ordinal", "unit", "scale", "long"
36
- out["$ref"] = "../../../core/3.1.1/definitions.json#/integer_list"
37
- when "string"
38
- out["$ref"] = "../../../core/3.1.1/definitions.json#/string_list"
39
- else
40
- raise "Error: List of #{item['type']} is not supported: #{item.inspect}"
41
- end
42
-
43
- if item["values"]
44
- value_list = item["values"].keys.join('|')
45
- out['pattern'] = /(?-mix:^(#{value_list})(?:,(#{value_list}))*$)/
46
- end
47
-
48
- if item["pattern"]
49
- puts "Warning: Pattern not support for lists: #{item.inspect}"
50
- end
39
+ # convert an item which is not a string-list, to json schema
40
+ def self.handle_types item, out
41
+ case item['type']
42
+ when "string", "base64"
43
+ out["type"] = "string"
44
+ when "boolean"
45
+ out["$ref"] = "../../../core/3.1.1/definitions.json#/boolean"
46
+ when "timestamp"
47
+ out["$ref"] = "../../../core/3.1.1/definitions.json#/timestamp"
48
+ when "integer", "ordinal", "unit", "scale", "long"
49
+ out["$ref"] = "../../../core/3.1.1/definitions.json#/integer"
50
+ when 'array' # a json array
51
+ build_json_array item['items'], out
51
52
  else
52
- case item['type']
53
- when "string", "base64"
54
- out["type"] = "string"
55
- when "boolean"
56
- out["$ref"] = "../../../core/3.1.1/definitions.json#/boolean"
57
- when "timestamp"
58
- out["$ref"] = "../../../core/3.1.1/definitions.json#/timestamp"
59
- when "integer", "ordinal", "unit", "scale", "long"
60
- out["$ref"] = "../../../core/3.1.1/definitions.json#/integer"
61
- else
62
- out["type"] = "string"
63
- end
64
-
65
- if item["values"]
66
- case item["values"]
67
- when Hash
68
- out["enum"] = item["values"].keys.sort
69
- when Array
70
- out["enum"] = item["values"].sort
71
- else
72
- raise "Error: Values must be specified as either a Hash or an Array"
73
- end
74
-
75
- end
53
+ out["type"] = "string"
54
+ end
55
+ end
76
56
 
77
- if item["pattern"]
78
- out["pattern"] = item["pattern"]
79
- end
57
+ # convert an yaml item with type: array to json schema
58
+ def self.build_json_array item, out
59
+ required = item.select { |k,v| v['optional'] != true }.keys.sort
60
+ out.merge!({
61
+ "type" => "array",
62
+ "items" => {
63
+ "type" => "object",
64
+ "required" => required,
65
+ "additionalProperties": false
66
+ }
67
+ })
68
+ out["items"]["properties"] = {}
69
+ item.each_pair do |key,v|
70
+ out["items"]["properties"][key] = build_value(v)
80
71
  end
72
+ out
73
+ end
81
74
 
82
- # with draft-07 and older, using a $ref mean all other properties are ignored.
83
- # to avoid this we need to use an allOf.
84
- # this is changed from draft-08, but unfortunately, there is still no Ruby validator for that
75
+ # with draft-07 and older, using a $ref mean all other properties are ignored.
76
+ # to avoid this we need to use an allOf.
77
+ # this is changed from draft-08, but unfortunately, there is still no Ruby validator for that
78
+ def self.wrap_refs out
85
79
  if out.keys.include? '$ref'
86
80
  ref = out.delete '$ref'
87
81
  { "allOf" => [out,{"$ref"=>ref}]}
@@ -90,6 +84,47 @@ module RSMP
90
84
  end
91
85
  end
92
86
 
87
+ # convert a yaml item with list: true to json schema
88
+ def self.handle_string_list item, out
89
+ case item['type']
90
+ when "boolean_list"
91
+ out["$ref"] = "../../../core/3.1.1/definitions.json#/boolean_list"
92
+ when "integer_list"
93
+ out["$ref"] = "../../../core/3.1.1/definitions.json#/integer_list"
94
+ when "string_list"
95
+ out["$ref"] = "../../../core/3.1.1/definitions.json#/string_list"
96
+ else
97
+ raise "Error: List of #{item['type']} is not supported: #{item.inspect}"
98
+ end
99
+
100
+ if item["values"]
101
+ value_list = item["values"].keys.join('|')
102
+ out['pattern'] = /(?-mix:^(#{value_list})(?:,(#{value_list}))*$)/
103
+ end
104
+
105
+ puts "Warning: Pattern not support for lists: #{item.inspect}" if item["pattern"]
106
+ end
107
+
108
+ # convert yaml values to jsons schema enum
109
+ def self.handle_enum item, out
110
+ if item["values"]
111
+ case item["values"]
112
+ when Hash
113
+ out["enum"] = item["values"].keys.sort
114
+ when Array
115
+ out["enum"] = item["values"].sort
116
+ else
117
+ raise "Error: Values must be specified as either a Hash or an Array"
118
+ end
119
+ end
120
+ end
121
+
122
+ # convert yaml pattern to jsons schema
123
+ def self.handle_pattern item, out
124
+ out["pattern"] = item["pattern"] if item["pattern"]
125
+ end
126
+
127
+ # convert yaml alarm/status/command item to corresponding jsons schema
93
128
  def self.build_item item, property_key: 'v'
94
129
  json = { "allOf" => [ { "description" => item['description'] } ] }
95
130
  if item['arguments']
@@ -104,6 +139,7 @@ module RSMP
104
139
  json
105
140
  end
106
141
 
142
+ # convert alarms to json schema
107
143
  def self.output_alarms out, items
108
144
  list = items.keys.sort.map do |key|
109
145
  {
@@ -121,11 +157,13 @@ module RSMP
121
157
  items.each_pair { |key,item| output_alarm out, key, item }
122
158
  end
123
159
 
160
+ # convert an alarm to json schema
124
161
  def self.output_alarm out, key, item
125
162
  json = build_item item
126
163
  out["alarms/#{key}.json"] = output_json json
127
164
  end
128
165
 
166
+ # convert statuses to json schema
129
167
  def self.output_statuses out, items
130
168
  list = [ { "properties" => { "sCI" => { "enum"=> items.keys.sort }}} ]
131
169
  items.keys.sort.each do |key|
@@ -139,11 +177,13 @@ module RSMP
139
177
  items.each_pair { |key,item| output_status out, key, item }
140
178
  end
141
179
 
180
+ # convert a status to json schema
142
181
  def self.output_status out, key, item
143
182
  json = build_item item, property_key:'s'
144
183
  out["statuses/#{key}.json"] = output_json json
145
184
  end
146
185
 
186
+ # convert commands to json schema
147
187
  def self.output_commands out, items
148
188
  list = [ { "properties" => { "cCI" => { "enum"=> items.keys.sort }}} ]
149
189
  items.keys.sort.each do |key|
@@ -164,12 +204,14 @@ module RSMP
164
204
  items.each_pair { |key,item| output_command out, key, item }
165
205
  end
166
206
 
207
+ # convert a command to json schema
167
208
  def self.output_command out, key, item
168
209
  json = build_item item
169
210
  json["allOf"].first["properties"]['cO'] = { "const" => item['command'] }
170
211
  out["commands/#{key}.json"] = output_json json
171
212
  end
172
213
 
214
+ # output the json schema root
173
215
  def self.output_root out, meta
174
216
  json = {
175
217
  "name"=> meta['name'],
@@ -197,6 +239,7 @@ module RSMP
197
239
  out["sxl.json"] = output_json json
198
240
  end
199
241
 
242
+ # generate the json schema from a string containing yaml
200
243
  def self.generate sxl
201
244
  out = {}
202
245
  output_root out, sxl[:meta]
@@ -206,6 +249,7 @@ module RSMP
206
249
  out
207
250
  end
208
251
 
252
+ # convert yaml to json schema and write files to a folder
209
253
  def self.write sxl, folder
210
254
  out = generate sxl
211
255
  out.each_pair do |relative_path,str|
@@ -215,9 +259,7 @@ module RSMP
215
259
  file.puts str
216
260
  end
217
261
  end
218
-
219
262
  end
220
-
221
263
  end
222
264
  end
223
265
  end
@@ -6,32 +6,66 @@ end
6
6
  module RSMP::Schema
7
7
  @@schemas = nil
8
8
 
9
- def self.schemas
10
- return @@schemas if @@schemas
9
+ def self.setup
10
+ @@schemas = {}
11
11
  schemas_path = File.expand_path( File.join(__dir__,'..','..','schemas') )
12
- @@schemas = {
13
- core: {
14
- '3.1.1' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'core','3.1.1','rsmp.json')) ),
15
- '3.1.2' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'core','3.1.2','rsmp.json')) ),
16
- '3.1.3' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'core','3.1.3','rsmp.json')) ),
17
- '3.1.4' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'core','3.1.4','rsmp.json')) ),
18
- '3.1.5' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'core','3.1.5','rsmp.json')) ),
19
- '3.2' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'core','3.2', 'rsmp.json')) )
20
- },
21
- # note that tlc 1.0.11 and 1.0.12 does not exist (unreleased drafts)
22
- tlc: {
23
- '1.0.7' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'tlc','1.0.7' ,'sxl.json')) ),
24
- '1.0.8' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'tlc','1.0.8' ,'sxl.json')) ),
25
- '1.0.9' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'tlc','1.0.9' ,'sxl.json')) ),
26
- '1.0.10' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'tlc','1.0.10','sxl.json')) ),
27
- '1.0.13' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'tlc','1.0.13','sxl.json')) ),
28
- '1.0.14' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'tlc','1.0.14','sxl.json')) ),
29
- '1.0.15' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'tlc','1.0.15','sxl.json')) ),
30
- '1.1' => JSONSchemer.schema( Pathname.new(File.join(schemas_path, 'tlc','1.1', 'sxl.json')) )
31
- }
32
- }
12
+ Dir.glob("#{schemas_path}/*").select {|f| File.directory? f}.each do |type_path|
13
+ type = File.basename(type_path).to_sym
14
+ @@schemas[type] = {}
15
+ Dir.glob("#{type_path}/*").select {|f| File.directory? f}.each do |schema_path|
16
+ version = File.basename(schema_path)
17
+ if type == :core
18
+ file = 'rsmp.json'
19
+ else
20
+ file = 'sxl.json'
21
+ end
22
+ @@schemas[type][version] = JSONSchemer.schema(
23
+ Pathname.new(File.join(schema_path,file))
24
+ )
25
+ end
26
+ end
27
+ end
28
+
29
+ # get all schemas, oganized by type and version
30
+ def self.schemas
31
+ raise RuntimeError.new("No schemas available, perhaps Schema.setup was never called?") unless @@schemas
32
+ @@schemas
33
+ end
34
+
35
+ # get array of core schema versions
36
+ def self.core_versions
37
+ versions :core
38
+ end
39
+
40
+ # get earliest core schema version
41
+ def self.earliest_core_version
42
+ earliest_version :core
33
43
  end
34
44
 
45
+ # get latesty core schema version
46
+ def self.latest_core_version
47
+ latest_version :core
48
+ end
49
+
50
+ # get array of schema versions for a particular schema type
51
+ def self.versions type
52
+ schemas = find_schemas!(type).keys
53
+ sort_versions(schemas)
54
+ end
55
+
56
+ # get earliest schema version for a particular schema type
57
+ def self.earliest_version type
58
+ schemas = find_schemas!(type).keys
59
+ sort_versions(schemas).first
60
+ end
61
+
62
+ # get latest schema version for a particular schema type
63
+ def self.latest_version type
64
+ schemas = find_schemas!(type).keys
65
+ sort_versions(schemas).last
66
+ end
67
+
68
+ # validate an rsmp messages using a schema object
35
69
  def self.validate_using_schema message, schema
36
70
  raise ArgumentError.new("message missing") unless message
37
71
  raise ArgumentError.new("schema missing") unless schema
@@ -44,17 +78,28 @@ module RSMP::Schema
44
78
  end
45
79
  end
46
80
 
81
+ # sort version strings
82
+ def self.sort_versions versions
83
+ versions.sort_by { |k| Gem::Version.new(k) }
84
+ end
85
+
86
+ # find schemas versions for particular schema type
87
+ # return nil if type not found
47
88
  def self.find_schemas type
48
89
  raise ArgumentError.new("type missing") unless type
49
- schemas[type.to_sym]
90
+ schemas = @@schemas[type.to_sym]
50
91
  end
51
92
 
93
+ # find schemas versions for particular schema type
94
+ # raise error if not found
52
95
  def self.find_schemas! type
53
96
  schemas = find_schemas type
54
97
  raise UnknownSchemaTypeError.new("Unknown schema type #{type}") unless schemas
55
98
  schemas
56
99
  end
57
100
 
101
+ # find schema for a particular schema and version
102
+ # return nil if not found
58
103
  def self.find_schema type, version, options={}
59
104
  raise ArgumentError.new("version missing") unless version
60
105
  version = sanitize_version version if options[:lenient]
@@ -65,11 +110,18 @@ module RSMP::Schema
65
110
  nil
66
111
  end
67
112
 
113
+ # get major.minor.patch part of a version string, where patch is optional
114
+ # ignore trailing characters, e.g.
115
+ # 3.1.3.32A => 3.1.3
116
+ # 3.1A3r3 >= 3.1
117
+ # return nil if string doesn't match
68
118
  def self.sanitize_version version
69
119
  matched = /^\d+\.\d+(\.\d+)?/.match version
70
120
  matched.to_s if matched
71
121
  end
72
122
 
123
+ # find schema for a particular schema and version
124
+ # raise error if not found
73
125
  def self.find_schema! type, version, options={}
74
126
  schema = find_schema type, version, options
75
127
  raise ArgumentError.new("version missing") unless version
@@ -82,10 +134,14 @@ module RSMP::Schema
82
134
  raise UnknownSchemaVersionError.new("Unknown schema version #{type} #{version}")
83
135
  end
84
136
 
137
+ # true if a particular schema type and version found
85
138
  def self.has_schema? type, version, options={}
86
139
  find_schema(type,version, options) != nil
87
140
  end
88
141
 
142
+ # validate using a particular schema and version
143
+ # raises error if schema is not found
144
+ # return nil if validation succeds, otherwise returns an array of errors
89
145
  def self.validate message, schemas, options={}
90
146
  raise ArgumentError.new("message missing") unless message
91
147
  raise ArgumentError.new("schemas missing") unless schemas
@@ -1,5 +1,5 @@
1
1
  module RSMP
2
2
  module Schema
3
- VERSION = "0.1.1"
3
+ VERSION = "0.2.2"
4
4
  end
5
5
  end
data/lib/rsmp_schema.rb CHANGED
@@ -5,3 +5,5 @@ require 'rsmp_schema/schema'
5
5
  require 'rsmp_schema/convert/import/yaml'
6
6
  require 'rsmp_schema/convert/export/json_schema'
7
7
  require 'rsmp_schema/version'
8
+
9
+ RSMP::Schema.setup
@@ -177,15 +177,13 @@ objects:
177
177
  During maintenance work the controller might be using dark mode (no output to the signal heads).
178
178
  arguments:
179
179
  intersection:
180
- list: true
181
- type: integer
180
+ type: integer_list
182
181
  description: |-
183
182
  0: Not applicable (only one intersection exists or applicable for all intersection of the traffic light controller)
184
183
  Other value: Intersection number
185
184
  range: "[0-255]"
186
185
  status:
187
- list: true
188
- type: boolean
186
+ type: boolean_list
189
187
  description: |-
190
188
  False: Traffic Light Controller in dark mode
191
189
  True: Traffic Light Controller not in dark mode
@@ -196,15 +194,13 @@ objects:
196
194
  Signal timings is controlled manually by service personnel using the operating panel of the controller.
197
195
  arguments:
198
196
  intersection:
199
- list: true
200
- type: integer
197
+ type: integer_list
201
198
  description: |-
202
199
  0: Not applicable (only one intersection exists or applicable for all intersection of the traffic light controller)
203
200
  Other value: Intersection number
204
201
  range: "[0-255]"
205
202
  status:
206
- list: true
207
- type: boolean
203
+ type: boolean_list
208
204
  description: |-
209
205
  False: Manual control inactive
210
206
  True: Manual control active
@@ -215,15 +211,13 @@ objects:
215
211
  Usually only used in case normal detectors can't be used, e.g. during maintenance work.
216
212
  arguments:
217
213
  intersection:
218
- list: true
219
- type: integer
214
+ type: integer_list
220
215
  description: |-
221
216
  0: Not applicable (only one intersection exists or applicable for all intersection of the traffic light controller)
222
217
  Other value: Intersection number
223
218
  range: "[0-255]"
224
219
  status:
225
- list: true
226
- type: boolean
220
+ type: boolean_list
227
221
  description: |-
228
222
  False: Fixed time control inactive
229
223
  True: Fixed time control active
@@ -234,15 +228,13 @@ objects:
234
228
  Used to determine if the controller is operating independently or operating with other controllers (coordination).
235
229
  arguments:
236
230
  intersection:
237
- list: true
238
- type: integer
231
+ type: integer_list
239
232
  description: |-
240
233
  0: Not applicable (only one intersection exists or applicable for all intersection of the traffic light controller)
241
234
  Other value: Intersection number
242
235
  range: "[0-255]"
243
236
  status:
244
- list: true
245
- type: boolean
237
+ type: boolean_list
246
238
  description: |-
247
239
  False: Isolated control disabled
248
240
  True: Isolated control enabled (Vehicle actuated control or Fixed time control)
@@ -253,15 +245,13 @@ objects:
253
245
  Yellow flash may be used during a serious fault (depending on configuration) or maintenance work. It can also be manually set using M0001.
254
246
  arguments:
255
247
  intersection:
256
- list: true
257
- type: integer
248
+ type: integer_list
258
249
  description: |-
259
250
  0: Not applicable (only one intersection exists or applicable for all intersection of the traffic light controller)
260
251
  Other value: Intersection number
261
252
  range: "[0-255]"
262
253
  status:
263
- list: true
264
- type: boolean
254
+ type: boolean_list
265
255
  description: |-
266
256
  False: Yellow flash disabled
267
257
  True: Yellow flash enabled
@@ -272,15 +262,13 @@ objects:
272
262
  All red can be manually set using the controllers operating panel during maintenance work.
273
263
  arguments:
274
264
  intersection:
275
- list: true
276
- type: integer
265
+ type: integer_list
277
266
  description: |-
278
267
  0: Not applicable (only one intersection exists or applicable for all intersection of the traffic light controller)
279
268
  Other value: Intersection number
280
269
  range: "[0-255]"
281
270
  status:
282
- list: true
283
- type: boolean
271
+ type: boolean_list
284
272
  description: |-
285
273
  False: All red disabled
286
274
  True: All red enabled
@@ -291,15 +279,13 @@ objects:
291
279
  The "police key" is a external control switch present in some controllers that manually switches the controller to either dark mode or yellow flash.
292
280
  arguments:
293
281
  intersection:
294
- list: true
295
- type: integer
282
+ type: integer_list
296
283
  description: |-
297
284
  0: Not applicable (only one intersection exists or applicable for all intersection of the traffic light controller)
298
285
  Other value: Intersection number
299
286
  range: "[0-255]"
300
287
  status:
301
- list: true
302
- type: integer
288
+ type: integer_list
303
289
  values:
304
290
  0: disabled
305
291
  1: dark mode
@@ -367,15 +353,13 @@ objects:
367
353
  Can be used for the management system to check the current control mode (startup, normal, standby, failure, test).
368
354
  arguments:
369
355
  intersection:
370
- list: true
371
- type: integer
356
+ type: integer_list
372
357
  description: |-
373
358
  0: Not applicable (only one intersection exists or applicable for all intersection of the traffic light controller)
374
359
  Other value: Intersection number
375
360
  range: "[0-255]"
376
361
  controlmode:
377
- list: true
378
- type: string
362
+ type: string_list
379
363
  values:
380
364
  startup: Startup mode
381
365
  control: Normal control
@@ -469,29 +453,25 @@ objects:
469
453
  Requires security code 2.
470
454
  arguments:
471
455
  status:
472
- list: true
456
+ type: string_list
473
457
  description: Set operating mode
474
- type: string
475
458
  values:
476
459
  NormalControl: Normal Control
477
460
  YellowFlash: Enables yellow flash
478
461
  Dark: Enables dark mode
479
462
  securityCode:
480
- list: true
463
+ type: string_list
481
464
  description: Security code 2
482
- type: string
483
465
  timeout:
484
- list: true
466
+ type: integer_list
485
467
  description: |-
486
468
  Time in minutes until controller automatically reverts to previous functional position.
487
469
  0=no automatic return
488
- type: integer
489
470
  min: 0
490
471
  max: 1440
491
472
  intersection:
492
- list: true
473
+ type: integer_list
493
474
  description: Intersection number
494
- type: integer
495
475
  min: 0
496
476
  max: 255
497
477
  command: setValue