shaf 1.6.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/iana_link_relations.csv.gz +0 -0
- data/lib/shaf.rb +6 -0
- data/lib/shaf/alps/attribute_serializer.rb +41 -0
- data/lib/shaf/alps/json_serializer.rb +50 -0
- data/lib/shaf/alps/relation_serializer.rb +70 -0
- data/lib/shaf/api_doc/link_relations.rb +77 -0
- data/lib/shaf/authenticator.rb +56 -0
- data/lib/shaf/authenticator/base.rb +161 -0
- data/lib/shaf/authenticator/basic_auth.rb +25 -0
- data/lib/shaf/authenticator/challenge.rb +32 -0
- data/lib/shaf/authenticator/parameter.rb +31 -0
- data/lib/shaf/authenticator/request.rb +17 -0
- data/lib/shaf/command/console.rb +1 -1
- data/lib/shaf/command/generate.rb +5 -2
- data/lib/shaf/command/new.rb +20 -7
- data/lib/shaf/command/templates/Gemfile.erb +1 -0
- data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +1 -5
- data/lib/shaf/errors.rb +11 -0
- data/lib/shaf/extensions.rb +3 -3
- data/lib/shaf/extensions/api_routes.rb +60 -0
- data/lib/shaf/extensions/authorize.rb +11 -9
- data/lib/shaf/extensions/log.rb +1 -1
- data/lib/shaf/extensions/resource_uris.rb +139 -63
- data/lib/shaf/extensions/symbolic_routes.rb +22 -19
- data/lib/shaf/formable.rb +1 -2
- data/lib/shaf/formable/form.rb +1 -1
- data/lib/shaf/generator.rb +2 -0
- data/lib/shaf/generator/base.rb +2 -3
- data/lib/shaf/generator/controller.rb +11 -7
- data/lib/shaf/generator/doc.rb +17 -0
- data/lib/shaf/generator/forms.rb +1 -0
- data/lib/shaf/generator/helper.rb +2 -1
- data/lib/shaf/generator/migration/base.rb +7 -3
- data/lib/shaf/generator/migration/type.rb +4 -26
- data/lib/shaf/generator/migration/types.rb +45 -16
- data/lib/shaf/generator/model.rb +1 -2
- data/lib/shaf/generator/profile.rb +52 -0
- data/lib/shaf/generator/serializer.rb +38 -73
- data/lib/shaf/generator/templates/api/policy.rb.erb +2 -2
- data/lib/shaf/generator/templates/api/profile.rb.erb +16 -0
- data/lib/shaf/generator/templates/api/serializer.rb.erb +2 -2
- data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +1 -2
- data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
- data/lib/shaf/helpers.rb +4 -0
- data/lib/shaf/helpers/authentication.rb +79 -0
- data/lib/shaf/helpers/payload.rb +14 -34
- data/lib/shaf/helpers/vary.rb +8 -0
- data/lib/shaf/logger.rb +12 -0
- data/lib/shaf/parser.rb +65 -0
- data/lib/shaf/parser/base.rb +44 -0
- data/lib/shaf/parser/form_data.rb +15 -0
- data/lib/shaf/parser/json.rb +26 -0
- data/lib/shaf/profile.rb +110 -0
- data/lib/shaf/profile/attribute.rb +29 -0
- data/lib/shaf/profile/evaluator.rb +46 -0
- data/lib/shaf/profile/relation.rb +29 -0
- data/lib/shaf/profile/unique_id.rb +58 -0
- data/lib/shaf/profiles.rb +42 -0
- data/lib/shaf/profiles/shaf_basic.rb +20 -0
- data/lib/shaf/profiles/shaf_error.rb +48 -0
- data/lib/shaf/profiles/shaf_form.rb +109 -0
- data/lib/shaf/responder.rb +41 -2
- data/lib/shaf/responder/alps_json.rb +25 -0
- data/lib/shaf/responder/base.rb +20 -17
- data/lib/shaf/responder/hal.rb +65 -4
- data/lib/shaf/responder/html.rb +43 -16
- data/lib/shaf/responder/problem_json.rb +1 -1
- data/lib/shaf/serializer.rb +31 -0
- data/lib/shaf/settings.rb +25 -12
- data/lib/shaf/spec.rb +1 -0
- data/lib/shaf/spec/authenticator.rb +13 -0
- data/lib/shaf/spec/base.rb +1 -1
- data/lib/shaf/spec/http_method_utils.rb +1 -1
- data/lib/shaf/spec/integration_spec.rb +25 -13
- data/lib/shaf/spec/payload_utils.rb +2 -2
- data/lib/shaf/supported_http_methods.rb +15 -0
- data/lib/shaf/tasks/api_doc_task.rb +24 -3
- data/lib/shaf/tasks/routes_task.rb +14 -17
- data/lib/shaf/upgrade/manifest.rb +11 -2
- data/lib/shaf/upgrade/package.rb +73 -43
- data/lib/shaf/upgrade/version.rb +11 -10
- data/lib/shaf/utils.rb +19 -5
- data/lib/shaf/version.rb +3 -1
- data/lib/shaf/yard.rb +34 -0
- data/lib/shaf/yard/attribute_method_handler.rb +19 -0
- data/lib/shaf/yard/attribute_object.rb +30 -0
- data/lib/shaf/yard/base_method_handler.rb +30 -0
- data/lib/shaf/yard/link_method_handler.rb +39 -0
- data/lib/shaf/yard/link_object.rb +60 -0
- data/lib/shaf/yard/nested_attributes.rb +37 -0
- data/lib/shaf/yard/parser.rb +64 -0
- data/lib/shaf/yard/profile_method_handler.rb +51 -0
- data/lib/shaf/yard/profile_object.rb +21 -0
- data/lib/shaf/yard/resource_object.rb +55 -0
- data/lib/shaf/yard/serializer_handler.rb +27 -0
- data/templates/api/controllers/base_controller.rb +0 -10
- data/templates/api/controllers/docs_controller.rb +5 -3
- data/templates/api/controllers/root_controller.rb +7 -1
- data/templates/api/policies/base_policy.rb +2 -0
- data/templates/api/serializers/base_serializer.rb +1 -3
- data/templates/api/serializers/error_serializer.rb +1 -5
- data/templates/api/serializers/form_serializer.rb +1 -5
- data/templates/api/serializers/validation_error_serializer.rb +1 -5
- data/templates/config/bootstrap.rb +1 -2
- data/templates/config/directories.rb +52 -44
- data/templates/config/helpers.rb +1 -1
- data/templates/config/initializers.rb +52 -8
- data/templates/config/initializers/authentication.rb +18 -0
- data/templates/config/initializers/db_migrations.rb +2 -2
- data/templates/config/initializers/logging.rb +2 -2
- data/templates/spec/spec_helper.rb +2 -0
- data/upgrades/0.5.0.tar.gz +0 -0
- data/upgrades/1.0.4.tar.gz +0 -0
- data/upgrades/1.1.0.tar.gz +0 -0
- data/upgrades/2.0.0.tar.gz +0 -0
- data/yard_templates/api_doc/doc_index/html/body.erb +3 -0
- data/yard_templates/api_doc/doc_index/setup.rb +8 -0
- data/yard_templates/api_doc/html/css/api-doc.css +222 -0
- data/yard_templates/api_doc/html/favicon.ico +0 -0
- data/yard_templates/api_doc/html/js/switch_tab.js +17 -0
- data/yard_templates/api_doc/html/setup.rb +59 -0
- data/yard_templates/api_doc/layout/html/footer.erb +3 -0
- data/yard_templates/api_doc/layout/html/header.erb +7 -0
- data/yard_templates/api_doc/layout/html/layout.erb +13 -0
- data/yard_templates/api_doc/layout/setup.rb +24 -0
- data/yard_templates/api_doc/profile/html/attributes.erb +10 -0
- data/yard_templates/api_doc/profile/html/profile.erb +6 -0
- data/yard_templates/api_doc/profile/html/relations.erb +10 -0
- data/yard_templates/api_doc/profile/setup.rb +38 -0
- data/yard_templates/api_doc/profile_attribute/html/attribute.erb +23 -0
- data/yard_templates/api_doc/profile_attribute/setup.rb +21 -0
- data/yard_templates/api_doc/profile_relation/html/relation.erb +37 -0
- data/yard_templates/api_doc/profile_relation/setup.rb +41 -0
- data/yard_templates/api_doc/resource/html/attributes.erb +10 -0
- data/yard_templates/api_doc/resource/html/profile.erb +14 -0
- data/yard_templates/api_doc/resource/html/relations.erb +10 -0
- data/yard_templates/api_doc/resource/html/resource.erb +5 -0
- data/yard_templates/api_doc/resource/setup.rb +56 -0
- data/yard_templates/api_doc/resource_attribute/html/attribute.erb +23 -0
- data/yard_templates/api_doc/resource_attribute/setup.rb +20 -0
- data/yard_templates/api_doc/resource_relation/html/relation.erb +47 -0
- data/yard_templates/api_doc/resource_relation/setup.rb +80 -0
- data/yard_templates/api_doc/setup.rb +31 -0
- data/yard_templates/api_doc/sidebar/html/profile_list.erb +8 -0
- data/yard_templates/api_doc/sidebar/html/search.erb +7 -0
- data/yard_templates/api_doc/sidebar/html/serializer_list.erb +8 -0
- data/yard_templates/api_doc/sidebar/html/sidebar.erb +13 -0
- data/yard_templates/api_doc/sidebar/setup.rb +56 -0
- metadata +137 -30
- metadata.gz.sig +1 -3
- data/lib/shaf/extensions/current_user.rb +0 -48
- data/lib/shaf/responder/hal_serializable.rb +0 -64
data/lib/shaf/profile.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shaf/profile/evaluator'
|
4
|
+
require 'shaf/extensions/resource_uris'
|
5
|
+
|
6
|
+
module Shaf
|
7
|
+
class Profile
|
8
|
+
include Shaf::UriHelper
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def inherited(child)
|
12
|
+
Profiles.register child
|
13
|
+
end
|
14
|
+
|
15
|
+
def name(str = nil)
|
16
|
+
@name = str if str
|
17
|
+
@name if defined? @name
|
18
|
+
end
|
19
|
+
|
20
|
+
def doc(str = nil)
|
21
|
+
@doc = str if str
|
22
|
+
@doc if defined? @doc
|
23
|
+
end
|
24
|
+
|
25
|
+
def example(str)
|
26
|
+
examples << str
|
27
|
+
end
|
28
|
+
|
29
|
+
def match?(str)
|
30
|
+
normalize(name) == normalize(str)
|
31
|
+
end
|
32
|
+
|
33
|
+
def attributes
|
34
|
+
@attributes ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
def relations
|
38
|
+
@relations ||= []
|
39
|
+
end
|
40
|
+
|
41
|
+
def examples
|
42
|
+
@examples ||= []
|
43
|
+
end
|
44
|
+
|
45
|
+
def attribute(*args, **kwargs, &block)
|
46
|
+
evaluator.attribute(*args, **kwargs, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def relation(*args, **kwargs, &block)
|
50
|
+
evaluator.rel(*args, **kwargs, &block)
|
51
|
+
end
|
52
|
+
alias rel relation
|
53
|
+
|
54
|
+
def descriptor(id)
|
55
|
+
find_attribute(id) || find_relation(id)
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_attribute(id)
|
59
|
+
attributes.find { |attr| attr.id.to_sym == id.to_sym }
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_relation(id)
|
63
|
+
relations.find { |rel| rel.id.to_sym == id.to_sym }
|
64
|
+
end
|
65
|
+
|
66
|
+
def use(*descriptors, from:, doc: nil)
|
67
|
+
descriptors.each do |id|
|
68
|
+
desc = from.descriptor(id)
|
69
|
+
href = profile_path(from.name, fragment_id: id)
|
70
|
+
|
71
|
+
case desc
|
72
|
+
when Relation
|
73
|
+
kwargs = {
|
74
|
+
doc: doc || desc&.doc,
|
75
|
+
href: href,
|
76
|
+
http_methods: desc.http_methods,
|
77
|
+
payload_type: desc.payload_type,
|
78
|
+
content_type: desc.content_type,
|
79
|
+
}
|
80
|
+
relation(id, **kwargs)
|
81
|
+
when Attribute
|
82
|
+
attribute(id, href: href, doc: doc)
|
83
|
+
when NilClass
|
84
|
+
raise "#{from.name} does not have a descriptor with id #{id}"
|
85
|
+
else
|
86
|
+
raise Errors::ServerError, "Unsupported descriptor: #{desc}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def evaluator
|
94
|
+
Evaluator.new(parent: self)
|
95
|
+
end
|
96
|
+
|
97
|
+
def normalize(name)
|
98
|
+
name.to_s.downcase.tr('-', '_')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def name
|
103
|
+
normalize(self.class.name)
|
104
|
+
end
|
105
|
+
|
106
|
+
def normalize(str)
|
107
|
+
self.class.normalize(str)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shaf/profile/unique_id'
|
4
|
+
|
5
|
+
module Shaf
|
6
|
+
class Profile
|
7
|
+
class Attribute
|
8
|
+
include UniqueId
|
9
|
+
|
10
|
+
attr_reader :name, :doc, :href, :type, :parent
|
11
|
+
|
12
|
+
def initialize(name, **opts)
|
13
|
+
@name = name.to_sym
|
14
|
+
@doc = opts[:doc].freeze
|
15
|
+
@href = opts[:href].freeze
|
16
|
+
@type = opts[:type]&.to_s
|
17
|
+
@parent = opts[:parent]
|
18
|
+
end
|
19
|
+
|
20
|
+
def attributes
|
21
|
+
@attributes ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def relations
|
25
|
+
@relations ||= []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shaf/profile/attribute'
|
4
|
+
require 'shaf/profile/relation'
|
5
|
+
|
6
|
+
module Shaf
|
7
|
+
class Profile
|
8
|
+
class Evaluator
|
9
|
+
attr_reader :parent, :allowed
|
10
|
+
|
11
|
+
def initialize(parent:, allowed: nil)
|
12
|
+
@parent = parent
|
13
|
+
@allowed = allowed && Array(allowed).map(&:to_sym)
|
14
|
+
end
|
15
|
+
|
16
|
+
def attribute(name, doc:, type: :string, &block)
|
17
|
+
return unless allow? :attribute
|
18
|
+
|
19
|
+
attr = Attribute.new(name, doc: doc, type: type, parent: parent)
|
20
|
+
self.class.new(parent: attr, allowed: allowed).instance_exec(&block) if block
|
21
|
+
parent.attributes << attr
|
22
|
+
end
|
23
|
+
|
24
|
+
def relation(name, **kwargs, &block)
|
25
|
+
return unless allow? :rel
|
26
|
+
|
27
|
+
rel = Relation.new(name, parent: parent, **kwargs)
|
28
|
+
self.class.new(parent: rel, allowed: [:attribute]).instance_exec(&block) if block
|
29
|
+
parent.relations << rel
|
30
|
+
end
|
31
|
+
alias rel relation
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def allow?(name)
|
36
|
+
return true unless allowed
|
37
|
+
return true if allowed.include? name
|
38
|
+
|
39
|
+
Shaf.log.warn "#{name} is not allowed to be nested inside #{parent.class} " \
|
40
|
+
"(or parent object containing #{parent.class})"
|
41
|
+
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shaf/profile/unique_id'
|
4
|
+
|
5
|
+
module Shaf
|
6
|
+
class Profile
|
7
|
+
class Relation
|
8
|
+
include UniqueId
|
9
|
+
|
10
|
+
attr_reader :name, :doc, :href, :http_methods, :payload_type, :content_type, :parent
|
11
|
+
|
12
|
+
def initialize(name, **opts)
|
13
|
+
@name = name.to_sym
|
14
|
+
@doc = opts[:doc].freeze
|
15
|
+
@href = opts[:href].freeze
|
16
|
+
http_methods = Array(opts[:http_method]) + Array(opts[:http_methods])
|
17
|
+
http_methods << 'GET' if http_methods.empty?
|
18
|
+
@http_methods = http_methods.map { |m| m.to_s.upcase }.uniq.freeze
|
19
|
+
@payload_type = opts[:payload_type].freeze
|
20
|
+
@content_type = opts[:content_type].freeze
|
21
|
+
@parent = opts[:parent]
|
22
|
+
end
|
23
|
+
|
24
|
+
def attributes
|
25
|
+
@attributes ||= []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
class Profile
|
5
|
+
module UniqueId
|
6
|
+
def id
|
7
|
+
return @id if defined? @id
|
8
|
+
@id = __find_unique_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def __pending_id?
|
12
|
+
@__pending_id ||= false
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def __find_unique_id
|
18
|
+
@__pending_id = true
|
19
|
+
|
20
|
+
return name.to_s unless __id_collision? name.to_s
|
21
|
+
|
22
|
+
id = [parent.name, name].join('_')
|
23
|
+
return id unless __id_collision? id
|
24
|
+
|
25
|
+
id = "#{id}0"
|
26
|
+
|
27
|
+
loop do
|
28
|
+
id = id.next
|
29
|
+
break id unless __id_collision? id
|
30
|
+
end
|
31
|
+
ensure
|
32
|
+
@__pending_id = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def __id_collision? id
|
36
|
+
descriptor = self
|
37
|
+
|
38
|
+
loop do
|
39
|
+
break false unless descriptor.respond_to?(:parent) && descriptor.parent
|
40
|
+
descriptor = descriptor.parent
|
41
|
+
|
42
|
+
__parent_descriptors(descriptor).each do |desc|
|
43
|
+
next if desc == self
|
44
|
+
next if desc.__pending_id?
|
45
|
+
return true if desc.id == id
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def __parent_descriptors(parent)
|
51
|
+
descriptors = []
|
52
|
+
descriptors += parent.attributes if parent.respond_to? :attributes
|
53
|
+
descriptors += parent.relations if parent.respond_to? :relations
|
54
|
+
descriptors
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'shaf/errors'
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Profiles
|
5
|
+
class ProfileNotFoundError < Errors::NotFoundError
|
6
|
+
def initialize(name)
|
7
|
+
msg = %Q(Profile with name "#{name}" does not exist)
|
8
|
+
super(msg, id: name)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def register(clazz)
|
14
|
+
profiles << clazz
|
15
|
+
end
|
16
|
+
|
17
|
+
def find(name)
|
18
|
+
name = String(name)
|
19
|
+
return if name.empty?
|
20
|
+
|
21
|
+
profiles.find { |profile| profile.match? name }
|
22
|
+
end
|
23
|
+
|
24
|
+
def find!(name)
|
25
|
+
find(name) or raise ProfileNotFoundError, name
|
26
|
+
end
|
27
|
+
|
28
|
+
def profiles
|
29
|
+
@profiles ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear
|
33
|
+
@profiles.clear
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
require 'shaf/profile'
|
40
|
+
require 'shaf/profiles/shaf_form'
|
41
|
+
require 'shaf/profiles/shaf_error'
|
42
|
+
require 'shaf/profiles/shaf_basic'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Profiles
|
5
|
+
class ShafBasic < Shaf::Profile
|
6
|
+
name 'shaf-basic'
|
7
|
+
|
8
|
+
rel :delete,
|
9
|
+
http_method: :delete,
|
10
|
+
doc: <<~DOC
|
11
|
+
When a resource contains a link with rel 'delete', this
|
12
|
+
means that the autenticated user (or any user if the
|
13
|
+
current user has not been authenticated), may send a
|
14
|
+
DELETE request to the href of the link.
|
15
|
+
If a DELETE request is sent to this href then the corresponding
|
16
|
+
resource will be deleted.
|
17
|
+
DOC
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Profiles
|
5
|
+
class ShafError < Shaf::Profile
|
6
|
+
name 'shaf-error'
|
7
|
+
|
8
|
+
doc 'This profile describes a set of descriptors for generic error messages.'
|
9
|
+
example <<~EXAMPLE
|
10
|
+
{
|
11
|
+
"title": "Invalid entity",
|
12
|
+
"message": "The user could not be saved, due to unfulfilled requirements",
|
13
|
+
"code": "VALIDATION_ERROR",
|
14
|
+
"fields": {
|
15
|
+
"email": ["cannot be empty"]
|
16
|
+
}
|
17
|
+
}
|
18
|
+
EXAMPLE
|
19
|
+
|
20
|
+
example <<~EXAMPLE
|
21
|
+
{
|
22
|
+
"title": "Unpermitted action",
|
23
|
+
"message": "User is not allowed to edit this resource",
|
24
|
+
"code": "FORBIDDEN_ERROR",
|
25
|
+
}
|
26
|
+
EXAMPLE
|
27
|
+
|
28
|
+
attribute :code,
|
29
|
+
type: :string,
|
30
|
+
doc: 'An identifier that describes the type of error.'
|
31
|
+
|
32
|
+
attribute :title,
|
33
|
+
type: :string,
|
34
|
+
doc: 'A short string used for labeling the error.'
|
35
|
+
|
36
|
+
attribute :message,
|
37
|
+
type: :string,
|
38
|
+
doc: 'A description with details about the error.'
|
39
|
+
|
40
|
+
attribute :fields,
|
41
|
+
type: :string,
|
42
|
+
doc: 'An object describing validation errors. ' \
|
43
|
+
'The keys correspond to attributes of a resource. ' \
|
44
|
+
'The values are an array of strings describing validation errors ' \
|
45
|
+
'for the corresponding attribute.'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Profiles
|
5
|
+
class ShafForm < Shaf::Profile
|
6
|
+
name 'shaf-form'
|
7
|
+
|
8
|
+
doc 'This profile describes how a set of descriptors should be interpreted to ' \
|
9
|
+
'turn a generic media type into a form (similar to HTML forms). For ' \
|
10
|
+
'example, the HAL media type (application/hal+json) does not specify any ' \
|
11
|
+
'semantics about forms, however we can add semantics about forms to a HAL ' \
|
12
|
+
'document using this profile.'
|
13
|
+
|
14
|
+
example <<~EXAMPLE
|
15
|
+
Given the following form:
|
16
|
+
|
17
|
+
```json
|
18
|
+
"create-form": {
|
19
|
+
"method": "POST",
|
20
|
+
"name": "create-post",
|
21
|
+
"title": "Create Post",
|
22
|
+
"href": "/posts",
|
23
|
+
"type": "application/json",
|
24
|
+
"_links": {
|
25
|
+
"self": {
|
26
|
+
"href": "http://localhost:3000/posts/form"
|
27
|
+
}
|
28
|
+
},
|
29
|
+
"fields": [
|
30
|
+
{
|
31
|
+
"name": "title",
|
32
|
+
"type": "string",
|
33
|
+
},
|
34
|
+
{
|
35
|
+
"name": "message",
|
36
|
+
"type": "string",
|
37
|
+
}
|
38
|
+
]
|
39
|
+
}
|
40
|
+
```
|
41
|
+
|
42
|
+
A client should then present a user interface with the possiblity to fill in two fields accepting string values (if applicable the interface should be titled "Create Post"). The entered values should be mapped to the keys title resp. message. (The client may of course fill in the details itself whenever possible). When the form is to be submitted, the client constructs a json string with the keys and values and makes a POST request to http://localhost:3000/posts/form with the header Content-Type header set to application/json and the json string as body.
|
43
|
+
|
44
|
+
Using Curl, the form above could be submitted using:
|
45
|
+
|
46
|
+
```sh
|
47
|
+
curl -H "Content-Type: application/json" \
|
48
|
+
-X POST \
|
49
|
+
-d '{"title": "hello", "message": "world"}' \
|
50
|
+
localhost:3000/posts
|
51
|
+
```
|
52
|
+
EXAMPLE
|
53
|
+
|
54
|
+
attribute :method,
|
55
|
+
type: :string,
|
56
|
+
doc: 'The HTTP method used for submitting the form'
|
57
|
+
|
58
|
+
attribute :href,
|
59
|
+
type: :string,
|
60
|
+
doc: 'The target uri that the form should be submitted to'
|
61
|
+
|
62
|
+
attribute :name,
|
63
|
+
type: :string,
|
64
|
+
doc: 'A string used to indentify the form.'
|
65
|
+
|
66
|
+
attribute :title,
|
67
|
+
type: :string,
|
68
|
+
doc: 'A string used as title/label when showing the form in a user interface'
|
69
|
+
|
70
|
+
attribute :type,
|
71
|
+
type: :string,
|
72
|
+
doc: 'The media type that must be set in the CONTENT-TYPE header when submiting the form'
|
73
|
+
|
74
|
+
attribute :submit,
|
75
|
+
type: :string,
|
76
|
+
doc: 'A string used as label for the CallToAction (e.g. the submit button text)'
|
77
|
+
|
78
|
+
attribute :fields, doc: 'A list of field descriptors' do
|
79
|
+
|
80
|
+
attribute :name,
|
81
|
+
type: :string,
|
82
|
+
doc: 'A string that should be used as key for the field.'
|
83
|
+
|
84
|
+
attribute :type,
|
85
|
+
type: :string,
|
86
|
+
doc: 'A string specifying the possible values accepted by the field.'
|
87
|
+
|
88
|
+
attribute :title,
|
89
|
+
type: :string,
|
90
|
+
doc: 'A string used to present this field in a UI. If not present name may be used instead.'
|
91
|
+
|
92
|
+
attribute :value,
|
93
|
+
doc: 'A prefilled value. Should be shown in a user interface (unless hidden) and sent back on submission unless changed.'
|
94
|
+
|
95
|
+
attribute :required,
|
96
|
+
type: :boolean,
|
97
|
+
doc: 'A boolean specifying that the field must be given a value before the form is submitted.'
|
98
|
+
|
99
|
+
attribute :hidden,
|
100
|
+
type: :boolean,
|
101
|
+
doc: 'A boolean specifying that the field should not be shown in a user interface.'
|
102
|
+
|
103
|
+
attribute :accepted_values,
|
104
|
+
type: :array,
|
105
|
+
doc: 'A list of av acceptable values. Submitting a value not present in this list SHOULD result in a client error response.'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|