contracto 0.4.2 → 0.4.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c35eed00df100a4c4a2117e8a0d239cb20dcd117
4
- data.tar.gz: a55bf9e05603df1c4b03cc354d34096945795d02
3
+ metadata.gz: 893f39a06bea5e901e247bab7d2de6c37a3a0825
4
+ data.tar.gz: 1b8741717d48f6fa368529bfdd0c1c5815d67ba1
5
5
  SHA512:
6
- metadata.gz: 43d467496a10704a35fec13b81d11b1a2ccf4d7e502a371748ae637df018e022191a8af6ef0fd3bcbf056c7730172b4d8ac04484341b4adbfc66512c11919143
7
- data.tar.gz: bd325007dca162ebc8f9ae2e4d8c5455c9429ac0bacff26a4fdb003c0fef5a900449874cce5845815b719934d0eeb39466360e3fc3069d295d393ff8c1e33c6e
6
+ metadata.gz: 18efae1e83e4e38f7746080e0626c18207775f0edd47e5a6b359b66e9266ed57f85b405c38cb977aa22eab1b2b1dfc831f46af2868d76b6a668ab85c7b9385dd
7
+ data.tar.gz: 975b7ee12c51b0e6d77ea83d1c6510beae86243e872801d3c83b961e3b8d7da17c038234af37bbb72e380dac0644a384c3a4a056b3810e2a1c5afc38f3e1e924
data/README.md CHANGED
@@ -1,5 +1,3 @@
1
- # IN DEVELOPMENT
2
-
3
1
  # Contracto
4
2
 
5
3
  Creates HTTP server based on your contract.
@@ -12,11 +10,11 @@ Creates HTTP server based on your contract.
12
10
 
13
11
  Start server:
14
12
 
15
- $ contracto start git@github.com:kv109/contracto_sample-contract.git
13
+ $ contracto start https://github.com/contracto-lab/contracto-format.git
16
14
 
17
15
  Test server (default port is __54321__):
18
16
 
19
- $ curl 0.0.0.0:54321/users; curl 0.0.0.0:54321/users/1; curl 0.0.0.0:54321/users/2
17
+ $ curl localhost:54321/users; curl localhost:54321/users/1; curl localhost:54321/users/2
20
18
 
21
19
  Stop server:
22
20
 
@@ -6,7 +6,7 @@ class Contracto::Command::Init
6
6
  initialized = Contracto::SystemActionChain.new(*actions).execute
7
7
 
8
8
  if initialized
9
- puts 'contract initialized, enter \'contracto start\' to start server'
9
+ puts 'contract initialized, enter "contracto start" to start server'
10
10
  else
11
11
  puts 'initializing contract failed'
12
12
  end
@@ -3,6 +3,7 @@ class Contracto::Command::Start
3
3
  require_relative 'start/local'
4
4
 
5
5
  def initialize(args)
6
+ puts 'starting contracto server...'
6
7
  if args.first
7
8
  @strategy = Contracto::Command::Start::Remote.new(args.first)
8
9
  else
@@ -3,6 +3,7 @@ class Contracto::Command::Stop
3
3
  end
4
4
 
5
5
  def execute
6
+ puts 'killing contracto server...'
6
7
  Contracto::SystemActionChain.new(*actions).execute
7
8
  end
8
9
 
@@ -4,18 +4,17 @@ class Contracto::Config
4
4
  class << self
5
5
 
6
6
  attr_accessor :repo_url
7
- attr_accessor :root_dir
8
7
 
9
8
  def configure
10
9
  yield self if block_given?
11
10
  end
12
11
 
12
+ def root_dir=(root_dir)
13
+ @root_dir = "#{current_dir}/#{root_dir}"
14
+ end
15
+
13
16
  def root_dir
14
- @cached_root_dir ||= if @root_dir
15
- "#{current_dir}/#{@root_dir}"
16
- else
17
- default_root_dir
18
- end
17
+ @root_dir || default_root_dir
19
18
  end
20
19
 
21
20
  end
@@ -1,4 +1,5 @@
1
1
  class Contracto::Contract
2
+ require_relative 'contract/request'
2
3
  require_relative 'contract/response'
3
4
  require_relative 'stats'
4
5
 
@@ -6,8 +7,8 @@ class Contracto::Contract
6
7
 
7
8
  def initialize(hash)
8
9
  @hash = hash
9
- @request = Contracto::Contract::Request.new(@hash.fetch('request'))
10
- @responses = Contracto::Contract::Responses.new(@hash.fetch('responses'))
10
+ @request = Contracto::Contract::Request.new(@hash.fetch('schema').fetch('request'))
11
+ @responses = Contracto::Contract::Responses.new(@hash.fetch('examples'))
11
12
  end
12
13
 
13
14
  def http_method
@@ -45,18 +46,4 @@ class Contracto::Contract
45
46
  @responses.count
46
47
  end
47
48
  end
48
-
49
- class Contracto::Contract::Request
50
- def initialize(hash)
51
- @hash = hash
52
- end
53
-
54
- def http_method
55
- @hash.fetch('http_method')
56
- end
57
-
58
- def url_pattern
59
- @hash.fetch('path')
60
- end
61
- end
62
- end
49
+ end
@@ -0,0 +1,61 @@
1
+ class Contracto::Contract::Request
2
+ def initialize(hash)
3
+ @hash = hash
4
+ end
5
+
6
+ def http_method
7
+ @hash.fetch('method')
8
+ end
9
+
10
+ def url_pattern
11
+ PathToSinatraPathAdapter.new(@hash.fetch('path')).sinatra_path
12
+ end
13
+
14
+ class PathToSinatraPathAdapter
15
+ def initialize(path)
16
+ @path = path
17
+ end
18
+
19
+ def sinatra_path
20
+ parse_brackets! if path_with_brackets?
21
+ @path
22
+ end
23
+
24
+ private
25
+
26
+ def parse_brackets!
27
+ if fully_dynamic_route?
28
+ path_to_regexp!
29
+ else
30
+ brackets_to_colons!
31
+ end
32
+ end
33
+
34
+ def brackets_to_colons!
35
+ @path.gsub!('{', ':')
36
+ @path.gsub!('}', '')
37
+ end
38
+
39
+ def path_to_regexp!
40
+ brackets_to_named_captures!
41
+ @path = Regexp.new(@path)
42
+ end
43
+
44
+ def brackets_to_named_captures!
45
+ matches = @path.scan(/(\{([^\/])*\})/).map(&:first)
46
+ matches.each do |match|
47
+ @path.gsub! match, "(?<#{match}>\\w*)"
48
+ end
49
+ @path.gsub!('{', '')
50
+ @path.gsub!('}', '')
51
+ end
52
+
53
+ def fully_dynamic_route?
54
+ !@path.scan(/\}[^\/]/).empty? || !@path.scan(/[^\/]\{/).empty?
55
+ end
56
+
57
+ def path_with_brackets?
58
+ !!@path.match('{')
59
+ end
60
+ end
61
+ end
@@ -50,10 +50,7 @@ class Contracto::Contract::Response
50
50
  end
51
51
 
52
52
  def body
53
- set_body
54
- @body.tap do
55
- replace_params_placeholders_with_params_value
56
- end
53
+ File.read(Contracto::Config.root_dir + body_path)
57
54
  end
58
55
 
59
56
  private
@@ -66,18 +63,10 @@ class Contracto::Contract::Response
66
63
  end
67
64
  end
68
65
 
69
- def set_body
70
- @body = File.read(Contracto::Config.root_dir + body_path)
71
- end
72
-
73
66
  def human_header_key_to_http_header_key(key)
74
67
  key = key.upcase
75
68
  key = key.gsub('-', '_')
76
69
  key = 'HTTP_' + key
77
70
  key
78
71
  end
79
-
80
- def replace_params_placeholders_with_params_value
81
- # TODO
82
- end
83
72
  end
@@ -4,6 +4,9 @@ class Contracto::CouldNotDownloadContractError < StandardError
4
4
  end
5
5
  end
6
6
 
7
+ class Contracto::CouldNotStartServer < StandardError
8
+ end
9
+
7
10
  class Contracto::ServerAlreadyRunningError < StandardError
8
11
  def initialize
9
12
  super 'Could not start: Contracto server is already running'
@@ -0,0 +1,31 @@
1
+ require 'sinatra/base'
2
+
3
+ class Contracto::Server < Sinatra::Base
4
+
5
+ require_relative 'server/controller'
6
+ require_relative 'stats'
7
+
8
+ set :port, Contracto::Constants::PORT
9
+ set :show_exceptions, false
10
+
11
+ get '/contracto' do
12
+ "*** Contracto server is working! [#{Gem::Specification.find_by_name('contracto').version}] ***"
13
+ end
14
+
15
+ get '/contracto/terminate' do
16
+ Thread.new { sleep 1; Process.kill 'INT', Process.pid }
17
+ status 200
18
+ Contracto::Stats.summary rescue ''
19
+ end
20
+
21
+ not_found do
22
+ status 404
23
+ "Could not found example for #{request.url}"
24
+ end
25
+
26
+ error do |ex|
27
+ status 500
28
+ ["#{ex.class}: #{ex.message}", ex.backtrace[0, 15].join("\n")].join("\n")
29
+ end
30
+ end
31
+
@@ -0,0 +1,22 @@
1
+ class Contracto::Server < Sinatra::Base
2
+
3
+ jsons_with_contracts = Dir["#{Contracto::Config.root_dir}/**/*.contract.json"].map do |file_with_contract|
4
+ File.read file_with_contract
5
+ end
6
+
7
+ if jsons_with_contracts.empty?
8
+ puts "warning: no contracts found in #{Contracto::Config.root_dir}, create some *.contract.json files"
9
+ end
10
+
11
+ Contracto::Parser.new(jsons_with_contracts).contracts.each do |contract|
12
+ send(contract.http_method, contract.url_pattern) do
13
+ contract.response_body(params, http_headers)
14
+ end
15
+ end
16
+
17
+ def http_headers
18
+ env.select {|k,v| k.start_with? 'HTTP_'}
19
+ end
20
+
21
+ end
22
+
@@ -0,0 +1,75 @@
1
+ require 'daemons'
2
+ require 'net/http'
3
+ require 'uri'
4
+
5
+ class Contracto::Server < Sinatra::Base
6
+
7
+ extend Contracto::Constants
8
+
9
+ class << self
10
+
11
+ def start_contracto_server!
12
+ if contracto_server_running?
13
+ puts 'contracto server is already running, enter "contracto stop" to kill it'
14
+ return
15
+ elsif !root_dir_exists?
16
+ puts "current dir does not contain required contracto dir (#{Contracto::Config.root_dir})"
17
+ return
18
+ end
19
+
20
+ create_routes_from_contract
21
+ start_daemon!
22
+ wait_until_server_is_running
23
+ end
24
+
25
+ def stop_contracto_server!
26
+ uri = URI.parse("http://localhost:#{port}/contracto/terminate")
27
+ response = Net::HTTP.get_response(uri)
28
+ if response.is_a?(Net::HTTPOK)
29
+ puts 'contracto server killed'
30
+ puts response.body
31
+ else
32
+ puts "something went wrong: [#{response.code}] #{response.body}]"
33
+ end
34
+ rescue Errno::ECONNREFUSED
35
+ puts 'contracto server could not be killed (already dead or was never alive)'
36
+ end
37
+
38
+ private
39
+
40
+ def root_dir_exists?
41
+ Dir.exists?(Contracto::Config.root_dir)
42
+ end
43
+
44
+ def contracto_server_running?
45
+ uri = URI.parse("http://localhost:#{port}/contracto")
46
+ Net::HTTP.get_response(uri).is_a?(Net::HTTPOK)
47
+ rescue Errno::ECONNREFUSED
48
+ false
49
+ end
50
+
51
+ def create_routes_from_contract
52
+ require_relative 'contract_routes'
53
+ end
54
+
55
+ def start_daemon!
56
+ Daemons.call(app_name: server_pidfile_name, dir: Contracto::Config.root_dir, dir_mode: :normal) do
57
+ Contracto::Server.run!
58
+ end
59
+ end
60
+
61
+ def wait_until_server_is_running
62
+ 10.downto(0).each do |n|
63
+ sleep 1
64
+ puts "waiting for contracto server, #{n} tries left..."
65
+ if contracto_server_running?
66
+ puts '...contracto server is working'
67
+ return true
68
+ end
69
+ end
70
+ raise Contracto::CouldNotStartServer
71
+ end
72
+ end
73
+
74
+ end
75
+
@@ -1,4 +1,7 @@
1
1
  class Contracto::Stats
2
+
3
+ NA_TEXT = 'N/A'
4
+
2
5
  class << self
3
6
  attr_accessor :all_contracts
4
7
 
@@ -13,5 +16,35 @@ class Contracto::Stats
13
16
  def all_responses
14
17
  @all_responses ||= all_contracts.map(&:responses).map(&:count).inject(&:+)
15
18
  end
19
+
20
+ def contracts_usage
21
+ return NA_TEXT if all_contracts.size.zero?
22
+
23
+ (used_contracts.size/all_contracts.size.to_f).round(2)
24
+ end
25
+
26
+ def responses_usage
27
+ return NA_TEXT if all_responses.size.zero?
28
+
29
+ (used_responses.size/all_responses.size.to_f).round(2)
30
+ end
31
+
32
+ def contracts_stats_summary
33
+ "contracts usage: #{used_contracts.size}/#{all_contracts.size} (#{contracts_usage * 100}%)"
34
+ end
35
+
36
+ def responses_stats_summary
37
+ "examples usage: #{used_responses.size}/#{all_responses.size} (#{responses_usage * 100}%)"
38
+ end
39
+
40
+ def summary
41
+ length = contracts_stats_summary.length
42
+ [
43
+ 'stats'.center(length, '-'),
44
+ contracts_stats_summary,
45
+ responses_stats_summary,
46
+ ('-' * length) + ' '
47
+ ].join("\n")
48
+ end
16
49
  end
17
- end
50
+ end
@@ -1,8 +1,11 @@
1
+ require 'fileutils'
2
+
1
3
  class Contracto::SystemAction
2
- require 'fileutils'
4
+ require_relative 'server'
5
+
6
+ extend Contracto::Constants
3
7
 
4
8
  class << self
5
- include Contracto::Constants
6
9
 
7
10
  def create_sample_contract
8
11
  if Dir.exists?(Contracto::Config.root_dir)
@@ -17,37 +20,15 @@ class Contracto::SystemAction
17
20
  end
18
21
 
19
22
  def start_server
20
- raise Contracto::ServerAlreadyRunningError if server_already_running?
21
-
22
- require_relative 'server/ruby/server'
23
- require 'daemons'
24
-
25
- options = {
26
- app_name: server_pidfile_name,
27
- dir: Contracto::Config.root_dir,
28
- dir_mode: :normal
29
- }
30
-
31
- Daemons.call(options) do
32
- Contracto::Server.run!
33
- end
34
-
35
- 5.downto(0).each do |n|
36
- sleep 1
37
- puts "waiting for contracto server, #{n} tries left..."
38
- break if test_request(silent: true)
39
- end
40
- end
41
-
42
- def stop_server
43
- puts 'killing server...'
44
- system "curl 0.0.0.0:#{port}/contracto_terminate"
45
- puts '...server killed'
23
+ Contracto::Server.start_contracto_server!
46
24
  end
47
25
 
48
26
  def revert_start_server
49
27
  stop_server
50
- rescue StandardError
28
+ end
29
+
30
+ def stop_server
31
+ Contracto::Server.stop_contracto_server!
51
32
  end
52
33
 
53
34
  def clone_repo
@@ -66,15 +47,6 @@ class Contracto::SystemAction
66
47
  FileUtils.rm_rf Contracto::Config.root_dir
67
48
  end
68
49
 
69
- def server_already_running?
70
- test_request(silent: true)
71
- end
72
-
73
- def test_request(options = {})
74
- args = ''
75
- args << '-s -o /dev/null' if options[:silent]
76
- system "curl #{args} 0.0.0.0:#{port}/contracto"
77
- end
78
50
  end
79
51
  end
80
52
 
@@ -1,3 +1,3 @@
1
1
  module Contracto
2
- VERSION = '0.4.2'
2
+ VERSION = '0.4.3'
3
3
  end
@@ -1,9 +1,11 @@
1
1
  {
2
- "request": {
3
- "http_method": "get",
4
- "path": "/users/:id"
2
+ "schema": {
3
+ "request": {
4
+ "method": "get",
5
+ "path": "/users/:id"
6
+ }
5
7
  },
6
- "responses": [
8
+ "examples": [
7
9
  {
8
10
  "request": {
9
11
  "params": {
@@ -1,9 +1,11 @@
1
1
  {
2
- "request": {
3
- "http_method": "get",
4
- "path": "/users"
2
+ "schema": {
3
+ "request": {
4
+ "method": "get",
5
+ "path": "/users"
6
+ }
5
7
  },
6
- "responses": [
8
+ "examples": [
7
9
  {
8
10
  "request": {
9
11
  "params": {
@@ -1,9 +1,11 @@
1
1
  {
2
- "request": {
3
- "http_method": "post",
4
- "path": "/users"
2
+ "schema": {
3
+ "request": {
4
+ "method": "post",
5
+ "path": "/users"
6
+ }
5
7
  },
6
- "responses": [
8
+ "examples": [
7
9
  {
8
10
  "request": {
9
11
  "params": {
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contracto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kacper Walanus
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-17 00:00:00.000000000 Z
11
+ date: 2015-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -119,11 +119,13 @@ files:
119
119
  - lib/contracto/config.rb
120
120
  - lib/contracto/constants.rb
121
121
  - lib/contracto/contract.rb
122
+ - lib/contracto/contract/request.rb
122
123
  - lib/contracto/contract/response.rb
123
124
  - lib/contracto/errors.rb
124
125
  - lib/contracto/parser.rb
125
- - lib/contracto/server/ruby/config.ru
126
- - lib/contracto/server/ruby/server.rb
126
+ - lib/contracto/server.rb
127
+ - lib/contracto/server/contract_routes.rb
128
+ - lib/contracto/server/controller.rb
127
129
  - lib/contracto/stats.rb
128
130
  - lib/contracto/system_action.rb
129
131
  - lib/contracto/system_action_chain.rb
@@ -131,10 +133,9 @@ files:
131
133
  - script/send_test_requests.sh
132
134
  - script/start_from_remote.sh
133
135
  - script/start_locally.sh
134
- - spec/fixtures/get_users.con.json
135
- - spec/fixtures/get_users_by_id.con.json
136
- - spec/fixtures/my_data.con.json
137
- - spec/fixtures/post_users.con.json
136
+ - spec/fixtures/get_user_by_id.contract.json
137
+ - spec/fixtures/get_users.contract.json
138
+ - spec/fixtures/post_users.contract.json
138
139
  - spec/fixtures/users/get_users.json
139
140
  - spec/fixtures/users/get_users.xml
140
141
  - spec/fixtures/users/get_users_by_id_id_1.json
@@ -168,10 +169,9 @@ signing_key:
168
169
  specification_version: 4
169
170
  summary: XXX
170
171
  test_files:
171
- - spec/fixtures/get_users.con.json
172
- - spec/fixtures/get_users_by_id.con.json
173
- - spec/fixtures/my_data.con.json
174
- - spec/fixtures/post_users.con.json
172
+ - spec/fixtures/get_user_by_id.contract.json
173
+ - spec/fixtures/get_users.contract.json
174
+ - spec/fixtures/post_users.contract.json
175
175
  - spec/fixtures/users/get_users.json
176
176
  - spec/fixtures/users/get_users.xml
177
177
  - spec/fixtures/users/get_users_by_id_id_1.json
@@ -1,5 +0,0 @@
1
- require File.expand_path('../server', __FILE__) # hack for replacing #require_relative (see config.ru require_relative issue)
2
-
3
- set :run, false
4
- set :environment, :development
5
- run Sinatra::Application
@@ -1,66 +0,0 @@
1
- require 'sinatra/base'
2
-
3
- class Contracto::Server < Sinatra::Base
4
- require_relative '../../stats'
5
-
6
- set :port, Contracto::Constants::PORT
7
-
8
- get '/contracto' do
9
- "*** Contracto server is working! [#{Gem::Specification.find_by_name('contracto').version}] ***"
10
- end
11
-
12
- get '/contracto_terminate' do
13
- Thread.new { sleep 1; Process.kill 'INT', Process.pid }
14
- status 200
15
- body_on_terminate
16
- end
17
-
18
- jsons_with_contracts = Dir["#{Contracto::Config.root_dir}/**/*.con.json"].map do |file_with_contract|
19
- File.read file_with_contract
20
- end
21
-
22
- Contracto::Parser.new(jsons_with_contracts).contracts.each do |contract|
23
- send(contract.http_method, contract.url_pattern) do
24
- begin
25
- contract.response_body(params, http_headers)
26
- rescue StandardError => ex
27
- status 500
28
- error_response(ex)
29
- end
30
- end
31
- end
32
-
33
- def http_headers
34
- env.select {|k,v| k.start_with? 'HTTP_'}
35
- end
36
-
37
- def error_response(ex)
38
- ["#{ex.class}: #{ex.message}", ex.backtrace[0, 15].join("\n")].join("\n")
39
- end
40
-
41
- def body_on_terminate
42
- used_contracts_count = Contracto::Stats.used_contracts.size
43
- all_contracts_count = Contracto::Stats.all_contracts.size
44
- used_responses_count = Contracto::Stats.used_responses.size
45
- all_responses_count = Contracto::Stats.all_responses.size
46
-
47
- contracts_usage = if all_contracts_count.zero?
48
- 'N/A'
49
- else
50
- "#{(used_contracts_count / all_contracts_count.to_f).round(2)}%"
51
- end
52
-
53
- responses_usage = if all_responses_count.zero?
54
- 'N/A'
55
- else
56
- "#{(used_responses_count / all_responses_count.to_f).round(2)}%"
57
- end
58
-
59
- [
60
- "Used contracts: #{used_contracts_count}/#{all_contracts_count} (#{contracts_usage})",
61
- "Used responses: #{used_responses_count}/#{all_responses_count} (#{responses_usage})",
62
- ''
63
- ].join("\n")
64
- end
65
- end
66
-
@@ -1,23 +0,0 @@
1
- {
2
- "request": {
3
- "http_method": "get",
4
- "path": "/my/data",
5
- "meta": {
6
- "request": {
7
-
8
- },
9
- "response": {
10
- "body": {
11
- "type": "object",
12
- "embedded": [
13
- {
14
- "name": "id",
15
- "type": "string"
16
- }
17
- ]
18
- }
19
- }
20
- }
21
- },
22
- "responses": []
23
- }