tiki 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 22362bea858f0557ca006c51c2b13a6fc5b48c7f182cbbfe4a98d0354c6705b0
4
+ data.tar.gz: dca021fca6946434e767c9353be636542c90612dd7ba18aeca74ebc4581a2446
5
+ SHA512:
6
+ metadata.gz: 567b28b4ab36e5d66a96dc246883c9cd4d5bf508b73b534b4897ca44c6b5797e267c3d455bdff5733ba1b9e862a1b417919940e6c9a69faf21f2daf2fb962a6d
7
+ data.tar.gz: 1d89c45ccd07820e41a1b3a4cab67ed3ec31312b6460d7344e4d4880276230654f862cfde375796fa7d75ae04abc66423aeea211abd982ddabbd7c0345aab57c
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'hanami-router', '>= 2.0.0.beta4'
4
+ gem 'optimist'
5
+
6
+ group :development do
7
+ gem 'rspec'
8
+ end
data/bin/tiki ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'tiki'
3
+
4
+ tiki
@@ -0,0 +1,76 @@
1
+ require_relative './props'
2
+ require_relative './schema'
3
+ require_relative './reference'
4
+ require_relative './parameter'
5
+
6
+ using Props
7
+
8
+ class Components
9
+ hash_props :schemas, :parameters
10
+
11
+ # Schema
12
+ def schema(name, type = nil, title = nil, **named, &block)
13
+ @schemas ||= []
14
+ schema = Schema.new type, title, **named
15
+ schema.instance_eval(&block) if block
16
+ @schemas.push [name, schema]
17
+ end
18
+
19
+ def object(name, title = nil, **named, &block)
20
+ schema name, :object, title, **named, &block
21
+ end
22
+
23
+ def array(name, items_type = nil, title = nil, ref: nil, **named, &block)
24
+ if ref
25
+ schema :array, title do
26
+ items ref: ref
27
+ end
28
+ else
29
+ schema name, :array, title do
30
+ items items_type, **named, &block
31
+ end
32
+ end
33
+ end
34
+
35
+ def hash_map(name, items_type = nil, title = nil, ref: nil, **named, &block)
36
+ if ref
37
+ schema :object, title do
38
+ additional_properties ref: ref
39
+ end
40
+ else
41
+ schema name, :object, title do
42
+ additional_properties items_type, **named, &block
43
+ end
44
+ end
45
+ end
46
+
47
+ # Response
48
+ def response(name = nil, _description = nil, ref: nil, **named, &block)
49
+ if ref
50
+ reference = Reference.new ref
51
+ @responses << [name, reference]
52
+ else
53
+ @responses ||= []
54
+ response = Response.new description, **named
55
+ response.instance_eval(&block) if block
56
+ @responses << [name, response]
57
+ end
58
+ end
59
+
60
+ # Parameter
61
+ def parameter(name = nil, ref: nil, **named, &block)
62
+ @parameters ||= []
63
+ if ref
64
+ reference = Reference.new ref
65
+ @parameters << [name, reference]
66
+ else
67
+ parameter = Parameter.new name, **named
68
+ parameter.instance_eval(&block) if block
69
+ @parameters << [name, parameter]
70
+ end
71
+ end
72
+
73
+ def to_spec
74
+ hash_props
75
+ end
76
+ end
@@ -0,0 +1,36 @@
1
+ require_relative './media-type'
2
+
3
+ module Content
4
+ def content(mime = nil, **named, &block)
5
+ @content ||= []
6
+ media_type = MediaType.new(**named)
7
+ media_type.instance_eval(&block) if block
8
+ @content.push [resolve_mime_type(mime), media_type]
9
+ media_type
10
+ end
11
+
12
+ def schema(type = nil, title = nil, **named, &block)
13
+ content do
14
+ schema type, title, **named, &block
15
+ end
16
+ end
17
+
18
+ def object(title, **named, &block)
19
+ schema :object, title, **named, &block
20
+ end
21
+
22
+ def array(items_type = nil, title = nil, **named, &block)
23
+ schema :array, title do
24
+ items items_type, **named, &block
25
+ end
26
+ end
27
+ end
28
+
29
+ MIME_TYPES = {
30
+ json: 'application/json',
31
+ xml: 'application/xml'
32
+ }
33
+
34
+ def resolve_mime_type(mime)
35
+ MIME_TYPES[mime] || mime || 'application/json'
36
+ end
@@ -0,0 +1,16 @@
1
+ require_relative './props'
2
+
3
+ using Props
4
+
5
+ class ExternalDocumentation
6
+ props :url, :description
7
+ scalar_props :url, :description
8
+
9
+ def initialize(url = nil)
10
+ @url = url
11
+ end
12
+
13
+ def to_spec
14
+ scalar_props
15
+ end
16
+ end
@@ -0,0 +1,57 @@
1
+ module ListHelpers
2
+ refine Symbol do
3
+ def |(other)
4
+ OrList.new self, other
5
+ end
6
+
7
+ def &(other)
8
+ AndList.new self, other
9
+ end
10
+ end
11
+
12
+ refine String do
13
+ def |(other)
14
+ OrList.new self, other
15
+ end
16
+
17
+ def &(other)
18
+ AndList.new self, other
19
+ end
20
+ end
21
+
22
+ refine Array do
23
+ def |(other)
24
+ OrList.new self, other
25
+ end
26
+
27
+ def &(other)
28
+ AndList.new self, other
29
+ end
30
+ end
31
+ end
32
+
33
+ class OrList
34
+ attr_reader :list
35
+
36
+ def initialize(*args)
37
+ @list = args
38
+ end
39
+
40
+ def |(other)
41
+ @list << other
42
+ self
43
+ end
44
+ end
45
+
46
+ class AndList
47
+ attr_reader :list
48
+
49
+ def initialize(*args)
50
+ @list = args
51
+ end
52
+
53
+ def &(other)
54
+ @list << other
55
+ self
56
+ end
57
+ end
@@ -0,0 +1,32 @@
1
+ require_relative './props'
2
+ require_relative './reference'
3
+ require_relative './schema'
4
+
5
+ using Props
6
+
7
+ class MediaType
8
+ props :ref
9
+
10
+ def initialize(schema: :object, ref: nil)
11
+ if ref
12
+ @schema = Reference.new ref
13
+ elsif schema
14
+ @schema = Schema.new schema
15
+ end
16
+ end
17
+
18
+ def schema(type = :object, title = nil, ref: nil, &block)
19
+ if ref
20
+ @schema = Reference.new ref
21
+ else
22
+ @schema = Schema.new type, title
23
+ @schema.instance_eval(&block) if block
24
+ end
25
+ end
26
+
27
+ def to_spec
28
+ props = {}
29
+ props[:schema] = @schema.to_spec if @schema
30
+ props
31
+ end
32
+ end
@@ -0,0 +1,88 @@
1
+ require_relative './props'
2
+ require_relative './parameter'
3
+ require_relative './response'
4
+ require_relative './reference'
5
+ require_relative './reason'
6
+ require_relative './request-body'
7
+
8
+ using Props
9
+
10
+ class Operation
11
+ props :operation_id, :summary, :description, :deprecated, :tags
12
+ scalar_props :operation_id, :summary, :description, :deprecated, :tags
13
+ hash_props :responses
14
+ array_props :parameters
15
+ object_props :request_body
16
+
17
+ def initialize(operation_id = nil)
18
+ @operation_id = operation_id
19
+ @responses = []
20
+ end
21
+
22
+ def external_documentation(url = nil, &block)
23
+ @external_documentation = ExternalDocumentation.new url
24
+ @external_documentation.instance_eval(&block) if block
25
+ end
26
+
27
+ def parameter(name = nil, ref: nil, **named, &block)
28
+ @parameters ||= []
29
+ if ref
30
+ reference = Reference.new ref
31
+ @parameters.push reference
32
+ else
33
+ parameter = Parameter.new name, **named
34
+ parameter.instance_eval(&block) if block
35
+ @parameters.push parameter
36
+ end
37
+ end
38
+
39
+ def request_body(*args, ref: nil, **named, &block)
40
+ if ref
41
+ @request_body = Reference.new ref, :request_body
42
+ else
43
+ body = RequestBody.new(*args, **named)
44
+ body.instance_eval(&block) if block
45
+ @request_body = body
46
+ end
47
+ end
48
+
49
+ def request_body!(*args, **named)
50
+ request_body(*args, required: true, **named)
51
+ end
52
+
53
+ alias request_body? request_body
54
+ alias body request_body
55
+ alias body! request_body!
56
+ alias body? request_body?
57
+
58
+ def response(status = nil, schema = nil, description: nil, ref: nil, &block)
59
+ if ref
60
+ @responses.push [status || 200, Reference.new(ref, :response)]
61
+ else
62
+ unless status.is_a?(Integer) || schema
63
+ schema = status
64
+ status = 200
65
+ end
66
+ response = Response.new schema, description: description || reason(status)
67
+ response.instance_eval(&block) if block
68
+ @responses.push [status, response]
69
+ end
70
+ end
71
+
72
+ def responses(*statuses)
73
+ statuses.each { |status| response status }
74
+ end
75
+
76
+ def tags(*tags)
77
+ @tags = tags.flatten
78
+ end
79
+
80
+ def to_spec
81
+ props = {}
82
+ scalar_props props
83
+ array_props props
84
+ hash_props props
85
+ object_props props
86
+ props
87
+ end
88
+ end
@@ -0,0 +1,48 @@
1
+ require_relative './props'
2
+ require_relative './schema'
3
+
4
+ using Props
5
+
6
+ INS = %i[query header cookie].freeze
7
+
8
+ class Parameter
9
+ props :description
10
+
11
+ def initialize(name, in: :query, required: false, schema: :string)
12
+ @name = name
13
+ @in = binding.local_variable_get(:in)
14
+ @required = required || @in == :path
15
+ @schema = Schema.new schema if schema
16
+ end
17
+
18
+ def required(required = true)
19
+ @required = required || @in == :path
20
+ end
21
+
22
+ def schema(type, &block)
23
+ @schema = Schema.new type
24
+ @schema.instance_eval(&block) if block
25
+ end
26
+
27
+ def path
28
+ @in = :path
29
+ @required = true
30
+ end
31
+
32
+ INS.each do |_in|
33
+ define_method _in do
34
+ @in = _in
35
+ end
36
+ end
37
+
38
+ def to_spec
39
+ props = { name: @name, in: @in, schema: @schema.to_spec }
40
+ props[:required] = true if @required
41
+ props[:description] = @description if @description
42
+ props
43
+ end
44
+
45
+ def get_name
46
+ @name.to_s
47
+ end
48
+ end
@@ -0,0 +1,71 @@
1
+ require_relative './props'
2
+ require_relative './operation'
3
+ require_relative './server'
4
+
5
+ using Props
6
+
7
+ VERBS = %i[get put post delete options head patch trace].freeze
8
+
9
+ class PathItem
10
+ include ServerMethods
11
+
12
+ props :ref, :summary, :description
13
+ scalar_props :summary, :description
14
+ array_props :servers
15
+
16
+ def initialize(parent, summary = nil, ref: nil)
17
+ @parameters = []
18
+ @parent = parent
19
+ @summary = summary
20
+ @ref = ref
21
+ end
22
+
23
+ VERBS.each do |verb|
24
+ define_method verb do |operation_id = nil, &block|
25
+ @operations ||= []
26
+ operation = Operation.new operation_id
27
+ operation.instance_eval(&block) if block
28
+ @operations.push [verb, operation]
29
+ end
30
+ end
31
+
32
+ def parameter(name = nil, ref: nil, **named, &block)
33
+ if ref
34
+ reference = Reference.new ref
35
+ @parameters.push reference
36
+ else
37
+ named[:in] ||= :path
38
+ parameter = Parameter.new name, **named
39
+ parameter.instance_eval(&block) if block
40
+ @parameters.push parameter
41
+ end
42
+ end
43
+
44
+ def path(url, summary = nil, **named, &block)
45
+ child = @parent.child url, @parameters
46
+ path = PathItem.new child, summary, **named
47
+ path.instance_eval(&block) if block
48
+ child.add path
49
+ end
50
+
51
+ def to_spec
52
+ if @ref
53
+ { :$ref => @ref }
54
+ else
55
+ props = @operations&.map(&->((n, s)) { { n => s.to_spec } })&.inject(&:merge) || {}
56
+ parameters = @parent.parameters + @parameters
57
+ props[:parameters] = parameters.map(&:to_spec) unless parameters.empty?
58
+ scalar_props props
59
+ array_props props
60
+ props
61
+ end
62
+ end
63
+
64
+ def check_parameters(path)
65
+ parameters = @parent.parameters + @parameters
66
+ params = path.scan(/\{([a-zA-Z0-9_]+)\}/).map(&:first)
67
+ params.each do |param|
68
+ @parameters << Parameter.new(param, in: :path) unless parameters.find { |p| p.get_name == param }
69
+ end
70
+ end
71
+ end
data/lib/tiki/props.rb ADDED
@@ -0,0 +1,96 @@
1
+ module Props
2
+ refine Class do
3
+ def props(*names)
4
+ names.flatten.each do |name|
5
+ define_method name do |value|
6
+ instance_variable_set("@#{name}", value)
7
+ end
8
+ end
9
+ end
10
+
11
+ def marker_props(*names)
12
+ names.flatten.each do |name|
13
+ define_method name do
14
+ instance_variable_set("@#{name}", true)
15
+ end
16
+ end
17
+ end
18
+
19
+ def named_props(*names)
20
+ define_method :named_props do |named|
21
+ names = names.flatten
22
+ named.each_pair do |name, value|
23
+ if names.include? name
24
+ instance_variable_set "@#{name}", value unless value.nil?
25
+ else
26
+ puts "Unknown named argument #{name}"
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def scalar_props(*names)
33
+ define_method :scalar_props do |props = {}|
34
+ names.flatten.each do |name|
35
+ value = instance_variable_get "@#{name}"
36
+ props[to_camel(name).to_sym] = value unless value.nil?
37
+ end
38
+ props
39
+ end
40
+ end
41
+
42
+ def object_props(*names)
43
+ define_method :object_props do |props = {}|
44
+ names.flatten.each do |name|
45
+ value = instance_variable_get "@#{name}"
46
+ props[to_camel(name).to_sym] = value.to_spec unless value.nil?
47
+ end
48
+ props
49
+ end
50
+ end
51
+
52
+ def object_or_scalar_props(*names)
53
+ define_method :object_or_scalar_props do |props = {}|
54
+ names.flatten.each do |name|
55
+ value = instance_variable_get "@#{name}"
56
+ unless value.nil?
57
+ value = value.to_spec if value.respond_to? :to_spec
58
+ props[to_camel(name).to_sym] = value
59
+ end
60
+ end
61
+ props
62
+ end
63
+ end
64
+
65
+ def hash_props(*names)
66
+ define_method :hash_props do |props = {}|
67
+ names.flatten.each do |name|
68
+ hashes = instance_variable_get "@#{name}"
69
+ unless hashes.nil?
70
+ value = hashes.map(&->((n, s)) { { n => s.to_spec } }).inject(&:merge) || {}
71
+ props[to_camel(name).to_sym] = value
72
+ end
73
+ end
74
+ props
75
+ end
76
+ end
77
+
78
+ def array_props(*names)
79
+ define_method :array_props do |props = {}|
80
+ names.flatten.each do |name|
81
+ array = instance_variable_get "@#{name}"
82
+ props[to_camel(name).to_sym] = array.map(&:to_spec) unless array.nil?
83
+ end
84
+ props
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def to_camel(snake)
91
+ snake.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
92
+ end
93
+
94
+ def to_snake(camel)
95
+ camel.to_s.gsub(/([A-Z])/) { "_#{Regexp.last_match(1).downcase}" }
96
+ end
@@ -0,0 +1,66 @@
1
+ REASONS =
2
+ {
3
+ 100 => 'Continue',
4
+ 101 => 'Switching Protocols',
5
+ 102 => 'Processing',
6
+ 200 => 'OK',
7
+ 201 => 'Created',
8
+ 202 => 'Accepted',
9
+ 203 => 'Non-Authoritative Information',
10
+ 204 => 'No Content',
11
+ 205 => 'Reset Content',
12
+ 206 => 'Partial Content',
13
+ 207 => 'Multi-Status',
14
+ 208 => 'Already Reported',
15
+ 226 => 'IM Used',
16
+ 300 => 'Multiple Choices',
17
+ 301 => 'Moved Permanently',
18
+ 302 => 'Found',
19
+ 303 => 'See Other',
20
+ 304 => 'Not Modified',
21
+ 305 => 'Use Proxy',
22
+ 307 => 'Temporary Redirect',
23
+ 308 => 'Permanent Redirect',
24
+ 400 => 'Bad Request',
25
+ 401 => 'Unauthorized',
26
+ 402 => 'Payment Required',
27
+ 403 => 'Forbidden',
28
+ 404 => 'Not Found',
29
+ 405 => 'Method Not Allowed',
30
+ 406 => 'Not Acceptable',
31
+ 407 => 'Proxy Authentication Required',
32
+ 408 => 'Request Timeout',
33
+ 409 => 'Conflict',
34
+ 410 => 'Gone',
35
+ 411 => 'Length Required',
36
+ 412 => 'Precondition Failed',
37
+ 413 => 'Payload Too Large',
38
+ 414 => 'URI Too Long',
39
+ 415 => 'Unsupported Media Type',
40
+ 416 => 'Range Not Satisfiable',
41
+ 417 => 'Expectation Failed',
42
+ 421 => 'Misdirected Request',
43
+ 422 => 'Unprocessable Entity',
44
+ 423 => 'Locked',
45
+ 424 => 'Failed Dependency',
46
+ 426 => 'Upgrade Required',
47
+ 428 => 'Precondition Required',
48
+ 429 => 'Too Many Requests',
49
+ 431 => 'Request Header Fields Too Large',
50
+ 451 => 'Unavailable For Legal Reasons',
51
+ 500 => 'Internal Server Error',
52
+ 501 => 'Not Implemented',
53
+ 502 => 'Bad Gateway',
54
+ 503 => 'Service Unavailable',
55
+ 504 => 'Gateway Timeout',
56
+ 505 => 'HTTP Version Not Supported',
57
+ 506 => 'Variant Also Negotiates',
58
+ 507 => 'Insufficient Storage',
59
+ 508 => 'Loop Detected',
60
+ 510 => 'Not Extended',
61
+ 511 => 'Network Authentication Required'
62
+ }.each { |_, v| v.freeze }.freeze
63
+
64
+ def reason(status)
65
+ REASONS[status]
66
+ end
@@ -0,0 +1,21 @@
1
+ GROUPS = {
2
+ schema: 'components/schemas',
3
+ response: 'components/responses',
4
+ request_body: 'components/requestBody'
5
+ }.freeze
6
+
7
+ class Reference
8
+ attr_reader :ref
9
+
10
+ def initialize(ref, group = :schema)
11
+ @ref = create_reference ref, group
12
+ end
13
+
14
+ def to_spec
15
+ { :$ref => @ref }
16
+ end
17
+ end
18
+
19
+ def create_reference(ref, group = :schema)
20
+ ref.is_a?(Symbol) ? "#/#{GROUPS[group]}/#{ref}" : ref
21
+ end
@@ -0,0 +1,27 @@
1
+ require_relative './props'
2
+ require_relative './content'
3
+
4
+ using Props
5
+
6
+ class RequestBody
7
+ include Content
8
+ props :description, :required
9
+ scalar_props :description
10
+ hash_props :content
11
+
12
+ def initialize(type = nil, mime = nil, description: nil, required: false)
13
+ @description = description
14
+ @required = required
15
+ content mime do
16
+ schema(type) if type
17
+ end
18
+ end
19
+
20
+ def to_spec
21
+ props = {}
22
+ scalar_props props
23
+ hash_props props
24
+ props[:required] = @required if @required
25
+ props
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require_relative './props'
2
+ require_relative './content'
3
+
4
+ using Props
5
+
6
+ # TODO: headers and links
7
+
8
+ class Response
9
+ include Content
10
+ props :description
11
+ scalar_props :description
12
+ hash_props :content
13
+
14
+ def initialize(type = nil, mime = nil, description: nil)
15
+ @description = description
16
+ content mime do
17
+ schema(type) if type
18
+ end
19
+ end
20
+
21
+ def to_spec
22
+ props = {}
23
+ scalar_props props
24
+ hash_props props
25
+ props
26
+ end
27
+ end