freckle_io 0.0.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.
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