# frozen_string_literal: true require 'faraday' require 'faraday_middleware' require 'json' require 'json_schema' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/inflector' require 'dato/version' require 'dato/repo' require 'dato/api_error' require 'cacert' module Dato module ApiClient def self.included(base) base.extend ClassMethods base.class_eval do attr_reader :token, :base_url, :schema, :extra_headers end end module ClassMethods def json_schema(subdomain) define_method(:initialize) do |token, options = {}| @token = token @base_url = options[:base_url] || "https://#{subdomain}.datocms.com" @extra_headers = options[:extra_headers] || {} end response = Faraday.get( # FOR DEV # "http://#{subdomain}.lvh.me:3001/docs/#{subdomain}-hyperschema.json" "https://#{subdomain}.datocms.com/docs/#{subdomain}-hyperschema.json" ) schema = JsonSchema.parse!(JSON.parse(response.body)) schema.expand_references! schema.definitions.each do |type, schema| is_collection = schema.links.select { |x| x.rel === 'instances' }.any? namespace = is_collection ? type.pluralize : type define_method(namespace) do instance_variable_set( "@#{namespace}", instance_variable_get("@#{namespace}") || Dato::Repo.new(self, type, schema) ) end end end end def put(absolute_path, body = {}, params = {}) request(:put, absolute_path, body, params) end def post(absolute_path, body = {}, params = {}) request(:post, absolute_path, body, params) end def get(absolute_path, params = {}) request(:get, absolute_path, nil, params) end def delete(absolute_path, params = {}) request(:delete, absolute_path, nil, params) end def request(*args) method, absolute_path, body, params = args response = connection.send(method, absolute_path, body) do |c| if params c.params = params end end if response.body.is_a?(Hash) response.body.with_indifferent_access else nil end rescue Faraday::SSLError => e raise e if ENV['SSL_CERT_FILE'] == Cacert.pem Cacert.set_in_env request(*args) rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e puts e.message raise e rescue Faraday::ClientError => e if e.response[:status] == 429 to_wait = e.response[:headers]['x-ratelimit-reset'].to_i puts "Rate limit exceeded, waiting #{to_wait} seconds..." sleep(to_wait + 1) request(*args) elsif e.response[:status] == 422 && batch_data_validation?(e.response) puts "Validating items, waiting 1 second and retrying..." sleep(1) request(*args) else error = ApiError.new(e.response) puts "====" puts error.message puts "====" raise error end end private def batch_data_validation?(response) body = begin JSON.parse(response[:body]) rescue JSON::ParserError => e nil end return false unless body return false unless body["data"] body["data"].any? do |e| e["attributes"]["code"] == "BATCH_DATA_VALIDATION_IN_PROGRESS" end rescue false end def connection default_headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json', 'Authorization' => "Bearer #{@token}", 'User-Agent' => "ruby-client v#{Dato::VERSION}", 'X-Api-Version' => '3' } options = { url: base_url, headers: default_headers.merge(extra_headers), } @connection ||= Faraday.new(options) do |c| c.request :json c.response :json, content_type: /\bjson$/ c.response :raise_error c.use FaradayMiddleware::FollowRedirects c.adapter :net_http end end end end