shaf 1.6.0 → 3.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/iana_link_relations.csv.gz +0 -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/app.rb +32 -7
- 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/authenticator.rb +56 -0
- data/lib/shaf/command/base.rb +4 -0
- data/lib/shaf/command/console.rb +1 -1
- data/lib/shaf/command/generate.rb +5 -2
- data/lib/shaf/command/new.rb +24 -7
- data/lib/shaf/command/server.rb +5 -1
- data/lib/shaf/command/templates/Gemfile.erb +1 -0
- data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +9 -12
- data/lib/shaf/errors.rb +11 -0
- 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 +95 -137
- data/lib/shaf/extensions/symbolic_routes.rb +9 -19
- data/lib/shaf/extensions.rb +3 -3
- data/lib/shaf/formable/builder.rb +58 -19
- data/lib/shaf/formable/form.rb +14 -11
- data/lib/shaf/formable.rb +10 -24
- data/lib/shaf/generator/base.rb +84 -3
- data/lib/shaf/generator/controller.rb +30 -42
- data/lib/shaf/generator/doc.rb +17 -0
- data/lib/shaf/generator/forms.rb +11 -14
- data/lib/shaf/generator/helper.rb +2 -1
- data/lib/shaf/generator/migration/add_column.rb +0 -4
- data/lib/shaf/generator/migration/add_index.rb +0 -4
- data/lib/shaf/generator/migration/base.rb +15 -3
- data/lib/shaf/generator/migration/create_table.rb +0 -4
- data/lib/shaf/generator/migration/drop_column.rb +0 -4
- data/lib/shaf/generator/migration/rename_column.rb +0 -4
- data/lib/shaf/generator/migration/type.rb +4 -26
- data/lib/shaf/generator/migration/types.rb +45 -16
- data/lib/shaf/generator/model.rb +29 -15
- data/lib/shaf/generator/policy.rb +8 -14
- data/lib/shaf/generator/profile.rb +47 -0
- data/lib/shaf/generator/scaffold.rb +6 -9
- data/lib/shaf/generator/serializer.rb +64 -98
- data/lib/shaf/generator/templates/api/controller.rb.erb +13 -13
- data/lib/shaf/generator/templates/api/forms.rb.erb +2 -2
- data/lib/shaf/generator/templates/api/model.rb.erb +1 -1
- 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 +3 -3
- data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +15 -16
- data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
- data/lib/shaf/generator.rb +2 -0
- data/lib/shaf/helpers/authentication.rb +79 -0
- data/lib/shaf/helpers/paginate.rb +1 -1
- data/lib/shaf/helpers/payload.rb +14 -34
- data/lib/shaf/helpers/vary.rb +8 -0
- data/lib/shaf/helpers.rb +4 -0
- data/lib/shaf/logger.rb +12 -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/parser.rb +65 -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/profile.rb +115 -0
- data/lib/shaf/profiles/shaf_basic.rb +20 -0
- data/lib/shaf/profiles/shaf_error.rb +49 -0
- data/lib/shaf/profiles/shaf_form.rb +110 -0
- data/lib/shaf/profiles.rb +46 -0
- data/lib/shaf/registrable_factory.rb +62 -32
- data/lib/shaf/responder/alps_json.rb +25 -0
- data/lib/shaf/responder/base.rb +20 -17
- data/lib/shaf/responder/hal.rb +66 -5
- data/lib/shaf/responder/html.rb +43 -16
- data/lib/shaf/responder/problem_json.rb +2 -2
- data/lib/shaf/responder.rb +41 -2
- data/lib/shaf/router.rb +65 -12
- data/lib/shaf/serializer.rb +35 -0
- data/lib/shaf/settings.rb +25 -12
- 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 +26 -14
- data/lib/shaf/spec/payload_utils.rb +2 -2
- data/lib/shaf/spec.rb +1 -0
- data/lib/shaf/supported_http_methods.rb +15 -0
- data/lib/shaf/tasks/routes_task.rb +14 -17
- data/lib/shaf/tasks.rb +0 -1
- data/lib/shaf/upgrade/manifest.rb +11 -2
- data/lib/shaf/upgrade/package.rb +73 -45
- 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/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/lib/shaf/yard.rb +34 -0
- data/lib/shaf.rb +6 -0
- data/templates/Rakefile +0 -6
- data/templates/api/controllers/base_controller.rb +0 -12
- 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/root_serializer.rb +0 -11
- 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/authentication.rb +18 -0
- data/templates/config/initializers/db_migrations.rb +2 -2
- data/templates/config/initializers/logging.rb +2 -2
- data/templates/config/initializers/middleware.rb +6 -0
- data/templates/config/initializers.rb +52 -8
- data/templates/config.ru +1 -1
- data/templates/spec/spec_helper.rb +5 -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/1.6.1.tar.gz +0 -0
- data/upgrades/2.0.0.tar.gz +0 -0
- data/upgrades/3.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
- data.tar.gz.sig +2 -2
- metadata +143 -38
- metadata.gz.sig +0 -0
- data/lib/shaf/api_doc/comment.rb +0 -27
- data/lib/shaf/api_doc/document.rb +0 -137
- data/lib/shaf/extensions/current_user.rb +0 -48
- data/lib/shaf/middleware.rb +0 -1
- data/lib/shaf/responder/hal_serializable.rb +0 -64
- data/lib/shaf/tasks/api_doc_task.rb +0 -125
@@ -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
|
data/lib/shaf/profile.rb
ADDED
@@ -0,0 +1,115 @@
|
|
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
|
+
module NormalizeName
|
9
|
+
private def normalize(name)
|
10
|
+
name.to_s.downcase.tr('-', '_')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
extend NormalizeName
|
15
|
+
include NormalizeName
|
16
|
+
include Shaf::UriHelper
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def inherited(child)
|
20
|
+
Profiles.register child
|
21
|
+
end
|
22
|
+
|
23
|
+
def name(str = nil)
|
24
|
+
@name = str if str
|
25
|
+
@name if defined? @name
|
26
|
+
end
|
27
|
+
|
28
|
+
def doc(str = nil)
|
29
|
+
@doc = str if str
|
30
|
+
@doc if defined? @doc
|
31
|
+
end
|
32
|
+
|
33
|
+
def urn(value = nil)
|
34
|
+
@urn = value if value
|
35
|
+
@urn if defined? @urn
|
36
|
+
end
|
37
|
+
|
38
|
+
def example(str)
|
39
|
+
examples << str
|
40
|
+
end
|
41
|
+
|
42
|
+
def match?(str)
|
43
|
+
normalize(name) == normalize(str)
|
44
|
+
end
|
45
|
+
|
46
|
+
def attributes
|
47
|
+
@attributes ||= []
|
48
|
+
end
|
49
|
+
|
50
|
+
def relations
|
51
|
+
@relations ||= []
|
52
|
+
end
|
53
|
+
|
54
|
+
def examples
|
55
|
+
@examples ||= []
|
56
|
+
end
|
57
|
+
|
58
|
+
def attribute(*args, **kwargs, &block)
|
59
|
+
evaluator.attribute(*args, **kwargs, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def relation(*args, **kwargs, &block)
|
63
|
+
evaluator.rel(*args, **kwargs, &block)
|
64
|
+
end
|
65
|
+
alias rel relation
|
66
|
+
|
67
|
+
def descriptor(id)
|
68
|
+
find_attribute(id) || find_relation(id)
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_attribute(id)
|
72
|
+
attributes.find { |attr| attr.id.to_sym == id.to_sym }
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_relation(id)
|
76
|
+
relations.find { |rel| rel.id.to_sym == id.to_sym }
|
77
|
+
end
|
78
|
+
|
79
|
+
def use(*descriptors, from:, doc: nil)
|
80
|
+
descriptors.each do |id|
|
81
|
+
desc = from.descriptor(id)
|
82
|
+
href = profile_path(from.name, fragment_id: id)
|
83
|
+
|
84
|
+
case desc
|
85
|
+
when Relation
|
86
|
+
kwargs = {
|
87
|
+
doc: doc || desc&.doc,
|
88
|
+
href: href,
|
89
|
+
http_methods: desc.http_methods,
|
90
|
+
payload_type: desc.payload_type,
|
91
|
+
content_type: desc.content_type,
|
92
|
+
}
|
93
|
+
relation(id, **kwargs)
|
94
|
+
when Attribute
|
95
|
+
attribute(id, href: href, doc: doc)
|
96
|
+
when NilClass
|
97
|
+
raise "#{from.name} does not have a descriptor with id #{id}"
|
98
|
+
else
|
99
|
+
raise Errors::ServerError, "Unsupported descriptor: #{desc}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def evaluator
|
107
|
+
Evaluator.new(parent: self)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def name
|
112
|
+
normalize(self.class.name)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -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,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Profiles
|
5
|
+
class ShafError < Shaf::Profile
|
6
|
+
name 'shaf-error'
|
7
|
+
urn 'urn:shaf:error'
|
8
|
+
|
9
|
+
doc 'This profile describes a set of descriptors for generic error messages.'
|
10
|
+
example <<~EXAMPLE
|
11
|
+
{
|
12
|
+
"title": "Invalid entity",
|
13
|
+
"message": "The user could not be saved, due to unfulfilled requirements",
|
14
|
+
"code": "VALIDATION_ERROR",
|
15
|
+
"fields": {
|
16
|
+
"email": ["cannot be empty"]
|
17
|
+
}
|
18
|
+
}
|
19
|
+
EXAMPLE
|
20
|
+
|
21
|
+
example <<~EXAMPLE
|
22
|
+
{
|
23
|
+
"title": "Unpermitted action",
|
24
|
+
"message": "User is not allowed to edit this resource",
|
25
|
+
"code": "FORBIDDEN_ERROR",
|
26
|
+
}
|
27
|
+
EXAMPLE
|
28
|
+
|
29
|
+
attribute :code,
|
30
|
+
type: :string,
|
31
|
+
doc: 'An identifier that describes the type of error.'
|
32
|
+
|
33
|
+
attribute :title,
|
34
|
+
type: :string,
|
35
|
+
doc: 'A short string used for labeling the error.'
|
36
|
+
|
37
|
+
attribute :message,
|
38
|
+
type: :string,
|
39
|
+
doc: 'A description with details about the error.'
|
40
|
+
|
41
|
+
attribute :fields,
|
42
|
+
type: :string,
|
43
|
+
doc: 'An object describing validation errors. ' \
|
44
|
+
'The keys correspond to attributes of a resource. ' \
|
45
|
+
'The values are an array of strings describing validation errors ' \
|
46
|
+
'for the corresponding attribute.'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Profiles
|
5
|
+
class ShafForm < Shaf::Profile
|
6
|
+
name 'shaf-form'
|
7
|
+
urn 'urn:shaf:form'
|
8
|
+
|
9
|
+
doc 'This profile describes how a set of descriptors should be interpreted to ' \
|
10
|
+
'turn a generic media type into a form (similar to HTML forms). For ' \
|
11
|
+
'example, the HAL media type (application/hal+json) does not specify any ' \
|
12
|
+
'semantics about forms, however we can add semantics about forms to a HAL ' \
|
13
|
+
'document using this profile.'
|
14
|
+
|
15
|
+
example <<~EXAMPLE
|
16
|
+
Given the following form:
|
17
|
+
|
18
|
+
```json
|
19
|
+
"create-form": {
|
20
|
+
"method": "POST",
|
21
|
+
"name": "create-post",
|
22
|
+
"title": "Create Post",
|
23
|
+
"href": "/posts",
|
24
|
+
"type": "application/json",
|
25
|
+
"_links": {
|
26
|
+
"self": {
|
27
|
+
"href": "http://localhost:3000/posts/form"
|
28
|
+
}
|
29
|
+
},
|
30
|
+
"fields": [
|
31
|
+
{
|
32
|
+
"name": "title",
|
33
|
+
"type": "string",
|
34
|
+
},
|
35
|
+
{
|
36
|
+
"name": "message",
|
37
|
+
"type": "string",
|
38
|
+
}
|
39
|
+
]
|
40
|
+
}
|
41
|
+
```
|
42
|
+
|
43
|
+
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.
|
44
|
+
|
45
|
+
Using Curl, the form above could be submitted using:
|
46
|
+
|
47
|
+
```sh
|
48
|
+
curl -H "Content-Type: application/json" \
|
49
|
+
-X POST \
|
50
|
+
-d '{"title": "hello", "message": "world"}' \
|
51
|
+
localhost:3000/posts
|
52
|
+
```
|
53
|
+
EXAMPLE
|
54
|
+
|
55
|
+
attribute :method,
|
56
|
+
type: :string,
|
57
|
+
doc: 'The HTTP method used for submitting the form'
|
58
|
+
|
59
|
+
attribute :href,
|
60
|
+
type: :string,
|
61
|
+
doc: 'The target uri that the form should be submitted to'
|
62
|
+
|
63
|
+
attribute :name,
|
64
|
+
type: :string,
|
65
|
+
doc: 'A string used to indentify the form.'
|
66
|
+
|
67
|
+
attribute :title,
|
68
|
+
type: :string,
|
69
|
+
doc: 'A string used as title/label when showing the form in a user interface'
|
70
|
+
|
71
|
+
attribute :type,
|
72
|
+
type: :string,
|
73
|
+
doc: 'The media type that must be set in the CONTENT-TYPE header when submiting the form'
|
74
|
+
|
75
|
+
attribute :submit,
|
76
|
+
type: :string,
|
77
|
+
doc: 'A string used as label for the CallToAction (e.g. the submit button text)'
|
78
|
+
|
79
|
+
attribute :fields, doc: 'A list of field descriptors' do
|
80
|
+
|
81
|
+
attribute :name,
|
82
|
+
type: :string,
|
83
|
+
doc: 'A string that should be used as key for the field.'
|
84
|
+
|
85
|
+
attribute :type,
|
86
|
+
type: :string,
|
87
|
+
doc: 'A string specifying the possible values accepted by the field.'
|
88
|
+
|
89
|
+
attribute :title,
|
90
|
+
type: :string,
|
91
|
+
doc: 'A string used to present this field in a UI. If not present name may be used instead.'
|
92
|
+
|
93
|
+
attribute :value,
|
94
|
+
doc: 'A prefilled value. Should be shown in a user interface (unless hidden) and sent back on submission unless changed.'
|
95
|
+
|
96
|
+
attribute :required,
|
97
|
+
type: :boolean,
|
98
|
+
doc: 'A boolean specifying that the field must be given a value before the form is submitted.'
|
99
|
+
|
100
|
+
attribute :hidden,
|
101
|
+
type: :boolean,
|
102
|
+
doc: 'A boolean specifying that the field should not be shown in a user interface.'
|
103
|
+
|
104
|
+
attribute :accepted_values,
|
105
|
+
type: :array,
|
106
|
+
doc: 'A list of av acceptable values. Submitting a value not present in this list SHOULD result in a client error response.'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,46 @@
|
|
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 unregister(clazz)
|
18
|
+
profiles.delete(clazz)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find(name)
|
22
|
+
name = String(name)
|
23
|
+
return if name.empty?
|
24
|
+
|
25
|
+
profiles.find { |profile| profile.match? name }
|
26
|
+
end
|
27
|
+
|
28
|
+
def find!(name)
|
29
|
+
find(name) or raise ProfileNotFoundError, name
|
30
|
+
end
|
31
|
+
|
32
|
+
def profiles
|
33
|
+
@profiles ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
def clear
|
37
|
+
@profiles.clear
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
require 'shaf/profile'
|
44
|
+
require 'shaf/profiles/shaf_form'
|
45
|
+
require 'shaf/profiles/shaf_error'
|
46
|
+
require 'shaf/profiles/shaf_basic'
|
@@ -1,15 +1,58 @@
|
|
1
1
|
module Shaf
|
2
2
|
module RegistrableFactory
|
3
|
-
|
4
3
|
class NotFoundError < StandardError; end
|
4
|
+
class NoIdentifiersError < StandardError
|
5
|
+
|
6
|
+
def initialize(clazz)
|
7
|
+
super <<~ERR
|
8
|
+
#{clazz} does not have an @identifiers ivar.
|
9
|
+
Did you perhaps forget to call `#{clazz}.identifier`?
|
10
|
+
ERR
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Entry
|
15
|
+
attr_reader :clazz
|
16
|
+
|
17
|
+
def initialize(clazz)
|
18
|
+
@clazz = clazz
|
19
|
+
end
|
20
|
+
|
21
|
+
def match?(strings)
|
22
|
+
raise NoIdentifiersError, clazz unless identifiers
|
23
|
+
return false if strings.size < identifiers.size
|
24
|
+
identifiers.zip(strings).all? { |pattern, str| matching_identifier? str, pattern }
|
25
|
+
end
|
26
|
+
|
27
|
+
def identifier_count
|
28
|
+
identifiers&.size || 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def usage
|
32
|
+
clazz.instance_variable_get(:@usage)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def identifiers
|
38
|
+
clazz.identified_by
|
39
|
+
end
|
40
|
+
|
41
|
+
def matching_identifier?(str, pattern)
|
42
|
+
return false if pattern.nil? || str.nil? || str.empty?
|
43
|
+
pattern = pattern.to_s if pattern.is_a? Symbol
|
44
|
+
return str == pattern if pattern.is_a? String
|
45
|
+
!!str.match(pattern)
|
46
|
+
end
|
47
|
+
end
|
5
48
|
|
6
49
|
def all
|
7
|
-
reg.
|
50
|
+
reg.map(&:clazz)
|
8
51
|
end
|
9
52
|
|
10
|
-
def each
|
53
|
+
def each(&block)
|
11
54
|
return all.each unless block_given?
|
12
|
-
all.each
|
55
|
+
all.each(&block)
|
13
56
|
end
|
14
57
|
|
15
58
|
def size
|
@@ -17,34 +60,31 @@ module Shaf
|
|
17
60
|
end
|
18
61
|
|
19
62
|
def register(clazz)
|
20
|
-
reg << clazz
|
63
|
+
reg << Entry.new(clazz)
|
21
64
|
end
|
22
65
|
|
23
66
|
def unregister(*str)
|
24
67
|
return if str.empty? || !str.all?
|
25
|
-
reg.delete_if { |
|
68
|
+
reg.delete_if { |entry| entry.match? str }
|
26
69
|
end
|
27
70
|
|
28
71
|
def lookup(*str)
|
29
|
-
|
30
|
-
reg.select { |clazz| matching_class? str, clazz }
|
31
|
-
.sort_by(&method(:identifier_count))
|
32
|
-
.last
|
72
|
+
lookup_entry(*str)&.clazz
|
33
73
|
end
|
34
74
|
|
35
75
|
def usage
|
36
76
|
reg.compact.map do |entry|
|
37
|
-
usage = entry.
|
77
|
+
usage = entry.usage
|
38
78
|
usage.respond_to?(:call) ? usage.call : usage
|
39
79
|
end
|
40
80
|
end
|
41
81
|
|
42
82
|
def create(*params, **options)
|
43
|
-
|
44
|
-
raise NotFoundError.new(%Q(Command '#{ARGV}' is not supported)) unless
|
83
|
+
entry = lookup_entry(*params)
|
84
|
+
raise NotFoundError.new(%Q(Command '#{ARGV}' is not supported)) unless entry
|
45
85
|
|
46
|
-
args = init_args(
|
47
|
-
clazz.new(*args, **options)
|
86
|
+
args = init_args(entry, params)
|
87
|
+
entry.clazz.new(*args, **options)
|
48
88
|
end
|
49
89
|
|
50
90
|
private
|
@@ -53,25 +93,15 @@ module Shaf
|
|
53
93
|
@reg ||= []
|
54
94
|
end
|
55
95
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
def matching_identifier?(str, pattern)
|
63
|
-
return false if pattern.nil? || str.nil? || str.empty?
|
64
|
-
pattern = pattern.to_s if pattern.is_a? Symbol
|
65
|
-
return str == pattern if pattern.is_a? String
|
66
|
-
str.match(pattern) || false
|
67
|
-
end
|
68
|
-
|
69
|
-
def identifier_count(clazz)
|
70
|
-
clazz.instance_variable_get(:@identifiers)&.size || 0
|
96
|
+
def lookup_entry(*str)
|
97
|
+
return if str.empty? || !str.all?
|
98
|
+
reg.select { |entry| entry.match? str }
|
99
|
+
.sort_by { |entry| entry.identifier_count }
|
100
|
+
.last
|
71
101
|
end
|
72
102
|
|
73
|
-
def init_args(
|
74
|
-
first_non_id = identifier_count
|
103
|
+
def init_args(entry, params)
|
104
|
+
first_non_id = entry.identifier_count
|
75
105
|
params[first_non_id..-1]
|
76
106
|
end
|
77
107
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'shaf/alps/json_serializer'
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Responder
|
5
|
+
class AlpsJson < Base
|
6
|
+
mime_type :alps_json, 'application/alps+json'
|
7
|
+
|
8
|
+
def self.can_handle?(resource)
|
9
|
+
return false unless resource.is_a? Class
|
10
|
+
resource <= Shaf::Profile
|
11
|
+
end
|
12
|
+
|
13
|
+
def body
|
14
|
+
JSON.generate hash
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def hash
|
20
|
+
ALPS::JsonSerializer.call(resource)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
data/lib/shaf/responder/base.rb
CHANGED
@@ -2,12 +2,14 @@ module Shaf
|
|
2
2
|
module Responder
|
3
3
|
class Response
|
4
4
|
attr_reader :content_type, :body, :serialized_hash, :resource
|
5
|
+
attr_accessor :preload_links
|
5
6
|
|
6
7
|
def initialize(content_type:, body:, serialized_hash: {}, resource: nil)
|
7
8
|
@content_type = content_type
|
8
9
|
@body = body
|
9
10
|
@serialized_hash = serialized_hash
|
10
11
|
@resource = resource
|
12
|
+
@preload_links = []
|
11
13
|
end
|
12
14
|
|
13
15
|
def log_entry
|
@@ -36,11 +38,10 @@ module Shaf
|
|
36
38
|
end
|
37
39
|
|
38
40
|
def call(controller, resource, preload: [], **kwargs)
|
39
|
-
responder = new(controller, resource, **kwargs)
|
41
|
+
responder = new(controller, resource, preload_rels: preload, **kwargs)
|
40
42
|
response = responder.build_response
|
41
43
|
log_response(controller, response)
|
42
|
-
|
43
|
-
write_response(controller, response, preload_links)
|
44
|
+
write_response(controller, response)
|
44
45
|
end
|
45
46
|
|
46
47
|
def can_handle?(_obj)
|
@@ -58,23 +59,14 @@ module Shaf
|
|
58
59
|
controller.log.send(type, msg)
|
59
60
|
end
|
60
61
|
|
61
|
-
def
|
62
|
-
Array(rels).map do |rel|
|
63
|
-
links = responder.lookup_rel(rel, response)
|
64
|
-
links = [links].compact unless links.is_a? Array
|
65
|
-
log(controller, PRELOAD_FAILED_MSG % rel) if links.empty?
|
66
|
-
links
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def write_response(controller, response, preload_links)
|
62
|
+
def write_response(controller, response)
|
71
63
|
controller.content_type(response.content_type)
|
72
|
-
add_preload_links(controller, response
|
64
|
+
add_preload_links(controller, response)
|
73
65
|
controller.body(response.body)
|
74
66
|
end
|
75
67
|
|
76
|
-
def add_preload_links(controller, response
|
77
|
-
preload_links.each do |links|
|
68
|
+
def add_preload_links(controller, response)
|
69
|
+
response.preload_links.each do |links|
|
78
70
|
links.each do |link|
|
79
71
|
next unless link[:href]
|
80
72
|
# Nginx http2_push_preload only processes relative URIs with absolute path
|
@@ -111,9 +103,20 @@ module Shaf
|
|
111
103
|
body: body,
|
112
104
|
serialized_hash: serialized_hash,
|
113
105
|
resource: resource
|
114
|
-
)
|
106
|
+
).tap do |response|
|
107
|
+
response.preload_links = preload_links(response)
|
108
|
+
end
|
115
109
|
end
|
116
110
|
|
111
|
+
def preload_links(response)
|
112
|
+
Array(options[:preload_rels]).map do |rel|
|
113
|
+
links = lookup_rel(rel, response)
|
114
|
+
links = [links].compact unless links.is_a? Array
|
115
|
+
log(controller, PRELOAD_FAILED_MSG % rel) if links.empty?
|
116
|
+
links
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
117
120
|
def lookup_rel(_rel, _response)
|
118
121
|
[]
|
119
122
|
end
|