modern 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +8 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +173 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +10 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +36 -0
  10. data/Rakefile +10 -0
  11. data/TODOS.md +4 -0
  12. data/bin/console +9 -0
  13. data/bin/setup +8 -0
  14. data/example/Gemfile +9 -0
  15. data/example/Gemfile.lock +102 -0
  16. data/example/config.ru +11 -0
  17. data/example/example.rb +19 -0
  18. data/lib/modern/app/error_handling.rb +45 -0
  19. data/lib/modern/app/request_handling/input_handling.rb +65 -0
  20. data/lib/modern/app/request_handling/output_handling.rb +54 -0
  21. data/lib/modern/app/request_handling/request_container.rb +55 -0
  22. data/lib/modern/app/request_handling.rb +70 -0
  23. data/lib/modern/app/router.rb +27 -0
  24. data/lib/modern/app/trie_router.rb +37 -0
  25. data/lib/modern/app.rb +82 -0
  26. data/lib/modern/capsule.rb +17 -0
  27. data/lib/modern/configuration.rb +16 -0
  28. data/lib/modern/core_ext/array.rb +23 -0
  29. data/lib/modern/core_ext/hash.rb +17 -0
  30. data/lib/modern/descriptor/content.rb +14 -0
  31. data/lib/modern/descriptor/converters/input/base.rb +29 -0
  32. data/lib/modern/descriptor/converters/input/json.rb +21 -0
  33. data/lib/modern/descriptor/converters/output/base.rb +25 -0
  34. data/lib/modern/descriptor/converters/output/json.rb +48 -0
  35. data/lib/modern/descriptor/converters/output/yaml.rb +21 -0
  36. data/lib/modern/descriptor/converters.rb +4 -0
  37. data/lib/modern/descriptor/core.rb +63 -0
  38. data/lib/modern/descriptor/info.rb +27 -0
  39. data/lib/modern/descriptor/parameters.rb +149 -0
  40. data/lib/modern/descriptor/request_body.rb +13 -0
  41. data/lib/modern/descriptor/response.rb +26 -0
  42. data/lib/modern/descriptor/route.rb +93 -0
  43. data/lib/modern/descriptor/security.rb +104 -0
  44. data/lib/modern/descriptor/server.rb +12 -0
  45. data/lib/modern/descriptor.rb +15 -0
  46. data/lib/modern/doc_generator/open_api3/operations.rb +114 -0
  47. data/lib/modern/doc_generator/open_api3/paths.rb +24 -0
  48. data/lib/modern/doc_generator/open_api3/schema_default_types.rb +50 -0
  49. data/lib/modern/doc_generator/open_api3/schemas.rb +171 -0
  50. data/lib/modern/doc_generator/open_api3/security_schemes.rb +15 -0
  51. data/lib/modern/doc_generator/open_api3.rb +141 -0
  52. data/lib/modern/dsl/info.rb +39 -0
  53. data/lib/modern/dsl/response_builder.rb +41 -0
  54. data/lib/modern/dsl/root.rb +38 -0
  55. data/lib/modern/dsl/route_builder.rb +130 -0
  56. data/lib/modern/dsl/scope.rb +144 -0
  57. data/lib/modern/dsl/scope_settings.rb +39 -0
  58. data/lib/modern/dsl.rb +14 -0
  59. data/lib/modern/errors/error.rb +7 -0
  60. data/lib/modern/errors/setup_errors.rb +11 -0
  61. data/lib/modern/errors/web_errors.rb +83 -0
  62. data/lib/modern/errors.rb +3 -0
  63. data/lib/modern/redirect.rb +30 -0
  64. data/lib/modern/request.rb +34 -0
  65. data/lib/modern/response.rb +39 -0
  66. data/lib/modern/services.rb +17 -0
  67. data/lib/modern/struct.rb +25 -0
  68. data/lib/modern/types.rb +41 -0
  69. data/lib/modern/util/header_parsing.rb +27 -0
  70. data/lib/modern/util/trie_node.rb +53 -0
  71. data/lib/modern/version.rb +6 -0
  72. data/lib/modern.rb +8 -0
  73. data/manual/01-why_modern.md +115 -0
  74. data/modern.gemspec +54 -0
  75. metadata +439 -0
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "modern/struct"
4
+ require "modern/errors/web_errors"
5
+
6
+ module Modern
7
+ module Descriptor
8
+ module Parameters
9
+ class Base < Modern::Struct
10
+ # TODO: maybe do something behind-the-scenes where `required` is
11
+ # expressed as part of dry-types (| Types::Nil); we need
12
+ # the field for easily generating a doc later, but we could parse
13
+ # it out of the type if we had to.
14
+ attribute :name, Types::Strict::String
15
+ attribute :type, Types::Type
16
+ attribute :description, Types::Strict::String.optional.default(nil)
17
+ attribute :deprecated, Types::Strict::Bool.default(false)
18
+
19
+ def friendly_name
20
+ name
21
+ end
22
+
23
+ def retrieve(request, route_captures)
24
+ ret = do_retrieve(request, route_captures)
25
+
26
+ raise Errors::BadRequestError, "Invalid/missing parameter '#{friendly_name}'." \
27
+ if required && ret.nil?
28
+
29
+ begin
30
+ ret.nil? ? nil : type[ret]
31
+ rescue StandardError => _err
32
+ raise Errors::InternalServiceError,
33
+ "Couldn't interpret the value provided for parameter '#{friendly_name}': #{ret}."
34
+ end
35
+ end
36
+
37
+ def openapi3_in
38
+ self.class.name.split("::").last.downcase
39
+ end
40
+
41
+ def do_retrieve(_request, _route_captures)
42
+ raise "#{self.class.name}#do_retrieve(request, route_captures) must be implemented."
43
+ end
44
+
45
+ def to_openapi3(is_api_key = false)
46
+ {
47
+ name: friendly_name,
48
+ in: openapi3_in,
49
+ required: !is_api_key ? required : nil,
50
+ description: !is_api_key ? description : nil,
51
+ deprecated: !is_api_key ? deprecated : nil
52
+ }.compact
53
+ end
54
+ end
55
+
56
+ class Path < Base
57
+ # TODO: add 'matrix' and 'label'
58
+ attribute :style, Types::Coercible::String.default("simple").enum("simple")
59
+
60
+ def required
61
+ true
62
+ end
63
+
64
+ def do_retrieve(_request, route_captures)
65
+ route_captures[name]
66
+ end
67
+ end
68
+
69
+ class Cookie < Base
70
+ attribute :cookie_name, Types::Coercible::String
71
+ attribute :style, Types::Coercible::String.default("form").enum("form")
72
+ attribute :required, Types::Strict::Bool.default(false)
73
+
74
+ def friendly_name
75
+ cookie_name
76
+ end
77
+
78
+ def do_retrieve(request, _route_captures = nil)
79
+ request.cookies[cookie_name]
80
+ end
81
+ end
82
+
83
+ class Header < Base
84
+ attribute :header_name, Types::Coercible::String
85
+ attribute :style, Types::Coercible::String.default("simple").enum("simple")
86
+ attribute :required, Types::Strict::Bool.default(false)
87
+
88
+ attr_reader :rack_env_key
89
+
90
+ def initialize(fields)
91
+ super(fields)
92
+
93
+ @rack_env_key = "HTTP_" + header_name.upcase.tr("-", "_")
94
+ end
95
+
96
+ def friendly_name
97
+ header_name
98
+ end
99
+
100
+ def do_retrieve(request, _route_captures = nil)
101
+ request.env[@rack_env_key]
102
+ end
103
+ end
104
+
105
+ class Query < Base
106
+ # TODO: add 'space_delimited', 'pipe_delimited', 'deep_object'
107
+ attribute :style, Types::Coercible::String.default("form").enum("form")
108
+ attribute :required, Types::Strict::Bool.default(false)
109
+
110
+ attribute :allow_empty_value, Types::Strict::Bool.default(false)
111
+
112
+ attr_reader :parser
113
+
114
+ def initialize(fields)
115
+ super(fields)
116
+
117
+ @query_parser = Rack::QueryParser.make_default(99, 99)
118
+ end
119
+
120
+ def do_retrieve(request, _route_captures = nil)
121
+ @query_parser.parse_query(request.query_string)[name]
122
+ end
123
+
124
+ def to_openapi3(is_api_key = false)
125
+ super.merge(
126
+ allowEmptyValue: !is_api_key ? description : nil,
127
+ ).compact
128
+ end
129
+ end
130
+
131
+ def self.from_inputs(name, parameter_type, opts)
132
+ opts = opts.merge(name: name.to_s)
133
+
134
+ case parameter_type.to_sym
135
+ when :path
136
+ Modern::Descriptor::Parameters::Path.new(opts)
137
+ when :cookie
138
+ Modern::Descriptor::Parameters::Cookie.new(opts)
139
+ when :header
140
+ Modern::Descriptor::Parameters::Header.new(opts)
141
+ when :query
142
+ Modern::Descriptor::Parameters::Query.new(opts)
143
+ else
144
+ raise "Unrecognized parameter type '#{parameter_type}'.'"
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "modern/struct"
4
+
5
+ module Modern
6
+ module Descriptor
7
+ class RequestBody < Modern::Struct
8
+ attribute :type, (Types::Type | Types::Struct).optional.default(nil)
9
+ attribute :required, Types::Strict::Bool.default(false)
10
+ attribute :description, Types::Strict::String.optional.default(nil)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "modern/struct"
4
+ require "modern/descriptor/content"
5
+ require "modern/descriptor/parameters"
6
+
7
+ module Modern
8
+ module Descriptor
9
+ class Response < Modern::Struct
10
+ # TODO: figure out a good way to handle header responses
11
+ # This is in part an API response type of thing; how do we ensure that
12
+ # an action defines the header that it says it's defining?
13
+ attribute :http_code, Types::Strict::Int | Types.Value(:default)
14
+ attribute :description, Types::Strict::String.default("No description provided.")
15
+ attribute :content, Types.array_of(Content)
16
+
17
+ attr_reader :content_by_type
18
+
19
+ def initialize(fields)
20
+ super
21
+
22
+ @content_by_type = content.map { |c| [c.media_type, c] }.to_h.freeze
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ require "modern/struct"
6
+
7
+ require "modern/descriptor/converters"
8
+ require "modern/descriptor/response"
9
+ require "modern/descriptor/parameters"
10
+ require "modern/descriptor/request_body"
11
+ require "modern/descriptor/security"
12
+
13
+ module Modern
14
+ module Descriptor
15
+ class Route < Modern::Struct
16
+ # TODO: define OpenAPI-style callbacks
17
+ TEMPLATE_TOKEN = %r|\{.+\}|
18
+ OPENAPI_CAPTURE = %r|/\{(?<name>.+?)\}|
19
+
20
+ attribute :id, Types::String
21
+
22
+ attribute :http_method, Types::HttpMethod
23
+ attribute :path, Types::HttpPath
24
+
25
+ attribute :summary, Types::Strict::String.optional.default(nil)
26
+ attribute :description, Types::Strict::String.optional.default(nil)
27
+ attribute :deprecated, Types::Strict::Bool.default(false)
28
+ attribute :tags, Types.array_of(Types::Coercible::String)
29
+
30
+ attribute :parameters, Types.array_of(Parameters::Base)
31
+ attribute :request_body, RequestBody.optional.default(nil)
32
+ attribute :responses, Types.array_of(Response)
33
+
34
+ attribute :input_converters, Types.array_of(Modern::Descriptor::Converters::Input::Base)
35
+ attribute :output_converters, Types.array_of(Modern::Descriptor::Converters::Output::Base)
36
+
37
+ attribute :security, Types.array_of(Security::Base)
38
+ attribute :helpers, Types.array_of(Types.Instance(Module))
39
+ attribute :action, Types::RouteAction
40
+
41
+ attr_reader :path_matcher
42
+ attr_reader :route_tokens
43
+
44
+ attr_reader :content_types
45
+ attr_reader :responses_by_code
46
+
47
+ attr_reader :input_converters_by_type
48
+ attr_reader :output_converters_by_type
49
+
50
+ attr_reader :request_container_class
51
+
52
+ def initialize(fields)
53
+ super
54
+
55
+ @path_matcher = Regexp.new("^" + path.gsub(OPENAPI_CAPTURE, "/(?<\\k<name>>[^/]+)") + "$")
56
+ @route_tokens =
57
+ path.sub(%r|^/|, "").split("/").map do |token|
58
+ TEMPLATE_TOKEN =~ token ? :templated : token
59
+ end
60
+
61
+ @content_types = responses.map { |r| r.content.map(&:media_type) }.flatten.to_set.freeze
62
+ @responses_by_code = responses.map { |r| [r.http_code, r] }.to_h.freeze
63
+
64
+ raise "Cannot create a Route without a Response where http_code = :default." \
65
+ unless @responses_by_code.key?(:default)
66
+
67
+ _nondefault_content = @content_types - @responses_by_code[:default].content.map(&:media_type).to_set
68
+ # TODO: figure out how to better validate these
69
+ # This might be a larger-scale problem. The DSL creates a route with this, and you can end
70
+ # up in a case where you try to add a new content type to another response type. This causes
71
+ # the commented test below to fail unless you defined the :default response, with that content
72
+ # type, higher in the DSL. We might need some sort of intermediate builder class, or a way to
73
+ # squelch this error somehow...
74
+ # require 'pry'; binding.pry
75
+ # raise "Missing content types in default HTTP response for #{id}: #{nondefault_content.to_a.join(', ')}" \
76
+ # unless nondefault_content.empty?
77
+
78
+ @input_converters_by_type = input_converters.map { |c| [c.media_type.downcase.strip, c] }.to_h.freeze
79
+ @output_converters_by_type = output_converters.map { |c| [c.media_type.downcase.strip, c] }.to_h.freeze
80
+
81
+ @request_container_class =
82
+ if helpers.empty?
83
+ Modern::App::RequestHandling::FullRequestContainer
84
+ else
85
+ rcc = Class.new(Modern::App::RequestHandling::FullRequestContainer)
86
+ helpers.each { |h| rcc.send(:include, h) }
87
+
88
+ rcc
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "modern/descriptor/parameters"
4
+ require "modern/errors/web_errors"
5
+ require "modern/struct"
6
+
7
+ module Modern
8
+ module Descriptor
9
+ module Security
10
+ # TODO: implement OAuth2 security, with flow objects
11
+ # TODO: implement OpenID Connect security
12
+
13
+ # Securities in Modern allow for specifying the "plumbing" bits of the
14
+ # security in a predictable, unsurprising way. If you specify that you use
15
+ # HTTP authorization with the `Foobar` scheme--great. Modern finds the
16
+ # authorization header, checks to see if it's Foobar type, and retrieves
17
+ # the value that signifies its authentication. This value will be passed
18
+ # to the validator, which can determine whether or not its a valid bit of
19
+ # authentication.
20
+ #
21
+ # The idea is that, given that the validation gets access to a
22
+ # `PartialRequestContainer` that includes both the request and the
23
+ # application service set, it can connect to the auth server/user
24
+ # database/whatever and make sure it's atually a legitimate user. Since
25
+ # {Modern::Request} has a mutable store in {Modern::Request#local_store},
26
+ # the validator can then store a User object (or whatever) into it for use
27
+ # in the actual application.
28
+ class Base < Modern::Struct
29
+ attribute :name, Types::Strict::String
30
+ attribute :description, Types::Strict::String.optional.default(nil)
31
+ attribute :validation, Types::SecurityAction
32
+
33
+ def validate(container)
34
+ value = do_credential_fetch(container.request)
35
+
36
+ if value.nil?
37
+ false
38
+ else
39
+ !!container.instance_exec(value, &validation)
40
+ end
41
+ end
42
+
43
+ def do_credential_fetch(_request)
44
+ raise "#{self.class.name}#do_credential_fetch(request) must be implemented."
45
+ end
46
+
47
+ def to_openapi3
48
+ {
49
+ description: description
50
+ }
51
+ end
52
+ end
53
+
54
+ class ApiKey < Base
55
+ attribute :parameter, Parameters::Query | Parameters::Header | Parameters::Cookie
56
+
57
+ def initialize(fields)
58
+ super
59
+
60
+ # I didn't want to rewrite all my parameter logic.
61
+ raise Modern::Errors::SetupError, "Parameter must not be 'required' (internal limitation)." \
62
+ if parameter.required
63
+ end
64
+
65
+ def do_credential_fetch(request)
66
+ parameter.do_retrieve(request)
67
+ end
68
+
69
+ def to_openapi3
70
+ parameter.to_openapi3(true).merge(type: "apiKey")
71
+ end
72
+ end
73
+
74
+ class Http < Base
75
+ # aside: some people think that the Authorization field can support multiple sets of credentials,
76
+ # as RFC 7230 suggests that headers can be sent "multiple" times by using a comma to split them.
77
+ # however, this is for headers like Accept-Encoding. We don't need to split Authorization.
78
+ SPLITTER = %r,([^\s]+?)\s+(.*+),
79
+
80
+ attribute :scheme, Types::Strict::String
81
+
82
+ def do_credential_fetch(request)
83
+ header = request.env["HTTP_AUTHORIZATION"]
84
+
85
+ if header.nil?
86
+ nil
87
+ else
88
+ match = SPLITTER.match(header)
89
+ # yields #<MatchData "Bearer foo" 1:"Bearer" 2:"foo">
90
+
91
+ match[2].strip if !match.nil? && match[1].casecmp(scheme).zero?
92
+ end
93
+ end
94
+
95
+ def to_openapi3
96
+ super.merge(
97
+ type: "http",
98
+ scheme: scheme
99
+ )
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "modern/struct"
4
+
5
+ module Modern
6
+ module Descriptor
7
+ class Server < Modern::Struct
8
+ attribute :url, Modern::Types::Strict::String
9
+ attribute :description, Modern::Types::Strict::String.optional.default(nil)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modern/struct'
4
+
5
+ module Modern
6
+ module Descriptor
7
+ def self.define(title, version, &block)
8
+ require 'modern/dsl'
9
+
10
+ Modern::DSL::Root.build(title, version, &block)
11
+ end
12
+ end
13
+ end
14
+
15
+ Dir["#{__dir__}/descriptor/**/*.rb"].each { |f| require_relative f }
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modern
4
+ module DocGenerator
5
+ class OpenAPI3
6
+ module Operations
7
+ def _operation(route)
8
+ {
9
+ operationId: route.id,
10
+ summary: route.summary,
11
+ description: route.description,
12
+ deprecated: route.deprecated,
13
+ tags: route.tags.uniq,
14
+
15
+ security: route.security.map { |s| _security_requirement(s) },
16
+
17
+ parameters: route.parameters.map { |p| _parameter(p) },
18
+ requestBody: _request_body(route),
19
+ responses:
20
+ route.responses_by_code.map do |code, response|
21
+ [code, _response(response)]
22
+ end.to_h,
23
+ # TODO: implement callbacks
24
+ callbacks: nil
25
+ }.compact
26
+ end
27
+
28
+ def _security_requirement(security)
29
+ # TODO: OAuth2
30
+ ret = {}
31
+ ret[security.name] = []
32
+
33
+ ret
34
+ end
35
+
36
+ def _parameter(parameter)
37
+ # TODO: this should be in the parameter class, or that logic should be here.
38
+
39
+ parameter.to_openapi3.merge(
40
+ schema: _build_schema_value({}, {}, parameter.type)
41
+ )
42
+ end
43
+
44
+ def _request_body(route)
45
+ body = route.request_body
46
+
47
+ if body.nil?
48
+ nil
49
+ else
50
+ schema =
51
+ if body.type.nil?
52
+ nil
53
+ elsif body.type.is_a?(Class) && body.type.ancestors.include?(Dry::Struct)
54
+ _struct_ref(body.type)
55
+ else
56
+ # TODO: make this less wasteful (see _response)
57
+ _build_schema_value({}, {}, body.type)
58
+ end
59
+
60
+ {
61
+ required: body.required,
62
+ content:
63
+ route.input_converters.map(&:media_type).map do |content_type|
64
+ [
65
+ content_type,
66
+ {
67
+ schema: schema
68
+ }.compact
69
+ ]
70
+ end.to_h.compact
71
+ }
72
+ end
73
+ end
74
+
75
+ def _response(response)
76
+ {
77
+ description: response.description,
78
+ # headers:
79
+ # response.headers.map do |header|
80
+ # [
81
+ # header.name,
82
+ # {
83
+ # description: header.description
84
+ # }.compact
85
+ # ]
86
+ # end.to_h,
87
+ content:
88
+ response.content_by_type.map do |content_type, content|
89
+ [
90
+ content_type,
91
+ {
92
+ schema:
93
+ if content.type.nil?
94
+ nil
95
+ elsif content.type.is_a?(Class) && content.type.ancestors.include?(Dry::Struct)
96
+ _struct_ref(content.type)
97
+ else
98
+ # TODO: make this less wasteful
99
+ # Right now, this reuses the schema walking stuff. It totally will re-walk
100
+ # existing schemas as far as `content.type` will reach. Which is slower
101
+ # during startup than we'd like, but it's fixable later by converting
102
+ # the methods to using state (that's why `OpenAPI3` is an object in the
103
+ # first place, but I wrote myself into a corner here).
104
+ _build_schema_value({}, {}, content.type)
105
+ end
106
+ }.compact
107
+ ]
108
+ end.to_h
109
+ }.compact
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './operations'
4
+
5
+ module Modern
6
+ module DocGenerator
7
+ class OpenAPI3
8
+ module Paths
9
+ include Operations
10
+
11
+ def _paths(descriptor)
12
+ descriptor.routes_by_path.map do |path, routes_by_method|
13
+ [
14
+ path,
15
+ routes_by_method.map do |method, route|
16
+ [method.downcase, _operation(route)]
17
+ end.to_h
18
+ ]
19
+ end.to_h
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modern
4
+ module DocGenerator
5
+ class OpenAPI3
6
+ module SchemaDefaultTypes
7
+ def _register_default_types!
8
+ # TODO: handle all default types
9
+ # This misses a few types, mostly because I don't yet know how to
10
+ # handle them:
11
+ # - I don't understand the Types::Form set of types well enough.
12
+ # - I have not provided a DateTime mapping because you shouldn't
13
+ # use DateTime and I have no idea how to do so sanely.
14
+ # - We can't coerce Symbols, so those are out.
15
+ [Types::String, Types::Strict::String, Types::Coercible::String].each do |t|
16
+ register_literal_type(t, type: "string")
17
+ end
18
+
19
+ [Types::Int, Types::Strict::Int, Types::Coercible::Int].each do |t|
20
+ register_literal_type(t, type: "integer", format: "int64")
21
+ end
22
+
23
+ [
24
+ Types::Bool, Types::Strict::Bool,
25
+ Types::True, Types::Strict::True,
26
+ Types::False, Types::Strict::False
27
+ ].each do |t|
28
+ register_literal_type(t, type: "boolean")
29
+ end
30
+
31
+ [Types::Float, Types::Strict::Float, Types::Coercible::Float].each do |t|
32
+ register_literal_type(t, type: "number", format: "double")
33
+ end
34
+
35
+ [Types::Date, Types::Strict::Date, Types::Json::Date].each do |t|
36
+ register_literal_type(t, type: "string", format: "date")
37
+ end
38
+
39
+ [Types::Time, Types::Strict::Time, Types::Json::Time].each do |t|
40
+ register_literal_type(t, type: "string", format: "date")
41
+ end
42
+
43
+ [Types::Decimal, Types::Strict::Decimal, Types::Coercible::Decimal].each do |t|
44
+ register_literal_type(t, type: "number")
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end