spree_api 6.0.0.rc1 → 6.0.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +0 -17
  3. data/app/controllers/concerns/spree/api/v3/admin/product_membership.rb +10 -0
  4. data/app/controllers/concerns/spree/api/v3/admin/stock_receipt_actions.rb +26 -4
  5. data/app/controllers/concerns/spree/api/v3/admin/subclassed_resource.rb +14 -28
  6. data/app/controllers/concerns/spree/api/v3/admin/validation_details.rb +1 -1
  7. data/app/controllers/concerns/spree/api/v3/cart_resolvable.rb +3 -2
  8. data/app/controllers/concerns/spree/api/v3/channel_resolution.rb +1 -1
  9. data/app/controllers/concerns/spree/api/v3/company_address_writes.rb +3 -1
  10. data/app/controllers/concerns/spree/api/v3/error_handler.rb +48 -2
  11. data/app/controllers/concerns/spree/api/v3/file_uploads.rb +35 -0
  12. data/app/controllers/concerns/spree/api/v3/money_params.rb +52 -0
  13. data/app/controllers/concerns/spree/api/v3/orders/claim_actions.rb +1 -5
  14. data/app/controllers/concerns/spree/api/v3/orders/delivery_actions.rb +6 -6
  15. data/app/controllers/concerns/spree/api/v3/orders/exchange_actions.rb +1 -4
  16. data/app/controllers/concerns/spree/api/v3/orders/fulfillment_actions.rb +1 -5
  17. data/app/controllers/concerns/spree/api/v3/orders/return_actions.rb +1 -4
  18. data/app/controllers/concerns/spree/api/v3/orders/shipping_label_actions.rb +5 -21
  19. data/app/controllers/concerns/spree/api/v3/params_normalizer.rb +5 -0
  20. data/app/controllers/concerns/spree/api/v3/purchase_serialization.rb +26 -0
  21. data/app/controllers/concerns/spree/api/v3/request_params.rb +65 -0
  22. data/app/controllers/concerns/spree/api/v3/strict_money_params.rb +94 -0
  23. data/app/controllers/spree/api/v3/admin/admin_users_controller.rb +10 -1
  24. data/app/controllers/spree/api/v3/admin/allowed_origins_controller.rb +8 -1
  25. data/app/controllers/spree/api/v3/admin/api_keys_controller.rb +15 -7
  26. data/app/controllers/spree/api/v3/admin/base_controller.rb +1 -0
  27. data/app/controllers/spree/api/v3/admin/catalogs/order_minimums_controller.rb +3 -1
  28. data/app/controllers/spree/api/v3/admin/catalogs/quantity_rules_controller.rb +7 -5
  29. data/app/controllers/spree/api/v3/admin/catalogs_controller.rb +60 -33
  30. data/app/controllers/spree/api/v3/admin/categories/products_controller.rb +4 -1
  31. data/app/controllers/spree/api/v3/admin/categories_controller.rb +40 -20
  32. data/app/controllers/spree/api/v3/admin/channels_controller.rb +39 -6
  33. data/app/controllers/spree/api/v3/admin/claim_reasons_controller.rb +5 -1
  34. data/app/controllers/spree/api/v3/admin/collections/products_controller.rb +4 -1
  35. data/app/controllers/spree/api/v3/admin/collections_controller.rb +44 -15
  36. data/app/controllers/spree/api/v3/admin/commission_rates_controller.rb +60 -12
  37. data/app/controllers/spree/api/v3/admin/companies/addresses_controller.rb +11 -0
  38. data/app/controllers/spree/api/v3/admin/companies/memberships_controller.rb +9 -1
  39. data/app/controllers/spree/api/v3/admin/companies/tax_exemption_certificates_controller.rb +19 -18
  40. data/app/controllers/spree/api/v3/admin/companies/tax_identifiers_controller.rb +6 -1
  41. data/app/controllers/spree/api/v3/admin/companies_controller.rb +17 -3
  42. data/app/controllers/spree/api/v3/admin/countries_controller.rb +5 -1
  43. data/app/controllers/spree/api/v3/admin/custom_field_definitions_controller.rb +21 -11
  44. data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +12 -5
  45. data/app/controllers/spree/api/v3/admin/customer_groups_controller.rb +9 -1
  46. data/app/controllers/spree/api/v3/admin/customers/addresses_controller.rb +12 -7
  47. data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +11 -9
  48. data/app/controllers/spree/api/v3/admin/customers/tax_identifiers_controller.rb +6 -1
  49. data/app/controllers/spree/api/v3/admin/customers_controller.rb +35 -7
  50. data/app/controllers/spree/api/v3/admin/data_requests_controller.rb +8 -0
  51. data/app/controllers/spree/api/v3/admin/delivery_method_rules_controller.rb +1 -1
  52. data/app/controllers/spree/api/v3/admin/delivery_methods/rules_controller.rb +15 -7
  53. data/app/controllers/spree/api/v3/admin/delivery_methods_controller.rb +92 -57
  54. data/app/controllers/spree/api/v3/admin/delivery_profiles/origin_groups_controller.rb +8 -1
  55. data/app/controllers/spree/api/v3/admin/delivery_profiles_controller.rb +13 -1
  56. data/app/controllers/spree/api/v3/admin/delivery_zones_controller.rb +21 -5
  57. data/app/controllers/spree/api/v3/admin/email_templates/drafts_controller.rb +14 -1
  58. data/app/controllers/spree/api/v3/admin/email_templates/previews_controller.rb +4 -0
  59. data/app/controllers/spree/api/v3/admin/email_templates/publications_controller.rb +7 -0
  60. data/app/controllers/spree/api/v3/admin/email_templates/restorations_controller.rb +8 -0
  61. data/app/controllers/spree/api/v3/admin/email_templates/test_emails_controller.rb +4 -0
  62. data/app/controllers/spree/api/v3/admin/exports_controller.rb +23 -6
  63. data/app/controllers/spree/api/v3/admin/files_controller.rb +29 -0
  64. data/app/controllers/spree/api/v3/admin/gift_card_batches_controller.rb +20 -1
  65. data/app/controllers/spree/api/v3/admin/gift_cards_controller.rb +19 -1
  66. data/app/controllers/spree/api/v3/admin/imports_controller.rb +36 -9
  67. data/app/controllers/spree/api/v3/admin/integrations_controller.rb +13 -1
  68. data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +9 -2
  69. data/app/controllers/spree/api/v3/admin/invitations_controller.rb +8 -1
  70. data/app/controllers/spree/api/v3/admin/locales_controller.rb +1 -1
  71. data/app/controllers/spree/api/v3/admin/markets_controller.rb +30 -9
  72. data/app/controllers/spree/api/v3/admin/me_controller.rb +12 -3
  73. data/app/controllers/spree/api/v3/admin/media_controller.rb +22 -9
  74. data/app/controllers/spree/api/v3/admin/media_library_controller.rb +4 -0
  75. data/app/controllers/spree/api/v3/admin/option_types_controller.rb +24 -7
  76. data/app/controllers/spree/api/v3/admin/order_cancellation_reasons_controller.rb +5 -1
  77. data/app/controllers/spree/api/v3/admin/order_routing_rules_controller.rb +13 -1
  78. data/app/controllers/spree/api/v3/admin/orders/claims_controller.rb +39 -1
  79. data/app/controllers/spree/api/v3/admin/orders/deliveries_controller.rb +19 -1
  80. data/app/controllers/spree/api/v3/admin/orders/discount_codes_controller.rb +5 -1
  81. data/app/controllers/spree/api/v3/admin/orders/discounts_controller.rb +29 -5
  82. data/app/controllers/spree/api/v3/admin/orders/exchanges_controller.rb +36 -1
  83. data/app/controllers/spree/api/v3/admin/orders/fees_controller.rb +22 -1
  84. data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +66 -7
  85. data/app/controllers/spree/api/v3/admin/orders/gift_cards_controller.rb +6 -2
  86. data/app/controllers/spree/api/v3/admin/orders/items_controller.rb +11 -1
  87. data/app/controllers/spree/api/v3/admin/orders/labels_controller.rb +35 -2
  88. data/app/controllers/spree/api/v3/admin/orders/payments_controller.rb +16 -5
  89. data/app/controllers/spree/api/v3/admin/orders/refunds_controller.rb +6 -0
  90. data/app/controllers/spree/api/v3/admin/orders/returns_controller.rb +37 -1
  91. data/app/controllers/spree/api/v3/admin/orders/store_credits_controller.rb +6 -3
  92. data/app/controllers/spree/api/v3/admin/orders_controller.rb +74 -60
  93. data/app/controllers/spree/api/v3/admin/package_types_controller.rb +30 -5
  94. data/app/controllers/spree/api/v3/admin/password_resets_controller.rb +17 -0
  95. data/app/controllers/spree/api/v3/admin/payment_methods_controller.rb +38 -22
  96. data/app/controllers/spree/api/v3/admin/policies_controller.rb +8 -4
  97. data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +27 -15
  98. data/app/controllers/spree/api/v3/admin/prices_controller.rb +51 -4
  99. data/app/controllers/spree/api/v3/admin/product_types_controller.rb +25 -8
  100. data/app/controllers/spree/api/v3/admin/products/digital_assets_controller.rb +27 -10
  101. data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +9 -14
  102. data/app/controllers/spree/api/v3/admin/products_controller.rb +121 -70
  103. data/app/controllers/spree/api/v3/admin/promotion_actions_controller.rb +11 -2
  104. data/app/controllers/spree/api/v3/admin/promotion_rules_controller.rb +10 -1
  105. data/app/controllers/spree/api/v3/admin/promotions_controller.rb +50 -14
  106. data/app/controllers/spree/api/v3/admin/purchase_orders_controller.rb +54 -7
  107. data/app/controllers/spree/api/v3/admin/refund_reasons_controller.rb +5 -1
  108. data/app/controllers/spree/api/v3/admin/reporting/saved_reports_controller.rb +9 -1
  109. data/app/controllers/spree/api/v3/admin/reporting_controller.rb +34 -0
  110. data/app/controllers/spree/api/v3/admin/resource_controller.rb +1 -0
  111. data/app/controllers/spree/api/v3/admin/return_reasons_controller.rb +5 -1
  112. data/app/controllers/spree/api/v3/admin/roles_controller.rb +13 -1
  113. data/app/controllers/spree/api/v3/admin/seller_payouts_controller.rb +6 -0
  114. data/app/controllers/spree/api/v3/admin/seller_requirement_submissions_controller.rb +15 -0
  115. data/app/controllers/spree/api/v3/admin/seller_requirements_controller.rb +32 -6
  116. data/app/controllers/spree/api/v3/admin/sellers_controller.rb +75 -22
  117. data/app/controllers/spree/api/v3/admin/setup_controller.rb +54 -15
  118. data/app/controllers/spree/api/v3/admin/stock_levels_controller.rb +38 -9
  119. data/app/controllers/spree/api/v3/admin/stock_locations_controller.rb +41 -9
  120. data/app/controllers/spree/api/v3/admin/stock_transfers_controller.rb +48 -7
  121. data/app/controllers/spree/api/v3/admin/store_controller.rb +122 -53
  122. data/app/controllers/spree/api/v3/admin/store_credits_controller.rb +1 -7
  123. data/app/controllers/spree/api/v3/admin/suppliers_controller.rb +16 -6
  124. data/app/controllers/spree/api/v3/admin/tax_categories_controller.rb +7 -4
  125. data/app/controllers/spree/api/v3/admin/tax_rates_controller.rb +21 -3
  126. data/app/controllers/spree/api/v3/admin/tracking_carriers_controller.rb +4 -0
  127. data/app/controllers/spree/api/v3/admin/translations/batches_controller.rb +17 -9
  128. data/app/controllers/spree/api/v3/admin/translations_controller.rb +4 -0
  129. data/app/controllers/spree/api/v3/admin/translations_coverage_controller.rb +5 -0
  130. data/app/controllers/spree/api/v3/admin/updates_controller.rb +39 -0
  131. data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +23 -1
  132. data/app/controllers/spree/api/v3/base_controller.rb +2 -0
  133. data/app/controllers/spree/api/v3/resource_controller.rb +63 -10
  134. data/app/controllers/spree/api/v3/seller/auth_controller.rb +8 -0
  135. data/app/controllers/spree/api/v3/seller/base_controller.rb +1 -0
  136. data/app/controllers/spree/api/v3/seller/countries_controller.rb +2 -2
  137. data/app/controllers/spree/api/v3/seller/delivery_methods_controller.rb +49 -60
  138. data/app/controllers/spree/api/v3/seller/exports_controller.rb +18 -1
  139. data/app/controllers/spree/api/v3/seller/files_controller.rb +27 -0
  140. data/app/controllers/spree/api/v3/seller/imports_controller.rb +25 -9
  141. data/app/controllers/spree/api/v3/seller/invitation_acceptances_controller.rb +9 -1
  142. data/app/controllers/spree/api/v3/seller/me_controller.rb +14 -3
  143. data/app/controllers/spree/api/v3/seller/onboarding_controller.rb +11 -0
  144. data/app/controllers/spree/api/v3/seller/orders/claims_controller.rb +35 -0
  145. data/app/controllers/spree/api/v3/seller/orders/deliveries_controller.rb +12 -0
  146. data/app/controllers/spree/api/v3/seller/orders/exchanges_controller.rb +33 -0
  147. data/app/controllers/spree/api/v3/seller/orders/fulfillments_controller.rb +28 -2
  148. data/app/controllers/spree/api/v3/seller/orders/labels_controller.rb +22 -1
  149. data/app/controllers/spree/api/v3/seller/orders/notes_controller.rb +8 -1
  150. data/app/controllers/spree/api/v3/seller/orders/returns_controller.rb +32 -0
  151. data/app/controllers/spree/api/v3/seller/orders_controller.rb +35 -14
  152. data/app/controllers/spree/api/v3/seller/package_types_controller.rb +21 -9
  153. data/app/controllers/spree/api/v3/seller/password_resets_controller.rb +10 -0
  154. data/app/controllers/spree/api/v3/seller/policies_controller.rb +12 -4
  155. data/app/controllers/spree/api/v3/seller/products_controller.rb +114 -28
  156. data/app/controllers/spree/api/v3/seller/profile_controller.rb +33 -10
  157. data/app/controllers/spree/api/v3/seller/requirement_submissions_controller.rb +12 -5
  158. data/app/controllers/spree/api/v3/seller/resource_controller.rb +12 -0
  159. data/app/controllers/spree/api/v3/seller/stock_locations_controller.rb +44 -13
  160. data/app/controllers/spree/api/v3/seller/tax_identifiers_controller.rb +10 -1
  161. data/app/controllers/spree/api/v3/seller/team_controller.rb +4 -0
  162. data/app/controllers/spree/api/v3/store/auth_controller.rb +10 -0
  163. data/app/controllers/spree/api/v3/store/carts/discount_codes_controller.rb +8 -1
  164. data/app/controllers/spree/api/v3/store/carts/fulfillments_controller.rb +10 -2
  165. data/app/controllers/spree/api/v3/store/carts/gift_cards_controller.rb +8 -1
  166. data/app/controllers/spree/api/v3/store/carts/items_controller.rb +17 -5
  167. data/app/controllers/spree/api/v3/store/carts/payment_sessions_controller.rb +20 -6
  168. data/app/controllers/spree/api/v3/store/carts/payments_controller.rb +11 -0
  169. data/app/controllers/spree/api/v3/store/carts/po_documents_controller.rb +24 -26
  170. data/app/controllers/spree/api/v3/store/carts/store_credits_controller.rb +7 -1
  171. data/app/controllers/spree/api/v3/store/carts/tax_identifiers_controller.rb +5 -1
  172. data/app/controllers/spree/api/v3/store/carts_controller.rb +56 -47
  173. data/app/controllers/spree/api/v3/store/companies/addresses_controller.rb +12 -4
  174. data/app/controllers/spree/api/v3/store/companies/members_controller.rb +4 -0
  175. data/app/controllers/spree/api/v3/store/companies_controller.rb +5 -1
  176. data/app/controllers/spree/api/v3/store/company_invitations_controller.rb +9 -1
  177. data/app/controllers/spree/api/v3/store/customer/addresses_controller.rb +8 -7
  178. data/app/controllers/spree/api/v3/store/customer/data_requests_controller.rb +5 -0
  179. data/app/controllers/spree/api/v3/store/customer/password_resets_controller.rb +17 -0
  180. data/app/controllers/spree/api/v3/store/customer/payment_setup_sessions_controller.rb +10 -5
  181. data/app/controllers/spree/api/v3/store/customer/tax_identifiers_controller.rb +5 -1
  182. data/app/controllers/spree/api/v3/store/customers_controller.rb +28 -2
  183. data/app/controllers/spree/api/v3/store/newsletter_subscribers_controller.rb +22 -0
  184. data/app/controllers/spree/api/v3/store/orders/claims_controller.rb +11 -2
  185. data/app/controllers/spree/api/v3/store/orders/returns_controller.rb +10 -1
  186. data/app/controllers/spree/api/v3/store/orders_controller.rb +47 -10
  187. data/app/controllers/spree/api/v3/store/products_controller.rb +8 -0
  188. data/app/controllers/spree/api/v3/store/wishlist_items_controller.rb +10 -4
  189. data/app/controllers/spree/api/v3/store/wishlists_controller.rb +12 -4
  190. data/app/request_schemas/spree/api/v3/address_request_schema.rb +27 -0
  191. data/app/request_schemas/spree/api/v3/admin/address_request_schema.rb +13 -0
  192. data/app/request_schemas/spree/api/v3/admin/bulk_tags_request_schema.rb +17 -0
  193. data/app/request_schemas/spree/api/v3/admin/catalog_order_minimum_request_schema.rb +17 -0
  194. data/app/request_schemas/spree/api/v3/admin/email_template_language_request_schema.rb +17 -0
  195. data/app/request_schemas/spree/api/v3/admin/email_template_preview_request_schema.rb +32 -0
  196. data/app/request_schemas/spree/api/v3/admin/price_adjustment_tier_request_schema.rb +21 -0
  197. data/app/request_schemas/spree/api/v3/admin/price_list_price_request_schema.rb +21 -0
  198. data/app/request_schemas/spree/api/v3/admin/price_list_rule_request_schema.rb +19 -0
  199. data/app/request_schemas/spree/api/v3/admin/product_membership_reposition_request_schema.rb +17 -0
  200. data/app/request_schemas/spree/api/v3/admin/promotion_action_calculator_request_schema.rb +18 -0
  201. data/app/request_schemas/spree/api/v3/admin/promotion_action_line_item_request_schema.rb +15 -0
  202. data/app/request_schemas/spree/api/v3/admin/promotion_action_request_schema.rb +19 -0
  203. data/app/request_schemas/spree/api/v3/admin/promotion_rule_request_schema.rb +22 -0
  204. data/app/request_schemas/spree/api/v3/admin/reason_request_schema.rb +14 -0
  205. data/app/request_schemas/spree/api/v3/admin/tax_identifier_request_schema.rb +15 -0
  206. data/app/request_schemas/spree/api/v3/custom_field_value_request_schema.rb +15 -0
  207. data/app/request_schemas/spree/api/v3/file_upload_request_schema.rb +20 -0
  208. data/app/request_schemas/spree/api/v3/media_request_schema.rb +27 -0
  209. data/app/request_schemas/spree/api/v3/price_request_schema.rb +17 -0
  210. data/app/request_schemas/spree/api/v3/stock_level_request_schema.rb +18 -0
  211. data/app/request_schemas/spree/api/v3/store/address_request_schema.rb +29 -0
  212. data/app/request_schemas/spree/api/v3/tax_identifier_request_schema.rb +18 -0
  213. data/app/request_schemas/spree/api/v3/variant_option_request_schema.rb +13 -0
  214. data/app/request_schemas/spree/api/v3/variant_request_schema.rb +52 -0
  215. data/app/serializers/concerns/spree/api/v3/purchase_summary.rb +48 -0
  216. data/app/serializers/spree/api/v3/address_serializer.rb +4 -2
  217. data/app/serializers/spree/api/v3/admin/address_serializer.rb +3 -0
  218. data/app/serializers/spree/api/v3/admin/applied_promotion_serializer.rb +3 -1
  219. data/app/serializers/spree/api/v3/admin/cart_serializer.rb +3 -1
  220. data/app/serializers/spree/api/v3/admin/catalog_order_minimum_serializer.rb +3 -2
  221. data/app/serializers/spree/api/v3/admin/catalog_price_serializer.rb +3 -4
  222. data/app/serializers/spree/api/v3/admin/catalog_price_tier_serializer.rb +4 -3
  223. data/app/serializers/spree/api/v3/admin/channel_serializer.rb +5 -7
  224. data/app/serializers/spree/api/v3/admin/claim_line_item_serializer.rb +7 -0
  225. data/app/serializers/spree/api/v3/admin/claim_serializer.rb +5 -0
  226. data/app/serializers/spree/api/v3/admin/commission_line_serializer.rb +7 -18
  227. data/app/serializers/spree/api/v3/admin/commission_rate_serializer.rb +7 -11
  228. data/app/serializers/spree/api/v3/admin/commission_rule_serializer.rb +0 -2
  229. data/app/serializers/spree/api/v3/admin/country_serializer.rb +4 -0
  230. data/app/serializers/spree/api/v3/admin/customer_serializer.rb +3 -7
  231. data/app/serializers/spree/api/v3/admin/delivery_method_rule_serializer.rb +0 -2
  232. data/app/serializers/spree/api/v3/admin/delivery_method_serializer.rb +14 -10
  233. data/app/serializers/spree/api/v3/admin/delivery_method_service_serializer.rb +9 -1
  234. data/app/serializers/spree/api/v3/admin/delivery_rate_serializer.rb +8 -0
  235. data/app/serializers/spree/api/v3/admin/discount_serializer.rb +3 -1
  236. data/app/serializers/spree/api/v3/admin/exchange_line_item_serializer.rb +5 -0
  237. data/app/serializers/spree/api/v3/admin/exchange_serializer.rb +5 -0
  238. data/app/serializers/spree/api/v3/admin/fee_serializer.rb +3 -1
  239. data/app/serializers/spree/api/v3/admin/file_upload_serializer.rb +11 -0
  240. data/app/serializers/spree/api/v3/admin/file_upload_target_serializer.rb +10 -0
  241. data/app/serializers/spree/api/v3/admin/fulfillment_serializer.rb +13 -11
  242. data/app/serializers/spree/api/v3/admin/gift_card_serializer.rb +3 -3
  243. data/app/serializers/spree/api/v3/admin/import_serializer.rb +2 -2
  244. data/app/serializers/spree/api/v3/admin/integration_serializer.rb +1 -3
  245. data/app/serializers/spree/api/v3/admin/line_item_serializer.rb +12 -12
  246. data/app/serializers/spree/api/v3/admin/locale_serializer.rb +13 -0
  247. data/app/serializers/spree/api/v3/admin/market_serializer.rb +3 -0
  248. data/app/serializers/spree/api/v3/admin/media_serializer.rb +3 -2
  249. data/app/serializers/spree/api/v3/admin/order_group_serializer.rb +2 -0
  250. data/app/serializers/spree/api/v3/admin/order_routing_rule_serializer.rb +1 -3
  251. data/app/serializers/spree/api/v3/admin/order_serializer.rb +33 -33
  252. data/app/serializers/spree/api/v3/admin/payment_method_serializer.rb +1 -4
  253. data/app/serializers/spree/api/v3/admin/payment_serializer.rb +6 -3
  254. data/app/serializers/spree/api/v3/admin/payment_split_serializer.rb +8 -9
  255. data/app/serializers/spree/api/v3/admin/price_adjustment_tier_serializer.rb +3 -6
  256. data/app/serializers/spree/api/v3/admin/price_history_serializer.rb +5 -2
  257. data/app/serializers/spree/api/v3/admin/price_list_serializer.rb +1 -5
  258. data/app/serializers/spree/api/v3/admin/price_rule_serializer.rb +1 -3
  259. data/app/serializers/spree/api/v3/admin/price_serializer.rb +2 -0
  260. data/app/serializers/spree/api/v3/admin/product_serializer.rb +2 -0
  261. data/app/serializers/spree/api/v3/admin/promotion_action_serializer.rb +4 -6
  262. data/app/serializers/spree/api/v3/admin/promotion_rule_serializer.rb +0 -2
  263. data/app/serializers/spree/api/v3/admin/purchase_order_item_serializer.rb +5 -11
  264. data/app/serializers/spree/api/v3/admin/purchase_order_serializer.rb +4 -4
  265. data/app/serializers/spree/api/v3/admin/refund_serializer.rb +7 -3
  266. data/app/serializers/spree/api/v3/admin/reporting_result_serializer.rb +9 -2
  267. data/app/serializers/spree/api/v3/admin/return_line_item_serializer.rb +8 -0
  268. data/app/serializers/spree/api/v3/admin/return_serializer.rb +8 -4
  269. data/app/serializers/spree/api/v3/admin/seller_balance_serializer.rb +15 -10
  270. data/app/serializers/spree/api/v3/admin/seller_payout_serializer.rb +3 -5
  271. data/app/serializers/spree/api/v3/admin/seller_serializer.rb +3 -1
  272. data/app/serializers/spree/api/v3/admin/seller_transfer_serializer.rb +7 -8
  273. data/app/serializers/spree/api/v3/admin/shipping_label_serializer.rb +4 -4
  274. data/app/serializers/spree/api/v3/admin/stock_location_serializer.rb +3 -0
  275. data/app/serializers/spree/api/v3/admin/stock_movement_serializer.rb +2 -6
  276. data/app/serializers/spree/api/v3/admin/store_credit_event_serializer.rb +5 -0
  277. data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +9 -4
  278. data/app/serializers/spree/api/v3/admin/store_serializer.rb +17 -98
  279. data/app/serializers/spree/api/v3/admin/supplier_serializer.rb +1 -1
  280. data/app/serializers/spree/api/v3/admin/tax_exemption_certificate_serializer.rb +3 -1
  281. data/app/serializers/spree/api/v3/admin/tax_line_serializer.rb +4 -2
  282. data/app/serializers/spree/api/v3/admin/tax_rate_serializer.rb +2 -12
  283. data/app/serializers/spree/api/v3/admin/update_status_serializer.rb +28 -0
  284. data/app/serializers/spree/api/v3/admin/variant_serializer.rb +7 -3
  285. data/app/serializers/spree/api/v3/base_serializer.rb +112 -7
  286. data/app/serializers/spree/api/v3/cart_serializer.rb +11 -29
  287. data/app/serializers/spree/api/v3/claim_line_item_serializer.rb +5 -13
  288. data/app/serializers/spree/api/v3/claim_serializer.rb +2 -3
  289. data/app/serializers/spree/api/v3/country_serializer.rb +2 -1
  290. data/app/serializers/spree/api/v3/currency_serializer.rb +9 -1
  291. data/app/serializers/spree/api/v3/customer_serializer.rb +3 -2
  292. data/app/serializers/spree/api/v3/delivery_rate_serializer.rb +8 -12
  293. data/app/serializers/spree/api/v3/delivery_zone_member_serializer.rb +1 -1
  294. data/app/serializers/spree/api/v3/discount_serializer.rb +4 -1
  295. data/app/serializers/spree/api/v3/exchange_line_item_serializer.rb +2 -8
  296. data/app/serializers/spree/api/v3/exchange_serializer.rb +2 -3
  297. data/app/serializers/spree/api/v3/file_upload_serializer.rb +28 -0
  298. data/app/serializers/spree/api/v3/file_upload_target_serializer.rb +17 -0
  299. data/app/serializers/spree/api/v3/gift_card_batch_serializer.rb +2 -4
  300. data/app/serializers/spree/api/v3/gift_card_serializer.rb +4 -2
  301. data/app/serializers/spree/api/v3/import_serializer.rb +0 -12
  302. data/app/serializers/spree/api/v3/line_item_serializer.rb +15 -5
  303. data/app/serializers/spree/api/v3/locale_serializer.rb +3 -1
  304. data/app/serializers/spree/api/v3/market_serializer.rb +5 -5
  305. data/app/serializers/spree/api/v3/media_serializer.rb +7 -2
  306. data/app/serializers/spree/api/v3/order_group_serializer.rb +12 -4
  307. data/app/serializers/spree/api/v3/order_serializer.rb +5 -23
  308. data/app/serializers/spree/api/v3/payment_session_serializer.rb +2 -4
  309. data/app/serializers/spree/api/v3/price_history_serializer.rb +5 -5
  310. data/app/serializers/spree/api/v3/price_serializer.rb +6 -9
  311. data/app/serializers/spree/api/v3/product_filter_price_range_serializer.rb +10 -4
  312. data/app/serializers/spree/api/v3/product_serializer.rb +3 -3
  313. data/app/serializers/spree/api/v3/refund_serializer.rb +8 -5
  314. data/app/serializers/spree/api/v3/return_line_item_serializer.rb +3 -11
  315. data/app/serializers/spree/api/v3/return_serializer.rb +3 -6
  316. data/app/serializers/spree/api/v3/seller/address_serializer.rb +12 -0
  317. data/app/serializers/spree/api/v3/seller/balance_serializer.rb +15 -10
  318. data/app/serializers/spree/api/v3/seller/claim_line_item_serializer.rb +7 -0
  319. data/app/serializers/spree/api/v3/seller/claim_serializer.rb +5 -0
  320. data/app/serializers/spree/api/v3/seller/country_serializer.rb +19 -0
  321. data/app/serializers/spree/api/v3/seller/delivery_method_rule_serializer.rb +1 -3
  322. data/app/serializers/spree/api/v3/seller/delivery_method_serializer.rb +4 -8
  323. data/app/serializers/spree/api/v3/seller/delivery_rate_serializer.rb +9 -8
  324. data/app/serializers/spree/api/v3/seller/exchange_line_item_serializer.rb +5 -0
  325. data/app/serializers/spree/api/v3/seller/exchange_serializer.rb +5 -0
  326. data/app/serializers/spree/api/v3/seller/file_upload_serializer.rb +11 -0
  327. data/app/serializers/spree/api/v3/seller/file_upload_target_serializer.rb +10 -0
  328. data/app/serializers/spree/api/v3/seller/import_serializer.rb +2 -2
  329. data/app/serializers/spree/api/v3/seller/media_serializer.rb +2 -5
  330. data/app/serializers/spree/api/v3/seller/order_line_item_serializer.rb +4 -3
  331. data/app/serializers/spree/api/v3/seller/order_serializer.rb +16 -16
  332. data/app/serializers/spree/api/v3/seller/payment_split_serializer.rb +8 -9
  333. data/app/serializers/spree/api/v3/seller/payout_serializer.rb +2 -4
  334. data/app/serializers/spree/api/v3/seller/price_history_serializer.rb +11 -0
  335. data/app/serializers/spree/api/v3/seller/price_serializer.rb +11 -0
  336. data/app/serializers/spree/api/v3/seller/product_serializer.rb +2 -0
  337. data/app/serializers/spree/api/v3/seller/profile_serializer.rb +7 -7
  338. data/app/serializers/spree/api/v3/seller/return_line_item_serializer.rb +8 -0
  339. data/app/serializers/spree/api/v3/seller/return_serializer.rb +8 -5
  340. data/app/serializers/spree/api/v3/seller/state_serializer.rb +10 -0
  341. data/app/serializers/spree/api/v3/seller/stock_location_serializer.rb +3 -0
  342. data/app/serializers/spree/api/v3/seller/transfer_serializer.rb +6 -7
  343. data/app/serializers/spree/api/v3/seller/variant_serializer.rb +8 -4
  344. data/app/serializers/spree/api/v3/stock_location_serializer.rb +8 -1
  345. data/app/serializers/spree/api/v3/store_credit_event_serializer.rb +1 -3
  346. data/app/serializers/spree/api/v3/store_credit_serializer.rb +3 -5
  347. data/app/serializers/spree/api/v3/tax_line_serializer.rb +2 -4
  348. data/app/serializers/spree/api/v3/variant_serializer.rb +3 -3
  349. data/app/services/spree/api/v3/filter_predicates.rb +29 -0
  350. data/app/services/spree/api/v3/filter_table.rb +337 -0
  351. data/app/services/spree/api/v3/filters_aggregator.rb +2 -2
  352. data/app/services/spree/api/v3/request_schema.rb +430 -0
  353. data/app/services/spree/webhooks/deliver_webhook.rb +7 -3
  354. data/config/initializers/typelizer.rb +1 -1
  355. data/config/routes.rb +5 -3
  356. data/lib/spree/api/configuration.rb +5 -0
  357. data/lib/spree/api/dependencies.rb +7 -6
  358. data/lib/spree/api/middleware/request_size_limit.rb +24 -3
  359. data/lib/spree/api/openapi/filter_documentation.rb +136 -0
  360. data/lib/spree/api/openapi/schema_helper.rb +126 -80
  361. data/lib/spree/api/preference_families.rb +46 -0
  362. data/lib/spree/api/preference_types.rb +138 -0
  363. data/lib/spree/api/typelizer_generation.rb +4 -0
  364. data/lib/tasks/filters.rake +12 -0
  365. data/lib/typelizer/open_enums.rb +10 -0
  366. metadata +60 -10
  367. data/app/controllers/concerns/spree/api/v3/address_params.rb +0 -17
  368. data/app/controllers/concerns/spree/api/v3/direct_uploads.rb +0 -35
  369. data/app/controllers/spree/api/v3/admin/direct_uploads_controller.rb +0 -54
  370. data/app/controllers/spree/api/v3/seller/direct_uploads_controller.rb +0 -21
@@ -0,0 +1,136 @@
1
+ module Spree
2
+ module Api
3
+ module OpenAPI
4
+ # Turns one list endpoint's filter table into what a reader of the API
5
+ # reference sees: `q[search]` and the other scopes as named query
6
+ # parameters, and the attribute filters as a table rendered above the
7
+ # endpoint (Mintlify's `x-mint.content`).
8
+ class FilterDocumentation
9
+ KIND_LABELS = {
10
+ 'text' => 'Text', 'decimal' => 'Amount', 'integer' => 'Number', 'date' => 'Date',
11
+ 'datetime' => 'Date and time', 'id' => 'Prefixed ID', 'type' => 'Type', 'boolean' => 'True or false'
12
+ }.freeze
13
+
14
+ SCOPE_SCHEMAS = {
15
+ 'boolean' => { type: :boolean }, 'integer' => { type: :integer }, 'decimal' => { type: :number }
16
+ }.freeze
17
+
18
+ # Raised when an endpoint offers `q[search]` and nothing says what it matches.
19
+ class MissingSearchDescription < StandardError; end
20
+
21
+ # @param endpoint [Spree::Api::V3::FilterTable::Endpoint]
22
+ # @param querying_path [String] the docs page explaining predicates
23
+ # @param search [String, nil] what `q[search]` matches, for a model
24
+ # whose search is not declared with `search_by`
25
+ # @param scopes [Hash{String => String}] descriptions for other scopes
26
+ def initialize(endpoint, querying_path:, search: nil, scopes: {})
27
+ @endpoint = endpoint
28
+ @table = endpoint.table
29
+ @querying_path = querying_path
30
+ @search = search
31
+ @scopes = scopes.stringify_keys
32
+ end
33
+
34
+ # @return [Array<Hash>] rswag parameter definitions, `q[search]` first
35
+ def parameters
36
+ names = @table.scopes.keys.sort_by { |scope| scope == 'search' ? '' : scope }
37
+ names.map { |scope| scope_parameter(scope, @table.scopes[scope]) }
38
+ end
39
+
40
+ # @return [String] MDX for the endpoint page
41
+ def content
42
+ lines = ['## Filters', '']
43
+ lines << "Filter with `q[<attribute>_<predicate>]=value`, for example `#{example_key}`. Send a list as " \
44
+ "`q[<attribute>_in][]=a&q[<attribute>_in][]=b`. [How filters work](#{@querying_path})."
45
+ lines << ''
46
+ lines.concat(attribute_rows(@table))
47
+
48
+ associations = association_rows
49
+ if associations.any?
50
+ lines << '' << '<Accordion title="Filters through associations">' << ''
51
+ lines << 'Prefix an associated record\'s attribute with the association, as in ' \
52
+ "`q[#{associations.first[0]}id_eq]`. The same predicates apply." << ''
53
+ lines << '| Prefix | Attributes |' << '|---|---|'
54
+ associations.each { |prefix, attributes| lines << "| `#{prefix}` | #{attributes.map { |name| "`#{name}`" }.join(', ')} |" }
55
+ lines << '' << '</Accordion>'
56
+ end
57
+
58
+ if @endpoint.to_h['custom_fields']
59
+ lines << '' << "Also filters and sorts on the store's searchable custom fields, as " \
60
+ '`q[cf_<namespace>_<key>_<predicate>]` and `sort=cf_<namespace>_<key>`.'
61
+ end
62
+
63
+ sortable = @endpoint.sortable - @table.aliased_attributes
64
+ if sortable.any?
65
+ lines << '' << "**Sort by:** #{sortable.map { |field| "`#{field}`" }.join(', ')}. " \
66
+ 'Prefix a field with `-` to sort descending.'
67
+ end
68
+
69
+ lines.join("\n")
70
+ end
71
+
72
+ private
73
+
74
+ def scope_parameter(scope, type)
75
+ list = type.is_a?(Hash) || type.is_a?(Array)
76
+ {
77
+ name: list ? "q[#{scope}][]" : "q[#{scope}]",
78
+ in: :query,
79
+ required: false,
80
+ schema: scope_schema(type),
81
+ description: scope == 'search' ? search_description : @scopes.fetch(scope) { default_scope_description(scope, type) }
82
+ }
83
+ end
84
+
85
+ def scope_schema(type)
86
+ case type
87
+ when Hash then { type: :array, items: scope_schema(type['list']) }
88
+ when Array then { type: :array, items: scope_schema(type.first), minItems: type.size, maxItems: type.size }
89
+ else SCOPE_SCHEMAS.fetch(type, { type: :string })
90
+ end
91
+ end
92
+
93
+ def search_description
94
+ return @search if @search
95
+
96
+ attributes = @table.model.search_attributes
97
+ raise MissingSearchDescription, "#{@table.model.name}.search has no description; pass `search:` to filter_parameters_for" if attributes.empty?
98
+
99
+ "Matches any of #{attributes.map { |path| path.tr('_', ' ') }.to_sentence} (partial, case-insensitive)."
100
+ end
101
+
102
+ def default_scope_description(scope, type)
103
+ type == 'boolean' ? "Set to `true` to apply `#{scope}`." : "Applies `#{scope}` to the given value."
104
+ end
105
+
106
+ def attribute_rows(table)
107
+ rows = ['| Attribute | Kind | Predicates |', '|---|---|---|']
108
+ table.attributes.except(*table.aliased_attributes).each do |attribute, kind|
109
+ label = kind.is_a?(Hash) ? "One of #{kind['enum'].map { |value| "`#{value}`" }.join(', ')}" : KIND_LABELS.fetch(kind)
110
+ predicates = Spree::Api::V3::FilterPredicates::BY_KIND.fetch(kind.is_a?(Hash) ? 'enum' : kind)
111
+ rows << "| `#{attribute}` | #{label} | #{predicates.map { |predicate| "`#{predicate}`" }.join(' ')} |"
112
+ end
113
+ rows
114
+ end
115
+
116
+ def association_rows
117
+ visible = ->(table) { table.attributes.keys - table.aliased_attributes }
118
+ @table.associations.flat_map do |name, associated|
119
+ nested = associated.associations.reject { |_, table| table.model == @table.model }
120
+ [["#{name}_", visible.(associated)]] + nested.map { |hop, table| ["#{name}_#{hop}_", visible.(table)] }
121
+ end
122
+ end
123
+
124
+ def example_key
125
+ texts = @table.attributes.select { |_, value| value == 'text' }
126
+ attribute = (%w[number name code email] & texts.keys).first || texts.keys.first
127
+ kind = 'text' if attribute
128
+ attribute, kind = @table.attributes.first if attribute.nil?
129
+ return 'q[id_eq]=…' if attribute.nil?
130
+
131
+ kind == 'text' ? "q[#{attribute}_cont]=…" : "q[#{attribute}_eq]=…"
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'spree/api/preference_families'
4
+
3
5
  module Spree
4
6
  module Api
5
7
  module OpenAPI
@@ -33,6 +35,29 @@ module Spree
33
35
  end
34
36
 
35
37
  # Common schemas that are not from serializers
38
+ # The nested calculator of a promotion action and a delivery method,
39
+ # keyed by the preference family its `preferences` belong to.
40
+ CALCULATOR_SCHEMAS = {
41
+ 'PromotionCalculator' => [:PromotionActionCalculator, {
42
+ type: :object,
43
+ description: "The action's nested calculator (when the action carries one — null for actions like `free_shipping`)",
44
+ properties: {
45
+ type: { type: :string, example: 'flat_rate', description: 'Wire shorthand for the calculator subclass' },
46
+ preferences: { '$ref' => '#/components/schemas/PromotionCalculatorPreferences' }
47
+ },
48
+ required: %w[type preferences]
49
+ }],
50
+ 'DeliveryCalculator' => [:DeliveryMethodCalculator, {
51
+ type: :object,
52
+ description: 'How the delivery method prices a shipment',
53
+ properties: {
54
+ type: { type: :string, example: 'flat_rate', description: 'Wire shorthand for the calculator subclass' },
55
+ preferences: { '$ref' => '#/components/schemas/DeliveryCalculatorPreferences' }
56
+ },
57
+ required: %w[type preferences]
58
+ }]
59
+ }.freeze
60
+
36
61
  def common_schemas
37
62
  {
38
63
  PaginationMeta: {
@@ -162,26 +187,13 @@ module Spree
162
187
  },
163
188
  required: %w[id name code]
164
189
  },
165
- PreferenceField: {
190
+ PreferenceSchema: {
166
191
  type: :object,
167
- description: 'A single configurable preference on a payment method, promotion rule/action, or calculator. The frontend uses `type` + `default` to render a sensible input.',
168
- properties: {
169
- key: { type: :string, example: 'amount_min' },
170
- type: { type: :string, example: 'decimal', description: 'string | text | password | integer | decimal | boolean | array | hash' },
171
- default: { description: 'Default value (any JSON type), null when there is no default', nullable: true }
172
- },
173
- required: %w[key type]
174
- },
175
- PromotionActionCalculator: {
176
- type: :object,
177
- description: "The action's nested calculator (when the action carries one — null for actions like `free_shipping`)",
178
- properties: {
179
- type: { type: :string, example: 'flat_rate', description: 'Wire shorthand for the calculator subclass' },
180
- label: { type: :string, example: 'Flat Rate' },
181
- preferences: { type: :object, additionalProperties: true },
182
- preference_schema: { type: :array, items: { '$ref' => '#/components/schemas/PreferenceField' } }
183
- },
184
- required: %w[type label preferences preference_schema]
192
+ description: 'The JSON Schema (draft 2020-12) of a type\'s `preferences`: one property per setting, ' \
193
+ 'with `format` (`money`, `currency`, `iso-country`, `prefixed-id`, …), `enum`, `default`, ' \
194
+ '`x-spree-secret` for a secret and `x-spree-prefix` for an id list. Unknown keys are refused.',
195
+ additionalProperties: true,
196
+ example: { type: 'object', properties: { amount: { type: 'string', format: 'money', default: '0.0' } }, additionalProperties: false }
185
197
  },
186
198
  PromotionActionLineItem: {
187
199
  type: :object,
@@ -243,7 +255,7 @@ module Spree
243
255
  Rails.logger.warn "Failed to load Typelizer admin schemas: #{e.message}"
244
256
  end
245
257
 
246
- schemas
258
+ add_preference_schemas(schemas, Spree::Api::PreferenceFamilies::REGISTRIES.keys)
247
259
  end
248
260
 
249
261
  # Get all seller schemas (Typelizer + common).
@@ -280,7 +292,7 @@ module Spree
280
292
  Rails.logger.warn "Failed to load Typelizer seller schemas: #{e.message}"
281
293
  end
282
294
 
283
- schemas
295
+ add_preference_schemas(schemas, Spree::Api::PreferenceFamilies::SELLER)
284
296
  end
285
297
 
286
298
  # Hand-written shapes the seller branch answers with that no serializer
@@ -353,45 +365,6 @@ module Spree
353
365
  },
354
366
  required: %w[status progress requirements]
355
367
  },
356
- SellerCountry: {
357
- type: :object,
358
- description: "A country as the panel's address forms need it",
359
- properties: {
360
- iso: { type: :string, example: 'US' },
361
- iso3: { type: :string, example: 'USA' },
362
- name: { type: :string, example: 'United States' },
363
- states_required: { type: :boolean, example: true },
364
- zipcode_required: { type: :boolean, example: true },
365
- states: {
366
- type: :array,
367
- items: {
368
- type: :object,
369
- properties: {
370
- abbr: { type: :string, example: 'CA' },
371
- name: { type: :string, example: 'California' }
372
- },
373
- required: %w[abbr name]
374
- }
375
- }
376
- },
377
- required: %w[iso iso3 name]
378
- },
379
- DirectUploadResponse: {
380
- type: :object,
381
- description: 'A presigned upload target. PUT the file to `direct_upload.url` with the given headers, then send `signed_id` as the submission\'s `file`.',
382
- properties: {
383
- signed_id: { type: :string, description: 'Send this as a submission\'s `file`', example: 'eyJfcmFpbHMiOnsibWVzc2FnZSI6...' },
384
- direct_upload: {
385
- type: :object,
386
- properties: {
387
- url: { type: :string, example: 'https://storage.example.com/uploads/abc123' },
388
- headers: { type: :object, additionalProperties: { type: :string } }
389
- },
390
- required: %w[url headers]
391
- }
392
- },
393
- required: %w[signed_id direct_upload]
394
- },
395
368
  AuthProvidersResponse: {
396
369
  type: :object,
397
370
  description: 'Sign-in methods this marketplace offers sellers, for the login page',
@@ -415,17 +388,45 @@ module Spree
415
388
  s[:'x-typelizer'] = true
416
389
  strip_null_from_enums(s)
417
390
  end
391
+ patch_typescript_only_types(schemas, writer_name)
418
392
  patch_cart_schema(schemas)
419
393
  patch_fulfillment_schema(schemas)
420
394
  patch_admin_user_schema(schemas)
421
395
  patch_promotion_rule_schema(schemas)
422
396
  patch_promotion_action_schema(schemas)
423
- patch_price_rule_schema(schemas)
424
397
  patch_import_schema(schemas)
425
398
  schemas
426
399
  end
427
400
  end
428
401
 
402
+ # Typelizer publishes a type only TypeScript can express (`Array<{ … }>`,
403
+ # `any`) as a plain object. Give each such property the shape the API
404
+ # sends — an array, or any value — so responses validate against it;
405
+ # the patches below then point some arrays at a named item component.
406
+ def patch_typescript_only_types(schemas, writer_name)
407
+ Typelizer.interfaces(writer_name: writer_name).each do |interface|
408
+ properties = (schemas[interface.name] || schemas[interface.name.to_sym])&.dig(:properties)
409
+ next unless properties
410
+
411
+ interface.properties.each do |property|
412
+ key = [property.name.to_s, property.name.to_sym].find { |name| properties.key?(name) }
413
+ schema = key && !property.type.is_a?(Array) && typescript_only_type_schema(property.type.to_s)
414
+ next unless schema
415
+
416
+ schema[:nullable] = true if property.nullable || property.type.to_s.end_with?('| null')
417
+ properties[key] = schema.merge(properties[key].slice(:description))
418
+ end
419
+ end
420
+ end
421
+
422
+ def typescript_only_type_schema(type)
423
+ case type.delete_suffix(' | null')
424
+ when /\AArray<(string|number|boolean)>\z/ then { type: :array, items: { type: Regexp.last_match(1).to_sym } }
425
+ when /\AArray</ then { type: :array, items: { type: :object } }
426
+ when 'any', 'unknown' then {}
427
+ end
428
+ end
429
+
429
430
  # Same Array<{...}> issue as cart/fulfillment — patch Import#schema_fields
430
431
  # to reference the ImportSchemaField component schema.
431
432
  def patch_import_schema(schemas)
@@ -504,22 +505,12 @@ module Spree
504
505
  return unless rule
505
506
 
506
507
  patch_id_arrays(rule, %w[product_ids category_ids customer_ids option_value_ids])
507
- patch_preference_schema(rule)
508
- end
509
-
510
- def patch_price_rule_schema(schemas)
511
- rule = schemas['PriceRule'] || schemas[:PriceRule]
512
- return unless rule
513
-
514
- patch_preference_schema(rule)
515
508
  end
516
509
 
517
510
  def patch_promotion_action_schema(schemas)
518
511
  action = schemas['PromotionAction'] || schemas[:PromotionAction]
519
512
  return unless action
520
513
 
521
- patch_preference_schema(action)
522
-
523
514
  props = action[:properties]
524
515
  return unless props
525
516
 
@@ -557,17 +548,72 @@ module Spree
557
548
  end
558
549
  end
559
550
 
560
- def patch_preference_schema(schema)
561
- props = schema[:properties]
562
- return unless props
551
+ # One component per subtype Spree ships (`PromotionRuleItemTotalPreferences`)
552
+ # and one per family (`PromotionRulePreferences`) that the family's
553
+ # resource points its `preferences` at. A type an extension registers
554
+ # matches the final plain object; its shape is the `schema` in the
555
+ # family's `/types` response.
556
+ #
557
+ # @param schemas [Hash] components, changed in place
558
+ # @param families [Array<String>]
559
+ # @return [Hash] the components
560
+ def add_preference_schemas(schemas, families)
561
+ Spree::Api::PreferenceFamilies.schemas(families).each do |family, members|
562
+ refs = members.to_h do |type, schema|
563
+ name = Spree::Api::PreferenceFamilies.member_name(family, type)
564
+ schemas[name] = openapi_schema(schema)
565
+ [type, "#/components/schemas/#{name}"]
566
+ end
563
567
 
564
- key = props.key?('preference_schema') ? 'preference_schema' : :preference_schema
565
- return unless props[key]
568
+ schemas["#{family}Preferences"] = {
569
+ description: "Settings of a #{family.titleize.downcase}, shaped by its `type` (see `x-spree-preferences-by-type`). " \
570
+ 'A type an extension registers is a plain object, described by the `schema` its `/types` entry carries.',
571
+ anyOf: refs.values.map { |ref| { '$ref' => ref } } + [{ type: :object, additionalProperties: true }],
572
+ 'x-spree-preferences-by-type' => refs
573
+ }
566
574
 
567
- props[key] = {
568
- type: :array,
569
- items: { '$ref' => '#/components/schemas/PreferenceField' }
570
- }
575
+ resource = schemas[family] || schemas[family.to_sym]
576
+ props = resource&.dig(:properties)
577
+ key = props&.key?('preferences') ? 'preferences' : :preferences
578
+ props[key] = { '$ref' => "#/components/schemas/#{family}Preferences" } if props&.key?(key)
579
+ end
580
+
581
+ # A calculator names its family's union, so it is published only
582
+ # alongside that union.
583
+ CALCULATOR_SCHEMAS.slice(*families).each_value { |name, schema| schemas[name] = schema }
584
+ patch_delivery_method_calculator(schemas)
585
+ schemas
586
+ end
587
+
588
+ # The generated JSON Schema is draft 2020-12; OpenAPI 3.0 spells a
589
+ # nullable type as `nullable: true` and has no `propertyNames`.
590
+ def openapi_schema(schema)
591
+ case schema
592
+ when Hash
593
+ converted = schema.each_with_object({}) do |(key, value), result|
594
+ next if key == 'propertyNames'
595
+
596
+ result[key] = %w[enum default required].include?(key) ? value : openapi_schema(value)
597
+ end
598
+ if schema['type'].is_a?(Array)
599
+ converted['type'] = (schema['type'] - ['null']).first
600
+ converted['nullable'] = true if schema['type'].include?('null')
601
+ end
602
+ converted['enum'] = converted['enum'].compact if converted['enum']
603
+ converted['x-spree-property-names'] = openapi_schema(schema['propertyNames']) if schema['propertyNames']
604
+ converted
605
+ when Array then schema.map { |item| openapi_schema(item) }
606
+ else schema
607
+ end
608
+ end
609
+
610
+ def patch_delivery_method_calculator(schemas)
611
+ method = schemas['DeliveryMethod'] || schemas[:DeliveryMethod]
612
+ props = method&.dig(:properties)
613
+ return unless props
614
+
615
+ key = props.key?('calculator') ? 'calculator' : :calculator
616
+ props[key] = { allOf: [{ '$ref' => '#/components/schemas/DeliveryMethodCalculator' }], nullable: true } if props[key]
571
617
  end
572
618
 
573
619
  # Typelizer cannot represent Array<{...}> inline object types in OpenAPI,
@@ -0,0 +1,46 @@
1
+ module Spree
2
+ module Api
3
+ # The configurable families whose subtypes each declare their own
4
+ # `preferences`: the subtypes Spree ships, keyed by their wire `type`, with
5
+ # the JSON Schema of each one's preferences. The OpenAPI components and the
6
+ # SDK's preference types are both generated from it, so the two cannot
7
+ # describe different shapes.
8
+ module PreferenceFamilies
9
+ # Family name => its registry. The name is the resource the family's
10
+ # rows are serialized as, or, for calculators, the calculator's role.
11
+ REGISTRIES = {
12
+ 'CommissionRule' => -> { Spree.commission_rules },
13
+ 'DeliveryCalculator' => -> { Spree::DeliveryMethod.calculators },
14
+ 'DeliveryMethodRule' => -> { Spree.delivery_method_rules },
15
+ 'Integration' => -> { Spree::Integration.registered_classes },
16
+ 'OrderRoutingRule' => -> { Spree.order_routing.rules },
17
+ 'PaymentMethod' => -> { Spree::PaymentMethod.providers },
18
+ 'PriceRule' => -> { Array(Spree.pricing&.rules) },
19
+ 'PromotionCalculator' => lambda {
20
+ Spree.calculators.promotion_actions_create_adjustments + Spree.calculators.promotion_actions_create_item_adjustments
21
+ },
22
+ 'PromotionRule' => -> { Spree.promotions.rules },
23
+ 'SellerRequirement' => -> { Spree.seller_requirements }
24
+ }.freeze
25
+
26
+ # The families a seller reads and writes.
27
+ SELLER = %w[DeliveryCalculator DeliveryMethodRule].freeze
28
+
29
+ # @param names [Array<String>] family names, every family by default
30
+ # @return [Hash{String => Hash{String => Hash}}] family => { type => JSON Schema of its preferences }
31
+ def self.schemas(names = REGISTRIES.keys)
32
+ names.to_h do |name|
33
+ classes = REGISTRIES.fetch(name).call.map { |entry| entry.is_a?(Class) ? entry : entry.to_s.constantize }
34
+ [name, classes.uniq.sort_by(&:api_type).to_h { |klass| [klass.api_type, klass.preference_json_schema] }]
35
+ end
36
+ end
37
+
38
+ # @param family [String] e.g. `PromotionRule`
39
+ # @param type [String] e.g. `item_total`
40
+ # @return [String] e.g. `PromotionRuleItemTotalPreferences`
41
+ def self.member_name(family, type)
42
+ "#{family}#{type.camelize}Preferences"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,138 @@
1
+ require 'spree/api/preference_families'
2
+
3
+ module Spree
4
+ module Api
5
+ # Renders each configurable family's preference schemas
6
+ # ({Spree::Api::PreferenceFamilies}) into the TypeScript types the Admin
7
+ # and Seller SDKs export: one interface per subtype Spree ships, a map from
8
+ # `type` to it, and the family's resource with `preferences` typed by its
9
+ # `type`.
10
+ #
11
+ # The map is an interface, so an extension types its own subtypes by
12
+ # declaration merging. A union with a catch-all member for unknown types
13
+ # would stop TypeScript from narrowing on `type` at all.
14
+ #
15
+ # Run by `rake typelizer:generate`; `preference_types_spec.rb` fails when
16
+ # the committed files no longer match the declarations.
17
+ class PreferenceTypes
18
+ HEADER = "// This file is auto-generated from Spree's preference declarations by `rake typelizer:generate`. Do not edit directly.\n".freeze
19
+
20
+ FILES = {
21
+ 'packages/admin-sdk/src/types/preferences.ts' => PreferenceFamilies::REGISTRIES.keys,
22
+ 'packages/seller-sdk/src/types/preferences.ts' => PreferenceFamilies::SELLER
23
+ }.freeze
24
+
25
+ # Families serialized as rows of their own; calculators are nested in
26
+ # their owner, whose serializer types `calculator` with `Typed<Family>`.
27
+ RESOURCES = (PreferenceFamilies::REGISTRIES.keys - %w[DeliveryCalculator PromotionCalculator]).freeze
28
+
29
+ # @return [Hash{String => String}] file contents keyed by path under the monorepo root
30
+ def render
31
+ FILES.transform_values { |families| source(PreferenceFamilies.schemas(families)) }
32
+ end
33
+
34
+ # Typelizer imports a type a serializer names (`TypedDeliveryCalculator`)
35
+ # from its generated index, so that index re-exports these types too.
36
+ INDEX_EXPORT = "export type * from '../preferences'\n".freeze
37
+
38
+ # @param root [Pathname] the monorepo root
39
+ # @return [void]
40
+ def write!(root)
41
+ render.each do |path, content|
42
+ File.write(root.join(path), content)
43
+
44
+ index = root.join(path).dirname.join('generated/index.ts')
45
+ File.write(index, index.read + INDEX_EXPORT) if index.exist? && !index.read.include?(INDEX_EXPORT)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ NARROWED = <<~TS.freeze
52
+ /** `Resource` with its `preferences` typed by its `type`, one member per entry of `Map`. */
53
+ type Narrowed<Resource, Map> = {
54
+ [Type in keyof Map]: Omit<Resource, 'type' | 'preferences'> & {
55
+ type: Type
56
+ preferences: Map[Type]
57
+ }
58
+ }[keyof Map]
59
+ TS
60
+
61
+ def source(families)
62
+ resources = families.keys & RESOURCES
63
+ imports = resources.map { |family| "import type #{family} from './generated/#{family}'\n" }.join
64
+ parts = families.flat_map { |family, members| family_parts(family, members, resources.include?(family)) }
65
+ parts.unshift(NARROWED) if resources.any?
66
+ parts.unshift(imports) if imports.present?
67
+
68
+ HEADER + parts.join("\n")
69
+ end
70
+
71
+ def family_parts(family, members, resource)
72
+ label = family.titleize.downcase
73
+ parts = members.map do |type, schema|
74
+ name = PreferenceFamilies.member_name(family, type)
75
+ properties = schema['properties']
76
+ next "/** Settings of the `#{type}` #{label}: none. */\nexport type #{name} = Record<string, never>\n" if properties.empty?
77
+
78
+ fields = properties.map { |key, property| "#{field_comment(property)} #{key}: #{ts_type(property)}\n" }.join
79
+ # A type, not an interface, so it stays assignable to
80
+ # `Record<string, unknown>`, which generic preference forms take.
81
+ "/** Settings of the `#{type}` #{label}. */\nexport type #{name} = {\n#{fields}}\n"
82
+ end
83
+
84
+ map = "#{family}PreferencesMap"
85
+ entries = members.keys.map { |type| " #{type}: #{PreferenceFamilies.member_name(family, type)}\n" }.join
86
+ ignore = members.empty? ? "// biome-ignore lint/suspicious/noEmptyInterface: extensions add their types by declaration merging\n" : ''
87
+ parts << "/**\n * The settings of each #{label} type Spree ships, by `type`. An interface,\n" \
88
+ " * so an extension types its own by declaration merging.\n */\n" \
89
+ "#{ignore}export interface #{map} {#{"\n#{entries}" if entries.present?}}\n"
90
+ article = label.match?(/\A[aeiou]/) ? 'An' : 'A'
91
+ parts << if resource
92
+ "/** #{article} #{label} whose `preferences` are typed by its `type`. */\nexport type Typed#{family} = Narrowed<#{family}, #{map}>\n"
93
+ else
94
+ # A calculator is nested in its owner, which types its
95
+ # `calculator` field with this union.
96
+ "/** #{article} #{label}, its `preferences` typed by its `type`. */\n" \
97
+ "export type Typed#{family} = {\n [Type in keyof #{map}]: {\n type: Type\n preferences: #{map}[Type]\n }\n}[keyof #{map}]\n"
98
+ end
99
+ parts
100
+ end
101
+
102
+ def field_comment(property)
103
+ note = if property['x-spree-secret'] then 'Masked when read (`••••1234`); send it back unchanged to keep it, `null` to clear it.'
104
+ elsif property.dig('items', 'x-spree-prefix') then "Prefixed `#{property.dig('items', 'x-spree-prefix')}_` ids."
105
+ elsif property['format'] == 'money' then 'An amount, as an exact decimal string.'
106
+ elsif property['format'] then "Format: `#{property['format']}`."
107
+ end
108
+ note ? " /** #{note} */\n" : ''
109
+ end
110
+
111
+ def ts_type(schema)
112
+ types = Array(schema['type'])
113
+ base = base_type(schema, (types - ['null']).first)
114
+ types.include?('null') ? "#{base} | null" : base
115
+ end
116
+
117
+ def base_type(schema, type)
118
+ return schema['enum'].compact.map { |value| value.is_a?(String) ? "'#{value}'" : value.to_s }.join(' | ') if schema['enum']
119
+
120
+ case type
121
+ when 'string' then 'string'
122
+ when 'integer', 'number' then 'number'
123
+ when 'boolean' then 'boolean'
124
+ when 'array' then schema['items'] ? "Array<#{ts_type(schema['items'])}>" : 'Array<unknown>'
125
+ when 'object'
126
+ if schema['properties']
127
+ "{ #{schema['properties'].map { |key, property| "#{key}: #{ts_type(property)}" }.join('; ')} }"
128
+ elsif schema['additionalProperties'].is_a?(Hash)
129
+ "Record<string, #{ts_type(schema['additionalProperties'])}>"
130
+ else
131
+ 'Record<string, unknown>'
132
+ end
133
+ else 'unknown'
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -29,6 +29,10 @@ module Spree
29
29
  # The typed webhook events in @spree/sdk, from the event catalog.
30
30
  require Spree::Api::Engine.root.join('lib/spree/api/webhook_event_types').to_s
31
31
  Spree::Api::WebhookEventTypes.new.write!(api_root.join('../..'))
32
+
33
+ # Each configurable family's preferences, typed by `type`, in the Admin and Seller SDKs.
34
+ require Spree::Api::Engine.root.join('lib/spree/api/preference_types').to_s
35
+ Spree::Api::PreferenceTypes.new.write!(api_root.join('../..'))
32
36
  end
33
37
  end
34
38
  end
@@ -0,0 +1,12 @@
1
+ namespace :spree do
2
+ namespace :api do
3
+ desc 'Print the filter tables of every list endpoint, as JSON, for `spree filters types`'
4
+ task filter_tables: :environment do
5
+ Rails.application.eager_load!
6
+ apis = %w[store admin seller].index_with { |api| Spree::Api::V3::FilterTable.spec_for(api) }
7
+
8
+ # One line: the CLI drops boot noise line by line.
9
+ puts apis.to_json
10
+ end
11
+ end
12
+ end
@@ -7,10 +7,19 @@ module Typelizer
7
7
  # open, and the OpenAPI property becomes an open enum, so a client generated
8
8
  # from either still accepts an added value while every built-in value stays
9
9
  # documented and autocompletable.
10
+ #
11
+ # ISO lists are the exception: a named type several serializers and the
12
+ # request types share, but closed, since nothing outside the standard can
13
+ # add a country or a currency, and markets only accept the locales
14
+ # `Spree::Locales::ALL` lists.
10
15
  module OpenEnums
16
+ CLOSED_TYPE_NAMES = %w[CountryCode CurrencyCode LocaleCode].freeze
17
+
11
18
  module Property
12
19
  def enum_definition(...)
13
20
  definition = super
21
+ return definition if CLOSED_TYPE_NAMES.include?(enum_type_name)
22
+
14
23
  definition && "#{definition} | (string & {})"
15
24
  end
16
25
  end
@@ -20,6 +29,7 @@ module Typelizer
20
29
 
21
30
  def inline_schema(definition, property, openapi_version:)
22
31
  return super unless property.enum_type_name && property.enum.is_a?(Array) && !property.multi
32
+ return super if CLOSED_TYPE_NAMES.include?(property.enum_type_name)
23
33
 
24
34
  open = { anyOf: [{ type: :string, enum: property.enum.compact }, { type: :string }] }
25
35
  apply_nullable(open, property, openapi_version: openapi_version)