Module: RSMP::Convert::Export::JSONSchema
- Defined in:
- lib/rsmp/convert/export/json_schema.rb,
lib/rsmp/convert/export/json_schema/items.rb,
lib/rsmp/convert/export/json_schema/values.rb,
lib/rsmp/convert/export/json_schema/outputs.rb
Overview
Converts SXL definitions to JSON Schema files.
Constant Summary collapse
- JSON_OPTIONS =
{ array_nl: "\n", object_nl: "\n", indent: ' ', space_before: ' ', space: ' ' }.freeze
- DEFINITIONS_SOURCE =
Default path to definitions.json bundled with the gem’s core schemas
File.('../../../../schemas/core/3.1.2/definitions.json', __dir__)
- GUARDS_JSON =
{ '$schema' => 'https://json-schema.org/draft/2020-12/schema', '$defs' => { 'q_unknown_or_undefined' => { 'allOf' => [ { 'required' => ['q'] }, { 'properties' => { 'q' => { 'enum' => %w[undefined unknown] } } } ] } } }.freeze
Class Method Summary collapse
- .build_default_item(item, arguments, property_key) ⇒ Object
-
.build_item(item, property_key: 'v') ⇒ Object
convert yaml alarm/status/command item to corresponding json schema.
-
.build_json_array(item, out) ⇒ Object
convert an yaml item with type: array to json schema.
- .build_status_item(item, arguments) ⇒ Object
-
.build_value(item) ⇒ Object
convert a yaml item to json schema.
- .enum_keys(item) ⇒ Object
-
.generate(sxl) ⇒ Object
generate the json schema from a string containing yaml.
-
.handle_enum(item, out) ⇒ Object
convert yaml values to json schema enum.
-
.handle_pattern(item, out) ⇒ Object
convert yaml pattern to json schema.
-
.handle_string_list(item, out) ⇒ Object
convert a yaml item with list: true to json schema.
-
.handle_types(item, out) ⇒ Object
convert an item which is not a string-list, to json schema.
-
.output_alarm(out, key, item) ⇒ Object
convert an alarm to json schema.
-
.output_alarms(out, items) ⇒ Object
convert alarms to json schema.
-
.output_command(out, key, item) ⇒ Object
convert a command to json schema.
-
.output_commands(out, items) ⇒ Object
convert commands to json schema.
- .output_json(item) ⇒ Object
-
.output_root(out, meta) ⇒ Object
output the json schema root.
-
.output_status(out, key, item) ⇒ Object
convert a status to json schema.
-
.output_statuses(out, items) ⇒ Object
convert statuses to json schema.
- .root_type_rules ⇒ Object
- .simple_item(item) ⇒ Object
- .stringify_values(values) ⇒ Object
- .validate_hash_values!(item) ⇒ Object
-
.wrap_refs(out) ⇒ Object
JSON Schema 2020-12 allows combining $ref with other properties directly.
-
.write(sxl, folder) ⇒ Object
convert yaml to json schema and write files to a folder.
Class Method Details
.build_default_item(item, arguments, property_key) ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/rsmp/convert/export/json_schema/items.rb', line 38 def self.build_default_item(item, arguments, property_key) rules = arguments.map do |key, argument| { 'if' => { 'properties' => { 'n' => { 'const' => key } } }, 'then' => { 'properties' => { property_key => build_value(argument) } } } end { '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'description' => item['description'], 'properties' => { 'n' => { 'enum' => arguments.keys.sort } }, 'allOf' => rules } end |
.build_item(item, property_key: 'v') ⇒ Object
convert yaml alarm/status/command item to corresponding json schema
7 8 9 10 11 12 |
# File 'lib/rsmp/convert/export/json_schema/items.rb', line 7 def self.build_item(item, property_key: 'v') arguments = item['arguments'] return simple_item(item) unless arguments property_key == 's' ? build_status_item(item, arguments) : build_default_item(item, arguments, property_key) end |
.build_json_array(item, out) ⇒ Object
convert an yaml item with type: array to json schema
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/rsmp/convert/export/json_schema/values.rb', line 37 def self.build_json_array(item, out) required = item.reject { |_k, v| v['optional'] == true }.keys.sort out.merge!({ 'type' => 'array', 'items' => { 'type' => 'object', 'required' => required, 'unevaluatedProperties' => false } }) out['items']['properties'] = {} item.each_pair do |key, v| out['items']['properties'][key] = build_value(v) end out end |
.build_status_item(item, arguments) ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/rsmp/convert/export/json_schema/items.rb', line 21 def self.build_status_item(item, arguments) branches = arguments.map do |key, argument| { 'if' => { 'properties' => { 'n' => { 'const' => key } } }, 'then' => { 'properties' => { 's' => build_value(argument) } } } end { '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'description' => item['description'], 'properties' => { 'n' => { 'enum' => arguments.keys.sort } }, 'if' => { '$ref' => '../defs/guards.json#/$defs/q_unknown_or_undefined' }, 'then' => {}, 'else' => { 'allOf' => branches } } end |
.build_value(item) ⇒ Object
convert a yaml item to json schema
7 8 9 10 11 12 13 14 15 16 17 18 |
# File 'lib/rsmp/convert/export/json_schema/values.rb', line 7 def self.build_value(item) out = {} out['description'] = item['description'] if item['description'] if item['type'] =~ /_list$/ handle_string_list item, out else handle_types item, out handle_enum item, out handle_pattern item, out end wrap_refs out end |
.enum_keys(item) ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/rsmp/convert/export/json_schema/values.rb', line 87 def self.enum_keys(item) case item['values'] when Hash validate_hash_values! item item['values'].keys.sort when Array item['values'].sort else raise 'Error: Values must be specified as either a Hash or an Array, ' \ "got #{item['values'].class}" end end |
.generate(sxl) ⇒ Object
generate the json schema from a string containing yaml
33 34 35 36 37 38 39 40 |
# File 'lib/rsmp/convert/export/json_schema.rb', line 33 def self.generate(sxl) out = {} output_root out, sxl[:meta] output_alarms out, sxl[:alarms] output_statuses out, sxl[:statuses] output_commands out, sxl[:commands] out end |
.handle_enum(item, out) ⇒ Object
convert yaml values to json schema enum
81 82 83 84 85 |
# File 'lib/rsmp/convert/export/json_schema/values.rb', line 81 def self.handle_enum(item, out) return unless item['values'] out['enum'] = stringify_values(enum_keys(item)) end |
.handle_pattern(item, out) ⇒ Object
convert yaml pattern to json schema
114 115 116 |
# File 'lib/rsmp/convert/export/json_schema/values.rb', line 114 def self.handle_pattern(item, out) out['pattern'] = item['pattern'] if item['pattern'] end |
.handle_string_list(item, out) ⇒ Object
convert a yaml item with list: true to json schema
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/rsmp/convert/export/json_schema/values.rb', line 60 def self.handle_string_list(item, out) case item['type'] when 'boolean_list' out['$ref'] = '../defs/definitions.json#/boolean_list' when 'integer_list' out['$ref'] = '../defs/definitions.json#/integer_list' when 'string_list' out['$ref'] = '../defs/definitions.json#/string_list' else raise "Error: List of #{item['type']} is not supported: #{item.inspect}" end if item['values'] value_list = item['values'].keys.join('|') out['pattern'] = /(?-mix:^(#{value_list})(?:,(#{value_list}))*$)/ end puts "Warning: Pattern not support for lists: #{item.inspect}" if item['pattern'] end |
.handle_types(item, out) ⇒ Object
convert an item which is not a string-list, to json schema
21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/rsmp/convert/export/json_schema/values.rb', line 21 def self.handle_types(item, out) case item['type'] when 'boolean' out['$ref'] = '../defs/definitions.json#/boolean' when 'timestamp' out['$ref'] = '../defs/definitions.json#/timestamp' when 'integer', 'ordinal', 'unit', 'scale', 'long' out['$ref'] = '../defs/definitions.json#/integer' when 'array' # a json array build_json_array item['items'], out else # string, base64, and any unknown types out['type'] = 'string' end end |
.output_alarm(out, key, item) ⇒ Object
convert an alarm to json schema
38 39 40 41 |
# File 'lib/rsmp/convert/export/json_schema/outputs.rb', line 38 def self.output_alarm(out, key, item) json = build_item item out["alarms/#{key}.json"] = output_json json end |
.output_alarms(out, items) ⇒ Object
convert alarms to json schema
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/rsmp/convert/export/json_schema/outputs.rb', line 19 def self.output_alarms(out, items) list = items.keys.sort.map do |key| { 'if' => { 'required' => ['aCId'], 'properties' => { 'aCId' => { 'const' => key } } }, 'then' => { '$ref' => "#{key}.json" } } end json = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'properties' => { 'aCId' => { 'enum' => items.keys.sort }, 'rvs' => { 'items' => { 'allOf' => list } } } } out['alarms/alarms.json'] = output_json json items.each_pair { |key, item| output_alarm out, key, item } end |
.output_command(out, key, item) ⇒ Object
convert a command to json schema
99 100 101 102 103 104 |
# File 'lib/rsmp/convert/export/json_schema/outputs.rb', line 99 def self.output_command(out, key, item) json = build_item item json['properties'] ||= {} json['properties']['cO'] = { 'const' => item['command'] } out["commands/#{key}.json"] = output_json json end |
.output_commands(out, items) ⇒ Object
convert commands to json schema
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/rsmp/convert/export/json_schema/outputs.rb', line 69 def self.output_commands(out, items) list = [{ 'properties' => { 'cCI' => { 'enum' => items.keys.sort } } }] items.keys.sort.each do |key| list << { 'if' => { 'required' => ['cCI'], 'properties' => { 'cCI' => { 'const' => key } } }, 'then' => { '$ref' => "#{key}.json" } } end json = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'items' => { 'allOf' => list } } out['commands/commands.json'] = output_json json json = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'properties' => { 'arg' => { '$ref' => 'commands.json' } } } out['commands/command_requests.json'] = output_json json json = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'properties' => { 'rvs' => { '$ref' => 'commands.json' } } } out['commands/command_responses.json'] = output_json json items.each_pair { |key, item| output_command out, key, item } end |
.output_json(item) ⇒ Object
25 26 27 |
# File 'lib/rsmp/convert/export/json_schema.rb', line 25 def self.output_json(item) JSON.generate(item, JSON_OPTIONS) end |
.output_root(out, meta) ⇒ Object
output the json schema root
107 108 109 110 111 112 113 114 115 116 |
# File 'lib/rsmp/convert/export/json_schema/outputs.rb', line 107 def self.output_root(out, ) json = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'name' => ['name'], 'description' => ['description'], 'version' => ['version'], 'allOf' => root_type_rules } out['rsmp.json'] = output_json json end |
.output_status(out, key, item) ⇒ Object
convert a status to json schema
63 64 65 66 |
# File 'lib/rsmp/convert/export/json_schema/outputs.rb', line 63 def self.output_status(out, key, item) json = build_item item, property_key: 's' out["statuses/#{key}.json"] = output_json json end |
.output_statuses(out, items) ⇒ Object
convert statuses to json schema
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/rsmp/convert/export/json_schema/outputs.rb', line 44 def self.output_statuses(out, items) out['defs/guards.json'] ||= output_json(GUARDS_JSON) list = [{ 'properties' => { 'sCI' => { 'enum' => items.keys.sort } } }] items.keys.sort.each do |key| list << { 'if' => { 'required' => ['sCI'], 'properties' => { 'sCI' => { 'const' => key } } }, 'then' => { '$ref' => "#{key}.json" } } end json = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'properties' => { 'sS' => { 'items' => { 'allOf' => list } } } } out['statuses/statuses.json'] = output_json json items.each_pair { |key, item| output_status out, key, item } end |
.root_type_rules ⇒ Object
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/rsmp/convert/export/json_schema/outputs.rb', line 118 def self.root_type_rules [ { 'if' => { 'required' => ['type'], 'properties' => { 'type' => { 'const' => 'CommandRequest' } } }, 'then' => { '$ref' => 'commands/command_requests.json' } }, { 'if' => { 'required' => ['type'], 'properties' => { 'type' => { 'const' => 'CommandResponse' } } }, 'then' => { '$ref' => 'commands/command_responses.json' } }, { 'if' => { 'required' => ['type'], 'properties' => { 'type' => { 'enum' => %w[StatusRequest StatusResponse StatusSubscribe StatusUnsubscribe StatusUpdate] } } }, 'then' => { '$ref' => 'statuses/statuses.json' } }, { 'if' => { 'required' => ['type'], 'properties' => { 'type' => { 'const' => 'Alarm' } } }, 'then' => { '$ref' => 'alarms/alarms.json' } } ] end |
.simple_item(item) ⇒ Object
14 15 16 17 18 19 |
# File 'lib/rsmp/convert/export/json_schema/items.rb', line 14 def self.simple_item(item) { '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'description' => item['description'] } end |
.stringify_values(values) ⇒ Object
109 110 111 |
# File 'lib/rsmp/convert/export/json_schema/values.rb', line 109 def self.stringify_values(values) values.map { |v| v.is_a?(Integer) || v.is_a?(Float) ? v.to_s : v } end |
.validate_hash_values!(item) ⇒ Object
100 101 102 103 104 105 106 107 |
# File 'lib/rsmp/convert/export/json_schema/values.rb', line 100 def self.validate_hash_values!(item) item['values'].each_pair do |k, v| next unless ['', nil].include?(v) raise "Error: '#{k}' has empty value in #{item}. " \ '(When using a hash to specify \'values\', the hash values cannot be empty.)' end end |
.wrap_refs(out) ⇒ Object
JSON Schema 2020-12 allows combining $ref with other properties directly
55 56 57 |
# File 'lib/rsmp/convert/export/json_schema/values.rb', line 55 def self.wrap_refs(out) out end |
.write(sxl, folder) ⇒ Object
convert yaml to json schema and write files to a folder
43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/rsmp/convert/export/json_schema.rb', line 43 def self.write(sxl, folder) out = generate sxl out.each_pair do |relative_path, str| path = File.join(folder, relative_path) FileUtils.mkdir_p File.dirname(path) File.open(path, 'w+') { |file| file.puts str } end # Copy definitions.json so each version folder is self-contained defs_dest = File.join(folder, 'defs', 'definitions.json') FileUtils.mkdir_p File.dirname(defs_dest) FileUtils.cp DEFINITIONS_SOURCE, defs_dest if File.exist?(DEFINITIONS_SOURCE) end |