dato 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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