dato 0.5.1 → 0.6.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9b90a79945cd778861e44a0d09bfffad27a606d4f51e448cf3a83c85ef98b5a
4
- data.tar.gz: 9dfa1d22880392d377e0fd15d2f72302c7acbc242d58d954ebfe7fa4242e1a26
3
+ metadata.gz: 7107aad93e6809c4140c2167e1d5ce2cc446c45bf05e66521916a357a52f2b2a
4
+ data.tar.gz: a3f336225fa11c3d721b07171b432201326b03e58fb394ec0f26911c5320f8e9
5
5
  SHA512:
6
- metadata.gz: 29413d8c200bcc4c648151409d6be67889edb172325fc78e8b9d81d4042eae7c6a880aef623c505a2c697ec727577d393b4cdff861f8f2369ce63ec1cf20485d
7
- data.tar.gz: 46b2fb45112a58f7942deeda7143d2493976ae4957f2a6996b75b06c56cdb892a142355da83f09cbed705bacf249f9c7249f2ba652eb64b6199dccfed16bb7b8
6
+ metadata.gz: df93d0a0883dd2a6d728cb5f8ceb7e4b6c46f9063a75bc79ecf1442458f67f5a32aa0a95f7be9a9fc89360b02636206c1bf4bcc6f7689847eb2d2c7e9a269969
7
+ data.tar.gz: 4a733da2b3626f47018fc6fadefb96d50ffd4959b871f952c78c382225504faa6d894d4aaaed68ba458d91fa6e138fe18ec2e28d550d840a2771c6e823cf48a4
data/Rakefile CHANGED
@@ -1,44 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'bundler/gem_tasks'
3
3
  require 'rspec/core/rake_task'
4
- require 'open-uri'
5
-
6
- require_relative './build/build_client'
7
4
 
8
5
  RSpec::Core::RakeTask.new(:spec)
9
-
10
6
  task default: :spec
11
-
12
- desc 'Regenerates the client starting from the JSON Hyperschema'
13
- task :regenerate do
14
- BuildClient.new(
15
- open('https://site-api.datocms.com/docs/site-api-hyperschema.json').read,
16
- 'site',
17
- %w(session item upload user#update)
18
- ).build
19
-
20
- BuildClient.new(
21
- open('https://site-api.datocms.com/docs/account-api-hyperschema.json').read,
22
- 'account',
23
- %w(
24
- session
25
- account#create account#reset_password
26
- subscription
27
- portal_session
28
- )
29
- ).build
30
- end
31
-
32
- desc 'Open an irb (or pry) session preloaded with this gem'
33
- task :console do
34
- begin
35
- require 'pry'
36
- gem_name = File.basename(Dir.pwd)
37
- sh %(pry -I lib -r #{gem_name}.rb)
38
- rescue LoadError => _
39
- sh %(irb -rubygems -I lib -r #{gem_name}.rb)
40
- end
41
- end
42
-
43
- require 'rubocop/rake_task'
44
- RuboCop::RakeTask.new
@@ -1,79 +1,12 @@
1
1
  # frozen_string_literal: true
2
- require 'faraday'
3
- require 'faraday_middleware'
4
- require 'json'
5
- require 'active_support/core_ext/hash/indifferent_access'
6
-
7
- require 'dato/version'
8
-
9
- require 'dato/account/repo/account'
10
- require 'dato/account/repo/site'
11
- require 'dato/account/repo/deploy_event'
12
- require 'dato/api_error'
13
-
14
- require 'cacert'
2
+ require 'dato/api_client'
15
3
 
16
4
  module Dato
17
5
  module Account
18
6
  class Client
19
- REPOS = {
20
- account: Repo::Account,
21
- sites: Repo::Site,
22
- deploy_events: Repo::DeployEvent
23
- }.freeze
24
-
25
- attr_reader :token, :base_url, :schema, :extra_headers
26
-
27
- def initialize(token, options = {})
28
- @token = token
29
- @base_url = options[:base_url] || 'https://account-api.datocms.com'
30
- @extra_headers = options[:extra_headers] || {}
31
- end
32
-
33
- REPOS.each do |method_name, repo_klass|
34
- define_method method_name do
35
- instance_variable_set(
36
- "@#{method_name}",
37
- instance_variable_get("@#{method_name}") ||
38
- repo_klass.new(self)
39
- )
40
- end
41
- end
42
-
43
- def request(*args)
44
- connection.send(*args).body.with_indifferent_access
45
- rescue Faraday::SSLError => e
46
- raise e if ENV['SSL_CERT_FILE'] == Cacert.pem
47
-
48
- Cacert.set_in_env
49
- request(*args)
50
- rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
51
- raise e
52
- rescue Faraday::ClientError => e
53
- raise ApiError, e
54
- end
55
-
56
- private
57
-
58
- def connection
59
- options = {
60
- url: base_url,
61
- headers: extra_headers.merge(
62
- 'Accept' => 'application/json',
63
- 'Content-Type' => 'application/json',
64
- 'Authorization' => "Bearer #{@token}",
65
- 'User-Agent' => "ruby-client v#{Dato::VERSION}"
66
- )
67
- }
7
+ include ApiClient
68
8
 
69
- @connection ||= Faraday.new(options) do |c|
70
- c.request :json
71
- c.response :json, content_type: /\bjson$/
72
- c.response :raise_error
73
- c.use FaradayMiddleware::FollowRedirects
74
- c.adapter :net_http
75
- end
76
- end
9
+ json_schema 'account-api'
77
10
  end
78
11
  end
79
12
  end
@@ -0,0 +1,98 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'json'
4
+ require 'json_schema'
5
+ require 'active_support/core_ext/hash/indifferent_access'
6
+ require 'active_support/inflector'
7
+
8
+ require 'dato/version'
9
+ require 'dato/repo'
10
+
11
+ require 'dato/api_error'
12
+
13
+ require 'cacert'
14
+
15
+ module Dato
16
+ module ApiClient
17
+ def self.included(base)
18
+ base.extend ClassMethods
19
+
20
+ base.class_eval do
21
+ attr_reader :token, :base_url, :schema, :extra_headers
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ def json_schema(subdomain)
27
+ define_method(:initialize) do |token, options = {}|
28
+ @token = token
29
+ @base_url = options[:base_url] || "https://#{subdomain}.datocms.com"
30
+ @extra_headers = options[:extra_headers] || {}
31
+ end
32
+
33
+ url = URI.parse("https://#{subdomain}.datocms.com/docs/#{subdomain}-hyperschema.json")
34
+ response = Net::HTTP.get(url)
35
+
36
+ schema = JsonSchema.parse!(JSON.parse(response))
37
+ schema.expand_references!
38
+
39
+ schema.definitions.each do |type, schema|
40
+ is_collection = schema.links.select{|x| x.rel === "instances"}.any?
41
+
42
+ if is_collection
43
+ type = type.pluralize
44
+ end
45
+
46
+ define_method(type) do
47
+ instance_variable_set(
48
+ "@#{type}",
49
+ instance_variable_get("@#{type}") ||
50
+ Dato::Repo.new(self, type, schema)
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def request(*args)
58
+ connection.send(*args).body.with_indifferent_access
59
+ rescue Faraday::SSLError => e
60
+ raise e if ENV['SSL_CERT_FILE'] == Cacert.pem
61
+
62
+ Cacert.set_in_env
63
+ request(*args)
64
+ rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
65
+ puts e.message
66
+ raise e
67
+ rescue Faraday::ClientError => e
68
+ error = ApiError.new(e)
69
+ puts '===='
70
+ puts error.message
71
+ puts '===='
72
+ raise error
73
+ end
74
+
75
+ private
76
+
77
+ def connection
78
+ options = {
79
+ url: base_url,
80
+ headers: extra_headers.merge(
81
+ 'Accept' => 'application/json',
82
+ 'Content-Type' => 'application/json',
83
+ 'Authorization' => "Bearer #{@token}",
84
+ 'User-Agent' => "ruby-client v#{Dato::VERSION}",
85
+ 'X-Api-Version' => "2"
86
+ )
87
+ }
88
+
89
+ @connection ||= Faraday.new(options) do |c|
90
+ c.request :json
91
+ c.response :json, content_type: /\bjson$/
92
+ c.response :raise_error
93
+ c.use FaradayMiddleware::FollowRedirects
94
+ c.adapter :net_http
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,21 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
  module Dato
3
3
  class JsonApiSerializer
4
- attr_reader :type, :attributes, :relationships
5
- attr_reader :required_attributes, :required_relationships
6
-
7
- def initialize(
8
- type:,
9
- attributes: [],
10
- required_attributes: [],
11
- relationships: {},
12
- required_relationships: []
13
- )
4
+ attr_reader :link, :type
5
+
6
+ def initialize(type, link)
7
+ @link = link
14
8
  @type = type
15
- @attributes = attributes
16
- @required_attributes = required_attributes
17
- @relationships = relationships
18
- @required_relationships = required_relationships
19
9
  end
20
10
 
21
11
  def serialize(resource, id = nil)
@@ -37,7 +27,7 @@ module Dato
37
27
  def serialized_attributes(resource)
38
28
  result = {}
39
29
 
40
- attributes.each do |attribute|
30
+ attributes(resource).each do |attribute|
41
31
  if resource.key? attribute
42
32
  result[attribute] = resource[attribute]
43
33
  elsif required_attributes.include? attribute
@@ -54,7 +44,6 @@ module Dato
54
44
  relationships.each do |relationship, meta|
55
45
  if resource.key? relationship
56
46
  value = resource[relationship]
57
-
58
47
  data = if value
59
48
  if meta[:collection]
60
49
  value.map do |id|
@@ -66,12 +55,78 @@ module Dato
66
55
  end
67
56
  result[relationship] = { data: data }
68
57
 
69
- elsif required_relationships.include? relationship
58
+ elsif required_relationships.include?(relationship)
70
59
  throw "Required attribute: #{relationship}"
71
60
  end
72
61
  end
73
62
 
74
63
  result
75
64
  end
65
+
66
+ def attributes(resource)
67
+ if type == "item"
68
+ resource.keys - [:item_type, :id]
69
+ end
70
+
71
+ link_attributes["properties"].keys.map(&:to_sym)
72
+ end
73
+
74
+ def required_attributes
75
+ if type == "item"
76
+ []
77
+ end
78
+
79
+ (link_attributes.required || []).map(&:to_sym)
80
+ end
81
+
82
+ def relationships
83
+ if type == "item"
84
+ {
85
+ item_type: { collection: false, type: 'item_type' }
86
+ }
87
+ end
88
+
89
+ if !link_relationships
90
+ return {}
91
+ end
92
+
93
+ link_relationships.properties.reduce({}) do |acc, (relationship, schema)|
94
+ is_collection = schema.properties["data"].type.first == 'array'
95
+
96
+ definition = if is_collection
97
+ schema.properties['data'].items
98
+ elsif schema.properties['data'].type.first == 'object'
99
+ schema.properties['data']
100
+ else
101
+ schema.properties['data'].any_of.find do |option|
102
+ option.type.first == 'object'
103
+ end
104
+ end
105
+
106
+ type = definition.properties['type']
107
+ .pattern.source.gsub(/(^\^|\$$)/, '')
108
+
109
+ acc[relationship.to_sym] = {
110
+ collection: is_collection,
111
+ type: type,
112
+ }
113
+ end
114
+ end
115
+
116
+ def required_relationships
117
+ if type == "item"
118
+ %i(item_type)
119
+ end
120
+
121
+ (link_relationships.required || []).map(&:to_sym)
122
+ end
123
+
124
+ def link_attributes
125
+ link.schema.properties["data"].properties["attributes"]
126
+ end
127
+
128
+ def link_relationships
129
+ link.schema.properties["data"].properties["relationships"]
130
+ end
76
131
  end
77
132
  end
@@ -0,0 +1,33 @@
1
+ module Dato
2
+ class Paginator
3
+ def initialize(client, base_endpoint, filters)
4
+ @client, @base_endpoint, @filters = client, base_endpoint, filters
5
+ end
6
+
7
+ def response
8
+ items_per_page = 100
9
+
10
+ base_response = @client.request(
11
+ :get, @base_endpoint, @filters.dup.merge('page[limit]' => items_per_page)
12
+ )
13
+
14
+ extra_pages = (
15
+ base_response[:meta][:total_count] / items_per_page.to_f
16
+ ).ceil - 1
17
+
18
+ extra_pages.times do |page|
19
+ base_response[:data] += @client.request(
20
+ :get,
21
+ @base_endpoint,
22
+ @filters.dup.merge(
23
+ 'page[offset]' => items_per_page * (page + 1),
24
+ 'page[limit]' => items_per_page
25
+ )
26
+ )[:data]
27
+ end
28
+
29
+ base_response
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+ require 'dato/json_api_serializer'
3
+ require 'dato/json_api_deserializer'
4
+ require 'dato/paginator'
5
+
6
+ module Dato
7
+ class Repo
8
+ attr_reader :client, :type, :schema
9
+
10
+ IDENTITY_REGEXP = /\{\(.*?definitions%2F(.*?)%2Fdefinitions%2Fidentity\)}/
11
+
12
+ METHOD_NAMES = {
13
+ "instances" => :all,
14
+ "self" => :find,
15
+ }
16
+
17
+ def initialize(client, type, schema)
18
+ @client = client
19
+ @type = type
20
+ @schema = schema
21
+
22
+ schema.links.each do |link|
23
+ method_name = METHOD_NAMES.fetch(link.rel, link.rel)
24
+
25
+ self.define_singleton_method(method_name) do |*args|
26
+ min_arguments_count = [
27
+ link.href.scan(IDENTITY_REGEXP).size,
28
+ link.schema && link.method != :get ? 1 : 0
29
+ ].sum
30
+
31
+ args.size >= min_arguments_count or
32
+ raise ArgumentError.new(
33
+ "wrong number of arguments (given #{args.size}, expected #{min_arguments_count})"
34
+ )
35
+
36
+ last_url_id = nil
37
+
38
+ url = link["href"].gsub(IDENTITY_REGEXP) do |_stuff|
39
+ last_url_id = args.shift.to_s
40
+ end
41
+
42
+ response = if %i(post put).include?(link.method)
43
+ body = if link.schema
44
+ unserialized_body = args.shift
45
+
46
+ JsonApiSerializer.new(type, link).serialize(
47
+ unserialized_body,
48
+ last_url_id
49
+ )
50
+ else
51
+ {}
52
+ end
53
+
54
+ client.request(link.method, url, body)
55
+
56
+ elsif link.method == :delete
57
+ client.request(:delete, url)
58
+
59
+ elsif link.method == :get
60
+ query_string = args.shift
61
+
62
+ all_pages = (args[0] || {}).
63
+ symbolize_keys.
64
+ fetch(:all_pages, false)
65
+
66
+ is_paginated_endpoint = link.schema &&
67
+ link.schema.properties.has_key?("page[limit]")
68
+
69
+ if is_paginated_endpoint && all_pages
70
+ Paginator.new(client, url, query_string).response
71
+ else
72
+ client.request(:get, url, query_string)
73
+ end
74
+ end
75
+
76
+ options = if args.any?
77
+ args.shift.symbolize_keys
78
+ else
79
+ {}
80
+ end
81
+
82
+ if options.fetch(:deserialize_response, true)
83
+ JsonApiDeserializer.new.deserialize(response)
84
+ else
85
+ response
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end