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.
-
#money_like_object?(node) ⇒ Boolean
Detect if an object looks like a Money type Money objects have: amount/Amount (number) + currencyCode/CurrencyCode (string).
-
#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.
33 34 35 36 37 38 |
# File 'lib/generator/notification.rb', line 33 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.
25 26 27 |
# File 'lib/generator/notification.rb', line 25 def file_path @file_path end |
#schema ⇒ Object (readonly)
Returns the value of attribute schema.
25 26 27 |
# File 'lib/generator/notification.rb', line 25 def schema @schema end |
Class Method Details
.schema_type ⇒ Object
28 29 30 |
# File 'lib/generator/notification.rb', line 28 def schema_type "notifications" end |
Instance Method Details
#class_name ⇒ Object
Class name for the notification (e.g., "AnyOfferChanged")
115 116 117 118 |
# File 'lib/generator/notification.rb', line 115 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.)
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/generator/notification.rb', line 182 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
203 204 205 206 207 |
# File 'lib/generator/notification.rb', line 203 def envelope_required_properties required = schema["required"] || [] envelope_keys = envelope_properties.keys required.select { |r| envelope_keys.include?(r) } end |
#generate ⇒ Object
40 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 |
# File 'lib/generator/notification.rb', line 40 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_name}") end |
#money_like_object?(node) ⇒ Boolean
Detect if an object looks like a Money type Money objects have: amount/Amount (number) + currencyCode/CurrencyCode (string)
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/generator/notification.rb', line 336 def money_like_object?(node) return false unless node.is_a?(Hash) && node["type"] == "object" return false unless node["properties"] props = node["properties"] # Check for both camelCase and PascalCase variants has_amount = props.key?("Amount") || props.key?("amount") || props.key?("currencyAmount") has_currency = props.key?("CurrencyCode") || props.key?("currencyCode") return false unless has_amount && has_currency # Verify types amount_prop = props["Amount"] || props["amount"] || props["currencyAmount"] currency_prop = props["CurrencyCode"] || props["currencyCode"] amount_prop&.dig("type") == "number" && currency_prop&.dig("type") == "string" end |
#notification_name ⇒ Object
Extract notification name from filename (e.g., "AnyOfferChangedNotification.json" => "AnyOfferChanged")
72 73 74 75 76 77 |
# File 'lib/generator/notification.rb', line 72 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)
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/generator/notification.rb', line 92 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
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/generator/notification.rb', line 158 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)
80 81 82 83 84 85 86 87 88 89 |
# File 'lib/generator/notification.rb', line 80 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
215 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 |
# File 'lib/generator/notification.rb', line 215 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
250 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 |
# File 'lib/generator/notification.rb', line 250 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)
210 211 212 |
# File 'lib/generator/notification.rb', line 210 def payload_schema schema.dig("properties", "payload") || schema.dig("properties", "Payload") end |
#payload_version ⇒ Object
Extract payload version from the schema examples or filename
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/generator/notification.rb', line 121 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)
150 151 152 153 154 155 |
# File 'lib/generator/notification.rb', line 150 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
285 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 |
# File 'lib/generator/notification.rb', line 285 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 money_like_object?(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) && money_like_object?(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.)
139 140 141 |
# File 'lib/generator/notification.rb', line 139 def wrapper_properties sorted_properties(schema["properties"] || {}) end |
#wrapper_required_properties ⇒ Object
Required properties from the wrapper level
144 145 146 |
# File 'lib/generator/notification.rb', line 144 def wrapper_required_properties schema["required"] || [] end |