wcc-contentful 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +1 -1
- data/.gitignore +5 -0
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +8 -1
- data/Guardfile +23 -1
- data/app/controllers/wcc/contentful/application_controller.rb +7 -0
- data/app/controllers/wcc/contentful/webhook_controller.rb +30 -0
- data/app/jobs/wcc/contentful/delayed_sync_job.rb +14 -0
- data/bin/rails +14 -0
- data/config/initializers/mime_types.rb +3 -0
- data/config/routes.rb +5 -0
- data/lib/generators/wcc/menu_generator.rb +4 -4
- data/lib/generators/wcc/templates/contentful_shell_wrapper +109 -68
- data/lib/generators/wcc/templates/menu/menu.rb +4 -6
- data/lib/generators/wcc/templates/menu/menu_button.rb +4 -6
- data/lib/generators/wcc/templates/release +2 -2
- data/lib/generators/wcc/templates/wcc_contentful.rb +0 -1
- data/lib/wcc/contentful/client_ext.rb +1 -1
- data/lib/wcc/contentful/configuration.rb +76 -35
- data/lib/wcc/contentful/engine.rb +13 -0
- data/lib/wcc/contentful/exceptions.rb +6 -0
- data/lib/wcc/contentful/graphql/builder.rb +8 -3
- data/lib/wcc/contentful/helpers.rb +6 -0
- data/lib/wcc/contentful/indexed_representation.rb +31 -0
- data/lib/wcc/contentful/model.rb +82 -1
- data/lib/wcc/contentful/model_builder.rb +18 -8
- data/lib/wcc/contentful/model_validators.rb +69 -18
- data/lib/wcc/contentful/simple_client/http_adapter.rb +15 -0
- data/lib/wcc/contentful/simple_client/typhoeus_adapter.rb +30 -0
- data/lib/wcc/contentful/simple_client.rb +67 -18
- data/lib/wcc/contentful/store/base.rb +89 -0
- data/lib/wcc/contentful/store/cdn_adapter.rb +13 -19
- data/lib/wcc/contentful/store/lazy_cache_store.rb +76 -0
- data/lib/wcc/contentful/store/memory_store.rb +17 -23
- data/lib/wcc/contentful/store/postgres_store.rb +32 -19
- data/lib/wcc/contentful/store.rb +62 -0
- data/lib/wcc/contentful/version.rb +1 -1
- data/lib/wcc/contentful.rb +113 -24
- data/wcc-contentful.gemspec +4 -0
- metadata +75 -2
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'http'
|
4
|
-
|
5
3
|
class WCC::Contentful::Configuration
|
6
4
|
ATTRIBUTES = %i[
|
7
5
|
access_token
|
@@ -9,47 +7,79 @@ class WCC::Contentful::Configuration
|
|
9
7
|
space
|
10
8
|
default_locale
|
11
9
|
content_delivery
|
12
|
-
|
10
|
+
http_adapter
|
11
|
+
sync_cache_store
|
12
|
+
webhook_username
|
13
|
+
webhook_password
|
13
14
|
].freeze
|
14
15
|
attr_accessor(*ATTRIBUTES)
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
##
|
18
|
+
# Defines the method by which content is downloaded from the Contentful CDN.
|
19
|
+
#
|
20
|
+
# [:direct] `config.content_delivery = :direct`
|
21
|
+
# with the `:direct` method, all queries result in web requests to
|
22
|
+
# 'https://cdn.contentful.com' via the
|
23
|
+
# {SimpleClient}[rdoc-ref:WCC::Contentful::SimpleClient::Cdn]
|
24
|
+
#
|
25
|
+
# [:eager_sync] `config.content_delivery = :eager_sync, [sync_store], [options]`
|
26
|
+
# with the `:eager_sync` method, the entire content of the Contentful
|
27
|
+
# space is downloaded locally and stored in the
|
28
|
+
# {Sync Store}[rdoc-ref:WCC::Contentful.store]. The application is responsible
|
29
|
+
# to periodically call `WCC::Contentful.sync!` to keep the store updated.
|
30
|
+
# Alternatively, the provided {Engine}[WCC::Contentful::Engine]
|
31
|
+
# can be mounted to receive a webhook from the Contentful space
|
32
|
+
# on publish events:
|
33
|
+
# mount WCC::Contentful::Engine, at: '/wcc/contentful'
|
34
|
+
#
|
35
|
+
# [:lazy_sync] `config.content_delivery = :lazy_sync, [cache]`
|
36
|
+
# The `:lazy_sync` method is a hybrid between the other two methods.
|
37
|
+
# Frequently accessed data is stored in an ActiveSupport::Cache implementation
|
38
|
+
# and is kept up-to-date via the Sync API. Any data that is not present
|
39
|
+
# in the cache is fetched from the CDN like in the `:direct` method.
|
40
|
+
# The application is still responsible to periodically call `sync!`
|
41
|
+
# or to mount the provided Engine.
|
42
|
+
#
|
43
|
+
def content_delivery=(params)
|
44
|
+
cd, *cd_params = params
|
45
|
+
unless cd.is_a? Symbol
|
46
|
+
raise ArgumentError, 'content_delivery must be a symbol, use store= to '\
|
47
|
+
'directly set contentful CDN access adapter'
|
48
|
+
end
|
21
49
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
}
|
28
|
-
}.freeze
|
50
|
+
WCC::Contentful::Store::Factory.new(
|
51
|
+
self,
|
52
|
+
cd,
|
53
|
+
cd_params
|
54
|
+
).validate!
|
29
55
|
|
30
|
-
|
31
|
-
|
32
|
-
@content_delivery = symbol
|
56
|
+
@content_delivery = cd
|
57
|
+
@content_delivery_params = cd_params
|
33
58
|
end
|
34
59
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
60
|
+
##
|
61
|
+
# Initializes the configured Sync Store.
|
62
|
+
def store
|
63
|
+
@store ||= WCC::Contentful::Store::Factory.new(
|
64
|
+
self,
|
65
|
+
@content_delivery,
|
66
|
+
@content_delivery_params
|
67
|
+
).build_sync_store
|
42
68
|
end
|
43
69
|
|
44
|
-
|
45
|
-
|
46
|
-
|
70
|
+
##
|
71
|
+
# Directly sets the adapter layer for communicating with Contentful
|
72
|
+
def store=(value)
|
73
|
+
@content_delivery = :custom
|
74
|
+
@store = value
|
47
75
|
end
|
48
76
|
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
|
77
|
+
# Sets the adapter which is used to make HTTP requests.
|
78
|
+
# If left unset, the gem attempts to load either 'http' or 'typhoeus'.
|
79
|
+
# You can pass your own adapter which responds to 'call', or even a lambda
|
80
|
+
# that accepts the following parameters:
|
81
|
+
# ->(url, query, headers = {}, proxy = {}) { ... }
|
82
|
+
attr_writer :http_adapter
|
53
83
|
|
54
84
|
def initialize
|
55
85
|
@access_token = ''
|
@@ -57,12 +87,21 @@ class WCC::Contentful::Configuration
|
|
57
87
|
@space = ''
|
58
88
|
@default_locale = nil
|
59
89
|
@content_delivery = :direct
|
60
|
-
@sync_store = :memory
|
61
90
|
end
|
62
91
|
|
92
|
+
##
|
93
|
+
# Gets a {CDN Client}[rdoc-ref:WCC::Contentful::SimpleClient::Cdn] which provides
|
94
|
+
# methods for getting and paging raw JSON data from the Contentful CDN.
|
63
95
|
attr_reader :client
|
64
96
|
attr_reader :management_client
|
65
97
|
|
98
|
+
##
|
99
|
+
# Called by WCC::Contentful.init! to configure the
|
100
|
+
# Contentful clients. This method can be called independently of `init!` if
|
101
|
+
# the application would prefer not to generate all the models.
|
102
|
+
#
|
103
|
+
# If the {contentful.rb}[https://github.com/contentful/contentful.rb] gem is
|
104
|
+
# loaded, it is extended to make use of the `http_adapter` lambda.
|
66
105
|
def configure_contentful
|
67
106
|
@client = nil
|
68
107
|
@management_client = nil
|
@@ -81,13 +120,15 @@ class WCC::Contentful::Configuration
|
|
81
120
|
@client = WCC::Contentful::SimpleClient::Cdn.new(
|
82
121
|
access_token: access_token,
|
83
122
|
space: space,
|
84
|
-
default_locale: default_locale
|
123
|
+
default_locale: default_locale,
|
124
|
+
adapter: http_adapter
|
85
125
|
)
|
86
126
|
return unless management_token.present?
|
87
127
|
@management_client = WCC::Contentful::SimpleClient::Management.new(
|
88
128
|
management_token: management_token,
|
89
129
|
space: space,
|
90
|
-
default_locale: default_locale
|
130
|
+
default_locale: default_locale,
|
131
|
+
adapter: http_adapter
|
91
132
|
)
|
92
133
|
end
|
93
134
|
end
|
@@ -46,7 +46,7 @@ module WCC::Contentful::Graphql
|
|
46
46
|
|
47
47
|
resolve ->(_obj, args, _ctx) {
|
48
48
|
if args['id'].nil?
|
49
|
-
store.find_by(content_type: content_type)
|
49
|
+
store.find_by(content_type: content_type)
|
50
50
|
else
|
51
51
|
store.find(args['id'])
|
52
52
|
end
|
@@ -58,8 +58,13 @@ module WCC::Contentful::Graphql
|
|
58
58
|
argument :filter, Types::FilterType
|
59
59
|
|
60
60
|
resolve ->(_obj, args, ctx) {
|
61
|
-
relation = store.
|
62
|
-
|
61
|
+
relation = store.find_all(content_type: content_type)
|
62
|
+
# TODO: improve this POC
|
63
|
+
if args[:filter]
|
64
|
+
filter = {}
|
65
|
+
filter[args[:filter]['field']] = { eq: args[:filter][:eq] }
|
66
|
+
relation = relation.apply(filter, ctx)
|
67
|
+
end
|
63
68
|
relation.result
|
64
69
|
}
|
65
70
|
end
|
@@ -32,6 +32,16 @@ module WCC::Contentful
|
|
32
32
|
@types.to_json
|
33
33
|
end
|
34
34
|
|
35
|
+
def deep_dup
|
36
|
+
self.class.new(@types.deep_dup)
|
37
|
+
end
|
38
|
+
|
39
|
+
def ==(other)
|
40
|
+
my_keys = keys
|
41
|
+
return false unless my_keys == other.keys
|
42
|
+
my_keys.all? { |k| self[k] == other[k] }
|
43
|
+
end
|
44
|
+
|
35
45
|
class ContentType
|
36
46
|
ATTRIBUTES = %i[
|
37
47
|
name
|
@@ -57,6 +67,18 @@ module WCC::Contentful
|
|
57
67
|
|
58
68
|
hash_or_id.each { |k, v| public_send("#{k}=", v) }
|
59
69
|
end
|
70
|
+
|
71
|
+
def deep_dup
|
72
|
+
dup_hash =
|
73
|
+
ATTRIBUTES.each_with_object({}) do |att, h|
|
74
|
+
h[att] = public_send(att)
|
75
|
+
end
|
76
|
+
self.class.new(dup_hash)
|
77
|
+
end
|
78
|
+
|
79
|
+
def ==(other)
|
80
|
+
ATTRIBUTES.all? { |att| public_send(att) == other.public_send(att) }
|
81
|
+
end
|
60
82
|
end
|
61
83
|
|
62
84
|
class Field
|
@@ -96,6 +118,11 @@ module WCC::Contentful
|
|
96
118
|
return
|
97
119
|
end
|
98
120
|
|
121
|
+
unless hash_or_id.is_a?(Hash)
|
122
|
+
ATTRIBUTES.each { |att| public_send("#{att}=", hash_or_id.public_send(att)) }
|
123
|
+
return
|
124
|
+
end
|
125
|
+
|
99
126
|
if raw_type = hash_or_id.delete('type')
|
100
127
|
raw_type = raw_type.to_sym
|
101
128
|
unless TYPES.include?(raw_type)
|
@@ -106,6 +133,10 @@ module WCC::Contentful
|
|
106
133
|
|
107
134
|
hash_or_id.each { |k, v| public_send("#{k}=", v) }
|
108
135
|
end
|
136
|
+
|
137
|
+
def ==(other)
|
138
|
+
ATTRIBUTES.all? { |att| public_send(att) == other.public_send(att) }
|
139
|
+
end
|
109
140
|
end
|
110
141
|
end
|
111
142
|
end
|
data/lib/wcc/contentful/model.rb
CHANGED
@@ -5,20 +5,101 @@ class WCC::Contentful::Model
|
|
5
5
|
extend WCC::Contentful::Helpers
|
6
6
|
extend WCC::Contentful::ModelValidators
|
7
7
|
|
8
|
+
# The Model base class maintains a registry which is best expressed as a
|
9
|
+
# class var.
|
10
|
+
# rubocop:disable Style/ClassVars
|
11
|
+
|
8
12
|
class << self
|
13
|
+
##
|
14
|
+
# The configured store which executes all model queries against either the
|
15
|
+
# Contentful CDN or a locally-downloaded copy.
|
16
|
+
#
|
17
|
+
# See the {sync_store}[rdoc-ref:WCC::Contentful::Configuration.sync_store] parameter
|
18
|
+
# on the WCC::Contentful::Configuration class.
|
9
19
|
attr_accessor :store
|
20
|
+
|
21
|
+
def const_missing(name)
|
22
|
+
raise WCC::Contentful::ContentTypeNotFoundError,
|
23
|
+
"Content type '#{content_type_from_constant(name)}' does not exist in the space"
|
24
|
+
end
|
10
25
|
end
|
11
26
|
|
27
|
+
@@registry = {}
|
28
|
+
|
12
29
|
def self.all_models
|
30
|
+
# TODO: this needs to use the registry but it's OK for now cause we only
|
31
|
+
# use it in specs
|
13
32
|
WCC::Contentful::Model.constants(false).map { |k| WCC::Contentful::Model.const_get(k) }
|
14
33
|
end
|
15
34
|
|
35
|
+
##
|
36
|
+
# Finds an Entry or Asset by ID in the configured contentful space
|
37
|
+
# and returns an initialized instance of the appropriate model type.
|
38
|
+
#
|
39
|
+
# Makes use of the configured {store}[rdoc-ref:WCC::Contentful::Model.store]
|
40
|
+
# to access the Contentful CDN.
|
16
41
|
def self.find(id, context = nil)
|
17
42
|
return unless raw = store.find(id)
|
18
43
|
|
19
44
|
content_type = content_type_from_raw(raw)
|
20
45
|
|
21
|
-
const =
|
46
|
+
unless const = @@registry[content_type]
|
47
|
+
begin
|
48
|
+
# The app may have defined a model and we haven't loaded it yet
|
49
|
+
const = Object.const_missing(constant_from_content_type(content_type).to_s)
|
50
|
+
rescue NameError
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
unless const
|
55
|
+
# Autoloading couldn't find their model - we'll register our own.
|
56
|
+
const = WCC::Contentful::Model.const_get(constant_from_content_type(content_type))
|
57
|
+
register_for_content_type(content_type, klass: const)
|
58
|
+
end
|
59
|
+
|
22
60
|
const.new(raw, context)
|
23
61
|
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Registers a class constant to be instantiated when resolving an instance
|
65
|
+
# of the given content type. This automatically happens for the first subclass
|
66
|
+
# of a generated model type, example:
|
67
|
+
#
|
68
|
+
# class MyMenu < WCC::Contentful::Model::Menu
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# In the above case, instances of MyMenu will be instantiated whenever a 'menu'
|
72
|
+
# content type is resolved.
|
73
|
+
# The mapping can be made explicit with the optional parameters. Example:
|
74
|
+
#
|
75
|
+
# class MyFoo < WCC::Contentful::Model::Foo
|
76
|
+
# register_for_content_type 'bar' # MyFoo is assumed
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# # in initializers/wcc_contentful.rb
|
80
|
+
# WCC::Contentful::Model.register_for_content_type('bar', klass: MyFoo)
|
81
|
+
def self.register_for_content_type(content_type = nil, klass: nil)
|
82
|
+
klass ||= self
|
83
|
+
raise ArgumentError, "#{klass} must be a class constant!" unless klass.respond_to?(:new)
|
84
|
+
content_type ||= content_type_from_constant(klass)
|
85
|
+
|
86
|
+
@@registry[content_type] = klass
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Returns the current registry of content type names to constants.
|
91
|
+
def self.registry
|
92
|
+
return {} unless @@registry
|
93
|
+
@@registry.dup.freeze
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Checks if a content type has already been registered to a class and returns
|
98
|
+
# that class. If nil, the generated WCC::Contentful::Model::{content_type} class
|
99
|
+
# will be resolved for this content type.
|
100
|
+
def self.registered?(content_type)
|
101
|
+
@@registry[content_type]
|
102
|
+
end
|
24
103
|
end
|
104
|
+
|
105
|
+
# rubocop:enable Style/ClassVars
|
@@ -40,9 +40,16 @@ module WCC::Contentful
|
|
40
40
|
new(raw, context) if raw.present?
|
41
41
|
end
|
42
42
|
|
43
|
-
define_singleton_method(:find_all) do |context = nil|
|
44
|
-
|
45
|
-
|
43
|
+
define_singleton_method(:find_all) do |filter = nil, context = nil|
|
44
|
+
if filter
|
45
|
+
filter.transform_keys! { |k| k.to_s.camelize(:lower) }
|
46
|
+
bad_fields = filter.keys.reject { |k| fields.include?(k) }
|
47
|
+
raise ArgumentError, "These fields do not exist: #{bad_fields}" unless bad_fields.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
query = WCC::Contentful::Model.store.find_all(content_type: content_type)
|
51
|
+
query = query.apply(filter) if filter
|
52
|
+
query.map { |r| new(r, context) }
|
46
53
|
end
|
47
54
|
|
48
55
|
define_singleton_method(:find_by) do |filter, context = nil|
|
@@ -50,11 +57,14 @@ module WCC::Contentful
|
|
50
57
|
bad_fields = filter.keys.reject { |k| fields.include?(k) }
|
51
58
|
raise ArgumentError, "These fields do not exist: #{bad_fields}" unless bad_fields.empty?
|
52
59
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
60
|
+
result = WCC::Contentful::Model.store.find_by(content_type: content_type, filter: filter)
|
61
|
+
new(result, context)
|
62
|
+
end
|
63
|
+
|
64
|
+
define_singleton_method(:inherited) do |subclass|
|
65
|
+
# only register if it's not already registered
|
66
|
+
return if WCC::Contentful::Model.registered?(typedef.content_type)
|
67
|
+
WCC::Contentful::Model.register_for_content_type(typedef.content_type, klass: subclass)
|
58
68
|
end
|
59
69
|
|
60
70
|
define_method(:initialize) do |raw, context = nil|
|
@@ -6,43 +6,94 @@ require_relative 'model_validators/dsl'
|
|
6
6
|
|
7
7
|
module WCC::Contentful::ModelValidators
|
8
8
|
def schema
|
9
|
-
return if
|
10
|
-
field_validations = @field_validations
|
9
|
+
return if validations.nil? || validations.empty?
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# "title": { ... },
|
16
|
-
# "sections": { ... },
|
17
|
-
# ...
|
18
|
-
# }
|
19
|
-
# }
|
11
|
+
all_field_validations =
|
12
|
+
validations.each_with_object({}) do |(content_type, procs), h|
|
13
|
+
next if procs.empty?
|
20
14
|
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
#
|
25
|
-
|
15
|
+
# "page": {
|
16
|
+
# "sys": { ... }
|
17
|
+
# "fields": {
|
18
|
+
# "title": { ... },
|
19
|
+
# "sections": { ... },
|
20
|
+
# ...
|
21
|
+
# }
|
22
|
+
# }
|
23
|
+
h[content_type] =
|
24
|
+
Dry::Validation.Schema do
|
25
|
+
# Had to dig through the internals of Dry::Validation to find
|
26
|
+
# this magic incantation
|
27
|
+
procs.each { |dsl| instance_eval(&dsl.to_proc) }
|
28
|
+
end
|
26
29
|
end
|
27
30
|
|
28
31
|
Dry::Validation.Schema do
|
29
|
-
|
32
|
+
all_field_validations.each do |content_type, fields_schema|
|
33
|
+
required(content_type).schema do
|
34
|
+
required('fields').schema(fields_schema)
|
35
|
+
end
|
36
|
+
end
|
30
37
|
end
|
31
38
|
end
|
32
39
|
|
40
|
+
def validations
|
41
|
+
# This needs to be a class variable so that subclasses defined in application
|
42
|
+
# code can add to the total package of model validations
|
43
|
+
# rubocop:disable Style/ClassVars
|
44
|
+
@@validations ||= {}
|
45
|
+
# rubocop:enable Style/ClassVars
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Accepts a block which uses the {dry-validation DSL}[http://dry-rb.org/gems/dry-validation/]
|
50
|
+
# to validate the 'fields' object of a content type.
|
33
51
|
def validate_fields(&block)
|
34
52
|
raise ArgumentError, 'validate_fields requires a block' unless block_given?
|
35
53
|
dsl = ProcDsl.new(Proc.new(&block))
|
36
54
|
|
37
|
-
|
55
|
+
ct = try(:content_type) || name.demodulize.camelize(:lower)
|
56
|
+
(validations[ct] ||= []) << dsl
|
38
57
|
end
|
39
58
|
|
59
|
+
##
|
60
|
+
# Validates a single field is of the expected type.
|
61
|
+
# Type expectations are one of:
|
62
|
+
#
|
63
|
+
# [:String] the field type must be `Symbol` or `Text`
|
64
|
+
# [:Int] the field type must be `Integer`
|
65
|
+
# [:Float] the field type must be `Number`
|
66
|
+
# [:DateTime] the field type must be 'Date'
|
67
|
+
# [:Asset] the field must be a link and the `linkType` must be `Asset`
|
68
|
+
# [:Link] the field must be a link and the `linkType` must be `Entry`.
|
69
|
+
# [:Location] the field type must be `Location`
|
70
|
+
# [:Boolean] the field type must be `Boolean`
|
71
|
+
# [:Json] the field type must be `Json` - a json blob.
|
72
|
+
# [:Array] the field must be a List.
|
73
|
+
#
|
74
|
+
# Additional validation options can be enforced:
|
75
|
+
#
|
76
|
+
# [:required] the 'Required Field' checkbox must be checked
|
77
|
+
# [:optional] the 'Required Field' checkbox must not be checked
|
78
|
+
# [:link_to] (only `:Link` or `:Array` type) the given content type(s) must be
|
79
|
+
# checked in the 'Accept only specified entry type' validations
|
80
|
+
# Example:
|
81
|
+
# validate_field :button, :Link, link_to: ['button', 'altButton']
|
82
|
+
#
|
83
|
+
# [:items] (only `:Array` type) the items of the list must be of the given type.
|
84
|
+
# Example:
|
85
|
+
# validate_field :my_strings, :Array, items: :String
|
86
|
+
#
|
87
|
+
# Examples:
|
88
|
+
# see WCC::Contentful::Model::Menu and WCC::Contentful::Model::MenuButton
|
40
89
|
def validate_field(field, type, *options)
|
41
90
|
dsl = FieldDsl.new(field, type, options)
|
42
91
|
|
43
|
-
|
92
|
+
ct = try(:content_type) || name.demodulize.camelize(:lower)
|
93
|
+
(validations[ct] ||= []) << dsl
|
44
94
|
end
|
45
95
|
|
96
|
+
##
|
46
97
|
# Accepts a content types response from the API and transforms it
|
47
98
|
# to be acceptible for the validator.
|
48
99
|
def self.transform_content_types_for_validation(content_types)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
gem 'http'
|
4
|
+
require 'http'
|
5
|
+
|
6
|
+
class HttpAdapter
|
7
|
+
def call(url, query, headers = {}, proxy = {})
|
8
|
+
if proxy[:host]
|
9
|
+
HTTP[headers].via(proxy[:host], proxy[:port], proxy[:username], proxy[:password])
|
10
|
+
.get(url, params: query)
|
11
|
+
else
|
12
|
+
HTTP[headers].get(url, params: query)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
gem 'typhoeus'
|
4
|
+
require 'typhoeus'
|
5
|
+
|
6
|
+
class TyphoeusAdapter
|
7
|
+
def call(url, query, headers = {}, proxy = {})
|
8
|
+
raise NotImplementedError, 'Proxying Not Yet Implemented' if proxy[:host]
|
9
|
+
|
10
|
+
TyphoeusAdapter::Response.new(
|
11
|
+
Typhoeus.get(
|
12
|
+
url,
|
13
|
+
params: query,
|
14
|
+
headers: headers
|
15
|
+
)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
Response =
|
20
|
+
Struct.new(:raw) do
|
21
|
+
delegate :body, to: :raw
|
22
|
+
delegate :to_s, to: :body
|
23
|
+
delegate :code, to: :raw
|
24
|
+
delegate :headers, to: :raw
|
25
|
+
|
26
|
+
def status
|
27
|
+
raw.code
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|