media_types-serialization 0.8.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +16 -3
  3. data/.prettierrc +1 -0
  4. data/CHANGELOG.md +42 -0
  5. data/CODE_OF_CONDUCT.md +74 -74
  6. data/Gemfile.lock +74 -83
  7. data/README.md +691 -179
  8. data/lib/media_types/problem.rb +64 -0
  9. data/lib/media_types/serialization.rb +497 -173
  10. data/lib/media_types/serialization/base.rb +115 -91
  11. data/lib/media_types/serialization/error.rb +186 -0
  12. data/lib/media_types/serialization/fake_validator.rb +52 -0
  13. data/lib/media_types/serialization/serialization_dsl.rb +117 -0
  14. data/lib/media_types/serialization/serialization_registration.rb +245 -0
  15. data/lib/media_types/serialization/serializers/api_viewer.rb +133 -0
  16. data/lib/media_types/serialization/serializers/common_css.rb +168 -0
  17. data/lib/media_types/serialization/serializers/endpoint_description_serializer.rb +80 -0
  18. data/lib/media_types/serialization/serializers/fallback_not_acceptable_serializer.rb +85 -0
  19. data/lib/media_types/serialization/serializers/fallback_unsupported_media_type_serializer.rb +58 -0
  20. data/lib/media_types/serialization/serializers/input_validation_error_serializer.rb +89 -0
  21. data/lib/media_types/serialization/serializers/problem_serializer.rb +100 -0
  22. data/lib/media_types/serialization/utils/accept_header.rb +77 -0
  23. data/lib/media_types/serialization/utils/accept_language_header.rb +82 -0
  24. data/lib/media_types/serialization/utils/header_list.rb +89 -0
  25. data/lib/media_types/serialization/version.rb +1 -1
  26. data/media_types-serialization.gemspec +48 -50
  27. metadata +48 -79
  28. data/.travis.yml +0 -17
  29. data/lib/generators/media_types/serialization/api_viewer/api_viewer_generator.rb +0 -25
  30. data/lib/generators/media_types/serialization/api_viewer/templates/api_viewer.html.erb +0 -98
  31. data/lib/generators/media_types/serialization/api_viewer/templates/initializer.rb +0 -33
  32. data/lib/generators/media_types/serialization/api_viewer/templates/template_controller.rb +0 -23
  33. data/lib/media_types/serialization/media_type/register.rb +0 -4
  34. data/lib/media_types/serialization/migrations_command.rb +0 -38
  35. data/lib/media_types/serialization/migrations_support.rb +0 -50
  36. data/lib/media_types/serialization/mime_type_support.rb +0 -64
  37. data/lib/media_types/serialization/no_content_type_given.rb +0 -11
  38. data/lib/media_types/serialization/no_media_type_serializers.rb +0 -11
  39. data/lib/media_types/serialization/no_serializer_for_content_type.rb +0 -15
  40. data/lib/media_types/serialization/renderer.rb +0 -41
  41. data/lib/media_types/serialization/renderer/register.rb +0 -4
  42. data/lib/media_types/serialization/wrapper.rb +0 -13
  43. data/lib/media_types/serialization/wrapper/html_wrapper.rb +0 -45
  44. data/lib/media_types/serialization/wrapper/media_collection_wrapper.rb +0 -59
  45. data/lib/media_types/serialization/wrapper/media_index_wrapper.rb +0 -59
  46. data/lib/media_types/serialization/wrapper/media_object_wrapper.rb +0 -55
  47. data/lib/media_types/serialization/wrapper_support.rb +0 -38
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+ require 'media_types/serialization/base'
5
+ require 'media_types/serialization/utils/accept_language_header'
6
+
7
+ module MediaTypes
8
+ module Serialization
9
+ module Serializers
10
+ class ProblemSerializer < MediaTypes::Serialization::Base
11
+
12
+ unvalidated 'application/vnd.delftsolutions.problem'
13
+ disable_wildcards
14
+
15
+ output do |problem, _, context|
16
+ raise 'No translations defined, add at least one title' unless problem.translations.keys.any?
17
+
18
+ accept_language_header = Utils::AcceptLanguageHeader.new(context.request.get_header(HEADER_ACCEPT_LANGUAGE) || '')
19
+ translation_entry = accept_language_header.map do |locale|
20
+ problem.translations.keys.find do |l|
21
+ l.start_with? locale.locale
22
+ end
23
+ end.compact.first || problem.translations.keys.first
24
+ translation = problem.translations[translation_entry]
25
+
26
+ title = translation[:title]
27
+ detail = translation[:detail] || problem.error.message
28
+
29
+ problem.custom_attributes.each do |key, value|
30
+ attribute key, value
31
+ end
32
+
33
+ attribute :type, problem.type
34
+ attribute :title, title unless title.nil?
35
+ attribute :detail, detail unless detail.nil?
36
+ attribute :instance, problem.instance unless problem.instance.nil?
37
+
38
+ emit
39
+ end
40
+ output_alias 'application/problem+json'
41
+
42
+ output_raw view: :html do |problem, _, context|
43
+ accept_language_header = Utils::AcceptLanguageHeader.new(context.request.get_header(HEADER_ACCEPT_LANGUAGE) || '')
44
+ translation_entry = accept_language_header.map do |locale|
45
+ problem.translations.keys.find do |l|
46
+ l.starts_with? locale.locale
47
+ end
48
+ end.compact.first || problem.translations.keys.first
49
+ translation = problem.translations[translation_entry]
50
+
51
+ title = translation[:title]
52
+ detail = translation[:detail] || problem.error.message
53
+
54
+ detail_lang = translation[:detail].nil? ? 'en' : translation_entry
55
+
56
+ input = OpenStruct.new(
57
+ title: title,
58
+ detail: detail,
59
+ help_url: problem.type,
60
+ css: CommonCSS.css,
61
+ )
62
+
63
+ template = ERB.new <<-TEMPLATE
64
+ <html lang="en">
65
+ <head>
66
+ <title>Error - <%= CGI::escapeHTML(title) %></title>
67
+ <style>
68
+ <%= css.split("\n").join("\n ") %>
69
+ </style>
70
+ </head>
71
+ <body>
72
+ <header>
73
+ <div id="logo"></div>
74
+ <h1>Error</h1>
75
+ </header>
76
+ <section id="content">
77
+ <nav>
78
+ <section id="description" lang="#{translation_entry}">
79
+ <h2><a href="<%= help_url %>"><%= CGI::escapeHTML(title) %></a></h2>
80
+ </section>
81
+ </nav>
82
+ <main lang="#{detail_lang}">
83
+ <p><%= detail %>
84
+ </main>
85
+ </section>
86
+ <!-- Made with ❤ by: https://delftsolutions.com -->
87
+ </body>
88
+ </html>
89
+ TEMPLATE
90
+ template.result(input.instance_eval { binding })
91
+ end
92
+
93
+ enable_wildcards
94
+
95
+ output_alias_optional 'text/html', view: :html
96
+
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,77 @@
1
+ =begin
2
+ The MIT License (MIT)
3
+
4
+ Copyright (c) 2019 Derk-Jan Karrenbeld
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ =end
24
+
25
+ require 'media_types/serialization/utils/header_list'
26
+
27
+ module MediaTypes
28
+ module Serialization
29
+ module Utils
30
+ class AcceptHeader < DelegateClass(Array)
31
+ def initialize(value)
32
+ __setobj__ HeaderList.new(value, entry_klazz: AcceptHeader::Entry)
33
+ end
34
+
35
+ class Entry
36
+ def initialize(media_type, index:, parameters:)
37
+ self.media_type = media_type
38
+ self.parameters = parameters
39
+ self.index = index
40
+
41
+ freeze
42
+ end
43
+
44
+ attr_reader :media_type
45
+
46
+ # noinspection RubyInstanceMethodNamingConvention
47
+ def q
48
+ parameters.fetch(:q) { 1.0 }.to_f
49
+ end
50
+
51
+ def <=>(other)
52
+ quality = other.q <=> q
53
+ return quality unless quality.zero?
54
+ index <=> other.send(:index)
55
+ end
56
+
57
+ def [](parameter)
58
+ parameters.fetch(String(parameter).to_sym)
59
+ end
60
+
61
+ def to_header
62
+ to_s
63
+ end
64
+
65
+ def to_s
66
+ [media_type].concat(parameters.map { |k, v| "#{k}=#{v}" }).compact.reject(&:empty?).join('; ')
67
+ end
68
+
69
+ private
70
+
71
+ attr_writer :media_type
72
+ attr_accessor :parameters, :index
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,82 @@
1
+ =begin
2
+ The MIT License (MIT)
3
+
4
+ Copyright (c) 2019 Derk-Jan Karrenbeld
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ =end
24
+
25
+ require 'media_types/serialization/utils/header_list'
26
+
27
+ module MediaTypes
28
+ module Serialization
29
+ module Utils
30
+ class AcceptLanguageHeader < DelegateClass(Array)
31
+ def initialize(value)
32
+ __setobj__ HeaderList.new(value, entry_klazz: Entry)
33
+ end
34
+
35
+ class Entry
36
+
37
+ DELIMITER = '-'
38
+
39
+ attr_reader :locale, :region, :language
40
+
41
+ def initialize(locale, index:, parameters:)
42
+ self.locale = locale
43
+ # TODO: support extlang correctly, maybe we don't even need this
44
+ self.language, self.region = locale.split(DELIMITER)
45
+ self.parameters = parameters
46
+ self.index = index
47
+
48
+ freeze
49
+ end
50
+
51
+ # noinspection RubyInstanceMethodNamingConvention
52
+ def q
53
+ parameters.fetch(:q) { 1.0 }.to_f
54
+ end
55
+
56
+ def <=>(other)
57
+ quality = other.q <=> q
58
+ return quality unless quality.zero?
59
+ index <=> other.send(:index)
60
+ end
61
+
62
+ def [](parameter)
63
+ parameters.fetch(String(parameter).to_sym)
64
+ end
65
+
66
+ def to_header
67
+ to_s
68
+ end
69
+
70
+ def to_s
71
+ [locale].concat(parameters.map { |k, v| "#{k}=#{v}" }).compact.reject(&:empty?).join('; ')
72
+ end
73
+
74
+ private
75
+
76
+ attr_writer :locale, :region, :language
77
+ attr_accessor :parameters, :index
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,89 @@
1
+ =begin
2
+ The MIT License (MIT)
3
+
4
+ Copyright (c) 2019 Derk-Jan Karrenbeld
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ =end
24
+
25
+ module MediaTypes
26
+ module Serialization
27
+ module Utils
28
+ ##
29
+ # @example Accept values
30
+ #
31
+ # class AcceptHeader < DelegateClass(Array)
32
+ # def initialize(value)
33
+ # super MediaTypes::Serialization::Utils::HeaderList.new(value, entry_klazz: AcceptHeader::Entry)
34
+ # end
35
+ #
36
+ # class Entry
37
+ # def initialize(media_type, index: parameters:)
38
+ # ...
39
+ # end
40
+ #
41
+ # def q
42
+ # parameters.fetch(:q) { 1.0 }.to_f
43
+ # end
44
+ #
45
+ # def <=>(other)
46
+ # quality = other.q <=> q
47
+ # return quality unless quality.zero?
48
+ # index <=> other.index
49
+ # end
50
+ # end
51
+ # end
52
+ #
53
+ # Accept.new(['*/*; q=0.1', 'application/json, text/html; q=0.8'])
54
+ # # => List['application/json', 'text/html', '*/*']
55
+ #
56
+ module HeaderList
57
+ HEADER_DELIMITER = ','
58
+ PARAMETER_DELIMITER = ';'
59
+
60
+ module_function
61
+
62
+ def parse(combined, entry_klazz:)
63
+ Array(combined).map { |line| line.split(HEADER_DELIMITER) }.flatten.each_with_index.map do |entry, index|
64
+ value, *parameters = entry.strip.split(PARAMETER_DELIMITER)
65
+ indexed_parameters = ::Hash[Array(parameters).map { |p| p.strip.split('=') }].transform_keys!(&:to_sym)
66
+ entry_klazz.new(value, index: index, parameters: indexed_parameters)
67
+ end
68
+ end
69
+
70
+ def new(combined, entry_klazz:)
71
+ result = parse(combined, entry_klazz: entry_klazz)
72
+ entry_klazz.instance_methods(false).include?(:<=>) ? result.sort! : result
73
+ end
74
+
75
+ def to_header(list)
76
+ # noinspection RubyBlockToMethodReference
77
+ list.map { |entry| stringify_entry(entry) }
78
+ .join("#{HEADER_DELIMITER} ")
79
+ end
80
+
81
+ def stringify_entry(entry)
82
+ return entry.to_header if entry.respond_to?(:to_header)
83
+ return entry.to_s if entry.respond_to?(:to_s)
84
+ entry.inspect
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,5 +1,5 @@
1
1
  module MediaTypes
2
2
  module Serialization
3
- VERSION = '0.8.0'
3
+ VERSION = '1.1.0'.freeze
4
4
  end
5
5
  end
@@ -1,50 +1,48 @@
1
-
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'media_types/serialization/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = 'media_types-serialization'
8
- spec.version = MediaTypes::Serialization::VERSION
9
- spec.authors = ['Derk-Jan Karrenbeld']
10
- spec.email = ['derk-jan@xpbytes.com']
11
-
12
- spec.summary = 'Add media types supported serialization using your favourite serializer'
13
- spec.homepage = 'https://github.com/XPBytes/media_types-serialization'
14
- spec.license = 'MIT'
15
-
16
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
- # to allow pushing to a single host or delete this section to allow pushing to any host.
18
- if spec.respond_to?(:metadata)
19
- # spec.metadata['allowed_push_host'] = 'TODO: Set to 'http://mygemserver.com''
20
-
21
- spec.metadata['homepage_uri'] = spec.homepage
22
- spec.metadata['source_code_uri'] = spec.homepage
23
- spec.metadata['changelog_uri'] = spec.homepage + '/CHANGELOG.md'
24
- else
25
- raise 'RubyGems 2.0 or newer is required to protect against ' \
26
- 'public gem pushes.'
27
- end
28
-
29
- # Specify which files should be added to the gem when it is released.
30
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
32
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
33
- end
34
- spec.bindir = 'exe'
35
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
- spec.require_paths = ['lib']
37
-
38
- spec.add_dependency 'actionpack', '>= 4.0.0'
39
- spec.add_dependency 'activesupport', '>= 4.0.0'
40
- spec.add_dependency 'media_types', '>= 0.6.2'
41
- spec.add_dependency 'oj', '>= 3.5.0'
42
- spec.add_dependency 'http_headers-accept', '>= 0.2.2', '< 1.0.0'
43
- spec.add_dependency 'http_headers-link', '< 1.0.0'
44
-
45
- spec.add_development_dependency 'awesome_print'
46
- spec.add_development_dependency 'bundler', '~> 2.0'
47
- spec.add_development_dependency 'rails', '~> 5.2'
48
- spec.add_development_dependency 'rake', '~> 10.0'
49
- spec.add_development_dependency 'minitest', '~> 5.0'
50
- end
1
+
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'media_types/serialization/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'media_types-serialization'
8
+ spec.version = MediaTypes::Serialization::VERSION
9
+ spec.authors = ['Derk-Jan Karrenbeld', 'Max Maton']
10
+ spec.email = ['derk-jan@xpbytes.com', 'max@delftsolutions.nl']
11
+
12
+ spec.summary = 'Add media types supported serialization using your favourite serializer'
13
+ spec.homepage = 'https://github.com/XPBytes/media_types-serialization'
14
+ spec.license = 'MIT'
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ # spec.metadata['allowed_push_host'] = 'TODO: Set to 'http://mygemserver.com''
20
+
21
+ spec.metadata['homepage_uri'] = spec.homepage
22
+ spec.metadata['source_code_uri'] = spec.homepage
23
+ spec.metadata['changelog_uri'] = spec.homepage + '/CHANGELOG.md'
24
+ else
25
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
26
+ 'public gem pushes.'
27
+ end
28
+
29
+ # Specify which files should be added to the gem when it is released.
30
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
32
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
33
+ end
34
+ spec.bindir = 'exe'
35
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
+ spec.require_paths = ['lib']
37
+
38
+ spec.add_dependency 'actionpack', '>= 4.0.0'
39
+ spec.add_dependency 'activesupport', '>= 4.0.0'
40
+ spec.add_dependency 'media_types', '>= 2.0.0', '< 3.0.0'
41
+
42
+ spec.add_development_dependency 'awesome_print'
43
+ spec.add_development_dependency 'bundler'
44
+ spec.add_development_dependency 'rails', '~> 5.2'
45
+ spec.add_development_dependency 'rake', '~> 13.0'
46
+ spec.add_development_dependency 'minitest', '~> 5.0'
47
+ spec.add_development_dependency 'oj'
48
+ end