contracto 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
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
- }