diff --git a/CHANGELOG.md b/CHANGELOG.md index dc17bfdbe..16318cf02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Note: For changes to the API, see https://shopify.dev/changelog?filter=api - [#1347](https://github.com/Shopify/shopify-api-ruby/pull/1347) Extend webhook registration to support filters - [#1344](https://github.com/Shopify/shopify-api-ruby/pull/1344) Allow ShopifyAPI::Webhooks::Registry to update a webhook when fields or metafield_namespaces are changed. - [#1343](https://github.com/Shopify/shopify-api-ruby/pull/1343) Make ShopifyAPI::Context::scope parameter optional. `scope` defaults to empty list `[]`. +- [#1348](https://github.com/Shopify/shopify-api-ruby/pull/1348) Add config option that will disable the REST API client and REST resources. New apps should use the GraphQL Admin API ## 14.6.0 diff --git a/docs/usage/rest.md b/docs/usage/rest.md index f1dd99aec..4d29010be 100644 --- a/docs/usage/rest.md +++ b/docs/usage/rest.md @@ -1,4 +1,6 @@ # Make a REST API call +> [!WARNING] +> The Admin REST API has been deprecated. New apps should use the GraphQL Admin API. For more information see [All in on GraphQL](https://www.shopify.com/ca/partners/blog/all-in-on-graphql). New apps will be created with the config option `rest_disabled: true`. This will raise a `ShopifyAPI::Errors::DisabledResourceError` if you try to use the REST API. Once OAuth is complete, we can use `ShopifyAPI`'s REST library to make authenticated API calls to the Shopify Admin API. #### Required Session diff --git a/lib/shopify_api/clients/rest/admin.rb b/lib/shopify_api/clients/rest/admin.rb index 04622795e..4c484851a 100644 --- a/lib/shopify_api/clients/rest/admin.rb +++ b/lib/shopify_api/clients/rest/admin.rb @@ -9,6 +9,11 @@ class Admin < HttpClient sig { params(session: T.nilable(Auth::Session), api_version: T.nilable(String)).void } def initialize(session: nil, api_version: nil) + if Context.rest_disabled + raise Errors::DisabledResourceError, + "The Admin REST API has been deprecated. Please use the GraphQL Admin API. For more information see https://www.shopify.com/ca/partners/blog/all-in-on-graphql" + end + @api_version = T.let(api_version || Context.api_version, String) if api_version if api_version == Context.api_version diff --git a/lib/shopify_api/context.rb b/lib/shopify_api/context.rb index 73919ad7a..1e0f62ceb 100644 --- a/lib/shopify_api/context.rb +++ b/lib/shopify_api/context.rb @@ -22,6 +22,7 @@ class Context @user_agent_prefix = T.let(nil, T.nilable(String)) @old_api_secret_key = T.let(nil, T.nilable(String)) @response_as_struct = T.let(false, T.nilable(T::Boolean)) + @rest_disabled = T.let(false, T.nilable(T::Boolean)) @rest_resource_loader = T.let(nil, T.nilable(Zeitwerk::Loader)) @@ -45,6 +46,7 @@ class << self old_api_secret_key: T.nilable(String), api_host: T.nilable(String), response_as_struct: T.nilable(T::Boolean), + rest_disabled: T.nilable(T::Boolean), ).void end def setup( @@ -62,7 +64,8 @@ def setup( user_agent_prefix: nil, old_api_secret_key: nil, api_host: nil, - response_as_struct: false + response_as_struct: false, + rest_disabled: false ) unless ShopifyAPI::AdminVersions::SUPPORTED_ADMIN_VERSIONS.include?(api_version) raise Errors::UnsupportedVersionError, @@ -82,6 +85,7 @@ def setup( @user_agent_prefix = user_agent_prefix @old_api_secret_key = old_api_secret_key @response_as_struct = response_as_struct + @rest_disabled = rest_disabled @log_level = if valid_log_level?(log_level) log_level.to_sym else @@ -178,6 +182,11 @@ def host_name T.must(URI(T.must(host)).host) end + sig { returns(T::Boolean) } + def rest_disabled + T.must(@rest_disabled) + end + private sig { params(log_level: T.any(Symbol, String)).returns(T::Boolean) } diff --git a/lib/shopify_api/errors/disabled_resource_error.rb b/lib/shopify_api/errors/disabled_resource_error.rb new file mode 100644 index 000000000..d8187aa9c --- /dev/null +++ b/lib/shopify_api/errors/disabled_resource_error.rb @@ -0,0 +1,8 @@ +# typed: strict +# frozen_string_literal: true + +module ShopifyAPI + module Errors + class DisabledResourceError < StandardError; end + end +end diff --git a/test/clients/base_rest_resource_test.rb b/test/clients/base_rest_resource_test.rb index 56859dffb..fd5e83204 100644 --- a/test/clients/base_rest_resource_test.rb +++ b/test/clients/base_rest_resource_test.rb @@ -14,6 +14,29 @@ def setup ShopifyAPI::Context.load_rest_resources(api_version: ShopifyAPI::Context.api_version) end + def test_rest_disabled + ShopifyAPI::Context.setup( + api_key: "test-key", + api_secret_key: "test-secret-key", + api_version: "2023-01", + is_private: true, + is_embedded: false, + rest_disabled: true, + ) + assert_raises(ShopifyAPI::Errors::DisabledResourceError) do + TestHelpers::FakeResource.find(id: 1, session: @session) + end + + ShopifyAPI::Context.setup( + api_key: "test-key", + api_secret_key: "test-secret-key", + api_version: "2023-01", + is_private: true, + is_embedded: false, + rest_disabled: false, + ) + end + def test_finds_resource_by_id body = { fake_resource: { id: 1, attribute: "attribute" } }.to_json diff --git a/test/clients/rest/admin_test.rb b/test/clients/rest/admin_test.rb index 626883d9e..4b63c9362 100644 --- a/test/clients/rest/admin_test.rb +++ b/test/clients/rest/admin_test.rb @@ -50,6 +50,44 @@ def test_api_version_can_be_overrriden run_test(:post) end + def test_raises_error_when_rest_is_disabled + ShopifyAPI::Context.setup( + api_key: "test-key", + api_secret_key: "test-secret-key", + api_version: "2023-01", + is_private: true, + is_embedded: false, + rest_disabled: true, + ) + session = ShopifyAPI::Auth::Session.new( + shop: "test-shop.myshopify.com", + access_token: SecureRandom.alphanumeric(10), + ) + + assert_raises(ShopifyAPI::Errors::DisabledResourceError, + "REST API access has been disabled via Context.rest_disabled") do + ShopifyAPI::Clients::Rest::Admin.new(session: session) + end + end + + def test_client_works_normally_when_rest_is_not_disabled + ShopifyAPI::Context.setup( + api_key: "test-key", + api_secret_key: "test-secret-key", + api_version: "2023-01", + is_private: true, + is_embedded: false, + rest_disabled: false, + ) + session = ShopifyAPI::Auth::Session.new( + shop: "test-shop.myshopify.com", + access_token: SecureRandom.alphanumeric(10), + ) + + client = ShopifyAPI::Clients::Rest::Admin.new(session: session) + refute_nil(client) + end + private def run_test(http_method, path = "/some-path", expected_path = "some-path.json") diff --git a/test/context_test.rb b/test/context_test.rb index d15333c5e..a508a56c6 100644 --- a/test/context_test.rb +++ b/test/context_test.rb @@ -178,6 +178,29 @@ def test_scope_config_can_be_optional_and_defaults_to_empty assert_equal(ShopifyAPI::Auth::AuthScopes.new, ShopifyAPI::Context.scope) end + def test_rest_disabled_defaults_to_false + ShopifyAPI::Context.setup( + api_key: "test-key", + api_secret_key: "test-secret-key", + api_version: "2023-01", + is_private: true, + is_embedded: false, + ) + refute(ShopifyAPI::Context.rest_disabled) + end + + def test_rest_disabled_can_be_set_in_setup + ShopifyAPI::Context.setup( + api_key: "test-key", + api_secret_key: "test-secret-key", + api_version: "2023-01", + is_private: true, + is_embedded: false, + rest_disabled: true, + ) + assert(ShopifyAPI::Context.rest_disabled) + end + def teardown ShopifyAPI::Context.deactivate_session end