shaf 0.1.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -0
- data/bin/shaf +57 -0
- data/lib/shaf.rb +9 -0
- data/lib/shaf/api_doc.rb +124 -0
- data/lib/shaf/api_doc/comment.rb +27 -0
- data/lib/shaf/api_doc/document.rb +133 -0
- data/lib/shaf/app.rb +22 -0
- data/lib/shaf/command.rb +42 -0
- data/lib/shaf/command/console.rb +17 -0
- data/lib/shaf/command/generate.rb +19 -0
- data/lib/shaf/command/new.rb +79 -0
- data/lib/shaf/command/server.rb +15 -0
- data/lib/shaf/command/templates/Gemfile.erb +30 -0
- data/lib/shaf/doc_model.rb +54 -0
- data/lib/shaf/errors.rb +77 -0
- data/lib/shaf/extensions.rb +11 -0
- data/lib/shaf/extensions/authorize.rb +42 -0
- data/lib/shaf/extensions/resource_uris.rb +153 -0
- data/lib/shaf/formable.rb +188 -0
- data/lib/shaf/generator.rb +69 -0
- data/lib/shaf/generator/controller.rb +106 -0
- data/lib/shaf/generator/migration.rb +122 -0
- data/lib/shaf/generator/migration/add_column.rb +49 -0
- data/lib/shaf/generator/migration/create_table.rb +40 -0
- data/lib/shaf/generator/migration/drop_column.rb +45 -0
- data/lib/shaf/generator/migration/empty.rb +21 -0
- data/lib/shaf/generator/migration/rename_column.rb +48 -0
- data/lib/shaf/generator/model.rb +68 -0
- data/lib/shaf/generator/policy.rb +43 -0
- data/lib/shaf/generator/scaffold.rb +26 -0
- data/lib/shaf/generator/serializer.rb +258 -0
- data/lib/shaf/generator/templates/api/controller.rb.erb +62 -0
- data/lib/shaf/generator/templates/api/model.rb.erb +20 -0
- data/lib/shaf/generator/templates/api/policy.rb.erb +26 -0
- data/lib/shaf/generator/templates/api/serializer.rb.erb +24 -0
- data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +98 -0
- data/lib/shaf/generator/templates/spec/model.rb.erb +40 -0
- data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +46 -0
- data/lib/shaf/helpers.rb +15 -0
- data/lib/shaf/helpers/json_html.rb +65 -0
- data/lib/shaf/helpers/paginate.rb +24 -0
- data/lib/shaf/helpers/payload.rb +115 -0
- data/lib/shaf/helpers/session.rb +53 -0
- data/lib/shaf/middleware.rb +1 -0
- data/lib/shaf/middleware/request_id.rb +16 -0
- data/lib/shaf/registrable_factory.rb +71 -0
- data/lib/shaf/settings.rb +33 -0
- data/lib/shaf/spec.rb +6 -0
- data/lib/shaf/spec/http_method_utils.rb +24 -0
- data/lib/shaf/spec/integration_spec.rb +53 -0
- data/lib/shaf/spec/model.rb +17 -0
- data/lib/shaf/spec/payload_test.rb +78 -0
- data/lib/shaf/spec/payload_utils.rb +176 -0
- data/lib/shaf/spec/serializer_spec.rb +24 -0
- data/lib/shaf/tasks.rb +4 -0
- data/lib/shaf/tasks/db.rb +61 -0
- data/lib/shaf/tasks/test.rb +43 -0
- data/lib/shaf/utils.rb +53 -0
- data/lib/shaf/version.rb +3 -0
- data/templates/Rakefile +13 -0
- data/templates/api/controllers/base_controller.rb +57 -0
- data/templates/api/controllers/docs_controller.rb +16 -0
- data/templates/api/controllers/root_controller.rb +8 -0
- data/templates/api/serializers/error_serializer.rb +10 -0
- data/templates/api/serializers/form_serializer.rb +42 -0
- data/templates/api/serializers/root_serializer.rb +16 -0
- data/templates/config.ru +4 -0
- data/templates/config/bootstrap.rb +12 -0
- data/templates/config/constants.rb +5 -0
- data/templates/config/customize.rb +3 -0
- data/templates/config/database.rb +40 -0
- data/templates/config/directories.rb +32 -0
- data/templates/config/helpers.rb +18 -0
- data/templates/config/initializers.rb +12 -0
- data/templates/config/initializers/db_migrations.rb +18 -0
- data/templates/config/initializers/hal_presenter.rb +6 -0
- data/templates/config/initializers/logging.rb +7 -0
- data/templates/config/initializers/sequel.rb +4 -0
- data/templates/config/settings.yml +19 -0
- data/templates/frontend/assets/css/main.css +70 -0
- data/templates/frontend/views/form.erb +16 -0
- data/templates/frontend/views/layout.erb +11 -0
- data/templates/frontend/views/payload.erb +8 -0
- data/templates/spec/integration/root_spec.rb +14 -0
- data/templates/spec/serializers/root_serializer_spec.rb +12 -0
- data/templates/spec/spec_helper.rb +4 -0
- metadata +348 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Session
|
5
|
+
|
6
|
+
SESSION_TTL = 60 * 60 * 24 * 2 # 2 days
|
7
|
+
|
8
|
+
def login(email, password)
|
9
|
+
return unless email && password
|
10
|
+
user = User.first(email: email) or return
|
11
|
+
bcrypt = BCrypt::Password.new(user.password_digest)
|
12
|
+
return unless bcrypt == password
|
13
|
+
@current_user = user
|
14
|
+
|
15
|
+
Session.where(user_id: user.id).delete
|
16
|
+
params = {
|
17
|
+
user_id: user.id,
|
18
|
+
expire_at: Time.now + SESSION_TTL,
|
19
|
+
}
|
20
|
+
Session.create(params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def extend_session(session)
|
24
|
+
return unless session
|
25
|
+
session.update(expire_at: Time.now + SESSION_TTL)
|
26
|
+
session.auth_token = request.env['HTTP_X_AUTH_TOKEN']
|
27
|
+
session
|
28
|
+
end
|
29
|
+
|
30
|
+
def logout
|
31
|
+
current_session&.destroy
|
32
|
+
end
|
33
|
+
|
34
|
+
def current_user
|
35
|
+
unless defined?(@current_user) && @current_user
|
36
|
+
return unless request.env.key? 'HTTP_X_AUTH_TOKEN'
|
37
|
+
digest = Digest::SHA256.hexdigest(request.env['HTTP_X_AUTH_TOKEN'])
|
38
|
+
session = Session.where(auth_token_digest: digest).first
|
39
|
+
@current_user = User[session.user_id] if session&.valid?
|
40
|
+
end
|
41
|
+
@current_user
|
42
|
+
end
|
43
|
+
|
44
|
+
def current_session
|
45
|
+
unless @current_session
|
46
|
+
return unless current_user
|
47
|
+
@current_session = Session.where(user_id: current_user.id).first
|
48
|
+
end
|
49
|
+
@current_session
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'shaf/middleware/request_id'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Shaf
|
2
|
+
module RegistrableFactory
|
3
|
+
|
4
|
+
class NotFoundError < StandardError; end
|
5
|
+
|
6
|
+
def all
|
7
|
+
reg.dup
|
8
|
+
end
|
9
|
+
|
10
|
+
def size
|
11
|
+
reg.size
|
12
|
+
end
|
13
|
+
|
14
|
+
def register(clazz)
|
15
|
+
reg << clazz
|
16
|
+
end
|
17
|
+
|
18
|
+
def unregister(*str)
|
19
|
+
return if str.empty? || !str.all?
|
20
|
+
reg.delete_if { |clazz| matching_class? str, clazz }
|
21
|
+
end
|
22
|
+
|
23
|
+
def lookup(*str)
|
24
|
+
return if str.empty? || !str.all?
|
25
|
+
reg.detect { |clazz| matching_class? str, clazz }
|
26
|
+
end
|
27
|
+
|
28
|
+
def usage
|
29
|
+
reg.compact.map do |entry|
|
30
|
+
usage = entry.instance_eval { @usage }
|
31
|
+
usage.respond_to?(:call) ? usage.call : usage
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def create(*params)
|
36
|
+
clazz = lookup(*params)
|
37
|
+
raise NotFoundError.new(%Q(Command '#{ARGV}' is not supported)) unless clazz
|
38
|
+
|
39
|
+
args = init_args(clazz, params)
|
40
|
+
clazz.new(*args)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def reg
|
46
|
+
@reg ||= []
|
47
|
+
end
|
48
|
+
|
49
|
+
def matching_class?(strings, clazz)
|
50
|
+
identifiers = clazz.instance_eval { @identifiers }
|
51
|
+
return false if strings.size < identifiers.size
|
52
|
+
identifiers.zip(strings).all? { |pattern, str| matching_identifier? str, pattern }
|
53
|
+
end
|
54
|
+
|
55
|
+
def matching_identifier?(str, pattern)
|
56
|
+
return false if pattern.nil? || str.nil? || str.empty?
|
57
|
+
pattern = pattern.to_s if pattern.is_a? Symbol
|
58
|
+
return str == pattern if pattern.is_a? String
|
59
|
+
str.match(pattern) || false
|
60
|
+
end
|
61
|
+
|
62
|
+
def identifier_count(clazz)
|
63
|
+
clazz.instance_eval { @identifiers }&.size || 0
|
64
|
+
end
|
65
|
+
|
66
|
+
def init_args(clazz, params)
|
67
|
+
first_non_id = identifier_count(clazz)
|
68
|
+
params[first_non_id..-1]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
|
4
|
+
module Shaf
|
5
|
+
class Settings
|
6
|
+
SETTINGS_FILE = 'config/settings.yml'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def load
|
10
|
+
@settings = File.exist?(SETTINGS_FILE) ?
|
11
|
+
YAML.load(File.read(SETTINGS_FILE)) : {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def env
|
15
|
+
@env ||= (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(method, *args)
|
19
|
+
load unless defined? @settings
|
20
|
+
|
21
|
+
define_singleton_method(method) do
|
22
|
+
@settings.dig(env.to_s, method.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
return public_send(method)
|
26
|
+
end
|
27
|
+
|
28
|
+
def respond_to_missing?(method, include_private = false)
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/shaf/spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Spec
|
3
|
+
module HttpMethodUtils
|
4
|
+
include ::Rack::Test::Methods
|
5
|
+
|
6
|
+
[:get, :put, :post, :delete].each do |m|
|
7
|
+
define_method m do |*args|
|
8
|
+
set_headers
|
9
|
+
super(*args)
|
10
|
+
set_payload parse_response(last_response.body)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def status
|
15
|
+
last_response&.status
|
16
|
+
end
|
17
|
+
|
18
|
+
def headers
|
19
|
+
last_response&.headers
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Spec
|
3
|
+
class IntegrationSpec < Minitest::Spec
|
4
|
+
include Minitest::Hooks
|
5
|
+
include HttpMethodUtils
|
6
|
+
include PayloadUtils
|
7
|
+
include UriHelper
|
8
|
+
|
9
|
+
TRANSACTION_OPTIONS = {
|
10
|
+
rollback: :always,
|
11
|
+
savepoint: true,
|
12
|
+
auto_savepoint: true
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
register_spec_type self do |desc, args|
|
16
|
+
next unless args && args.is_a?(Hash)
|
17
|
+
args[:type]&.to_s == 'integration'
|
18
|
+
end
|
19
|
+
|
20
|
+
around do |&block|
|
21
|
+
DB.transaction(TRANSACTION_OPTIONS) { super(&block) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_headers
|
25
|
+
if defined?(@__integration_test_auth_token) && @__integration_test_auth_token
|
26
|
+
header 'X-AUTH-TOKEN', @__integration_test_auth_token
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_response(body)
|
31
|
+
return nil if body.empty?
|
32
|
+
JSON.parse(body, symbolize_names: true)
|
33
|
+
end
|
34
|
+
|
35
|
+
def app
|
36
|
+
App.instance
|
37
|
+
end
|
38
|
+
|
39
|
+
# def login(email, pass)
|
40
|
+
# params = {email: email, password: pass}
|
41
|
+
# header 'Content-Type', 'application/json'
|
42
|
+
# post Shaf::UriHelper.session_uri, JSON.generate(params)
|
43
|
+
# @__integration_test_auth_token = attribute[:auth_token]
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# def logout
|
47
|
+
# delete Shaf::UriHelper.session_uri
|
48
|
+
# @__integration_test_auth_token = nil
|
49
|
+
# end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
$:.unshift '.'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'test/test_utils/payload'
|
4
|
+
|
5
|
+
class PayloadTest < Minitest::Test
|
6
|
+
include TestUtils::Payload
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@payload = {
|
10
|
+
attr1: 1,
|
11
|
+
attr2: 2,
|
12
|
+
_links: {
|
13
|
+
link1: {
|
14
|
+
href: '/link1'
|
15
|
+
},
|
16
|
+
link2: {
|
17
|
+
href: '/link2'
|
18
|
+
}
|
19
|
+
},
|
20
|
+
_embedded: {
|
21
|
+
embed1: {
|
22
|
+
attr_e1: 'e1',
|
23
|
+
_links: {
|
24
|
+
link_e1: {
|
25
|
+
href: '/links/e1'
|
26
|
+
}
|
27
|
+
},
|
28
|
+
_embedded: {
|
29
|
+
embed1a: {
|
30
|
+
attr_e1a: 'e1a'
|
31
|
+
}
|
32
|
+
}
|
33
|
+
},
|
34
|
+
embed2: [
|
35
|
+
{
|
36
|
+
attr_embed2a: 'e2a',
|
37
|
+
_links: {
|
38
|
+
link_e2a: {
|
39
|
+
href: 'links/e2a'
|
40
|
+
}
|
41
|
+
}
|
42
|
+
},
|
43
|
+
{
|
44
|
+
attr_embed2b: 'e2b',
|
45
|
+
_links: {
|
46
|
+
link_e2b: {
|
47
|
+
href: 'links/e2b'
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
]
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
#TODO How to verify that we do get assertions
|
57
|
+
|
58
|
+
def test_links
|
59
|
+
assert_link :link1, '/link1'
|
60
|
+
assert_link :link2, '/link2'
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_embedded_without_block
|
64
|
+
assert_equal @payload[:_embedded], embedded
|
65
|
+
assert_equal @payload[:_embedded][:embed1], embedded(:embed1)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_embedded_with_block
|
69
|
+
embedded :embed1 do
|
70
|
+
assert_attribute :attr_e1, 'e1'
|
71
|
+
assert_link :link_e1, '/links/e1'
|
72
|
+
|
73
|
+
embedded :embed1a do
|
74
|
+
assert_attribute :attr_e1a, 'e1a'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'minitest/assertions'
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Spec
|
5
|
+
module PayloadUtils
|
6
|
+
|
7
|
+
class Embedded
|
8
|
+
include HttpMethodUtils
|
9
|
+
include PayloadUtils
|
10
|
+
include Minitest::Assertions
|
11
|
+
|
12
|
+
# This is needed by Minitest::Assertions
|
13
|
+
# And we need that module to have all assert_*/refute_* methods
|
14
|
+
# available in this class
|
15
|
+
attr_accessor :assertions
|
16
|
+
|
17
|
+
def initialize(payload, context, block)
|
18
|
+
@payload = payload
|
19
|
+
@context = context
|
20
|
+
@block = block
|
21
|
+
@assertions = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def call
|
25
|
+
instance_exec(&@block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(method, *args, &block)
|
29
|
+
if @context&.respond_to? method
|
30
|
+
define_singleton_method(method) { |*a, &b| @context.public_send method, *a, &b }
|
31
|
+
return public_send(method, *args, &block)
|
32
|
+
end
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def respond_to_missing?(method, include_private = false)
|
37
|
+
return true if @context&.respond_to? method
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def set_payload(payload)
|
44
|
+
@payload = payload
|
45
|
+
@payload = JSON.parse(payload, symbolize_names: true) if payload.is_a?(String)
|
46
|
+
end
|
47
|
+
|
48
|
+
def last_payload
|
49
|
+
refute @payload.nil?, "No previous response body"
|
50
|
+
@payload
|
51
|
+
end
|
52
|
+
|
53
|
+
def attributes
|
54
|
+
last_payload.reject { |key,_| [:_links, :_embedded].include? key }
|
55
|
+
end
|
56
|
+
|
57
|
+
def links
|
58
|
+
last_payload[:_links] || []
|
59
|
+
end
|
60
|
+
|
61
|
+
def link_rels
|
62
|
+
links.keys
|
63
|
+
end
|
64
|
+
|
65
|
+
def embedded_resources
|
66
|
+
last_payload[:_embedded]&.keys || []
|
67
|
+
end
|
68
|
+
|
69
|
+
def embedded(name = nil)
|
70
|
+
assert_has_embedded name
|
71
|
+
keys = [:_embedded, name&.to_sym].compact
|
72
|
+
return last_payload.dig(*keys) unless block_given?
|
73
|
+
Embedded.new(last_payload.dig(*keys), self, Proc.new).call
|
74
|
+
end
|
75
|
+
|
76
|
+
def follow_rel(rel, method: nil)
|
77
|
+
assert_has_link(rel)
|
78
|
+
link = links[rel.to_sym]
|
79
|
+
if method && respond_to?(method)
|
80
|
+
public_send(method, link[:href])
|
81
|
+
else
|
82
|
+
get link[:href]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def fill_form(fields)
|
87
|
+
fields.map do |field|
|
88
|
+
value = case field[:type]
|
89
|
+
when 'integer'
|
90
|
+
field[:name].size
|
91
|
+
when 'string'
|
92
|
+
"value for #{field[:name]}"
|
93
|
+
else
|
94
|
+
"type not supported"
|
95
|
+
end
|
96
|
+
[field[:name], value]
|
97
|
+
end.to_h
|
98
|
+
end
|
99
|
+
|
100
|
+
def assert_status(code)
|
101
|
+
assert_equal code, status,
|
102
|
+
"Response status was expected to be #{code}."
|
103
|
+
end
|
104
|
+
|
105
|
+
def assert_header(key, value)
|
106
|
+
assert_equal value, headers[key],
|
107
|
+
"Response was expected have header #{key} = #{value}."
|
108
|
+
end
|
109
|
+
|
110
|
+
def assert_has_attribute(attr)
|
111
|
+
assert last_payload[attr.to_sym],
|
112
|
+
"Response does not contain attribute '#{attr}': #{last_payload}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def refute_has_attribute(attr)
|
116
|
+
refute last_payload[attr.to_sym],
|
117
|
+
"Response contains disallowed attribute '#{attr}': #{last_payload}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def assert_has_attributes(*attrs)
|
121
|
+
attrs.each { |attr| assert_has_attribute(attr) }
|
122
|
+
end
|
123
|
+
|
124
|
+
def refute_has_attributes(*attrs)
|
125
|
+
attrs.each { |attr| refute_has_attribute(attr) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def assert_attribute(attr, expected)
|
129
|
+
assert_has_attribute(attr)
|
130
|
+
assert_equal expected, last_payload[attr.to_sym]
|
131
|
+
end
|
132
|
+
|
133
|
+
def assert_has_link(rel)
|
134
|
+
assert last_payload.key?(:_links), "Response does not have any links: #{last_payload}"
|
135
|
+
assert last_payload[:_links][rel.to_sym],
|
136
|
+
"Response does not contain link with rel '#{rel}': #{last_payload}"
|
137
|
+
assert last_payload[:_links][rel.to_sym][:href],
|
138
|
+
"link with rel '#{rel}' in ressponse does not have a href: #{last_payload}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def refute_has_link(rel)
|
142
|
+
refute last_payload.dig(:_links, rel.to_sym),
|
143
|
+
"Response contains disallowed link with rel '#{rel}': #{last_payload}"
|
144
|
+
end
|
145
|
+
|
146
|
+
def assert_has_links(*rels)
|
147
|
+
rels.each { |rel| assert_has_link(rel) }
|
148
|
+
end
|
149
|
+
|
150
|
+
def refute_has_links(*rels)
|
151
|
+
rels.each { |rel| refute_has_link(rel) }
|
152
|
+
end
|
153
|
+
|
154
|
+
def assert_link(rel, expected)
|
155
|
+
assert_has_link(rel)
|
156
|
+
assert_equal expected, last_payload.dig(:_links, rel.to_sym, :href)
|
157
|
+
end
|
158
|
+
|
159
|
+
def assert_has_embedded(*names)
|
160
|
+
names.each do |name|
|
161
|
+
assert last_payload.key?(:_embedded),
|
162
|
+
"Response does not have any embedded resources: #{last_payload}"
|
163
|
+
assert last_payload[:_embedded][name.to_sym],
|
164
|
+
"Response does not contain embedded resource with name '#{name}': #{last_payload}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def refute_has_embedded(*names)
|
169
|
+
names.each do |name|
|
170
|
+
refute last_payload.dig(:_embedded, name.to_sym),
|
171
|
+
"Response contains disallowed embedded resource with name '#{name}': #{last_payload}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|