repull 0.2.10 → 0.2.14
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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/repull/api/airbnb_api.rb +332 -99
- data/lib/repull/api/atlas_api.rb +5 -5
- data/lib/repull/api/availability_api.rb +145 -3
- data/lib/repull/api/billing_api.rb +7 -7
- data/lib/repull/api/booking_com_api.rb +210 -54
- data/lib/repull/api/connect_api.rb +761 -8
- data/lib/repull/api/conversations_api.rb +92 -11
- data/lib/repull/api/guests_api.rb +82 -7
- data/lib/repull/api/health_api.rb +318 -0
- data/lib/repull/api/kv_api.rb +1 -1
- data/lib/repull/api/listings_api.rb +303 -24
- data/lib/repull/api/markets_api.rb +3 -3
- data/lib/repull/api/plumguide_api.rb +19 -11
- data/lib/repull/api/pricing_api.rb +15 -15
- data/lib/repull/api/properties_api.rb +12 -9
- data/lib/repull/api/quotes_api.rb +109 -0
- data/lib/repull/api/reservations_api.rb +166 -7
- data/lib/repull/api/reviews_api.rb +81 -7
- data/lib/repull/api/schema_api.rb +1 -1
- data/lib/repull/api/system_api.rb +1 -1
- data/lib/repull/api/vrbo_api.rb +10 -10
- data/lib/repull/api/webhooks_api.rb +11 -9
- data/lib/repull/api_client.rb +1 -1
- data/lib/repull/api_error.rb +1 -1
- data/lib/repull/api_model_base.rb +1 -1
- data/lib/repull/configuration.rb +1 -1
- data/lib/repull/models/account_created_event.rb +1 -1
- data/lib/repull/models/account_created_payload.rb +1 -1
- data/lib/repull/models/account_disconnected_event.rb +1 -1
- data/lib/repull/models/account_disconnected_payload.rb +1 -1
- data/lib/repull/models/acknowledge_booking_reservations_request.rb +1 -1
- data/lib/repull/models/ai_operation.rb +1 -1
- data/lib/repull/models/ai_operation_completed_event.rb +1 -1
- data/lib/repull/models/ai_operation_completed_payload.rb +1 -1
- data/lib/repull/models/ai_operation_failed_event.rb +1 -1
- data/lib/repull/models/ai_operation_failed_payload.rb +1 -1
- data/lib/repull/models/ai_operation_failed_payload_error.rb +1 -1
- data/lib/repull/models/airbnb_alteration.rb +154 -10
- data/lib/repull/models/airbnb_amenity.rb +1 -1
- data/lib/repull/models/airbnb_availability_write_request.rb +20 -1
- data/lib/repull/models/airbnb_calendar_operation.rb +97 -8
- data/lib/repull/models/airbnb_connection.rb +4 -4
- data/lib/repull/models/airbnb_connection_accessibility_amenities_inner.rb +1 -1
- data/lib/repull/models/airbnb_connection_amenities_inner.rb +1 -1
- data/lib/repull/models/airbnb_connection_host.rb +1 -1
- data/lib/repull/models/airbnb_connection_response.rb +1 -1
- data/lib/repull/models/airbnb_connection_summary.rb +1 -1
- data/lib/repull/models/airbnb_data_freshness.rb +3 -3
- data/lib/repull/models/airbnb_listing.rb +2 -2
- data/lib/repull/models/airbnb_listing_action_request.rb +1 -1
- data/lib/repull/models/airbnb_listing_list_response.rb +2 -2
- data/lib/repull/models/airbnb_pricing_write_request.rb +54 -3
- data/lib/repull/models/airbnb_pricing_write_request_records_inner.rb +236 -0
- data/lib/repull/models/airbnb_reservation.rb +1 -1
- data/lib/repull/models/airbnb_reservation_list_response.rb +2 -2
- data/lib/repull/models/airbnb_review.rb +1 -1
- data/lib/repull/models/airbnb_review_list_response.rb +1 -1
- data/lib/repull/models/airbnb_thread.rb +1 -1
- data/lib/repull/models/airbnb_thread_list_response.rb +1 -1
- data/lib/repull/models/airbnb_transaction.rb +502 -0
- data/lib/repull/models/airbnb_transaction_guest_breakdown.rb +171 -0
- data/lib/repull/models/airbnb_transaction_host_breakdown.rb +246 -0
- data/lib/repull/models/airbnb_transaction_payout.rb +175 -0
- data/lib/repull/models/{booking_conversation_list_response.rb → alteration_change.rb} +22 -19
- data/lib/repull/models/alteration_webhook_object.rb +296 -0
- data/lib/repull/models/availability_batch_write_request.rb +296 -0
- data/lib/repull/models/availability_write_request.rb +259 -0
- data/lib/repull/models/availability_write_result.rb +178 -0
- data/lib/repull/models/availability_write_result_synced.rb +176 -0
- data/lib/repull/models/availability_write_settings.rb +215 -0
- data/lib/repull/models/booking_availability_state_response.rb +149 -0
- data/lib/repull/models/booking_availability_update.rb +1 -1
- data/lib/repull/models/booking_availability_update_request.rb +1 -1
- data/lib/repull/models/booking_availability_update_request_property_id.rb +1 -1
- data/lib/repull/models/booking_availability_update_request_updates_inner.rb +1 -1
- data/lib/repull/models/booking_connect_listing_option.rb +1 -1
- data/lib/repull/models/booking_connect_room.rb +1 -1
- data/lib/repull/models/booking_connect_rooms_response.rb +39 -5
- data/lib/repull/models/booking_conversation.rb +1 -1
- data/lib/repull/models/booking_pricing_rate_update.rb +1 -1
- data/lib/repull/models/booking_pricing_rate_update_date_range.rb +1 -1
- data/lib/repull/models/booking_pricing_rate_update_restrictions.rb +1 -1
- data/lib/repull/models/booking_pricing_response.rb +1 -1
- data/lib/repull/models/booking_pricing_update_request.rb +1 -1
- data/lib/repull/models/booking_pricing_update_response.rb +1 -1
- data/lib/repull/models/booking_property.rb +1 -1
- data/lib/repull/models/booking_reservation.rb +277 -0
- data/lib/repull/models/booking_reservation_room.rb +271 -0
- data/lib/repull/models/booking_room_mapping.rb +18 -8
- data/lib/repull/models/booking_rooms_rates_response.rb +170 -0
- data/lib/repull/models/booking_rooms_rates_response_rooms_inner.rb +170 -0
- data/lib/repull/models/booking_rooms_rates_response_rooms_inner_rates_inner.rb +212 -0
- data/lib/repull/models/booking_setup_request.rb +1 -1
- data/lib/repull/models/booking_verify_hotel_request.rb +1 -1
- data/lib/repull/models/booking_verify_hotel_response.rb +40 -6
- data/lib/repull/models/bulk_pricing_failure.rb +1 -1
- data/lib/repull/models/bulk_pricing_item.rb +1 -1
- data/lib/repull/models/bulk_pricing_request.rb +1 -1
- data/lib/repull/models/bulk_pricing_response.rb +1 -1
- data/lib/repull/models/calendar_day.rb +1 -1
- data/lib/repull/models/calendar_response.rb +1 -1
- data/lib/repull/models/calendar_updated_event.rb +1 -1
- data/lib/repull/models/calendar_updated_payload.rb +1 -1
- data/lib/repull/models/calendar_updated_payload_range.rb +1 -1
- data/lib/repull/models/clear_kv200_response.rb +1 -1
- data/lib/repull/models/connect_host.rb +1 -1
- data/lib/repull/models/connect_provider.rb +1 -1
- data/lib/repull/models/connect_provider_list_response.rb +1 -1
- data/lib/repull/models/connect_session.rb +1 -1
- data/lib/repull/models/connect_status.rb +18 -6
- data/lib/repull/models/connect_status_accounts_inner.rb +188 -0
- data/lib/repull/models/connection.rb +1 -1
- data/lib/repull/models/connection_list_response.rb +1 -1
- data/lib/repull/models/conversation.rb +13 -2
- data/lib/repull/models/conversation_detail.rb +13 -2
- data/lib/repull/models/conversation_guest.rb +1 -1
- data/lib/repull/models/conversation_guest_contact.rb +1 -1
- data/lib/repull/models/conversation_host.rb +1 -1
- data/lib/repull/models/conversation_list_response.rb +1 -1
- data/lib/repull/models/conversation_message_attachment.rb +1 -1
- data/lib/repull/models/create_airbnb_alteration_request.rb +1 -1
- data/lib/repull/models/create_airbnb_offer_request.rb +1 -1
- data/lib/repull/models/create_billing_checkout_request.rb +1 -1
- data/lib/repull/models/create_booking_webhook_request.rb +1 -1
- data/lib/repull/models/create_connect_session_request.rb +1 -1
- data/lib/repull/models/create_connection_request.rb +2 -2
- data/lib/repull/models/create_webhook_request.rb +1 -1
- data/lib/repull/models/custom_schema.rb +1 -1
- data/lib/repull/models/custom_schema_create.rb +1 -1
- data/lib/repull/models/custom_schema_create_response.rb +1 -1
- data/lib/repull/models/custom_schema_delete_response.rb +1 -1
- data/lib/repull/models/custom_schema_list_response.rb +1 -1
- data/lib/repull/models/custom_schema_summary.rb +1 -1
- data/lib/repull/models/custom_schema_update.rb +1 -1
- data/lib/repull/models/delete_airbnb_listing_photo200_response.rb +1 -1
- data/lib/repull/models/delete_connection200_response.rb +234 -0
- data/lib/repull/models/delete_kv200_response.rb +1 -1
- data/lib/repull/models/error.rb +1 -1
- data/lib/repull/models/error_error.rb +14 -2
- data/lib/repull/models/error_error_support.rb +1 -1
- data/lib/repull/models/get_airbnb_alteration200_response.rb +2 -2
- data/lib/repull/models/get_airbnb_checkin_guide200_response.rb +192 -0
- data/lib/repull/models/get_airbnb_listing_quality200_response.rb +2 -2
- data/lib/repull/models/get_airbnb_listing_settings200_response.rb +2 -2
- data/lib/repull/models/get_airbnb_thread200_response.rb +2 -2
- data/lib/repull/models/get_health200_response.rb +1 -1
- data/lib/repull/models/get_usage_logs200_response.rb +1 -1
- data/lib/repull/models/get_usage_logs200_response_data_inner.rb +1 -1
- data/lib/repull/models/get_usage_logs200_response_pagination.rb +3 -3
- data/lib/repull/models/get_usage_summary200_response.rb +2 -2
- data/lib/repull/models/get_usage_summary200_response_breakdown_inner.rb +1 -1
- data/lib/repull/models/get_usage_summary200_response_limits.rb +3 -3
- data/lib/repull/models/get_usage_summary200_response_remaining.rb +2 -2
- data/lib/repull/models/get_usage_summary200_response_status_distribution.rb +1 -1
- data/lib/repull/models/get_usage_summary200_response_timeline_inner.rb +1 -1
- data/lib/repull/models/get_usage_summary200_response_totals.rb +1 -1
- data/lib/repull/models/get_usage_summary200_response_used.rb +2 -2
- data/lib/repull/models/get_usage_tier200_response.rb +2 -2
- data/lib/repull/models/get_usage_tier200_response_limits.rb +4 -4
- data/lib/repull/models/get_usage_tier200_response_remaining.rb +3 -3
- data/lib/repull/models/get_usage_tier200_response_used.rb +3 -3
- data/lib/repull/models/guest.rb +1 -1
- data/lib/repull/models/guest_contact.rb +1 -1
- data/lib/repull/models/guest_create_request.rb +250 -0
- data/lib/repull/models/guest_create_response.rb +227 -0
- data/lib/repull/models/guest_create_response_contacts_inner.rb +199 -0
- data/lib/repull/models/guest_flag.rb +1 -1
- data/lib/repull/models/guest_list_response.rb +1 -1
- data/lib/repull/models/guest_note.rb +1 -1
- data/lib/repull/models/guest_profile.rb +1 -1
- data/lib/repull/models/guest_reservations_summary.rb +1 -1
- data/lib/repull/models/list_airbnb_alterations200_response.rb +2 -2
- data/lib/repull/models/list_airbnb_listing_amenities200_response.rb +2 -2
- data/lib/repull/models/list_airbnb_listing_amenities200_response_data.rb +1 -1
- data/lib/repull/models/list_airbnb_transactions200_response.rb +3 -3
- data/lib/repull/models/list_booking_reservations200_response.rb +104 -0
- data/lib/repull/models/list_kv200_response.rb +1 -1
- data/lib/repull/models/list_kv200_response_data_inner.rb +1 -1
- data/lib/repull/models/list_kv200_response_pagination.rb +2 -2
- data/lib/repull/models/listing.rb +2 -2
- data/lib/repull/models/listing_active_request.rb +2 -2
- data/lib/repull/models/listing_active_response.rb +1 -1
- data/lib/repull/models/listing_address.rb +1 -1
- data/lib/repull/models/listing_amenity.rb +1 -1
- data/lib/repull/models/listing_channel.rb +1 -1
- data/lib/repull/models/listing_comp.rb +1 -1
- data/lib/repull/models/listing_comp_nightly.rb +1 -1
- data/lib/repull/models/listing_comp_ratings.rb +1 -1
- data/lib/repull/models/listing_comps_response.rb +1 -1
- data/lib/repull/models/listing_content.rb +1 -1
- data/lib/repull/models/listing_content_update_request.rb +1 -1
- data/lib/repull/models/listing_content_update_request_address.rb +1 -1
- data/lib/repull/models/listing_content_update_request_amenities.rb +1 -1
- data/lib/repull/models/listing_content_update_request_amenities_one_of_inner.rb +1 -1
- data/lib/repull/models/listing_content_update_request_occupancy.rb +1 -1
- data/lib/repull/models/listing_content_update_request_photos_inner.rb +1 -1
- data/lib/repull/models/listing_content_update_request_photos_inner_one_of.rb +1 -1
- data/lib/repull/models/listing_content_update_request_policies.rb +1 -1
- data/lib/repull/models/listing_content_update_response.rb +1 -1
- data/lib/repull/models/listing_create_request.rb +1 -1
- data/lib/repull/models/listing_create_response.rb +1 -1
- data/lib/repull/models/listing_created_event.rb +1 -1
- data/lib/repull/models/listing_created_payload.rb +1 -1
- data/lib/repull/models/listing_created_payload_address.rb +1 -1
- data/lib/repull/models/listing_deleted_event.rb +1 -1
- data/lib/repull/models/listing_deleted_payload.rb +1 -1
- data/lib/repull/models/listing_details.rb +1 -1
- data/lib/repull/models/listing_generate_content_request.rb +1 -1
- data/lib/repull/models/listing_generate_content_response.rb +1 -1
- data/lib/repull/models/listing_list_response.rb +1 -1
- data/lib/repull/models/{studio_file.rb → listing_photo.rb} +23 -34
- data/lib/repull/models/listing_photo_delete_request.rb +165 -0
- data/lib/repull/models/{studio_error.rb → listing_photo_delete_response.rb} +20 -12
- data/lib/repull/models/listing_photo_upload_url_request.rb +203 -0
- data/lib/repull/models/listing_photo_upload_url_response.rb +198 -0
- data/lib/repull/models/{vrbo_listing_list_response.rb → listing_photos_response.rb} +19 -19
- data/lib/repull/models/listing_pricing_apply_request.rb +1 -1
- data/lib/repull/models/listing_pricing_apply_response.rb +1 -1
- data/lib/repull/models/listing_pricing_history_entry.rb +2 -2
- data/lib/repull/models/listing_pricing_history_response.rb +1 -1
- data/lib/repull/models/listing_pricing_recommendation.rb +1 -1
- data/lib/repull/models/listing_pricing_response.rb +1 -1
- data/lib/repull/models/listing_pricing_response_comp_summary.rb +1 -1
- data/lib/repull/models/listing_pricing_response_date_range.rb +1 -1
- data/lib/repull/models/listing_pricing_response_listing.rb +1 -1
- data/lib/repull/models/listing_pricing_strategy.rb +1 -1
- data/lib/repull/models/listing_pricing_strategy_input.rb +1 -1
- data/lib/repull/models/listing_publish_airbnb_request.rb +1 -1
- data/lib/repull/models/listing_publish_response.rb +1 -1
- data/lib/repull/models/listing_publish_status_channel.rb +1 -1
- data/lib/repull/models/listing_publish_status_connection.rb +1 -1
- data/lib/repull/models/listing_publish_status_response.rb +1 -1
- data/lib/repull/models/listing_quality_tier.rb +1 -1
- data/lib/repull/models/listing_segment.rb +1 -1
- data/lib/repull/models/listing_segment_recommendation.rb +1 -1
- data/lib/repull/models/listing_segments_response.rb +1 -1
- data/lib/repull/models/listing_segments_response_scope.rb +1 -1
- data/lib/repull/models/listing_status_batch_request.rb +212 -0
- data/lib/repull/models/listing_status_batch_response.rb +223 -0
- data/lib/repull/models/listing_updated_event.rb +1 -1
- data/lib/repull/models/listing_updated_payload.rb +1 -1
- data/lib/repull/models/map_airbnb_listing_request.rb +1 -1
- data/lib/repull/models/map_airbnb_listing_response.rb +1 -1
- data/lib/repull/models/map_connect_booking_rooms_request.rb +1 -1
- data/lib/repull/models/map_connect_booking_rooms_response.rb +16 -5
- data/lib/repull/models/market_browse_category.rb +1 -1
- data/lib/repull/models/market_browse_entry.rb +1 -1
- data/lib/repull/models/market_browse_featured.rb +1 -1
- data/lib/repull/models/market_browse_response.rb +1 -1
- data/lib/repull/models/market_calendar_day.rb +1 -1
- data/lib/repull/models/market_calendar_day_events_inner.rb +1 -1
- data/lib/repull/models/market_calendar_response.rb +1 -1
- data/lib/repull/models/market_detail_response.rb +1 -1
- data/lib/repull/models/market_detail_response_price_distribution_inner.rb +1 -1
- data/lib/repull/models/market_detail_response_property_type_mix_inner.rb +1 -1
- data/lib/repull/models/market_detail_response_supply_trend_inner.rb +1 -1
- data/lib/repull/models/market_detail_response_top_comps.rb +1 -1
- data/lib/repull/models/market_event.rb +1 -1
- data/lib/repull/models/market_my_listing.rb +1 -1
- data/lib/repull/models/market_summary.rb +1 -1
- data/lib/repull/models/market_top_comp.rb +1 -1
- data/lib/repull/models/markets_overview_response.rb +1 -1
- data/lib/repull/models/markets_overview_response_browse.rb +1 -1
- data/lib/repull/models/markets_overview_response_subscriptions.rb +1 -1
- data/lib/repull/models/markets_overview_response_totals.rb +1 -1
- data/lib/repull/models/message.rb +1 -1
- data/lib/repull/models/message_list_response.rb +1 -1
- data/lib/repull/models/pagination.rb +1 -1
- data/lib/repull/models/payment_completed_event.rb +1 -1
- data/lib/repull/models/payment_completed_payload.rb +1 -1
- data/lib/repull/models/payment_refunded_event.rb +1 -1
- data/lib/repull/models/payment_refunded_payload.rb +1 -1
- data/lib/repull/models/plumguide_listing.rb +1 -1
- data/lib/repull/models/plumguide_listing_list_response.rb +1 -1
- data/lib/repull/models/property.rb +17 -7
- data/lib/repull/models/property_availability.rb +33 -7
- data/lib/repull/models/property_availability_coverage.rb +222 -0
- data/lib/repull/models/property_availability_day.rb +4 -4
- data/lib/repull/models/property_list_response.rb +1 -1
- data/lib/repull/models/quote.rb +240 -0
- data/lib/repull/models/quote_pricing.rb +218 -0
- data/lib/repull/models/reply_booking_review200_response.rb +1 -1
- data/lib/repull/models/reply_booking_review_request.rb +1 -1
- data/lib/repull/models/reply_to_review201_response.rb +165 -0
- data/lib/repull/models/reply_to_review_request.rb +165 -0
- data/lib/repull/models/repull_ping_event.rb +1 -1
- data/lib/repull/models/repull_ping_payload.rb +1 -1
- data/lib/repull/models/reservation.rb +1 -1
- data/lib/repull/models/{studio_deployment.rb → reservation_alteration_created_event.rb} +67 -57
- data/lib/repull/models/reservation_alteration_created_payload.rb +177 -0
- data/lib/repull/models/reservation_alteration_responded_event.rb +217 -0
- data/lib/repull/models/reservation_alteration_responded_payload.rb +175 -0
- data/lib/repull/models/reservation_cancelled_event.rb +1 -1
- data/lib/repull/models/reservation_cancelled_payload.rb +1 -1
- data/lib/repull/models/reservation_create_request.rb +403 -0
- data/lib/repull/models/reservation_create_response.rb +233 -0
- data/lib/repull/models/reservation_created_event.rb +1 -1
- data/lib/repull/models/reservation_created_payload.rb +1 -1
- data/lib/repull/models/reservation_financials.rb +36 -8
- data/lib/repull/models/reservation_guest_financials.rb +173 -0
- data/lib/repull/models/reservation_guest_input.rb +193 -0
- data/lib/repull/models/reservation_host_financials.rb +207 -0
- data/lib/repull/models/reservation_list_response.rb +1 -1
- data/lib/repull/models/reservation_message_received_event.rb +1 -1
- data/lib/repull/models/reservation_message_received_payload.rb +1 -1
- data/lib/repull/models/reservation_message_received_payload_from.rb +1 -1
- data/lib/repull/models/reservation_money_line.rb +216 -0
- data/lib/repull/models/reservation_occupancy.rb +1 -1
- data/lib/repull/models/reservation_primary_guest.rb +1 -1
- data/lib/repull/models/reservation_update_request.rb +255 -0
- data/lib/repull/models/reservation_update_response.rb +240 -0
- data/lib/repull/models/reservation_updated_event.rb +1 -1
- data/lib/repull/models/reservation_updated_payload.rb +1 -1
- data/lib/repull/models/reservation_webhook_object.rb +1 -1
- data/lib/repull/models/respond_airbnb_review_request.rb +1 -1
- data/lib/repull/models/review.rb +14 -3
- data/lib/repull/models/review_category.rb +1 -1
- data/lib/repull/models/{sandbox_reset_result.rb → review_created_event.rb} +59 -59
- data/lib/repull/models/review_created_payload.rb +165 -0
- data/lib/repull/models/review_list_response.rb +1 -1
- data/lib/repull/models/review_responded_event.rb +217 -0
- data/lib/repull/models/review_responded_payload.rb +175 -0
- data/lib/repull/models/review_response.rb +1 -1
- data/lib/repull/models/review_webhook_object.rb +277 -0
- data/lib/repull/models/rotate_webhook_secret200_response.rb +1 -1
- data/lib/repull/models/select_connect_provider_request.rb +1 -1
- data/lib/repull/models/select_provider_response.rb +1 -1
- data/lib/repull/models/send_airbnb_message_request.rb +187 -0
- data/lib/repull/models/{sandbox_reset_result_deleted.rb → send_booking_message_request.rb} +53 -50
- data/lib/repull/models/{studio_error_error.rb → send_message_request.rb} +31 -50
- data/lib/repull/models/send_message_response.rb +241 -0
- data/lib/repull/models/set_kv_request.rb +1 -1
- data/lib/repull/models/{booking_property_list_response.rb → submit_beds24_credentials200_response.rb} +19 -19
- data/lib/repull/models/submit_beds24_credentials_request.rb +177 -0
- data/lib/repull/models/submit_bookingsync_credentials_request.rb +177 -0
- data/lib/repull/models/submit_guesty_credentials_request.rb +177 -0
- data/lib/repull/models/submit_hospitable_credentials_request.rb +177 -0
- data/lib/repull/models/submit_hostaway_credentials_request.rb +177 -0
- data/lib/repull/models/submit_igms_credentials_request.rb +177 -0
- data/lib/repull/models/submit_lodgify_credentials_request.rb +177 -0
- data/lib/repull/models/submit_ownerrez_credentials_request.rb +177 -0
- data/lib/repull/models/submit_smoobu_credentials_request.rb +177 -0
- data/lib/repull/models/submit_vrbo_credentials_request.rb +177 -0
- data/lib/repull/models/{sandbox_fixture_ref.rb → sync_airbnb_transactions200_response.rb} +36 -38
- data/lib/repull/models/sync_airbnb_transactions_request.rb +201 -0
- data/lib/repull/models/test_webhook_request.rb +1 -1
- data/lib/repull/models/update_airbnb_message_request.rb +1 -1
- data/lib/repull/models/update_booking_charges_request.rb +1 -1
- data/lib/repull/models/update_listing_pricing_strategy200_response.rb +1 -1
- data/lib/repull/models/update_webhook_request.rb +1 -1
- data/lib/repull/models/vrbo_listing.rb +1 -1
- data/lib/repull/models/vrbo_reservation.rb +1 -1
- data/lib/repull/models/vrbo_reservation_list_response.rb +1 -1
- data/lib/repull/models/webhook_delivery.rb +1 -1
- data/lib/repull/models/webhook_delivery_detail.rb +1 -1
- data/lib/repull/models/webhook_delivery_list_response.rb +1 -1
- data/lib/repull/models/webhook_event.rb +12 -4
- data/lib/repull/models/webhook_event_catalog.rb +1 -1
- data/lib/repull/models/webhook_event_catalog_domains_inner.rb +1 -1
- data/lib/repull/models/webhook_event_catalog_entry.rb +1 -1
- data/lib/repull/models/webhook_event_type.rb +6 -2
- data/lib/repull/models/webhook_list_response.rb +1 -1
- data/lib/repull/models/webhook_subscription.rb +1 -1
- data/lib/repull/version.rb +2 -2
- data/lib/repull.rb +75 -15
- data/openapi/v1.json +6119 -2021
- data/scripts/check-spec-freshness.py +399 -0
- data/scripts/regen.sh +32 -3
- metadata +77 -18
- data/lib/repull/api/sandbox_api.rb +0 -136
- data/lib/repull/models/sandbox_seed_result.rb +0 -278
- data/lib/repull/models/studio_generation.rb +0 -215
- data/lib/repull/models/studio_project.rb +0 -241
- data/spec/api/ai_api_spec.rb +0 -47
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
SDK spec-freshness guard.
|
|
4
|
+
|
|
5
|
+
Fails CI when this SDK's view of the Repull API disagrees with the live spec at
|
|
6
|
+
https://api.repull.dev/openapi.json.
|
|
7
|
+
|
|
8
|
+
WHY THIS EXISTS
|
|
9
|
+
---------------
|
|
10
|
+
Every Repull SDK is generated from a committed snapshot of the OpenAPI spec
|
|
11
|
+
(openapi/v1.json). Nothing used to compare that snapshot to the live API, so
|
|
12
|
+
drift was invisible:
|
|
13
|
+
|
|
14
|
+
* repull-sdk and repull-dotnet carried a 89-path snapshot that still declared
|
|
15
|
+
ten /api/studio/* paths -- a different product on a different host -- inside
|
|
16
|
+
the public API types, plus four endpoints that only ever returned 501/404.
|
|
17
|
+
* Four SDKs still shipped /v1/sandbox/seed and /v1/sandbox/reset months after
|
|
18
|
+
the sandbox was deleted from the API.
|
|
19
|
+
* Every SDK was missing between 24 and 49 real endpoints, so those operations
|
|
20
|
+
were unreachable from any SDK.
|
|
21
|
+
|
|
22
|
+
None of that failed a single check.
|
|
23
|
+
|
|
24
|
+
TWO MODES
|
|
25
|
+
---------
|
|
26
|
+
snapshot (generated SDKs: python, php, go, ruby, dotnet, ts, mcp)
|
|
27
|
+
Compares the PATH SET of the committed snapshot against the live spec and
|
|
28
|
+
fails in BOTH directions:
|
|
29
|
+
- a path in the snapshot that the API does not have (the /api/studio/*
|
|
30
|
+
and /v1/sandbox/* class of bug -- SDK ships dead code)
|
|
31
|
+
- a path the API has that the snapshot is missing (the reason dozens of
|
|
32
|
+
operations were unreachable -- SDK is behind)
|
|
33
|
+
|
|
34
|
+
source (hand-written SDKs with no snapshot: repull-ai-sdk)
|
|
35
|
+
Scans source files for hardcoded "/v1/..." request paths and fails if any of
|
|
36
|
+
them is absent from the live spec. One direction only: a hand-written SDK is
|
|
37
|
+
not obliged to cover every endpoint, but it must never ship a call to an
|
|
38
|
+
endpoint that does not exist. This is exactly the bug that let repull-ai-sdk
|
|
39
|
+
ship six tools against /v1/market/context, /v1/analytics/revenue,
|
|
40
|
+
/v1/analytics/occupancy, /v1/cleaning/rota, /v1/agent/quota and
|
|
41
|
+
/v1/agent/usage -- all six 404.
|
|
42
|
+
|
|
43
|
+
The scheduled (cron) run matters as much as the pull_request run: the API can
|
|
44
|
+
grow new endpoints without anyone touching the SDK repo, and a PR-only check
|
|
45
|
+
would never notice.
|
|
46
|
+
|
|
47
|
+
USAGE
|
|
48
|
+
python3 scripts/check-spec-freshness.py # snapshot mode, openapi/v1.json
|
|
49
|
+
python3 scripts/check-spec-freshness.py --snapshot path.json
|
|
50
|
+
python3 scripts/check-spec-freshness.py --mode source --source-dir src
|
|
51
|
+
|
|
52
|
+
ENV
|
|
53
|
+
REPULL_OPENAPI_URL override the live spec URL (default https://api.repull.dev/openapi.json)
|
|
54
|
+
|
|
55
|
+
EXIT CODES
|
|
56
|
+
0 in sync
|
|
57
|
+
1 drift detected
|
|
58
|
+
2 could not fetch the live spec (network/HTTP) -- deliberately distinct
|
|
59
|
+
from 1 so an outage is not mistaken for drift
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
from __future__ import annotations
|
|
63
|
+
|
|
64
|
+
import argparse
|
|
65
|
+
import json
|
|
66
|
+
import os
|
|
67
|
+
import re
|
|
68
|
+
import subprocess
|
|
69
|
+
import sys
|
|
70
|
+
import time
|
|
71
|
+
import urllib.error
|
|
72
|
+
import urllib.request
|
|
73
|
+
|
|
74
|
+
DEFAULT_SPEC_URL = "https://api.repull.dev/openapi.json"
|
|
75
|
+
FETCH_ATTEMPTS = 3
|
|
76
|
+
FETCH_BACKOFF_SECONDS = 2
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# live spec
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
def _fetch_with_curl(url: str) -> str:
|
|
84
|
+
"""
|
|
85
|
+
Fetch via curl.
|
|
86
|
+
|
|
87
|
+
api.repull.dev sits behind Cloudflare, which fingerprints the TLS handshake
|
|
88
|
+
(JA3/JA4) -- not just headers. Python's urllib/OpenSSL handshake gets a hard
|
|
89
|
+
403 on EVERY request no matter what User-Agent or Accept headers are set,
|
|
90
|
+
while curl on the same machine gets a 200. Setting a browser User-Agent does
|
|
91
|
+
NOT help, because the block is below the HTTP layer.
|
|
92
|
+
|
|
93
|
+
So curl is the primary transport. It is preinstalled on ubuntu-latest (and
|
|
94
|
+
every other GitHub runner image), so this costs nothing in CI.
|
|
95
|
+
"""
|
|
96
|
+
proc = subprocess.run(
|
|
97
|
+
[
|
|
98
|
+
"curl", "--silent", "--show-error", "--fail",
|
|
99
|
+
"--location", "--max-time", "30",
|
|
100
|
+
"--header", "Accept: application/json",
|
|
101
|
+
url,
|
|
102
|
+
],
|
|
103
|
+
capture_output=True,
|
|
104
|
+
text=True,
|
|
105
|
+
)
|
|
106
|
+
if proc.returncode != 0:
|
|
107
|
+
raise RuntimeError(f"curl exit {proc.returncode}: {proc.stderr.strip()}")
|
|
108
|
+
return proc.stdout
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _fetch_with_urllib(url: str) -> str:
|
|
112
|
+
req = urllib.request.Request(url, headers={"Accept": "application/json"})
|
|
113
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
114
|
+
if resp.status != 200:
|
|
115
|
+
raise RuntimeError(f"HTTP {resp.status}")
|
|
116
|
+
return resp.read().decode("utf-8")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def fetch_live_spec(url: str) -> dict:
|
|
120
|
+
last_err: Exception | None = None
|
|
121
|
+
for attempt in range(1, FETCH_ATTEMPTS + 1):
|
|
122
|
+
for transport in (_fetch_with_curl, _fetch_with_urllib):
|
|
123
|
+
try:
|
|
124
|
+
return json.loads(transport(url))
|
|
125
|
+
except FileNotFoundError as err: # curl missing -> try urllib
|
|
126
|
+
last_err = err
|
|
127
|
+
except Exception as err: # noqa: BLE001 - retry anything transient
|
|
128
|
+
last_err = err
|
|
129
|
+
if attempt < FETCH_ATTEMPTS:
|
|
130
|
+
print(
|
|
131
|
+
f" fetch attempt {attempt}/{FETCH_ATTEMPTS} failed ({last_err}); retrying...",
|
|
132
|
+
file=sys.stderr,
|
|
133
|
+
)
|
|
134
|
+
time.sleep(FETCH_BACKOFF_SECONDS * attempt)
|
|
135
|
+
|
|
136
|
+
print(f"\nERROR: could not fetch the live spec at {url}: {last_err}", file=sys.stderr)
|
|
137
|
+
print("This is a fetch failure, not drift. Re-run the job.", file=sys.stderr)
|
|
138
|
+
sys.exit(2)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
LIVE_SPEC: dict | None = None
|
|
142
|
+
LOCAL_SPEC: dict | None = None
|
|
143
|
+
|
|
144
|
+
HTTP_METHODS = ("get", "post", "put", "patch", "delete", "head", "options")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def schema_fingerprint(spec: dict) -> set[str]:
|
|
148
|
+
"""Every property as "Schema.path:type" -- the SHAPE, not the prose.
|
|
149
|
+
|
|
150
|
+
Operation inventory alone is not enough. Nineteen schema corrections shipped
|
|
151
|
+
-- ten fields renamed because the serializer camelCases keys, three list
|
|
152
|
+
responses that are arrays rather than objects, four ids that are strings
|
|
153
|
+
rather than integers, and lat/lng likewise -- and every SDK snapshot stayed
|
|
154
|
+
stale while this check printed OK, because not one path or method had moved.
|
|
155
|
+
|
|
156
|
+
Descriptions, examples and enums are deliberately excluded: prose changes
|
|
157
|
+
constantly and would make this cry wolf, and repull-ruby's regen strips
|
|
158
|
+
enums from its snapshot before generating, so comparing them would fail
|
|
159
|
+
there forever for a reason that has nothing to do with drift.
|
|
160
|
+
"""
|
|
161
|
+
out: set[str] = set()
|
|
162
|
+
|
|
163
|
+
def walk(node, where, depth=0):
|
|
164
|
+
if not isinstance(node, dict) or depth > 12:
|
|
165
|
+
return
|
|
166
|
+
props = node.get("properties")
|
|
167
|
+
if isinstance(props, dict):
|
|
168
|
+
for k, v in props.items():
|
|
169
|
+
t = v.get("type") if isinstance(v, dict) else None
|
|
170
|
+
ref = v.get("$ref") if isinstance(v, dict) else None
|
|
171
|
+
out.add(f"{where}.{k}:{t or ref or '?'}")
|
|
172
|
+
walk(v, f"{where}.{k}", depth + 1)
|
|
173
|
+
items = node.get("items")
|
|
174
|
+
if isinstance(items, dict):
|
|
175
|
+
out.add(f"{where}[]:{items.get('type') or items.get('$ref') or '?'}")
|
|
176
|
+
walk(items, f"{where}[]", depth + 1)
|
|
177
|
+
if isinstance(node.get("required"), list):
|
|
178
|
+
for r in node["required"]:
|
|
179
|
+
out.add(f"{where}.required:{r}")
|
|
180
|
+
|
|
181
|
+
for name, sch in (spec.get("components", {}).get("schemas", {}) or {}).items():
|
|
182
|
+
t = sch.get("type") if isinstance(sch, dict) else None
|
|
183
|
+
out.add(f"{name}:{t or '?'}")
|
|
184
|
+
walk(sch, name)
|
|
185
|
+
return out
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def spec_bare_paths(spec: dict, label: str) -> set[str]:
|
|
189
|
+
"""Just the path keys, for source mode -- a literal in code has no method."""
|
|
190
|
+
paths = spec.get("paths")
|
|
191
|
+
if not isinstance(paths, dict):
|
|
192
|
+
print(f"ERROR: {label} has no usable 'paths' object", file=sys.stderr)
|
|
193
|
+
sys.exit(2)
|
|
194
|
+
return set(paths)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def spec_paths(spec: dict, label: str) -> set[str]:
|
|
198
|
+
"""Every OPERATION, as "POST /v1/reservations" -- not just the path key.
|
|
199
|
+
|
|
200
|
+
Comparing bare path keys let a whole operation disappear unnoticed. Four
|
|
201
|
+
write endpoints (create a reservation, update a reservation, create a
|
|
202
|
+
guest, send a message to a guest) were added as new METHODS on paths that
|
|
203
|
+
already existed for GET, so the path set was byte-identical before and
|
|
204
|
+
after and every SDK silently lacked them while this check printed OK.
|
|
205
|
+
|
|
206
|
+
Method + path is the unit a caller actually reaches for, so it is the unit
|
|
207
|
+
the guard compares.
|
|
208
|
+
"""
|
|
209
|
+
paths = spec.get("paths")
|
|
210
|
+
if not isinstance(paths, dict):
|
|
211
|
+
print(f"ERROR: {label} has no usable 'paths' object", file=sys.stderr)
|
|
212
|
+
sys.exit(2)
|
|
213
|
+
ops: set[str] = set()
|
|
214
|
+
for path, item in paths.items():
|
|
215
|
+
if not isinstance(item, dict):
|
|
216
|
+
continue
|
|
217
|
+
for method in item:
|
|
218
|
+
if method.lower() in HTTP_METHODS:
|
|
219
|
+
ops.add(f"{method.upper()} {path}")
|
|
220
|
+
return ops
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
# snapshot mode
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
def run_snapshot_mode(snapshot_path: str, live: set[str]) -> int:
|
|
228
|
+
if not os.path.isfile(snapshot_path):
|
|
229
|
+
print(f"ERROR: snapshot not found at {snapshot_path}", file=sys.stderr)
|
|
230
|
+
return 2
|
|
231
|
+
|
|
232
|
+
with open(snapshot_path, encoding="utf-8") as handle:
|
|
233
|
+
local = spec_paths(json.load(handle), snapshot_path)
|
|
234
|
+
|
|
235
|
+
stale = sorted(local - live) # SDK declares it, API does not have it
|
|
236
|
+
missing = sorted(live - local) # API has it, SDK cannot reach it
|
|
237
|
+
|
|
238
|
+
# Schema-shape comparison. `live_spec`/`local_spec` are set by main().
|
|
239
|
+
schema_stale: list[str] = []
|
|
240
|
+
schema_missing: list[str] = []
|
|
241
|
+
if LIVE_SPEC is not None and LOCAL_SPEC is not None:
|
|
242
|
+
lf = schema_fingerprint(LOCAL_SPEC)
|
|
243
|
+
rf = schema_fingerprint(LIVE_SPEC)
|
|
244
|
+
schema_stale = sorted(lf - rf)
|
|
245
|
+
schema_missing = sorted(rf - lf)
|
|
246
|
+
|
|
247
|
+
print(f" snapshot : {snapshot_path} ({len(local)} operations)")
|
|
248
|
+
print(f" live : {len(live)} operations")
|
|
249
|
+
|
|
250
|
+
if not stale and not missing and not schema_stale and not schema_missing:
|
|
251
|
+
print(f"\nOK: snapshot matches the live API exactly ({len(local)} operations).")
|
|
252
|
+
return 0
|
|
253
|
+
|
|
254
|
+
print("\nFAIL: committed OpenAPI snapshot has drifted from the live API.\n")
|
|
255
|
+
|
|
256
|
+
if stale:
|
|
257
|
+
print(f" {len(stale)} operation(s) in the SDK that the API DOES NOT HAVE")
|
|
258
|
+
print(" (the SDK ships dead code -- these calls will 404):")
|
|
259
|
+
for path in stale:
|
|
260
|
+
print(f" - {path}")
|
|
261
|
+
print()
|
|
262
|
+
|
|
263
|
+
if missing:
|
|
264
|
+
print(f" {len(missing)} operation(s) the API HAS that the SDK IS MISSING")
|
|
265
|
+
print(" (these operations are unreachable from this SDK):")
|
|
266
|
+
for path in missing:
|
|
267
|
+
print(f" + {path}")
|
|
268
|
+
print()
|
|
269
|
+
|
|
270
|
+
if schema_stale or schema_missing:
|
|
271
|
+
n = len(schema_stale) + len(schema_missing)
|
|
272
|
+
print(f" {n} schema difference(s) -- the shapes the SDK types are built")
|
|
273
|
+
print(" from no longer match the API:")
|
|
274
|
+
for d in schema_missing[:12]:
|
|
275
|
+
print(f" + {d}")
|
|
276
|
+
for d in schema_stale[:12]:
|
|
277
|
+
print(f" - {d}")
|
|
278
|
+
if n > 24:
|
|
279
|
+
print(f" ... and {n - 24} more")
|
|
280
|
+
print()
|
|
281
|
+
|
|
282
|
+
print(" FIX: re-run this repo's regen script to pull the live spec and")
|
|
283
|
+
print(" regenerate the client, then commit the snapshot and the")
|
|
284
|
+
print(" generated code together. Never hand-edit generated types.")
|
|
285
|
+
return 1
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# ---------------------------------------------------------------------------
|
|
289
|
+
# source mode
|
|
290
|
+
# ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
SOURCE_SUFFIXES = (".ts", ".tsx", ".js", ".mjs", ".py", ".go", ".rb", ".php", ".cs")
|
|
293
|
+
SKIP_DIRS = {
|
|
294
|
+
"node_modules", "dist", "build", "vendor", ".git", ".claude",
|
|
295
|
+
"__pycache__", "coverage", ".next", "bin", "obj",
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
# Matches a quoted/templated request path literal beginning with /v1/.
|
|
299
|
+
PATH_LITERAL = re.compile(r"""['"`](/v1/[A-Za-z0-9_\-./{}$@:]*)['"`]""")
|
|
300
|
+
# ${id} / {id} / :id / #{id} -> a single wildcard segment
|
|
301
|
+
INTERPOLATION = re.compile(r"\$\{[^}]*\}|#\{[^}]*\}|\{[^}]*\}|(?<=/):[A-Za-z_][A-Za-z0-9_]*")
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def normalize(path: str) -> tuple[str, ...]:
|
|
305
|
+
"""Turn a path into comparable segments, with parameters as a wildcard."""
|
|
306
|
+
path = path.split("?", 1)[0].rstrip("/")
|
|
307
|
+
segments: list[str] = []
|
|
308
|
+
for segment in path.split("/"):
|
|
309
|
+
if not segment:
|
|
310
|
+
continue
|
|
311
|
+
collapsed = INTERPOLATION.sub("\x00", segment)
|
|
312
|
+
segments.append("*" if "\x00" in collapsed else segment)
|
|
313
|
+
return tuple(segments)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def live_shapes(live: set[str]) -> set[tuple[str, ...]]:
|
|
317
|
+
return {normalize(p) for p in live}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def iter_source_files(root: str):
|
|
321
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
322
|
+
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS and not d.startswith(".")]
|
|
323
|
+
for name in filenames:
|
|
324
|
+
if name.endswith(SOURCE_SUFFIXES) and ".test." not in name and ".spec." not in name:
|
|
325
|
+
yield os.path.join(dirpath, name)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def run_source_mode(source_dir: str, live: set[str]) -> int:
|
|
329
|
+
if not os.path.isdir(source_dir):
|
|
330
|
+
print(f"ERROR: source dir not found at {source_dir}", file=sys.stderr)
|
|
331
|
+
return 2
|
|
332
|
+
|
|
333
|
+
shapes = live_shapes(live)
|
|
334
|
+
found: dict[str, set[str]] = {}
|
|
335
|
+
|
|
336
|
+
for file_path in iter_source_files(source_dir):
|
|
337
|
+
try:
|
|
338
|
+
text = open(file_path, encoding="utf-8", errors="replace").read()
|
|
339
|
+
except OSError:
|
|
340
|
+
continue
|
|
341
|
+
for raw in PATH_LITERAL.findall(text):
|
|
342
|
+
found.setdefault(raw, set()).add(file_path)
|
|
343
|
+
|
|
344
|
+
print(f" source dir : {source_dir}")
|
|
345
|
+
print(f" live : {len(live)} paths")
|
|
346
|
+
print(f" found : {len(found)} distinct /v1/ path literal(s) in source")
|
|
347
|
+
|
|
348
|
+
bogus = sorted(p for p in found if normalize(p) not in shapes)
|
|
349
|
+
|
|
350
|
+
if not bogus:
|
|
351
|
+
print("\nOK: every /v1/ path referenced in source exists in the live API.")
|
|
352
|
+
return 0
|
|
353
|
+
|
|
354
|
+
print(f"\nFAIL: {len(bogus)} path(s) referenced in source do NOT exist in the live API.")
|
|
355
|
+
print("These calls will 404 at runtime.\n")
|
|
356
|
+
for path in bogus:
|
|
357
|
+
print(f" - {path}")
|
|
358
|
+
for where in sorted(found[path]):
|
|
359
|
+
print(f" {where}")
|
|
360
|
+
print()
|
|
361
|
+
print(" FIX: delete the tool/method, or repoint it at a real endpoint.")
|
|
362
|
+
print(f" Full endpoint list: {os.environ.get('REPULL_OPENAPI_URL', DEFAULT_SPEC_URL)}")
|
|
363
|
+
return 1
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# ---------------------------------------------------------------------------
|
|
367
|
+
|
|
368
|
+
def main() -> int:
|
|
369
|
+
parser = argparse.ArgumentParser(description="Fail when an SDK drifts from the live Repull OpenAPI spec.")
|
|
370
|
+
parser.add_argument("--mode", choices=("snapshot", "source"), default="snapshot")
|
|
371
|
+
parser.add_argument("--snapshot", default="openapi/v1.json", help="committed spec snapshot (snapshot mode)")
|
|
372
|
+
parser.add_argument("--source-dir", default="src", help="source root to scan (source mode)")
|
|
373
|
+
parser.add_argument("--spec-url", default=os.environ.get("REPULL_OPENAPI_URL", DEFAULT_SPEC_URL))
|
|
374
|
+
args = parser.parse_args()
|
|
375
|
+
|
|
376
|
+
print("Repull SDK spec-freshness check")
|
|
377
|
+
print(f" spec url : {args.spec_url}")
|
|
378
|
+
|
|
379
|
+
spec = fetch_live_spec(args.spec_url)
|
|
380
|
+
|
|
381
|
+
if args.mode == "snapshot":
|
|
382
|
+
global LIVE_SPEC, LOCAL_SPEC
|
|
383
|
+
LIVE_SPEC = spec
|
|
384
|
+
try:
|
|
385
|
+
with open(args.snapshot, encoding="utf-8") as fh:
|
|
386
|
+
LOCAL_SPEC = json.load(fh)
|
|
387
|
+
except Exception:
|
|
388
|
+
LOCAL_SPEC = None
|
|
389
|
+
# Method + path, so an operation added to an existing path is visible.
|
|
390
|
+
return run_snapshot_mode(args.snapshot, spec_paths(spec, args.spec_url))
|
|
391
|
+
|
|
392
|
+
# Source mode matches bare "/v1/..." literals found in code, which carry no
|
|
393
|
+
# method, so it compares against paths rather than operations.
|
|
394
|
+
return run_source_mode(args.source_dir, spec_bare_paths(spec, args.spec_url))
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
if __name__ == "__main__":
|
|
399
|
+
sys.exit(main())
|
data/scripts/regen.sh
CHANGED
|
@@ -24,7 +24,7 @@ trap 'rm -rf "$GEN_OUT"' EXIT
|
|
|
24
24
|
echo "==> snapshotting spec from $SPEC_URL"
|
|
25
25
|
curl --fail --silent --show-error -o "$SPEC_PATH" "$SPEC_URL"
|
|
26
26
|
|
|
27
|
-
echo "==> normalizing spec (drop narrow string enums)"
|
|
27
|
+
echo "==> normalizing spec (drop narrow string enums, patch missing path params)"
|
|
28
28
|
python3 - "$SPEC_PATH" <<'PY'
|
|
29
29
|
import json, sys
|
|
30
30
|
path = sys.argv[1]
|
|
@@ -35,8 +35,37 @@ for name, schema in spec.get('components', {}).get('schemas', {}).items():
|
|
|
35
35
|
if isinstance(prop, dict) and 'enum' in prop and prop.get('type') == 'string':
|
|
36
36
|
prop.pop('enum')
|
|
37
37
|
removed += 1
|
|
38
|
-
json.dump(spec, open(path, 'w'), indent=2)
|
|
39
38
|
print(f'dropped {removed} string enums')
|
|
39
|
+
|
|
40
|
+
# The live spec's POST /v1/reviews/{id}/reply omits the `id` path-parameter
|
|
41
|
+
# declaration (present in the path template, missing from `parameters`),
|
|
42
|
+
# which fails openapi-generator's spec validation. Patch it in-place, mirroring
|
|
43
|
+
# the declaration style used by sibling {id}-scoped write routes (e.g.
|
|
44
|
+
# /v1/channels/airbnb/alterations/{id}/accept). Remove this patch once the
|
|
45
|
+
# live spec declares the parameter itself.
|
|
46
|
+
patched = 0
|
|
47
|
+
for path_tpl, item in spec.get('paths', {}).items():
|
|
48
|
+
if '{id}' not in path_tpl:
|
|
49
|
+
continue
|
|
50
|
+
for method, op in item.items():
|
|
51
|
+
if method not in ('get', 'post', 'put', 'patch', 'delete') or not isinstance(op, dict):
|
|
52
|
+
continue
|
|
53
|
+
params = op.get('parameters', [])
|
|
54
|
+
if any(isinstance(p, dict) and p.get('in') == 'path' and p.get('name') == 'id' for p in params):
|
|
55
|
+
continue
|
|
56
|
+
params.append({
|
|
57
|
+
'name': 'id',
|
|
58
|
+
'in': 'path',
|
|
59
|
+
'required': True,
|
|
60
|
+
'schema': {'type': 'string'},
|
|
61
|
+
'description': 'Resource id from the path.',
|
|
62
|
+
})
|
|
63
|
+
op['parameters'] = params
|
|
64
|
+
patched += 1
|
|
65
|
+
print(f'patched missing path param: {method.upper()} {path_tpl}')
|
|
66
|
+
print(f'patched {patched} missing path-parameter declarations')
|
|
67
|
+
|
|
68
|
+
json.dump(spec, open(path, 'w'), indent=2)
|
|
40
69
|
PY
|
|
41
70
|
|
|
42
71
|
echo "==> generating ruby client into $GEN_OUT"
|
|
@@ -44,7 +73,7 @@ npx --yes @openapitools/openapi-generator-cli@latest generate \
|
|
|
44
73
|
-i "$SPEC_PATH" \
|
|
45
74
|
-g ruby \
|
|
46
75
|
-o "$GEN_OUT" \
|
|
47
|
-
--additional-properties=gemName=repull,moduleName=Repull,gemVersion=0.2.
|
|
76
|
+
--additional-properties=gemName=repull,moduleName=Repull,gemVersion=0.2.14
|
|
48
77
|
|
|
49
78
|
echo "==> syncing lib/ + docs/"
|
|
50
79
|
rm -rf "$ROOT/lib/repull" "$ROOT/lib/repull.rb" "$ROOT/docs"
|