shaf 1.6.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/iana_link_relations.csv.gz +0 -0
  5. data/lib/shaf.rb +6 -0
  6. data/lib/shaf/alps/attribute_serializer.rb +41 -0
  7. data/lib/shaf/alps/json_serializer.rb +50 -0
  8. data/lib/shaf/alps/relation_serializer.rb +70 -0
  9. data/lib/shaf/api_doc/link_relations.rb +77 -0
  10. data/lib/shaf/authenticator.rb +56 -0
  11. data/lib/shaf/authenticator/base.rb +161 -0
  12. data/lib/shaf/authenticator/basic_auth.rb +25 -0
  13. data/lib/shaf/authenticator/challenge.rb +32 -0
  14. data/lib/shaf/authenticator/parameter.rb +31 -0
  15. data/lib/shaf/authenticator/request.rb +17 -0
  16. data/lib/shaf/command/console.rb +1 -1
  17. data/lib/shaf/command/generate.rb +5 -2
  18. data/lib/shaf/command/new.rb +20 -7
  19. data/lib/shaf/command/templates/Gemfile.erb +1 -0
  20. data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +1 -5
  21. data/lib/shaf/errors.rb +11 -0
  22. data/lib/shaf/extensions.rb +3 -3
  23. data/lib/shaf/extensions/api_routes.rb +60 -0
  24. data/lib/shaf/extensions/authorize.rb +11 -9
  25. data/lib/shaf/extensions/log.rb +1 -1
  26. data/lib/shaf/extensions/resource_uris.rb +139 -63
  27. data/lib/shaf/extensions/symbolic_routes.rb +22 -19
  28. data/lib/shaf/formable.rb +1 -2
  29. data/lib/shaf/formable/form.rb +1 -1
  30. data/lib/shaf/generator.rb +2 -0
  31. data/lib/shaf/generator/base.rb +2 -3
  32. data/lib/shaf/generator/controller.rb +11 -7
  33. data/lib/shaf/generator/doc.rb +17 -0
  34. data/lib/shaf/generator/forms.rb +1 -0
  35. data/lib/shaf/generator/helper.rb +2 -1
  36. data/lib/shaf/generator/migration/base.rb +7 -3
  37. data/lib/shaf/generator/migration/type.rb +4 -26
  38. data/lib/shaf/generator/migration/types.rb +45 -16
  39. data/lib/shaf/generator/model.rb +1 -2
  40. data/lib/shaf/generator/profile.rb +52 -0
  41. data/lib/shaf/generator/serializer.rb +38 -73
  42. data/lib/shaf/generator/templates/api/policy.rb.erb +2 -2
  43. data/lib/shaf/generator/templates/api/profile.rb.erb +16 -0
  44. data/lib/shaf/generator/templates/api/serializer.rb.erb +2 -2
  45. data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +1 -2
  46. data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
  47. data/lib/shaf/helpers.rb +4 -0
  48. data/lib/shaf/helpers/authentication.rb +79 -0
  49. data/lib/shaf/helpers/payload.rb +14 -34
  50. data/lib/shaf/helpers/vary.rb +8 -0
  51. data/lib/shaf/logger.rb +12 -0
  52. data/lib/shaf/parser.rb +65 -0
  53. data/lib/shaf/parser/base.rb +44 -0
  54. data/lib/shaf/parser/form_data.rb +15 -0
  55. data/lib/shaf/parser/json.rb +26 -0
  56. data/lib/shaf/profile.rb +110 -0
  57. data/lib/shaf/profile/attribute.rb +29 -0
  58. data/lib/shaf/profile/evaluator.rb +46 -0
  59. data/lib/shaf/profile/relation.rb +29 -0
  60. data/lib/shaf/profile/unique_id.rb +58 -0
  61. data/lib/shaf/profiles.rb +42 -0
  62. data/lib/shaf/profiles/shaf_basic.rb +20 -0
  63. data/lib/shaf/profiles/shaf_error.rb +48 -0
  64. data/lib/shaf/profiles/shaf_form.rb +109 -0
  65. data/lib/shaf/responder.rb +41 -2
  66. data/lib/shaf/responder/alps_json.rb +25 -0
  67. data/lib/shaf/responder/base.rb +20 -17
  68. data/lib/shaf/responder/hal.rb +65 -4
  69. data/lib/shaf/responder/html.rb +43 -16
  70. data/lib/shaf/responder/problem_json.rb +1 -1
  71. data/lib/shaf/serializer.rb +31 -0
  72. data/lib/shaf/settings.rb +25 -12
  73. data/lib/shaf/spec.rb +1 -0
  74. data/lib/shaf/spec/authenticator.rb +13 -0
  75. data/lib/shaf/spec/base.rb +1 -1
  76. data/lib/shaf/spec/http_method_utils.rb +1 -1
  77. data/lib/shaf/spec/integration_spec.rb +25 -13
  78. data/lib/shaf/spec/payload_utils.rb +2 -2
  79. data/lib/shaf/supported_http_methods.rb +15 -0
  80. data/lib/shaf/tasks/api_doc_task.rb +24 -3
  81. data/lib/shaf/tasks/routes_task.rb +14 -17
  82. data/lib/shaf/upgrade/manifest.rb +11 -2
  83. data/lib/shaf/upgrade/package.rb +73 -43
  84. data/lib/shaf/upgrade/version.rb +11 -10
  85. data/lib/shaf/utils.rb +19 -5
  86. data/lib/shaf/version.rb +3 -1
  87. data/lib/shaf/yard.rb +34 -0
  88. data/lib/shaf/yard/attribute_method_handler.rb +19 -0
  89. data/lib/shaf/yard/attribute_object.rb +30 -0
  90. data/lib/shaf/yard/base_method_handler.rb +30 -0
  91. data/lib/shaf/yard/link_method_handler.rb +39 -0
  92. data/lib/shaf/yard/link_object.rb +60 -0
  93. data/lib/shaf/yard/nested_attributes.rb +37 -0
  94. data/lib/shaf/yard/parser.rb +64 -0
  95. data/lib/shaf/yard/profile_method_handler.rb +51 -0
  96. data/lib/shaf/yard/profile_object.rb +21 -0
  97. data/lib/shaf/yard/resource_object.rb +55 -0
  98. data/lib/shaf/yard/serializer_handler.rb +27 -0
  99. data/templates/api/controllers/base_controller.rb +0 -10
  100. data/templates/api/controllers/docs_controller.rb +5 -3
  101. data/templates/api/controllers/root_controller.rb +7 -1
  102. data/templates/api/policies/base_policy.rb +2 -0
  103. data/templates/api/serializers/base_serializer.rb +1 -3
  104. data/templates/api/serializers/error_serializer.rb +1 -5
  105. data/templates/api/serializers/form_serializer.rb +1 -5
  106. data/templates/api/serializers/validation_error_serializer.rb +1 -5
  107. data/templates/config/bootstrap.rb +1 -2
  108. data/templates/config/directories.rb +52 -44
  109. data/templates/config/helpers.rb +1 -1
  110. data/templates/config/initializers.rb +52 -8
  111. data/templates/config/initializers/authentication.rb +18 -0
  112. data/templates/config/initializers/db_migrations.rb +2 -2
  113. data/templates/config/initializers/logging.rb +2 -2
  114. data/templates/spec/spec_helper.rb +2 -0
  115. data/upgrades/0.5.0.tar.gz +0 -0
  116. data/upgrades/1.0.4.tar.gz +0 -0
  117. data/upgrades/1.1.0.tar.gz +0 -0
  118. data/upgrades/2.0.0.tar.gz +0 -0
  119. data/yard_templates/api_doc/doc_index/html/body.erb +3 -0
  120. data/yard_templates/api_doc/doc_index/setup.rb +8 -0
  121. data/yard_templates/api_doc/html/css/api-doc.css +222 -0
  122. data/yard_templates/api_doc/html/favicon.ico +0 -0
  123. data/yard_templates/api_doc/html/js/switch_tab.js +17 -0
  124. data/yard_templates/api_doc/html/setup.rb +59 -0
  125. data/yard_templates/api_doc/layout/html/footer.erb +3 -0
  126. data/yard_templates/api_doc/layout/html/header.erb +7 -0
  127. data/yard_templates/api_doc/layout/html/layout.erb +13 -0
  128. data/yard_templates/api_doc/layout/setup.rb +24 -0
  129. data/yard_templates/api_doc/profile/html/attributes.erb +10 -0
  130. data/yard_templates/api_doc/profile/html/profile.erb +6 -0
  131. data/yard_templates/api_doc/profile/html/relations.erb +10 -0
  132. data/yard_templates/api_doc/profile/setup.rb +38 -0
  133. data/yard_templates/api_doc/profile_attribute/html/attribute.erb +23 -0
  134. data/yard_templates/api_doc/profile_attribute/setup.rb +21 -0
  135. data/yard_templates/api_doc/profile_relation/html/relation.erb +37 -0
  136. data/yard_templates/api_doc/profile_relation/setup.rb +41 -0
  137. data/yard_templates/api_doc/resource/html/attributes.erb +10 -0
  138. data/yard_templates/api_doc/resource/html/profile.erb +14 -0
  139. data/yard_templates/api_doc/resource/html/relations.erb +10 -0
  140. data/yard_templates/api_doc/resource/html/resource.erb +5 -0
  141. data/yard_templates/api_doc/resource/setup.rb +56 -0
  142. data/yard_templates/api_doc/resource_attribute/html/attribute.erb +23 -0
  143. data/yard_templates/api_doc/resource_attribute/setup.rb +20 -0
  144. data/yard_templates/api_doc/resource_relation/html/relation.erb +47 -0
  145. data/yard_templates/api_doc/resource_relation/setup.rb +80 -0
  146. data/yard_templates/api_doc/setup.rb +31 -0
  147. data/yard_templates/api_doc/sidebar/html/profile_list.erb +8 -0
  148. data/yard_templates/api_doc/sidebar/html/search.erb +7 -0
  149. data/yard_templates/api_doc/sidebar/html/serializer_list.erb +8 -0
  150. data/yard_templates/api_doc/sidebar/html/sidebar.erb +13 -0
  151. data/yard_templates/api_doc/sidebar/setup.rb +56 -0
  152. metadata +137 -30
  153. metadata.gz.sig +1 -3
  154. data/lib/shaf/extensions/current_user.rb +0 -48
  155. data/lib/shaf/responder/hal_serializable.rb +0 -64
@@ -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