freckle_io 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +5 -0
  3. data/.env.sample +3 -0
  4. data/.env.test.sample +3 -0
  5. data/.gitignore +5 -0
  6. data/.rubocop.yml +67 -0
  7. data/.travis.yml +20 -0
  8. data/Gemfile +8 -0
  9. data/README.md +95 -0
  10. data/Rakefile +5 -0
  11. data/freckle-io.gemspec +37 -0
  12. data/icon.svg +192 -0
  13. data/lib/freckle_io/authentication.rb +26 -0
  14. data/lib/freckle_io/client/entries.rb +53 -0
  15. data/lib/freckle_io/client/project_groups.rb +36 -0
  16. data/lib/freckle_io/client/projects.rb +42 -0
  17. data/lib/freckle_io/client/tags.rb +33 -0
  18. data/lib/freckle_io/client/users.rb +35 -0
  19. data/lib/freckle_io/configuration.rb +48 -0
  20. data/lib/freckle_io/connection.rb +80 -0
  21. data/lib/freckle_io/errors/configuration.rb +5 -0
  22. data/lib/freckle_io/errors/connection.rb +9 -0
  23. data/lib/freckle_io/errors/params.rb +8 -0
  24. data/lib/freckle_io/paginator.rb +70 -0
  25. data/lib/freckle_io/params.rb +46 -0
  26. data/lib/freckle_io/request/multiple_pages.rb +62 -0
  27. data/lib/freckle_io/request/single_page.rb +73 -0
  28. data/lib/freckle_io/validator/entry.rb +57 -0
  29. data/lib/freckle_io/validator/project.rb +46 -0
  30. data/lib/freckle_io/validator/project_group.rb +30 -0
  31. data/lib/freckle_io/validator/restricted_hash.rb +11 -0
  32. data/lib/freckle_io/validator/tag.rb +30 -0
  33. data/lib/freckle_io/validator/user.rb +43 -0
  34. data/lib/freckle_io/validator/validation.yml +14 -0
  35. data/lib/freckle_io.rb +35 -0
  36. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Entries/with_configuration/_all/result_must_be_a_Request_MultiplePages.yml +48 -0
  37. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Entries/with_configuration/_all/returns_a_entry_for_each_response.yml +48 -0
  38. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Entries/with_configuration/with_valid_params/params_should_be_valid.yml +48 -0
  39. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Entries/with_configuration/with_validator/call_entry_s_validator.yml +48 -0
  40. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_ProjectGroups/with_configuration/_all/result_must_be_a_Request_MultiplePages.yml +99 -0
  41. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_ProjectGroups/with_configuration/_all/returns_a_project_group_for_each_response.yml +99 -0
  42. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_ProjectGroups/with_configuration/with_valid_params/params_should_be_valid.yml +48 -0
  43. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_ProjectGroups/with_configuration/with_validator/call_project_group_s_validator.yml +99 -0
  44. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Projects/with_configuration/_all/result_must_be_a_Request_MultiplePages.yml +491 -0
  45. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Projects/with_configuration/_all/returns_a_project_for_each_response.yml +491 -0
  46. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Projects/with_configuration/_show/get_a_spacific_project.yml +48 -0
  47. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Projects/with_configuration/_show/raw_links_should_be_a_empty_array.yml +48 -0
  48. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Projects/with_configuration/with_valid_params/params_should_be_valid.yml +48 -0
  49. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Projects/with_configuration/with_validator/call_project_s_validator.yml +491 -0
  50. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Tags/with_configuration/_all/result_must_be_a_Request_MultiplePages.yml +48 -0
  51. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Tags/with_configuration/_all/returns_a_tag_for_each_response.yml +48 -0
  52. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Tags/with_configuration/with_valid_params/params_should_be_valid.yml +48 -0
  53. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Tags/with_configuration/with_validator/call_tag_s_validator.yml +48 -0
  54. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Users/with_configuration/_all/result_must_be_a_Request_MultiplePages.yml +99 -0
  55. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Users/with_configuration/_all/returns_a_user_for_each_response.yml +99 -0
  56. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Users/with_configuration/_show/get_a_spacific_user.yml +48 -0
  57. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Users/with_configuration/_show/raw_links_should_be_a_empty_array.yml +48 -0
  58. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Users/with_configuration/with_valid_params/params_should_be_valid.yml +48 -0
  59. data/spec/fixtures/vcr_cassettes/FreckleIO_Client_Users/with_configuration/with_validator/call_user_s_validator.yml +99 -0
  60. data/spec/fixtures/vcr_cassettes/FreckleIO_Connection/with_default_configuration/with_a_valid_response/must_be_a_json_format.yml +51 -0
  61. data/spec/fixtures/vcr_cassettes/FreckleIO_Connection/with_default_configuration/with_freckle_token_authentication/set_X-FreckleToken.yml +51 -0
  62. data/spec/fixtures/vcr_cassettes/FreckleIO_Connection/with_default_configuration/with_header/set_user_agent.yml +51 -0
  63. data/spec/fixtures/vcr_cassettes/FreckleIO_Connection/with_default_configuration/with_invalid_url/raises_a_resource_not_found_error_for_invalid_resource.yml +40 -0
  64. data/spec/fixtures/vcr_cassettes/FreckleIO_Connection/with_default_configuration/with_page_param/response_must_be_success.yml +51 -0
  65. data/spec/fixtures/vcr_cassettes/FreckleIO_Connection/with_default_configuration/with_per_page_param/response_must_have_per_page_elements.yml +51 -0
  66. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_MultiplePages/with_configuration/_get/returns_an_array_of_faraday_response.yml +99 -0
  67. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_MultiplePages/with_configuration/_get/returns_freckle_default_per_page_elements.yml +99 -0
  68. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_MultiplePages/with_per_page/_get/return_per_page_number_elements.yml +148 -0
  69. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_first/next_page_should_be_a_SinglePage.yml +99 -0
  70. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_first/prev_page_should_be_nil.yml +51 -0
  71. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_first/when_get_last_page_from_the_first_page/next_should_be_nil.yml +99 -0
  72. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_get/returns_correct_raw_links_for_the_first_page.yml +51 -0
  73. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_last/next_page_should_be_nil.yml +99 -0
  74. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_last/returns_correct_raw_links_for_the_last_page.yml +99 -0
  75. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_last/when_get_prev_page_from_the_last_page/prev_should_be_nil.yml +147 -0
  76. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_next/previous_page_is_a_SinglePage.yml +147 -0
  77. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_next/previous_page_should_be_not_nil.yml +147 -0
  78. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_prev/next_page_is_a_SinglePage.yml +195 -0
  79. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_prev/when_get_prev_page_from_the_first_page/prev_should_be_nil.yml +147 -0
  80. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_total_page/with_first_page/returns_the_range_of_pages.yml +51 -0
  81. data/spec/fixtures/vcr_cassettes/FreckleIO_Request_SinglePage/with_configuration/_total_page/with_next_page/returns_an_empty_array.yml +99 -0
  82. data/spec/freckle_io/client/entries_spec.rb +359 -0
  83. data/spec/freckle_io/client/project_groups_spec.rb +132 -0
  84. data/spec/freckle_io/client/projects_spec.rb +183 -0
  85. data/spec/freckle_io/client/tags_spec.rb +132 -0
  86. data/spec/freckle_io/client/users_spec.rb +150 -0
  87. data/spec/freckle_io/configuration_spec.rb +70 -0
  88. data/spec/freckle_io/connection_spec.rb +116 -0
  89. data/spec/freckle_io/paginator_spec.rb +105 -0
  90. data/spec/freckle_io/params_spec.rb +15 -0
  91. data/spec/freckle_io/request/multiple_pages_spec.rb +39 -0
  92. data/spec/freckle_io/request/single_page_spec.rb +107 -0
  93. data/spec/spec_helper.rb +69 -0
  94. data/spec/support/anonymize/entries.rb +60 -0
  95. data/spec/support/anonymize/project_groups.rb +55 -0
  96. data/spec/support/anonymize/projects.rb +60 -0
  97. data/spec/support/anonymize/tags.rb +47 -0
  98. data/spec/support/anonymize/users.rb +59 -0
  99. data/spec/support/anonymize_interaction.rb +60 -0
  100. data/spec/support/entry_info.rb +19 -0
  101. data/spec/support/project_group_info.rb +13 -0
  102. data/spec/support/project_info.rb +26 -0
  103. data/spec/support/shared/links/raw_links.rb +13 -0
  104. data/spec/support/tag_info.rb +11 -0
  105. data/spec/support/user_info.rb +21 -0
  106. metadata +382 -0
@@ -0,0 +1,48 @@
1
+ require "freckle_io/authentication"
2
+
3
+ module FreckleIO
4
+ class Configuration
5
+ include Authentication
6
+
7
+ DEFAULT_URL = "https://api.letsfreckle.com/v2".freeze
8
+
9
+ attr_writer :auth_type
10
+ attr_writer :token
11
+ attr_writer :url
12
+ attr_writer :per_page
13
+ attr_writer :max_concurrency
14
+
15
+ def initialize
16
+ @token = nil
17
+ end
18
+
19
+ def auth_type
20
+ unless valid_auth?(@auth_type)
21
+ raise(
22
+ Errors::Configuration,
23
+ "#{@auth_type} isn't valid type authentication"
24
+ )
25
+ end
26
+
27
+ @auth_type || raise(Errors::Configuration, "Authentication type missing!")
28
+ end
29
+
30
+ def token
31
+ @token || raise(Errors::Configuration, "Token missing!")
32
+ end
33
+
34
+ def url
35
+ @url || self.class::DEFAULT_URL
36
+ end
37
+
38
+ def per_page
39
+ @per_page || nil
40
+ end
41
+
42
+ def max_concurrency
43
+ @max_concurrency || raise(
44
+ Errors::Configuration, "Max concurrency missing!"
45
+ )
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,80 @@
1
+ require "freckle_io/authentication"
2
+ require "faraday"
3
+ require "faraday_middleware"
4
+
5
+ module FreckleIO
6
+ class Connection
7
+ include FreckleIO::Authentication
8
+
9
+ # rubocop:disable Metrics/MethodLength
10
+ def get(path, params: {}, request_options: {})
11
+ response = connection.get do |request|
12
+ authorize_request(request)
13
+ set_request_options(request, request_options)
14
+ request.url path, params
15
+ end
16
+
17
+ response
18
+ rescue Faraday::ConnectionFailed => e
19
+ raise Errors::Connection::Failed.new(e), e.message
20
+ rescue Faraday::ResourceNotFound => e
21
+ raise Errors::Connection::ResourceNotFound.new(e), e.message
22
+ rescue Faraday::ClientError => e
23
+ raise Errors::Connection::ClientError.new(e), e.message
24
+ end
25
+ # rubocop:enable Metrics/MethodLength
26
+
27
+ def get_in_parallel(
28
+ path,
29
+ from_page_number,
30
+ to_page_number,
31
+ params: {},
32
+ request_options: {}
33
+ )
34
+ responses = []
35
+
36
+ connection.in_parallel(manager) do
37
+ (from_page_number..to_page_number).each do |page|
38
+ merged_params = {page: page}.merge(params)
39
+
40
+ responses << get(
41
+ path, params: merged_params, request_options: request_options
42
+ )
43
+ end
44
+ end
45
+
46
+ responses
47
+ end
48
+
49
+ private
50
+
51
+ def connection
52
+ @connection ||= Faraday.new(default_options) do |connection|
53
+ connection.request :json
54
+ connection.response :json, content_type: /\bjson$/
55
+ connection.response :raise_error
56
+ connection.adapter :typhoeus
57
+ end
58
+ end
59
+
60
+ def default_options
61
+ {
62
+ url: FreckleIO.configuration.url,
63
+ headers: {
64
+ user_agent: "MyFreckleBot/1.0"
65
+ }
66
+ }
67
+ end
68
+
69
+ def set_request_options(request, options)
70
+ request.options.timeout = options[:timeout]
71
+ request.options.open_timeout = options[:open_timeout]
72
+ end
73
+
74
+ def manager
75
+ @manager ||= Typhoeus::Hydra.new(
76
+ max_concurrency: FreckleIO.configuration.max_concurrency
77
+ )
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,5 @@
1
+ module FreckleIO
2
+ module Errors
3
+ class Configuration < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module FreckleIO
2
+ module Errors
3
+ module Connection
4
+ class Failed < StandardError; end
5
+ class ResourceNotFound < StandardError; end
6
+ class ClientError < StandardError; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module FreckleIO
2
+ module Errors
3
+ module Params
4
+ class InvalidParams < StandardError; end
5
+ class InvalidModule < StandardError; end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,70 @@
1
+ require "uri"
2
+ require "cgi"
3
+
4
+ module FreckleIO
5
+ class Paginator
6
+ attr_reader :raw_links
7
+
8
+ def initialize(raw_links)
9
+ @raw_links = raw_links
10
+ end
11
+
12
+ def next
13
+ find("next")&.dig(:url)
14
+ end
15
+
16
+ def prev
17
+ find("prev")&.dig(:url)
18
+ end
19
+
20
+ def first
21
+ find("first")&.dig(:url)
22
+ end
23
+
24
+ def last
25
+ find("last")&.dig(:url)
26
+ end
27
+
28
+ def total_pages
29
+ find("last")&.dig(:number_page)
30
+ end
31
+
32
+ private
33
+
34
+ def find(rel)
35
+ pages.find { |page| page[:rel] == rel }
36
+ end
37
+
38
+ def pages
39
+ return {} if raw_links.empty?
40
+
41
+ @pages ||=
42
+ raw_links.split(",").map do |link|
43
+ url, rel, number_page = split_and_clean_link(link)
44
+
45
+ {
46
+ url: "#{url.path}?#{url.query}",
47
+ rel: rel,
48
+ number_page: number_page
49
+ }
50
+ end
51
+ end
52
+
53
+ # Example of link response headers:
54
+ # "<https://api.letsfreckle.com/v2/users?page=2>; rel=\"last\""
55
+ #
56
+ # The first element is an URI. "Rel" means the relative position
57
+ # of the current page, in this case is last page.
58
+
59
+ def split_and_clean_link(link)
60
+ raw_url, rel = link.split(";").map(&:strip)
61
+
62
+ raw_url = raw_url.gsub(/<|>/, "")
63
+ rel = rel.gsub(/(rel=)?"/, "")
64
+ url = URI.parse(raw_url)
65
+ page = CGI.parse(url.query)["page"].first
66
+
67
+ [url, rel, page]
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,46 @@
1
+ module FreckleIO
2
+ class Params
3
+ attr_reader :params
4
+ attr_reader :allowed_keys
5
+ attr_reader :validator_module
6
+
7
+ def initialize(
8
+ params,
9
+ allowed_keys,
10
+ validator_module
11
+ )
12
+ @params = params
13
+ @allowed_keys = allowed_keys
14
+ @validator_module = validator_module
15
+ end
16
+
17
+ def call
18
+ return validator.output if valid?
19
+
20
+ raise Errors::Params::InvalidParams.new, validator_messages
21
+ end
22
+
23
+ private
24
+
25
+ def valid?
26
+ validator.errors.empty?
27
+ end
28
+
29
+ def validator
30
+ @validator ||= which_validator_module.validation(params, allowed_keys)
31
+ end
32
+
33
+ def which_validator_module
34
+ Kernel.const_get(validator_module)
35
+ rescue NameError => e
36
+ raise Errors::Params::InvalidModule.new(e), e.message
37
+ end
38
+
39
+ def validator_messages
40
+ messages = validator.messages(full: true)
41
+ return messages.values.join(",") if messages.is_a?(Hash)
42
+
43
+ messages.join(",")
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,62 @@
1
+ require "faraday_middleware"
2
+
3
+ module FreckleIO
4
+ module Request
5
+ class MultiplePages
6
+ attr_reader :path
7
+ attr_reader :params
8
+ attr_reader :last_responses
9
+
10
+ def get(path, params: {})
11
+ @path ||= path
12
+ @params ||= default_params.merge(params).compact
13
+
14
+ retrieve_all_pages
15
+
16
+ self
17
+ end
18
+
19
+ private
20
+
21
+ def retrieve_all_pages
22
+ @last_responses ||= client.get_in_parallel(
23
+ path,
24
+ 2,
25
+ total_pages,
26
+ params: params
27
+ )
28
+
29
+ @last_responses.unshift([@first_response])
30
+ @last_responses.flatten!
31
+ end
32
+
33
+ def total_pages
34
+ @total_pages ||= first_page.total_pages
35
+ end
36
+
37
+ def first_page
38
+ @first_page ||= first_single_page.get(
39
+ path,
40
+ params: params
41
+ )
42
+ @first_response = @first_page.last_response
43
+
44
+ @first_page
45
+ end
46
+
47
+ def first_single_page
48
+ @first_single_page ||= SinglePage.new
49
+ end
50
+
51
+ def client
52
+ @client ||= Connection.new
53
+ end
54
+
55
+ def default_params
56
+ {
57
+ per_page: FreckleIO.configuration.per_page
58
+ }
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,73 @@
1
+ require "freckle_io/paginator"
2
+
3
+ module FreckleIO
4
+ module Request
5
+ class SinglePage
6
+ attr_reader :last_response
7
+ attr_reader :raw_links
8
+
9
+ def initialize
10
+ @last_response = nil
11
+ @raw_links = nil
12
+ end
13
+
14
+ def get(path, params: {}, request_options: {})
15
+ @last_response = client.get(
16
+ path,
17
+ params: params,
18
+ request_options: request_options
19
+ )
20
+
21
+ @raw_links = @last_response.headers["link"] || []
22
+
23
+ self
24
+ end
25
+
26
+ def next
27
+ next? ? get(paginator.next) : nil
28
+ end
29
+
30
+ def next?
31
+ paginator.next
32
+ end
33
+
34
+ def prev
35
+ prev? ? get(paginator.prev) : nil
36
+ end
37
+
38
+ def prev?
39
+ paginator.prev
40
+ end
41
+
42
+ def last
43
+ last? ? get(paginator.last) : nil
44
+ end
45
+
46
+ def last?
47
+ paginator.last
48
+ end
49
+
50
+ def first
51
+ first? ? get(paginator.first) : nil
52
+ end
53
+
54
+ def first?
55
+ paginator.first
56
+ end
57
+
58
+ def total_pages
59
+ paginator.total_pages.to_i
60
+ end
61
+
62
+ private
63
+
64
+ def paginator
65
+ @paginator = Paginator.new(raw_links)
66
+ end
67
+
68
+ def client
69
+ @client = Connection.new
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,57 @@
1
+ module FreckleIO
2
+ module Validator
3
+ module Entry
4
+ # rubocop:disable Metrics/MethodLength,
5
+ # rubocop:disable Metrics/AbcSize,
6
+ # rubocop:disable Lint/NestedMethodDefinition
7
+ # rubocop:disable Metrics/BlockLength,
8
+ def self.validation(params, valid_keys)
9
+ Dry::Validation.Schema do
10
+ configure do
11
+ config.messages_file = File.join(
12
+ __dir__, "validation.yml"
13
+ )
14
+ config.namespace = :entry
15
+
16
+ predicates(RestrictedHash)
17
+
18
+ option :allowed_keys
19
+
20
+ def valid_tag_filter_type
21
+ ["and", "combination of"]
22
+ end
23
+ end
24
+
25
+ restricted_hash?(allowed_keys) do
26
+ optional(:user_ids).filled :str?
27
+ optional(:description).filled :str?
28
+ optional(:project_ids).filled :str?
29
+ optional(:tag_ids).filled :str?
30
+ optional(:tag_filter_type).filled :str?
31
+ optional(
32
+ :tag_filter_type
33
+ ).value(included_in?: valid_tag_filter_type)
34
+ optional(:invoice_ids).filled :str?
35
+ optional(:import_ids).filled :str?
36
+ optional(:from).filled :str?
37
+ optional(:to).filled :str?
38
+ optional(:invoiced).filled :bool?
39
+ optional(:invoiced_at_from).filled :str?
40
+ optional(:invoiced_at_to).filled :str?
41
+ optional(:updated_from).filled :str?
42
+ optional(:updated_to).filled :str?
43
+ optional(:billable).filled :bool?
44
+ optional(:approved_at_from).filled :str?
45
+ optional(:approved_at_to).filled :str?
46
+ optional(:approved_by_ids).filled :str?
47
+ optional(:per_page).filled :int?
48
+ end
49
+ end.with(allowed_keys: valid_keys).call(params)
50
+ end
51
+ # rubocop:enable Metrics/MethodLength,
52
+ # rubocop:enable Metrics/AbcSize,
53
+ # rubocop:enable Lint/NestedMethodDefinition
54
+ # rubocop:enable Metrics/BlockLength,
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,46 @@
1
+ module FreckleIO
2
+ module Validator
3
+ module Project
4
+ # rubocop:disable Metrics/MethodLength,
5
+ # rubocop:disable Metrics/AbcSize,
6
+ # rubocop:disable Lint/NestedMethodDefinition
7
+ def self.validation(params, valid_keys)
8
+ Dry::Validation.Schema do
9
+ configure do
10
+ config.messages_file = File.join(
11
+ __dir__, "validation.yml"
12
+ )
13
+ config.namespace = :project
14
+
15
+ predicates(RestrictedHash)
16
+
17
+ option :allowed_keys
18
+
19
+ def valid_increment
20
+ %w(1 5 6 10 15)
21
+ end
22
+
23
+ def valid_increment?(_options, values)
24
+ values.split(",").all? do |value|
25
+ valid_increment.include?(value)
26
+ end
27
+ end
28
+ end
29
+
30
+ restricted_hash?(allowed_keys) do
31
+ optional(:name).filled :str?
32
+ optional(:project_group_ids).filled :str?
33
+ optional(:billing_increment).filled :str?
34
+ optional(:billing_increment).filled(valid_increment?: {})
35
+ optional(:enabled).filled :bool?
36
+ optional(:billable).filled :bool?
37
+ optional(:per_page).filled :int?
38
+ end
39
+ end.with(allowed_keys: valid_keys).call(params)
40
+ end
41
+ # rubocop:enable Metrics/MethodLength,
42
+ # rubocop:enable Metrics/AbcSize,
43
+ # rubocop:enable Lint/NestedMethodDefinition
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,30 @@
1
+ module FreckleIO
2
+ module Validator
3
+ module ProjectGroup
4
+ # rubocop:disable Metrics/MethodLength,
5
+ # rubocop:disable Metrics/AbcSize,
6
+ def self.validation(params, valid_keys)
7
+ Dry::Validation.Schema do
8
+ configure do
9
+ config.messages_file = File.join(
10
+ __dir__, "validation.yml"
11
+ )
12
+ config.namespace = :project_group
13
+
14
+ predicates(RestrictedHash)
15
+
16
+ option :allowed_keys
17
+ end
18
+
19
+ restricted_hash?(allowed_keys) do
20
+ optional(:name).filled :str?
21
+ optional(:project_ids).filled :str?
22
+ optional(:per_page).filled :int?
23
+ end
24
+ end.with(allowed_keys: valid_keys).call(params)
25
+ end
26
+ # rubocop:enable Metrics/MethodLength,
27
+ # rubocop:enable Metrics/AbcSize,
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ module FreckleIO
2
+ module Validator
3
+ module RestrictedHash
4
+ include Dry::Logic::Predicates
5
+
6
+ predicate(:restricted_hash?) do |allowed_keys, hash|
7
+ (hash.keys - allowed_keys).empty?
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ module FreckleIO
2
+ module Validator
3
+ module Tag
4
+ # rubocop:disable Metrics/MethodLength,
5
+ # rubocop:disable Metrics/AbcSize,
6
+ def self.validation(params, valid_keys)
7
+ Dry::Validation.Schema do
8
+ configure do
9
+ config.messages_file = File.join(
10
+ __dir__, "validation.yml"
11
+ )
12
+ config.namespace = :tag
13
+
14
+ predicates(RestrictedHash)
15
+
16
+ option :allowed_keys
17
+ end
18
+
19
+ restricted_hash?(allowed_keys) do
20
+ optional(:name).filled :str?
21
+ optional(:billable).filled :bool?
22
+ optional(:per_page).filled :int?
23
+ end
24
+ end.with(allowed_keys: valid_keys).call(params)
25
+ end
26
+ # rubocop:enable Metrics/MethodLength,
27
+ # rubocop:enable Metrics/AbcSize,
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ module FreckleIO
2
+ module Validator
3
+ module User
4
+ # rubocop:disable Metrics/MethodLength,
5
+ # rubocop:disable Metrics/AbcSize,
6
+ # rubocop:disable Lint/NestedMethodDefinition
7
+ def self.validation(params, valid_keys)
8
+ Dry::Validation.Schema do
9
+ configure do
10
+ config.messages_file = File.join(
11
+ __dir__, "validation.yml"
12
+ )
13
+ config.namespace = :user
14
+
15
+ predicates(RestrictedHash)
16
+
17
+ option :allowed_keys
18
+
19
+ def valid_roles
20
+ %w(supervisor leader coworker contractor)
21
+ end
22
+
23
+ def valid_states
24
+ %w(disabled pending active suspended)
25
+ end
26
+ end
27
+
28
+ restricted_hash?(allowed_keys) do
29
+ optional(:name).filled :str?
30
+ optional(:email).filled :str?
31
+ optional(:email).value(format?: /\A[^@\s]+@[^@\s]+\z/)
32
+ optional(:role).value(included_in?: valid_roles)
33
+ optional(:state).value(included_in?: valid_states)
34
+ optional(:per_page).filled :int?
35
+ end
36
+ end.with(allowed_keys: valid_keys).call(params)
37
+ end
38
+ # rubocop:enable Metrics/MethodLength,
39
+ # rubocop:enable Metrics/AbcSize,
40
+ # rubocop:enable Lint/NestedMethodDefinition
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,14 @@
1
+ en:
2
+ errors:
3
+ rules:
4
+ user:
5
+ restricted_hash?: "has unknown keys. Allowed keys are: name, email, role, state and per_page"
6
+ project:
7
+ restricted_hash?: "has unknown keys. Allowed keys are: name, project_group_ids, billing_increment, enabled, billable and per_page"
8
+ valid_increment?: "has invalid values. Allowed values are: 1, 5, 6, 10, 15"
9
+ entry:
10
+ restricted_hash?: "has unknown keys. Allowed keys are: user_ids, description, project_ids, tag_ids, tag_filter_type, invoice_ids, import_ids, from, to, invoiced, invoice_at_from, invoice_at_to, updated_from, updated_to, billable, approved_at_from, approved_at_to, approved_by_ids and per_page"
11
+ tag:
12
+ restricted_hash?: "has unknown keys. Allowed keys are: name, billable and per_page"
13
+ project_group:
14
+ restricted_hash?: "has unknown keys. Allowed keys are: name, project ids and per_page"
data/lib/freckle_io.rb ADDED
@@ -0,0 +1,35 @@
1
+ require "dry-validation"
2
+
3
+ require "freckle_io/configuration"
4
+ require "freckle_io/connection"
5
+ require "freckle_io/params"
6
+ require "freckle_io/errors/configuration"
7
+ require "freckle_io/errors/connection"
8
+ require "freckle_io/errors/params"
9
+ require "freckle_io/request/multiple_pages"
10
+ require "freckle_io/request/single_page"
11
+ require "freckle_io/validator/restricted_hash"
12
+ require "freckle_io/validator/user"
13
+ require "freckle_io/validator/project"
14
+ require "freckle_io/validator/entry"
15
+ require "freckle_io/validator/tag"
16
+ require "freckle_io/validator/project_group"
17
+ require "freckle_io/client/users"
18
+ require "freckle_io/client/projects"
19
+ require "freckle_io/client/entries"
20
+ require "freckle_io/client/tags"
21
+ require "freckle_io/client/project_groups"
22
+
23
+ module FreckleIO
24
+ def self.configuration
25
+ @configuration ||= Configuration.new
26
+ end
27
+
28
+ def self.reset
29
+ @configuration = Configuration.new
30
+ end
31
+
32
+ def self.configure
33
+ yield(configuration)
34
+ end
35
+ end