Class: Generator::Notification
- Inherits:
-
Object
- Object
- Generator::Notification
- Includes:
- FileWriter, SchemaGenerator, SchemaHelpers
- Defined in:
- lib/generator/notification.rb
Overview
Generates Ruby type classes for SP-API notification payloads from JSON Schema files
Constant Summary
Constants included from Formatter
Instance Attribute Summary collapse
-
#file_path ⇒ Object
readonly
Returns the value of attribute file_path.
-
#schema ⇒ Object
readonly
Returns the value of attribute schema.
Class Method Summary collapse
Instance Method Summary collapse
-
#class_name ⇒ Object
Class name for the notification (e.g., "AnyOfferChanged").
-
#envelope_properties ⇒ Object
Get envelope properties (notificationVersion, notificationType, eventTime, etc.).
-
#envelope_required_properties ⇒ Object
Get required envelope properties.
- #generate ⇒ Object
-
#initialize(file_path) ⇒ Notification
constructor
A new instance of Notification.
-
#notification_name ⇒ Object
Extract notification name from filename (e.g., "AnyOfferChangedNotification.json" => "AnyOfferChanged").
-
#notification_object_name ⇒ Object
Get the actual notification object name within payload (handles casing variations).
-
#notification_type ⇒ Object
Detect if this is Type A (nested) or Type B (flat) notification.
-
#payload_key_name ⇒ Object
Get the actual payload key name from the schema (handles PascalCase vs camelCase).
-
#payload_properties ⇒ Object
Get payload properties based on notification type.
-
#payload_required_properties ⇒ Object
Get required payload properties based on notification type.
-
#payload_schema ⇒ Object
Get payload schema (the full payload object).
-
#payload_version ⇒ Object
Extract payload version from the schema examples or filename.
-
#raw_description ⇒ Object
Class description from schema (no leading spaces - template handles indentation) Raw description from schema (will be formatted by Type class).
-
#replace_inline_objects_with_refs(properties, extracted_types) ⇒ Object
Replace inline object definitions with $ref pointers to extracted types.
-
#wrapper_properties ⇒ Object
Properties from the wrapper level (notificationVersion, notificationType, etc.).
-
#wrapper_required_properties ⇒ Object
Required properties from the wrapper level.
Methods included from SchemaGenerator
#generate_main_file!, included, #needs_money?, #output_file_path, #sorted_properties
Methods included from SchemaHelpers
#api_name_for_type_resolver, #attribute_name_for, #format_property_comment, #generate_nested_types!, #generate_rbs!, #generic_placeholder?, #needs_money?, #nested_type_files, #ruby_type_for, #type_resolver
Methods included from Formatter
#convert_doc_links_to_full_url, #convert_html_links_to_yard, #format_method_definition, #split_long_comment_line
Methods included from FileWriter
Constructor Details
#initialize(file_path) ⇒ Notification
Returns a new instance of Notification.
34 35 36 37 38 39 |
# File 'lib/generator/notification.rb', line 34 def initialize(file_path) @file_path = file_path @schema = JSON.parse(File.read(file_path)) resolve_root_ref! apply_schema_fixes! end |
Instance Attribute Details
#file_path ⇒ Object (readonly)
Returns the value of attribute file_path.
26 27 28 |
# File 'lib/generator/notification.rb', line 26 def file_path @file_path end |
#schema ⇒ Object (readonly)
Returns the value of attribute schema.
26 27 28 |
# File 'lib/generator/notification.rb', line 26 def schema @schema end |
Class Method Details
.schema_type ⇒ Object
29 30 31 |
# File 'lib/generator/notification.rb', line 29 def schema_type "notifications" end |
Instance Method Details
#class_name ⇒ Object
Class name for the notification (e.g., "AnyOfferChanged")
116 117 118 119 |
# File 'lib/generator/notification.rb', line 116 def class_name # Underscore first to ensure ActiveSupport::Inflector applies acronym rules correctly notification_name.underscore.camelize end |
#envelope_properties ⇒ Object
Get envelope properties (notificationVersion, notificationType, eventTime, etc.)
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/generator/notification.rb', line 183 def envelope_properties props = {} ["notificationVersion", "NotificationVersion"].each do |key| props[key] = schema.dig("properties", key) if schema.dig("properties", key) end ["notificationType", "NotificationType"].each do |key| props[key] = schema.dig("properties", key) if schema.dig("properties", key) end ["payloadVersion", "PayloadVersion"].each do |key| props[key] = schema.dig("properties", key) if schema.dig("properties", key) end ["eventTime", "EventTime"].each do |key| props[key] = schema.dig("properties", key) if schema.dig("properties", key) end ["notificationMetadata", "NotificationMetadata"].each do |key| props[key] = schema.dig("properties", key) if schema.dig("properties", key) end props end |
#envelope_required_properties ⇒ Object
Get required envelope properties
204 205 206 207 208 |
# File 'lib/generator/notification.rb', line 204 def envelope_required_properties required = schema["required"] || [] envelope_keys = envelope_properties.keys required.select { |r| envelope_keys.include?(r) } end |
#generate ⇒ Object
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/generator/notification.rb', line 41 def generate written_files = [] all_types = [] # Generate nested types first nested_results = generate_nested_types! written_files.concat(nested_results[:files]) all_types.concat(nested_results[:types]) # Generate Payload and Notification as types payload_results = generate_payload_types! written_files.concat(payload_results[:files]) all_types.concat(payload_results[:types]) notification_result = generate_notification_type! written_files << notification_result[:file] all_types << notification_result[:type] # Generate main convenience file written_files << generate_main_file! # Reload to pick up newly generated files for RBS introspection IntrospectionLoader.reload written_files << generate_rbs!(all_types) # Batch format all written files format_files(written_files) Generator.logger.info("Generated notification #{notification_name.underscore}") end |
#notification_name ⇒ Object
Extract notification name from filename (e.g., "AnyOfferChangedNotification.json" => "AnyOfferChanged")
73 74 75 76 77 78 |
# File 'lib/generator/notification.rb', line 73 def notification_name base_name = File.basename(file_path, ".json") # Remove "Notification" suffix but keep version suffix (e.g., "_2023-12-13") # Convert dashes in version suffix to underscores for valid Ruby identifiers base_name.sub(/Notification$/, "").tr("-", "_") end |
#notification_object_name ⇒ Object
Get the actual notification object name within payload (handles casing variations)
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/generator/notification.rb', line 93 def notification_object_name payload_schema = schema.dig("properties", "payload") || schema.dig("properties", "Payload") return unless payload_schema # If payload uses a $ref, resolve it if payload_schema["$ref"] ref_path = payload_schema["$ref"].sub("#/definitions/", "") payload_schema = schema.dig("definitions", ref_path) return unless payload_schema end # Try different casing conventions [ "#{notification_name.camelize(:lower)}Notification", # feedProcessingFinishedNotification "#{notification_name.camelize}Notification", # FeedProcessingFinishedNotification ].each do |name| return name if payload_schema.dig("properties", name) end nil end |
#notification_type ⇒ Object
Detect if this is Type A (nested) or Type B (flat) notification
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/generator/notification.rb', line 159 def notification_type payload_schema = schema.dig("properties", "payload") || schema.dig("properties", "Payload") return :unknown unless payload_schema # If payload uses a $ref, resolve it if payload_schema["$ref"] ref_path = payload_schema["$ref"].sub("#/definitions/", "") payload_schema = schema.dig("definitions", ref_path) return :unknown unless payload_schema end # Type A: has nested notification object (e.g., feedProcessingFinishedNotification) [ "#{notification_name.camelize(:lower)}Notification", "#{notification_name.camelize}Notification", ].each do |name| return :nested if payload_schema.dig("properties", name) end # Type B: flat payload :flat end |
#payload_key_name ⇒ Object
Get the actual payload key name from the schema (handles PascalCase vs camelCase)
81 82 83 84 85 86 87 88 89 90 |
# File 'lib/generator/notification.rb', line 81 def payload_key_name # Check if schema uses "payload" or "Payload" if schema.dig("properties", "payload") "payload" elsif schema.dig("properties", "Payload") "Payload" else "payload" # fallback end end |
#payload_properties ⇒ Object
Get payload properties based on notification type
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/generator/notification.rb', line 216 def payload_properties return {} unless payload_schema resolved_payload = payload_schema # Resolve $ref if present if resolved_payload&.dig("$ref") ref_path = resolved_payload["$ref"].sub("#/definitions/", "") resolved_payload = schema.dig("definitions", ref_path) return {} unless resolved_payload end if notification_type == :nested # Type A: extract the nested notification object properties [ "#{notification_name.camelize(:lower)}Notification", "#{notification_name.camelize}Notification", ].each do |name| nested_obj = resolved_payload.dig("properties", name) next unless nested_obj # Resolve nested object $ref if present if nested_obj["$ref"] ref_path = nested_obj["$ref"].sub("#/definitions/", "") nested_obj = schema.dig("definitions", ref_path) end return sorted_properties(nested_obj["properties"] || {}) if nested_obj end {} else # Type B: use payload properties directly sorted_properties(resolved_payload["properties"] || {}) end end |
#payload_required_properties ⇒ Object
Get required payload properties based on notification type
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/generator/notification.rb', line 251 def payload_required_properties return [] unless payload_schema resolved_payload = payload_schema # Resolve $ref if present if resolved_payload&.dig("$ref") ref_path = resolved_payload["$ref"].sub("#/definitions/", "") resolved_payload = schema.dig("definitions", ref_path) return [] unless resolved_payload end if notification_type == :nested # Type A: get required from nested notification object [ "#{notification_name.camelize(:lower)}Notification", "#{notification_name.camelize}Notification", ].each do |name| nested_obj = resolved_payload.dig("properties", name) next unless nested_obj # Resolve nested object $ref if present if nested_obj["$ref"] ref_path = nested_obj["$ref"].sub("#/definitions/", "") nested_obj = schema.dig("definitions", ref_path) end return nested_obj["required"] || [] if nested_obj end [] else # Type B: use payload required directly resolved_payload["required"] || [] end end |
#payload_schema ⇒ Object
Get payload schema (the full payload object)
211 212 213 |
# File 'lib/generator/notification.rb', line 211 def payload_schema schema.dig("properties", "payload") || schema.dig("properties", "Payload") end |
#payload_version ⇒ Object
Extract payload version from the schema examples or filename
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/generator/notification.rb', line 122 def payload_version # Try to get from examples first examples = schema.dig("examples") if examples&.first return examples.first["payloadVersion"] || examples.first["PayloadVersion"] end # Fall back to filename-based version (e.g., "_2023-12-13") if file_path =~ /_(\d{4}-\d{2}-\d{2})\.json$/ matched = ::Regexp.last_match(1) return matched if matched end # Default to 1.0 if no version found "1.0" end |
#raw_description ⇒ Object
Class description from schema (no leading spaces - template handles indentation) Raw description from schema (will be formatted by Type class)
151 152 153 154 155 156 |
# File 'lib/generator/notification.rb', line 151 def raw_description return unless schema["description"] return if generic_placeholder?(schema["description"]) schema["description"] end |
#replace_inline_objects_with_refs(properties, extracted_types) ⇒ Object
Replace inline object definitions with $ref pointers to extracted types
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/generator/notification.rb', line 286 def replace_inline_objects_with_refs(properties, extracted_types) result = {} properties.each do |prop_name, prop_def| # Check if this is a money-like object if MoneyDetector.money_like?(prop_def) result[prop_name] = { "$ref" => "#/definitions/Money", "description" => prop_def["description"], }.compact next end # Check if this property matches an extracted type name if prop_def["type"] == "object" && extracted_types.key?(prop_name) # Replace with $ref result[prop_name] = { "$ref" => "#/definitions/#{prop_name}" } elsif prop_def["type"] == "array" && prop_def["items"] # Handle array items - check if they're objects that match extracted types items = prop_def["items"] # Handle case where items is an array (non-standard format) items = items.first if items.is_a?(Array) && !items.empty? # Check if items are money-like objects if items.is_a?(Hash) && MoneyDetector.money_like?(items) result[prop_name] = prop_def.dup result[prop_name]["items"] = { "$ref" => "#/definitions/Money" } next end # Check if items are inline objects that match extracted types if items.is_a?(Hash) && (items["type"] == "object" || items["anyOf"]) item_type_name = prop_name.singularize if extracted_types.key?(item_type_name) result[prop_name] = prop_def.dup result[prop_name]["items"] = { "$ref" => "#/definitions/#{item_type_name}" } else result[prop_name] = prop_def end else result[prop_name] = prop_def end else result[prop_name] = prop_def end end result end |
#wrapper_properties ⇒ Object
Properties from the wrapper level (notificationVersion, notificationType, etc.)
140 141 142 |
# File 'lib/generator/notification.rb', line 140 def wrapper_properties sorted_properties(schema["properties"] || {}) end |
#wrapper_required_properties ⇒ Object
Required properties from the wrapper level
145 146 147 |
# File 'lib/generator/notification.rb', line 145 def wrapper_required_properties schema["required"] || [] end |