svelte 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ module Svelte
2
+ # Describes a Swagger API Path
3
+ class Path
4
+ attr_reader :non_parameter_elements, :parameter_elements
5
+
6
+ # Creates a new Path.
7
+ # @param path [String] path i.e. `'/store/inventory'`
8
+ # @param operations [Hash] path operations
9
+ def initialize(path:, operations:)
10
+ @path = path
11
+ separate_path_elements
12
+ @raw_operations = operations
13
+ end
14
+
15
+ # Path operations
16
+ # @return [Array<Operation>] list of operations for the path
17
+ def operations
18
+ validate_operations
19
+ @operations ||= @raw_operations.map do |operation, properties|
20
+ Operation.new(verb: operation, properties: properties, path: self)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def separate_path_elements
27
+ path_elements = @path.split('/').reject(&:empty?)
28
+ @non_parameter_elements,
29
+ @parameter_elements = path_elements.partition do |element|
30
+ !element.match(/\{\w+\}/)
31
+ end
32
+ @parameter_elements.map! { |p| p.scan(/{(\w*)}/) }.flatten!
33
+ end
34
+
35
+ def validate_operations
36
+ unless @raw_operations.is_a?(Hash)
37
+ raise JSONError, "Expected the path to contain a list of operations"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ module Svelte
2
+ # Dynamically builds a module hierarchy on top of a given module
3
+ # based on the given Path
4
+ class PathBuilder
5
+ class << self
6
+ # Builds a new Module hierarchy on top of `module_constant`
7
+ # If the path contains more than one part, modules will be built
8
+ # on top of each other.
9
+ #
10
+ # Example:
11
+ # If the `path` is `/store/inventory` and the `module_constant` is
12
+ # `Test`, the resulting module hierarchy will be `Test::Store::Inventory`
13
+ # @param path [Path] path to build
14
+ # @param module_constant [Module] operation to build
15
+ def build(path:, module_constant:)
16
+ create_module_hierarchy(base_module: module_constant,
17
+ additional_modules: path.non_parameter_elements)
18
+ end
19
+
20
+ private
21
+
22
+ def create_module_hierarchy(base_module:, additional_modules:)
23
+ additional_modules.reduce(base_module) do |current_module, element|
24
+ constant_name = StringManipulator.constant_name_for(element)
25
+
26
+ unless current_module.const_defined?(constant_name, false)
27
+ current_module.const_set(constant_name, Module.new)
28
+ end
29
+
30
+ current_module.const_get(constant_name, false)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'typhoeus'
4
+ require 'typhoeus/adapters/faraday'
5
+
6
+ module Svelte
7
+ # Rest client to make requests to the service endpoints
8
+ class RestClient
9
+ class << self
10
+
11
+ # Makes an http call to a given REST endpoint
12
+ # @param verb [String] http verb to use for the request
13
+ # (`get`, `post`, `put`, etc.)
14
+ # @param url [String] request url
15
+ # @param params [Hash] parameters to send to the request
16
+ # @param options [Hash] options
17
+ # @raise [HTTPError] if an HTTP layer error occurs,
18
+ # an exception will be raised
19
+ #
20
+ # @return [Faraday::Response] http response from the service
21
+ def call(verb:, url:, params: {}, options: {})
22
+ connection.send verb, url, params do |request|
23
+ request.options.timeout = options[:timeout] if options[:timeout]
24
+ end
25
+ rescue Faraday::TimeoutError => e
26
+ raise HTTPError.new(parent: e)
27
+ rescue Faraday::ResourceNotFound => e
28
+ raise HTTPError.new(parent: e)
29
+ rescue Faraday::ClientError => e
30
+ raise HTTPError.new(parent: e)
31
+ end
32
+
33
+ private
34
+
35
+ def connection
36
+ @@connection ||= Faraday.new(ssl: { verify: true }) do |faraday|
37
+ faraday.request :json
38
+ faraday.response :json, content_type: /\bjson$/
39
+ faraday.adapter :typhoeus
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,38 @@
1
+ module Svelte
2
+ # Dynamically generates a client to consume a Swagger API
3
+ class Service
4
+ class << self
5
+ # Generate a Service via URL or JSON.
6
+ # @param url [String] full URL of the Swagger API spec
7
+ # @param json [String] full Swagger API spec as a String
8
+ # @param module_name [String] constant name where Svelte will
9
+ # build the functionality on top of
10
+ # @return [Module] A newly created `Module` with the
11
+ # module hierarchy and methods to consume the Swagger API
12
+ # The new module will be built on top of `Svelte::Service` and will
13
+ # have `module_name` as its constant name, therefore it can also be
14
+ # accessed via `Svelte::Service::<module_name>`. For example, if
15
+ # `module_name` is `Comments`, the new module will be built in
16
+ # `Svelte::Service::Comments`
17
+ # @note Either `url` or `json` need to be provided. `url` will take
18
+ # precedence over `json`
19
+ def create(url:, json:, module_name:, options:)
20
+ json = get_json(url: url) if url
21
+ SwaggerBuilder.new(raw_hash: JSON.parse(json.to_s),
22
+ module_name: module_name,
23
+ options: options).make_resource
24
+ end
25
+
26
+ private
27
+
28
+ def get_json(url:)
29
+ Faraday.get(url).body
30
+ rescue Faraday::ClientError => e
31
+ raise HTTPError.new(
32
+ message: "Could not get API json from #{url}",
33
+ parent: e
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,66 @@
1
+ module Svelte
2
+ # Provides helper methods to manipulate strings in order to generate
3
+ # valid constant and method names from them
4
+ module StringManipulator
5
+ class << self
6
+ # Generates a valid Ruby constant name as similar as possible to `string`
7
+ # @param string [String] input string
8
+ # @return [String] a valid Ruby constant name based on `string`
9
+ def constant_name_for(string)
10
+ s = fixify(string)
11
+ pascalize(s)
12
+ end
13
+
14
+ # Generates a valid Ruby method name as similar as possible to `string`
15
+ # @param string [String] input string
16
+ # @return [String] a valid Ruby method name based on `string`
17
+ def method_name_for(string)
18
+ s = fixify(string)
19
+ snakify(s)
20
+ end
21
+
22
+ private
23
+
24
+ # Converts a single digit to a number.
25
+ def fixify(string)
26
+ dictionary = {
27
+ '1' => 'One',
28
+ '2' => 'Two',
29
+ '3' => 'Three',
30
+ '4' => 'Four',
31
+ '5' => 'Five',
32
+ '6' => 'Six',
33
+ '7' => 'Seven',
34
+ '8' => 'Eight',
35
+ '9' => 'Nine',
36
+ '0' => 'Zero'
37
+ }
38
+
39
+ string.sub(/^(\d+)/) { dictionary[Regexp.last_match[1]] }
40
+ end
41
+
42
+ def pascalize(string)
43
+ string.split('-').map(&method(:capitalize_first_char)).join
44
+ end
45
+
46
+ def capitalize_first_char(string)
47
+ string.sub(/^(.)/) { Regexp.last_match[1].capitalize }
48
+ end
49
+
50
+ def snakify(string)
51
+ string.gsub('-', '_').
52
+ # This first regex handles the case of a string ending in an acroynm
53
+ gsub(/([a-z])([A-Z]+)\z/, '\1_\2').
54
+ # This regex then handles acronyms in other places, including at
55
+ # the start of the string
56
+ # This is aided by the fact that a acronym cannot be preceded by
57
+ # an unrelated capital in camel case.
58
+ gsub(/([A-Z]+)([A-Z])([^A-Z_])/, '\1_\2\3').
59
+ # This then ensures all lower case letters that
60
+ # are not yet followed by an underscore
61
+ # or another lower case letter get an underscored appended.
62
+ gsub(/([a-z])([^a-z_])/, '\1_\2').downcase
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,94 @@
1
+ module Svelte
2
+ # Dynamically builds Swagger API paths and operations on top of a given module
3
+ class SwaggerBuilder
4
+ attr_reader :raw_hash, :module_name, :configuration
5
+
6
+ # Creates a new SwaggerBuilder
7
+ # @param raw_hash [Hash] Swagger API definition
8
+ # @param module_name [String] name of the constant you want built
9
+ # @param options [Hash] Swagger API options. It will be used to build the
10
+ # [Configuration]. Supports the `:host` value for now.
11
+ def initialize(raw_hash:, module_name:, options:)
12
+ @raw_hash = raw_hash
13
+ @module_name = module_name
14
+ @configuration = build_configuration(options)
15
+ validate
16
+ end
17
+
18
+ # Dynamically creates a new resource on top of `Svelte::Service` with the
19
+ # name `module_name`, based on the Swagger API description provided
20
+ # in `raw_hash`
21
+ # @return [Module] the module built
22
+ def make_resource
23
+ resource = Module.new
24
+ paths.each do |path|
25
+ new_module = PathBuilder.build(path: path, module_constant: resource)
26
+ path.operations.each do |operation|
27
+ OperationBuilder.build(operation: operation,
28
+ module_constant: new_module,
29
+ configuration: configuration)
30
+ end
31
+ end
32
+ Service.const_set(module_name, resource)
33
+ end
34
+
35
+ # @return [Array<Path>] Paths of the Swagger spec
36
+ def paths
37
+ raw_hash['paths'].map do |path, operations|
38
+ Path.new(path: path, operations: operations)
39
+ end
40
+ end
41
+
42
+ # @return [String] base path of the Swagger spec
43
+ def base_path
44
+ raw_hash['basePath']
45
+ end
46
+
47
+ # @return [String] host of the Swagger spec
48
+ def host
49
+ raw_hash['host']
50
+ end
51
+
52
+ private
53
+
54
+ def build_configuration(_options)
55
+ options = {
56
+ host: host,
57
+ base_path: base_path,
58
+ protocol: _options[:protocol]
59
+ }
60
+ Configuration.new(options: options)
61
+ end
62
+
63
+ def validate
64
+ validate_version
65
+ validate_paths
66
+ validate_host
67
+ validate_base_path
68
+ end
69
+
70
+ def validate_version
71
+ if raw_hash['swagger'] != '2.0'
72
+ raise VersionError
73
+ end
74
+ end
75
+
76
+ def validate_paths
77
+ unless raw_hash['paths'].is_a?(Hash)
78
+ raise JSONError, 'Expected JSON to contain an object of valid paths'
79
+ end
80
+ end
81
+
82
+ def validate_host
83
+ unless raw_hash['host'].is_a?(String)
84
+ raise JSONError, '`host` field in JSON is invalid'
85
+ end
86
+ end
87
+
88
+ def validate_base_path
89
+ unless raw_hash['basePath'].is_a?(String)
90
+ raise JSONError, '`basePath` field in JSON is invalid'
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,4 @@
1
+ module Svelte
2
+ # Version
3
+ VERSION = '0.1.1'.freeze
4
+ end
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'svelte/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'svelte'
8
+ spec.version = Svelte::VERSION
9
+ spec.authors = ['notonthehighstreet.com']
10
+ spec.email = ['tech.contact@notonthehighstreet.com']
11
+
12
+ spec.summary = 'Dynamic Ruby API Client from Swagger JSON Spec'
13
+ spec.description = 'This gem consumes a Swagger API json file and maps the API into easy-to-use Ruby objects'
14
+ spec.homepage = 'https://github.com/notonthehighstreet/svelte'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+ spec.required_ruby_version = '~> 2.1'
21
+
22
+ spec.add_dependency 'faraday', '~> 0.9'
23
+ spec.add_dependency 'faraday_middleware', '~> 0.10'
24
+ spec.add_dependency 'typhoeus', '~> 1.0'
25
+
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'redcarpet', '~> 3.3'
28
+ spec.add_development_dependency 'rspec', '~> 3.4'
29
+ spec.add_development_dependency 'simplecov', '~> 0.11'
30
+ spec.add_development_dependency 'webmock', '~> 1.22'
31
+ spec.add_development_dependency 'yard', '~> 0.8'
32
+ spec.add_development_dependency 'rubocop', '~> 0.36'
33
+ end
metadata ADDED
@@ -0,0 +1,219 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: svelte
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - notonthehighstreet.com
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '0.9'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '0.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday_middleware
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0.10'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: typhoeus
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: redcarpet
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '3.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '3.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '3.4'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '3.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '0.11'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '0.11'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '1.22'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: '1.22'
125
+ - !ruby/object:Gem::Dependency
126
+ name: yard
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: '0.8'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: '0.8'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ~>
144
+ - !ruby/object:Gem::Version
145
+ version: '0.36'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ~>
151
+ - !ruby/object:Gem::Version
152
+ version: '0.36'
153
+ description: This gem consumes a Swagger API json file and maps the API into easy-to-use
154
+ Ruby objects
155
+ email:
156
+ - tech.contact@notonthehighstreet.com
157
+ executables: []
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - .gitignore
162
+ - .rspec
163
+ - .rubocop.yml
164
+ - .ruby-version
165
+ - .travis.yml
166
+ - .yardopts
167
+ - CODE_OF_CONDUCT.md
168
+ - Gemfile
169
+ - LICENSE.txt
170
+ - README.md
171
+ - Rakefile
172
+ - bin/console
173
+ - bin/setup
174
+ - lib/svelte.rb
175
+ - lib/svelte/configuration.rb
176
+ - lib/svelte/errors.rb
177
+ - lib/svelte/errors/http_error.rb
178
+ - lib/svelte/errors/json_error.rb
179
+ - lib/svelte/errors/parameter_error.rb
180
+ - lib/svelte/errors/version_error.rb
181
+ - lib/svelte/generic_operation.rb
182
+ - lib/svelte/model_factory.rb
183
+ - lib/svelte/model_factory/parameter.rb
184
+ - lib/svelte/operation.rb
185
+ - lib/svelte/operation_builder.rb
186
+ - lib/svelte/path.rb
187
+ - lib/svelte/path_builder.rb
188
+ - lib/svelte/rest_client.rb
189
+ - lib/svelte/service.rb
190
+ - lib/svelte/string_manipulator.rb
191
+ - lib/svelte/swagger_builder.rb
192
+ - lib/svelte/version.rb
193
+ - svelte.gemspec
194
+ homepage: https://github.com/notonthehighstreet/svelte
195
+ licenses:
196
+ - MIT
197
+ metadata: {}
198
+ post_install_message:
199
+ rdoc_options: []
200
+ require_paths:
201
+ - lib
202
+ required_ruby_version: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - ~>
205
+ - !ruby/object:Gem::Version
206
+ version: '2.1'
207
+ required_rubygems_version: !ruby/object:Gem::Requirement
208
+ requirements:
209
+ - - '>='
210
+ - !ruby/object:Gem::Version
211
+ version: '0'
212
+ requirements: []
213
+ rubyforge_project:
214
+ rubygems_version: 2.4.6
215
+ signing_key:
216
+ specification_version: 4
217
+ summary: Dynamic Ruby API Client from Swagger JSON Spec
218
+ test_files: []
219
+ has_rdoc: